summaryrefslogtreecommitdiffstats
path: root/usr.sbin/nginx/src
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/nginx/src')
-rw-r--r--usr.sbin/nginx/src/event/modules/ngx_aio_module.c170
-rw-r--r--usr.sbin/nginx/src/event/modules/ngx_devpoll_module.c568
-rw-r--r--usr.sbin/nginx/src/event/modules/ngx_epoll_module.c774
-rw-r--r--usr.sbin/nginx/src/event/modules/ngx_eventport_module.c601
-rw-r--r--usr.sbin/nginx/src/event/modules/ngx_kqueue_module.c784
-rw-r--r--usr.sbin/nginx/src/event/modules/ngx_poll_module.c442
-rw-r--r--usr.sbin/nginx/src/event/modules/ngx_rtsig_module.c734
-rw-r--r--usr.sbin/nginx/src/event/modules/ngx_select_module.c434
-rw-r--r--usr.sbin/nginx/src/event/modules/ngx_win32_select_module.c399
-rw-r--r--usr.sbin/nginx/src/event/ngx_event.c1274
-rw-r--r--usr.sbin/nginx/src/event/ngx_event.h572
-rw-r--r--usr.sbin/nginx/src/event/ngx_event_accept.c427
-rw-r--r--usr.sbin/nginx/src/event/ngx_event_busy_lock.c285
-rw-r--r--usr.sbin/nginx/src/event/ngx_event_busy_lock.h64
-rw-r--r--usr.sbin/nginx/src/event/ngx_event_connect.c258
-rw-r--r--usr.sbin/nginx/src/event/ngx_event_connect.h75
-rw-r--r--usr.sbin/nginx/src/event/ngx_event_mutex.c69
-rw-r--r--usr.sbin/nginx/src/event/ngx_event_openssl.c2354
-rw-r--r--usr.sbin/nginx/src/event/ngx_event_openssl.h158
-rw-r--r--usr.sbin/nginx/src/event/ngx_event_pipe.c995
-rw-r--r--usr.sbin/nginx/src/event/ngx_event_pipe.h94
-rw-r--r--usr.sbin/nginx/src/event/ngx_event_posted.c172
-rw-r--r--usr.sbin/nginx/src/event/ngx_event_posted.h74
-rw-r--r--usr.sbin/nginx/src/event/ngx_event_timer.c158
-rw-r--r--usr.sbin/nginx/src/event/ngx_event_timer.h101
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_access_module.c383
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_addition_filter_module.c249
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_auth_basic_module.c477
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_autoindex_module.c671
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_browser_module.c712
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_charset_filter_module.c1680
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_chunked_filter_module.c204
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_dav_module.c1140
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_degradation_module.c242
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_empty_gif_module.c146
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_fastcgi_module.c2869
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_flv_module.c253
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_geo_module.c1440
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_geoip_module.c670
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_gzip_filter_module.c1205
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_gzip_static_module.c298
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_headers_filter_module.c612
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_image_filter_module.c1414
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_index_module.c515
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_limit_req_module.c811
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_limit_zone_module.c556
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_log_module.c1375
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_map_module.c574
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_memcached_module.c623
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_not_modified_filter_module.c142
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_proxy_module.c2812
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_random_index_module.c316
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_range_filter_module.c861
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_realip_module.c475
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_referer_module.c612
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_rewrite_module.c1006
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_scgi_module.c1666
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_secure_link_module.c354
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_split_clients_module.c241
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_ssi_filter_module.c2799
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_ssi_filter_module.h107
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_ssl_module.c637
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_ssl_module.h51
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_static_module.c275
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_stub_status_module.c143
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_sub_filter_module.c715
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_upstream_ip_hash_module.c236
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_userid_filter_module.c845
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_uwsgi_module.c1718
-rw-r--r--usr.sbin/nginx/src/http/modules/ngx_http_xslt_filter_module.c983
-rw-r--r--usr.sbin/nginx/src/http/modules/perl/Makefile.PL37
-rw-r--r--usr.sbin/nginx/src/http/modules/perl/nginx.pm133
-rw-r--r--usr.sbin/nginx/src/http/modules/perl/nginx.xs978
-rw-r--r--usr.sbin/nginx/src/http/modules/perl/ngx_http_perl_module.c1075
-rw-r--r--usr.sbin/nginx/src/http/modules/perl/ngx_http_perl_module.h66
-rw-r--r--usr.sbin/nginx/src/http/modules/perl/typemap3
-rw-r--r--usr.sbin/nginx/src/http/ngx_http.c2070
-rw-r--r--usr.sbin/nginx/src/http/ngx_http.h159
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_busy_lock.c306
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_busy_lock.h53
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_cache.h147
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_config.h74
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_copy_filter_module.c294
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_core_module.c4703
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_core_module.h532
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_file_cache.c1776
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_header_filter_module.c626
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_parse.c1719
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_parse_time.c275
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_postpone_filter_module.c177
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_request.c3156
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_request.h571
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_request_body.c638
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_script.c1749
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_script.h256
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_special_response.c779
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_upstream.c4529
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_upstream.h346
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_upstream_round_robin.c781
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_upstream_round_robin.h84
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_variables.c2035
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_variables.h114
-rw-r--r--usr.sbin/nginx/src/http/ngx_http_write_filter_module.c310
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail.c541
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail.h406
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail_auth_http_module.c1451
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail_core_module.c552
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail_handler.c772
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail_imap_handler.c456
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail_imap_module.c252
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail_imap_module.h38
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail_parse.c884
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail_pop3_handler.c499
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail_pop3_module.c263
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail_pop3_module.h37
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail_proxy_module.c1088
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail_smtp_handler.c871
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail_smtp_module.c307
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail_smtp_module.h44
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail_ssl_module.c485
-rw-r--r--usr.sbin/nginx/src/mail/ngx_mail_ssl_module.h51
-rw-r--r--usr.sbin/nginx/src/misc/ngx_cpp_test_module.cpp27
-rw-r--r--usr.sbin/nginx/src/misc/ngx_google_perftools_module.c125
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_aio_read.c108
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_aio_read_chain.c77
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_aio_write.c108
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_aio_write_chain.c99
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_alloc.c89
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_alloc.h44
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_atomic.h310
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_channel.c257
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_channel.h33
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_daemon.c68
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_darwin.h19
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_darwin_config.h93
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_darwin_init.c169
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_darwin_sendfile_chain.c365
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_errno.c86
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_errno.h67
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_file_aio_read.c213
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_files.c555
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_files.h340
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_freebsd.h23
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_freebsd_config.h123
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_freebsd_init.c261
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_freebsd_rfork_thread.c755
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_freebsd_rfork_thread.h121
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_freebsd_sendfile_chain.c430
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_gcc_atomic_amd64.h81
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_gcc_atomic_ppc.h154
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_gcc_atomic_sparc64.h81
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_gcc_atomic_x86.h126
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_linux.h17
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_linux_aio_read.c134
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_linux_config.h121
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_linux_init.c90
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_linux_sendfile_chain.c377
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_os.h83
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_posix_config.h152
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_posix_init.c117
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_process.c584
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_process.h86
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_process_cycle.c1385
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_process_cycle.h60
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_pthread_thread.c277
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_readv_chain.c257
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_recv.c179
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_send.c72
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_setproctitle.c134
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_setproctitle.h51
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_shmem.c125
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_shmem.h28
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_socket.c115
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_socket.h63
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_solaris.h15
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_solaris_config.h106
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_solaris_init.c74
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_solaris_sendfilev_chain.c250
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_sunpro_amd64.il42
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_sunpro_atomic_sparc64.h60
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_sunpro_sparc64.il35
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_sunpro_x86.il43
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_thread.h127
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_time.c103
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_time.h65
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_udp_recv.c114
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_user.c108
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_user.h23
-rw-r--r--usr.sbin/nginx/src/os/unix/ngx_writev_chain.c180
-rw-r--r--usr.sbin/nginx/src/os/unix/rfork_thread.S72
-rw-r--r--usr.sbin/nginx/src/pcre/LICENCE68
-rw-r--r--usr.sbin/nginx/src/pcre/config.h262
-rw-r--r--usr.sbin/nginx/src/pcre/pcre.h346
-rw-r--r--usr.sbin/nginx/src/pcre/pcre_chartables.c198
-rw-r--r--usr.sbin/nginx/src/pcre/pcre_compile.c7462
-rw-r--r--usr.sbin/nginx/src/pcre/pcre_exec.c6468
-rw-r--r--usr.sbin/nginx/src/pcre/pcre_fullinfo.c174
-rw-r--r--usr.sbin/nginx/src/pcre/pcre_globals.c84
-rw-r--r--usr.sbin/nginx/src/pcre/pcre_internal.h1968
-rw-r--r--usr.sbin/nginx/src/pcre/pcre_newline.c162
-rw-r--r--usr.sbin/nginx/src/pcre/pcre_ord2utf8.c87
-rw-r--r--usr.sbin/nginx/src/pcre/pcre_tables.c544
-rw-r--r--usr.sbin/nginx/src/pcre/pcre_try_flipped.c139
-rw-r--r--usr.sbin/nginx/src/pcre/pcre_ucd.c2981
-rw-r--r--usr.sbin/nginx/src/pcre/pcre_valid_utf8.c299
-rw-r--r--usr.sbin/nginx/src/pcre/pcre_xclass.c174
-rw-r--r--usr.sbin/nginx/src/pcre/ucp.h165
207 files changed, 120609 insertions, 0 deletions
diff --git a/usr.sbin/nginx/src/event/modules/ngx_aio_module.c b/usr.sbin/nginx/src/event/modules/ngx_aio_module.c
new file mode 100644
index 00000000000..71f7d40c63c
--- /dev/null
+++ b/usr.sbin/nginx/src/event/modules/ngx_aio_module.c
@@ -0,0 +1,170 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+extern ngx_event_module_t ngx_kqueue_module_ctx;
+
+
+static ngx_int_t ngx_aio_init(ngx_cycle_t *cycle, ngx_msec_t timer);
+static void ngx_aio_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_aio_add_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_aio_del_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_aio_del_connection(ngx_connection_t *c, ngx_uint_t flags);
+static ngx_int_t ngx_aio_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+ ngx_uint_t flags);
+
+
+ngx_os_io_t ngx_os_aio = {
+ ngx_aio_read,
+ ngx_aio_read_chain,
+ NULL,
+ ngx_aio_write,
+ ngx_aio_write_chain,
+ 0
+};
+
+
+static ngx_str_t aio_name = ngx_string("aio");
+
+ngx_event_module_t ngx_aio_module_ctx = {
+ &aio_name,
+ NULL, /* create configuration */
+ NULL, /* init configuration */
+
+ {
+ ngx_aio_add_event, /* add an event */
+ ngx_aio_del_event, /* delete an event */
+ NULL, /* enable an event */
+ NULL, /* disable an event */
+ NULL, /* add an connection */
+ ngx_aio_del_connection, /* delete an connection */
+ NULL, /* process the changes */
+ ngx_aio_process_events, /* process the events */
+ ngx_aio_init, /* init the events */
+ ngx_aio_done /* done the events */
+ }
+
+};
+
+ngx_module_t ngx_aio_module = {
+ NGX_MODULE_V1,
+ &ngx_aio_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_EVENT_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+#if (NGX_HAVE_KQUEUE)
+
+static ngx_int_t
+ngx_aio_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+{
+ if (ngx_kqueue_module_ctx.actions.init(cycle, timer) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ ngx_io = ngx_os_aio;
+
+ ngx_event_flags = NGX_USE_AIO_EVENT;
+ ngx_event_actions = ngx_aio_module_ctx.actions;
+
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_aio_done(ngx_cycle_t *cycle)
+{
+ ngx_kqueue_module_ctx.actions.done(cycle);
+}
+
+
+/* the event adding and deleting are needed for the listening sockets */
+
+static ngx_int_t
+ngx_aio_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+ return ngx_kqueue_module_ctx.actions.add(ev, event, flags);
+}
+
+
+static ngx_int_t
+ngx_aio_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+ return ngx_kqueue_module_ctx.actions.del(ev, event, flags);
+}
+
+
+static ngx_int_t
+ngx_aio_del_connection(ngx_connection_t *c, ngx_uint_t flags)
+{
+ int rc;
+
+ if (c->read->active == 0 && c->write->active == 0) {
+ return NGX_OK;
+ }
+
+ if (flags & NGX_CLOSE_EVENT) {
+ return NGX_OK;
+ }
+
+ rc = aio_cancel(c->fd, NULL);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "aio_cancel: %d", rc);
+
+ if (rc == AIO_CANCELED) {
+ c->read->active = 0;
+ c->write->active = 0;
+ return NGX_OK;
+ }
+
+ if (rc == AIO_ALLDONE) {
+ c->read->active = 0;
+ c->write->active = 0;
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "aio_cancel() returned AIO_ALLDONE");
+ return NGX_OK;
+ }
+
+ if (rc == -1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+ "aio_cancel() failed");
+ return NGX_ERROR;
+ }
+
+ if (rc == AIO_NOTCANCELED) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "aio_cancel() returned AIO_NOTCANCELED");
+
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_aio_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
+{
+ return ngx_kqueue_module_ctx.actions.process_events(cycle, timer, flags);
+}
+
+#endif /* NGX_HAVE_KQUEUE */
diff --git a/usr.sbin/nginx/src/event/modules/ngx_devpoll_module.c b/usr.sbin/nginx/src/event/modules/ngx_devpoll_module.c
new file mode 100644
index 00000000000..e88c999b861
--- /dev/null
+++ b/usr.sbin/nginx/src/event/modules/ngx_devpoll_module.c
@@ -0,0 +1,568 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (NGX_TEST_BUILD_DEVPOLL)
+
+/* Solaris declarations */
+
+#define POLLREMOVE 0x0800
+#define DP_POLL 0xD001
+#define DP_ISPOLLED 0xD002
+
+struct dvpoll {
+ struct pollfd *dp_fds;
+ int dp_nfds;
+ int dp_timeout;
+};
+
+#endif
+
+
+typedef struct {
+ ngx_uint_t changes;
+ ngx_uint_t events;
+} ngx_devpoll_conf_t;
+
+
+static ngx_int_t ngx_devpoll_init(ngx_cycle_t *cycle, ngx_msec_t timer);
+static void ngx_devpoll_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_devpoll_add_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_devpoll_del_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_devpoll_set_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_devpoll_process_events(ngx_cycle_t *cycle,
+ ngx_msec_t timer, ngx_uint_t flags);
+
+static void *ngx_devpoll_create_conf(ngx_cycle_t *cycle);
+static char *ngx_devpoll_init_conf(ngx_cycle_t *cycle, void *conf);
+
+static int dp = -1;
+static struct pollfd *change_list, *event_list;
+static ngx_uint_t nchanges, max_changes, nevents;
+
+static ngx_event_t **change_index;
+
+
+static ngx_str_t devpoll_name = ngx_string("/dev/poll");
+
+static ngx_command_t ngx_devpoll_commands[] = {
+
+ { ngx_string("devpoll_changes"),
+ NGX_EVENT_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ 0,
+ offsetof(ngx_devpoll_conf_t, changes),
+ NULL },
+
+ { ngx_string("devpoll_events"),
+ NGX_EVENT_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ 0,
+ offsetof(ngx_devpoll_conf_t, events),
+ NULL },
+
+ ngx_null_command
+};
+
+
+ngx_event_module_t ngx_devpoll_module_ctx = {
+ &devpoll_name,
+ ngx_devpoll_create_conf, /* create configuration */
+ ngx_devpoll_init_conf, /* init configuration */
+
+ {
+ ngx_devpoll_add_event, /* add an event */
+ ngx_devpoll_del_event, /* delete an event */
+ ngx_devpoll_add_event, /* enable an event */
+ ngx_devpoll_del_event, /* disable an event */
+ NULL, /* add an connection */
+ NULL, /* delete an connection */
+ NULL, /* process the changes */
+ ngx_devpoll_process_events, /* process the events */
+ ngx_devpoll_init, /* init the events */
+ ngx_devpoll_done, /* done the events */
+ }
+
+};
+
+ngx_module_t ngx_devpoll_module = {
+ NGX_MODULE_V1,
+ &ngx_devpoll_module_ctx, /* module context */
+ ngx_devpoll_commands, /* module directives */
+ NGX_EVENT_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_devpoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+{
+ size_t n;
+ ngx_devpoll_conf_t *dpcf;
+
+ dpcf = ngx_event_get_conf(cycle->conf_ctx, ngx_devpoll_module);
+
+ if (dp == -1) {
+ dp = open("/dev/poll", O_RDWR);
+
+ if (dp == -1) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+ "open(/dev/poll) failed");
+ return NGX_ERROR;
+ }
+ }
+
+ if (max_changes < dpcf->changes) {
+ if (nchanges) {
+ n = nchanges * sizeof(struct pollfd);
+ if (write(dp, change_list, n) != (ssize_t) n) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "write(/dev/poll) failed");
+ return NGX_ERROR;
+ }
+
+ nchanges = 0;
+ }
+
+ if (change_list) {
+ ngx_free(change_list);
+ }
+
+ change_list = ngx_alloc(sizeof(struct pollfd) * dpcf->changes,
+ cycle->log);
+ if (change_list == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (change_index) {
+ ngx_free(change_index);
+ }
+
+ change_index = ngx_alloc(sizeof(ngx_event_t *) * dpcf->changes,
+ cycle->log);
+ if (change_index == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ max_changes = dpcf->changes;
+
+ if (nevents < dpcf->events) {
+ if (event_list) {
+ ngx_free(event_list);
+ }
+
+ event_list = ngx_alloc(sizeof(struct pollfd) * dpcf->events,
+ cycle->log);
+ if (event_list == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ nevents = dpcf->events;
+
+ ngx_io = ngx_os_io;
+
+ ngx_event_actions = ngx_devpoll_module_ctx.actions;
+
+ ngx_event_flags = NGX_USE_LEVEL_EVENT|NGX_USE_FD_EVENT;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_devpoll_done(ngx_cycle_t *cycle)
+{
+ if (close(dp) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "close(/dev/poll) failed");
+ }
+
+ dp = -1;
+
+ ngx_free(change_list);
+ ngx_free(event_list);
+ ngx_free(change_index);
+
+ change_list = NULL;
+ event_list = NULL;
+ change_index = NULL;
+ max_changes = 0;
+ nchanges = 0;
+ nevents = 0;
+}
+
+
+static ngx_int_t
+ngx_devpoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+#if (NGX_DEBUG)
+ ngx_connection_t *c;
+#endif
+
+#if (NGX_READ_EVENT != POLLIN)
+ event = (event == NGX_READ_EVENT) ? POLLIN : POLLOUT;
+#endif
+
+#if (NGX_DEBUG)
+ c = ev->data;
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "devpoll add event: fd:%d ev:%04Xi", c->fd, event);
+#endif
+
+ ev->active = 1;
+
+ return ngx_devpoll_set_event(ev, event, 0);
+}
+
+
+static ngx_int_t
+ngx_devpoll_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+ ngx_event_t *e;
+ ngx_connection_t *c;
+
+ c = ev->data;
+
+#if (NGX_READ_EVENT != POLLIN)
+ event = (event == NGX_READ_EVENT) ? POLLIN : POLLOUT;
+#endif
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "devpoll del event: fd:%d ev:%04Xi", c->fd, event);
+
+ if (ngx_devpoll_set_event(ev, POLLREMOVE, flags) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ ev->active = 0;
+
+ if (flags & NGX_CLOSE_EVENT) {
+ e = (event == POLLIN) ? c->write : c->read;
+
+ if (e) {
+ e->active = 0;
+ }
+
+ return NGX_OK;
+ }
+
+ /* restore the pair event if it exists */
+
+ if (event == POLLIN) {
+ e = c->write;
+ event = POLLOUT;
+
+ } else {
+ e = c->read;
+ event = POLLIN;
+ }
+
+ if (e && e->active) {
+ return ngx_devpoll_set_event(e, event, 0);
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_devpoll_set_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+ size_t n;
+ ngx_connection_t *c;
+
+ c = ev->data;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "devpoll fd:%d ev:%04Xi fl:%04Xi", c->fd, event, flags);
+
+ if (nchanges >= max_changes) {
+ ngx_log_error(NGX_LOG_WARN, ev->log, 0,
+ "/dev/pool change list is filled up");
+
+ n = nchanges * sizeof(struct pollfd);
+ if (write(dp, change_list, n) != (ssize_t) n) {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+ "write(/dev/poll) failed");
+ return NGX_ERROR;
+ }
+
+ nchanges = 0;
+ }
+
+ change_list[nchanges].fd = c->fd;
+ change_list[nchanges].events = (short) event;
+ change_list[nchanges].revents = 0;
+
+ change_index[nchanges] = ev;
+ ev->index = nchanges;
+
+ nchanges++;
+
+ if (flags & NGX_CLOSE_EVENT) {
+ n = nchanges * sizeof(struct pollfd);
+ if (write(dp, change_list, n) != (ssize_t) n) {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+ "write(/dev/poll) failed");
+ return NGX_ERROR;
+ }
+
+ nchanges = 0;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_devpoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+ ngx_uint_t flags)
+{
+ int events, revents, rc;
+ size_t n;
+ ngx_fd_t fd;
+ ngx_err_t err;
+ ngx_int_t i;
+ ngx_uint_t level;
+ ngx_event_t *rev, *wev, **queue;
+ ngx_connection_t *c;
+ struct pollfd pfd;
+ struct dvpoll dvp;
+
+ /* NGX_TIMER_INFINITE == INFTIM */
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "devpoll timer: %M", timer);
+
+ if (nchanges) {
+ n = nchanges * sizeof(struct pollfd);
+ if (write(dp, change_list, n) != (ssize_t) n) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "write(/dev/poll) failed");
+ return NGX_ERROR;
+ }
+
+ nchanges = 0;
+ }
+
+ dvp.dp_fds = event_list;
+ dvp.dp_nfds = (int) nevents;
+ dvp.dp_timeout = timer;
+ events = ioctl(dp, DP_POLL, &dvp);
+
+ err = (events == -1) ? ngx_errno : 0;
+
+ if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
+ ngx_time_update();
+ }
+
+ if (err) {
+ if (err == NGX_EINTR) {
+
+ if (ngx_event_timer_alarm) {
+ ngx_event_timer_alarm = 0;
+ return NGX_OK;
+ }
+
+ level = NGX_LOG_INFO;
+
+ } else {
+ level = NGX_LOG_ALERT;
+ }
+
+ ngx_log_error(level, cycle->log, err, "ioctl(DP_POLL) failed");
+ return NGX_ERROR;
+ }
+
+ if (events == 0) {
+ if (timer != NGX_TIMER_INFINITE) {
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "ioctl(DP_POLL) returned no events without timeout");
+ return NGX_ERROR;
+ }
+
+ ngx_mutex_lock(ngx_posted_events_mutex);
+
+ for (i = 0; i < events; i++) {
+
+ fd = event_list[i].fd;
+ revents = event_list[i].revents;
+
+ c = ngx_cycle->files[fd];
+
+ if (c == NULL || c->fd == -1) {
+
+ pfd.fd = fd;
+ pfd.events = 0;
+ pfd.revents = 0;
+
+ rc = ioctl(dp, DP_ISPOLLED, &pfd);
+
+ switch (rc) {
+
+ case -1:
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "ioctl(DP_ISPOLLED) failed for socket %d, event",
+ fd, revents);
+ break;
+
+ case 0:
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "phantom event %04Xd for closed and removed socket %d",
+ revents, fd);
+ break;
+
+ default:
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "unexpected event %04Xd for closed and removed socket %d, ",
+ "ioctl(DP_ISPOLLED) returned rc:%d, fd:%d, event %04Xd",
+ revents, fd, rc, pfd.fd, pfd.revents);
+
+ pfd.fd = fd;
+ pfd.events = POLLREMOVE;
+ pfd.revents = 0;
+
+ if (write(dp, &pfd, sizeof(struct pollfd))
+ != (ssize_t) sizeof(struct pollfd))
+ {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "write(/dev/poll) for %d failed, fd");
+ }
+
+ if (close(fd) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "close(%d) failed", fd);
+ }
+
+ break;
+ }
+
+ continue;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "devpoll: fd:%d, ev:%04Xd, rev:%04Xd",
+ fd, event_list[i].events, revents);
+
+ if (revents & (POLLERR|POLLHUP|POLLNVAL)) {
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "ioctl(DP_POLL) error fd:%d ev:%04Xd rev:%04Xd",
+ fd, event_list[i].events, revents);
+ }
+
+ if (revents & ~(POLLIN|POLLOUT|POLLERR|POLLHUP|POLLNVAL)) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "strange ioctl(DP_POLL) events "
+ "fd:%d ev:%04Xd rev:%04Xd",
+ fd, event_list[i].events, revents);
+ }
+
+ if ((revents & (POLLERR|POLLHUP|POLLNVAL))
+ && (revents & (POLLIN|POLLOUT)) == 0)
+ {
+ /*
+ * if the error events were returned without POLLIN or POLLOUT,
+ * then add these flags to handle the events at least in one
+ * active handler
+ */
+
+ revents |= POLLIN|POLLOUT;
+ }
+
+ rev = c->read;
+
+ if ((revents & POLLIN) && rev->active) {
+
+ if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) {
+ rev->posted_ready = 1;
+
+ } else {
+ rev->ready = 1;
+ }
+
+ if (flags & NGX_POST_EVENTS) {
+ queue = (ngx_event_t **) (rev->accept ?
+ &ngx_posted_accept_events : &ngx_posted_events);
+
+ ngx_locked_post_event(rev, queue);
+
+ } else {
+ rev->handler(rev);
+ }
+ }
+
+ wev = c->write;
+
+ if ((revents & POLLOUT) && wev->active) {
+
+ if (flags & NGX_POST_THREAD_EVENTS) {
+ wev->posted_ready = 1;
+
+ } else {
+ wev->ready = 1;
+ }
+
+ if (flags & NGX_POST_EVENTS) {
+ ngx_locked_post_event(wev, &ngx_posted_events);
+
+ } else {
+ wev->handler(wev);
+ }
+ }
+ }
+
+ ngx_mutex_unlock(ngx_posted_events_mutex);
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_devpoll_create_conf(ngx_cycle_t *cycle)
+{
+ ngx_devpoll_conf_t *dpcf;
+
+ dpcf = ngx_palloc(cycle->pool, sizeof(ngx_devpoll_conf_t));
+ if (dpcf == NULL) {
+ return NULL;
+ }
+
+ dpcf->changes = NGX_CONF_UNSET;
+ dpcf->events = NGX_CONF_UNSET;
+
+ return dpcf;
+}
+
+
+static char *
+ngx_devpoll_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+ ngx_devpoll_conf_t *dpcf = conf;
+
+ ngx_conf_init_uint_value(dpcf->changes, 32);
+ ngx_conf_init_uint_value(dpcf->events, 32);
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/event/modules/ngx_epoll_module.c b/usr.sbin/nginx/src/event/modules/ngx_epoll_module.c
new file mode 100644
index 00000000000..e30501b00e1
--- /dev/null
+++ b/usr.sbin/nginx/src/event/modules/ngx_epoll_module.c
@@ -0,0 +1,774 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (NGX_TEST_BUILD_EPOLL)
+
+/* epoll declarations */
+
+#define EPOLLIN 0x001
+#define EPOLLPRI 0x002
+#define EPOLLOUT 0x004
+#define EPOLLRDNORM 0x040
+#define EPOLLRDBAND 0x080
+#define EPOLLWRNORM 0x100
+#define EPOLLWRBAND 0x200
+#define EPOLLMSG 0x400
+#define EPOLLERR 0x008
+#define EPOLLHUP 0x010
+
+#define EPOLLET 0x80000000
+#define EPOLLONESHOT 0x40000000
+
+#define EPOLL_CTL_ADD 1
+#define EPOLL_CTL_DEL 2
+#define EPOLL_CTL_MOD 3
+
+typedef union epoll_data {
+ void *ptr;
+ int fd;
+ uint32_t u32;
+ uint64_t u64;
+} epoll_data_t;
+
+struct epoll_event {
+ uint32_t events;
+ epoll_data_t data;
+};
+
+int epoll_create(int size)
+{
+ return -1;
+}
+
+int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
+{
+ return -1;
+}
+
+int epoll_wait(int epfd, struct epoll_event *events, int nevents, int timeout)
+{
+ return -1;
+}
+
+#if (NGX_HAVE_FILE_AIO)
+
+#define SYS_io_setup 245
+#define SYS_io_destroy 246
+#define SYS_io_getevents 247
+#define SYS_eventfd 323
+
+typedef u_int aio_context_t;
+
+struct io_event {
+ uint64_t data; /* the data field from the iocb */
+ uint64_t obj; /* what iocb this event came from */
+ int64_t res; /* result code for this event */
+ int64_t res2; /* secondary result */
+};
+
+
+int eventfd(u_int initval)
+{
+ return -1;
+}
+
+#endif
+#endif
+
+
+typedef struct {
+ ngx_uint_t events;
+} ngx_epoll_conf_t;
+
+
+static ngx_int_t ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer);
+static void ngx_epoll_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_epoll_del_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_epoll_add_connection(ngx_connection_t *c);
+static ngx_int_t ngx_epoll_del_connection(ngx_connection_t *c,
+ ngx_uint_t flags);
+static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+ ngx_uint_t flags);
+
+#if (NGX_HAVE_FILE_AIO)
+static void ngx_epoll_eventfd_handler(ngx_event_t *ev);
+#endif
+
+static void *ngx_epoll_create_conf(ngx_cycle_t *cycle);
+static char *ngx_epoll_init_conf(ngx_cycle_t *cycle, void *conf);
+
+static int ep = -1;
+static struct epoll_event *event_list;
+static ngx_uint_t nevents;
+
+#if (NGX_HAVE_FILE_AIO)
+
+int ngx_eventfd = -1;
+aio_context_t ngx_aio_ctx = 0;
+
+static ngx_event_t ngx_eventfd_event;
+static ngx_connection_t ngx_eventfd_conn;
+
+#endif
+
+static ngx_str_t epoll_name = ngx_string("epoll");
+
+static ngx_command_t ngx_epoll_commands[] = {
+
+ { ngx_string("epoll_events"),
+ NGX_EVENT_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ 0,
+ offsetof(ngx_epoll_conf_t, events),
+ NULL },
+
+ ngx_null_command
+};
+
+
+ngx_event_module_t ngx_epoll_module_ctx = {
+ &epoll_name,
+ ngx_epoll_create_conf, /* create configuration */
+ ngx_epoll_init_conf, /* init configuration */
+
+ {
+ ngx_epoll_add_event, /* add an event */
+ ngx_epoll_del_event, /* delete an event */
+ ngx_epoll_add_event, /* enable an event */
+ ngx_epoll_del_event, /* disable an event */
+ ngx_epoll_add_connection, /* add an connection */
+ ngx_epoll_del_connection, /* delete an connection */
+ NULL, /* process the changes */
+ ngx_epoll_process_events, /* process the events */
+ ngx_epoll_init, /* init the events */
+ ngx_epoll_done, /* done the events */
+ }
+};
+
+ngx_module_t ngx_epoll_module = {
+ NGX_MODULE_V1,
+ &ngx_epoll_module_ctx, /* module context */
+ ngx_epoll_commands, /* module directives */
+ NGX_EVENT_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+/*
+ * We call io_setup(), io_destroy() io_submit(), and io_getevents() directly
+ * as syscalls instead of libaio usage, because the library header file
+ * supports eventfd() since 0.3.107 version only.
+ *
+ * Also we do not use eventfd() in glibc, because glibc supports it
+ * since 2.8 version and glibc maps two syscalls eventfd() and eventfd2()
+ * into single eventfd() function with different number of parameters.
+ */
+
+static long
+io_setup(u_int nr_reqs, aio_context_t *ctx)
+{
+ return syscall(SYS_io_setup, nr_reqs, ctx);
+}
+
+
+static int
+io_destroy(aio_context_t ctx)
+{
+ return syscall(SYS_io_destroy, ctx);
+}
+
+
+static long
+io_getevents(aio_context_t ctx, long min_nr, long nr, struct io_event *events,
+ struct timespec *tmo)
+{
+ return syscall(SYS_io_getevents, ctx, min_nr, nr, events, tmo);
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+{
+ ngx_epoll_conf_t *epcf;
+
+ epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);
+
+ if (ep == -1) {
+ ep = epoll_create(cycle->connection_n / 2);
+
+ if (ep == -1) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+ "epoll_create() failed");
+ return NGX_ERROR;
+ }
+
+#if (NGX_HAVE_FILE_AIO)
+ {
+ int n;
+ struct epoll_event ee;
+
+ ngx_eventfd = syscall(SYS_eventfd, 0);
+
+ if (ngx_eventfd == -1) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+ "eventfd() failed");
+ return NGX_ERROR;
+ }
+
+ n = 1;
+
+ if (ioctl(ngx_eventfd, FIONBIO, &n) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+ "ioctl(eventfd, FIONBIO) failed");
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "eventfd: %d", ngx_eventfd);
+
+ n = io_setup(1024, &ngx_aio_ctx);
+
+ if (n != 0) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, -n, "io_setup() failed");
+ return NGX_ERROR;
+ }
+
+ ngx_eventfd_event.data = &ngx_eventfd_conn;
+ ngx_eventfd_event.handler = ngx_epoll_eventfd_handler;
+ ngx_eventfd_event.log = cycle->log;
+ ngx_eventfd_event.active = 1;
+ ngx_eventfd_conn.fd = ngx_eventfd;
+ ngx_eventfd_conn.read = &ngx_eventfd_event;
+ ngx_eventfd_conn.log = cycle->log;
+
+ ee.events = EPOLLIN|EPOLLET;
+ ee.data.ptr = &ngx_eventfd_conn;
+
+ if (epoll_ctl(ep, EPOLL_CTL_ADD, ngx_eventfd, &ee) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+ "epoll_ctl(EPOLL_CTL_ADD, eventfd) failed");
+ return NGX_ERROR;
+ }
+ }
+#endif
+ }
+
+ if (nevents < epcf->events) {
+ if (event_list) {
+ ngx_free(event_list);
+ }
+
+ event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,
+ cycle->log);
+ if (event_list == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ nevents = epcf->events;
+
+ ngx_io = ngx_os_io;
+
+ ngx_event_actions = ngx_epoll_module_ctx.actions;
+
+#if (NGX_HAVE_CLEAR_EVENT)
+ ngx_event_flags = NGX_USE_CLEAR_EVENT
+#else
+ ngx_event_flags = NGX_USE_LEVEL_EVENT
+#endif
+ |NGX_USE_GREEDY_EVENT
+ |NGX_USE_EPOLL_EVENT;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_epoll_done(ngx_cycle_t *cycle)
+{
+ if (close(ep) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "epoll close() failed");
+ }
+
+ ep = -1;
+
+#if (NGX_HAVE_FILE_AIO)
+
+ if (io_destroy(ngx_aio_ctx) != 0) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "io_destroy() failed");
+ }
+
+ ngx_aio_ctx = 0;
+
+#endif
+
+ ngx_free(event_list);
+
+ event_list = NULL;
+ nevents = 0;
+}
+
+
+static ngx_int_t
+ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+ int op;
+ uint32_t events, prev;
+ ngx_event_t *e;
+ ngx_connection_t *c;
+ struct epoll_event ee;
+
+ c = ev->data;
+
+ events = (uint32_t) event;
+
+ if (event == NGX_READ_EVENT) {
+ e = c->write;
+ prev = EPOLLOUT;
+#if (NGX_READ_EVENT != EPOLLIN)
+ events = EPOLLIN;
+#endif
+
+ } else {
+ e = c->read;
+ prev = EPOLLIN;
+#if (NGX_WRITE_EVENT != EPOLLOUT)
+ events = EPOLLOUT;
+#endif
+ }
+
+ if (e->active) {
+ op = EPOLL_CTL_MOD;
+ events |= prev;
+
+ } else {
+ op = EPOLL_CTL_ADD;
+ }
+
+ ee.events = events | (uint32_t) flags;
+ ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "epoll add event: fd:%d op:%d ev:%08XD",
+ c->fd, op, ee.events);
+
+ if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+ "epoll_ctl(%d, %d) failed", op, c->fd);
+ return NGX_ERROR;
+ }
+
+ ev->active = 1;
+#if 0
+ ev->oneshot = (flags & NGX_ONESHOT_EVENT) ? 1 : 0;
+#endif
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_epoll_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+ int op;
+ uint32_t prev;
+ ngx_event_t *e;
+ ngx_connection_t *c;
+ struct epoll_event ee;
+
+ /*
+ * when the file descriptor is closed, the epoll automatically deletes
+ * it from its queue, so we do not need to delete explicity the event
+ * before the closing the file descriptor
+ */
+
+ if (flags & NGX_CLOSE_EVENT) {
+ ev->active = 0;
+ return NGX_OK;
+ }
+
+ c = ev->data;
+
+ if (event == NGX_READ_EVENT) {
+ e = c->write;
+ prev = EPOLLOUT;
+
+ } else {
+ e = c->read;
+ prev = EPOLLIN;
+ }
+
+ if (e->active) {
+ op = EPOLL_CTL_MOD;
+ ee.events = prev | (uint32_t) flags;
+ ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);
+
+ } else {
+ op = EPOLL_CTL_DEL;
+ ee.events = 0;
+ ee.data.ptr = NULL;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "epoll del event: fd:%d op:%d ev:%08XD",
+ c->fd, op, ee.events);
+
+ if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+ "epoll_ctl(%d, %d) failed", op, c->fd);
+ return NGX_ERROR;
+ }
+
+ ev->active = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_epoll_add_connection(ngx_connection_t *c)
+{
+ struct epoll_event ee;
+
+ ee.events = EPOLLIN|EPOLLOUT|EPOLLET;
+ ee.data.ptr = (void *) ((uintptr_t) c | c->read->instance);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "epoll add connection: fd:%d ev:%08XD", c->fd, ee.events);
+
+ if (epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+ "epoll_ctl(EPOLL_CTL_ADD, %d) failed", c->fd);
+ return NGX_ERROR;
+ }
+
+ c->read->active = 1;
+ c->write->active = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_epoll_del_connection(ngx_connection_t *c, ngx_uint_t flags)
+{
+ int op;
+ struct epoll_event ee;
+
+ /*
+ * when the file descriptor is closed the epoll automatically deletes
+ * it from its queue so we do not need to delete explicity the event
+ * before the closing the file descriptor
+ */
+
+ if (flags & NGX_CLOSE_EVENT) {
+ c->read->active = 0;
+ c->write->active = 0;
+ return NGX_OK;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "epoll del connection: fd:%d", c->fd);
+
+ op = EPOLL_CTL_DEL;
+ ee.events = 0;
+ ee.data.ptr = NULL;
+
+ if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+ "epoll_ctl(%d, %d) failed", op, c->fd);
+ return NGX_ERROR;
+ }
+
+ c->read->active = 0;
+ c->write->active = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
+{
+ int events;
+ uint32_t revents;
+ ngx_int_t instance, i;
+ ngx_uint_t level;
+ ngx_err_t err;
+ ngx_event_t *rev, *wev, **queue;
+ ngx_connection_t *c;
+
+ /* NGX_TIMER_INFINITE == INFTIM */
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "epoll timer: %M", timer);
+
+ events = epoll_wait(ep, event_list, (int) nevents, timer);
+
+ err = (events == -1) ? ngx_errno : 0;
+
+ if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
+ ngx_time_update();
+ }
+
+ if (err) {
+ if (err == NGX_EINTR) {
+
+ if (ngx_event_timer_alarm) {
+ ngx_event_timer_alarm = 0;
+ return NGX_OK;
+ }
+
+ level = NGX_LOG_INFO;
+
+ } else {
+ level = NGX_LOG_ALERT;
+ }
+
+ ngx_log_error(level, cycle->log, err, "epoll_wait() failed");
+ return NGX_ERROR;
+ }
+
+ if (events == 0) {
+ if (timer != NGX_TIMER_INFINITE) {
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "epoll_wait() returned no events without timeout");
+ return NGX_ERROR;
+ }
+
+ ngx_mutex_lock(ngx_posted_events_mutex);
+
+ for (i = 0; i < events; i++) {
+ c = event_list[i].data.ptr;
+
+ instance = (uintptr_t) c & 1;
+ c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
+
+ rev = c->read;
+
+ if (c->fd == -1 || rev->instance != instance) {
+
+ /*
+ * the stale event from a file descriptor
+ * that was just closed in this iteration
+ */
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "epoll: stale event %p", c);
+ continue;
+ }
+
+ revents = event_list[i].events;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "epoll: fd:%d ev:%04XD d:%p",
+ c->fd, revents, event_list[i].data.ptr);
+
+ if (revents & (EPOLLERR|EPOLLHUP)) {
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "epoll_wait() error on fd:%d ev:%04XD",
+ c->fd, revents);
+ }
+
+#if 0
+ if (revents & ~(EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP)) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "strange epoll_wait() events fd:%d ev:%04XD",
+ c->fd, revents);
+ }
+#endif
+
+ if ((revents & (EPOLLERR|EPOLLHUP))
+ && (revents & (EPOLLIN|EPOLLOUT)) == 0)
+ {
+ /*
+ * if the error events were returned without EPOLLIN or EPOLLOUT,
+ * then add these flags to handle the events at least in one
+ * active handler
+ */
+
+ revents |= EPOLLIN|EPOLLOUT;
+ }
+
+ if ((revents & EPOLLIN) && rev->active) {
+
+ if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) {
+ rev->posted_ready = 1;
+
+ } else {
+ rev->ready = 1;
+ }
+
+ if (flags & NGX_POST_EVENTS) {
+ queue = (ngx_event_t **) (rev->accept ?
+ &ngx_posted_accept_events : &ngx_posted_events);
+
+ ngx_locked_post_event(rev, queue);
+
+ } else {
+ rev->handler(rev);
+ }
+ }
+
+ wev = c->write;
+
+ if ((revents & EPOLLOUT) && wev->active) {
+
+ if (flags & NGX_POST_THREAD_EVENTS) {
+ wev->posted_ready = 1;
+
+ } else {
+ wev->ready = 1;
+ }
+
+ if (flags & NGX_POST_EVENTS) {
+ ngx_locked_post_event(wev, &ngx_posted_events);
+
+ } else {
+ wev->handler(wev);
+ }
+ }
+ }
+
+ ngx_mutex_unlock(ngx_posted_events_mutex);
+
+ return NGX_OK;
+}
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+static void
+ngx_epoll_eventfd_handler(ngx_event_t *ev)
+{
+ int n;
+ long i, events;
+ uint64_t ready;
+ ngx_err_t err;
+ ngx_event_t *e;
+ ngx_event_aio_t *aio;
+ struct io_event event[64];
+ struct timespec ts;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "eventfd handler");
+
+ n = read(ngx_eventfd, &ready, 8);
+
+ err = ngx_errno;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0, "eventfd: %d", n);
+
+ if (n != 8) {
+ if (n == -1) {
+ if (err == NGX_EAGAIN) {
+ return;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, ev->log, err, "read(eventfd) failed");
+ return;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+ "read(eventfd) returned only %d bytes", n);
+ return;
+ }
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+
+ while (ready) {
+
+ events = io_getevents(ngx_aio_ctx, 1, 64, event, &ts);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "io_getevents: %l", events);
+
+ if (events > 0) {
+ ready -= events;
+
+ for (i = 0; i < events; i++) {
+
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "io_event: %uXL %uXL %L %L",
+ event[i].data, event[i].obj,
+ event[i].res, event[i].res2);
+
+ e = (ngx_event_t *) (uintptr_t) event[i].data;
+
+ e->complete = 1;
+ e->active = 0;
+ e->ready = 1;
+
+ aio = e->data;
+ aio->res = event[i].res;
+
+ ngx_post_event(e, &ngx_posted_events);
+ }
+
+ continue;
+ }
+
+ if (events == 0) {
+ return;
+ }
+
+ /* events < 0 */
+ ngx_log_error(NGX_LOG_ALERT, ev->log, -events, "io_getevents() failed");
+ return;
+ }
+}
+
+#endif
+
+
+static void *
+ngx_epoll_create_conf(ngx_cycle_t *cycle)
+{
+ ngx_epoll_conf_t *epcf;
+
+ epcf = ngx_palloc(cycle->pool, sizeof(ngx_epoll_conf_t));
+ if (epcf == NULL) {
+ return NULL;
+ }
+
+ epcf->events = NGX_CONF_UNSET;
+
+ return epcf;
+}
+
+
+static char *
+ngx_epoll_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+ ngx_epoll_conf_t *epcf = conf;
+
+ ngx_conf_init_uint_value(epcf->events, 512);
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/event/modules/ngx_eventport_module.c b/usr.sbin/nginx/src/event/modules/ngx_eventport_module.c
new file mode 100644
index 00000000000..f4f0119a597
--- /dev/null
+++ b/usr.sbin/nginx/src/event/modules/ngx_eventport_module.c
@@ -0,0 +1,601 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (NGX_TEST_BUILD_EVENTPORT)
+
+#define ushort_t u_short
+#define uint_t u_int
+
+/* Solaris declarations */
+
+#define PORT_SOURCE_AIO 1
+#define PORT_SOURCE_TIMER 2
+#define PORT_SOURCE_USER 3
+#define PORT_SOURCE_FD 4
+#define PORT_SOURCE_ALERT 5
+#define PORT_SOURCE_MQ 6
+
+#define ETIME 64
+
+#define SIGEV_PORT 4
+
+typedef struct {
+ int portev_events; /* event data is source specific */
+ ushort_t portev_source; /* event source */
+ ushort_t portev_pad; /* port internal use */
+ uintptr_t portev_object; /* source specific object */
+ void *portev_user; /* user cookie */
+} port_event_t;
+
+typedef struct port_notify {
+ int portnfy_port; /* bind request(s) to port */
+ void *portnfy_user; /* user defined */
+} port_notify_t;
+
+#if (__FreeBSD_version < 700005)
+
+typedef struct itimerspec { /* definition per POSIX.4 */
+ struct timespec it_interval;/* timer period */
+ struct timespec it_value; /* timer expiration */
+} itimerspec_t;
+
+#endif
+
+int port_create(void)
+{
+ return -1;
+}
+
+int port_associate(int port, int source, uintptr_t object, int events,
+ void *user)
+{
+ return -1;
+}
+
+int port_dissociate(int port, int source, uintptr_t object)
+{
+ return -1;
+}
+
+int port_getn(int port, port_event_t list[], uint_t max, uint_t *nget,
+ struct timespec *timeout)
+{
+ return -1;
+}
+
+int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
+{
+ return -1;
+}
+
+int timer_settime(timer_t timerid, int flags, const struct itimerspec *value,
+ struct itimerspec *ovalue)
+{
+ return -1;
+}
+
+int timer_delete(timer_t timerid)
+{
+ return -1;
+}
+
+#endif
+
+
+typedef struct {
+ ngx_uint_t events;
+} ngx_eventport_conf_t;
+
+
+static ngx_int_t ngx_eventport_init(ngx_cycle_t *cycle, ngx_msec_t timer);
+static void ngx_eventport_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_eventport_add_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_eventport_del_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_eventport_process_events(ngx_cycle_t *cycle,
+ ngx_msec_t timer, ngx_uint_t flags);
+
+static void *ngx_eventport_create_conf(ngx_cycle_t *cycle);
+static char *ngx_eventport_init_conf(ngx_cycle_t *cycle, void *conf);
+
+static int ep = -1;
+static port_event_t *event_list;
+static ngx_uint_t nevents;
+static timer_t event_timer = (timer_t) -1;
+
+static ngx_str_t eventport_name = ngx_string("eventport");
+
+
+static ngx_command_t ngx_eventport_commands[] = {
+
+ { ngx_string("eventport_events"),
+ NGX_EVENT_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ 0,
+ offsetof(ngx_eventport_conf_t, events),
+ NULL },
+
+ ngx_null_command
+};
+
+
+ngx_event_module_t ngx_eventport_module_ctx = {
+ &eventport_name,
+ ngx_eventport_create_conf, /* create configuration */
+ ngx_eventport_init_conf, /* init configuration */
+
+ {
+ ngx_eventport_add_event, /* add an event */
+ ngx_eventport_del_event, /* delete an event */
+ ngx_eventport_add_event, /* enable an event */
+ ngx_eventport_del_event, /* disable an event */
+ NULL, /* add an connection */
+ NULL, /* delete an connection */
+ NULL, /* process the changes */
+ ngx_eventport_process_events, /* process the events */
+ ngx_eventport_init, /* init the events */
+ ngx_eventport_done, /* done the events */
+ }
+
+};
+
+ngx_module_t ngx_eventport_module = {
+ NGX_MODULE_V1,
+ &ngx_eventport_module_ctx, /* module context */
+ ngx_eventport_commands, /* module directives */
+ NGX_EVENT_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_eventport_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+{
+ port_notify_t pn;
+ struct itimerspec its;
+ struct sigevent sev;
+ ngx_eventport_conf_t *epcf;
+
+ epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_eventport_module);
+
+ if (ep == -1) {
+ ep = port_create();
+
+ if (ep == -1) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+ "port_create() failed");
+ return NGX_ERROR;
+ }
+ }
+
+ if (nevents < epcf->events) {
+ if (event_list) {
+ ngx_free(event_list);
+ }
+
+ event_list = ngx_alloc(sizeof(port_event_t) * epcf->events,
+ cycle->log);
+ if (event_list == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ ngx_event_flags = NGX_USE_EVENTPORT_EVENT;
+
+ if (timer) {
+ ngx_memzero(&pn, sizeof(port_notify_t));
+ pn.portnfy_port = ep;
+
+ ngx_memzero(&sev, sizeof(struct sigevent));
+ sev.sigev_notify = SIGEV_PORT;
+#if !(NGX_TEST_BUILD_EVENTPORT)
+ sev.sigev_value.sival_ptr = &pn;
+#endif
+
+ if (timer_create(CLOCK_REALTIME, &sev, &event_timer) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+ "timer_create() failed");
+ return NGX_ERROR;
+ }
+
+ its.it_interval.tv_sec = timer / 1000;
+ its.it_interval.tv_nsec = (timer % 1000) * 1000000;
+ its.it_value.tv_sec = timer / 1000;
+ its.it_value.tv_nsec = (timer % 1000) * 1000000;
+
+ if (timer_settime(event_timer, 0, &its, NULL) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+ "timer_settime() failed");
+ return NGX_ERROR;
+ }
+
+ ngx_event_flags |= NGX_USE_TIMER_EVENT;
+ }
+
+ nevents = epcf->events;
+
+ ngx_io = ngx_os_io;
+
+ ngx_event_actions = ngx_eventport_module_ctx.actions;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_eventport_done(ngx_cycle_t *cycle)
+{
+ if (event_timer != (timer_t) -1) {
+ if (timer_delete(event_timer) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "timer_delete() failed");
+ }
+
+ event_timer = (timer_t) -1;
+ }
+
+ if (close(ep) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "close() event port failed");
+ }
+
+ ep = -1;
+
+ ngx_free(event_list);
+
+ event_list = NULL;
+ nevents = 0;
+}
+
+
+static ngx_int_t
+ngx_eventport_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+ ngx_int_t events, prev;
+ ngx_event_t *e;
+ ngx_connection_t *c;
+
+ c = ev->data;
+
+ events = event;
+
+ if (event == NGX_READ_EVENT) {
+ e = c->write;
+ prev = POLLOUT;
+#if (NGX_READ_EVENT != POLLIN)
+ events = POLLIN;
+#endif
+
+ } else {
+ e = c->read;
+ prev = POLLIN;
+#if (NGX_WRITE_EVENT != POLLOUT)
+ events = POLLOUT;
+#endif
+ }
+
+ if (e->oneshot) {
+ events |= prev;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "eventport add event: fd:%d ev:%04Xi", c->fd, events);
+
+ if (port_associate(ep, PORT_SOURCE_FD, c->fd, events,
+ (void *) ((uintptr_t) ev | ev->instance))
+ == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+ "port_associate() failed");
+ return NGX_ERROR;
+ }
+
+ ev->active = 1;
+ ev->oneshot = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_eventport_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+ ngx_event_t *e;
+ ngx_connection_t *c;
+
+ /*
+ * when the file descriptor is closed, the event port automatically
+ * dissociates it from the port, so we do not need to dissociate explicity
+ * the event before the closing the file descriptor
+ */
+
+ if (flags & NGX_CLOSE_EVENT) {
+ ev->active = 0;
+ ev->oneshot = 0;
+ return NGX_OK;
+ }
+
+ c = ev->data;
+
+ if (event == NGX_READ_EVENT) {
+ e = c->write;
+ event = POLLOUT;
+
+ } else {
+ e = c->read;
+ event = POLLIN;
+ }
+
+ if (e->oneshot) {
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "eventport change event: fd:%d ev:%04Xi", c->fd, event);
+
+ if (port_associate(ep, PORT_SOURCE_FD, c->fd, event,
+ (void *) ((uintptr_t) ev | ev->instance))
+ == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+ "port_associate() failed");
+ return NGX_ERROR;
+ }
+
+ } else {
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "eventport del event: fd:%d", c->fd);
+
+ if (port_dissociate(ep, PORT_SOURCE_FD, c->fd) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+ "port_dissociate() failed");
+ return NGX_ERROR;
+ }
+ }
+
+ ev->active = 0;
+ ev->oneshot = 0;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_eventport_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+ ngx_uint_t flags)
+{
+ int n, revents;
+ u_int events;
+ ngx_err_t err;
+ ngx_int_t instance;
+ ngx_uint_t i, level;
+ ngx_event_t *ev, *rev, *wev, **queue;
+ ngx_connection_t *c;
+ struct timespec ts, *tp;
+
+ if (timer == NGX_TIMER_INFINITE) {
+ tp = NULL;
+
+ } else {
+ ts.tv_sec = timer / 1000;
+ ts.tv_nsec = (timer % 1000) * 1000000;
+ tp = &ts;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "eventport timer: %M", timer);
+
+ events = 1;
+
+ n = port_getn(ep, event_list, (u_int) nevents, &events, tp);
+
+ err = ngx_errno;
+
+ if (flags & NGX_UPDATE_TIME) {
+ ngx_time_update();
+ }
+
+ if (n == -1) {
+ if (err == ETIME) {
+ if (timer != NGX_TIMER_INFINITE) {
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "port_getn() returned no events without timeout");
+ return NGX_ERROR;
+ }
+
+ level = (err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT;
+ ngx_log_error(level, cycle->log, err, "port_getn() failed");
+ return NGX_ERROR;
+ }
+
+ if (events == 0) {
+ if (timer != NGX_TIMER_INFINITE) {
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "port_getn() returned no events without timeout");
+ return NGX_ERROR;
+ }
+
+ ngx_mutex_lock(ngx_posted_events_mutex);
+
+ for (i = 0; i < events; i++) {
+
+ if (event_list[i].portev_source == PORT_SOURCE_TIMER) {
+ ngx_time_update();
+ continue;
+ }
+
+ ev = event_list[i].portev_user;
+
+ switch (event_list[i].portev_source) {
+
+ case PORT_SOURCE_FD:
+
+ instance = (uintptr_t) ev & 1;
+ ev = (ngx_event_t *) ((uintptr_t) ev & (uintptr_t) ~1);
+
+ if (ev->closed || ev->instance != instance) {
+
+ /*
+ * the stale event from a file descriptor
+ * that was just closed in this iteration
+ */
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "eventport: stale event %p", ev);
+ continue;
+ }
+
+ revents = event_list[i].portev_events;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "eventport: fd:%d, ev:%04Xd",
+ event_list[i].portev_object, revents);
+
+ if (revents & (POLLERR|POLLHUP|POLLNVAL)) {
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "port_getn() error fd:%d ev:%04Xd",
+ event_list[i].portev_object, revents);
+ }
+
+ if (revents & ~(POLLIN|POLLOUT|POLLERR|POLLHUP|POLLNVAL)) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "strange port_getn() events fd:%d ev:%04Xd",
+ event_list[i].portev_object, revents);
+ }
+
+ if ((revents & (POLLERR|POLLHUP|POLLNVAL))
+ && (revents & (POLLIN|POLLOUT)) == 0)
+ {
+ /*
+ * if the error events were returned without POLLIN or POLLOUT,
+ * then add these flags to handle the events at least in one
+ * active handler
+ */
+
+ revents |= POLLIN|POLLOUT;
+ }
+
+ c = ev->data;
+ rev = c->read;
+ wev = c->write;
+
+ rev->active = 0;
+ wev->active = 0;
+
+ if (revents & POLLIN) {
+
+ if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) {
+ rev->posted_ready = 1;
+
+ } else {
+ rev->ready = 1;
+ }
+
+ if (flags & NGX_POST_EVENTS) {
+ queue = (ngx_event_t **) (rev->accept ?
+ &ngx_posted_accept_events : &ngx_posted_events);
+
+ ngx_locked_post_event(rev, queue);
+
+ } else {
+ rev->handler(rev);
+
+ if (ev->closed) {
+ continue;
+ }
+ }
+
+ if (rev->accept) {
+ if (ngx_use_accept_mutex) {
+ ngx_accept_events = 1;
+ continue;
+ }
+
+ if (port_associate(ep, PORT_SOURCE_FD, c->fd, POLLIN,
+ (void *) ((uintptr_t) ev | ev->instance))
+ == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+ "port_associate() failed");
+ return NGX_ERROR;
+ }
+ }
+ }
+
+ if (revents & POLLOUT) {
+
+ if (flags & NGX_POST_THREAD_EVENTS) {
+ wev->posted_ready = 1;
+
+ } else {
+ wev->ready = 1;
+ }
+
+ if (flags & NGX_POST_EVENTS) {
+ ngx_locked_post_event(wev, &ngx_posted_events);
+
+ } else {
+ wev->handler(wev);
+ }
+ }
+
+ continue;
+
+ default:
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "unexpected even_port object %d",
+ event_list[i].portev_object);
+ continue;
+ }
+ }
+
+ ngx_mutex_unlock(ngx_posted_events_mutex);
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_eventport_create_conf(ngx_cycle_t *cycle)
+{
+ ngx_eventport_conf_t *epcf;
+
+ epcf = ngx_palloc(cycle->pool, sizeof(ngx_eventport_conf_t));
+ if (epcf == NULL) {
+ return NULL;
+ }
+
+ epcf->events = NGX_CONF_UNSET;
+
+ return epcf;
+}
+
+
+static char *
+ngx_eventport_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+ ngx_eventport_conf_t *epcf = conf;
+
+ ngx_conf_init_uint_value(epcf->events, 32);
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/event/modules/ngx_kqueue_module.c b/usr.sbin/nginx/src/event/modules/ngx_kqueue_module.c
new file mode 100644
index 00000000000..d78e56259d3
--- /dev/null
+++ b/usr.sbin/nginx/src/event/modules/ngx_kqueue_module.c
@@ -0,0 +1,784 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+typedef struct {
+ ngx_uint_t changes;
+ ngx_uint_t events;
+} ngx_kqueue_conf_t;
+
+
+static ngx_int_t ngx_kqueue_init(ngx_cycle_t *cycle, ngx_msec_t timer);
+static void ngx_kqueue_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_kqueue_add_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_kqueue_del_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_kqueue_set_event(ngx_event_t *ev, ngx_int_t filter,
+ ngx_uint_t flags);
+static ngx_int_t ngx_kqueue_process_changes(ngx_cycle_t *cycle, ngx_uint_t try);
+static ngx_int_t ngx_kqueue_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+ ngx_uint_t flags);
+static ngx_inline void ngx_kqueue_dump_event(ngx_log_t *log,
+ struct kevent *kev);
+
+static void *ngx_kqueue_create_conf(ngx_cycle_t *cycle);
+static char *ngx_kqueue_init_conf(ngx_cycle_t *cycle, void *conf);
+
+
+int ngx_kqueue = -1;
+
+/*
+ * The "change_list" should be declared as ngx_thread_volatile.
+ * However, the use of the change_list is localized in kqueue functions and
+ * is protected by the mutex so even the "icc -ipo" should not build the code
+ * with the race condition. Thus we avoid the declaration to make a more
+ * readable code.
+ */
+
+static struct kevent *change_list, *change_list0, *change_list1;
+static struct kevent *event_list;
+static ngx_uint_t max_changes, nchanges, nevents;
+
+#if (NGX_THREADS)
+static ngx_mutex_t *list_mutex;
+static ngx_mutex_t *kevent_mutex;
+#endif
+
+
+
+static ngx_str_t kqueue_name = ngx_string("kqueue");
+
+static ngx_command_t ngx_kqueue_commands[] = {
+
+ { ngx_string("kqueue_changes"),
+ NGX_EVENT_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ 0,
+ offsetof(ngx_kqueue_conf_t, changes),
+ NULL },
+
+ { ngx_string("kqueue_events"),
+ NGX_EVENT_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ 0,
+ offsetof(ngx_kqueue_conf_t, events),
+ NULL },
+
+ ngx_null_command
+};
+
+
+ngx_event_module_t ngx_kqueue_module_ctx = {
+ &kqueue_name,
+ ngx_kqueue_create_conf, /* create configuration */
+ ngx_kqueue_init_conf, /* init configuration */
+
+ {
+ ngx_kqueue_add_event, /* add an event */
+ ngx_kqueue_del_event, /* delete an event */
+ ngx_kqueue_add_event, /* enable an event */
+ ngx_kqueue_del_event, /* disable an event */
+ NULL, /* add an connection */
+ NULL, /* delete an connection */
+ ngx_kqueue_process_changes, /* process the changes */
+ ngx_kqueue_process_events, /* process the events */
+ ngx_kqueue_init, /* init the events */
+ ngx_kqueue_done /* done the events */
+ }
+
+};
+
+ngx_module_t ngx_kqueue_module = {
+ NGX_MODULE_V1,
+ &ngx_kqueue_module_ctx, /* module context */
+ ngx_kqueue_commands, /* module directives */
+ NGX_EVENT_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_kqueue_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+{
+ ngx_kqueue_conf_t *kcf;
+ struct timespec ts;
+#if (NGX_HAVE_TIMER_EVENT)
+ struct kevent kev;
+#endif
+
+ kcf = ngx_event_get_conf(cycle->conf_ctx, ngx_kqueue_module);
+
+ if (ngx_kqueue == -1) {
+ ngx_kqueue = kqueue();
+
+ if (ngx_kqueue == -1) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+ "kqueue() failed");
+ return NGX_ERROR;
+ }
+
+#if (NGX_THREADS)
+
+ list_mutex = ngx_mutex_init(cycle->log, 0);
+ if (list_mutex == NULL) {
+ return NGX_ERROR;
+ }
+
+ kevent_mutex = ngx_mutex_init(cycle->log, 0);
+ if (kevent_mutex == NULL) {
+ return NGX_ERROR;
+ }
+
+#endif
+ }
+
+ if (max_changes < kcf->changes) {
+ if (nchanges) {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+
+ if (kevent(ngx_kqueue, change_list, (int) nchanges, NULL, 0, &ts)
+ == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "kevent() failed");
+ return NGX_ERROR;
+ }
+ nchanges = 0;
+ }
+
+ if (change_list0) {
+ ngx_free(change_list0);
+ }
+
+ change_list0 = ngx_alloc(kcf->changes * sizeof(struct kevent),
+ cycle->log);
+ if (change_list0 == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (change_list1) {
+ ngx_free(change_list1);
+ }
+
+ change_list1 = ngx_alloc(kcf->changes * sizeof(struct kevent),
+ cycle->log);
+ if (change_list1 == NULL) {
+ return NGX_ERROR;
+ }
+
+ change_list = change_list0;
+ }
+
+ max_changes = kcf->changes;
+
+ if (nevents < kcf->events) {
+ if (event_list) {
+ ngx_free(event_list);
+ }
+
+ event_list = ngx_alloc(kcf->events * sizeof(struct kevent), cycle->log);
+ if (event_list == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ ngx_event_flags = NGX_USE_ONESHOT_EVENT
+ |NGX_USE_KQUEUE_EVENT
+ |NGX_USE_VNODE_EVENT;
+
+#if (NGX_HAVE_TIMER_EVENT)
+
+ if (timer) {
+ kev.ident = 0;
+ kev.filter = EVFILT_TIMER;
+ kev.flags = EV_ADD|EV_ENABLE;
+ kev.fflags = 0;
+ kev.data = timer;
+ kev.udata = 0;
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+
+ if (kevent(ngx_kqueue, &kev, 1, NULL, 0, &ts) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "kevent(EVFILT_TIMER) failed");
+ return NGX_ERROR;
+ }
+
+ ngx_event_flags |= NGX_USE_TIMER_EVENT;
+ }
+
+#endif
+
+#if (NGX_HAVE_CLEAR_EVENT)
+ ngx_event_flags |= NGX_USE_CLEAR_EVENT;
+#else
+ ngx_event_flags |= NGX_USE_LEVEL_EVENT;
+#endif
+
+#if (NGX_HAVE_LOWAT_EVENT)
+ ngx_event_flags |= NGX_USE_LOWAT_EVENT;
+#endif
+
+ nevents = kcf->events;
+
+ ngx_io = ngx_os_io;
+
+ ngx_event_actions = ngx_kqueue_module_ctx.actions;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_kqueue_done(ngx_cycle_t *cycle)
+{
+ if (close(ngx_kqueue) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "kqueue close() failed");
+ }
+
+ ngx_kqueue = -1;
+
+#if (NGX_THREADS)
+ ngx_mutex_destroy(kevent_mutex);
+ ngx_mutex_destroy(list_mutex);
+#endif
+
+ ngx_free(change_list1);
+ ngx_free(change_list0);
+ ngx_free(event_list);
+
+ change_list1 = NULL;
+ change_list0 = NULL;
+ change_list = NULL;
+ event_list = NULL;
+ max_changes = 0;
+ nchanges = 0;
+ nevents = 0;
+}
+
+
+static ngx_int_t
+ngx_kqueue_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+ ngx_int_t rc;
+#if 0
+ ngx_event_t *e;
+ ngx_connection_t *c;
+#endif
+
+ ev->active = 1;
+ ev->disabled = 0;
+ ev->oneshot = (flags & NGX_ONESHOT_EVENT) ? 1 : 0;
+
+ ngx_mutex_lock(list_mutex);
+
+#if 0
+
+ if (ev->index < nchanges
+ && ((uintptr_t) change_list[ev->index].udata & (uintptr_t) ~1)
+ == (uintptr_t) ev)
+ {
+ if (change_list[ev->index].flags == EV_DISABLE) {
+
+ /*
+ * if the EV_DISABLE is still not passed to a kernel
+ * we will not pass it
+ */
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "kevent activated: %d: ft:%i",
+ ngx_event_ident(ev->data), event);
+
+ if (ev->index < --nchanges) {
+ e = (ngx_event_t *)
+ ((uintptr_t) change_list[nchanges].udata & (uintptr_t) ~1);
+ change_list[ev->index] = change_list[nchanges];
+ e->index = ev->index;
+ }
+
+ ngx_mutex_unlock(list_mutex);
+
+ return NGX_OK;
+ }
+
+ c = ev->data;
+
+ ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+ "previous event on #%d were not passed in kernel", c->fd);
+
+ ngx_mutex_unlock(list_mutex);
+
+ return NGX_ERROR;
+ }
+
+#endif
+
+ rc = ngx_kqueue_set_event(ev, event, EV_ADD|EV_ENABLE|flags);
+
+ ngx_mutex_unlock(list_mutex);
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_kqueue_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+ ngx_int_t rc;
+ ngx_event_t *e;
+
+ ev->active = 0;
+ ev->disabled = 0;
+
+ ngx_mutex_lock(list_mutex);
+
+ if (ev->index < nchanges
+ && ((uintptr_t) change_list[ev->index].udata & (uintptr_t) ~1)
+ == (uintptr_t) ev)
+ {
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "kevent deleted: %d: ft:%i",
+ ngx_event_ident(ev->data), event);
+
+ /* if the event is still not passed to a kernel we will not pass it */
+
+ nchanges--;
+
+ if (ev->index < nchanges) {
+ e = (ngx_event_t *)
+ ((uintptr_t) change_list[nchanges].udata & (uintptr_t) ~1);
+ change_list[ev->index] = change_list[nchanges];
+ e->index = ev->index;
+ }
+
+ ngx_mutex_unlock(list_mutex);
+
+ return NGX_OK;
+ }
+
+ /*
+ * when the file descriptor is closed the kqueue automatically deletes
+ * its filters so we do not need to delete explicity the event
+ * before the closing the file descriptor.
+ */
+
+ if (flags & NGX_CLOSE_EVENT) {
+ ngx_mutex_unlock(list_mutex);
+ return NGX_OK;
+ }
+
+ if (flags & NGX_DISABLE_EVENT) {
+ ev->disabled = 1;
+
+ } else {
+ flags |= EV_DELETE;
+ }
+
+ rc = ngx_kqueue_set_event(ev, event, flags);
+
+ ngx_mutex_unlock(list_mutex);
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_kqueue_set_event(ngx_event_t *ev, ngx_int_t filter, ngx_uint_t flags)
+{
+ struct kevent *kev;
+ struct timespec ts;
+ ngx_connection_t *c;
+
+ c = ev->data;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "kevent set event: %d: ft:%i fl:%04Xi",
+ c->fd, filter, flags);
+
+ if (nchanges >= max_changes) {
+ ngx_log_error(NGX_LOG_WARN, ev->log, 0,
+ "kqueue change list is filled up");
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+
+ if (kevent(ngx_kqueue, change_list, (int) nchanges, NULL, 0, &ts)
+ == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, "kevent() failed");
+ return NGX_ERROR;
+ }
+
+ nchanges = 0;
+ }
+
+ kev = &change_list[nchanges];
+
+ kev->ident = c->fd;
+ kev->filter = (short) filter;
+ kev->flags = (u_short) flags;
+ kev->udata = NGX_KQUEUE_UDATA_T ((uintptr_t) ev | ev->instance);
+
+ if (filter == EVFILT_VNODE) {
+ kev->fflags = NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND
+ |NOTE_ATTRIB|NOTE_RENAME
+#if (__FreeBSD__ == 4 && __FreeBSD_version >= 430000) \
+ || __FreeBSD_version >= 500018
+ |NOTE_REVOKE
+#endif
+ ;
+ kev->data = 0;
+
+ } else {
+#if (NGX_HAVE_LOWAT_EVENT)
+ if (flags & NGX_LOWAT_EVENT) {
+ kev->fflags = NOTE_LOWAT;
+ kev->data = ev->available;
+
+ } else {
+ kev->fflags = 0;
+ kev->data = 0;
+ }
+#else
+ kev->fflags = 0;
+ kev->data = 0;
+#endif
+ }
+
+ ev->index = nchanges;
+ nchanges++;
+
+ if (flags & NGX_FLUSH_EVENT) {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "kevent flush");
+
+ if (kevent(ngx_kqueue, change_list, (int) nchanges, NULL, 0, &ts)
+ == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, "kevent() failed");
+ return NGX_ERROR;
+ }
+
+ nchanges = 0;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_kqueue_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+ ngx_uint_t flags)
+{
+ int events, n;
+ ngx_int_t i, instance;
+ ngx_uint_t level;
+ ngx_err_t err;
+ ngx_event_t *ev, **queue;
+ struct timespec ts, *tp;
+
+ if (ngx_threaded) {
+ if (ngx_kqueue_process_changes(cycle, 0) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ n = 0;
+
+ } else {
+ n = (int) nchanges;
+ nchanges = 0;
+ }
+
+ if (timer == NGX_TIMER_INFINITE) {
+ tp = NULL;
+
+ } else {
+
+ ts.tv_sec = timer / 1000;
+ ts.tv_nsec = (timer % 1000) * 1000000;
+
+ /*
+ * 64-bit Darwin kernel has the bug: kernel level ts.tv_nsec is
+ * the int32_t while user level ts.tv_nsec is the long (64-bit),
+ * so on the big endian PowerPC all nanoseconds are lost.
+ */
+
+#if (NGX_DARWIN_KEVENT_BUG)
+ ts.tv_nsec <<= 32;
+#endif
+
+ tp = &ts;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "kevent timer: %M, changes: %d", timer, n);
+
+ events = kevent(ngx_kqueue, change_list, n, event_list, (int) nevents, tp);
+
+ err = (events == -1) ? ngx_errno : 0;
+
+ if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
+ ngx_time_update();
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "kevent events: %d", events);
+
+ if (err) {
+ if (err == NGX_EINTR) {
+
+ if (ngx_event_timer_alarm) {
+ ngx_event_timer_alarm = 0;
+ return NGX_OK;
+ }
+
+ level = NGX_LOG_INFO;
+
+ } else {
+ level = NGX_LOG_ALERT;
+ }
+
+ ngx_log_error(level, cycle->log, err, "kevent() failed");
+ return NGX_ERROR;
+ }
+
+ if (events == 0) {
+ if (timer != NGX_TIMER_INFINITE) {
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "kevent() returned no events without timeout");
+ return NGX_ERROR;
+ }
+
+ ngx_mutex_lock(ngx_posted_events_mutex);
+
+ for (i = 0; i < events; i++) {
+
+ ngx_kqueue_dump_event(cycle->log, &event_list[i]);
+
+ if (event_list[i].flags & EV_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, event_list[i].data,
+ "kevent() error on %d filter:%d flags:%04Xd",
+ event_list[i].ident, event_list[i].filter,
+ event_list[i].flags);
+ continue;
+ }
+
+#if (NGX_HAVE_TIMER_EVENT)
+
+ if (event_list[i].filter == EVFILT_TIMER) {
+ ngx_time_update();
+ continue;
+ }
+
+#endif
+
+ ev = (ngx_event_t *) event_list[i].udata;
+
+ switch (event_list[i].filter) {
+
+ case EVFILT_READ:
+ case EVFILT_WRITE:
+
+ instance = (uintptr_t) ev & 1;
+ ev = (ngx_event_t *) ((uintptr_t) ev & (uintptr_t) ~1);
+
+ if (ev->closed || ev->instance != instance) {
+
+ /*
+ * the stale event from a file descriptor
+ * that was just closed in this iteration
+ */
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "kevent: stale event %p", ev);
+ continue;
+ }
+
+ if (ev->log && (ev->log->log_level & NGX_LOG_DEBUG_CONNECTION)) {
+ ngx_kqueue_dump_event(ev->log, &event_list[i]);
+ }
+
+ if (ev->oneshot) {
+ ev->active = 0;
+ }
+
+#if (NGX_THREADS)
+
+ if ((flags & NGX_POST_THREAD_EVENTS) && !ev->accept) {
+ ev->posted_ready = 1;
+ ev->posted_available = event_list[i].data;
+
+ if (event_list[i].flags & EV_EOF) {
+ ev->posted_eof = 1;
+ ev->posted_errno = event_list[i].fflags;
+ }
+
+ ngx_locked_post_event(ev, &ngx_posted_events);
+
+ continue;
+ }
+
+#endif
+
+ ev->available = event_list[i].data;
+
+ if (event_list[i].flags & EV_EOF) {
+ ev->pending_eof = 1;
+ ev->kq_errno = event_list[i].fflags;
+ }
+
+ ev->ready = 1;
+
+ break;
+
+ case EVFILT_VNODE:
+ ev->kq_vnode = 1;
+
+ break;
+
+ case EVFILT_AIO:
+ ev->complete = 1;
+ ev->ready = 1;
+
+ break;
+
+ default:
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "unexpected kevent() filter %d",
+ event_list[i].filter);
+ continue;
+ }
+
+ if (flags & NGX_POST_EVENTS) {
+ queue = (ngx_event_t **) (ev->accept ? &ngx_posted_accept_events:
+ &ngx_posted_events);
+ ngx_locked_post_event(ev, queue);
+
+ continue;
+ }
+
+ ev->handler(ev);
+ }
+
+ ngx_mutex_unlock(ngx_posted_events_mutex);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_kqueue_process_changes(ngx_cycle_t *cycle, ngx_uint_t try)
+{
+ int n;
+ ngx_int_t rc;
+ ngx_err_t err;
+ struct timespec ts;
+ struct kevent *changes;
+
+ ngx_mutex_lock(kevent_mutex);
+
+ ngx_mutex_lock(list_mutex);
+
+ if (nchanges == 0) {
+ ngx_mutex_unlock(list_mutex);
+ ngx_mutex_unlock(kevent_mutex);
+ return NGX_OK;
+ }
+
+ changes = change_list;
+ if (change_list == change_list0) {
+ change_list = change_list1;
+ } else {
+ change_list = change_list0;
+ }
+
+ n = (int) nchanges;
+ nchanges = 0;
+
+ ngx_mutex_unlock(list_mutex);
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "kevent changes: %d", n);
+
+ if (kevent(ngx_kqueue, changes, n, NULL, 0, &ts) == -1) {
+ err = ngx_errno;
+ ngx_log_error((err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT,
+ cycle->log, err, "kevent() failed");
+ rc = NGX_ERROR;
+
+ } else {
+ rc = NGX_OK;
+ }
+
+ ngx_mutex_unlock(kevent_mutex);
+
+ return rc;
+}
+
+
+static ngx_inline void
+ngx_kqueue_dump_event(ngx_log_t *log, struct kevent *kev)
+{
+ ngx_log_debug6(NGX_LOG_DEBUG_EVENT, log, 0,
+ (kev->ident > 0x8000000 && kev->ident != (unsigned) -1) ?
+ "kevent: %p: ft:%d fl:%04Xd ff:%08Xd d:%d ud:%p":
+ "kevent: %d: ft:%d fl:%04Xd ff:%08Xd d:%d ud:%p",
+ kev->ident, kev->filter,
+ kev->flags, kev->fflags,
+ kev->data, kev->udata);
+}
+
+
+static void *
+ngx_kqueue_create_conf(ngx_cycle_t *cycle)
+{
+ ngx_kqueue_conf_t *kcf;
+
+ kcf = ngx_palloc(cycle->pool, sizeof(ngx_kqueue_conf_t));
+ if (kcf == NULL) {
+ return NULL;
+ }
+
+ kcf->changes = NGX_CONF_UNSET;
+ kcf->events = NGX_CONF_UNSET;
+
+ return kcf;
+}
+
+
+static char *
+ngx_kqueue_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+ ngx_kqueue_conf_t *kcf = conf;
+
+ ngx_conf_init_uint_value(kcf->changes, 512);
+ ngx_conf_init_uint_value(kcf->events, 512);
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/event/modules/ngx_poll_module.c b/usr.sbin/nginx/src/event/modules/ngx_poll_module.c
new file mode 100644
index 00000000000..ea947b7e5f7
--- /dev/null
+++ b/usr.sbin/nginx/src/event/modules/ngx_poll_module.c
@@ -0,0 +1,442 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+static ngx_int_t ngx_poll_init(ngx_cycle_t *cycle, ngx_msec_t timer);
+static void ngx_poll_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_poll_add_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_poll_del_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_poll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+ ngx_uint_t flags);
+static char *ngx_poll_init_conf(ngx_cycle_t *cycle, void *conf);
+
+
+static struct pollfd *event_list;
+static ngx_int_t nevents;
+
+
+static ngx_str_t poll_name = ngx_string("poll");
+
+ngx_event_module_t ngx_poll_module_ctx = {
+ &poll_name,
+ NULL, /* create configuration */
+ ngx_poll_init_conf, /* init configuration */
+
+ {
+ ngx_poll_add_event, /* add an event */
+ ngx_poll_del_event, /* delete an event */
+ ngx_poll_add_event, /* enable an event */
+ ngx_poll_del_event, /* disable an event */
+ NULL, /* add an connection */
+ NULL, /* delete an connection */
+ NULL, /* process the changes */
+ ngx_poll_process_events, /* process the events */
+ ngx_poll_init, /* init the events */
+ ngx_poll_done /* done the events */
+ }
+
+};
+
+ngx_module_t ngx_poll_module = {
+ NGX_MODULE_V1,
+ &ngx_poll_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_EVENT_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+
+static ngx_int_t
+ngx_poll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+{
+ struct pollfd *list;
+
+ if (event_list == NULL) {
+ nevents = 0;
+ }
+
+ if (ngx_process >= NGX_PROCESS_WORKER
+ || cycle->old_cycle == NULL
+ || cycle->old_cycle->connection_n < cycle->connection_n)
+ {
+ list = ngx_alloc(sizeof(struct pollfd) * cycle->connection_n,
+ cycle->log);
+ if (list == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (event_list) {
+ ngx_memcpy(list, event_list, sizeof(ngx_event_t *) * nevents);
+ ngx_free(event_list);
+ }
+
+ event_list = list;
+ }
+
+ ngx_io = ngx_os_io;
+
+ ngx_event_actions = ngx_poll_module_ctx.actions;
+
+ ngx_event_flags = NGX_USE_LEVEL_EVENT|NGX_USE_FD_EVENT;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_poll_done(ngx_cycle_t *cycle)
+{
+ ngx_free(event_list);
+
+ event_list = NULL;
+}
+
+
+static ngx_int_t
+ngx_poll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+ ngx_event_t *e;
+ ngx_connection_t *c;
+
+ c = ev->data;
+
+ ev->active = 1;
+
+ if (ev->index != NGX_INVALID_INDEX) {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+ "poll event fd:%d ev:%i is already set", c->fd, event);
+ return NGX_OK;
+ }
+
+ if (event == NGX_READ_EVENT) {
+ e = c->write;
+#if (NGX_READ_EVENT != POLLIN)
+ event = POLLIN;
+#endif
+
+ } else {
+ e = c->read;
+#if (NGX_WRITE_EVENT != POLLOUT)
+ event = POLLOUT;
+#endif
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "poll add event: fd:%d ev:%i", c->fd, event);
+
+ if (e == NULL || e->index == NGX_INVALID_INDEX) {
+ event_list[nevents].fd = c->fd;
+ event_list[nevents].events = (short) event;
+ event_list[nevents].revents = 0;
+
+ ev->index = nevents;
+ nevents++;
+
+ } else {
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "poll add index: %i", e->index);
+
+ event_list[e->index].events |= (short) event;
+ ev->index = e->index;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_poll_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+ ngx_event_t *e;
+ ngx_connection_t *c;
+
+ c = ev->data;
+
+ ev->active = 0;
+
+ if (ev->index == NGX_INVALID_INDEX) {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+ "poll event fd:%d ev:%i is already deleted",
+ c->fd, event);
+ return NGX_OK;
+ }
+
+ if (event == NGX_READ_EVENT) {
+ e = c->write;
+#if (NGX_READ_EVENT != POLLIN)
+ event = POLLIN;
+#endif
+
+ } else {
+ e = c->read;
+#if (NGX_WRITE_EVENT != POLLOUT)
+ event = POLLOUT;
+#endif
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "poll del event: fd:%d ev:%i", c->fd, event);
+
+ if (e == NULL || e->index == NGX_INVALID_INDEX) {
+ nevents--;
+
+ if (ev->index < (ngx_uint_t) nevents) {
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "index: copy event %ui to %i", nevents, ev->index);
+
+ event_list[ev->index] = event_list[nevents];
+
+ c = ngx_cycle->files[event_list[nevents].fd];
+
+ if (c->fd == -1) {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+ "unexpected last event");
+
+ } else {
+ if (c->read->index == (ngx_uint_t) nevents) {
+ c->read->index = ev->index;
+ }
+
+ if (c->write->index == (ngx_uint_t) nevents) {
+ c->write->index = ev->index;
+ }
+ }
+ }
+
+ } else {
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "poll del index: %i", e->index);
+
+ event_list[e->index].events &= (short) ~event;
+ }
+
+ ev->index = NGX_INVALID_INDEX;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_poll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
+{
+ int ready, revents;
+ ngx_err_t err;
+ ngx_int_t i, nready;
+ ngx_uint_t found, level;
+ ngx_event_t *ev, **queue;
+ ngx_connection_t *c;
+
+ /* NGX_TIMER_INFINITE == INFTIM */
+
+#if (NGX_DEBUG0)
+ if (cycle->log->log_level & NGX_LOG_DEBUG_ALL) {
+ for (i = 0; i < nevents; i++) {
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "poll: %d: fd:%d ev:%04Xd",
+ i, event_list[i].fd, event_list[i].events);
+ }
+ }
+#endif
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "poll timer: %M", timer);
+
+ ready = poll(event_list, (u_int) nevents, (int) timer);
+
+ err = (ready == -1) ? ngx_errno : 0;
+
+ if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
+ ngx_time_update();
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "poll ready %d of %d", ready, nevents);
+
+ if (err) {
+ if (err == NGX_EINTR) {
+
+ if (ngx_event_timer_alarm) {
+ ngx_event_timer_alarm = 0;
+ return NGX_OK;
+ }
+
+ level = NGX_LOG_INFO;
+
+ } else {
+ level = NGX_LOG_ALERT;
+ }
+
+ ngx_log_error(level, cycle->log, err, "poll() failed");
+ return NGX_ERROR;
+ }
+
+ if (ready == 0) {
+ if (timer != NGX_TIMER_INFINITE) {
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "poll() returned no events without timeout");
+ return NGX_ERROR;
+ }
+
+ ngx_mutex_lock(ngx_posted_events_mutex);
+
+ nready = 0;
+
+ for (i = 0; i < nevents && ready; i++) {
+
+ revents = event_list[i].revents;
+
+#if 1
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "poll: %d: fd:%d ev:%04Xd rev:%04Xd",
+ i, event_list[i].fd, event_list[i].events, revents);
+#else
+ if (revents) {
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "poll: %d: fd:%d ev:%04Xd rev:%04Xd",
+ i, event_list[i].fd, event_list[i].events, revents);
+ }
+#endif
+
+ if (revents & POLLNVAL) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "poll() error fd:%d ev:%04Xd rev:%04Xd",
+ event_list[i].fd, event_list[i].events, revents);
+ }
+
+ if (revents & ~(POLLIN|POLLOUT|POLLERR|POLLHUP|POLLNVAL)) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "strange poll() events fd:%d ev:%04Xd rev:%04Xd",
+ event_list[i].fd, event_list[i].events, revents);
+ }
+
+ if (event_list[i].fd == -1) {
+ /*
+ * the disabled event, a workaround for our possible bug,
+ * see the comment below
+ */
+ continue;
+ }
+
+ c = ngx_cycle->files[event_list[i].fd];
+
+ if (c->fd == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "unexpected event");
+
+ /*
+ * it is certainly our fault and it should be investigated,
+ * in the meantime we disable this event to avoid a CPU spinning
+ */
+
+ if (i == nevents - 1) {
+ nevents--;
+ } else {
+ event_list[i].fd = -1;
+ }
+
+ continue;
+ }
+
+ if ((revents & (POLLERR|POLLHUP|POLLNVAL))
+ && (revents & (POLLIN|POLLOUT)) == 0)
+ {
+ /*
+ * if the error events were returned without POLLIN or POLLOUT,
+ * then add these flags to handle the events at least in one
+ * active handler
+ */
+
+ revents |= POLLIN|POLLOUT;
+ }
+
+ found = 0;
+
+ if (revents & POLLIN) {
+ found = 1;
+
+ ev = c->read;
+
+ if ((flags & NGX_POST_THREAD_EVENTS) && !ev->accept) {
+ ev->posted_ready = 1;
+
+ } else {
+ ev->ready = 1;
+ }
+
+ queue = (ngx_event_t **) (ev->accept ? &ngx_posted_accept_events:
+ &ngx_posted_events);
+ ngx_locked_post_event(ev, queue);
+ }
+
+ if (revents & POLLOUT) {
+ found = 1;
+ ev = c->write;
+
+ if (flags & NGX_POST_THREAD_EVENTS) {
+ ev->posted_ready = 1;
+
+ } else {
+ ev->ready = 1;
+ }
+
+ ngx_locked_post_event(ev, &ngx_posted_events);
+ }
+
+ if (found) {
+ ready--;
+ continue;
+ }
+ }
+
+ ngx_mutex_unlock(ngx_posted_events_mutex);
+
+ if (ready != 0) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "poll ready != events");
+ }
+
+ return nready;
+}
+
+
+static char *
+ngx_poll_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+ ngx_event_conf_t *ecf;
+
+ ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
+
+ if (ecf->use != ngx_poll_module.ctx_index) {
+ return NGX_CONF_OK;
+ }
+
+#if (NGX_THREADS)
+
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+ "poll() is not supported in the threaded mode");
+ return NGX_CONF_ERROR;
+
+#else
+
+ return NGX_CONF_OK;
+
+#endif
+}
diff --git a/usr.sbin/nginx/src/event/modules/ngx_rtsig_module.c b/usr.sbin/nginx/src/event/modules/ngx_rtsig_module.c
new file mode 100644
index 00000000000..4b011007b0e
--- /dev/null
+++ b/usr.sbin/nginx/src/event/modules/ngx_rtsig_module.c
@@ -0,0 +1,734 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (NGX_TEST_BUILD_RTSIG)
+
+#ifdef SIGRTMIN
+#define si_fd _reason.__spare__.__spare2__[0]
+#else
+#define SIGRTMIN 33
+#define si_fd __spare__[0]
+#endif
+
+#define F_SETSIG 10
+#define KERN_RTSIGNR 30
+#define KERN_RTSIGMAX 31
+
+int sigtimedwait(const sigset_t *set, siginfo_t *info,
+ const struct timespec *timeout)
+{
+ return -1;
+}
+
+int ngx_linux_rtsig_max;
+
+#endif
+
+
+typedef struct {
+ ngx_uint_t signo;
+ ngx_uint_t overflow_events;
+ ngx_uint_t overflow_test;
+ ngx_uint_t overflow_threshold;
+} ngx_rtsig_conf_t;
+
+
+extern ngx_event_module_t ngx_poll_module_ctx;
+
+static ngx_int_t ngx_rtsig_init(ngx_cycle_t *cycle, ngx_msec_t timer);
+static void ngx_rtsig_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_rtsig_add_connection(ngx_connection_t *c);
+static ngx_int_t ngx_rtsig_del_connection(ngx_connection_t *c,
+ ngx_uint_t flags);
+static ngx_int_t ngx_rtsig_process_events(ngx_cycle_t *cycle,
+ ngx_msec_t timer, ngx_uint_t flags);
+static ngx_int_t ngx_rtsig_process_overflow(ngx_cycle_t *cycle,
+ ngx_msec_t timer, ngx_uint_t flags);
+
+static void *ngx_rtsig_create_conf(ngx_cycle_t *cycle);
+static char *ngx_rtsig_init_conf(ngx_cycle_t *cycle, void *conf);
+static char *ngx_check_ngx_overflow_threshold_bounds(ngx_conf_t *cf,
+ void *post, void *data);
+
+
+static sigset_t set;
+static ngx_uint_t overflow, overflow_current;
+static struct pollfd *overflow_list;
+
+
+static ngx_str_t rtsig_name = ngx_string("rtsig");
+
+static ngx_conf_num_bounds_t ngx_overflow_threshold_bounds = {
+ ngx_check_ngx_overflow_threshold_bounds, 2, 10
+};
+
+
+static ngx_command_t ngx_rtsig_commands[] = {
+
+ { ngx_string("rtsig_signo"),
+ NGX_EVENT_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ 0,
+ offsetof(ngx_rtsig_conf_t, signo),
+ NULL },
+
+ { ngx_string("rtsig_overflow_events"),
+ NGX_EVENT_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ 0,
+ offsetof(ngx_rtsig_conf_t, overflow_events),
+ NULL },
+
+ { ngx_string("rtsig_overflow_test"),
+ NGX_EVENT_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ 0,
+ offsetof(ngx_rtsig_conf_t, overflow_test),
+ NULL },
+
+ { ngx_string("rtsig_overflow_threshold"),
+ NGX_EVENT_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ 0,
+ offsetof(ngx_rtsig_conf_t, overflow_threshold),
+ &ngx_overflow_threshold_bounds },
+
+ ngx_null_command
+};
+
+
+ngx_event_module_t ngx_rtsig_module_ctx = {
+ &rtsig_name,
+ ngx_rtsig_create_conf, /* create configuration */
+ ngx_rtsig_init_conf, /* init configuration */
+
+ {
+ NULL, /* add an event */
+ NULL, /* delete an event */
+ NULL, /* enable an event */
+ NULL, /* disable an event */
+ ngx_rtsig_add_connection, /* add an connection */
+ ngx_rtsig_del_connection, /* delete an connection */
+ NULL, /* process the changes */
+ ngx_rtsig_process_events, /* process the events */
+ ngx_rtsig_init, /* init the events */
+ ngx_rtsig_done, /* done the events */
+ }
+
+};
+
+ngx_module_t ngx_rtsig_module = {
+ NGX_MODULE_V1,
+ &ngx_rtsig_module_ctx, /* module context */
+ ngx_rtsig_commands, /* module directives */
+ NGX_EVENT_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_rtsig_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+{
+ ngx_rtsig_conf_t *rtscf;
+
+ rtscf = ngx_event_get_conf(cycle->conf_ctx, ngx_rtsig_module);
+
+ sigemptyset(&set);
+ sigaddset(&set, (int) rtscf->signo);
+ sigaddset(&set, (int) rtscf->signo + 1);
+ sigaddset(&set, SIGIO);
+ sigaddset(&set, SIGALRM);
+
+ if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+ "sigprocmask() failed");
+ return NGX_ERROR;
+ }
+
+ if (overflow_list) {
+ ngx_free(overflow_list);
+ }
+
+ overflow_list = ngx_alloc(sizeof(struct pollfd) * rtscf->overflow_events,
+ cycle->log);
+ if (overflow_list == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_io = ngx_os_io;
+
+ ngx_event_actions = ngx_rtsig_module_ctx.actions;
+
+ ngx_event_flags = NGX_USE_RTSIG_EVENT
+ |NGX_USE_GREEDY_EVENT
+ |NGX_USE_FD_EVENT;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_rtsig_done(ngx_cycle_t *cycle)
+{
+ ngx_free(overflow_list);
+
+ overflow_list = NULL;
+}
+
+
+static ngx_int_t
+ngx_rtsig_add_connection(ngx_connection_t *c)
+{
+ ngx_uint_t signo;
+ ngx_rtsig_conf_t *rtscf;
+
+ if (c->read->accept && c->read->disabled) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "rtsig enable connection: fd:%d", c->fd);
+
+ if (fcntl(c->fd, F_SETOWN, ngx_pid) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+ "fcntl(F_SETOWN) failed");
+ return NGX_ERROR;
+ }
+
+ c->read->active = 1;
+ c->read->disabled = 0;
+ }
+
+ rtscf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_rtsig_module);
+
+ signo = rtscf->signo + c->read->instance;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "rtsig add connection: fd:%d signo:%ui", c->fd, signo);
+
+ if (fcntl(c->fd, F_SETFL, O_RDWR|O_NONBLOCK|O_ASYNC) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+ "fcntl(O_RDWR|O_NONBLOCK|O_ASYNC) failed");
+ return NGX_ERROR;
+ }
+
+ if (fcntl(c->fd, F_SETSIG, (int) signo) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+ "fcntl(F_SETSIG) failed");
+ return NGX_ERROR;
+ }
+
+ if (fcntl(c->fd, F_SETOWN, ngx_pid) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+ "fcntl(F_SETOWN) failed");
+ return NGX_ERROR;
+ }
+
+#if (NGX_HAVE_ONESIGFD)
+ if (fcntl(c->fd, F_SETAUXFL, O_ONESIGFD) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+ "fcntl(F_SETAUXFL) failed");
+ return NGX_ERROR;
+ }
+#endif
+
+ c->read->active = 1;
+ c->write->active = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_rtsig_del_connection(ngx_connection_t *c, ngx_uint_t flags)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "rtsig del connection: fd:%d", c->fd);
+
+ if ((flags & NGX_DISABLE_EVENT) && c->read->accept) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "rtsig disable connection: fd:%d", c->fd);
+
+ c->read->active = 0;
+ c->read->disabled = 1;
+ return NGX_OK;
+ }
+
+ if (flags & NGX_CLOSE_EVENT) {
+ c->read->active = 0;
+ c->write->active = 0;
+ return NGX_OK;
+ }
+
+ if (fcntl(c->fd, F_SETFL, O_RDWR|O_NONBLOCK) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+ "fcntl(O_RDWR|O_NONBLOCK) failed");
+ return NGX_ERROR;
+ }
+
+ c->read->active = 0;
+ c->write->active = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_rtsig_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
+{
+ int signo;
+ ngx_int_t instance;
+ ngx_err_t err;
+ siginfo_t si;
+ ngx_event_t *rev, *wev, **queue;
+ struct timespec ts, *tp;
+ struct sigaction sa;
+ ngx_connection_t *c;
+ ngx_rtsig_conf_t *rtscf;
+
+ if (timer == NGX_TIMER_INFINITE) {
+ tp = NULL;
+
+ } else {
+ ts.tv_sec = timer / 1000;
+ ts.tv_nsec = (timer % 1000) * 1000000;
+ tp = &ts;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "rtsig timer: %M", timer);
+
+ /* Linux's sigwaitinfo() is sigtimedwait() with the NULL timeout pointer */
+
+ signo = sigtimedwait(&set, &si, tp);
+
+ if (signo == -1) {
+ err = ngx_errno;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, err,
+ "rtsig signo:%d", signo);
+
+ if (flags & NGX_UPDATE_TIME) {
+ ngx_time_update();
+ }
+
+ if (err == NGX_EAGAIN) {
+
+ /* timeout */
+
+ if (timer != NGX_TIMER_INFINITE) {
+ return NGX_AGAIN;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+ "sigtimedwait() returned EAGAIN without timeout");
+ return NGX_ERROR;
+ }
+
+ ngx_log_error((err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT,
+ cycle->log, err, "sigtimedwait() failed");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "rtsig signo:%d fd:%d band:%04Xd",
+ signo, si.si_fd, si.si_band);
+
+ if (flags & NGX_UPDATE_TIME) {
+ ngx_time_update();
+ }
+
+ rtscf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_rtsig_module);
+
+ if (signo == (int) rtscf->signo || signo == (int) rtscf->signo + 1) {
+
+ if (overflow && (ngx_uint_t) si.si_fd > overflow_current) {
+ return NGX_OK;
+ }
+
+ c = ngx_cycle->files[si.si_fd];
+
+ if (c == NULL) {
+
+ /* the stale event */
+
+ return NGX_OK;
+ }
+
+ instance = signo - (int) rtscf->signo;
+
+ rev = c->read;
+
+ if (rev->instance != instance) {
+
+ /*
+ * the stale event from a file descriptor
+ * that was just closed in this iteration
+ */
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "rtsig: stale event %p", c);
+
+ return NGX_OK;
+ }
+
+ if ((si.si_band & (POLLIN|POLLHUP|POLLERR)) && rev->active) {
+
+ rev->ready = 1;
+
+ if (flags & NGX_POST_EVENTS) {
+ queue = (ngx_event_t **) (rev->accept ?
+ &ngx_posted_accept_events : &ngx_posted_events);
+
+ ngx_locked_post_event(rev, queue);
+
+ } else {
+ rev->handler(rev);
+ }
+ }
+
+ wev = c->write;
+
+ if ((si.si_band & (POLLOUT|POLLHUP|POLLERR)) && wev->active) {
+
+ wev->ready = 1;
+
+ if (flags & NGX_POST_EVENTS) {
+ ngx_locked_post_event(wev, &ngx_posted_events);
+
+ } else {
+ wev->handler(wev);
+ }
+ }
+
+ return NGX_OK;
+
+ } else if (signo == SIGALRM) {
+
+ ngx_time_update();
+
+ return NGX_OK;
+
+ } else if (signo == SIGIO) {
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "rt signal queue overflowed");
+
+ /* flush the RT signal queue */
+
+ ngx_memzero(&sa, sizeof(struct sigaction));
+ sa.sa_handler = SIG_DFL;
+ sigemptyset(&sa.sa_mask);
+
+ if (sigaction(rtscf->signo, &sa, NULL) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "sigaction(%d, SIG_DFL) failed", rtscf->signo);
+ }
+
+ if (sigaction(rtscf->signo + 1, &sa, NULL) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "sigaction(%d, SIG_DFL) failed", rtscf->signo + 1);
+ }
+
+ overflow = 1;
+ overflow_current = 0;
+ ngx_event_actions.process_events = ngx_rtsig_process_overflow;
+
+ return NGX_ERROR;
+
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "sigtimedwait() returned unexpected signal: %d", signo);
+
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_rtsig_process_overflow(ngx_cycle_t *cycle, ngx_msec_t timer,
+ ngx_uint_t flags)
+{
+ int name[2], rtsig_max, rtsig_nr, events, ready;
+ size_t len;
+ ngx_err_t err;
+ ngx_uint_t tested, n, i;
+ ngx_event_t *rev, *wev, **queue;
+ ngx_connection_t *c;
+ ngx_rtsig_conf_t *rtscf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "rtsig process overflow");
+
+ rtscf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_rtsig_module);
+
+ tested = 0;
+
+ for ( ;; ) {
+
+ n = 0;
+ while (n < rtscf->overflow_events) {
+
+ if (overflow_current == cycle->connection_n) {
+ break;
+ }
+
+ c = cycle->files[overflow_current++];
+
+ if (c == NULL || c->fd == -1) {
+ continue;
+ }
+
+ events = 0;
+
+ if (c->read->active && c->read->handler) {
+ events |= POLLIN;
+ }
+
+ if (c->write->active && c->write->handler) {
+ events |= POLLOUT;
+ }
+
+ if (events == 0) {
+ continue;
+ }
+
+ overflow_list[n].fd = c->fd;
+ overflow_list[n].events = events;
+ overflow_list[n].revents = 0;
+ n++;
+ }
+
+ if (n == 0) {
+ break;
+ }
+
+ for ( ;; ) {
+ ready = poll(overflow_list, n, 0);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "rtsig overflow poll:%d", ready);
+
+ if (ready == -1) {
+ err = ngx_errno;
+ ngx_log_error((err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT,
+ cycle->log, 0,
+ "poll() failed while the overflow recover");
+
+ if (err == NGX_EINTR) {
+ continue;
+ }
+ }
+
+ break;
+ }
+
+ if (ready <= 0) {
+ continue;
+ }
+
+ ngx_mutex_lock(ngx_posted_events_mutex);
+
+ for (i = 0; i < n; i++) {
+ c = cycle->files[overflow_list[i].fd];
+
+ if (c == NULL) {
+ continue;
+ }
+
+ rev = c->read;
+
+ if (rev->active
+ && !rev->closed
+ && rev->handler
+ && (overflow_list[i].revents
+ & (POLLIN|POLLERR|POLLHUP|POLLNVAL)))
+ {
+ tested++;
+
+ if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) {
+ rev->posted_ready = 1;
+
+ } else {
+ rev->ready = 1;
+ }
+
+ if (flags & NGX_POST_EVENTS) {
+ queue = (ngx_event_t **) (rev->accept ?
+ &ngx_posted_accept_events : &ngx_posted_events);
+
+ ngx_locked_post_event(rev, queue);
+
+ } else {
+ rev->handler(rev);
+ }
+ }
+
+ wev = c->write;
+
+ if (wev->active
+ && !wev->closed
+ && wev->handler
+ && (overflow_list[i].revents
+ & (POLLOUT|POLLERR|POLLHUP|POLLNVAL)))
+ {
+ tested++;
+
+ if (flags & NGX_POST_THREAD_EVENTS) {
+ wev->posted_ready = 1;
+
+ } else {
+ wev->ready = 1;
+ }
+
+ if (flags & NGX_POST_EVENTS) {
+ ngx_locked_post_event(wev, &ngx_posted_events);
+
+ } else {
+ wev->handler(wev);
+ }
+ }
+ }
+
+ ngx_mutex_unlock(ngx_posted_events_mutex);
+
+ if (tested >= rtscf->overflow_test) {
+
+ if (ngx_linux_rtsig_max) {
+
+ /*
+ * Check the current rt queue length to prevent
+ * the new overflow.
+ *
+ * learn the "/proc/sys/kernel/rtsig-max" value because
+ * it can be changed since the last checking
+ */
+
+ name[0] = CTL_KERN;
+ name[1] = KERN_RTSIGMAX;
+ len = sizeof(rtsig_max);
+
+ if (sysctl(name, 2, &rtsig_max, &len, NULL, 0) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, errno,
+ "sysctl(KERN_RTSIGMAX) failed");
+ return NGX_ERROR;
+ }
+
+ /* name[0] = CTL_KERN; */
+ name[1] = KERN_RTSIGNR;
+ len = sizeof(rtsig_nr);
+
+ if (sysctl(name, 2, &rtsig_nr, &len, NULL, 0) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, errno,
+ "sysctl(KERN_RTSIGNR) failed");
+ return NGX_ERROR;
+ }
+
+ /*
+ * drain the rt signal queue if the /"proc/sys/kernel/rtsig-nr"
+ * is bigger than
+ * "/proc/sys/kernel/rtsig-max" / "rtsig_overflow_threshold"
+ */
+
+ if (rtsig_max / (int) rtscf->overflow_threshold < rtsig_nr) {
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "rtsig queue state: %d/%d",
+ rtsig_nr, rtsig_max);
+ while (ngx_rtsig_process_events(cycle, 0, flags) == NGX_OK)
+ {
+ /* void */
+ }
+ }
+
+ } else {
+
+ /*
+ * Linux has not KERN_RTSIGMAX since 2.6.6-mm2
+ * so drain the rt signal queue unconditionally
+ */
+
+ while (ngx_rtsig_process_events(cycle, 0, flags) == NGX_OK) {
+ /* void */
+ }
+ }
+
+ tested = 0;
+ }
+ }
+
+ if (flags & NGX_UPDATE_TIME) {
+ ngx_time_update();
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "rt signal queue overflow recovered");
+
+ overflow = 0;
+ ngx_event_actions.process_events = ngx_rtsig_process_events;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_rtsig_create_conf(ngx_cycle_t *cycle)
+{
+ ngx_rtsig_conf_t *rtscf;
+
+ rtscf = ngx_palloc(cycle->pool, sizeof(ngx_rtsig_conf_t));
+ if (rtscf == NULL) {
+ return NULL;
+ }
+
+ rtscf->signo = NGX_CONF_UNSET;
+ rtscf->overflow_events = NGX_CONF_UNSET;
+ rtscf->overflow_test = NGX_CONF_UNSET;
+ rtscf->overflow_threshold = NGX_CONF_UNSET;
+
+ return rtscf;
+}
+
+
+static char *
+ngx_rtsig_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+ ngx_rtsig_conf_t *rtscf = conf;
+
+ /* LinuxThreads use the first 3 RT signals */
+ ngx_conf_init_uint_value(rtscf->signo, SIGRTMIN + 10);
+
+ ngx_conf_init_uint_value(rtscf->overflow_events, 16);
+ ngx_conf_init_uint_value(rtscf->overflow_test, 32);
+ ngx_conf_init_uint_value(rtscf->overflow_threshold, 10);
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_check_ngx_overflow_threshold_bounds(ngx_conf_t *cf, void *post, void *data)
+{
+ if (ngx_linux_rtsig_max) {
+ return ngx_conf_check_num_bounds(cf, post, data);
+ }
+
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "\"rtsig_overflow_threshold\" is not supported "
+ "since Linux 2.6.6-mm2, ignored");
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/event/modules/ngx_select_module.c b/usr.sbin/nginx/src/event/modules/ngx_select_module.c
new file mode 100644
index 00000000000..72dbe74bd69
--- /dev/null
+++ b/usr.sbin/nginx/src/event/modules/ngx_select_module.c
@@ -0,0 +1,434 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+static ngx_int_t ngx_select_init(ngx_cycle_t *cycle, ngx_msec_t timer);
+static void ngx_select_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_select_add_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_select_del_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_select_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+ ngx_uint_t flags);
+static void ngx_select_repair_fd_sets(ngx_cycle_t *cycle);
+static char *ngx_select_init_conf(ngx_cycle_t *cycle, void *conf);
+
+
+static fd_set master_read_fd_set;
+static fd_set master_write_fd_set;
+static fd_set work_read_fd_set;
+static fd_set work_write_fd_set;
+
+static ngx_int_t max_fd;
+static ngx_uint_t nevents;
+
+static ngx_event_t **event_index;
+
+
+static ngx_str_t select_name = ngx_string("select");
+
+ngx_event_module_t ngx_select_module_ctx = {
+ &select_name,
+ NULL, /* create configuration */
+ ngx_select_init_conf, /* init configuration */
+
+ {
+ ngx_select_add_event, /* add an event */
+ ngx_select_del_event, /* delete an event */
+ ngx_select_add_event, /* enable an event */
+ ngx_select_del_event, /* disable an event */
+ NULL, /* add an connection */
+ NULL, /* delete an connection */
+ NULL, /* process the changes */
+ ngx_select_process_events, /* process the events */
+ ngx_select_init, /* init the events */
+ ngx_select_done /* done the events */
+ }
+
+};
+
+ngx_module_t ngx_select_module = {
+ NGX_MODULE_V1,
+ &ngx_select_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_EVENT_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_select_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+{
+ ngx_event_t **index;
+
+ if (event_index == NULL) {
+ FD_ZERO(&master_read_fd_set);
+ FD_ZERO(&master_write_fd_set);
+ nevents = 0;
+ }
+
+ if (ngx_process >= NGX_PROCESS_WORKER
+ || cycle->old_cycle == NULL
+ || cycle->old_cycle->connection_n < cycle->connection_n)
+ {
+ index = ngx_alloc(sizeof(ngx_event_t *) * 2 * cycle->connection_n,
+ cycle->log);
+ if (index == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (event_index) {
+ ngx_memcpy(index, event_index, sizeof(ngx_event_t *) * nevents);
+ ngx_free(event_index);
+ }
+
+ event_index = index;
+ }
+
+ ngx_io = ngx_os_io;
+
+ ngx_event_actions = ngx_select_module_ctx.actions;
+
+ ngx_event_flags = NGX_USE_LEVEL_EVENT;
+
+ max_fd = -1;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_select_done(ngx_cycle_t *cycle)
+{
+ ngx_free(event_index);
+
+ event_index = NULL;
+}
+
+
+static ngx_int_t
+ngx_select_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+ ngx_connection_t *c;
+
+ c = ev->data;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "select add event fd:%d ev:%i", c->fd, event);
+
+ if (ev->index != NGX_INVALID_INDEX) {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+ "select event fd:%d ev:%i is already set", c->fd, event);
+ return NGX_OK;
+ }
+
+ if ((event == NGX_READ_EVENT && ev->write)
+ || (event == NGX_WRITE_EVENT && !ev->write))
+ {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+ "invalid select %s event fd:%d ev:%i",
+ ev->write ? "write" : "read", c->fd, event);
+ return NGX_ERROR;
+ }
+
+ if (event == NGX_READ_EVENT) {
+ FD_SET(c->fd, &master_read_fd_set);
+
+ } else if (event == NGX_WRITE_EVENT) {
+ FD_SET(c->fd, &master_write_fd_set);
+ }
+
+ if (max_fd != -1 && max_fd < c->fd) {
+ max_fd = c->fd;
+ }
+
+ ev->active = 1;
+
+ event_index[nevents] = ev;
+ ev->index = nevents;
+ nevents++;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_select_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+ ngx_event_t *e;
+ ngx_connection_t *c;
+
+ c = ev->data;
+
+ ev->active = 0;
+
+ if (ev->index == NGX_INVALID_INDEX) {
+ return NGX_OK;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "select del event fd:%d ev:%i", c->fd, event);
+
+ if (event == NGX_READ_EVENT) {
+ FD_CLR(c->fd, &master_read_fd_set);
+
+ } else if (event == NGX_WRITE_EVENT) {
+ FD_CLR(c->fd, &master_write_fd_set);
+ }
+
+ if (max_fd == c->fd) {
+ max_fd = -1;
+ }
+
+ if (ev->index < --nevents) {
+ e = event_index[nevents];
+ event_index[ev->index] = e;
+ e->index = ev->index;
+ }
+
+ ev->index = NGX_INVALID_INDEX;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_select_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+ ngx_uint_t flags)
+{
+ int ready, nready;
+ ngx_err_t err;
+ ngx_uint_t i, found;
+ ngx_event_t *ev, **queue;
+ struct timeval tv, *tp;
+ ngx_connection_t *c;
+
+ if (max_fd == -1) {
+ for (i = 0; i < nevents; i++) {
+ c = event_index[i]->data;
+ if (max_fd < c->fd) {
+ max_fd = c->fd;
+ }
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "change max_fd: %d", max_fd);
+ }
+
+#if (NGX_DEBUG)
+ if (cycle->log->log_level & NGX_LOG_DEBUG_ALL) {
+ for (i = 0; i < nevents; i++) {
+ ev = event_index[i];
+ c = ev->data;
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "select event: fd:%d wr:%d", c->fd, ev->write);
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "max_fd: %d", max_fd);
+ }
+#endif
+
+ if (timer == NGX_TIMER_INFINITE) {
+ tp = NULL;
+
+ } else {
+ tv.tv_sec = (long) (timer / 1000);
+ tv.tv_usec = (long) ((timer % 1000) * 1000);
+ tp = &tv;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "select timer: %M", timer);
+
+ work_read_fd_set = master_read_fd_set;
+ work_write_fd_set = master_write_fd_set;
+
+ ready = select(max_fd + 1, &work_read_fd_set, &work_write_fd_set, NULL, tp);
+
+ err = (ready == -1) ? ngx_errno : 0;
+
+ if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
+ ngx_time_update();
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "select ready %d", ready);
+
+ if (err) {
+ ngx_uint_t level;
+
+ if (err == NGX_EINTR) {
+
+ if (ngx_event_timer_alarm) {
+ ngx_event_timer_alarm = 0;
+ return NGX_OK;
+ }
+
+ level = NGX_LOG_INFO;
+
+ } else {
+ level = NGX_LOG_ALERT;
+ }
+
+ ngx_log_error(level, cycle->log, err, "select() failed");
+
+ if (err == EBADF) {
+ ngx_select_repair_fd_sets(cycle);
+ }
+
+ return NGX_ERROR;
+ }
+
+ if (ready == 0) {
+ if (timer != NGX_TIMER_INFINITE) {
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "select() returned no events without timeout");
+ return NGX_ERROR;
+ }
+
+ ngx_mutex_lock(ngx_posted_events_mutex);
+
+ nready = 0;
+
+ for (i = 0; i < nevents; i++) {
+ ev = event_index[i];
+ c = ev->data;
+ found = 0;
+
+ if (ev->write) {
+ if (FD_ISSET(c->fd, &work_write_fd_set)) {
+ found = 1;
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "select write %d", c->fd);
+ }
+
+ } else {
+ if (FD_ISSET(c->fd, &work_read_fd_set)) {
+ found = 1;
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "select read %d", c->fd);
+ }
+ }
+
+ if (found) {
+ ev->ready = 1;
+
+ queue = (ngx_event_t **) (ev->accept ? &ngx_posted_accept_events:
+ &ngx_posted_events);
+ ngx_locked_post_event(ev, queue);
+
+ nready++;
+ }
+ }
+
+ ngx_mutex_unlock(ngx_posted_events_mutex);
+
+ if (ready != nready) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "select ready != events: %d:%d", ready, nready);
+
+ ngx_select_repair_fd_sets(cycle);
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_select_repair_fd_sets(ngx_cycle_t *cycle)
+{
+ int n;
+ socklen_t len;
+ ngx_err_t err;
+ ngx_socket_t s;
+
+ for (s = 0; s <= max_fd; s++) {
+
+ if (FD_ISSET(s, &master_read_fd_set) == 0) {
+ continue;
+ }
+
+ len = sizeof(int);
+
+ if (getsockopt(s, SOL_SOCKET, SO_TYPE, &n, &len) == -1) {
+ err = ngx_socket_errno;
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+ "invalid descriptor #%d in read fd_set", s);
+
+ FD_CLR(s, &master_read_fd_set);
+ }
+ }
+
+ for (s = 0; s <= max_fd; s++) {
+
+ if (FD_ISSET(s, &master_write_fd_set) == 0) {
+ continue;
+ }
+
+ len = sizeof(int);
+
+ if (getsockopt(s, SOL_SOCKET, SO_TYPE, &n, &len) == -1) {
+ err = ngx_socket_errno;
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+ "invalid descriptor #%d in write fd_set", s);
+
+ FD_CLR(s, &master_write_fd_set);
+ }
+ }
+
+ max_fd = -1;
+}
+
+
+static char *
+ngx_select_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+ ngx_event_conf_t *ecf;
+
+ ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
+
+ if (ecf->use != ngx_select_module.ctx_index) {
+ return NGX_CONF_OK;
+ }
+
+ /* disable warning: the default FD_SETSIZE is 1024U in FreeBSD 5.x */
+
+ if (cycle->connection_n > FD_SETSIZE) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+ "the maximum number of files "
+ "supported by select() is %ud", FD_SETSIZE);
+ return NGX_CONF_ERROR;
+ }
+
+#if (NGX_THREADS)
+
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+ "select() is not supported in the threaded mode");
+ return NGX_CONF_ERROR;
+
+#else
+
+ return NGX_CONF_OK;
+
+#endif
+}
diff --git a/usr.sbin/nginx/src/event/modules/ngx_win32_select_module.c b/usr.sbin/nginx/src/event/modules/ngx_win32_select_module.c
new file mode 100644
index 00000000000..3d75d69a972
--- /dev/null
+++ b/usr.sbin/nginx/src/event/modules/ngx_win32_select_module.c
@@ -0,0 +1,399 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+static ngx_int_t ngx_select_init(ngx_cycle_t *cycle, ngx_msec_t timer);
+static void ngx_select_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_select_add_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_select_del_event(ngx_event_t *ev, ngx_int_t event,
+ ngx_uint_t flags);
+static ngx_int_t ngx_select_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+ ngx_uint_t flags);
+static void ngx_select_repair_fd_sets(ngx_cycle_t *cycle);
+static char *ngx_select_init_conf(ngx_cycle_t *cycle, void *conf);
+
+
+static fd_set master_read_fd_set;
+static fd_set master_write_fd_set;
+static fd_set work_read_fd_set;
+static fd_set work_write_fd_set;
+
+static ngx_uint_t max_read;
+static ngx_uint_t max_write;
+static ngx_uint_t nevents;
+
+static ngx_event_t **event_index;
+
+
+static ngx_str_t select_name = ngx_string("select");
+
+ngx_event_module_t ngx_select_module_ctx = {
+ &select_name,
+ NULL, /* create configuration */
+ ngx_select_init_conf, /* init configuration */
+
+ {
+ ngx_select_add_event, /* add an event */
+ ngx_select_del_event, /* delete an event */
+ ngx_select_add_event, /* enable an event */
+ ngx_select_del_event, /* disable an event */
+ NULL, /* add an connection */
+ NULL, /* delete an connection */
+ NULL, /* process the changes */
+ ngx_select_process_events, /* process the events */
+ ngx_select_init, /* init the events */
+ ngx_select_done /* done the events */
+ }
+
+};
+
+ngx_module_t ngx_select_module = {
+ NGX_MODULE_V1,
+ &ngx_select_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_EVENT_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_select_init(ngx_cycle_t *cycle, ngx_msec_t timer)
+{
+ ngx_event_t **index;
+
+ if (event_index == NULL) {
+ FD_ZERO(&master_read_fd_set);
+ FD_ZERO(&master_write_fd_set);
+ nevents = 0;
+ }
+
+ if (ngx_process >= NGX_PROCESS_WORKER
+ || cycle->old_cycle == NULL
+ || cycle->old_cycle->connection_n < cycle->connection_n)
+ {
+ index = ngx_alloc(sizeof(ngx_event_t *) * 2 * cycle->connection_n,
+ cycle->log);
+ if (index == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (event_index) {
+ ngx_memcpy(index, event_index, sizeof(ngx_event_t *) * nevents);
+ ngx_free(event_index);
+ }
+
+ event_index = index;
+ }
+
+ ngx_io = ngx_os_io;
+
+ ngx_event_actions = ngx_select_module_ctx.actions;
+
+ ngx_event_flags = NGX_USE_LEVEL_EVENT;
+
+ max_read = 0;
+ max_write = 0;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_select_done(ngx_cycle_t *cycle)
+{
+ ngx_free(event_index);
+
+ event_index = NULL;
+}
+
+
+static ngx_int_t
+ngx_select_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+ ngx_connection_t *c;
+
+ c = ev->data;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "select add event fd:%d ev:%i", c->fd, event);
+
+ if (ev->index != NGX_INVALID_INDEX) {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+ "select event fd:%d ev:%i is already set", c->fd, event);
+ return NGX_OK;
+ }
+
+ if ((event == NGX_READ_EVENT && ev->write)
+ || (event == NGX_WRITE_EVENT && !ev->write))
+ {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+ "invalid select %s event fd:%d ev:%i",
+ ev->write ? "write" : "read", c->fd, event);
+ return NGX_ERROR;
+ }
+
+ if ((event == NGX_READ_EVENT) && (max_read >= FD_SETSIZE)
+ || (event == NGX_WRITE_EVENT) && (max_write >= FD_SETSIZE))
+ {
+ ngx_log_error(NGX_LOG_ERR, ev->log, 0,
+ "maximum number of descriptors "
+ "supported by select() is %d", FD_SETSIZE);
+ return NGX_ERROR;
+ }
+
+ if (event == NGX_READ_EVENT) {
+ FD_SET(c->fd, &master_read_fd_set);
+ max_read++;
+
+ } else if (event == NGX_WRITE_EVENT) {
+ FD_SET(c->fd, &master_write_fd_set);
+ max_write++;
+ }
+
+ ev->active = 1;
+
+ event_index[nevents] = ev;
+ ev->index = nevents;
+ nevents++;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_select_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
+{
+ ngx_event_t *e;
+ ngx_connection_t *c;
+
+ c = ev->data;
+
+ ev->active = 0;
+
+ if (ev->index == NGX_INVALID_INDEX) {
+ return NGX_OK;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "select del event fd:%d ev:%i", c->fd, event);
+
+ if (event == NGX_READ_EVENT) {
+ FD_CLR(c->fd, &master_read_fd_set);
+ max_read--;
+
+ } else if (event == NGX_WRITE_EVENT) {
+ FD_CLR(c->fd, &master_write_fd_set);
+ max_write--;
+ }
+
+ if (ev->index < --nevents) {
+ e = event_index[nevents];
+ event_index[ev->index] = e;
+ e->index = ev->index;
+ }
+
+ ev->index = NGX_INVALID_INDEX;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_select_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
+ ngx_uint_t flags)
+{
+ int ready, nready;
+ ngx_err_t err;
+ ngx_uint_t i, found;
+ ngx_event_t *ev, **queue;
+ struct timeval tv, *tp;
+ ngx_connection_t *c;
+
+#if (NGX_DEBUG)
+ if (cycle->log->log_level & NGX_LOG_DEBUG_ALL) {
+ for (i = 0; i < nevents; i++) {
+ ev = event_index[i];
+ c = ev->data;
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "select event: fd:%d wr:%d", c->fd, ev->write);
+ }
+ }
+#endif
+
+ if (timer == NGX_TIMER_INFINITE) {
+ tp = NULL;
+
+ } else {
+ tv.tv_sec = (long) (timer / 1000);
+ tv.tv_usec = (long) ((timer % 1000) * 1000);
+ tp = &tv;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "select timer: %M", timer);
+
+ work_read_fd_set = master_read_fd_set;
+ work_write_fd_set = master_write_fd_set;
+
+ if (max_read || max_write) {
+ ready = select(0, &work_read_fd_set, &work_write_fd_set, NULL, tp);
+
+ } else {
+
+ /*
+ * Winsock select() requires that at least one descriptor set must be
+ * be non-null, and any non-null descriptor set must contain at least
+ * one handle to a socket. Otherwise select() returns WSAEINVAL.
+ */
+
+ ngx_msleep(timer);
+
+ ready = 0;
+ }
+
+ err = (ready == -1) ? ngx_socket_errno : 0;
+
+ if (flags & NGX_UPDATE_TIME) {
+ ngx_time_update();
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "select ready %d", ready);
+
+ if (err) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, err, "select() failed");
+
+ if (err == WSAENOTSOCK) {
+ ngx_select_repair_fd_sets(cycle);
+ }
+
+ return NGX_ERROR;
+ }
+
+ if (ready == 0) {
+ if (timer != NGX_TIMER_INFINITE) {
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "select() returned no events without timeout");
+ return NGX_ERROR;
+ }
+
+ ngx_mutex_lock(ngx_posted_events_mutex);
+
+ nready = 0;
+
+ for (i = 0; i < nevents; i++) {
+ ev = event_index[i];
+ c = ev->data;
+ found = 0;
+
+ if (ev->write) {
+ if (FD_ISSET(c->fd, &work_write_fd_set)) {
+ found = 1;
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "select write %d", c->fd);
+ }
+
+ } else {
+ if (FD_ISSET(c->fd, &work_read_fd_set)) {
+ found = 1;
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "select read %d", c->fd);
+ }
+ }
+
+ if (found) {
+ ev->ready = 1;
+
+ queue = (ngx_event_t **) (ev->accept ? &ngx_posted_accept_events:
+ &ngx_posted_events);
+ ngx_locked_post_event(ev, queue);
+
+ nready++;
+ }
+ }
+
+ ngx_mutex_unlock(ngx_posted_events_mutex);
+
+ if (ready != nready) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "select ready != events: %d:%d", ready, nready);
+
+ ngx_select_repair_fd_sets(cycle);
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_select_repair_fd_sets(ngx_cycle_t *cycle)
+{
+ int n;
+ u_int i;
+ socklen_t len;
+ ngx_err_t err;
+ ngx_socket_t s;
+
+ for (i = 0; i < master_read_fd_set.fd_count; i++) {
+
+ s = master_read_fd_set.fd_array[i];
+ len = sizeof(int);
+
+ if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char *) &n, &len) == -1) {
+ err = ngx_socket_errno;
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+ "invalid descriptor #%d in read fd_set", s);
+
+ FD_CLR(s, &master_read_fd_set);
+ }
+ }
+
+ for (i = 0; i < master_write_fd_set.fd_count; i++) {
+
+ s = master_write_fd_set.fd_array[i];
+ len = sizeof(int);
+
+ if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char *) &n, &len) == -1) {
+ err = ngx_socket_errno;
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+ "invalid descriptor #%d in write fd_set", s);
+
+ FD_CLR(s, &master_write_fd_set);
+ }
+ }
+}
+
+
+static char *
+ngx_select_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+ ngx_event_conf_t *ecf;
+
+ ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
+
+ if (ecf->use != ngx_select_module.ctx_index) {
+ return NGX_CONF_OK;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/event/ngx_event.c b/usr.sbin/nginx/src/event/ngx_event.c
new file mode 100644
index 00000000000..c57d37ec4a0
--- /dev/null
+++ b/usr.sbin/nginx/src/event/ngx_event.c
@@ -0,0 +1,1274 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define DEFAULT_CONNECTIONS 512
+
+
+extern ngx_module_t ngx_kqueue_module;
+extern ngx_module_t ngx_eventport_module;
+extern ngx_module_t ngx_devpoll_module;
+extern ngx_module_t ngx_epoll_module;
+extern ngx_module_t ngx_rtsig_module;
+extern ngx_module_t ngx_select_module;
+
+
+static ngx_int_t ngx_event_module_init(ngx_cycle_t *cycle);
+static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle);
+static char *ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+static char *ngx_event_connections(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_event_use(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_event_debug_connection(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+static void *ngx_event_create_conf(ngx_cycle_t *cycle);
+static char *ngx_event_init_conf(ngx_cycle_t *cycle, void *conf);
+
+
+static ngx_uint_t ngx_timer_resolution;
+sig_atomic_t ngx_event_timer_alarm;
+
+static ngx_uint_t ngx_event_max_module;
+
+ngx_uint_t ngx_event_flags;
+ngx_event_actions_t ngx_event_actions;
+
+
+static ngx_atomic_t connection_counter = 1;
+ngx_atomic_t *ngx_connection_counter = &connection_counter;
+
+
+ngx_atomic_t *ngx_accept_mutex_ptr;
+ngx_shmtx_t ngx_accept_mutex;
+ngx_uint_t ngx_use_accept_mutex;
+ngx_uint_t ngx_accept_events;
+ngx_uint_t ngx_accept_mutex_held;
+ngx_msec_t ngx_accept_mutex_delay;
+ngx_int_t ngx_accept_disabled;
+ngx_file_t ngx_accept_mutex_lock_file;
+
+
+#if (NGX_STAT_STUB)
+
+ngx_atomic_t ngx_stat_accepted0;
+ngx_atomic_t *ngx_stat_accepted = &ngx_stat_accepted0;
+ngx_atomic_t ngx_stat_handled0;
+ngx_atomic_t *ngx_stat_handled = &ngx_stat_handled0;
+ngx_atomic_t ngx_stat_requests0;
+ngx_atomic_t *ngx_stat_requests = &ngx_stat_requests0;
+ngx_atomic_t ngx_stat_active0;
+ngx_atomic_t *ngx_stat_active = &ngx_stat_active0;
+ngx_atomic_t ngx_stat_reading0;
+ngx_atomic_t *ngx_stat_reading = &ngx_stat_reading0;
+ngx_atomic_t ngx_stat_writing0;
+ngx_atomic_t *ngx_stat_writing = &ngx_stat_writing0;
+
+#endif
+
+
+
+static ngx_command_t ngx_events_commands[] = {
+
+ { ngx_string("events"),
+ NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+ ngx_events_block,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_core_module_t ngx_events_module_ctx = {
+ ngx_string("events"),
+ NULL,
+ NULL
+};
+
+
+ngx_module_t ngx_events_module = {
+ NGX_MODULE_V1,
+ &ngx_events_module_ctx, /* module context */
+ ngx_events_commands, /* module directives */
+ NGX_CORE_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t event_core_name = ngx_string("event_core");
+
+
+static ngx_command_t ngx_event_core_commands[] = {
+
+ { ngx_string("worker_connections"),
+ NGX_EVENT_CONF|NGX_CONF_TAKE1,
+ ngx_event_connections,
+ 0,
+ 0,
+ NULL },
+
+ { ngx_string("connections"),
+ NGX_EVENT_CONF|NGX_CONF_TAKE1,
+ ngx_event_connections,
+ 0,
+ 0,
+ NULL },
+
+ { ngx_string("use"),
+ NGX_EVENT_CONF|NGX_CONF_TAKE1,
+ ngx_event_use,
+ 0,
+ 0,
+ NULL },
+
+ { ngx_string("multi_accept"),
+ NGX_EVENT_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ 0,
+ offsetof(ngx_event_conf_t, multi_accept),
+ NULL },
+
+ { ngx_string("accept_mutex"),
+ NGX_EVENT_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ 0,
+ offsetof(ngx_event_conf_t, accept_mutex),
+ NULL },
+
+ { ngx_string("accept_mutex_delay"),
+ NGX_EVENT_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ 0,
+ offsetof(ngx_event_conf_t, accept_mutex_delay),
+ NULL },
+
+ { ngx_string("debug_connection"),
+ NGX_EVENT_CONF|NGX_CONF_TAKE1,
+ ngx_event_debug_connection,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+ngx_event_module_t ngx_event_core_module_ctx = {
+ &event_core_name,
+ ngx_event_create_conf, /* create configuration */
+ ngx_event_init_conf, /* init configuration */
+
+ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
+};
+
+
+ngx_module_t ngx_event_core_module = {
+ NGX_MODULE_V1,
+ &ngx_event_core_module_ctx, /* module context */
+ ngx_event_core_commands, /* module directives */
+ NGX_EVENT_MODULE, /* module type */
+ NULL, /* init master */
+ ngx_event_module_init, /* init module */
+ ngx_event_process_init, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+void
+ngx_process_events_and_timers(ngx_cycle_t *cycle)
+{
+ ngx_uint_t flags;
+ ngx_msec_t timer, delta;
+
+ if (ngx_timer_resolution) {
+ timer = NGX_TIMER_INFINITE;
+ flags = 0;
+
+ } else {
+ timer = ngx_event_find_timer();
+ flags = NGX_UPDATE_TIME;
+
+#if (NGX_THREADS)
+
+ if (timer == NGX_TIMER_INFINITE || timer > 500) {
+ timer = 500;
+ }
+
+#endif
+ }
+
+ if (ngx_use_accept_mutex) {
+ if (ngx_accept_disabled > 0) {
+ ngx_accept_disabled--;
+
+ } else {
+ if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
+ return;
+ }
+
+ if (ngx_accept_mutex_held) {
+ flags |= NGX_POST_EVENTS;
+
+ } else {
+ if (timer == NGX_TIMER_INFINITE
+ || timer > ngx_accept_mutex_delay)
+ {
+ timer = ngx_accept_mutex_delay;
+ }
+ }
+ }
+ }
+
+ delta = ngx_current_msec;
+
+ (void) ngx_process_events(cycle, timer, flags);
+
+ delta = ngx_current_msec - delta;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "timer delta: %M", delta);
+
+ if (ngx_posted_accept_events) {
+ ngx_event_process_posted(cycle, &ngx_posted_accept_events);
+ }
+
+ if (ngx_accept_mutex_held) {
+ ngx_shmtx_unlock(&ngx_accept_mutex);
+ }
+
+ if (delta) {
+ ngx_event_expire_timers();
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "posted events %p", ngx_posted_events);
+
+ if (ngx_posted_events) {
+ if (ngx_threaded) {
+ ngx_wakeup_worker_thread(cycle);
+
+ } else {
+ ngx_event_process_posted(cycle, &ngx_posted_events);
+ }
+ }
+}
+
+
+ngx_int_t
+ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
+{
+ if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+
+ /* kqueue, epoll */
+
+ if (!rev->active && !rev->ready) {
+ if (ngx_add_event(rev, NGX_READ_EVENT, NGX_CLEAR_EVENT)
+ == NGX_ERROR)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+
+ } else if (ngx_event_flags & NGX_USE_LEVEL_EVENT) {
+
+ /* select, poll, /dev/poll */
+
+ if (!rev->active && !rev->ready) {
+ if (ngx_add_event(rev, NGX_READ_EVENT, NGX_LEVEL_EVENT)
+ == NGX_ERROR)
+ {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+ }
+
+ if (rev->active && (rev->ready || (flags & NGX_CLOSE_EVENT))) {
+ if (ngx_del_event(rev, NGX_READ_EVENT, NGX_LEVEL_EVENT | flags)
+ == NGX_ERROR)
+ {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+ }
+
+ } else if (ngx_event_flags & NGX_USE_EVENTPORT_EVENT) {
+
+ /* event ports */
+
+ if (!rev->active && !rev->ready) {
+ if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+ }
+
+ if (rev->oneshot && !rev->ready) {
+ if (ngx_del_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+ }
+ }
+
+ /* aio, iocp, rtsig */
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_handle_write_event(ngx_event_t *wev, size_t lowat)
+{
+ ngx_connection_t *c;
+
+ if (lowat) {
+ c = wev->data;
+
+ if (ngx_send_lowat(c, lowat) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+
+ if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+
+ /* kqueue, epoll */
+
+ if (!wev->active && !wev->ready) {
+ if (ngx_add_event(wev, NGX_WRITE_EVENT,
+ NGX_CLEAR_EVENT | (lowat ? NGX_LOWAT_EVENT : 0))
+ == NGX_ERROR)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+
+ } else if (ngx_event_flags & NGX_USE_LEVEL_EVENT) {
+
+ /* select, poll, /dev/poll */
+
+ if (!wev->active && !wev->ready) {
+ if (ngx_add_event(wev, NGX_WRITE_EVENT, NGX_LEVEL_EVENT)
+ == NGX_ERROR)
+ {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+ }
+
+ if (wev->active && wev->ready) {
+ if (ngx_del_event(wev, NGX_WRITE_EVENT, NGX_LEVEL_EVENT)
+ == NGX_ERROR)
+ {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+ }
+
+ } else if (ngx_event_flags & NGX_USE_EVENTPORT_EVENT) {
+
+ /* event ports */
+
+ if (!wev->active && !wev->ready) {
+ if (ngx_add_event(wev, NGX_WRITE_EVENT, 0) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+ }
+
+ if (wev->oneshot && wev->ready) {
+ if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+ }
+ }
+
+ /* aio, iocp, rtsig */
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_event_module_init(ngx_cycle_t *cycle)
+{
+ void ***cf;
+ u_char *shared;
+ size_t size, cl;
+ ngx_shm_t shm;
+ ngx_time_t *tp;
+ ngx_core_conf_t *ccf;
+ ngx_event_conf_t *ecf;
+
+ cf = ngx_get_conf(cycle->conf_ctx, ngx_events_module);
+
+ if (cf == NULL) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+ "no \"events\" section in configuration");
+ return NGX_ERROR;
+ }
+
+ ecf = (*cf)[ngx_event_core_module.ctx_index];
+
+ if (!ngx_test_config && ngx_process <= NGX_PROCESS_MASTER) {
+ ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
+ "using the \"%s\" event method", ecf->name);
+ }
+
+ ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+ ngx_timer_resolution = ccf->timer_resolution;
+
+#if !(NGX_WIN32)
+ {
+ ngx_int_t limit;
+ struct rlimit rlmt;
+
+ if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "getrlimit(RLIMIT_NOFILE) failed, ignored");
+
+ } else {
+ if (ecf->connections > (ngx_uint_t) rlmt.rlim_cur
+ && (ccf->rlimit_nofile == NGX_CONF_UNSET
+ || ecf->connections > (ngx_uint_t) ccf->rlimit_nofile))
+ {
+ limit = (ccf->rlimit_nofile == NGX_CONF_UNSET) ?
+ (ngx_int_t) rlmt.rlim_cur : ccf->rlimit_nofile;
+
+ ngx_log_error(NGX_LOG_WARN, cycle->log, 0,
+ "%ui worker_connections are more than "
+ "open file resource limit: %i",
+ ecf->connections, limit);
+ }
+ }
+ }
+#endif /* !(NGX_WIN32) */
+
+
+ if (ccf->master == 0) {
+ return NGX_OK;
+ }
+
+ if (ngx_accept_mutex_ptr) {
+ return NGX_OK;
+ }
+
+
+ /* cl should be equal or bigger than cache line size */
+
+ cl = 128;
+
+ size = cl /* ngx_accept_mutex */
+ + cl /* ngx_connection_counter */
+ + cl; /* ngx_temp_number */
+
+#if (NGX_STAT_STUB)
+
+ size += cl /* ngx_stat_accepted */
+ + cl /* ngx_stat_handled */
+ + cl /* ngx_stat_requests */
+ + cl /* ngx_stat_active */
+ + cl /* ngx_stat_reading */
+ + cl; /* ngx_stat_writing */
+
+#endif
+
+ shm.size = size;
+ shm.name.len = sizeof("nginx_shared_zone");
+ shm.name.data = (u_char *) "nginx_shared_zone";
+ shm.log = cycle->log;
+
+ if (ngx_shm_alloc(&shm) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ shared = shm.addr;
+
+ ngx_accept_mutex_ptr = (ngx_atomic_t *) shared;
+ ngx_accept_mutex.spin = (ngx_uint_t) -1;
+
+ if (ngx_shmtx_create(&ngx_accept_mutex, shared, cycle->lock_file.data)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ ngx_connection_counter = (ngx_atomic_t *) (shared + 1 * cl);
+
+ (void) ngx_atomic_cmp_set(ngx_connection_counter, 0, 1);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "counter: %p, %d",
+ ngx_connection_counter, *ngx_connection_counter);
+
+ ngx_temp_number = (ngx_atomic_t *) (shared + 2 * cl);
+
+ tp = ngx_timeofday();
+
+ ngx_random_number = (tp->msec << 16) + ngx_pid;
+
+#if (NGX_STAT_STUB)
+
+ ngx_stat_accepted = (ngx_atomic_t *) (shared + 3 * cl);
+ ngx_stat_handled = (ngx_atomic_t *) (shared + 4 * cl);
+ ngx_stat_requests = (ngx_atomic_t *) (shared + 5 * cl);
+ ngx_stat_active = (ngx_atomic_t *) (shared + 6 * cl);
+ ngx_stat_reading = (ngx_atomic_t *) (shared + 7 * cl);
+ ngx_stat_writing = (ngx_atomic_t *) (shared + 8 * cl);
+
+#endif
+
+ return NGX_OK;
+}
+
+
+#if !(NGX_WIN32)
+
+void
+ngx_timer_signal_handler(int signo)
+{
+ ngx_event_timer_alarm = 1;
+
+#if 1
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, "timer signal");
+#endif
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_event_process_init(ngx_cycle_t *cycle)
+{
+ ngx_uint_t m, i;
+ ngx_event_t *rev, *wev;
+ ngx_listening_t *ls;
+ ngx_connection_t *c, *next, *old;
+ ngx_core_conf_t *ccf;
+ ngx_event_conf_t *ecf;
+ ngx_event_module_t *module;
+
+ ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+ ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
+
+ if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) {
+ ngx_use_accept_mutex = 1;
+ ngx_accept_mutex_held = 0;
+ ngx_accept_mutex_delay = ecf->accept_mutex_delay;
+
+ } else {
+ ngx_use_accept_mutex = 0;
+ }
+
+#if (NGX_THREADS)
+ ngx_posted_events_mutex = ngx_mutex_init(cycle->log, 0);
+ if (ngx_posted_events_mutex == NULL) {
+ return NGX_ERROR;
+ }
+#endif
+
+ if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
+ continue;
+ }
+
+ if (ngx_modules[m]->ctx_index != ecf->use) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+
+ if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
+ /* fatal */
+ exit(2);
+ }
+
+ break;
+ }
+
+#if !(NGX_WIN32)
+
+ if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
+ struct sigaction sa;
+ struct itimerval itv;
+
+ ngx_memzero(&sa, sizeof(struct sigaction));
+ sa.sa_handler = ngx_timer_signal_handler;
+ sigemptyset(&sa.sa_mask);
+
+ if (sigaction(SIGALRM, &sa, NULL) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "sigaction(SIGALRM) failed");
+ return NGX_ERROR;
+ }
+
+ itv.it_interval.tv_sec = ngx_timer_resolution / 1000;
+ itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000;
+ itv.it_value.tv_sec = ngx_timer_resolution / 1000;
+ itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000;
+
+ if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "setitimer() failed");
+ }
+ }
+
+ if (ngx_event_flags & NGX_USE_FD_EVENT) {
+ struct rlimit rlmt;
+
+ if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "getrlimit(RLIMIT_NOFILE) failed");
+ return NGX_ERROR;
+ }
+
+ cycle->files_n = (ngx_uint_t) rlmt.rlim_cur;
+
+ cycle->files = ngx_calloc(sizeof(ngx_connection_t *) * cycle->files_n,
+ cycle->log);
+ if (cycle->files == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+#endif
+
+ cycle->connections =
+ ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
+ if (cycle->connections == NULL) {
+ return NGX_ERROR;
+ }
+
+ c = cycle->connections;
+
+ cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
+ cycle->log);
+ if (cycle->read_events == NULL) {
+ return NGX_ERROR;
+ }
+
+ rev = cycle->read_events;
+ for (i = 0; i < cycle->connection_n; i++) {
+ rev[i].closed = 1;
+ rev[i].instance = 1;
+#if (NGX_THREADS)
+ rev[i].lock = &c[i].lock;
+ rev[i].own_lock = &c[i].lock;
+#endif
+ }
+
+ cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
+ cycle->log);
+ if (cycle->write_events == NULL) {
+ return NGX_ERROR;
+ }
+
+ wev = cycle->write_events;
+ for (i = 0; i < cycle->connection_n; i++) {
+ wev[i].closed = 1;
+#if (NGX_THREADS)
+ wev[i].lock = &c[i].lock;
+ wev[i].own_lock = &c[i].lock;
+#endif
+ }
+
+ i = cycle->connection_n;
+ next = NULL;
+
+ do {
+ i--;
+
+ c[i].data = next;
+ c[i].read = &cycle->read_events[i];
+ c[i].write = &cycle->write_events[i];
+ c[i].fd = (ngx_socket_t) -1;
+
+ next = &c[i];
+
+#if (NGX_THREADS)
+ c[i].lock = 0;
+#endif
+ } while (i);
+
+ cycle->free_connections = next;
+ cycle->free_connection_n = cycle->connection_n;
+
+ /* for each listening socket */
+
+ ls = cycle->listening.elts;
+ for (i = 0; i < cycle->listening.nelts; i++) {
+
+ c = ngx_get_connection(ls[i].fd, cycle->log);
+
+ if (c == NULL) {
+ return NGX_ERROR;
+ }
+
+ c->log = &ls[i].log;
+
+ c->listening = &ls[i];
+ ls[i].connection = c;
+
+ rev = c->read;
+
+ rev->log = c->log;
+ rev->accept = 1;
+
+#if (NGX_HAVE_DEFERRED_ACCEPT)
+ rev->deferred_accept = ls[i].deferred_accept;
+#endif
+
+ if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
+ if (ls[i].previous) {
+
+ /*
+ * delete the old accept events that were bound to
+ * the old cycle read events array
+ */
+
+ old = ls[i].previous->connection;
+
+ if (ngx_del_event(old->read, NGX_READ_EVENT, NGX_CLOSE_EVENT)
+ == NGX_ERROR)
+ {
+ return NGX_ERROR;
+ }
+
+ old->fd = (ngx_socket_t) -1;
+ }
+ }
+
+#if (NGX_WIN32)
+
+ if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
+ ngx_iocp_conf_t *iocpcf;
+
+ rev->handler = ngx_event_acceptex;
+
+ if (ngx_use_accept_mutex) {
+ continue;
+ }
+
+ if (ngx_add_event(rev, 0, NGX_IOCP_ACCEPT) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ ls[i].log.handler = ngx_acceptex_log_error;
+
+ iocpcf = ngx_event_get_conf(cycle->conf_ctx, ngx_iocp_module);
+ if (ngx_event_post_acceptex(&ls[i], iocpcf->post_acceptex)
+ == NGX_ERROR)
+ {
+ return NGX_ERROR;
+ }
+
+ } else {
+ rev->handler = ngx_event_accept;
+
+ if (ngx_use_accept_mutex) {
+ continue;
+ }
+
+ if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+
+#else
+
+ rev->handler = ngx_event_accept;
+
+ if (ngx_use_accept_mutex) {
+ continue;
+ }
+
+ if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
+ if (ngx_add_conn(c) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ } else {
+ if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+
+#endif
+
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_send_lowat(ngx_connection_t *c, size_t lowat)
+{
+ int sndlowat;
+
+#if (NGX_HAVE_LOWAT_EVENT)
+
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+ c->write->available = lowat;
+ return NGX_OK;
+ }
+
+#endif
+
+ if (lowat == 0 || c->sndlowat) {
+ return NGX_OK;
+ }
+
+ sndlowat = (int) lowat;
+
+ if (setsockopt(c->fd, SOL_SOCKET, SO_SNDLOWAT,
+ (const void *) &sndlowat, sizeof(int))
+ == -1)
+ {
+ ngx_connection_error(c, ngx_socket_errno,
+ "setsockopt(SO_SNDLOWAT) failed");
+ return NGX_ERROR;
+ }
+
+ c->sndlowat = 1;
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *rv;
+ void ***ctx;
+ ngx_uint_t i;
+ ngx_conf_t pcf;
+ ngx_event_module_t *m;
+
+ /* count the number of the event modules and set up their indices */
+
+ ngx_event_max_module = 0;
+ for (i = 0; ngx_modules[i]; i++) {
+ if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
+ continue;
+ }
+
+ ngx_modules[i]->ctx_index = ngx_event_max_module++;
+ }
+
+ ctx = ngx_pcalloc(cf->pool, sizeof(void *));
+ if (ctx == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
+ if (*ctx == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *(void **) conf = ctx;
+
+ for (i = 0; ngx_modules[i]; i++) {
+ if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
+ continue;
+ }
+
+ m = ngx_modules[i]->ctx;
+
+ if (m->create_conf) {
+ (*ctx)[ngx_modules[i]->ctx_index] = m->create_conf(cf->cycle);
+ if ((*ctx)[ngx_modules[i]->ctx_index] == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+ }
+
+ pcf = *cf;
+ cf->ctx = ctx;
+ cf->module_type = NGX_EVENT_MODULE;
+ cf->cmd_type = NGX_EVENT_CONF;
+
+ rv = ngx_conf_parse(cf, NULL);
+
+ *cf = pcf;
+
+ if (rv != NGX_CONF_OK)
+ return rv;
+
+ for (i = 0; ngx_modules[i]; i++) {
+ if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
+ continue;
+ }
+
+ m = ngx_modules[i]->ctx;
+
+ if (m->init_conf) {
+ rv = m->init_conf(cf->cycle, (*ctx)[ngx_modules[i]->ctx_index]);
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_event_connections(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_event_conf_t *ecf = conf;
+
+ ngx_str_t *value;
+
+ if (ecf->connections != NGX_CONF_UNSET_UINT) {
+ return "is duplicate";
+ }
+
+ if (ngx_strcmp(cmd->name.data, "connections") == 0) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "the \"connections\" directive is deprecated, "
+ "use the \"worker_connections\" directive instead");
+ }
+
+ value = cf->args->elts;
+ ecf->connections = ngx_atoi(value[1].data, value[1].len);
+ if (ecf->connections == (ngx_uint_t) NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid number \"%V\"", &value[1]);
+
+ return NGX_CONF_ERROR;
+ }
+
+ cf->cycle->connection_n = ecf->connections;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_event_use(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_event_conf_t *ecf = conf;
+
+ ngx_int_t m;
+ ngx_str_t *value;
+ ngx_event_conf_t *old_ecf;
+ ngx_event_module_t *module;
+
+ if (ecf->use != NGX_CONF_UNSET_UINT) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (cf->cycle->old_cycle->conf_ctx) {
+ old_ecf = ngx_event_get_conf(cf->cycle->old_cycle->conf_ctx,
+ ngx_event_core_module);
+ } else {
+ old_ecf = NULL;
+ }
+
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+ if (module->name->len == value[1].len) {
+ if (ngx_strcmp(module->name->data, value[1].data) == 0) {
+ ecf->use = ngx_modules[m]->ctx_index;
+ ecf->name = module->name->data;
+
+ if (ngx_process == NGX_PROCESS_SINGLE
+ && old_ecf
+ && old_ecf->use != ecf->use)
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "when the server runs without a master process "
+ "the \"%V\" event type must be the same as "
+ "in previous configuration - \"%s\" "
+ "and it can not be changed on the fly, "
+ "to change it you need to stop server "
+ "and start it again",
+ &value[1], old_ecf->name);
+
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+ }
+ }
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid event type \"%V\"", &value[1]);
+
+ return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_event_debug_connection(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+#if (NGX_DEBUG)
+ ngx_event_conf_t *ecf = conf;
+
+ ngx_int_t rc;
+ ngx_str_t *value;
+ ngx_event_debug_t *dc;
+ struct hostent *h;
+ ngx_cidr_t cidr;
+
+ value = cf->args->elts;
+
+ dc = ngx_array_push(&ecf->debug_connection);
+ if (dc == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ rc = ngx_ptocidr(&value[1], &cidr);
+
+ if (rc == NGX_DONE) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "low address bits of %V are meaningless", &value[1]);
+ rc = NGX_OK;
+ }
+
+ if (rc == NGX_OK) {
+
+ /* AF_INET only */
+
+ if (cidr.family != AF_INET) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"debug_connection\" supports IPv4 only");
+ return NGX_CONF_ERROR;
+ }
+
+ dc->mask = cidr.u.in.mask;
+ dc->addr = cidr.u.in.addr;
+
+ return NGX_CONF_OK;
+ }
+
+ h = gethostbyname((char *) value[1].data);
+
+ if (h == NULL || h->h_addr_list[0] == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "host \"%s\" not found", value[1].data);
+ return NGX_CONF_ERROR;
+ }
+
+ dc->mask = 0xffffffff;
+ dc->addr = *(in_addr_t *)(h->h_addr_list[0]);
+
+#else
+
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "\"debug_connection\" is ignored, you need to rebuild "
+ "nginx using --with-debug option to enable it");
+
+#endif
+
+ return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_event_create_conf(ngx_cycle_t *cycle)
+{
+ ngx_event_conf_t *ecf;
+
+ ecf = ngx_palloc(cycle->pool, sizeof(ngx_event_conf_t));
+ if (ecf == NULL) {
+ return NULL;
+ }
+
+ ecf->connections = NGX_CONF_UNSET_UINT;
+ ecf->use = NGX_CONF_UNSET_UINT;
+ ecf->multi_accept = NGX_CONF_UNSET;
+ ecf->accept_mutex = NGX_CONF_UNSET;
+ ecf->accept_mutex_delay = NGX_CONF_UNSET_MSEC;
+ ecf->name = (void *) NGX_CONF_UNSET;
+
+#if (NGX_DEBUG)
+
+ if (ngx_array_init(&ecf->debug_connection, cycle->pool, 4,
+ sizeof(ngx_event_debug_t)) == NGX_ERROR)
+ {
+ return NULL;
+ }
+
+#endif
+
+ return ecf;
+}
+
+
+static char *
+ngx_event_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+ ngx_event_conf_t *ecf = conf;
+
+#if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)
+ int fd;
+#endif
+#if (NGX_HAVE_RTSIG)
+ ngx_uint_t rtsig;
+ ngx_core_conf_t *ccf;
+#endif
+ ngx_int_t i;
+ ngx_module_t *module;
+ ngx_event_module_t *event_module;
+
+ module = NULL;
+
+#if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)
+
+ fd = epoll_create(100);
+
+ if (fd != -1) {
+ close(fd);
+ module = &ngx_epoll_module;
+
+ } else if (ngx_errno != NGX_ENOSYS) {
+ module = &ngx_epoll_module;
+ }
+
+#endif
+
+#if (NGX_HAVE_RTSIG)
+
+ if (module == NULL) {
+ module = &ngx_rtsig_module;
+ rtsig = 1;
+
+ } else {
+ rtsig = 0;
+ }
+
+#endif
+
+#if (NGX_HAVE_DEVPOLL)
+
+ module = &ngx_devpoll_module;
+
+#endif
+
+#if (NGX_HAVE_KQUEUE)
+
+ module = &ngx_kqueue_module;
+
+#endif
+
+#if (NGX_HAVE_SELECT)
+
+ if (module == NULL) {
+ module = &ngx_select_module;
+ }
+
+#endif
+
+ if (module == NULL) {
+ for (i = 0; ngx_modules[i]; i++) {
+
+ if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
+ continue;
+ }
+
+ event_module = ngx_modules[i]->ctx;
+
+ if (ngx_strcmp(event_module->name->data, event_core_name.data) == 0)
+ {
+ continue;
+ }
+
+ module = ngx_modules[i];
+ break;
+ }
+ }
+
+ if (module == NULL) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "no events module found");
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_init_uint_value(ecf->connections, DEFAULT_CONNECTIONS);
+ cycle->connection_n = ecf->connections;
+
+ ngx_conf_init_uint_value(ecf->use, module->ctx_index);
+
+ event_module = module->ctx;
+ ngx_conf_init_ptr_value(ecf->name, event_module->name->data);
+
+ ngx_conf_init_value(ecf->multi_accept, 0);
+ ngx_conf_init_value(ecf->accept_mutex, 1);
+ ngx_conf_init_msec_value(ecf->accept_mutex_delay, 500);
+
+
+#if (NGX_HAVE_RTSIG)
+
+ if (!rtsig) {
+ return NGX_CONF_OK;
+ }
+
+ if (ecf->accept_mutex) {
+ return NGX_CONF_OK;
+ }
+
+ ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+ if (ccf->worker_processes == 0) {
+ return NGX_CONF_OK;
+ }
+
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+ "the \"rtsig\" method requires \"accept_mutex\" to be on");
+
+ return NGX_CONF_ERROR;
+
+#else
+
+ return NGX_CONF_OK;
+
+#endif
+}
diff --git a/usr.sbin/nginx/src/event/ngx_event.h b/usr.sbin/nginx/src/event/ngx_event.h
new file mode 100644
index 00000000000..778da529d5b
--- /dev/null
+++ b/usr.sbin/nginx/src/event/ngx_event.h
@@ -0,0 +1,572 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_EVENT_H_INCLUDED_
+#define _NGX_EVENT_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_INVALID_INDEX 0xd0d0d0d0
+
+
+#if (NGX_HAVE_IOCP)
+
+typedef struct {
+ WSAOVERLAPPED ovlp;
+ ngx_event_t *event;
+ int error;
+} ngx_event_ovlp_t;
+
+#endif
+
+
+typedef struct {
+ ngx_uint_t lock;
+
+ ngx_event_t *events;
+ ngx_event_t *last;
+} ngx_event_mutex_t;
+
+
+struct ngx_event_s {
+ void *data;
+
+ unsigned write:1;
+
+ unsigned accept:1;
+
+ /* used to detect the stale events in kqueue, rtsig, and epoll */
+ unsigned instance:1;
+
+ /*
+ * the event was passed or would be passed to a kernel;
+ * in aio mode - operation was posted.
+ */
+ unsigned active:1;
+
+ unsigned disabled:1;
+
+ /* the ready event; in aio mode 0 means that no operation can be posted */
+ unsigned ready:1;
+
+ unsigned oneshot:1;
+
+ /* aio operation is complete */
+ unsigned complete:1;
+
+ unsigned eof:1;
+ unsigned error:1;
+
+ unsigned timedout:1;
+ unsigned timer_set:1;
+
+ unsigned delayed:1;
+
+ unsigned read_discarded:1;
+
+ unsigned unexpected_eof:1;
+
+ unsigned deferred_accept:1;
+
+ /* the pending eof reported by kqueue or in aio chain operation */
+ unsigned pending_eof:1;
+
+#if !(NGX_THREADS)
+ unsigned posted_ready:1;
+#endif
+
+#if (NGX_WIN32)
+ /* setsockopt(SO_UPDATE_ACCEPT_CONTEXT) was succesfull */
+ unsigned accept_context_updated:1;
+#endif
+
+#if (NGX_HAVE_KQUEUE)
+ unsigned kq_vnode:1;
+
+ /* the pending errno reported by kqueue */
+ int kq_errno;
+#endif
+
+ /*
+ * kqueue only:
+ * accept: number of sockets that wait to be accepted
+ * read: bytes to read when event is ready
+ * or lowat when event is set with NGX_LOWAT_EVENT flag
+ * write: available space in buffer when event is ready
+ * or lowat when event is set with NGX_LOWAT_EVENT flag
+ *
+ * iocp: TODO
+ *
+ * otherwise:
+ * accept: 1 if accept many, 0 otherwise
+ */
+
+#if (NGX_HAVE_KQUEUE) || (NGX_HAVE_IOCP)
+ int available;
+#else
+ unsigned available:1;
+#endif
+
+ ngx_event_handler_pt handler;
+
+
+#if (NGX_HAVE_AIO)
+
+#if (NGX_HAVE_IOCP)
+ ngx_event_ovlp_t ovlp;
+#else
+ struct aiocb aiocb;
+#endif
+
+#endif
+
+ ngx_uint_t index;
+
+ ngx_log_t *log;
+
+ ngx_rbtree_node_t timer;
+
+ unsigned closed:1;
+
+ /* to test on worker exit */
+ unsigned channel:1;
+ unsigned resolver:1;
+
+#if (NGX_THREADS)
+
+ unsigned locked:1;
+
+ unsigned posted_ready:1;
+ unsigned posted_timedout:1;
+ unsigned posted_eof:1;
+
+#if (NGX_HAVE_KQUEUE)
+ /* the pending errno reported by kqueue */
+ int posted_errno;
+#endif
+
+#if (NGX_HAVE_KQUEUE) || (NGX_HAVE_IOCP)
+ int posted_available;
+#else
+ unsigned posted_available:1;
+#endif
+
+ ngx_atomic_t *lock;
+ ngx_atomic_t *own_lock;
+
+#endif
+
+ /* the links of the posted queue */
+ ngx_event_t *next;
+ ngx_event_t **prev;
+
+
+#if 0
+
+ /* the threads support */
+
+ /*
+ * the event thread context, we store it here
+ * if $(CC) does not understand __thread declaration
+ * and pthread_getspecific() is too costly
+ */
+
+ void *thr_ctx;
+
+#if (NGX_EVENT_T_PADDING)
+
+ /* event should not cross cache line in SMP */
+
+ uint32_t padding[NGX_EVENT_T_PADDING];
+#endif
+#endif
+};
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+struct ngx_event_aio_s {
+ void *data;
+ ngx_event_handler_pt handler;
+ ngx_file_t *file;
+
+ ngx_fd_t fd;
+
+#if (NGX_HAVE_EVENTFD)
+ int64_t res;
+#if (NGX_TEST_BUILD_EPOLL)
+ ngx_err_t err;
+ size_t nbytes;
+#endif
+#else
+ ngx_err_t err;
+ size_t nbytes;
+#endif
+
+#if (NGX_HAVE_AIO_SENDFILE)
+ off_t last_offset;
+#endif
+
+ ngx_aiocb_t aiocb;
+ ngx_event_t event;
+};
+
+#endif
+
+
+typedef struct {
+ in_addr_t mask;
+ in_addr_t addr;
+} ngx_event_debug_t;
+
+
+typedef struct {
+ ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
+ ngx_int_t (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
+
+ ngx_int_t (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
+ ngx_int_t (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
+
+ ngx_int_t (*add_conn)(ngx_connection_t *c);
+ ngx_int_t (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);
+
+ ngx_int_t (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait);
+ ngx_int_t (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
+ ngx_uint_t flags);
+
+ ngx_int_t (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
+ void (*done)(ngx_cycle_t *cycle);
+} ngx_event_actions_t;
+
+
+extern ngx_event_actions_t ngx_event_actions;
+
+
+/*
+ * The event filter requires to read/write the whole data:
+ * select, poll, /dev/poll, kqueue, epoll.
+ */
+#define NGX_USE_LEVEL_EVENT 0x00000001
+
+/*
+ * The event filter is deleted after a notification without an additional
+ * syscall: kqueue, epoll.
+ */
+#define NGX_USE_ONESHOT_EVENT 0x00000002
+
+/*
+ * The event filter notifies only the changes and an initial level:
+ * kqueue, epoll.
+ */
+#define NGX_USE_CLEAR_EVENT 0x00000004
+
+/*
+ * The event filter has kqueue features: the eof flag, errno,
+ * available data, etc.
+ */
+#define NGX_USE_KQUEUE_EVENT 0x00000008
+
+/*
+ * The event filter supports low water mark: kqueue's NOTE_LOWAT.
+ * kqueue in FreeBSD 4.1-4.2 has no NOTE_LOWAT so we need a separate flag.
+ */
+#define NGX_USE_LOWAT_EVENT 0x00000010
+
+/*
+ * The event filter requires to do i/o operation until EAGAIN: epoll, rtsig.
+ */
+#define NGX_USE_GREEDY_EVENT 0x00000020
+
+/*
+ * The event filter is epoll.
+ */
+#define NGX_USE_EPOLL_EVENT 0x00000040
+
+/*
+ * No need to add or delete the event filters: rtsig.
+ */
+#define NGX_USE_RTSIG_EVENT 0x00000080
+
+/*
+ * No need to add or delete the event filters: overlapped, aio_read,
+ * aioread, io_submit.
+ */
+#define NGX_USE_AIO_EVENT 0x00000100
+
+/*
+ * Need to add socket or handle only once: i/o completion port.
+ * It also requires NGX_HAVE_AIO and NGX_USE_AIO_EVENT to be set.
+ */
+#define NGX_USE_IOCP_EVENT 0x00000200
+
+/*
+ * The event filter has no opaque data and requires file descriptors table:
+ * poll, /dev/poll, rtsig.
+ */
+#define NGX_USE_FD_EVENT 0x00000400
+
+/*
+ * The event module handles periodic or absolute timer event by itself:
+ * kqueue in FreeBSD 4.4, NetBSD 2.0, and MacOSX 10.4, Solaris 10's event ports.
+ */
+#define NGX_USE_TIMER_EVENT 0x00000800
+
+/*
+ * All event filters on file descriptor are deleted after a notification:
+ * Solaris 10's event ports.
+ */
+#define NGX_USE_EVENTPORT_EVENT 0x00001000
+
+/*
+ * The event filter support vnode notifications: kqueue.
+ */
+#define NGX_USE_VNODE_EVENT 0x00002000
+
+
+/*
+ * The event filter is deleted just before the closing file.
+ * Has no meaning for select and poll.
+ * kqueue, epoll, rtsig, eventport: allows to avoid explicit delete,
+ * because filter automatically is deleted
+ * on file close,
+ *
+ * /dev/poll: we need to flush POLLREMOVE event
+ * before closing file.
+ */
+#define NGX_CLOSE_EVENT 1
+
+/*
+ * disable temporarily event filter, this may avoid locks
+ * in kernel malloc()/free(): kqueue.
+ */
+#define NGX_DISABLE_EVENT 2
+
+/*
+ * event must be passed to kernel right now, do not wait until batch processing.
+ */
+#define NGX_FLUSH_EVENT 4
+
+
+/* these flags have a meaning only for kqueue */
+#define NGX_LOWAT_EVENT 0
+#define NGX_VNODE_EVENT 0
+
+
+#if (NGX_HAVE_KQUEUE)
+
+#define NGX_READ_EVENT EVFILT_READ
+#define NGX_WRITE_EVENT EVFILT_WRITE
+
+#undef NGX_VNODE_EVENT
+#define NGX_VNODE_EVENT EVFILT_VNODE
+
+/*
+ * NGX_CLOSE_EVENT, NGX_LOWAT_EVENT, and NGX_FLUSH_EVENT are the module flags
+ * and they must not go into a kernel so we need to choose the value
+ * that must not interfere with any existent and future kqueue flags.
+ * kqueue has such values - EV_FLAG1, EV_EOF, and EV_ERROR:
+ * they are reserved and cleared on a kernel entrance.
+ */
+#undef NGX_CLOSE_EVENT
+#define NGX_CLOSE_EVENT EV_EOF
+
+#undef NGX_LOWAT_EVENT
+#define NGX_LOWAT_EVENT EV_FLAG1
+
+#undef NGX_FLUSH_EVENT
+#define NGX_FLUSH_EVENT EV_ERROR
+
+#define NGX_LEVEL_EVENT 0
+#define NGX_ONESHOT_EVENT EV_ONESHOT
+#define NGX_CLEAR_EVENT EV_CLEAR
+
+#undef NGX_DISABLE_EVENT
+#define NGX_DISABLE_EVENT EV_DISABLE
+
+
+#elif (NGX_HAVE_DEVPOLL || NGX_HAVE_EVENTPORT)
+
+#define NGX_READ_EVENT POLLIN
+#define NGX_WRITE_EVENT POLLOUT
+
+#define NGX_LEVEL_EVENT 0
+#define NGX_ONESHOT_EVENT 1
+
+
+#elif (NGX_HAVE_EPOLL)
+
+#define NGX_READ_EVENT EPOLLIN
+#define NGX_WRITE_EVENT EPOLLOUT
+
+#define NGX_LEVEL_EVENT 0
+#define NGX_CLEAR_EVENT EPOLLET
+#define NGX_ONESHOT_EVENT 0x70000000
+#if 0
+#define NGX_ONESHOT_EVENT EPOLLONESHOT
+#endif
+
+
+#elif (NGX_HAVE_POLL)
+
+#define NGX_READ_EVENT POLLIN
+#define NGX_WRITE_EVENT POLLOUT
+
+#define NGX_LEVEL_EVENT 0
+#define NGX_ONESHOT_EVENT 1
+
+
+#else /* select */
+
+#define NGX_READ_EVENT 0
+#define NGX_WRITE_EVENT 1
+
+#define NGX_LEVEL_EVENT 0
+#define NGX_ONESHOT_EVENT 1
+
+#endif /* NGX_HAVE_KQUEUE */
+
+
+#if (NGX_HAVE_IOCP)
+#define NGX_IOCP_ACCEPT 0
+#define NGX_IOCP_IO 1
+#define NGX_IOCP_CONNECT 2
+#endif
+
+
+#ifndef NGX_CLEAR_EVENT
+#define NGX_CLEAR_EVENT 0 /* dummy declaration */
+#endif
+
+
+#define ngx_process_changes ngx_event_actions.process_changes
+#define ngx_process_events ngx_event_actions.process_events
+#define ngx_done_events ngx_event_actions.done
+
+#define ngx_add_event ngx_event_actions.add
+#define ngx_del_event ngx_event_actions.del
+#define ngx_add_conn ngx_event_actions.add_conn
+#define ngx_del_conn ngx_event_actions.del_conn
+
+#define ngx_add_timer ngx_event_add_timer
+#define ngx_del_timer ngx_event_del_timer
+
+
+extern ngx_os_io_t ngx_io;
+
+#define ngx_recv ngx_io.recv
+#define ngx_recv_chain ngx_io.recv_chain
+#define ngx_udp_recv ngx_io.udp_recv
+#define ngx_send ngx_io.send
+#define ngx_send_chain ngx_io.send_chain
+
+
+#define NGX_EVENT_MODULE 0x544E5645 /* "EVNT" */
+#define NGX_EVENT_CONF 0x02000000
+
+
+typedef struct {
+ ngx_uint_t connections;
+ ngx_uint_t use;
+
+ ngx_flag_t multi_accept;
+ ngx_flag_t accept_mutex;
+
+ ngx_msec_t accept_mutex_delay;
+
+ u_char *name;
+
+#if (NGX_DEBUG)
+ ngx_array_t debug_connection;
+#endif
+} ngx_event_conf_t;
+
+
+typedef struct {
+ ngx_str_t *name;
+
+ void *(*create_conf)(ngx_cycle_t *cycle);
+ char *(*init_conf)(ngx_cycle_t *cycle, void *conf);
+
+ ngx_event_actions_t actions;
+} ngx_event_module_t;
+
+
+extern ngx_atomic_t *ngx_connection_counter;
+
+extern ngx_atomic_t *ngx_accept_mutex_ptr;
+extern ngx_shmtx_t ngx_accept_mutex;
+extern ngx_uint_t ngx_use_accept_mutex;
+extern ngx_uint_t ngx_accept_events;
+extern ngx_uint_t ngx_accept_mutex_held;
+extern ngx_msec_t ngx_accept_mutex_delay;
+extern ngx_int_t ngx_accept_disabled;
+
+
+#if (NGX_STAT_STUB)
+
+extern ngx_atomic_t *ngx_stat_accepted;
+extern ngx_atomic_t *ngx_stat_handled;
+extern ngx_atomic_t *ngx_stat_requests;
+extern ngx_atomic_t *ngx_stat_active;
+extern ngx_atomic_t *ngx_stat_reading;
+extern ngx_atomic_t *ngx_stat_writing;
+
+#endif
+
+
+#define NGX_UPDATE_TIME 1
+#define NGX_POST_EVENTS 2
+#define NGX_POST_THREAD_EVENTS 4
+
+
+extern sig_atomic_t ngx_event_timer_alarm;
+extern ngx_uint_t ngx_event_flags;
+extern ngx_module_t ngx_events_module;
+extern ngx_module_t ngx_event_core_module;
+
+
+#define ngx_event_get_conf(conf_ctx, module) \
+ (*(ngx_get_conf(conf_ctx, ngx_events_module))) [module.ctx_index];
+
+
+
+void ngx_event_accept(ngx_event_t *ev);
+ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle);
+u_char *ngx_accept_log_error(ngx_log_t *log, u_char *buf, size_t len);
+
+
+void ngx_process_events_and_timers(ngx_cycle_t *cycle);
+ngx_int_t ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags);
+ngx_int_t ngx_handle_write_event(ngx_event_t *wev, size_t lowat);
+
+
+#if (NGX_WIN32)
+void ngx_event_acceptex(ngx_event_t *ev);
+ngx_int_t ngx_event_post_acceptex(ngx_listening_t *ls, ngx_uint_t n);
+u_char *ngx_acceptex_log_error(ngx_log_t *log, u_char *buf, size_t len);
+#endif
+
+
+ngx_int_t ngx_send_lowat(ngx_connection_t *c, size_t lowat);
+
+
+/* used in ngx_log_debugX() */
+#define ngx_event_ident(p) ((ngx_connection_t *) (p))->fd
+
+
+#include <ngx_event_timer.h>
+#include <ngx_event_posted.h>
+#include <ngx_event_busy_lock.h>
+
+#if (NGX_WIN32)
+#include <ngx_iocp_module.h>
+#endif
+
+
+#endif /* _NGX_EVENT_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/event/ngx_event_accept.c b/usr.sbin/nginx/src/event/ngx_event_accept.c
new file mode 100644
index 00000000000..2355d1bdadd
--- /dev/null
+++ b/usr.sbin/nginx/src/event/ngx_event_accept.c
@@ -0,0 +1,427 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+static ngx_int_t ngx_enable_accept_events(ngx_cycle_t *cycle);
+static ngx_int_t ngx_disable_accept_events(ngx_cycle_t *cycle);
+static void ngx_close_accepted_connection(ngx_connection_t *c);
+
+
+void
+ngx_event_accept(ngx_event_t *ev)
+{
+ socklen_t socklen;
+ ngx_err_t err;
+ ngx_log_t *log;
+ ngx_socket_t s;
+ ngx_event_t *rev, *wev;
+ ngx_listening_t *ls;
+ ngx_connection_t *c, *lc;
+ ngx_event_conf_t *ecf;
+ u_char sa[NGX_SOCKADDRLEN];
+#if (NGX_HAVE_ACCEPT4)
+ static ngx_uint_t use_accept4 = 1;
+#endif
+
+ ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);
+
+ if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
+ ev->available = 1;
+
+ } else if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) {
+ ev->available = ecf->multi_accept;
+ }
+
+ lc = ev->data;
+ ls = lc->listening;
+ ev->ready = 0;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "accept on %V, ready: %d", &ls->addr_text, ev->available);
+
+ do {
+ socklen = NGX_SOCKADDRLEN;
+
+#if (NGX_HAVE_ACCEPT4)
+ if (use_accept4) {
+ s = accept4(lc->fd, (struct sockaddr *) sa, &socklen,
+ SOCK_NONBLOCK);
+ } else {
+ s = accept(lc->fd, (struct sockaddr *) sa, &socklen);
+ }
+#else
+ s = accept(lc->fd, (struct sockaddr *) sa, &socklen);
+#endif
+
+ if (s == -1) {
+ err = ngx_socket_errno;
+
+ if (err == NGX_EAGAIN) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err,
+ "accept() not ready");
+ return;
+ }
+
+#if (NGX_HAVE_ACCEPT4)
+ ngx_log_error((ngx_uint_t) ((err == NGX_ECONNABORTED) ?
+ NGX_LOG_ERR : NGX_LOG_ALERT),
+ ev->log, err,
+ use_accept4 ? "accept4() failed" : "accept() failed");
+
+ if (use_accept4 && err == NGX_ENOSYS) {
+ use_accept4 = 0;
+ ngx_inherited_nonblocking = 0;
+ continue;
+ }
+#else
+ ngx_log_error((ngx_uint_t) ((err == NGX_ECONNABORTED) ?
+ NGX_LOG_ERR : NGX_LOG_ALERT),
+ ev->log, err, "accept() failed");
+#endif
+
+ if (err == NGX_ECONNABORTED) {
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+ ev->available--;
+ }
+
+ if (ev->available) {
+ continue;
+ }
+ }
+
+ return;
+ }
+
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_accepted, 1);
+#endif
+
+ ngx_accept_disabled = ngx_cycle->connection_n / 8
+ - ngx_cycle->free_connection_n;
+
+ c = ngx_get_connection(s, ev->log);
+
+ if (c == NULL) {
+ if (ngx_close_socket(s) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
+ ngx_close_socket_n " failed");
+ }
+
+ return;
+ }
+
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_active, 1);
+#endif
+
+ c->pool = ngx_create_pool(ls->pool_size, ev->log);
+ if (c->pool == NULL) {
+ ngx_close_accepted_connection(c);
+ return;
+ }
+
+ c->sockaddr = ngx_palloc(c->pool, socklen);
+ if (c->sockaddr == NULL) {
+ ngx_close_accepted_connection(c);
+ return;
+ }
+
+ ngx_memcpy(c->sockaddr, sa, socklen);
+
+ log = ngx_palloc(c->pool, sizeof(ngx_log_t));
+ if (log == NULL) {
+ ngx_close_accepted_connection(c);
+ return;
+ }
+
+ /* set a blocking mode for aio and non-blocking mode for others */
+
+ if (ngx_inherited_nonblocking) {
+ if (ngx_event_flags & NGX_USE_AIO_EVENT) {
+ if (ngx_blocking(s) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
+ ngx_blocking_n " failed");
+ ngx_close_accepted_connection(c);
+ return;
+ }
+ }
+
+ } else {
+ if (!(ngx_event_flags & (NGX_USE_AIO_EVENT|NGX_USE_RTSIG_EVENT))) {
+ if (ngx_nonblocking(s) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
+ ngx_nonblocking_n " failed");
+ ngx_close_accepted_connection(c);
+ return;
+ }
+ }
+ }
+
+ *log = ls->log;
+
+ c->recv = ngx_recv;
+ c->send = ngx_send;
+ c->recv_chain = ngx_recv_chain;
+ c->send_chain = ngx_send_chain;
+
+ c->log = log;
+ c->pool->log = log;
+
+ c->socklen = socklen;
+ c->listening = ls;
+ c->local_sockaddr = ls->sockaddr;
+
+ c->unexpected_eof = 1;
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+ if (c->sockaddr->sa_family == AF_UNIX) {
+ c->tcp_nopush = NGX_TCP_NOPUSH_DISABLED;
+ c->tcp_nodelay = NGX_TCP_NODELAY_DISABLED;
+#if (NGX_SOLARIS)
+ /* Solaris's sendfilev() supports AF_NCA, AF_INET, and AF_INET6 */
+ c->sendfile = 0;
+#endif
+ }
+#endif
+
+ rev = c->read;
+ wev = c->write;
+
+ wev->ready = 1;
+
+ if (ngx_event_flags & (NGX_USE_AIO_EVENT|NGX_USE_RTSIG_EVENT)) {
+ /* rtsig, aio, iocp */
+ rev->ready = 1;
+ }
+
+ if (ev->deferred_accept) {
+ rev->ready = 1;
+#if (NGX_HAVE_KQUEUE)
+ rev->available = 1;
+#endif
+ }
+
+ rev->log = log;
+ wev->log = log;
+
+ /*
+ * TODO: MT: - ngx_atomic_fetch_add()
+ * or protection by critical section or light mutex
+ *
+ * TODO: MP: - allocated in a shared memory
+ * - ngx_atomic_fetch_add()
+ * or protection by critical section or light mutex
+ */
+
+ c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
+
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_handled, 1);
+#endif
+
+#if (NGX_THREADS)
+ rev->lock = &c->lock;
+ wev->lock = &c->lock;
+ rev->own_lock = &c->lock;
+ wev->own_lock = &c->lock;
+#endif
+
+ if (ls->addr_ntop) {
+ c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len);
+ if (c->addr_text.data == NULL) {
+ ngx_close_accepted_connection(c);
+ return;
+ }
+
+ c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->addr_text.data,
+ ls->addr_text_max_len, 0);
+ if (c->addr_text.len == 0) {
+ ngx_close_accepted_connection(c);
+ return;
+ }
+ }
+
+#if (NGX_DEBUG)
+ {
+
+ in_addr_t i;
+ ngx_event_debug_t *dc;
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *) sa;
+ dc = ecf->debug_connection.elts;
+ for (i = 0; i < ecf->debug_connection.nelts; i++) {
+ if ((sin->sin_addr.s_addr & dc[i].mask) == dc[i].addr) {
+ log->log_level = NGX_LOG_DEBUG_CONNECTION|NGX_LOG_DEBUG_ALL;
+ break;
+ }
+ }
+
+ }
+#endif
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0,
+ "*%d accept: %V fd:%d", c->number, &c->addr_text, s);
+
+ if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
+ if (ngx_add_conn(c) == NGX_ERROR) {
+ ngx_close_accepted_connection(c);
+ return;
+ }
+ }
+
+ log->data = NULL;
+ log->handler = NULL;
+
+ ls->handler(c);
+
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+ ev->available--;
+ }
+
+ } while (ev->available);
+}
+
+
+ngx_int_t
+ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
+{
+ if (ngx_shmtx_trylock(&ngx_accept_mutex)) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "accept mutex locked");
+
+ if (ngx_accept_mutex_held
+ && ngx_accept_events == 0
+ && !(ngx_event_flags & NGX_USE_RTSIG_EVENT))
+ {
+ return NGX_OK;
+ }
+
+ if (ngx_enable_accept_events(cycle) == NGX_ERROR) {
+ ngx_shmtx_unlock(&ngx_accept_mutex);
+ return NGX_ERROR;
+ }
+
+ ngx_accept_events = 0;
+ ngx_accept_mutex_held = 1;
+
+ return NGX_OK;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "accept mutex lock failed: %ui", ngx_accept_mutex_held);
+
+ if (ngx_accept_mutex_held) {
+ if (ngx_disable_accept_events(cycle) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ ngx_accept_mutex_held = 0;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_enable_accept_events(ngx_cycle_t *cycle)
+{
+ ngx_uint_t i;
+ ngx_listening_t *ls;
+ ngx_connection_t *c;
+
+ ls = cycle->listening.elts;
+ for (i = 0; i < cycle->listening.nelts; i++) {
+
+ c = ls[i].connection;
+
+ if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
+
+ if (ngx_add_conn(c) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ } else {
+ if (ngx_add_event(c->read, NGX_READ_EVENT, 0) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_disable_accept_events(ngx_cycle_t *cycle)
+{
+ ngx_uint_t i;
+ ngx_listening_t *ls;
+ ngx_connection_t *c;
+
+ ls = cycle->listening.elts;
+ for (i = 0; i < cycle->listening.nelts; i++) {
+
+ c = ls[i].connection;
+
+ if (!c->read->active) {
+ continue;
+ }
+
+ if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
+ if (ngx_del_conn(c, NGX_DISABLE_EVENT) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ } else {
+ if (ngx_del_event(c->read, NGX_READ_EVENT, NGX_DISABLE_EVENT)
+ == NGX_ERROR)
+ {
+ return NGX_ERROR;
+ }
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_close_accepted_connection(ngx_connection_t *c)
+{
+ ngx_socket_t fd;
+
+ ngx_free_connection(c);
+
+ fd = c->fd;
+ c->fd = (ngx_socket_t) -1;
+
+ if (ngx_close_socket(fd) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno,
+ ngx_close_socket_n " failed");
+ }
+
+ if (c->pool) {
+ ngx_destroy_pool(c->pool);
+ }
+
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
+#endif
+}
+
+
+u_char *
+ngx_accept_log_error(ngx_log_t *log, u_char *buf, size_t len)
+{
+ return ngx_snprintf(buf, len, " while accepting new connection on %V",
+ log->data);
+}
diff --git a/usr.sbin/nginx/src/event/ngx_event_busy_lock.c b/usr.sbin/nginx/src/event/ngx_event_busy_lock.c
new file mode 100644
index 00000000000..249e20563a7
--- /dev/null
+++ b/usr.sbin/nginx/src/event/ngx_event_busy_lock.c
@@ -0,0 +1,285 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+static ngx_int_t ngx_event_busy_lock_look_cacheable(ngx_event_busy_lock_t *bl,
+ ngx_event_busy_lock_ctx_t *ctx);
+static void ngx_event_busy_lock_handler(ngx_event_t *ev);
+static void ngx_event_busy_lock_posted_handler(ngx_event_t *ev);
+
+
+/*
+ * NGX_OK: the busy lock is held
+ * NGX_AGAIN: the all busy locks are held but we will wait the specified time
+ * NGX_BUSY: ctx->timer == 0: there are many the busy locks
+ * ctx->timer != 0: there are many the waiting locks
+ */
+
+ngx_int_t
+ngx_event_busy_lock(ngx_event_busy_lock_t *bl, ngx_event_busy_lock_ctx_t *ctx)
+{
+ ngx_int_t rc;
+
+ ngx_mutex_lock(bl->mutex);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->event->log, 0,
+ "event busy lock: b:%d mb:%d",
+ bl->busy, bl->max_busy);
+
+ if (bl->busy < bl->max_busy) {
+ bl->busy++;
+
+ rc = NGX_OK;
+
+ } else if (ctx->timer && bl->waiting < bl->max_waiting) {
+ bl->waiting++;
+ ngx_add_timer(ctx->event, ctx->timer);
+ ctx->event->handler = ngx_event_busy_lock_handler;
+
+ if (bl->events) {
+ bl->last->next = ctx;
+
+ } else {
+ bl->events = ctx;
+ }
+
+ bl->last = ctx;
+
+ rc = NGX_AGAIN;
+
+ } else {
+ rc = NGX_BUSY;
+ }
+
+ ngx_mutex_unlock(bl->mutex);
+
+ return rc;
+}
+
+
+ngx_int_t
+ngx_event_busy_lock_cacheable(ngx_event_busy_lock_t *bl,
+ ngx_event_busy_lock_ctx_t *ctx)
+{
+ ngx_int_t rc;
+
+ ngx_mutex_lock(bl->mutex);
+
+ rc = ngx_event_busy_lock_look_cacheable(bl, ctx);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ctx->event->log, 0,
+ "event busy lock: %d w:%d mw:%d",
+ rc, bl->waiting, bl->max_waiting);
+
+ /*
+ * NGX_OK: no the same request, there is free slot and we locked it
+ * NGX_BUSY: no the same request and there is no free slot
+ * NGX_AGAIN: the same request is processing
+ */
+
+ if (rc == NGX_AGAIN) {
+
+ if (ctx->timer && bl->waiting < bl->max_waiting) {
+ bl->waiting++;
+ ngx_add_timer(ctx->event, ctx->timer);
+ ctx->event->handler = ngx_event_busy_lock_handler;
+
+ if (bl->events == NULL) {
+ bl->events = ctx;
+ } else {
+ bl->last->next = ctx;
+ }
+ bl->last = ctx;
+
+ } else {
+ rc = NGX_BUSY;
+ }
+ }
+
+ ngx_mutex_unlock(bl->mutex);
+
+ return rc;
+}
+
+
+void
+ngx_event_busy_unlock(ngx_event_busy_lock_t *bl,
+ ngx_event_busy_lock_ctx_t *ctx)
+{
+ ngx_event_t *ev;
+ ngx_event_busy_lock_ctx_t *wakeup;
+
+ ngx_mutex_lock(bl->mutex);
+
+ if (bl->events) {
+ wakeup = bl->events;
+ bl->events = bl->events->next;
+
+ } else {
+ wakeup = NULL;
+ bl->busy--;
+ }
+
+ /*
+ * MP: all ctx's and their queue must be in shared memory,
+ * each ctx has pid to wake up
+ */
+
+ if (wakeup == NULL) {
+ ngx_mutex_unlock(bl->mutex);
+ return;
+ }
+
+ if (ctx->md5) {
+ for (wakeup = bl->events; wakeup; wakeup = wakeup->next) {
+ if (wakeup->md5 == NULL || wakeup->slot != ctx->slot) {
+ continue;
+ }
+
+ wakeup->handler = ngx_event_busy_lock_posted_handler;
+ wakeup->cache_updated = 1;
+
+ ev = wakeup->event;
+
+ ngx_post_event(ev, &ngx_posted_events);
+ }
+
+ ngx_mutex_unlock(bl->mutex);
+
+ } else {
+ bl->waiting--;
+
+ ngx_mutex_unlock(bl->mutex);
+
+ wakeup->handler = ngx_event_busy_lock_posted_handler;
+ wakeup->locked = 1;
+
+ ev = wakeup->event;
+
+ if (ev->timer_set) {
+ ngx_del_timer(ev);
+ }
+
+ ngx_post_event(ev, &ngx_posted_events);
+ }
+}
+
+
+void
+ngx_event_busy_lock_cancel(ngx_event_busy_lock_t *bl,
+ ngx_event_busy_lock_ctx_t *ctx)
+{
+ ngx_event_busy_lock_ctx_t *c, *p;
+
+ ngx_mutex_lock(bl->mutex);
+
+ bl->waiting--;
+
+ if (ctx == bl->events) {
+ bl->events = ctx->next;
+
+ } else {
+ p = bl->events;
+ for (c = bl->events->next; c; c = c->next) {
+ if (c == ctx) {
+ p->next = ctx->next;
+ break;
+ }
+ p = c;
+ }
+ }
+
+ ngx_mutex_unlock(bl->mutex);
+}
+
+
+static ngx_int_t
+ngx_event_busy_lock_look_cacheable(ngx_event_busy_lock_t *bl,
+ ngx_event_busy_lock_ctx_t *ctx)
+{
+ ngx_int_t free;
+ ngx_uint_t i, bit, cacheable, mask;
+
+ bit = 0;
+ cacheable = 0;
+ free = -1;
+
+#if (NGX_SUPPRESS_WARN)
+ mask = 0;
+#endif
+
+ for (i = 0; i < bl->max_busy; i++) {
+
+ if ((bit & 7) == 0) {
+ mask = bl->md5_mask[i / 8];
+ }
+
+ if (mask & 1) {
+ if (ngx_memcmp(&bl->md5[i * 16], ctx->md5, 16) == 0) {
+ ctx->waiting = 1;
+ ctx->slot = i;
+ return NGX_AGAIN;
+ }
+ cacheable++;
+
+ } else if (free == -1) {
+ free = i;
+ }
+
+ if (cacheable == bl->cacheable) {
+ if (free == -1 && cacheable < bl->max_busy) {
+ free = i + 1;
+ }
+
+ break;
+ }
+
+ mask >>= 1;
+ bit++;
+ }
+
+ if (free == -1) {
+ return NGX_BUSY;
+ }
+
+#if 0
+ if (bl->busy == bl->max_busy) {
+ return NGX_BUSY;
+ }
+#endif
+
+ ngx_memcpy(&bl->md5[free * 16], ctx->md5, 16);
+ bl->md5_mask[free / 8] |= 1 << (free & 7);
+ ctx->slot = free;
+
+ bl->cacheable++;
+ bl->busy++;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_event_busy_lock_handler(ngx_event_t *ev)
+{
+ ev->handler = ngx_event_busy_lock_posted_handler;
+
+ ngx_post_event(ev, &ngx_posted_events);
+}
+
+
+static void
+ngx_event_busy_lock_posted_handler(ngx_event_t *ev)
+{
+ ngx_event_busy_lock_ctx_t *ctx;
+
+ ctx = ev->data;
+ ctx->handler(ev);
+}
diff --git a/usr.sbin/nginx/src/event/ngx_event_busy_lock.h b/usr.sbin/nginx/src/event/ngx_event_busy_lock.h
new file mode 100644
index 00000000000..3e767704af5
--- /dev/null
+++ b/usr.sbin/nginx/src/event/ngx_event_busy_lock.h
@@ -0,0 +1,64 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_EVENT_BUSY_LOCK_H_INCLUDED_
+#define _NGX_EVENT_BUSY_LOCK_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+typedef struct ngx_event_busy_lock_ctx_s ngx_event_busy_lock_ctx_t;
+
+struct ngx_event_busy_lock_ctx_s {
+ ngx_event_t *event;
+ ngx_event_handler_pt handler;
+ void *data;
+ ngx_msec_t timer;
+
+ unsigned locked:1;
+ unsigned waiting:1;
+ unsigned cache_updated:1;
+
+ char *md5;
+ ngx_int_t slot;
+
+ ngx_event_busy_lock_ctx_t *next;
+};
+
+
+typedef struct {
+ u_char *md5_mask;
+ char *md5;
+ ngx_uint_t cacheable;
+
+ ngx_uint_t busy;
+ ngx_uint_t max_busy;
+
+ ngx_uint_t waiting;
+ ngx_uint_t max_waiting;
+
+ ngx_event_busy_lock_ctx_t *events;
+ ngx_event_busy_lock_ctx_t *last;
+
+#if (NGX_THREADS)
+ ngx_mutex_t *mutex;
+#endif
+} ngx_event_busy_lock_t;
+
+
+ngx_int_t ngx_event_busy_lock(ngx_event_busy_lock_t *bl,
+ ngx_event_busy_lock_ctx_t *ctx);
+ngx_int_t ngx_event_busy_lock_cacheable(ngx_event_busy_lock_t *bl,
+ ngx_event_busy_lock_ctx_t *ctx);
+void ngx_event_busy_unlock(ngx_event_busy_lock_t *bl,
+ ngx_event_busy_lock_ctx_t *ctx);
+void ngx_event_busy_lock_cancel(ngx_event_busy_lock_t *bl,
+ ngx_event_busy_lock_ctx_t *ctx);
+
+
+#endif /* _NGX_EVENT_BUSY_LOCK_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/event/ngx_event_connect.c b/usr.sbin/nginx/src/event/ngx_event_connect.c
new file mode 100644
index 00000000000..fb8fd21105a
--- /dev/null
+++ b/usr.sbin/nginx/src/event/ngx_event_connect.c
@@ -0,0 +1,258 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+
+
+ngx_int_t
+ngx_event_connect_peer(ngx_peer_connection_t *pc)
+{
+ int rc;
+ ngx_int_t event;
+ ngx_err_t err;
+ ngx_uint_t level;
+ ngx_socket_t s;
+ ngx_event_t *rev, *wev;
+ ngx_connection_t *c;
+
+ rc = pc->get(pc, pc->data);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ s = ngx_socket(pc->sockaddr->sa_family, SOCK_STREAM, 0);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, "socket %d", s);
+
+ if (s == -1) {
+ ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+ ngx_socket_n " failed");
+ return NGX_ERROR;
+ }
+
+
+ c = ngx_get_connection(s, pc->log);
+
+ if (c == NULL) {
+ if (ngx_close_socket(s) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+ ngx_close_socket_n "failed");
+ }
+
+ return NGX_ERROR;
+ }
+
+ if (pc->rcvbuf) {
+ if (setsockopt(s, SOL_SOCKET, SO_RCVBUF,
+ (const void *) &pc->rcvbuf, sizeof(int)) == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+ "setsockopt(SO_RCVBUF) failed");
+ goto failed;
+ }
+ }
+
+ if (ngx_nonblocking(s) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+ ngx_nonblocking_n " failed");
+
+ goto failed;
+ }
+
+ if (pc->local) {
+ if (bind(s, pc->local->sockaddr, pc->local->socklen) == -1) {
+ ngx_log_error(NGX_LOG_CRIT, pc->log, ngx_socket_errno,
+ "bind(%V) failed", &pc->local->name);
+
+ goto failed;
+ }
+ }
+
+ c->recv = ngx_recv;
+ c->send = ngx_send;
+ c->recv_chain = ngx_recv_chain;
+ c->send_chain = ngx_send_chain;
+
+ c->sendfile = 1;
+
+ c->log_error = pc->log_error;
+
+ if (pc->sockaddr->sa_family != AF_INET) {
+ c->tcp_nopush = NGX_TCP_NOPUSH_DISABLED;
+ c->tcp_nodelay = NGX_TCP_NODELAY_DISABLED;
+
+#if (NGX_SOLARIS)
+ /* Solaris's sendfilev() supports AF_NCA, AF_INET, and AF_INET6 */
+ c->sendfile = 0;
+#endif
+ }
+
+ rev = c->read;
+ wev = c->write;
+
+ rev->log = pc->log;
+ wev->log = pc->log;
+
+ pc->connection = c;
+
+ c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
+
+#if (NGX_THREADS)
+
+ /* TODO: lock event when call completion handler */
+
+ rev->lock = pc->lock;
+ wev->lock = pc->lock;
+ rev->own_lock = &c->lock;
+ wev->own_lock = &c->lock;
+
+#endif
+
+ if (ngx_add_conn) {
+ if (ngx_add_conn(c) == NGX_ERROR) {
+ goto failed;
+ }
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pc->log, 0,
+ "connect to %V, fd:%d #%d", pc->name, s, c->number);
+
+ rc = connect(s, pc->sockaddr, pc->socklen);
+
+ if (rc == -1) {
+ err = ngx_socket_errno;
+
+
+ if (err != NGX_EINPROGRESS
+#if (NGX_WIN32)
+ /* Winsock returns WSAEWOULDBLOCK (NGX_EAGAIN) */
+ && err != NGX_EAGAIN
+#endif
+ )
+ {
+ if (err == NGX_ECONNREFUSED
+#if (NGX_LINUX)
+ /*
+ * Linux returns EAGAIN instead of ECONNREFUSED
+ * for unix sockets if listen queue is full
+ */
+ || err == NGX_EAGAIN
+#endif
+ || err == NGX_ECONNRESET
+ || err == NGX_ENETDOWN
+ || err == NGX_ENETUNREACH
+ || err == NGX_EHOSTDOWN
+ || err == NGX_EHOSTUNREACH)
+ {
+ level = NGX_LOG_ERR;
+
+ } else {
+ level = NGX_LOG_CRIT;
+ }
+
+ ngx_log_error(level, c->log, err, "connect() to %V failed",
+ pc->name);
+
+ return NGX_DECLINED;
+ }
+ }
+
+ if (ngx_add_conn) {
+ if (rc == -1) {
+
+ /* NGX_EINPROGRESS */
+
+ return NGX_AGAIN;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pc->log, 0, "connected");
+
+ wev->ready = 1;
+
+ return NGX_OK;
+ }
+
+ if (ngx_event_flags & NGX_USE_AIO_EVENT) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, ngx_socket_errno,
+ "connect(): %d", rc);
+
+ /* aio, iocp */
+
+ if (ngx_blocking(s) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+ ngx_blocking_n " failed");
+ goto failed;
+ }
+
+ /*
+ * FreeBSD's aio allows to post an operation on non-connected socket.
+ * NT does not support it.
+ *
+ * TODO: check in Win32, etc. As workaround we can use NGX_ONESHOT_EVENT
+ */
+
+ rev->ready = 1;
+ wev->ready = 1;
+
+ return NGX_OK;
+ }
+
+ if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+
+ /* kqueue */
+
+ event = NGX_CLEAR_EVENT;
+
+ } else {
+
+ /* select, poll, /dev/poll */
+
+ event = NGX_LEVEL_EVENT;
+ }
+
+ if (ngx_add_event(rev, NGX_READ_EVENT, event) != NGX_OK) {
+ goto failed;
+ }
+
+ if (rc == -1) {
+
+ /* NGX_EINPROGRESS */
+
+ if (ngx_add_event(wev, NGX_WRITE_EVENT, event) != NGX_OK) {
+ goto failed;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pc->log, 0, "connected");
+
+ wev->ready = 1;
+
+ return NGX_OK;
+
+failed:
+
+ ngx_free_connection(c);
+
+ if (ngx_close_socket(s) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+ ngx_close_socket_n " failed");
+ }
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_event_get_peer(ngx_peer_connection_t *pc, void *data)
+{
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/event/ngx_event_connect.h b/usr.sbin/nginx/src/event/ngx_event_connect.h
new file mode 100644
index 00000000000..d64f1bb101e
--- /dev/null
+++ b/usr.sbin/nginx/src/event/ngx_event_connect.h
@@ -0,0 +1,75 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_EVENT_CONNECT_H_INCLUDED_
+#define _NGX_EVENT_CONNECT_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define NGX_PEER_KEEPALIVE 1
+#define NGX_PEER_NEXT 2
+#define NGX_PEER_FAILED 4
+
+
+typedef struct ngx_peer_connection_s ngx_peer_connection_t;
+
+typedef ngx_int_t (*ngx_event_get_peer_pt)(ngx_peer_connection_t *pc,
+ void *data);
+typedef void (*ngx_event_free_peer_pt)(ngx_peer_connection_t *pc, void *data,
+ ngx_uint_t state);
+#if (NGX_SSL)
+
+typedef ngx_int_t (*ngx_event_set_peer_session_pt)(ngx_peer_connection_t *pc,
+ void *data);
+typedef void (*ngx_event_save_peer_session_pt)(ngx_peer_connection_t *pc,
+ void *data);
+#endif
+
+
+struct ngx_peer_connection_s {
+ ngx_connection_t *connection;
+
+ struct sockaddr *sockaddr;
+ socklen_t socklen;
+ ngx_str_t *name;
+
+ ngx_uint_t tries;
+
+ ngx_event_get_peer_pt get;
+ ngx_event_free_peer_pt free;
+ void *data;
+
+#if (NGX_SSL)
+ ngx_event_set_peer_session_pt set_session;
+ ngx_event_save_peer_session_pt save_session;
+#endif
+
+#if (NGX_THREADS)
+ ngx_atomic_t *lock;
+#endif
+
+ ngx_addr_t *local;
+
+ int rcvbuf;
+
+ ngx_log_t *log;
+
+ unsigned cached:1;
+
+ /* ngx_connection_log_error_e */
+ unsigned log_error:2;
+};
+
+
+ngx_int_t ngx_event_connect_peer(ngx_peer_connection_t *pc);
+ngx_int_t ngx_event_get_peer(ngx_peer_connection_t *pc, void *data);
+
+
+#endif /* _NGX_EVENT_CONNECT_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/event/ngx_event_mutex.c b/usr.sbin/nginx/src/event/ngx_event_mutex.c
new file mode 100644
index 00000000000..2335e5bafd0
--- /dev/null
+++ b/usr.sbin/nginx/src/event/ngx_event_mutex.c
@@ -0,0 +1,69 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ngx_int_t ngx_event_mutex_timedlock(ngx_event_mutex_t *m, ngx_msec_t timer,
+ ngx_event_t *ev)
+{
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "lock event mutex %p lock:%XD", m, m->lock);
+
+ if (m->lock) {
+
+ if (m->events == NULL) {
+ m->events = ev;
+
+ } else {
+ m->last->next = ev;
+ }
+
+ m->last = ev;
+ ev->next = NULL;
+
+#if (NGX_THREADS0)
+ ev->light = 1;
+#endif
+
+ ngx_add_timer(ev, timer);
+
+ return NGX_AGAIN;
+ }
+
+ m->lock = 1;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t ngx_event_mutex_unlock(ngx_event_mutex_t *m, ngx_log_t *log)
+{
+ ngx_event_t *ev;
+
+ if (m->lock == 0) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "tring to unlock the free event mutex %p", m);
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
+ "unlock event mutex %p, next event: %p", m, m->events);
+
+ m->lock = 0;
+
+ if (m->events) {
+ ev = m->events;
+ m->events = ev->next;
+
+ ev->next = (ngx_event_t *) ngx_posted_events;
+ ngx_posted_events = ev;
+ }
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/event/ngx_event_openssl.c b/usr.sbin/nginx/src/event/ngx_event_openssl.c
new file mode 100644
index 00000000000..692f5063999
--- /dev/null
+++ b/usr.sbin/nginx/src/event/ngx_event_openssl.c
@@ -0,0 +1,2354 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+typedef struct {
+ ngx_uint_t engine; /* unsigned engine:1; */
+} ngx_openssl_conf_t;
+
+
+static int ngx_http_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store);
+static void ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where,
+ int ret);
+static void ngx_ssl_handshake_handler(ngx_event_t *ev);
+static ngx_int_t ngx_ssl_handle_recv(ngx_connection_t *c, int n);
+static void ngx_ssl_write_handler(ngx_event_t *wev);
+static void ngx_ssl_read_handler(ngx_event_t *rev);
+static void ngx_ssl_shutdown_handler(ngx_event_t *ev);
+static void ngx_ssl_connection_error(ngx_connection_t *c, int sslerr,
+ ngx_err_t err, char *text);
+static void ngx_ssl_clear_error(ngx_log_t *log);
+
+static ngx_int_t ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone,
+ void *data);
+static int ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn,
+ ngx_ssl_session_t *sess);
+static ngx_ssl_session_t *ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn,
+ u_char *id, int len, int *copy);
+static void ngx_ssl_remove_session(SSL_CTX *ssl, ngx_ssl_session_t *sess);
+static void ngx_ssl_expire_sessions(ngx_ssl_session_cache_t *cache,
+ ngx_slab_pool_t *shpool, ngx_uint_t n);
+static void ngx_ssl_session_rbtree_insert_value(ngx_rbtree_node_t *temp,
+ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+
+static void *ngx_openssl_create_conf(ngx_cycle_t *cycle);
+static char *ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static void ngx_openssl_exit(ngx_cycle_t *cycle);
+
+
+static ngx_command_t ngx_openssl_commands[] = {
+
+ { ngx_string("ssl_engine"),
+ NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+ ngx_openssl_engine,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_core_module_t ngx_openssl_module_ctx = {
+ ngx_string("openssl"),
+ ngx_openssl_create_conf,
+ NULL
+};
+
+
+ngx_module_t ngx_openssl_module = {
+ NGX_MODULE_V1,
+ &ngx_openssl_module_ctx, /* module context */
+ ngx_openssl_commands, /* module directives */
+ NGX_CORE_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ ngx_openssl_exit, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static long ngx_ssl_protocols[] = {
+ SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1,
+ SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1,
+ SSL_OP_NO_SSLv2|SSL_OP_NO_TLSv1,
+ SSL_OP_NO_TLSv1,
+ SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3,
+ SSL_OP_NO_SSLv3,
+ SSL_OP_NO_SSLv2,
+ 0,
+};
+
+
+int ngx_ssl_connection_index;
+int ngx_ssl_server_conf_index;
+int ngx_ssl_session_cache_index;
+
+
+ngx_int_t
+ngx_ssl_init(ngx_log_t *log)
+{
+ OPENSSL_config(NULL);
+
+ SSL_library_init();
+ SSL_load_error_strings();
+
+ ENGINE_load_builtin_engines();
+
+ OpenSSL_add_all_algorithms();
+
+ ngx_ssl_connection_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+
+ if (ngx_ssl_connection_index == -1) {
+ ngx_ssl_error(NGX_LOG_ALERT, log, 0, "SSL_get_ex_new_index() failed");
+ return NGX_ERROR;
+ }
+
+ ngx_ssl_server_conf_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
+ NULL);
+ if (ngx_ssl_server_conf_index == -1) {
+ ngx_ssl_error(NGX_LOG_ALERT, log, 0,
+ "SSL_CTX_get_ex_new_index() failed");
+ return NGX_ERROR;
+ }
+
+ ngx_ssl_session_cache_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
+ NULL);
+ if (ngx_ssl_session_cache_index == -1) {
+ ngx_ssl_error(NGX_LOG_ALERT, log, 0,
+ "SSL_CTX_get_ex_new_index() failed");
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data)
+{
+ ssl->ctx = SSL_CTX_new(SSLv23_method());
+
+ if (ssl->ctx == NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "SSL_CTX_new() failed");
+ return NGX_ERROR;
+ }
+
+ if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_server_conf_index, data) == 0) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "SSL_CTX_set_ex_data() failed");
+ return NGX_ERROR;
+ }
+
+ /* client side options */
+
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_MICROSOFT_SESS_ID_BUG);
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_NETSCAPE_CHALLENGE_BUG);
+
+ /* server side options */
+
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG);
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER);
+
+ /* this option allow a potential SSL 2.0 rollback (CAN-2005-2969) */
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_MSIE_SSLV2_RSA_PADDING);
+
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_SSLEAY_080_CLIENT_DH_BUG);
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_TLS_D5_BUG);
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_TLS_BLOCK_PADDING_BUG);
+
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
+
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_SINGLE_DH_USE);
+
+ if (ngx_ssl_protocols[protocols >> 1] != 0) {
+ SSL_CTX_set_options(ssl->ctx, ngx_ssl_protocols[protocols >> 1]);
+ }
+
+ SSL_CTX_set_read_ahead(ssl->ctx, 1);
+
+ SSL_CTX_set_info_callback(ssl->ctx, ngx_ssl_info_callback);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
+ ngx_str_t *key)
+{
+ if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (SSL_CTX_use_certificate_chain_file(ssl->ctx, (char *) cert->data)
+ == 0)
+ {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "SSL_CTX_use_certificate_chain_file(\"%s\") failed",
+ cert->data);
+ return NGX_ERROR;
+ }
+
+ if (ngx_conf_full_name(cf->cycle, key, 1) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (SSL_CTX_use_PrivateKey_file(ssl->ctx, (char *) key->data,
+ SSL_FILETYPE_PEM)
+ == 0)
+ {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "SSL_CTX_use_PrivateKey_file(\"%s\") failed", key->data);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
+ ngx_int_t depth)
+{
+ STACK_OF(X509_NAME) *list;
+
+ SSL_CTX_set_verify(ssl->ctx, SSL_VERIFY_PEER, ngx_http_ssl_verify_callback);
+
+ SSL_CTX_set_verify_depth(ssl->ctx, depth);
+
+ if (cert->len == 0) {
+ return NGX_OK;
+ }
+
+ if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL)
+ == 0)
+ {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "SSL_CTX_load_verify_locations(\"%s\") failed",
+ cert->data);
+ return NGX_ERROR;
+ }
+
+ list = SSL_load_client_CA_file((char *) cert->data);
+
+ if (list == NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "SSL_load_client_CA_file(\"%s\") failed", cert->data);
+ return NGX_ERROR;
+ }
+
+ /*
+ * before 0.9.7h and 0.9.8 SSL_load_client_CA_file()
+ * always leaved an error in the error queue
+ */
+
+ ERR_clear_error();
+
+ SSL_CTX_set_client_CA_list(ssl->ctx, list);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *crl)
+{
+ X509_STORE *store;
+ X509_LOOKUP *lookup;
+
+ if (crl->len == 0) {
+ return NGX_OK;
+ }
+
+ if (ngx_conf_full_name(cf->cycle, crl, 1) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ store = SSL_CTX_get_cert_store(ssl->ctx);
+
+ if (store == NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "SSL_CTX_get_cert_store() failed");
+ return NGX_ERROR;
+ }
+
+ lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
+
+ if (lookup == NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "X509_STORE_add_lookup() failed");
+ return NGX_ERROR;
+ }
+
+ if (X509_LOOKUP_load_file(lookup, (char *) crl->data, X509_FILETYPE_PEM)
+ == 0)
+ {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "X509_LOOKUP_load_file(\"%s\") failed", crl->data);
+ return NGX_ERROR;
+ }
+
+ X509_STORE_set_flags(store,
+ X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
+
+ return NGX_OK;
+}
+
+
+static int
+ngx_http_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store)
+{
+#if (NGX_DEBUG)
+ char *subject, *issuer;
+ int err, depth;
+ X509 *cert;
+ X509_NAME *sname, *iname;
+ ngx_connection_t *c;
+ ngx_ssl_conn_t *ssl_conn;
+
+ ssl_conn = X509_STORE_CTX_get_ex_data(x509_store,
+ SSL_get_ex_data_X509_STORE_CTX_idx());
+
+ c = ngx_ssl_get_connection(ssl_conn);
+
+ cert = X509_STORE_CTX_get_current_cert(x509_store);
+ err = X509_STORE_CTX_get_error(x509_store);
+ depth = X509_STORE_CTX_get_error_depth(x509_store);
+
+ sname = X509_get_subject_name(cert);
+ subject = sname ? X509_NAME_oneline(sname, NULL, 0) : "(none)";
+
+ iname = X509_get_issuer_name(cert);
+ issuer = iname ? X509_NAME_oneline(iname, NULL, 0) : "(none)";
+
+ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "verify:%d, error:%d, depth:%d, "
+ "subject:\"%s\",issuer: \"%s\"",
+ ok, err, depth, subject, issuer);
+
+ if (sname) {
+ OPENSSL_free(subject);
+ }
+
+ if (iname) {
+ OPENSSL_free(issuer);
+ }
+#endif
+
+ return 1;
+}
+
+
+static void
+ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where, int ret)
+{
+ ngx_connection_t *c;
+
+ if (where & SSL_CB_HANDSHAKE_START) {
+ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+
+ if (c->ssl->handshaked) {
+ c->ssl->renegotiation = 1;
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL renegotiation");
+ }
+ }
+}
+
+
+RSA *
+ngx_ssl_rsa512_key_callback(SSL *ssl, int is_export, int key_length)
+{
+ static RSA *key;
+
+ if (key_length == 512) {
+ if (key == NULL) {
+ key = RSA_generate_key(512, RSA_F4, NULL, NULL);
+ }
+ }
+
+ return key;
+}
+
+
+ngx_int_t
+ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file)
+{
+ DH *dh;
+ BIO *bio;
+
+ /*
+ * -----BEGIN DH PARAMETERS-----
+ * MIGHAoGBALu8LcrYRnSQfEP89YDpz9vZWKP1aLQtSwju1OsPs1BMbAMCducQgAxc
+ * y7qokiYUxb7spWWl/fHSh6K8BJvmd4Bg6RqSp1fjBI9osHb302zI8pul34HcLKcl
+ * 7OZicMyaUDXYzs7vnqAnSmOrHlj6/UmI0PZdFGdX2gcd8EXP4WubAgEC
+ * -----END DH PARAMETERS-----
+ */
+
+ static unsigned char dh1024_p[] = {
+ 0xBB, 0xBC, 0x2D, 0xCA, 0xD8, 0x46, 0x74, 0x90, 0x7C, 0x43, 0xFC, 0xF5,
+ 0x80, 0xE9, 0xCF, 0xDB, 0xD9, 0x58, 0xA3, 0xF5, 0x68, 0xB4, 0x2D, 0x4B,
+ 0x08, 0xEE, 0xD4, 0xEB, 0x0F, 0xB3, 0x50, 0x4C, 0x6C, 0x03, 0x02, 0x76,
+ 0xE7, 0x10, 0x80, 0x0C, 0x5C, 0xCB, 0xBA, 0xA8, 0x92, 0x26, 0x14, 0xC5,
+ 0xBE, 0xEC, 0xA5, 0x65, 0xA5, 0xFD, 0xF1, 0xD2, 0x87, 0xA2, 0xBC, 0x04,
+ 0x9B, 0xE6, 0x77, 0x80, 0x60, 0xE9, 0x1A, 0x92, 0xA7, 0x57, 0xE3, 0x04,
+ 0x8F, 0x68, 0xB0, 0x76, 0xF7, 0xD3, 0x6C, 0xC8, 0xF2, 0x9B, 0xA5, 0xDF,
+ 0x81, 0xDC, 0x2C, 0xA7, 0x25, 0xEC, 0xE6, 0x62, 0x70, 0xCC, 0x9A, 0x50,
+ 0x35, 0xD8, 0xCE, 0xCE, 0xEF, 0x9E, 0xA0, 0x27, 0x4A, 0x63, 0xAB, 0x1E,
+ 0x58, 0xFA, 0xFD, 0x49, 0x88, 0xD0, 0xF6, 0x5D, 0x14, 0x67, 0x57, 0xDA,
+ 0x07, 0x1D, 0xF0, 0x45, 0xCF, 0xE1, 0x6B, 0x9B
+ };
+
+ static unsigned char dh1024_g[] = { 0x02 };
+
+
+ if (file->len == 0) {
+
+ dh = DH_new();
+ if (dh == NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "DH_new() failed");
+ return NGX_ERROR;
+ }
+
+ dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL);
+ dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL);
+
+ if (dh->p == NULL || dh->g == NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "BN_bin2bn() failed");
+ DH_free(dh);
+ return NGX_ERROR;
+ }
+
+ SSL_CTX_set_tmp_dh(ssl->ctx, dh);
+
+ DH_free(dh);
+
+ return NGX_OK;
+ }
+
+ if (ngx_conf_full_name(cf->cycle, file, 1) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ bio = BIO_new_file((char *) file->data, "r");
+ if (bio == NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "BIO_new_file(\"%s\") failed", file->data);
+ return NGX_ERROR;
+ }
+
+ dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+ if (dh == NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "PEM_read_bio_DHparams(\"%s\") failed", file->data);
+ BIO_free(bio);
+ return NGX_ERROR;
+ }
+
+ SSL_CTX_set_tmp_dh(ssl->ctx, dh);
+
+ DH_free(dh);
+ BIO_free(bio);
+
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+#ifndef OPENSSL_NO_ECDH
+ int nid;
+ EC_KEY *ecdh;
+
+ /*
+ * Elliptic-Curve Diffie-Hellman parameters are either "named curves"
+ * from RFC 4492 section 5.1.1, or explicitely described curves over
+ * binary fields. OpenSSL only supports the "named curves", which provide
+ * maximum interoperability.
+ */
+
+ nid = OBJ_sn2nid((const char *) name->data);
+ if (nid == 0) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "Unknown curve name \"%s\"", name->data);
+ return NGX_ERROR;
+ }
+
+ ecdh = EC_KEY_new_by_curve_name(nid);
+ if (ecdh == NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "Unable to create curve \"%s\"", name->data);
+ return NGX_ERROR;
+ }
+
+ SSL_CTX_set_tmp_ecdh(ssl->ctx, ecdh);
+
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_SINGLE_ECDH_USE);
+
+ EC_KEY_free(ecdh);
+#endif
+#endif
+
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, ngx_uint_t flags)
+{
+ ngx_ssl_connection_t *sc;
+
+ sc = ngx_pcalloc(c->pool, sizeof(ngx_ssl_connection_t));
+ if (sc == NULL) {
+ return NGX_ERROR;
+ }
+
+ sc->buffer = ((flags & NGX_SSL_BUFFER) != 0);
+
+ sc->connection = SSL_new(ssl->ctx);
+
+ if (sc->connection == NULL) {
+ ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_new() failed");
+ return NGX_ERROR;
+ }
+
+ if (SSL_set_fd(sc->connection, c->fd) == 0) {
+ ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_fd() failed");
+ return NGX_ERROR;
+ }
+
+ if (flags & NGX_SSL_CLIENT) {
+ SSL_set_connect_state(sc->connection);
+
+ } else {
+ SSL_set_accept_state(sc->connection);
+ }
+
+ if (SSL_set_ex_data(sc->connection, ngx_ssl_connection_index, c) == 0) {
+ ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_ex_data() failed");
+ return NGX_ERROR;
+ }
+
+ c->ssl = sc;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_set_session(ngx_connection_t *c, ngx_ssl_session_t *session)
+{
+ if (session) {
+ if (SSL_set_session(c->ssl->connection, session) == 0) {
+ ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_session() failed");
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_handshake(ngx_connection_t *c)
+{
+ int n, sslerr;
+ ngx_err_t err;
+
+ ngx_ssl_clear_error(c->log);
+
+ n = SSL_do_handshake(c->ssl->connection);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n);
+
+ if (n == 1) {
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+#if (NGX_DEBUG)
+ {
+ char buf[129], *s, *d;
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+ const
+#endif
+ SSL_CIPHER *cipher;
+
+ cipher = SSL_get_current_cipher(c->ssl->connection);
+
+ if (cipher) {
+ SSL_CIPHER_description(cipher, &buf[1], 128);
+
+ for (s = &buf[1], d = buf; *s; s++) {
+ if (*s == ' ' && *d == ' ') {
+ continue;
+ }
+
+ if (*s == LF || *s == CR) {
+ continue;
+ }
+
+ *++d = *s;
+ }
+
+ if (*d != ' ') {
+ d++;
+ }
+
+ *d = '\0';
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "SSL: %s, cipher: \"%s\"",
+ SSL_get_version(c->ssl->connection), &buf[1]);
+
+ if (SSL_session_reused(c->ssl->connection)) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "SSL reused session");
+ }
+
+ } else {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "SSL no shared ciphers");
+ }
+ }
+#endif
+
+ c->ssl->handshaked = 1;
+
+ c->recv = ngx_ssl_recv;
+ c->send = ngx_ssl_write;
+ c->recv_chain = ngx_ssl_recv_chain;
+ c->send_chain = ngx_ssl_send_chain;
+
+ /* initial handshake done, disable renegotiation (CVE-2009-3555) */
+ if (c->ssl->connection->s3) {
+ c->ssl->connection->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS;
+ }
+
+ return NGX_OK;
+ }
+
+ sslerr = SSL_get_error(c->ssl->connection, n);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
+
+ if (sslerr == SSL_ERROR_WANT_READ) {
+ c->read->ready = 0;
+ c->read->handler = ngx_ssl_handshake_handler;
+ c->write->handler = ngx_ssl_handshake_handler;
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ if (sslerr == SSL_ERROR_WANT_WRITE) {
+ c->write->ready = 0;
+ c->read->handler = ngx_ssl_handshake_handler;
+ c->write->handler = ngx_ssl_handshake_handler;
+
+ if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
+
+ c->ssl->no_wait_shutdown = 1;
+ c->ssl->no_send_shutdown = 1;
+ c->read->eof = 1;
+
+ if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, err,
+ "peer closed connection in SSL handshake");
+
+ return NGX_ERROR;
+ }
+
+ c->read->error = 1;
+
+ ngx_ssl_connection_error(c, sslerr, err, "SSL_do_handshake() failed");
+
+ return NGX_ERROR;
+}
+
+
+static void
+ngx_ssl_handshake_handler(ngx_event_t *ev)
+{
+ ngx_connection_t *c;
+
+ c = ev->data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "SSL handshake handler: %d", ev->write);
+
+ if (ev->timedout) {
+ c->ssl->handler(c);
+ return;
+ }
+
+ if (ngx_ssl_handshake(c) == NGX_AGAIN) {
+ return;
+ }
+
+ c->ssl->handler(c);
+}
+
+
+ssize_t
+ngx_ssl_recv_chain(ngx_connection_t *c, ngx_chain_t *cl)
+{
+ u_char *last;
+ ssize_t n, bytes;
+ ngx_buf_t *b;
+
+ bytes = 0;
+
+ b = cl->buf;
+ last = b->last;
+
+ for ( ;; ) {
+
+ n = ngx_ssl_recv(c, last, b->end - last);
+
+ if (n > 0) {
+ last += n;
+ bytes += n;
+
+ if (last == b->end) {
+ cl = cl->next;
+
+ if (cl == NULL) {
+ return bytes;
+ }
+
+ b = cl->buf;
+ last = b->last;
+ }
+
+ continue;
+ }
+
+ if (bytes) {
+
+ if (n == 0 || n == NGX_ERROR) {
+ c->read->ready = 1;
+ }
+
+ return bytes;
+ }
+
+ return n;
+ }
+}
+
+
+ssize_t
+ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size)
+{
+ int n, bytes;
+
+ if (c->ssl->last == NGX_ERROR) {
+ c->read->error = 1;
+ return NGX_ERROR;
+ }
+
+ if (c->ssl->last == NGX_DONE) {
+ c->read->ready = 0;
+ c->read->eof = 1;
+ return 0;
+ }
+
+ bytes = 0;
+
+ ngx_ssl_clear_error(c->log);
+
+ /*
+ * SSL_read() may return data in parts, so try to read
+ * until SSL_read() would return no data
+ */
+
+ for ( ;; ) {
+
+ n = SSL_read(c->ssl->connection, buf, size);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_read: %d", n);
+
+ if (n > 0) {
+ bytes += n;
+ }
+
+ c->ssl->last = ngx_ssl_handle_recv(c, n);
+
+ if (c->ssl->last == NGX_OK) {
+
+ size -= n;
+
+ if (size == 0) {
+ return bytes;
+ }
+
+ buf += n;
+
+ continue;
+ }
+
+ if (bytes) {
+ return bytes;
+ }
+
+ switch (c->ssl->last) {
+
+ case NGX_DONE:
+ c->read->ready = 0;
+ c->read->eof = 1;
+ return 0;
+
+ case NGX_ERROR:
+ c->read->error = 1;
+
+ /* fall thruogh */
+
+ case NGX_AGAIN:
+ return c->ssl->last;
+ }
+ }
+}
+
+
+static ngx_int_t
+ngx_ssl_handle_recv(ngx_connection_t *c, int n)
+{
+ int sslerr;
+ ngx_err_t err;
+
+ if (c->ssl->renegotiation) {
+ /*
+ * disable renegotiation (CVE-2009-3555):
+ * OpenSSL (at least up to 0.9.8l) does not handle disabled
+ * renegotiation gracefully, so drop connection here
+ */
+
+ ngx_log_error(NGX_LOG_NOTICE, c->log, 0, "SSL renegotiation disabled");
+
+ c->ssl->no_wait_shutdown = 1;
+ c->ssl->no_send_shutdown = 1;
+
+ return NGX_ERROR;
+ }
+
+ if (n > 0) {
+
+ if (c->ssl->saved_write_handler) {
+
+ c->write->handler = c->ssl->saved_write_handler;
+ c->ssl->saved_write_handler = NULL;
+ c->write->ready = 1;
+
+ if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_post_event(c->write, &ngx_posted_events);
+ }
+
+ return NGX_OK;
+ }
+
+ sslerr = SSL_get_error(c->ssl->connection, n);
+
+ err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
+
+ if (sslerr == SSL_ERROR_WANT_READ) {
+ c->read->ready = 0;
+ return NGX_AGAIN;
+ }
+
+ if (sslerr == SSL_ERROR_WANT_WRITE) {
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "peer started SSL renegotiation");
+
+ c->write->ready = 0;
+
+ if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ /*
+ * we do not set the timer because there is already the read event timer
+ */
+
+ if (c->ssl->saved_write_handler == NULL) {
+ c->ssl->saved_write_handler = c->write->handler;
+ c->write->handler = ngx_ssl_write_handler;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ c->ssl->no_wait_shutdown = 1;
+ c->ssl->no_send_shutdown = 1;
+
+ if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "peer shutdown SSL cleanly");
+ return NGX_DONE;
+ }
+
+ ngx_ssl_connection_error(c, sslerr, err, "SSL_read() failed");
+
+ return NGX_ERROR;
+}
+
+
+static void
+ngx_ssl_write_handler(ngx_event_t *wev)
+{
+ ngx_connection_t *c;
+
+ c = wev->data;
+
+ c->read->handler(c->read);
+}
+
+
+/*
+ * OpenSSL has no SSL_writev() so we copy several bufs into our 16K buffer
+ * before the SSL_write() call to decrease a SSL overhead.
+ *
+ * Besides for protocols such as HTTP it is possible to always buffer
+ * the output to decrease a SSL overhead some more.
+ */
+
+ngx_chain_t *
+ngx_ssl_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+ int n;
+ ngx_uint_t flush;
+ ssize_t send, size;
+ ngx_buf_t *buf;
+
+ if (!c->ssl->buffer) {
+
+ while (in) {
+ if (ngx_buf_special(in->buf)) {
+ in = in->next;
+ continue;
+ }
+
+ n = ngx_ssl_write(c, in->buf->pos, in->buf->last - in->buf->pos);
+
+ if (n == NGX_ERROR) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ if (n == NGX_AGAIN) {
+ c->buffered |= NGX_SSL_BUFFERED;
+ return in;
+ }
+
+ in->buf->pos += n;
+
+ if (in->buf->pos == in->buf->last) {
+ in = in->next;
+ }
+ }
+
+ return in;
+ }
+
+
+ /* the maximum limit size is the maximum int32_t value - the page size */
+
+ if (limit == 0 || limit > (off_t) (NGX_MAX_INT32_VALUE - ngx_pagesize)) {
+ limit = NGX_MAX_INT32_VALUE - ngx_pagesize;
+ }
+
+ buf = c->ssl->buf;
+
+ if (buf == NULL) {
+ buf = ngx_create_temp_buf(c->pool, NGX_SSL_BUFSIZE);
+ if (buf == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ c->ssl->buf = buf;
+ }
+
+ if (buf->start == NULL) {
+ buf->start = ngx_palloc(c->pool, NGX_SSL_BUFSIZE);
+ if (buf->start == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ buf->pos = buf->start;
+ buf->last = buf->start;
+ buf->end = buf->start + NGX_SSL_BUFSIZE;
+ }
+
+ send = 0;
+ flush = (in == NULL) ? 1 : 0;
+
+ for ( ;; ) {
+
+ while (in && buf->last < buf->end && send < limit) {
+ if (in->buf->last_buf || in->buf->flush) {
+ flush = 1;
+ }
+
+ if (ngx_buf_special(in->buf)) {
+ in = in->next;
+ continue;
+ }
+
+ size = in->buf->last - in->buf->pos;
+
+ if (size > buf->end - buf->last) {
+ size = buf->end - buf->last;
+ }
+
+ if (send + size > limit) {
+ size = (ssize_t) (limit - send);
+ flush = 1;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "SSL buf copy: %d", size);
+
+ ngx_memcpy(buf->last, in->buf->pos, size);
+
+ buf->last += size;
+ in->buf->pos += size;
+ send += size;
+
+ if (in->buf->pos == in->buf->last) {
+ in = in->next;
+ }
+ }
+
+ size = buf->last - buf->pos;
+
+ if (!flush && buf->last < buf->end && c->ssl->buffer) {
+ break;
+ }
+
+ n = ngx_ssl_write(c, buf->pos, size);
+
+ if (n == NGX_ERROR) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ if (n == NGX_AGAIN) {
+ c->buffered |= NGX_SSL_BUFFERED;
+ return in;
+ }
+
+ buf->pos += n;
+ c->sent += n;
+
+ if (n < size) {
+ break;
+ }
+
+ if (buf->pos == buf->last) {
+ buf->pos = buf->start;
+ buf->last = buf->start;
+ }
+
+ if (in == NULL || send == limit) {
+ break;
+ }
+ }
+
+ if (buf->pos < buf->last) {
+ c->buffered |= NGX_SSL_BUFFERED;
+
+ } else {
+ c->buffered &= ~NGX_SSL_BUFFERED;
+ }
+
+ return in;
+}
+
+
+ssize_t
+ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size)
+{
+ int n, sslerr;
+ ngx_err_t err;
+
+ ngx_ssl_clear_error(c->log);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL to write: %d", size);
+
+ n = SSL_write(c->ssl->connection, data, size);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_write: %d", n);
+
+ if (n > 0) {
+
+ if (c->ssl->saved_read_handler) {
+
+ c->read->handler = c->ssl->saved_read_handler;
+ c->ssl->saved_read_handler = NULL;
+ c->read->ready = 1;
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_post_event(c->read, &ngx_posted_events);
+ }
+
+ return n;
+ }
+
+ sslerr = SSL_get_error(c->ssl->connection, n);
+
+ err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
+
+ if (sslerr == SSL_ERROR_WANT_WRITE) {
+ c->write->ready = 0;
+ return NGX_AGAIN;
+ }
+
+ if (sslerr == SSL_ERROR_WANT_READ) {
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "peer started SSL renegotiation");
+
+ c->read->ready = 0;
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ /*
+ * we do not set the timer because there is already
+ * the write event timer
+ */
+
+ if (c->ssl->saved_read_handler == NULL) {
+ c->ssl->saved_read_handler = c->read->handler;
+ c->read->handler = ngx_ssl_read_handler;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ c->ssl->no_wait_shutdown = 1;
+ c->ssl->no_send_shutdown = 1;
+ c->write->error = 1;
+
+ ngx_ssl_connection_error(c, sslerr, err, "SSL_write() failed");
+
+ return NGX_ERROR;
+}
+
+
+static void
+ngx_ssl_read_handler(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+
+ c = rev->data;
+
+ c->write->handler(c->write);
+}
+
+
+void
+ngx_ssl_free_buffer(ngx_connection_t *c)
+{
+ if (c->ssl->buf && c->ssl->buf->start) {
+ if (ngx_pfree(c->pool, c->ssl->buf->start) == NGX_OK) {
+ c->ssl->buf->start = NULL;
+ }
+ }
+}
+
+
+ngx_int_t
+ngx_ssl_shutdown(ngx_connection_t *c)
+{
+ int n, sslerr, mode;
+ ngx_err_t err;
+
+ if (c->timedout) {
+ mode = SSL_RECEIVED_SHUTDOWN|SSL_SENT_SHUTDOWN;
+
+ } else {
+ mode = SSL_get_shutdown(c->ssl->connection);
+
+ if (c->ssl->no_wait_shutdown) {
+ mode |= SSL_RECEIVED_SHUTDOWN;
+ }
+
+ if (c->ssl->no_send_shutdown) {
+ mode |= SSL_SENT_SHUTDOWN;
+ }
+ }
+
+ SSL_set_shutdown(c->ssl->connection, mode);
+
+ ngx_ssl_clear_error(c->log);
+
+ n = SSL_shutdown(c->ssl->connection);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_shutdown: %d", n);
+
+ sslerr = 0;
+
+ /* SSL_shutdown() never returns -1, on error it returns 0 */
+
+ if (n != 1 && ERR_peek_error()) {
+ sslerr = SSL_get_error(c->ssl->connection, n);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "SSL_get_error: %d", sslerr);
+ }
+
+ if (n == 1 || sslerr == 0 || sslerr == SSL_ERROR_ZERO_RETURN) {
+ SSL_free(c->ssl->connection);
+ c->ssl = NULL;
+
+ return NGX_OK;
+ }
+
+ if (sslerr == SSL_ERROR_WANT_READ || sslerr == SSL_ERROR_WANT_WRITE) {
+ c->read->handler = ngx_ssl_shutdown_handler;
+ c->write->handler = ngx_ssl_shutdown_handler;
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (sslerr == SSL_ERROR_WANT_READ) {
+ ngx_add_timer(c->read, 30000);
+ }
+
+ return NGX_AGAIN;
+ }
+
+ err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
+
+ ngx_ssl_connection_error(c, sslerr, err, "SSL_shutdown() failed");
+
+ SSL_free(c->ssl->connection);
+ c->ssl = NULL;
+
+ return NGX_ERROR;
+}
+
+
+static void
+ngx_ssl_shutdown_handler(ngx_event_t *ev)
+{
+ ngx_connection_t *c;
+ ngx_connection_handler_pt handler;
+
+ c = ev->data;
+ handler = c->ssl->handler;
+
+ if (ev->timedout) {
+ c->timedout = 1;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "SSL shutdown handler");
+
+ if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
+ return;
+ }
+
+ handler(c);
+}
+
+
+static void
+ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, ngx_err_t err,
+ char *text)
+{
+ int n;
+ ngx_uint_t level;
+
+ level = NGX_LOG_CRIT;
+
+ if (sslerr == SSL_ERROR_SYSCALL) {
+
+ if (err == NGX_ECONNRESET
+ || err == NGX_EPIPE
+ || err == NGX_ENOTCONN
+ || err == NGX_ETIMEDOUT
+ || err == NGX_ECONNREFUSED
+ || err == NGX_ENETDOWN
+ || err == NGX_ENETUNREACH
+ || err == NGX_EHOSTDOWN
+ || err == NGX_EHOSTUNREACH)
+ {
+ switch (c->log_error) {
+
+ case NGX_ERROR_IGNORE_ECONNRESET:
+ case NGX_ERROR_INFO:
+ level = NGX_LOG_INFO;
+ break;
+
+ case NGX_ERROR_ERR:
+ level = NGX_LOG_ERR;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ } else if (sslerr == SSL_ERROR_SSL) {
+
+ n = ERR_GET_REASON(ERR_peek_error());
+
+ /* handshake failures */
+ if (n == SSL_R_BLOCK_CIPHER_PAD_IS_WRONG /* 129 */
+ || n == SSL_R_DIGEST_CHECK_FAILED /* 149 */
+ || n == SSL_R_LENGTH_MISMATCH /* 159 */
+ || n == SSL_R_NO_CIPHERS_PASSED /* 182 */
+ || n == SSL_R_NO_CIPHERS_SPECIFIED /* 183 */
+ || n == SSL_R_NO_SHARED_CIPHER /* 193 */
+ || n == SSL_R_RECORD_LENGTH_MISMATCH /* 213 */
+ || n == SSL_R_UNEXPECTED_MESSAGE /* 244 */
+ || n == SSL_R_UNEXPECTED_RECORD /* 245 */
+ || n == SSL_R_UNKNOWN_ALERT_TYPE /* 246 */
+ || n == SSL_R_UNKNOWN_PROTOCOL /* 252 */
+ || n == SSL_R_WRONG_VERSION_NUMBER /* 267 */
+ || n == SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC /* 281 */
+ || n == 1000 /* SSL_R_SSLV3_ALERT_CLOSE_NOTIFY */
+ || n == SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE /* 1010 */
+ || n == SSL_R_SSLV3_ALERT_BAD_RECORD_MAC /* 1020 */
+ || n == SSL_R_TLSV1_ALERT_DECRYPTION_FAILED /* 1021 */
+ || n == SSL_R_TLSV1_ALERT_RECORD_OVERFLOW /* 1022 */
+ || n == SSL_R_SSLV3_ALERT_DECOMPRESSION_FAILURE /* 1030 */
+ || n == SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE /* 1040 */
+ || n == SSL_R_SSLV3_ALERT_NO_CERTIFICATE /* 1041 */
+ || n == SSL_R_SSLV3_ALERT_BAD_CERTIFICATE /* 1042 */
+ || n == SSL_R_SSLV3_ALERT_UNSUPPORTED_CERTIFICATE /* 1043 */
+ || n == SSL_R_SSLV3_ALERT_CERTIFICATE_REVOKED /* 1044 */
+ || n == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED /* 1045 */
+ || n == SSL_R_SSLV3_ALERT_CERTIFICATE_UNKNOWN /* 1046 */
+ || n == SSL_R_SSLV3_ALERT_ILLEGAL_PARAMETER /* 1047 */
+ || n == SSL_R_TLSV1_ALERT_UNKNOWN_CA /* 1048 */
+ || n == SSL_R_TLSV1_ALERT_ACCESS_DENIED /* 1049 */
+ || n == SSL_R_TLSV1_ALERT_DECODE_ERROR /* 1050 */
+ || n == SSL_R_TLSV1_ALERT_DECRYPT_ERROR /* 1051 */
+ || n == SSL_R_TLSV1_ALERT_EXPORT_RESTRICTION /* 1060 */
+ || n == SSL_R_TLSV1_ALERT_PROTOCOL_VERSION /* 1070 */
+ || n == SSL_R_TLSV1_ALERT_INSUFFICIENT_SECURITY /* 1071 */
+ || n == SSL_R_TLSV1_ALERT_INTERNAL_ERROR /* 1080 */
+ || n == SSL_R_TLSV1_ALERT_USER_CANCELLED /* 1090 */
+ || n == SSL_R_TLSV1_ALERT_NO_RENEGOTIATION) /* 1100 */
+ {
+ switch (c->log_error) {
+
+ case NGX_ERROR_IGNORE_ECONNRESET:
+ case NGX_ERROR_INFO:
+ level = NGX_LOG_INFO;
+ break;
+
+ case NGX_ERROR_ERR:
+ level = NGX_LOG_ERR;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ ngx_ssl_error(level, c->log, err, text);
+}
+
+
+static void
+ngx_ssl_clear_error(ngx_log_t *log)
+{
+ while (ERR_peek_error()) {
+ ngx_ssl_error(NGX_LOG_ALERT, log, 0, "ignoring stale global SSL error");
+ }
+
+ ERR_clear_error();
+}
+
+
+void ngx_cdecl
+ngx_ssl_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, char *fmt, ...)
+{
+ u_long n;
+ va_list args;
+ u_char *p, *last;
+ u_char errstr[NGX_MAX_CONF_ERRSTR];
+
+ last = errstr + NGX_MAX_CONF_ERRSTR;
+
+ va_start(args, fmt);
+ p = ngx_vslprintf(errstr, last - 1, fmt, args);
+ va_end(args);
+
+ p = ngx_cpystrn(p, (u_char *) " (SSL:", last - p);
+
+ for ( ;; ) {
+
+ n = ERR_get_error();
+
+ if (n == 0) {
+ break;
+ }
+
+ if (p >= last) {
+ continue;
+ }
+
+ *p++ = ' ';
+
+ ERR_error_string_n(n, (char *) p, last - p);
+
+ while (p < last && *p) {
+ p++;
+ }
+ }
+
+ ngx_log_error(level, log, err, "%s)", errstr);
+}
+
+
+ngx_int_t
+ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx,
+ ssize_t builtin_session_cache, ngx_shm_zone_t *shm_zone, time_t timeout)
+{
+ long cache_mode;
+
+ if (builtin_session_cache == NGX_SSL_NO_SCACHE) {
+ SSL_CTX_set_session_cache_mode(ssl->ctx, SSL_SESS_CACHE_OFF);
+ return NGX_OK;
+ }
+
+ SSL_CTX_set_session_id_context(ssl->ctx, sess_ctx->data, sess_ctx->len);
+
+ if (builtin_session_cache == NGX_SSL_NONE_SCACHE) {
+
+ /*
+ * If the server explicitly says that it does not support
+ * session reuse (see SSL_SESS_CACHE_OFF above), then
+ * Outlook Express fails to upload a sent email to
+ * the Sent Items folder on the IMAP server via a separate IMAP
+ * connection in the background. Therefore we have a special
+ * mode (SSL_SESS_CACHE_SERVER|SSL_SESS_CACHE_NO_INTERNAL_STORE)
+ * where the server pretends that it supports session reuse,
+ * but it does not actually store any session.
+ */
+
+ SSL_CTX_set_session_cache_mode(ssl->ctx,
+ SSL_SESS_CACHE_SERVER
+ |SSL_SESS_CACHE_NO_AUTO_CLEAR
+ |SSL_SESS_CACHE_NO_INTERNAL_STORE);
+
+ SSL_CTX_sess_set_cache_size(ssl->ctx, 1);
+
+ return NGX_OK;
+ }
+
+ cache_mode = SSL_SESS_CACHE_SERVER;
+
+ if (shm_zone && builtin_session_cache == NGX_SSL_NO_BUILTIN_SCACHE) {
+ cache_mode |= SSL_SESS_CACHE_NO_INTERNAL;
+ }
+
+ SSL_CTX_set_session_cache_mode(ssl->ctx, cache_mode);
+
+ if (builtin_session_cache != NGX_SSL_NO_BUILTIN_SCACHE) {
+
+ if (builtin_session_cache != NGX_SSL_DFLT_BUILTIN_SCACHE) {
+ SSL_CTX_sess_set_cache_size(ssl->ctx, builtin_session_cache);
+ }
+ }
+
+ SSL_CTX_set_timeout(ssl->ctx, (long) timeout);
+
+ if (shm_zone) {
+ shm_zone->init = ngx_ssl_session_cache_init;
+
+ SSL_CTX_sess_set_new_cb(ssl->ctx, ngx_ssl_new_session);
+ SSL_CTX_sess_set_get_cb(ssl->ctx, ngx_ssl_get_cached_session);
+ SSL_CTX_sess_set_remove_cb(ssl->ctx, ngx_ssl_remove_session);
+
+ if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_session_cache_index, shm_zone)
+ == 0)
+ {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "SSL_CTX_set_ex_data() failed");
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone, void *data)
+{
+ size_t len;
+ ngx_slab_pool_t *shpool;
+ ngx_ssl_session_cache_t *cache;
+
+ if (data) {
+ shm_zone->data = data;
+ return NGX_OK;
+ }
+
+ if (shm_zone->shm.exists) {
+ shm_zone->data = data;
+ return NGX_OK;
+ }
+
+ shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+ cache = ngx_slab_alloc(shpool, sizeof(ngx_ssl_session_cache_t));
+ if (cache == NULL) {
+ return NGX_ERROR;
+ }
+
+ shpool->data = cache;
+ shm_zone->data = cache;
+
+ ngx_rbtree_init(&cache->session_rbtree, &cache->sentinel,
+ ngx_ssl_session_rbtree_insert_value);
+
+ ngx_queue_init(&cache->expire_queue);
+
+ len = sizeof(" in SSL session shared cache \"\"") + shm_zone->shm.name.len;
+
+ shpool->log_ctx = ngx_slab_alloc(shpool, len);
+ if (shpool->log_ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_sprintf(shpool->log_ctx, " in SSL session shared cache \"%V\"%Z",
+ &shm_zone->shm.name);
+
+ return NGX_OK;
+}
+
+
+/*
+ * The length of the session id is 16 bytes for SSLv2 sessions and
+ * between 1 and 32 bytes for SSLv3/TLSv1, typically 32 bytes.
+ * It seems that the typical length of the external ASN1 representation
+ * of a session is 118 or 119 bytes for SSLv3/TSLv1.
+ *
+ * Thus on 32-bit platforms we allocate separately an rbtree node,
+ * a session id, and an ASN1 representation, they take accordingly
+ * 64, 32, and 128 bytes.
+ *
+ * On 64-bit platforms we allocate separately an rbtree node + session_id,
+ * and an ASN1 representation, they take accordingly 128 and 128 bytes.
+ *
+ * OpenSSL's i2d_SSL_SESSION() and d2i_SSL_SESSION are slow,
+ * so they are outside the code locked by shared pool mutex
+ */
+
+static int
+ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess)
+{
+ int len;
+ u_char *p, *id, *cached_sess;
+ uint32_t hash;
+ SSL_CTX *ssl_ctx;
+ ngx_shm_zone_t *shm_zone;
+ ngx_connection_t *c;
+ ngx_slab_pool_t *shpool;
+ ngx_ssl_sess_id_t *sess_id;
+ ngx_ssl_session_cache_t *cache;
+ u_char buf[NGX_SSL_MAX_SESSION_SIZE];
+
+ len = i2d_SSL_SESSION(sess, NULL);
+
+ /* do not cache too big session */
+
+ if (len > (int) NGX_SSL_MAX_SESSION_SIZE) {
+ return 0;
+ }
+
+ p = buf;
+ i2d_SSL_SESSION(sess, &p);
+
+ c = ngx_ssl_get_connection(ssl_conn);
+
+ ssl_ctx = SSL_get_SSL_CTX(ssl_conn);
+ shm_zone = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_cache_index);
+
+ cache = shm_zone->data;
+ shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+ ngx_shmtx_lock(&shpool->mutex);
+
+ /* drop one or two expired sessions */
+ ngx_ssl_expire_sessions(cache, shpool, 1);
+
+ cached_sess = ngx_slab_alloc_locked(shpool, len);
+
+ if (cached_sess == NULL) {
+
+ /* drop the oldest non-expired session and try once more */
+
+ ngx_ssl_expire_sessions(cache, shpool, 0);
+
+ cached_sess = ngx_slab_alloc_locked(shpool, len);
+
+ if (cached_sess == NULL) {
+ sess_id = NULL;
+ goto failed;
+ }
+ }
+
+ sess_id = ngx_slab_alloc_locked(shpool, sizeof(ngx_ssl_sess_id_t));
+ if (sess_id == NULL) {
+ goto failed;
+ }
+
+#if (NGX_PTR_SIZE == 8)
+
+ id = sess_id->sess_id;
+
+#else
+
+ id = ngx_slab_alloc_locked(shpool, sess->session_id_length);
+ if (id == NULL) {
+ goto failed;
+ }
+
+#endif
+
+ ngx_memcpy(cached_sess, buf, len);
+
+ ngx_memcpy(id, sess->session_id, sess->session_id_length);
+
+ hash = ngx_crc32_short(sess->session_id, sess->session_id_length);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "ssl new session: %08XD:%d:%d",
+ hash, sess->session_id_length, len);
+
+ sess_id->node.key = hash;
+ sess_id->node.data = (u_char) sess->session_id_length;
+ sess_id->id = id;
+ sess_id->len = len;
+ sess_id->session = cached_sess;
+
+ sess_id->expire = ngx_time() + SSL_CTX_get_timeout(ssl_ctx);
+
+ ngx_queue_insert_head(&cache->expire_queue, &sess_id->queue);
+
+ ngx_rbtree_insert(&cache->session_rbtree, &sess_id->node);
+
+ ngx_shmtx_unlock(&shpool->mutex);
+
+ return 0;
+
+failed:
+
+ if (cached_sess) {
+ ngx_slab_free_locked(shpool, cached_sess);
+ }
+
+ if (sess_id) {
+ ngx_slab_free_locked(shpool, sess_id);
+ }
+
+ ngx_shmtx_unlock(&shpool->mutex);
+
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "could not add new SSL session to the session cache");
+
+ return 0;
+}
+
+
+static ngx_ssl_session_t *
+ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn, u_char *id, int len,
+ int *copy)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x0090707fL
+ const
+#endif
+ u_char *p;
+ uint32_t hash;
+ ngx_int_t rc;
+ ngx_shm_zone_t *shm_zone;
+ ngx_slab_pool_t *shpool;
+ ngx_rbtree_node_t *node, *sentinel;
+ ngx_ssl_session_t *sess;
+ ngx_ssl_sess_id_t *sess_id;
+ ngx_ssl_session_cache_t *cache;
+ u_char buf[NGX_SSL_MAX_SESSION_SIZE];
+#if (NGX_DEBUG)
+ ngx_connection_t *c;
+#endif
+
+ hash = ngx_crc32_short(id, (size_t) len);
+ *copy = 0;
+
+#if (NGX_DEBUG)
+ c = ngx_ssl_get_connection(ssl_conn);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "ssl get session: %08XD:%d", hash, len);
+#endif
+
+ shm_zone = SSL_CTX_get_ex_data(SSL_get_SSL_CTX(ssl_conn),
+ ngx_ssl_session_cache_index);
+
+ cache = shm_zone->data;
+
+ sess = NULL;
+
+ shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+ ngx_shmtx_lock(&shpool->mutex);
+
+ node = cache->session_rbtree.root;
+ sentinel = cache->session_rbtree.sentinel;
+
+ while (node != sentinel) {
+
+ if (hash < node->key) {
+ node = node->left;
+ continue;
+ }
+
+ if (hash > node->key) {
+ node = node->right;
+ continue;
+ }
+
+ /* hash == node->key */
+
+ do {
+ sess_id = (ngx_ssl_sess_id_t *) node;
+
+ rc = ngx_memn2cmp(id, sess_id->id,
+ (size_t) len, (size_t) node->data);
+ if (rc == 0) {
+
+ if (sess_id->expire > ngx_time()) {
+ ngx_memcpy(buf, sess_id->session, sess_id->len);
+
+ ngx_shmtx_unlock(&shpool->mutex);
+
+ p = buf;
+ sess = d2i_SSL_SESSION(NULL, &p, sess_id->len);
+
+ return sess;
+ }
+
+ ngx_queue_remove(&sess_id->queue);
+
+ ngx_rbtree_delete(&cache->session_rbtree, node);
+
+ ngx_slab_free_locked(shpool, sess_id->session);
+#if (NGX_PTR_SIZE == 4)
+ ngx_slab_free_locked(shpool, sess_id->id);
+#endif
+ ngx_slab_free_locked(shpool, sess_id);
+
+ sess = NULL;
+
+ goto done;
+ }
+
+ node = (rc < 0) ? node->left : node->right;
+
+ } while (node != sentinel && hash == node->key);
+
+ break;
+ }
+
+done:
+
+ ngx_shmtx_unlock(&shpool->mutex);
+
+ return sess;
+}
+
+
+void
+ngx_ssl_remove_cached_session(SSL_CTX *ssl, ngx_ssl_session_t *sess)
+{
+ SSL_CTX_remove_session(ssl, sess);
+
+ ngx_ssl_remove_session(ssl, sess);
+}
+
+
+static void
+ngx_ssl_remove_session(SSL_CTX *ssl, ngx_ssl_session_t *sess)
+{
+ size_t len;
+ u_char *id;
+ uint32_t hash;
+ ngx_int_t rc;
+ ngx_shm_zone_t *shm_zone;
+ ngx_slab_pool_t *shpool;
+ ngx_rbtree_node_t *node, *sentinel;
+ ngx_ssl_sess_id_t *sess_id;
+ ngx_ssl_session_cache_t *cache;
+
+ shm_zone = SSL_CTX_get_ex_data(ssl, ngx_ssl_session_cache_index);
+
+ if (shm_zone == NULL) {
+ return;
+ }
+
+ cache = shm_zone->data;
+
+ id = sess->session_id;
+ len = (size_t) sess->session_id_length;
+
+ hash = ngx_crc32_short(id, len);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
+ "ssl remove session: %08XD:%uz", hash, len);
+
+ shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+ ngx_shmtx_lock(&shpool->mutex);
+
+ node = cache->session_rbtree.root;
+ sentinel = cache->session_rbtree.sentinel;
+
+ while (node != sentinel) {
+
+ if (hash < node->key) {
+ node = node->left;
+ continue;
+ }
+
+ if (hash > node->key) {
+ node = node->right;
+ continue;
+ }
+
+ /* hash == node->key */
+
+ do {
+ sess_id = (ngx_ssl_sess_id_t *) node;
+
+ rc = ngx_memn2cmp(id, sess_id->id, len, (size_t) node->data);
+
+ if (rc == 0) {
+
+ ngx_queue_remove(&sess_id->queue);
+
+ ngx_rbtree_delete(&cache->session_rbtree, node);
+
+ ngx_slab_free_locked(shpool, sess_id->session);
+#if (NGX_PTR_SIZE == 4)
+ ngx_slab_free_locked(shpool, sess_id->id);
+#endif
+ ngx_slab_free_locked(shpool, sess_id);
+
+ goto done;
+ }
+
+ node = (rc < 0) ? node->left : node->right;
+
+ } while (node != sentinel && hash == node->key);
+
+ break;
+ }
+
+done:
+
+ ngx_shmtx_unlock(&shpool->mutex);
+}
+
+
+static void
+ngx_ssl_expire_sessions(ngx_ssl_session_cache_t *cache,
+ ngx_slab_pool_t *shpool, ngx_uint_t n)
+{
+ time_t now;
+ ngx_queue_t *q;
+ ngx_ssl_sess_id_t *sess_id;
+
+ now = ngx_time();
+
+ while (n < 3) {
+
+ if (ngx_queue_empty(&cache->expire_queue)) {
+ return;
+ }
+
+ q = ngx_queue_last(&cache->expire_queue);
+
+ sess_id = ngx_queue_data(q, ngx_ssl_sess_id_t, queue);
+
+ if (n++ != 0 && sess_id->expire > now) {
+ return;
+ }
+
+ ngx_queue_remove(q);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
+ "expire session: %08Xi", sess_id->node.key);
+
+ ngx_rbtree_delete(&cache->session_rbtree, &sess_id->node);
+
+ ngx_slab_free_locked(shpool, sess_id->session);
+#if (NGX_PTR_SIZE == 4)
+ ngx_slab_free_locked(shpool, sess_id->id);
+#endif
+ ngx_slab_free_locked(shpool, sess_id);
+ }
+}
+
+
+static void
+ngx_ssl_session_rbtree_insert_value(ngx_rbtree_node_t *temp,
+ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+ ngx_rbtree_node_t **p;
+ ngx_ssl_sess_id_t *sess_id, *sess_id_temp;
+
+ for ( ;; ) {
+
+ if (node->key < temp->key) {
+
+ p = &temp->left;
+
+ } else if (node->key > temp->key) {
+
+ p = &temp->right;
+
+ } else { /* node->key == temp->key */
+
+ sess_id = (ngx_ssl_sess_id_t *) node;
+ sess_id_temp = (ngx_ssl_sess_id_t *) temp;
+
+ p = (ngx_memn2cmp(sess_id->id, sess_id_temp->id,
+ (size_t) node->data, (size_t) temp->data)
+ < 0) ? &temp->left : &temp->right;
+ }
+
+ if (*p == sentinel) {
+ break;
+ }
+
+ temp = *p;
+ }
+
+ *p = node;
+ node->parent = temp;
+ node->left = sentinel;
+ node->right = sentinel;
+ ngx_rbt_red(node);
+}
+
+
+void
+ngx_ssl_cleanup_ctx(void *data)
+{
+ ngx_ssl_t *ssl = data;
+
+ SSL_CTX_free(ssl->ctx);
+}
+
+
+ngx_int_t
+ngx_ssl_get_protocol(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+ s->data = (u_char *) SSL_get_version(c->ssl->connection);
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_cipher_name(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+ s->data = (u_char *) SSL_get_cipher_name(c->ssl->connection);
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_session_id(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+ int len;
+ u_char *p, *buf;
+ SSL_SESSION *sess;
+
+ sess = SSL_get0_session(c->ssl->connection);
+
+ len = i2d_SSL_SESSION(sess, NULL);
+
+ buf = ngx_alloc(len, c->log);
+ if (buf == NULL) {
+ return NGX_ERROR;
+ }
+
+ s->len = 2 * len;
+ s->data = ngx_pnalloc(pool, 2 * len);
+ if (s->data == NULL) {
+ ngx_free(buf);
+ return NGX_ERROR;
+ }
+
+ p = buf;
+ i2d_SSL_SESSION(sess, &p);
+
+ ngx_hex_dump(s->data, buf, len);
+
+ ngx_free(buf);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_raw_certificate(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+ size_t len;
+ BIO *bio;
+ X509 *cert;
+
+ s->len = 0;
+
+ cert = SSL_get_peer_certificate(c->ssl->connection);
+ if (cert == NULL) {
+ return NGX_OK;
+ }
+
+ bio = BIO_new(BIO_s_mem());
+ if (bio == NULL) {
+ ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "BIO_new() failed");
+ X509_free(cert);
+ return NGX_ERROR;
+ }
+
+ if (PEM_write_bio_X509(bio, cert) == 0) {
+ ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "PEM_write_bio_X509() failed");
+ goto failed;
+ }
+
+ len = BIO_pending(bio);
+ s->len = len;
+
+ s->data = ngx_pnalloc(pool, len);
+ if (s->data == NULL) {
+ goto failed;
+ }
+
+ BIO_read(bio, s->data, len);
+
+ BIO_free(bio);
+ X509_free(cert);
+
+ return NGX_OK;
+
+failed:
+
+ BIO_free(bio);
+ X509_free(cert);
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_ssl_get_certificate(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+ u_char *p;
+ size_t len;
+ ngx_uint_t i;
+ ngx_str_t cert;
+
+ if (ngx_ssl_get_raw_certificate(c, pool, &cert) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (cert.len == 0) {
+ s->len = 0;
+ return NGX_OK;
+ }
+
+ len = cert.len - 1;
+
+ for (i = 0; i < cert.len - 1; i++) {
+ if (cert.data[i] == LF) {
+ len++;
+ }
+ }
+
+ s->len = len;
+ s->data = ngx_pnalloc(pool, len);
+ if (s->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = s->data;
+
+ for (i = 0; i < cert.len - 1; i++) {
+ *p++ = cert.data[i];
+ if (cert.data[i] == LF) {
+ *p++ = '\t';
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_subject_dn(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+ char *p;
+ size_t len;
+ X509 *cert;
+ X509_NAME *name;
+
+ s->len = 0;
+
+ cert = SSL_get_peer_certificate(c->ssl->connection);
+ if (cert == NULL) {
+ return NGX_OK;
+ }
+
+ name = X509_get_subject_name(cert);
+ if (name == NULL) {
+ X509_free(cert);
+ return NGX_ERROR;
+ }
+
+ p = X509_NAME_oneline(name, NULL, 0);
+
+ for (len = 0; p[len]; len++) { /* void */ }
+
+ s->len = len;
+ s->data = ngx_pnalloc(pool, len);
+ if (s->data == NULL) {
+ OPENSSL_free(p);
+ X509_free(cert);
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->data, p, len);
+
+ OPENSSL_free(p);
+ X509_free(cert);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_issuer_dn(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+ char *p;
+ size_t len;
+ X509 *cert;
+ X509_NAME *name;
+
+ s->len = 0;
+
+ cert = SSL_get_peer_certificate(c->ssl->connection);
+ if (cert == NULL) {
+ return NGX_OK;
+ }
+
+ name = X509_get_issuer_name(cert);
+ if (name == NULL) {
+ X509_free(cert);
+ return NGX_ERROR;
+ }
+
+ p = X509_NAME_oneline(name, NULL, 0);
+
+ for (len = 0; p[len]; len++) { /* void */ }
+
+ s->len = len;
+ s->data = ngx_pnalloc(pool, len);
+ if (s->data == NULL) {
+ OPENSSL_free(p);
+ X509_free(cert);
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->data, p, len);
+
+ OPENSSL_free(p);
+ X509_free(cert);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_serial_number(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+ size_t len;
+ X509 *cert;
+ BIO *bio;
+
+ s->len = 0;
+
+ cert = SSL_get_peer_certificate(c->ssl->connection);
+ if (cert == NULL) {
+ return NGX_OK;
+ }
+
+ bio = BIO_new(BIO_s_mem());
+ if (bio == NULL) {
+ X509_free(cert);
+ return NGX_ERROR;
+ }
+
+ i2a_ASN1_INTEGER(bio, X509_get_serialNumber(cert));
+ len = BIO_pending(bio);
+
+ s->len = len;
+ s->data = ngx_pnalloc(pool, len);
+ if (s->data == NULL) {
+ BIO_free(bio);
+ X509_free(cert);
+ return NGX_ERROR;
+ }
+
+ BIO_read(bio, s->data, len);
+ BIO_free(bio);
+ X509_free(cert);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_client_verify(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+ X509 *cert;
+
+ if (SSL_get_verify_result(c->ssl->connection) != X509_V_OK) {
+ ngx_str_set(s, "FAILED");
+ return NGX_OK;
+ }
+
+ cert = SSL_get_peer_certificate(c->ssl->connection);
+
+ if (cert) {
+ ngx_str_set(s, "SUCCESS");
+
+ } else {
+ ngx_str_set(s, "NONE");
+ }
+
+ X509_free(cert);
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_openssl_create_conf(ngx_cycle_t *cycle)
+{
+ ngx_openssl_conf_t *oscf;
+
+ oscf = ngx_pcalloc(cycle->pool, sizeof(ngx_openssl_conf_t));
+ if (oscf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * oscf->engine = 0;
+ */
+
+ return oscf;
+}
+
+
+static char *
+ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_openssl_conf_t *oscf = conf;
+
+ ENGINE *engine;
+ ngx_str_t *value;
+
+ if (oscf->engine) {
+ return "is duplicate";
+ }
+
+ oscf->engine = 1;
+
+ value = cf->args->elts;
+
+ engine = ENGINE_by_id((const char *) value[1].data);
+
+ if (engine == NULL) {
+ ngx_ssl_error(NGX_LOG_WARN, cf->log, 0,
+ "ENGINE_by_id(\"%V\") failed", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (ENGINE_set_default(engine, ENGINE_METHOD_ALL) == 0) {
+ ngx_ssl_error(NGX_LOG_WARN, cf->log, 0,
+ "ENGINE_set_default(\"%V\", ENGINE_METHOD_ALL) failed",
+ &value[1]);
+
+ ENGINE_free(engine);
+
+ return NGX_CONF_ERROR;
+ }
+
+ ENGINE_free(engine);
+
+ return NGX_CONF_OK;
+}
+
+
+static void
+ngx_openssl_exit(ngx_cycle_t *cycle)
+{
+ EVP_cleanup();
+ ENGINE_cleanup();
+}
diff --git a/usr.sbin/nginx/src/event/ngx_event_openssl.h b/usr.sbin/nginx/src/event/ngx_event_openssl.h
new file mode 100644
index 00000000000..204d5f08ed4
--- /dev/null
+++ b/usr.sbin/nginx/src/event/ngx_event_openssl.h
@@ -0,0 +1,158 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_EVENT_OPENSSL_H_INCLUDED_
+#define _NGX_EVENT_OPENSSL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/conf.h>
+#include <openssl/engine.h>
+#include <openssl/evp.h>
+
+#define NGX_SSL_NAME "OpenSSL"
+
+
+#define ngx_ssl_session_t SSL_SESSION
+#define ngx_ssl_conn_t SSL
+
+
+typedef struct {
+ SSL_CTX *ctx;
+ ngx_log_t *log;
+} ngx_ssl_t;
+
+
+typedef struct {
+ ngx_ssl_conn_t *connection;
+
+ ngx_int_t last;
+ ngx_buf_t *buf;
+
+ ngx_connection_handler_pt handler;
+
+ ngx_event_handler_pt saved_read_handler;
+ ngx_event_handler_pt saved_write_handler;
+
+ unsigned handshaked:1;
+ unsigned renegotiation:1;
+ unsigned buffer:1;
+ unsigned no_wait_shutdown:1;
+ unsigned no_send_shutdown:1;
+} ngx_ssl_connection_t;
+
+
+#define NGX_SSL_NO_SCACHE -2
+#define NGX_SSL_NONE_SCACHE -3
+#define NGX_SSL_NO_BUILTIN_SCACHE -4
+#define NGX_SSL_DFLT_BUILTIN_SCACHE -5
+
+
+#define NGX_SSL_MAX_SESSION_SIZE 4096
+
+typedef struct ngx_ssl_sess_id_s ngx_ssl_sess_id_t;
+
+struct ngx_ssl_sess_id_s {
+ ngx_rbtree_node_t node;
+ u_char *id;
+ size_t len;
+ u_char *session;
+ ngx_queue_t queue;
+ time_t expire;
+#if (NGX_PTR_SIZE == 8)
+ void *stub;
+ u_char sess_id[32];
+#endif
+};
+
+
+typedef struct {
+ ngx_rbtree_t session_rbtree;
+ ngx_rbtree_node_t sentinel;
+ ngx_queue_t expire_queue;
+} ngx_ssl_session_cache_t;
+
+
+
+#define NGX_SSL_SSLv2 2
+#define NGX_SSL_SSLv3 4
+#define NGX_SSL_TLSv1 8
+
+
+#define NGX_SSL_BUFFER 1
+#define NGX_SSL_CLIENT 2
+
+#define NGX_SSL_BUFSIZE 16384
+
+
+ngx_int_t ngx_ssl_init(ngx_log_t *log);
+ngx_int_t ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data);
+ngx_int_t ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
+ ngx_str_t *cert, ngx_str_t *key);
+ngx_int_t ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
+ ngx_str_t *cert, ngx_int_t depth);
+ngx_int_t ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *crl);
+RSA *ngx_ssl_rsa512_key_callback(SSL *ssl, int is_export, int key_length);
+ngx_int_t ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file);
+ngx_int_t ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name);
+ngx_int_t ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx,
+ ssize_t builtin_session_cache, ngx_shm_zone_t *shm_zone, time_t timeout);
+ngx_int_t ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c,
+ ngx_uint_t flags);
+
+void ngx_ssl_remove_cached_session(SSL_CTX *ssl, ngx_ssl_session_t *sess);
+ngx_int_t ngx_ssl_set_session(ngx_connection_t *c, ngx_ssl_session_t *session);
+#define ngx_ssl_get_session(c) SSL_get1_session(c->ssl->connection)
+#define ngx_ssl_free_session SSL_SESSION_free
+#define ngx_ssl_get_connection(ssl_conn) \
+ SSL_get_ex_data(ssl_conn, ngx_ssl_connection_index)
+#define ngx_ssl_get_server_conf(ssl_ctx) \
+ SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_server_conf_index)
+
+
+ngx_int_t ngx_ssl_get_protocol(ngx_connection_t *c, ngx_pool_t *pool,
+ ngx_str_t *s);
+ngx_int_t ngx_ssl_get_cipher_name(ngx_connection_t *c, ngx_pool_t *pool,
+ ngx_str_t *s);
+ngx_int_t ngx_ssl_get_session_id(ngx_connection_t *c, ngx_pool_t *pool,
+ ngx_str_t *s);
+ngx_int_t ngx_ssl_get_raw_certificate(ngx_connection_t *c, ngx_pool_t *pool,
+ ngx_str_t *s);
+ngx_int_t ngx_ssl_get_certificate(ngx_connection_t *c, ngx_pool_t *pool,
+ ngx_str_t *s);
+ngx_int_t ngx_ssl_get_subject_dn(ngx_connection_t *c, ngx_pool_t *pool,
+ ngx_str_t *s);
+ngx_int_t ngx_ssl_get_issuer_dn(ngx_connection_t *c, ngx_pool_t *pool,
+ ngx_str_t *s);
+ngx_int_t ngx_ssl_get_serial_number(ngx_connection_t *c, ngx_pool_t *pool,
+ ngx_str_t *s);
+ngx_int_t ngx_ssl_get_client_verify(ngx_connection_t *c, ngx_pool_t *pool,
+ ngx_str_t *s);
+
+
+ngx_int_t ngx_ssl_handshake(ngx_connection_t *c);
+ssize_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size);
+ssize_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size);
+ssize_t ngx_ssl_recv_chain(ngx_connection_t *c, ngx_chain_t *cl);
+ngx_chain_t *ngx_ssl_send_chain(ngx_connection_t *c, ngx_chain_t *in,
+ off_t limit);
+void ngx_ssl_free_buffer(ngx_connection_t *c);
+ngx_int_t ngx_ssl_shutdown(ngx_connection_t *c);
+void ngx_cdecl ngx_ssl_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+ char *fmt, ...);
+void ngx_ssl_cleanup_ctx(void *data);
+
+
+extern int ngx_ssl_connection_index;
+extern int ngx_ssl_server_conf_index;
+extern int ngx_ssl_session_cache_index;
+
+
+#endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/event/ngx_event_pipe.c b/usr.sbin/nginx/src/event/ngx_event_pipe.c
new file mode 100644
index 00000000000..d01b204463f
--- /dev/null
+++ b/usr.sbin/nginx/src/event/ngx_event_pipe.c
@@ -0,0 +1,995 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_pipe.h>
+
+
+static ngx_int_t ngx_event_pipe_read_upstream(ngx_event_pipe_t *p);
+static ngx_int_t ngx_event_pipe_write_to_downstream(ngx_event_pipe_t *p);
+
+static ngx_int_t ngx_event_pipe_write_chain_to_temp_file(ngx_event_pipe_t *p);
+static ngx_inline void ngx_event_pipe_remove_shadow_links(ngx_buf_t *buf);
+static ngx_inline void ngx_event_pipe_free_shadow_raw_buf(ngx_chain_t **free,
+ ngx_buf_t *buf);
+static ngx_int_t ngx_event_pipe_drain_chains(ngx_event_pipe_t *p);
+
+
+ngx_int_t
+ngx_event_pipe(ngx_event_pipe_t *p, ngx_int_t do_write)
+{
+ u_int flags;
+ ngx_int_t rc;
+ ngx_event_t *rev, *wev;
+
+ for ( ;; ) {
+ if (do_write) {
+ p->log->action = "sending to client";
+
+ rc = ngx_event_pipe_write_to_downstream(p);
+
+ if (rc == NGX_ABORT) {
+ return NGX_ABORT;
+ }
+
+ if (rc == NGX_BUSY) {
+ return NGX_OK;
+ }
+ }
+
+ p->read = 0;
+ p->upstream_blocked = 0;
+
+ p->log->action = "reading upstream";
+
+ if (ngx_event_pipe_read_upstream(p) == NGX_ABORT) {
+ return NGX_ABORT;
+ }
+
+ if (!p->read && !p->upstream_blocked) {
+ break;
+ }
+
+ do_write = 1;
+ }
+
+ if (p->upstream->fd != -1) {
+ rev = p->upstream->read;
+
+ flags = (rev->eof || rev->error) ? NGX_CLOSE_EVENT : 0;
+
+ if (ngx_handle_read_event(rev, flags) != NGX_OK) {
+ return NGX_ABORT;
+ }
+
+ if (rev->active && !rev->ready) {
+ ngx_add_timer(rev, p->read_timeout);
+
+ } else if (rev->timer_set) {
+ ngx_del_timer(rev);
+ }
+ }
+
+ if (p->downstream->fd != -1 && p->downstream->data == p->output_ctx) {
+ wev = p->downstream->write;
+ if (ngx_handle_write_event(wev, p->send_lowat) != NGX_OK) {
+ return NGX_ABORT;
+ }
+
+ if (!wev->delayed) {
+ if (wev->active && !wev->ready) {
+ ngx_add_timer(wev, p->send_timeout);
+
+ } else if (wev->timer_set) {
+ ngx_del_timer(wev);
+ }
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_event_pipe_read_upstream(ngx_event_pipe_t *p)
+{
+ ssize_t n, size;
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_chain_t *chain, *cl, *ln;
+
+ if (p->upstream_eof || p->upstream_error || p->upstream_done) {
+ return NGX_OK;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe read upstream: %d", p->upstream->read->ready);
+
+ for ( ;; ) {
+
+ if (p->upstream_eof || p->upstream_error || p->upstream_done) {
+ break;
+ }
+
+ if (p->preread_bufs == NULL && !p->upstream->read->ready) {
+ break;
+ }
+
+ if (p->preread_bufs) {
+
+ /* use the pre-read bufs if they exist */
+
+ chain = p->preread_bufs;
+ p->preread_bufs = NULL;
+ n = p->preread_size;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe preread: %z", n);
+
+ if (n) {
+ p->read = 1;
+ }
+
+ } else {
+
+#if (NGX_HAVE_KQUEUE)
+
+ /*
+ * kqueue notifies about the end of file or a pending error.
+ * This test allows not to allocate a buf on these conditions
+ * and not to call c->recv_chain().
+ */
+
+ if (p->upstream->read->available == 0
+ && p->upstream->read->pending_eof)
+ {
+ p->upstream->read->ready = 0;
+ p->upstream->read->eof = 0;
+ p->upstream_eof = 1;
+ p->read = 1;
+
+ if (p->upstream->read->kq_errno) {
+ p->upstream->read->error = 1;
+ p->upstream_error = 1;
+ p->upstream_eof = 0;
+
+ ngx_log_error(NGX_LOG_ERR, p->log,
+ p->upstream->read->kq_errno,
+ "kevent() reported that upstream "
+ "closed connection");
+ }
+
+ break;
+ }
+#endif
+
+ if (p->free_raw_bufs) {
+
+ /* use the free bufs if they exist */
+
+ chain = p->free_raw_bufs;
+ if (p->single_buf) {
+ p->free_raw_bufs = p->free_raw_bufs->next;
+ chain->next = NULL;
+ } else {
+ p->free_raw_bufs = NULL;
+ }
+
+ } else if (p->allocated < p->bufs.num) {
+
+ /* allocate a new buf if it's still allowed */
+
+ b = ngx_create_temp_buf(p->pool, p->bufs.size);
+ if (b == NULL) {
+ return NGX_ABORT;
+ }
+
+ p->allocated++;
+
+ chain = ngx_alloc_chain_link(p->pool);
+ if (chain == NULL) {
+ return NGX_ABORT;
+ }
+
+ chain->buf = b;
+ chain->next = NULL;
+
+ } else if (!p->cacheable
+ && p->downstream->data == p->output_ctx
+ && p->downstream->write->ready
+ && !p->downstream->write->delayed)
+ {
+ /*
+ * if the bufs are not needed to be saved in a cache and
+ * a downstream is ready then write the bufs to a downstream
+ */
+
+ p->upstream_blocked = 1;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe downstream ready");
+
+ break;
+
+ } else if (p->cacheable
+ || p->temp_file->offset < p->max_temp_file_size)
+ {
+
+ /*
+ * if it is allowed, then save some bufs from r->in
+ * to a temporary file, and add them to a r->out chain
+ */
+
+ rc = ngx_event_pipe_write_chain_to_temp_file(p);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe temp offset: %O", p->temp_file->offset);
+
+ if (rc == NGX_BUSY) {
+ break;
+ }
+
+ if (rc == NGX_AGAIN) {
+ if (ngx_event_flags & NGX_USE_LEVEL_EVENT
+ && p->upstream->read->active
+ && p->upstream->read->ready)
+ {
+ if (ngx_del_event(p->upstream->read, NGX_READ_EVENT, 0)
+ == NGX_ERROR)
+ {
+ return NGX_ABORT;
+ }
+ }
+ }
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ chain = p->free_raw_bufs;
+ if (p->single_buf) {
+ p->free_raw_bufs = p->free_raw_bufs->next;
+ chain->next = NULL;
+ } else {
+ p->free_raw_bufs = NULL;
+ }
+
+ } else {
+
+ /* there are no bufs to read in */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "no pipe bufs to read in");
+
+ break;
+ }
+
+ n = p->upstream->recv_chain(p->upstream, chain);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe recv chain: %z", n);
+
+ if (p->free_raw_bufs) {
+ chain->next = p->free_raw_bufs;
+ }
+ p->free_raw_bufs = chain;
+
+ if (n == NGX_ERROR) {
+ p->upstream_error = 1;
+ return NGX_ERROR;
+ }
+
+ if (n == NGX_AGAIN) {
+ if (p->single_buf) {
+ ngx_event_pipe_remove_shadow_links(chain->buf);
+ }
+
+ break;
+ }
+
+ p->read = 1;
+
+ if (n == 0) {
+ p->upstream_eof = 1;
+ break;
+ }
+ }
+
+ p->read_length += n;
+ cl = chain;
+ p->free_raw_bufs = NULL;
+
+ while (cl && n > 0) {
+
+ ngx_event_pipe_remove_shadow_links(cl->buf);
+
+ size = cl->buf->end - cl->buf->last;
+
+ if (n >= size) {
+ cl->buf->last = cl->buf->end;
+
+ /* STUB */ cl->buf->num = p->num++;
+
+ if (p->input_filter(p, cl->buf) == NGX_ERROR) {
+ return NGX_ABORT;
+ }
+
+ n -= size;
+ ln = cl;
+ cl = cl->next;
+ ngx_free_chain(p->pool, ln);
+
+ } else {
+ cl->buf->last += n;
+ n = 0;
+ }
+ }
+
+ if (cl) {
+ for (ln = cl; ln->next; ln = ln->next) { /* void */ }
+
+ ln->next = p->free_raw_bufs;
+ p->free_raw_bufs = cl;
+ }
+ }
+
+#if (NGX_DEBUG)
+
+ for (cl = p->busy; cl; cl = cl->next) {
+ ngx_log_debug8(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe buf busy s:%d t:%d f:%d "
+ "%p, pos %p, size: %z "
+ "file: %O, size: %z",
+ (cl->buf->shadow ? 1 : 0),
+ cl->buf->temporary, cl->buf->in_file,
+ cl->buf->start, cl->buf->pos,
+ cl->buf->last - cl->buf->pos,
+ cl->buf->file_pos,
+ cl->buf->file_last - cl->buf->file_pos);
+ }
+
+ for (cl = p->out; cl; cl = cl->next) {
+ ngx_log_debug8(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe buf out s:%d t:%d f:%d "
+ "%p, pos %p, size: %z "
+ "file: %O, size: %z",
+ (cl->buf->shadow ? 1 : 0),
+ cl->buf->temporary, cl->buf->in_file,
+ cl->buf->start, cl->buf->pos,
+ cl->buf->last - cl->buf->pos,
+ cl->buf->file_pos,
+ cl->buf->file_last - cl->buf->file_pos);
+ }
+
+ for (cl = p->in; cl; cl = cl->next) {
+ ngx_log_debug8(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe buf in s:%d t:%d f:%d "
+ "%p, pos %p, size: %z "
+ "file: %O, size: %z",
+ (cl->buf->shadow ? 1 : 0),
+ cl->buf->temporary, cl->buf->in_file,
+ cl->buf->start, cl->buf->pos,
+ cl->buf->last - cl->buf->pos,
+ cl->buf->file_pos,
+ cl->buf->file_last - cl->buf->file_pos);
+ }
+
+ for (cl = p->free_raw_bufs; cl; cl = cl->next) {
+ ngx_log_debug8(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe buf free s:%d t:%d f:%d "
+ "%p, pos %p, size: %z "
+ "file: %O, size: %z",
+ (cl->buf->shadow ? 1 : 0),
+ cl->buf->temporary, cl->buf->in_file,
+ cl->buf->start, cl->buf->pos,
+ cl->buf->last - cl->buf->pos,
+ cl->buf->file_pos,
+ cl->buf->file_last - cl->buf->file_pos);
+ }
+
+#endif
+
+ if ((p->upstream_eof || p->upstream_error) && p->free_raw_bufs) {
+
+ /* STUB */ p->free_raw_bufs->buf->num = p->num++;
+
+ if (p->input_filter(p, p->free_raw_bufs->buf) == NGX_ERROR) {
+ return NGX_ABORT;
+ }
+
+ p->free_raw_bufs = p->free_raw_bufs->next;
+
+ if (p->free_bufs && p->buf_to_file == NULL) {
+ for (cl = p->free_raw_bufs; cl; cl = cl->next) {
+ if (cl->buf->shadow == NULL) {
+ ngx_pfree(p->pool, cl->buf->start);
+ }
+ }
+ }
+ }
+
+ if (p->cacheable && p->in) {
+ if (ngx_event_pipe_write_chain_to_temp_file(p) == NGX_ABORT) {
+ return NGX_ABORT;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_event_pipe_write_to_downstream(ngx_event_pipe_t *p)
+{
+ u_char *prev;
+ size_t bsize;
+ ngx_int_t rc;
+ ngx_uint_t flush, flushed, prev_last_shadow;
+ ngx_chain_t *out, **ll, *cl, file;
+ ngx_connection_t *downstream;
+
+ downstream = p->downstream;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe write downstream: %d", downstream->write->ready);
+
+ flushed = 0;
+
+ for ( ;; ) {
+ if (p->downstream_error) {
+ return ngx_event_pipe_drain_chains(p);
+ }
+
+ if (p->upstream_eof || p->upstream_error || p->upstream_done) {
+
+ /* pass the p->out and p->in chains to the output filter */
+
+ for (cl = p->busy; cl; cl = cl->next) {
+ cl->buf->recycled = 0;
+ }
+
+ if (p->out) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe write downstream flush out");
+
+ for (cl = p->out; cl; cl = cl->next) {
+ cl->buf->recycled = 0;
+ }
+
+ rc = p->output_filter(p->output_ctx, p->out);
+
+ if (rc == NGX_ERROR) {
+ p->downstream_error = 1;
+ return ngx_event_pipe_drain_chains(p);
+ }
+
+ p->out = NULL;
+ }
+
+ if (p->in) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe write downstream flush in");
+
+ for (cl = p->in; cl; cl = cl->next) {
+ cl->buf->recycled = 0;
+ }
+
+ rc = p->output_filter(p->output_ctx, p->in);
+
+ if (rc == NGX_ERROR) {
+ p->downstream_error = 1;
+ return ngx_event_pipe_drain_chains(p);
+ }
+
+ p->in = NULL;
+ }
+
+ if (p->cacheable && p->buf_to_file) {
+
+ file.buf = p->buf_to_file;
+ file.next = NULL;
+
+ if (ngx_write_chain_to_temp_file(p->temp_file, &file)
+ == NGX_ERROR)
+ {
+ return NGX_ABORT;
+ }
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe write downstream done");
+
+ /* TODO: free unused bufs */
+
+ p->downstream_done = 1;
+ break;
+ }
+
+ if (downstream->data != p->output_ctx
+ || !downstream->write->ready
+ || downstream->write->delayed)
+ {
+ break;
+ }
+
+ /* bsize is the size of the busy recycled bufs */
+
+ prev = NULL;
+ bsize = 0;
+
+ for (cl = p->busy; cl; cl = cl->next) {
+
+ if (cl->buf->recycled) {
+ if (prev == cl->buf->start) {
+ continue;
+ }
+
+ bsize += cl->buf->end - cl->buf->start;
+ prev = cl->buf->start;
+ }
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe write busy: %uz", bsize);
+
+ out = NULL;
+
+ if (bsize >= (size_t) p->busy_size) {
+ flush = 1;
+ goto flush;
+ }
+
+ flush = 0;
+ ll = NULL;
+ prev_last_shadow = 1;
+
+ for ( ;; ) {
+ if (p->out) {
+ cl = p->out;
+
+ if (cl->buf->recycled
+ && bsize + cl->buf->last - cl->buf->pos > p->busy_size)
+ {
+ flush = 1;
+ break;
+ }
+
+ p->out = p->out->next;
+
+ ngx_event_pipe_free_shadow_raw_buf(&p->free_raw_bufs, cl->buf);
+
+ } else if (!p->cacheable && p->in) {
+ cl = p->in;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe write buf ls:%d %p %z",
+ cl->buf->last_shadow,
+ cl->buf->pos,
+ cl->buf->last - cl->buf->pos);
+
+ if (cl->buf->recycled
+ && cl->buf->last_shadow
+ && bsize + cl->buf->last - cl->buf->pos > p->busy_size)
+ {
+ if (!prev_last_shadow) {
+ p->in = p->in->next;
+
+ cl->next = NULL;
+
+ if (out) {
+ *ll = cl;
+ } else {
+ out = cl;
+ }
+ }
+
+ flush = 1;
+ break;
+ }
+
+ prev_last_shadow = cl->buf->last_shadow;
+
+ p->in = p->in->next;
+
+ } else {
+ break;
+ }
+
+ if (cl->buf->recycled) {
+ bsize += cl->buf->last - cl->buf->pos;
+ }
+
+ cl->next = NULL;
+
+ if (out) {
+ *ll = cl;
+ } else {
+ out = cl;
+ }
+ ll = &cl->next;
+ }
+
+ flush:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe write: out:%p, f:%d", out, flush);
+
+ if (out == NULL) {
+
+ if (!flush) {
+ break;
+ }
+
+ /* a workaround for AIO */
+ if (flushed++ > 10) {
+ return NGX_BUSY;
+ }
+ }
+
+ rc = p->output_filter(p->output_ctx, out);
+
+ if (rc == NGX_ERROR) {
+ p->downstream_error = 1;
+ return ngx_event_pipe_drain_chains(p);
+ }
+
+ ngx_chain_update_chains(&p->free, &p->busy, &out, p->tag);
+
+ for (cl = p->free; cl; cl = cl->next) {
+
+ if (cl->buf->temp_file) {
+ if (p->cacheable || !p->cyclic_temp_file) {
+ continue;
+ }
+
+ /* reset p->temp_offset if all bufs had been sent */
+
+ if (cl->buf->file_last == p->temp_file->offset) {
+ p->temp_file->offset = 0;
+ }
+ }
+
+ /* TODO: free buf if p->free_bufs && upstream done */
+
+ /* add the free shadow raw buf to p->free_raw_bufs */
+
+ if (cl->buf->last_shadow) {
+ if (ngx_event_pipe_add_free_buf(p, cl->buf->shadow) != NGX_OK) {
+ return NGX_ABORT;
+ }
+
+ cl->buf->last_shadow = 0;
+ }
+
+ cl->buf->shadow = NULL;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_event_pipe_write_chain_to_temp_file(ngx_event_pipe_t *p)
+{
+ ssize_t size, bsize;
+ ngx_buf_t *b;
+ ngx_chain_t *cl, *tl, *next, *out, **ll, **last_free, fl;
+
+ if (p->buf_to_file) {
+ fl.buf = p->buf_to_file;
+ fl.next = p->in;
+ out = &fl;
+
+ } else {
+ out = p->in;
+ }
+
+ if (!p->cacheable) {
+
+ size = 0;
+ cl = out;
+ ll = NULL;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe offset: %O", p->temp_file->offset);
+
+ do {
+ bsize = cl->buf->last - cl->buf->pos;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "pipe buf %p, pos %p, size: %z",
+ cl->buf->start, cl->buf->pos, bsize);
+
+ if ((size + bsize > p->temp_file_write_size)
+ || (p->temp_file->offset + size + bsize > p->max_temp_file_size))
+ {
+ break;
+ }
+
+ size += bsize;
+ ll = &cl->next;
+ cl = cl->next;
+
+ } while (cl);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0, "size: %z", size);
+
+ if (ll == NULL) {
+ return NGX_BUSY;
+ }
+
+ if (cl) {
+ p->in = cl;
+ *ll = NULL;
+
+ } else {
+ p->in = NULL;
+ p->last_in = &p->in;
+ }
+
+ } else {
+ p->in = NULL;
+ p->last_in = &p->in;
+ }
+
+ if (ngx_write_chain_to_temp_file(p->temp_file, out) == NGX_ERROR) {
+ return NGX_ABORT;
+ }
+
+ for (last_free = &p->free_raw_bufs;
+ *last_free != NULL;
+ last_free = &(*last_free)->next)
+ {
+ /* void */
+ }
+
+ if (p->buf_to_file) {
+ p->temp_file->offset = p->buf_to_file->last - p->buf_to_file->pos;
+ p->buf_to_file = NULL;
+ out = out->next;
+ }
+
+ for (cl = out; cl; cl = next) {
+ next = cl->next;
+ cl->next = NULL;
+
+ b = cl->buf;
+ b->file = &p->temp_file->file;
+ b->file_pos = p->temp_file->offset;
+ p->temp_file->offset += b->last - b->pos;
+ b->file_last = p->temp_file->offset;
+
+ b->in_file = 1;
+ b->temp_file = 1;
+
+ if (p->out) {
+ *p->last_out = cl;
+ } else {
+ p->out = cl;
+ }
+ p->last_out = &cl->next;
+
+ if (b->last_shadow) {
+
+ tl = ngx_alloc_chain_link(p->pool);
+ if (tl == NULL) {
+ return NGX_ABORT;
+ }
+
+ tl->buf = b->shadow;
+ tl->next = NULL;
+
+ *last_free = tl;
+ last_free = &tl->next;
+
+ b->shadow->pos = b->shadow->start;
+ b->shadow->last = b->shadow->start;
+
+ ngx_event_pipe_remove_shadow_links(b->shadow);
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+/* the copy input filter */
+
+ngx_int_t
+ngx_event_pipe_copy_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf)
+{
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+
+ if (buf->pos == buf->last) {
+ return NGX_OK;
+ }
+
+ 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;
+
+ return NGX_OK;
+}
+
+
+static ngx_inline void
+ngx_event_pipe_remove_shadow_links(ngx_buf_t *buf)
+{
+ ngx_buf_t *b, *next;
+
+ b = buf->shadow;
+
+ if (b == NULL) {
+ return;
+ }
+
+ while (!b->last_shadow) {
+ next = b->shadow;
+
+ b->temporary = 0;
+ b->recycled = 0;
+
+ b->shadow = NULL;
+ b = next;
+ }
+
+ b->temporary = 0;
+ b->recycled = 0;
+ b->last_shadow = 0;
+
+ b->shadow = NULL;
+
+ buf->shadow = NULL;
+}
+
+
+static ngx_inline void
+ngx_event_pipe_free_shadow_raw_buf(ngx_chain_t **free, ngx_buf_t *buf)
+{
+ ngx_buf_t *s;
+ ngx_chain_t *cl, **ll;
+
+ if (buf->shadow == NULL) {
+ return;
+ }
+
+ for (s = buf->shadow; !s->last_shadow; s = s->shadow) { /* void */ }
+
+ ll = free;
+
+ for (cl = *free; cl; cl = cl->next) {
+ if (cl->buf == s) {
+ *ll = cl->next;
+ break;
+ }
+
+ if (cl->buf->shadow) {
+ break;
+ }
+
+ ll = &cl->next;
+ }
+}
+
+
+ngx_int_t
+ngx_event_pipe_add_free_buf(ngx_event_pipe_t *p, ngx_buf_t *b)
+{
+ ngx_chain_t *cl;
+
+ cl = ngx_alloc_chain_link(p->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->pos = b->start;
+ b->last = b->start;
+ b->shadow = NULL;
+
+ cl->buf = b;
+
+ if (p->free_raw_bufs == NULL) {
+ p->free_raw_bufs = cl;
+ cl->next = NULL;
+
+ return NGX_OK;
+ }
+
+ if (p->free_raw_bufs->buf->pos == p->free_raw_bufs->buf->last) {
+
+ /* add the free buf to the list start */
+
+ cl->next = p->free_raw_bufs;
+ p->free_raw_bufs = cl;
+
+ return NGX_OK;
+ }
+
+ /* the first free buf is partialy filled, thus add the free buf after it */
+
+ cl->next = p->free_raw_bufs->next;
+ p->free_raw_bufs->next = cl;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_event_pipe_drain_chains(ngx_event_pipe_t *p)
+{
+ ngx_chain_t *cl, *tl;
+
+ for ( ;; ) {
+ if (p->busy) {
+ cl = p->busy;
+ p->busy = NULL;
+
+ } else if (p->out) {
+ cl = p->out;
+ p->out = NULL;
+
+ } else if (p->in) {
+ cl = p->in;
+ p->in = NULL;
+
+ } else {
+ return NGX_OK;
+ }
+
+ while (cl) {
+ if (cl->buf->last_shadow) {
+ if (ngx_event_pipe_add_free_buf(p, cl->buf->shadow) != NGX_OK) {
+ return NGX_ABORT;
+ }
+
+ cl->buf->last_shadow = 0;
+ }
+
+ cl->buf->shadow = NULL;
+ tl = cl->next;
+ cl->next = p->free;
+ p->free = cl;
+ cl = tl;
+ }
+ }
+}
diff --git a/usr.sbin/nginx/src/event/ngx_event_pipe.h b/usr.sbin/nginx/src/event/ngx_event_pipe.h
new file mode 100644
index 00000000000..00b8acf6689
--- /dev/null
+++ b/usr.sbin/nginx/src/event/ngx_event_pipe.h
@@ -0,0 +1,94 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_EVENT_PIPE_H_INCLUDED_
+#define _NGX_EVENT_PIPE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+typedef struct ngx_event_pipe_s ngx_event_pipe_t;
+
+typedef ngx_int_t (*ngx_event_pipe_input_filter_pt)(ngx_event_pipe_t *p,
+ ngx_buf_t *buf);
+typedef ngx_int_t (*ngx_event_pipe_output_filter_pt)(void *data,
+ ngx_chain_t *chain);
+
+
+struct ngx_event_pipe_s {
+ ngx_connection_t *upstream;
+ ngx_connection_t *downstream;
+
+ ngx_chain_t *free_raw_bufs;
+ ngx_chain_t *in;
+ ngx_chain_t **last_in;
+
+ ngx_chain_t *out;
+ ngx_chain_t **last_out;
+
+ ngx_chain_t *free;
+ ngx_chain_t *busy;
+
+ /*
+ * the input filter i.e. that moves HTTP/1.1 chunks
+ * from the raw bufs to an incoming chain
+ */
+
+ ngx_event_pipe_input_filter_pt input_filter;
+ void *input_ctx;
+
+ ngx_event_pipe_output_filter_pt output_filter;
+ void *output_ctx;
+
+ unsigned read:1;
+ unsigned cacheable:1;
+ unsigned single_buf:1;
+ unsigned free_bufs:1;
+ unsigned upstream_done:1;
+ unsigned upstream_error:1;
+ unsigned upstream_eof:1;
+ unsigned upstream_blocked:1;
+ unsigned downstream_done:1;
+ unsigned downstream_error:1;
+ unsigned cyclic_temp_file:1;
+
+ ngx_int_t allocated;
+ ngx_bufs_t bufs;
+ ngx_buf_tag_t tag;
+
+ ssize_t busy_size;
+
+ off_t read_length;
+
+ off_t max_temp_file_size;
+ ssize_t temp_file_write_size;
+
+ ngx_msec_t read_timeout;
+ ngx_msec_t send_timeout;
+ ssize_t send_lowat;
+
+ ngx_pool_t *pool;
+ ngx_log_t *log;
+
+ ngx_chain_t *preread_bufs;
+ size_t preread_size;
+ ngx_buf_t *buf_to_file;
+
+ ngx_temp_file_t *temp_file;
+
+ /* STUB */ int num;
+};
+
+
+ngx_int_t ngx_event_pipe(ngx_event_pipe_t *p, ngx_int_t do_write);
+ngx_int_t ngx_event_pipe_copy_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf);
+ngx_int_t ngx_event_pipe_add_free_buf(ngx_event_pipe_t *p, ngx_buf_t *b);
+
+
+#endif /* _NGX_EVENT_PIPE_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/event/ngx_event_posted.c b/usr.sbin/nginx/src/event/ngx_event_posted.c
new file mode 100644
index 00000000000..a7b09fdd6ee
--- /dev/null
+++ b/usr.sbin/nginx/src/event/ngx_event_posted.c
@@ -0,0 +1,172 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ngx_thread_volatile ngx_event_t *ngx_posted_accept_events;
+ngx_thread_volatile ngx_event_t *ngx_posted_events;
+
+#if (NGX_THREADS)
+ngx_mutex_t *ngx_posted_events_mutex;
+#endif
+
+
+void
+ngx_event_process_posted(ngx_cycle_t *cycle,
+ ngx_thread_volatile ngx_event_t **posted)
+{
+ ngx_event_t *ev;
+
+ for ( ;; ) {
+
+ ev = (ngx_event_t *) *posted;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "posted event %p", ev);
+
+ if (ev == NULL) {
+ return;
+ }
+
+ ngx_delete_posted_event(ev);
+
+ ev->handler(ev);
+ }
+}
+
+
+#if (NGX_THREADS) && !(NGX_WIN32)
+
+void
+ngx_wakeup_worker_thread(ngx_cycle_t *cycle)
+{
+ ngx_int_t i;
+#if 0
+ ngx_uint_t busy;
+ ngx_event_t *ev;
+
+ busy = 1;
+
+ if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+ return;
+ }
+
+ for (ev = (ngx_event_t *) ngx_posted_events; ev; ev = ev->next) {
+ if (*(ev->lock) == 0) {
+ busy = 0;
+ break;
+ }
+ }
+
+ ngx_mutex_unlock(ngx_posted_events_mutex);
+
+ if (busy) {
+ return;
+ }
+#endif
+
+ for (i = 0; i < ngx_threads_n; i++) {
+ if (ngx_threads[i].state == NGX_THREAD_FREE) {
+ ngx_cond_signal(ngx_threads[i].cv);
+ return;
+ }
+ }
+}
+
+
+ngx_int_t
+ngx_event_thread_process_posted(ngx_cycle_t *cycle)
+{
+ ngx_event_t *ev;
+
+ for ( ;; ) {
+
+ ev = (ngx_event_t *) ngx_posted_events;
+
+ for ( ;; ) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "posted event %p", ev);
+
+ if (ev == NULL) {
+ return NGX_OK;
+ }
+
+ if (ngx_trylock(ev->lock) == 0) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "posted event %p is busy", ev);
+
+ ev = ev->next;
+ continue;
+ }
+
+ if (ev->lock != ev->own_lock) {
+ if (*(ev->own_lock)) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "the own lock of the posted event %p is busy", ev);
+ ngx_unlock(ev->lock);
+ ev = ev->next;
+ continue;
+ }
+ *(ev->own_lock) = 1;
+ }
+
+ ngx_delete_posted_event(ev);
+
+ ev->locked = 1;
+
+ ev->ready |= ev->posted_ready;
+ ev->timedout |= ev->posted_timedout;
+ ev->pending_eof |= ev->posted_eof;
+#if (NGX_HAVE_KQUEUE)
+ ev->kq_errno |= ev->posted_errno;
+#endif
+ if (ev->posted_available) {
+ ev->available = ev->posted_available;
+ }
+
+ ev->posted_ready = 0;
+ ev->posted_timedout = 0;
+ ev->posted_eof = 0;
+#if (NGX_HAVE_KQUEUE)
+ ev->posted_errno = 0;
+#endif
+ ev->posted_available = 0;
+
+ ngx_mutex_unlock(ngx_posted_events_mutex);
+
+ ev->handler(ev);
+
+ ngx_mutex_lock(ngx_posted_events_mutex);
+
+ if (ev->locked) {
+ ngx_unlock(ev->lock);
+
+ if (ev->lock != ev->own_lock) {
+ ngx_unlock(ev->own_lock);
+ }
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "posted event %p is done", ev);
+
+ break;
+ }
+ }
+}
+
+#else
+
+void
+ngx_wakeup_worker_thread(ngx_cycle_t *cycle)
+{
+}
+
+#endif
diff --git a/usr.sbin/nginx/src/event/ngx_event_posted.h b/usr.sbin/nginx/src/event/ngx_event_posted.h
new file mode 100644
index 00000000000..f9c55e5a3c5
--- /dev/null
+++ b/usr.sbin/nginx/src/event/ngx_event_posted.h
@@ -0,0 +1,74 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_EVENT_POSTED_H_INCLUDED_
+#define _NGX_EVENT_POSTED_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (NGX_THREADS)
+extern ngx_mutex_t *ngx_posted_events_mutex;
+#endif
+
+
+#define ngx_locked_post_event(ev, queue) \
+ \
+ if (ev->prev == NULL) { \
+ ev->next = (ngx_event_t *) *queue; \
+ ev->prev = (ngx_event_t **) queue; \
+ *queue = ev; \
+ \
+ if (ev->next) { \
+ ev->next->prev = &ev->next; \
+ } \
+ \
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0, "post event %p", ev); \
+ \
+ } else { \
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0, \
+ "update posted event %p", ev); \
+ }
+
+
+#define ngx_post_event(ev, queue) \
+ \
+ ngx_mutex_lock(ngx_posted_events_mutex); \
+ ngx_locked_post_event(ev, queue); \
+ ngx_mutex_unlock(ngx_posted_events_mutex);
+
+
+#define ngx_delete_posted_event(ev) \
+ \
+ *(ev->prev) = ev->next; \
+ \
+ if (ev->next) { \
+ ev->next->prev = ev->prev; \
+ } \
+ \
+ ev->prev = NULL; \
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0, \
+ "delete posted event %p", ev);
+
+
+
+void ngx_event_process_posted(ngx_cycle_t *cycle,
+ ngx_thread_volatile ngx_event_t **posted);
+void ngx_wakeup_worker_thread(ngx_cycle_t *cycle);
+
+#if (NGX_THREADS)
+ngx_int_t ngx_event_thread_process_posted(ngx_cycle_t *cycle);
+#endif
+
+
+extern ngx_thread_volatile ngx_event_t *ngx_posted_accept_events;
+extern ngx_thread_volatile ngx_event_t *ngx_posted_events;
+
+
+#endif /* _NGX_EVENT_POSTED_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/event/ngx_event_timer.c b/usr.sbin/nginx/src/event/ngx_event_timer.c
new file mode 100644
index 00000000000..2931a9f4e2e
--- /dev/null
+++ b/usr.sbin/nginx/src/event/ngx_event_timer.c
@@ -0,0 +1,158 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (NGX_THREADS)
+ngx_mutex_t *ngx_event_timer_mutex;
+#endif
+
+
+ngx_thread_volatile ngx_rbtree_t ngx_event_timer_rbtree;
+static ngx_rbtree_node_t ngx_event_timer_sentinel;
+
+/*
+ * the event timer rbtree may contain the duplicate keys, however,
+ * it should not be a problem, because we use the rbtree to find
+ * a minimum timer value only
+ */
+
+ngx_int_t
+ngx_event_timer_init(ngx_log_t *log)
+{
+ ngx_rbtree_init(&ngx_event_timer_rbtree, &ngx_event_timer_sentinel,
+ ngx_rbtree_insert_timer_value);
+
+#if (NGX_THREADS)
+
+ if (ngx_event_timer_mutex) {
+ ngx_event_timer_mutex->log = log;
+ return NGX_OK;
+ }
+
+ ngx_event_timer_mutex = ngx_mutex_init(log, 0);
+ if (ngx_event_timer_mutex == NULL) {
+ return NGX_ERROR;
+ }
+
+#endif
+
+ return NGX_OK;
+}
+
+
+ngx_msec_t
+ngx_event_find_timer(void)
+{
+ ngx_msec_int_t timer;
+ ngx_rbtree_node_t *node, *root, *sentinel;
+
+ if (ngx_event_timer_rbtree.root == &ngx_event_timer_sentinel) {
+ return NGX_TIMER_INFINITE;
+ }
+
+ ngx_mutex_lock(ngx_event_timer_mutex);
+
+ root = ngx_event_timer_rbtree.root;
+ sentinel = ngx_event_timer_rbtree.sentinel;
+
+ node = ngx_rbtree_min(root, sentinel);
+
+ ngx_mutex_unlock(ngx_event_timer_mutex);
+
+ timer = (ngx_msec_int_t) node->key - (ngx_msec_int_t) ngx_current_msec;
+
+ return (ngx_msec_t) (timer > 0 ? timer : 0);
+}
+
+
+void
+ngx_event_expire_timers(void)
+{
+ ngx_event_t *ev;
+ ngx_rbtree_node_t *node, *root, *sentinel;
+
+ sentinel = ngx_event_timer_rbtree.sentinel;
+
+ for ( ;; ) {
+
+ ngx_mutex_lock(ngx_event_timer_mutex);
+
+ root = ngx_event_timer_rbtree.root;
+
+ if (root == sentinel) {
+ return;
+ }
+
+ node = ngx_rbtree_min(root, sentinel);
+
+ /* node->key <= ngx_current_time */
+
+ if ((ngx_msec_int_t) node->key - (ngx_msec_int_t) ngx_current_msec <= 0)
+ {
+ ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));
+
+#if (NGX_THREADS)
+
+ if (ngx_threaded && ngx_trylock(ev->lock) == 0) {
+
+ /*
+ * We can not change the timer of the event that is been
+ * handling by another thread. And we can not easy walk
+ * the rbtree to find a next expired timer so we exit the loop.
+ * However it should be rare case when the event that is
+ * been handling has expired timer.
+ */
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "event %p is busy in expire timers", ev);
+ break;
+ }
+#endif
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "event timer del: %d: %M",
+ ngx_event_ident(ev->data), ev->timer.key);
+
+ ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);
+
+ ngx_mutex_unlock(ngx_event_timer_mutex);
+
+#if (NGX_DEBUG)
+ ev->timer.left = NULL;
+ ev->timer.right = NULL;
+ ev->timer.parent = NULL;
+#endif
+
+ ev->timer_set = 0;
+
+#if (NGX_THREADS)
+ if (ngx_threaded) {
+ ev->posted_timedout = 1;
+
+ ngx_post_event(ev, &ngx_posted_events);
+
+ ngx_unlock(ev->lock);
+
+ continue;
+ }
+#endif
+
+ ev->timedout = 1;
+
+ ev->handler(ev);
+
+ continue;
+ }
+
+ break;
+ }
+
+ ngx_mutex_unlock(ngx_event_timer_mutex);
+}
diff --git a/usr.sbin/nginx/src/event/ngx_event_timer.h b/usr.sbin/nginx/src/event/ngx_event_timer.h
new file mode 100644
index 00000000000..61ffeaefe65
--- /dev/null
+++ b/usr.sbin/nginx/src/event/ngx_event_timer.h
@@ -0,0 +1,101 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_EVENT_TIMER_H_INCLUDED_
+#define _NGX_EVENT_TIMER_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define NGX_TIMER_INFINITE (ngx_msec_t) -1
+
+#define NGX_TIMER_LAZY_DELAY 300
+
+
+ngx_int_t ngx_event_timer_init(ngx_log_t *log);
+ngx_msec_t ngx_event_find_timer(void);
+void ngx_event_expire_timers(void);
+
+
+#if (NGX_THREADS)
+extern ngx_mutex_t *ngx_event_timer_mutex;
+#endif
+
+
+extern ngx_thread_volatile ngx_rbtree_t ngx_event_timer_rbtree;
+
+
+static ngx_inline void
+ngx_event_del_timer(ngx_event_t *ev)
+{
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "event timer del: %d: %M",
+ ngx_event_ident(ev->data), ev->timer.key);
+
+ ngx_mutex_lock(ngx_event_timer_mutex);
+
+ ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);
+
+ ngx_mutex_unlock(ngx_event_timer_mutex);
+
+#if (NGX_DEBUG)
+ ev->timer.left = NULL;
+ ev->timer.right = NULL;
+ ev->timer.parent = NULL;
+#endif
+
+ ev->timer_set = 0;
+}
+
+
+static ngx_inline void
+ngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer)
+{
+ ngx_msec_t key;
+ ngx_msec_int_t diff;
+
+ key = ngx_current_msec + timer;
+
+ if (ev->timer_set) {
+
+ /*
+ * Use a previous timer value if difference between it and a new
+ * value is less than NGX_TIMER_LAZY_DELAY milliseconds: this allows
+ * to minimize the rbtree operations for fast connections.
+ */
+
+ diff = (ngx_msec_int_t) (key - ev->timer.key);
+
+ if (ngx_abs(diff) < NGX_TIMER_LAZY_DELAY) {
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "event timer: %d, old: %M, new: %M",
+ ngx_event_ident(ev->data), ev->timer.key, key);
+ return;
+ }
+
+ ngx_del_timer(ev);
+ }
+
+ ev->timer.key = key;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "event timer add: %d: %M:%M",
+ ngx_event_ident(ev->data), timer, ev->timer.key);
+
+ ngx_mutex_lock(ngx_event_timer_mutex);
+
+ ngx_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer);
+
+ ngx_mutex_unlock(ngx_event_timer_mutex);
+
+ ev->timer_set = 1;
+}
+
+
+#endif /* _NGX_EVENT_TIMER_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_access_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_access_module.c
new file mode 100644
index 00000000000..a28ebf7b789
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_access_module.c
@@ -0,0 +1,383 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ in_addr_t mask;
+ in_addr_t addr;
+ ngx_uint_t deny; /* unsigned deny:1; */
+} ngx_http_access_rule_t;
+
+#if (NGX_HAVE_INET6)
+
+typedef struct {
+ struct in6_addr addr;
+ struct in6_addr mask;
+ ngx_uint_t deny; /* unsigned deny:1; */
+} ngx_http_access_rule6_t;
+
+#endif
+
+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 (conf->rules == NULL) {
+ conf->rules = prev->rules;
+ }
+
+#if (NGX_HAVE_INET6)
+ if (conf->rules6 == NULL) {
+ conf->rules6 = prev->rules6;
+ }
+#endif
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_access_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_access_handler;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_addition_filter_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_addition_filter_module.c
new file mode 100644
index 00000000000..9b22a0a122c
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_addition_filter_module.c
@@ -0,0 +1,249 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_str_t before_body;
+ ngx_str_t after_body;
+
+ ngx_hash_t types;
+ ngx_array_t *types_keys;
+} ngx_http_addition_conf_t;
+
+
+typedef struct {
+ ngx_uint_t before_body_sent;
+} ngx_http_addition_ctx_t;
+
+
+static void *ngx_http_addition_create_conf(ngx_conf_t *cf);
+static char *ngx_http_addition_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static ngx_int_t ngx_http_addition_filter_init(ngx_conf_t *cf);
+
+
+static ngx_command_t ngx_http_addition_commands[] = {
+
+ { ngx_string("add_before_body"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_addition_conf_t, before_body),
+ NULL },
+
+ { ngx_string("add_after_body"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_addition_conf_t, after_body),
+ NULL },
+
+ { ngx_string("addition_types"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_types_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_addition_conf_t, types_keys),
+ &ngx_http_html_default_types[0] },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_addition_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_addition_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_addition_create_conf, /* create location configuration */
+ ngx_http_addition_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_addition_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_addition_filter_module_ctx, /* module context */
+ ngx_http_addition_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_addition_header_filter(ngx_http_request_t *r)
+{
+ ngx_http_addition_ctx_t *ctx;
+ ngx_http_addition_conf_t *conf;
+
+ if (r->headers_out.status != NGX_HTTP_OK || r != r->main) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_addition_filter_module);
+
+ if (conf->before_body.len == 0 && conf->after_body.len == 0) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ if (ngx_http_test_content_type(r, &conf->types) == NULL) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_addition_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_addition_filter_module);
+
+ ngx_http_clear_content_length(r);
+ ngx_http_clear_accept_ranges(r);
+
+ 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;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_auth_basic_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_auth_basic_module.c
new file mode 100644
index 00000000000..45168de8404
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_auth_basic_module.c
@@ -0,0 +1,477 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_crypt.h>
+
+
+#define NGX_HTTP_AUTH_BUF_SIZE 2048
+
+
+typedef struct {
+ ngx_str_t passwd;
+} ngx_http_auth_basic_ctx_t;
+
+
+typedef struct {
+ ngx_str_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(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_auth_basic_user_file(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_conf_post_handler_pt ngx_http_auth_basic_p = ngx_http_auth_basic;
+
+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_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_auth_basic_loc_conf_t, realm),
+ &ngx_http_auth_basic_p },
+
+ { 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, 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.len == 0 || alcf->user_file.value.len == 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,
+ &alcf->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, &alcf->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,
+ &alcf->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, &alcf->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, &alcf->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)
+{
+ 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;
+ }
+
+ r->headers_out.www_authenticate->hash = 1;
+ ngx_str_set(&r->headers_out.www_authenticate->key, "WWW-Authenticate");
+ r->headers_out.www_authenticate->value = *realm;
+
+ 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.data == NULL) {
+ conf->realm = prev->realm;
+ }
+
+ if (conf->user_file.value.len == 0) {
+ 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(ngx_conf_t *cf, void *post, void *data)
+{
+ ngx_str_t *realm = data;
+
+ size_t len;
+ u_char *basic, *p;
+
+ if (ngx_strcmp(realm->data, "off") == 0) {
+ ngx_str_set(realm, "");
+ return NGX_CONF_OK;
+ }
+
+ len = sizeof("Basic realm=\"") - 1 + realm->len + 1;
+
+ basic = ngx_pnalloc(cf->pool, len);
+ if (basic == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ p = ngx_cpymem(basic, "Basic realm=\"", sizeof("Basic realm=\"") - 1);
+ p = ngx_cpymem(p, realm->data, realm->len);
+ *p = '"';
+
+ realm->len = len;
+ realm->data = basic;
+
+ return NGX_CONF_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.len) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &alcf->user_file;
+ ccv.zero = 1;
+ ccv.conf_prefix = 1;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_autoindex_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_autoindex_module.c
new file mode 100644
index 00000000000..b6793180698
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_autoindex_module.c
@@ -0,0 +1,671 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#if 0
+
+typedef struct {
+ ngx_buf_t *buf;
+ size_t size;
+ ngx_pool_t *pool;
+ size_t alloc_size;
+ ngx_chain_t **last_out;
+} ngx_http_autoindex_ctx_t;
+
+#endif
+
+
+typedef struct {
+ ngx_str_t name;
+ size_t utf_len;
+ size_t escape;
+
+ unsigned dir:1;
+ unsigned colon: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 configration */
+ ngx_http_autoindex_merge_loc_conf /* merge location configration */
+};
+
+
+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[] =
+"<html>" CRLF
+"<head><title>Index of "
+;
+
+
+static u_char header[] =
+"</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<h1>Index of "
+;
+
+static u_char tail[] =
+"</body>" CRLF
+"</html>" CRLF
+;
+
+
+static ngx_int_t
+ngx_http_autoindex_handler(ngx_http_request_t *r)
+{
+ u_char *last, *filename, scale;
+ off_t length;
+ size_t len, utf_len, 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) {
+ 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_HTML);
+
+ if (utf8) {
+ entry->utf_len = ngx_utf8_length(entry->name.data, entry->name.len);
+ } else {
+ entry->utf_len = len;
+ }
+
+ entry->colon = (ngx_strchr(entry->name.data, ':') != NULL);
+
+ 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);
+ }
+
+ len = sizeof(title) - 1
+ + r->uri.len
+ + sizeof(header) - 1
+ + r->uri.len
+ + sizeof("</h1>") - 1
+ + sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1
+ + sizeof("</pre><hr>") - 1
+ + sizeof(tail) - 1;
+
+ entry = entries.elts;
+ for (i = 0; i < entries.nelts; i++) {
+ len += sizeof("<a href=\"") - 1
+ + entry[i].name.len + entry[i].escape
+ + 1 /* 1 is for "/" */
+ + sizeof("\">") - 1
+ + entry[i].name.len - entry[i].utf_len + entry[i].colon * 2
+ + NGX_HTTP_AUTOINDEX_NAME_LEN + sizeof("&gt;") - 2
+ + sizeof("</a>") - 1
+ + sizeof(" 28-Sep-1970 12:00 ") - 1
+ + 20 /* the file size */
+ + 2;
+ }
+
+ b = ngx_create_temp_buf(r->pool, len);
+ if (b == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_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);
+ b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
+ b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
+ b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
+ b->last = ngx_cpymem(b->last, "</h1>", sizeof("</h1>") - 1);
+
+ b->last = ngx_cpymem(b->last, "<hr><pre><a href=\"../\">../</a>" CRLF,
+ sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1);
+
+ tp = ngx_timeofday();
+
+ for (i = 0; i < entries.nelts; i++) {
+ b->last = ngx_cpymem(b->last, "<a href=\"", sizeof("<a href=\"") - 1);
+
+ if (entry[i].colon) {
+ *b->last++ = '.';
+ *b->last++ = '/';
+ }
+
+ if (entry[i].escape) {
+ ngx_escape_uri(b->last, entry[i].name.data, entry[i].name.len,
+ NGX_ESCAPE_HTML);
+
+ 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) {
+ utf_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3 + 1;
+
+ } else {
+ utf_len = NGX_HTTP_AUTOINDEX_NAME_LEN + 1;
+ }
+
+ b->last = ngx_utf8_cpystrn(b->last, entry[i].name.data,
+ utf_len, entry[i].name.len + 1);
+ last = b->last;
+
+ } else {
+ b->last = ngx_cpystrn(b->last, entry[i].name.data,
+ NGX_HTTP_AUTOINDEX_NAME_LEN + 1);
+ last = b->last - 3;
+ }
+
+ if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
+ b->last = ngx_cpymem(last, "..&gt;</a>", sizeof("..&gt;</a>") - 1);
+
+ } else {
+ if (entry[i].dir && NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
+ *b->last++ = '/';
+ len++;
+ }
+
+ b->last = ngx_cpymem(b->last, "</a>", sizeof("</a>") - 1);
+ 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, "</pre><hr>", sizeof("</pre><hr>") - 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 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;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_browser_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_browser_module.c
new file mode 100644
index 00000000000..d400fec7fd6
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_browser_module.c
@@ -0,0 +1,712 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+/*
+ * The module can check browser versions conforming to the following formats:
+ * X, X.X, X.X.X, and X.X.X.X. The maximum values of each format may be
+ * 4000, 4000.99, 4000.99.99, and 4000.99.99.99.
+ */
+
+
+#define NGX_HTTP_MODERN_BROWSER 0
+#define NGX_HTTP_ANCIENT_BROWSER 1
+
+
+typedef struct {
+ u_char browser[12];
+ size_t skip;
+ size_t add;
+ u_char name[12];
+} ngx_http_modern_browser_mask_t;
+
+
+typedef struct {
+ ngx_uint_t version;
+ size_t skip;
+ size_t add;
+ u_char name[12];
+} ngx_http_modern_browser_t;
+
+
+typedef struct {
+ ngx_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_browsers = prev->modern_browsers;
+
+ } else {
+ 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->ancient_browsers = prev->ancient_browsers;
+ }
+
+ if (conf->modern_browser_value == NULL) {
+ conf->modern_browser_value = prev->modern_browser_value;
+ }
+
+ if (conf->modern_browser_value == NULL) {
+ conf->modern_browser_value = &ngx_http_variable_true_value;
+ }
+
+ if (conf->ancient_browser_value == NULL) {
+ conf->ancient_browser_value = prev->ancient_browser_value;
+ }
+
+ if (conf->ancient_browser_value == NULL) {
+ conf->ancient_browser_value = &ngx_http_variable_true_value;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static int ngx_libc_cdecl
+ngx_http_modern_browser_sort(const void *one, const void *two)
+{
+ ngx_http_modern_browser_t *first = (ngx_http_modern_browser_t *) one;
+ ngx_http_modern_browser_t *second = (ngx_http_modern_browser_t *) two;
+
+ return (first->skip - second->skip);
+}
+
+
+static char *
+ngx_http_modern_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_browser_conf_t *bcf = conf;
+
+ u_char c;
+ ngx_str_t *value;
+ ngx_uint_t i, n, version, ver, scale;
+ ngx_http_modern_browser_t *browser;
+ ngx_http_modern_browser_mask_t *mask;
+
+ value = cf->args->elts;
+
+ if (cf->args->nelts == 2) {
+ if (ngx_strcmp(value[1].data, "unlisted") == 0) {
+ bcf->modern_unlisted_browsers = 1;
+ return NGX_CONF_OK;
+ }
+
+ return NGX_CONF_ERROR;
+ }
+
+ if (bcf->modern_browsers == NULL) {
+ bcf->modern_browsers = ngx_array_create(cf->pool, 5,
+ sizeof(ngx_http_modern_browser_t));
+ if (bcf->modern_browsers == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ browser = ngx_array_push(bcf->modern_browsers);
+ if (browser == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ mask = ngx_http_modern_browser_masks;
+
+ for (n = 0; mask[n].browser[0] != '\0'; n++) {
+ if (ngx_strcasecmp(mask[n].browser, value[1].data) == 0) {
+ goto found;
+ }
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "unknown browser name \"%V\"", &value[1]);
+
+ return NGX_CONF_ERROR;
+
+found:
+
+ /*
+ * at this stage the skip field is used to store the browser slot,
+ * it will be used in sorting in merge stage and then will overwritten
+ * with a real value
+ */
+
+ browser->skip = n;
+
+ version = 0;
+ ver = 0;
+ scale = 1000000;
+
+ for (i = 0; i < value[2].len; i++) {
+
+ c = value[2].data[i];
+
+ if (c >= '0' && c <= '9') {
+ ver = ver * 10 + (c - '0');
+ continue;
+ }
+
+ if (c == '.') {
+ version += ver * scale;
+ ver = 0;
+ scale /= 100;
+ continue;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid browser version \"%V\"", &value[2]);
+
+ return NGX_CONF_ERROR;
+ }
+
+ version += ver * scale;
+
+ browser->version = version;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_ancient_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_browser_conf_t *bcf = conf;
+
+ ngx_str_t *value, *browser;
+ ngx_uint_t i;
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+ if (ngx_strcmp(value[i].data, "netscape4") == 0) {
+ bcf->netscape4 = 1;
+ continue;
+ }
+
+ if (bcf->ancient_browsers == NULL) {
+ bcf->ancient_browsers = ngx_array_create(cf->pool, 4,
+ sizeof(ngx_str_t));
+ if (bcf->ancient_browsers == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ browser = ngx_array_push(bcf->ancient_browsers);
+ if (browser == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *browser = value[i];
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_modern_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_browser_conf_t *bcf = conf;
+
+ ngx_str_t *value;
+
+ bcf->modern_browser_value = ngx_palloc(cf->pool,
+ sizeof(ngx_http_variable_value_t));
+ if (bcf->modern_browser_value == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ bcf->modern_browser_value->len = value[1].len;
+ bcf->modern_browser_value->valid = 1;
+ bcf->modern_browser_value->no_cacheable = 0;
+ bcf->modern_browser_value->not_found = 0;
+ bcf->modern_browser_value->data = value[1].data;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_ancient_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_browser_conf_t *bcf = conf;
+
+ ngx_str_t *value;
+
+ bcf->ancient_browser_value = ngx_palloc(cf->pool,
+ sizeof(ngx_http_variable_value_t));
+ if (bcf->ancient_browser_value == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ bcf->ancient_browser_value->len = value[1].len;
+ bcf->ancient_browser_value->valid = 1;
+ bcf->ancient_browser_value->no_cacheable = 0;
+ bcf->ancient_browser_value->not_found = 0;
+ bcf->ancient_browser_value->data = value[1].data;
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_charset_filter_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_charset_filter_module.c
new file mode 100644
index 00000000000..a6f9afcae50
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_charset_filter_module.c
@@ -0,0 +1,1680 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_CHARSET_OFF -2
+#define NGX_HTTP_NO_CHARSET -3
+#define NGX_HTTP_CHARSET_VAR 0x10000
+
+/* 1 byte length and up to 3 bytes for the UTF-8 encoding of the UCS-2 */
+#define NGX_UTF_LEN 4
+
+#define NGX_HTML_ENTITY_LEN (sizeof("&#1114111;") - 1)
+
+
+typedef struct {
+ u_char **tables;
+ ngx_str_t name;
+
+ unsigned length:16;
+ unsigned utf8:1;
+} ngx_http_charset_t;
+
+
+typedef struct {
+ ngx_int_t src;
+ ngx_int_t dst;
+} ngx_http_charset_recode_t;
+
+
+typedef struct {
+ ngx_int_t src;
+ ngx_int_t dst;
+ u_char *src2dst;
+ u_char *dst2src;
+} ngx_http_charset_tables_t;
+
+
+typedef struct {
+ ngx_array_t charsets; /* ngx_http_charset_t */
+ ngx_array_t tables; /* ngx_http_charset_tables_t */
+ ngx_array_t recodes; /* ngx_http_charset_recode_t */
+} ngx_http_charset_main_conf_t;
+
+
+typedef struct {
+ ngx_int_t charset;
+ ngx_int_t source_charset;
+ ngx_flag_t override_charset;
+
+ ngx_hash_t types;
+ ngx_array_t *types_keys;
+} ngx_http_charset_loc_conf_t;
+
+
+typedef struct {
+ u_char *table;
+ ngx_int_t charset;
+ ngx_str_t charset_name;
+
+ ngx_chain_t *busy;
+ ngx_chain_t *free_bufs;
+ ngx_chain_t *free_buffers;
+
+ size_t saved_len;
+ u_char saved[NGX_UTF_LEN];
+
+ unsigned length:16;
+ unsigned from_utf8:1;
+ unsigned to_utf8:1;
+} ngx_http_charset_ctx_t;
+
+
+typedef struct {
+ ngx_http_charset_tables_t *table;
+ ngx_http_charset_t *charset;
+ ngx_uint_t characters;
+} ngx_http_charset_conf_ctx_t;
+
+
+static ngx_int_t ngx_http_destination_charset(ngx_http_request_t *r,
+ ngx_str_t *name);
+static ngx_int_t ngx_http_main_request_charset(ngx_http_request_t *r,
+ ngx_str_t *name);
+static ngx_int_t ngx_http_source_charset(ngx_http_request_t *r,
+ ngx_str_t *name);
+static ngx_int_t ngx_http_get_charset(ngx_http_request_t *r, ngx_str_t *name);
+static ngx_inline void ngx_http_set_charset(ngx_http_request_t *r,
+ ngx_str_t *charset);
+static ngx_int_t ngx_http_charset_ctx(ngx_http_request_t *r,
+ ngx_http_charset_t *charsets, ngx_int_t charset, ngx_int_t source_charset);
+static ngx_uint_t ngx_http_charset_recode(ngx_buf_t *b, u_char *table);
+static ngx_chain_t *ngx_http_charset_recode_from_utf8(ngx_pool_t *pool,
+ ngx_buf_t *buf, ngx_http_charset_ctx_t *ctx);
+static ngx_chain_t *ngx_http_charset_recode_to_utf8(ngx_pool_t *pool,
+ ngx_buf_t *buf, ngx_http_charset_ctx_t *ctx);
+
+static ngx_chain_t *ngx_http_charset_get_buf(ngx_pool_t *pool,
+ ngx_http_charset_ctx_t *ctx);
+static ngx_chain_t *ngx_http_charset_get_buffer(ngx_pool_t *pool,
+ ngx_http_charset_ctx_t *ctx, size_t size);
+
+static char *ngx_http_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_charset_map(ngx_conf_t *cf, ngx_command_t *dummy,
+ void *conf);
+
+static char *ngx_http_set_charset_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static ngx_int_t ngx_http_add_charset(ngx_array_t *charsets, ngx_str_t *name);
+
+static void *ngx_http_charset_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_charset_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_charset_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_charset_postconfiguration(ngx_conf_t *cf);
+
+
+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;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_chunked_filter_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_chunked_filter_module.c
new file mode 100644
index 00000000000..9c4c5de14cb
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_chunked_filter_module.c
@@ -0,0 +1,204 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+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;
+
+ if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED
+ || r->headers_out.status == NGX_HTTP_NO_CONTENT
+ || 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;
+
+ } 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_buf_t *b;
+ ngx_chain_t out, tail, *cl, *tl, **ll;
+
+ if (in == NULL || !r->chunked || r->header_only) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ out.buf = NULL;
+ ll = &out.next;
+
+ 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) {
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ /* the "0000000000000000" is 64-bit hexadimal string */
+
+ chunk = ngx_palloc(r->pool, sizeof("0000000000000000" CRLF) - 1);
+ if (chunk == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->temporary = 1;
+ b->pos = chunk;
+ b->last = ngx_sprintf(chunk, "%xO" CRLF, size);
+
+ out.buf = b;
+ }
+
+ if (cl->buf->last_buf) {
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ 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;
+
+ if (size == 0) {
+ b->pos += 2;
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_next_body_filter(r, &out);
+ }
+
+ } else {
+ if (size == 0) {
+ *ll = NULL;
+ return ngx_http_next_body_filter(r, out.next);
+ }
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->memory = 1;
+ b->pos = (u_char *) CRLF;
+ b->last = b->pos + 2;
+ }
+
+ tail.buf = b;
+ tail.next = NULL;
+ *ll = &tail;
+
+ return ngx_http_next_body_filter(r, &out);
+}
+
+
+static ngx_int_t
+ngx_http_chunked_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_chunked_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_chunked_body_filter;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_dav_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_dav_module.c
new file mode 100644
index 00000000000..0761c1654fd
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_dav_module.c
@@ -0,0 +1,1140 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#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,
+ "can not 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;
+
+ 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, &copy.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, &copy.path, dir);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+ }
+ }
+
+ if (ngx_is_dir(&fi)) {
+
+ path.len -= 2; /* omit "/\0" */
+
+ if (r->method == NGX_HTTP_MOVE) {
+ if (ngx_rename_file(path.data, copy.path.data) != NGX_FILE_ERROR) {
+ return NGX_HTTP_CREATED;
+ }
+ }
+
+ if (ngx_create_dir(copy.path.data, ngx_file_access(&fi))
+ == NGX_FILE_ERROR)
+ {
+ return ngx_http_dav_error(r->connection->log, ngx_errno,
+ NGX_HTTP_NOT_FOUND,
+ ngx_create_dir_n, copy.path.data);
+ }
+
+ copy.len = path.len;
+
+ tree.init_handler = NULL;
+ tree.file_handler = ngx_http_dav_copy_tree_file;
+ tree.pre_tree_handler = ngx_http_dav_copy_dir;
+ tree.post_tree_handler = ngx_http_dav_copy_dir_time;
+ tree.spec_handler = ngx_http_dav_noop;
+ tree.data = &copy;
+ tree.alloc = 0;
+ tree.log = r->connection->log;
+
+ if (ngx_walk_tree(&tree, &path) == NGX_OK) {
+
+ if (r->method == NGX_HTTP_MOVE) {
+ rc = ngx_http_dav_delete_path(r, &path, 1);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+ }
+
+ return NGX_HTTP_CREATED;
+ }
+
+ } else {
+
+ if (r->method == NGX_HTTP_MOVE) {
+
+ dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
+
+ ext.access = 0;
+ ext.path_access = dlcf->access;
+ ext.time = -1;
+ ext.create_path = 1;
+ ext.delete_file = 0;
+ ext.log = r->connection->log;
+
+ if (ngx_ext_rename_file(&path, &copy.path, &ext) == NGX_OK) {
+ return NGX_HTTP_NO_CONTENT;
+ }
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
+
+ cf.size = ngx_file_size(&fi);
+ cf.buf_size = 0;
+ cf.access = dlcf->access;
+ cf.time = ngx_file_mtime(&fi);
+ cf.log = r->connection->log;
+
+ if (ngx_copy_file(path.data, copy.path.data, &cf) == NGX_OK) {
+ return NGX_HTTP_NO_CONTENT;
+ }
+ }
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_dav_copy_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+ u_char *p, *dir;
+ size_t len;
+ ngx_http_dav_copy_ctx_t *copy;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "http copy dir: \"%s\"", path->data);
+
+ copy = ctx->data;
+
+ len = copy->path.len + path->len;
+
+ dir = ngx_alloc(len + 1, ctx->log);
+ if (dir == NULL) {
+ return NGX_ABORT;
+ }
+
+ p = ngx_cpymem(dir, copy->path.data, copy->path.len);
+ (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "http copy dir to: \"%s\"", dir);
+
+ if (ngx_create_dir(dir, ngx_dir_access(ctx->access)) == NGX_FILE_ERROR) {
+ (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_create_dir_n,
+ dir);
+ }
+
+ ngx_free(dir);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_copy_dir_time(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+ u_char *p, *dir;
+ size_t len;
+ ngx_http_dav_copy_ctx_t *copy;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "http copy dir time: \"%s\"", path->data);
+
+ copy = ctx->data;
+
+ len = copy->path.len + path->len;
+
+ dir = ngx_alloc(len + 1, ctx->log);
+ if (dir == NULL) {
+ return NGX_ABORT;
+ }
+
+ p = ngx_cpymem(dir, copy->path.data, copy->path.len);
+ (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "http copy dir time to: \"%s\"", dir);
+
+#if (NGX_WIN32)
+ {
+ ngx_fd_t fd;
+
+ fd = ngx_open_file(dir, NGX_FILE_RDWR, NGX_FILE_OPEN, 0);
+
+ if (fd == NGX_INVALID_FILE) {
+ (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_open_file_n, dir);
+ goto failed;
+ }
+
+ if (ngx_set_file_time(NULL, fd, ctx->mtime) != NGX_OK) {
+ ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno,
+ ngx_set_file_time_n " \"%s\" failed", dir);
+ }
+
+ if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", dir);
+ }
+ }
+
+failed:
+
+#else
+
+ if (ngx_set_file_time(dir, 0, ctx->mtime) != NGX_OK) {
+ ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno,
+ ngx_set_file_time_n " \"%s\" failed", dir);
+ }
+
+#endif
+
+ ngx_free(dir);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_copy_tree_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+ u_char *p, *file;
+ size_t len;
+ ngx_copy_file_t cf;
+ ngx_http_dav_copy_ctx_t *copy;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "http copy file: \"%s\"", path->data);
+
+ copy = ctx->data;
+
+ len = copy->path.len + path->len;
+
+ file = ngx_alloc(len + 1, ctx->log);
+ if (file == NULL) {
+ return NGX_ABORT;
+ }
+
+ p = ngx_cpymem(file, copy->path.data, copy->path.len);
+ (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "http copy file to: \"%s\"", file);
+
+ cf.size = ctx->size;
+ cf.buf_size = 0;
+ cf.access = ctx->access;
+ cf.time = ctx->mtime;
+ cf.log = ctx->log;
+
+ (void) ngx_copy_file(path->data, file, &cf);
+
+ ngx_free(file);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_depth(ngx_http_request_t *r, ngx_int_t dflt)
+{
+ ngx_table_elt_t *depth;
+
+ depth = r->headers_in.depth;
+
+ if (depth == NULL) {
+ return dflt;
+ }
+
+ if (depth->value.len == 1) {
+
+ if (depth->value.data[0] == '0') {
+ return 0;
+ }
+
+ if (depth->value.data[0] == '1') {
+ return 1;
+ }
+
+ } else {
+
+ if (depth->value.len == sizeof("infinity") - 1
+ && ngx_strcmp(depth->value.data, "infinity") == 0)
+ {
+ return NGX_HTTP_DAV_INFINITY_DEPTH;
+ }
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client sent invalid \"Depth\" header: \"%V\"",
+ &depth->value);
+
+ return NGX_HTTP_DAV_INVALID_DEPTH;
+}
+
+
+static ngx_int_t
+ngx_http_dav_error(ngx_log_t *log, ngx_err_t err, ngx_int_t not_found,
+ char *failed, u_char *path)
+{
+ ngx_int_t rc;
+ ngx_uint_t level;
+
+ if (err == NGX_ENOENT || err == NGX_ENOTDIR || err == NGX_ENAMETOOLONG) {
+ level = NGX_LOG_ERR;
+ rc = not_found;
+
+ } else if (err == NGX_EACCES || err == NGX_EPERM) {
+ level = NGX_LOG_ERR;
+ rc = NGX_HTTP_FORBIDDEN;
+
+ } else if (err == NGX_EEXIST) {
+ level = NGX_LOG_ERR;
+ rc = NGX_HTTP_NOT_ALLOWED;
+
+ } else if (err == NGX_ENOSPC) {
+ level = NGX_LOG_CRIT;
+ rc = NGX_HTTP_INSUFFICIENT_STORAGE;
+
+ } else {
+ level = NGX_LOG_CRIT;
+ rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_log_error(level, log, err, "%s \"%s\" failed", failed, path);
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_dav_location(ngx_http_request_t *r, u_char *path)
+{
+ u_char *location;
+ ngx_http_core_loc_conf_t *clcf;
+
+ r->headers_out.location = ngx_palloc(r->pool, sizeof(ngx_table_elt_t));
+ if (r->headers_out.location == NULL) {
+ return NGX_ERROR;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (!clcf->alias && clcf->root_lengths == NULL) {
+ location = path + clcf->root.len;
+
+ } else {
+ location = ngx_pnalloc(r->pool, r->uri.len);
+ if (location == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(location, r->uri.data, r->uri.len);
+ }
+
+ /*
+ * we do not need to set the r->headers_out.location->hash and
+ * r->headers_out.location->key fields
+ */
+
+ r->headers_out.location->value.len = r->uri.len;
+ r->headers_out.location->value.data = location;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_dav_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_dav_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_dav_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->methods = 0;
+ */
+
+ conf->min_delete_depth = NGX_CONF_UNSET_UINT;
+ conf->access = NGX_CONF_UNSET_UINT;
+ conf->create_full_put_path = NGX_CONF_UNSET;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_dav_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_dav_loc_conf_t *prev = parent;
+ ngx_http_dav_loc_conf_t *conf = child;
+
+ ngx_conf_merge_bitmask_value(conf->methods, prev->methods,
+ (NGX_CONF_BITMASK_SET|NGX_HTTP_DAV_OFF));
+
+ ngx_conf_merge_uint_value(conf->min_delete_depth,
+ prev->min_delete_depth, 0);
+
+ ngx_conf_merge_uint_value(conf->access, prev->access, 0600);
+
+ ngx_conf_merge_value(conf->create_full_put_path,
+ prev->create_full_put_path, 0);
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_dav_handler;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_degradation_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_degradation_module.c
new file mode 100644
index 00000000000..a5f83bb20a7
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_degradation_module.c
@@ -0,0 +1,242 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ size_t sbrk_size;
+} ngx_http_degradation_main_conf_t;
+
+
+typedef struct {
+ ngx_uint_t degrade;
+} ngx_http_degradation_loc_conf_t;
+
+
+static ngx_conf_enum_t ngx_http_degrade[] = {
+ { ngx_string("204"), 204 },
+ { ngx_string("444"), 444 },
+ { ngx_null_string, 0 }
+};
+
+
+static void *ngx_http_degradation_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_degradation_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_degradation_merge_loc_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_http_degradation(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static ngx_int_t ngx_http_degradation_init(ngx_conf_t *cf);
+
+
+static ngx_command_t ngx_http_degradation_commands[] = {
+
+ { ngx_string("degradation"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+ ngx_http_degradation,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("degrade"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_degradation_loc_conf_t, degrade),
+ &ngx_http_degrade },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_degradation_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_degradation_init, /* postconfiguration */
+
+ ngx_http_degradation_create_main_conf, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_degradation_create_loc_conf, /* create location configuration */
+ ngx_http_degradation_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_degradation_module = {
+ NGX_MODULE_V1,
+ &ngx_http_degradation_module_ctx, /* module context */
+ ngx_http_degradation_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_degradation_handler(ngx_http_request_t *r)
+{
+ ngx_http_degradation_loc_conf_t *dlcf;
+
+ dlcf = ngx_http_get_module_loc_conf(r, ngx_http_degradation_module);
+
+ if (dlcf->degrade && ngx_http_degraded(r)) {
+ return dlcf->degrade;
+ }
+
+ return NGX_DECLINED;
+}
+
+
+ngx_uint_t
+ngx_http_degraded(ngx_http_request_t *r)
+{
+ time_t now;
+ ngx_uint_t log;
+ static size_t sbrk_size;
+ static time_t sbrk_time;
+ ngx_http_degradation_main_conf_t *dmcf;
+
+ dmcf = ngx_http_get_module_main_conf(r, ngx_http_degradation_module);
+
+ if (dmcf->sbrk_size) {
+
+ log = 0;
+ now = ngx_time();
+
+ /* lock mutex */
+
+ if (now != sbrk_time) {
+
+ /*
+ * ELF/i386 is loaded at 0x08000000, 128M
+ * ELF/amd64 is loaded at 0x00400000, 4M
+ *
+ * use a function address to substract the loading address
+ */
+
+ sbrk_size = (size_t) sbrk(0) - ((uintptr_t) ngx_palloc & ~0x3FFFFF);
+ sbrk_time = now;
+ log = 1;
+ }
+
+ /* unlock mutex */
+
+ if (sbrk_size >= dmcf->sbrk_size) {
+ if (log) {
+ ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+ "degradation sbrk:%uzM",
+ sbrk_size / (1024 * 1024));
+ }
+
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+static void *
+ngx_http_degradation_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_degradation_main_conf_t *dmcf;
+
+ dmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_degradation_main_conf_t));
+ if (dmcf == NULL) {
+ return NULL;
+ }
+
+ return dmcf;
+}
+
+
+static void *
+ngx_http_degradation_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_degradation_loc_conf_t *conf;
+
+ conf = ngx_palloc(cf->pool, sizeof(ngx_http_degradation_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->degrade = NGX_CONF_UNSET_UINT;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_degradation_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_degradation_loc_conf_t *prev = parent;
+ ngx_http_degradation_loc_conf_t *conf = child;
+
+ ngx_conf_merge_uint_value(conf->degrade, prev->degrade, 0);
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_degradation(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_degradation_main_conf_t *dmcf = conf;
+
+ ngx_str_t *value, s;
+
+ value = cf->args->elts;
+
+ if (ngx_strncmp(value[1].data, "sbrk=", 5) == 0) {
+
+ s.len = value[1].len - 5;
+ s.data = value[1].data + 5;
+
+ dmcf->sbrk_size = ngx_parse_size(&s);
+ if (dmcf->sbrk_size == (size_t) NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid sbrk size \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[1]);
+
+ return NGX_CONF_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_degradation_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_degradation_handler;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_empty_gif_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_empty_gif_module.c
new file mode 100644
index 00000000000..a896bd4b604
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_empty_gif_module.c
@@ -0,0 +1,146 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static char *ngx_http_empty_gif(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+static ngx_command_t ngx_http_empty_gif_commands[] = {
+
+ { ngx_string("empty_gif"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
+ ngx_http_empty_gif,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+/* the minimal single pixel transparent GIF, 43 bytes */
+
+static u_char ngx_empty_gif[] = {
+
+ 'G', 'I', 'F', '8', '9', 'a', /* header */
+
+ /* logical screen descriptor */
+ 0x01, 0x00, /* logical screen width */
+ 0x01, 0x00, /* logical screen height */
+ 0x80, /* global 1-bit color table */
+ 0x01, /* background color #1 */
+ 0x00, /* no aspect ratio */
+
+ /* global color table */
+ 0x00, 0x00, 0x00, /* #0: black */
+ 0xff, 0xff, 0xff, /* #1: white */
+
+ /* graphic control extension */
+ 0x21, /* extension introducer */
+ 0xf9, /* graphic control label */
+ 0x04, /* block size */
+ 0x01, /* transparent color is given, */
+ /* no disposal specified, */
+ /* user input is not expected */
+ 0x00, 0x00, /* delay time */
+ 0x01, /* transparent color #1 */
+ 0x00, /* block terminator */
+
+ /* image descriptor */
+ 0x2c, /* image separator */
+ 0x00, 0x00, /* image left position */
+ 0x00, 0x00, /* image top position */
+ 0x01, 0x00, /* image width */
+ 0x01, 0x00, /* image height */
+ 0x00, /* no local color table, no interlaced */
+
+ /* table based image data */
+ 0x02, /* LZW minimum code size, */
+ /* must be at least 2-bit */
+ 0x02, /* block size */
+ 0x4c, 0x01, /* compressed bytes 01_001_100, 0000000_1 */
+ /* 100: clear code */
+ /* 001: 1 */
+ /* 101: end of information code */
+ 0x00, /* block terminator */
+
+ 0x3B /* trailer */
+};
+
+
+static ngx_http_module_t ngx_http_empty_gif_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_empty_gif_module = {
+ NGX_MODULE_V1,
+ &ngx_http_empty_gif_module_ctx, /* module context */
+ ngx_http_empty_gif_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_http_gif_type = ngx_string("image/gif");
+
+
+static ngx_int_t
+ngx_http_empty_gif_handler(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_http_complex_value_t cv;
+
+ 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;
+ }
+
+ ngx_memzero(&cv, sizeof(ngx_http_complex_value_t));
+
+ cv.value.len = sizeof(ngx_empty_gif);
+ cv.value.data = ngx_empty_gif;
+ r->headers_out.last_modified_time = 23349600;
+
+ return ngx_http_send_response(r, NGX_HTTP_OK, &ngx_http_gif_type, &cv);
+}
+
+
+static char *
+ngx_http_empty_gif(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf;
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+ clcf->handler = ngx_http_empty_gif_handler;
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_fastcgi_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_fastcgi_module.c
new file mode 100644
index 00000000000..0bc95f8da4a
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_fastcgi_module.c
@@ -0,0 +1,2869 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+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;
+
+#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_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(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_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 },
+
+#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_TAKE2,
+ ngx_conf_set_keyval_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_FLAG,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, catch_stderr),
+ 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;
+
+ 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;
+
+ 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, 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);
+
+ 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);
+
+ 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_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);
+
+ 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);
+
+ *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;
+
+ 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 closed prematurely 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_version;
+ }
+
+ 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_cpystrn(h->key.data, r->header_name_start,
+ h->key.len + 1);
+ ngx_cpystrn(h->value.data, r->header_start,
+ h->value.len + 1);
+ }
+
+ 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) {
+ if (f->padding) {
+ f->state = ngx_http_fastcgi_st_padding;
+ } else {
+ f->state = ngx_http_fastcgi_st_version;
+ }
+ }
+
+ 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);
+
+ 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(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;
+
+ if (buf->pos == buf->last) {
+ return NGX_OK;
+ }
+
+ r = p->input_ctx;
+ f = ngx_http_get_module_ctx(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_version;
+ 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) {
+ f->state = ngx_http_fastcgi_st_version;
+ p->upstream_done = 1;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0,
+ "http fastcgi sent end request");
+
+ break;
+ }
+ }
+
+
+ if (f->state == ngx_http_fastcgi_st_padding) {
+
+ 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);
+
+ if (f->pos == f->last) {
+ break;
+ }
+
+ } else {
+ f->state = ngx_http_fastcgi_st_version;
+ }
+
+ continue;
+ }
+
+
+ /* f->type == NGX_HTTP_FASTCGI_STDOUT */
+
+ if (f->pos == f->last) {
+ break;
+ }
+
+ if (p->free) {
+ b = p->free->buf;
+ p->free = p->free->next;
+
+ } 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) {
+
+ if (f->padding) {
+ f->state = ngx_http_fastcgi_st_padding;
+ } else {
+ f->state = ngx_http_fastcgi_st_version;
+ }
+
+ f->pos += f->length;
+ b->last = f->pos;
+
+ continue;
+ }
+
+ if (f->pos + f->length == f->last) {
+
+ if (f->padding) {
+ f->state = ngx_http_fastcgi_st_padding;
+ } else {
+ f->state = ngx_http_fastcgi_st_version;
+ }
+
+ b->last = f->last;
+
+ break;
+ }
+
+ f->length -= f->last - f->pos;
+
+ b->last = f->last;
+
+ break;
+
+ }
+
+ 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.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;
+#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;
+
+ 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;
+
+ u_char *p;
+ size_t size;
+ uintptr_t *code;
+ ngx_uint_t i;
+ ngx_array_t headers_names;
+ ngx_keyval_t *src;
+ ngx_hash_key_t *hk;
+ ngx_hash_init_t hash;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_script_compile_t sc;
+ ngx_http_script_copy_code_t *copy;
+
+ 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_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 or bigger than "
+ "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 or bigger than "
+ "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 "
+ "the temporary files usage or must be equal or bigger than "
+ "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_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;
+ }
+
+#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_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 (conf->params_source == NULL) {
+ conf->flushes = prev->flushes;
+ conf->params_len = prev->params_len;
+ conf->params = prev->params;
+ conf->params_source = prev->params_source;
+ conf->headers_hash = prev->headers_hash;
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->params_source == NULL) {
+
+ if ((conf->upstream.cache == NULL)
+ == (prev->upstream.cache == NULL))
+ {
+ return NGX_CONF_OK;
+ }
+
+ /* 6 is a number of ngx_http_fastcgi_cache_headers entries */
+ conf->params_source = ngx_array_create(cf->pool, 6,
+ sizeof(ngx_keyval_t));
+ if (conf->params_source == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+#else
+
+ if (conf->params_source == NULL) {
+ return NGX_CONF_OK;
+ }
+
+#endif
+ }
+
+ conf->params_len = ngx_array_create(cf->pool, 64, 1);
+ if (conf->params_len == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->params = ngx_array_create(cf->pool, 512, 1);
+ if (conf->params == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ src = conf->params_source->elts;
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->upstream.cache) {
+ ngx_keyval_t *h, *s;
+
+ for (h = ngx_http_fastcgi_cache_headers; h->key.len; h++) {
+
+ for (i = 0; i < conf->params_source->nelts; i++) {
+ if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) {
+ goto next;
+ }
+ }
+
+ s = ngx_array_push(conf->params_source);
+ if (s == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *s = *h;
+
+ src = conf->params_source->elts;
+
+ next:
+
+ h++;
+ }
+ }
+
+#endif
+
+ for (i = 0; i < conf->params_source->nelts; 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_CONF_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_CONF_ERROR;
+ }
+
+ copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code;
+ copy->len = src[i].key.len;
+
+
+ size = (sizeof(ngx_http_script_copy_code_t)
+ + src[i].key.len + sizeof(uintptr_t) - 1)
+ & ~(sizeof(uintptr_t) - 1);
+
+ copy = ngx_array_push_n(conf->params, size);
+ if (copy == NULL) {
+ return NGX_CONF_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_CONF_ERROR;
+ }
+
+ code = ngx_array_push_n(conf->params_len, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+
+
+ code = ngx_array_push_n(conf->params, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+ }
+
+ code = ngx_array_push_n(conf->params_len, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_CONF_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;
+
+ if (ngx_hash_init(&hash, headers_names.elts, headers_names.nelts) != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+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.len) {
+ return "is duplicate";
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &flcf->cache_key;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+#endif
+
+
+static char *
+ngx_http_fastcgi_lowat_check(ngx_conf_t *cf, void *post, void *data)
+{
+#if (NGX_FREEBSD)
+ ssize_t *np = data;
+
+ if ((u_long) *np >= ngx_freebsd_net_inet_tcp_sendspace) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"fastcgi_send_lowat\" must be less than %d "
+ "(sysctl net.inet.tcp.sendspace)",
+ ngx_freebsd_net_inet_tcp_sendspace);
+
+ return NGX_CONF_ERROR;
+ }
+
+#elif !(NGX_HAVE_SO_SNDLOWAT)
+ ssize_t *np = data;
+
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "\"fastcgi_send_lowat\" is not supported, ignored");
+
+ *np = 0;
+
+#endif
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_flv_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_flv_module.c
new file mode 100644
index 00000000000..270d969db30
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_flv_module.c
@@ -0,0 +1,253 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static char *ngx_http_flv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+static ngx_command_t ngx_http_flv_commands[] = {
+
+ { ngx_string("flv"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
+ ngx_http_flv,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static u_char ngx_flv_header[] = "FLV\x1\x1\0\0\0\x9\0\0\0\x9";
+
+
+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_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:
+
+ 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_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 = 1;
+ b->last_in_chain = 1;
+
+ b->file->fd = of.fd;
+ b->file->name = path;
+ b->file->log = log;
+ b->file->directio = of.is_directio;
+
+ out[1].buf = b;
+ out[1].next = NULL;
+
+ return ngx_http_output_filter(r, &out[i]);
+}
+
+
+static char *
+ngx_http_flv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf;
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+ clcf->handler = ngx_http_flv_handler;
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_geo_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_geo_module.c
new file mode 100644
index 00000000000..7c5019605e2
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_geo_module.c
@@ -0,0 +1,1440 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_http_variable_value_t *value;
+ u_short start;
+ u_short end;
+} ngx_http_geo_range_t;
+
+
+typedef struct {
+ ngx_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;
+ 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;
+} ngx_http_geo_conf_ctx_t;
+
+
+typedef struct {
+ union {
+ ngx_radix_tree_t *tree;
+ ngx_http_geo_high_ranges_t high;
+ } u;
+
+ ngx_array_t *proxies;
+
+ ngx_int_t index;
+} ngx_http_geo_ctx_t;
+
+
+static in_addr_t ngx_http_geo_addr(ngx_http_request_t *r,
+ ngx_http_geo_ctx_t *ctx);
+static in_addr_t ngx_http_geo_real_addr(ngx_http_request_t *r,
+ ngx_http_geo_ctx_t *ctx);
+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 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 endianess;
+ 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
+};
+
+
+/* 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;
+
+ ngx_http_variable_value_t *vv;
+
+ vv = (ngx_http_variable_value_t *)
+ ngx_radix32tree_find(ctx->u.tree, ngx_http_geo_addr(r, ctx));
+
+ *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 addr;
+ ngx_uint_t n;
+ ngx_http_geo_range_t *range;
+
+ *v = *ctx->u.high.default_value;
+
+ addr = ngx_http_geo_addr(r, ctx);
+
+ range = ctx->u.high.low[addr >> 16];
+
+ if (range) {
+ n = addr & 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 in_addr_t
+ngx_http_geo_addr(ngx_http_request_t *r, ngx_http_geo_ctx_t *ctx)
+{
+ u_char *p, *ip;
+ size_t len;
+ in_addr_t addr;
+ ngx_uint_t i, n;
+ ngx_in_cidr_t *proxies;
+ ngx_table_elt_t *xfwd;
+
+ addr = ngx_http_geo_real_addr(r, ctx);
+
+ xfwd = r->headers_in.x_forwarded_for;
+
+ if (xfwd == NULL || ctx->proxies == NULL) {
+ return addr;
+ }
+
+ proxies = ctx->proxies->elts;
+ n = ctx->proxies->nelts;
+
+ for (i = 0; i < n; i++) {
+ if ((addr & proxies[i].mask) == proxies[i].addr) {
+
+ len = xfwd->value.len;
+ ip = xfwd->value.data;
+
+ for (p = ip + len - 1; p > ip; p--) {
+ if (*p == ' ' || *p == ',') {
+ p++;
+ len -= p - ip;
+ ip = p;
+ break;
+ }
+ }
+
+ return ntohl(ngx_inet_addr(ip, len));
+ }
+ }
+
+ return addr;
+}
+
+
+static in_addr_t
+ngx_http_geo_real_addr(ngx_http_request_t *r, ngx_http_geo_ctx_t *ctx)
+{
+ struct sockaddr_in *sin;
+ ngx_http_variable_value_t *v;
+#if (NGX_HAVE_INET6)
+ u_char *p;
+ in_addr_t addr;
+ struct sockaddr_in6 *sin6;
+#endif
+
+ if (ctx->index == -1) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http geo started: %V", &r->connection->addr_text);
+
+ switch (r->connection->sockaddr->sa_family) {
+
+ case AF_INET:
+ sin = (struct sockaddr_in *) r->connection->sockaddr;
+ return ntohl(sin->sin_addr.s_addr);
+
+#if (NGX_HAVE_INET6)
+
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) r->connection->sockaddr;
+
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ p = sin6->sin6_addr.s6_addr;
+ addr = p[12] << 24;
+ addr += p[13] << 16;
+ addr += p[14] << 8;
+ addr += p[15];
+
+ return addr;
+ }
+
+#endif
+ }
+
+ return INADDR_NONE;
+ }
+
+ 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 0;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http geo started: %v", v);
+
+ return ntohl(ngx_inet_addr(v->data, v->len));
+}
+
+
+static char *
+ngx_http_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *rv;
+ void **p;
+ 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;
+
+ 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];
+ 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];
+ 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(16384, 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(16384, 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;
+
+ if (ctx.high.low) {
+
+ if (!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;
+ }
+
+ p = (void **) ngx_cpymem(ctx.high.low[i], a->elts, len);
+ *p = 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);
+ }
+ }
+
+ geo->u.high = ctx.high;
+
+ var->get_handler = ngx_http_geo_range_variable;
+ var->data = (uintptr_t) geo;
+
+ if (ctx.high.default_value == NULL) {
+ ctx.high.default_value = &ngx_http_variable_null_value;
+ }
+
+ 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.tree = ctx.tree;
+
+ 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_find(ctx.tree, 0) != NGX_RADIX_NO_VALUE) {
+ return rv;
+ }
+
+ if (ngx_radix32tree_insert(ctx.tree, 0, 0,
+ (uintptr_t) &ngx_http_variable_null_value)
+ == NGX_ERROR)
+ {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ 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) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the \"ranges\" directive must be "
+ "the first directive inside \"geo\" block");
+ goto failed;
+ }
+
+ ctx->ranges = 1;
+
+ rv = NGX_CONF_OK;
+
+ goto done;
+ }
+ }
+
+ if (cf->args->nelts != 2) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid number of the geo parameters");
+ goto failed;
+ }
+
+ if (ngx_strcmp(value[0].data, "include") == 0) {
+
+ rv = ngx_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\" may not 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)
+{
+ ngx_int_t rc, del;
+ ngx_str_t *net;
+ ngx_uint_t i;
+ ngx_cidr_t cidr;
+ ngx_http_variable_value_t *val, *old;
+
+ if (ctx->tree == NULL) {
+ ctx->tree = ngx_radix_tree_create(ctx->pool, -1);
+ if (ctx->tree == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (ngx_strcmp(value[0].data, "default") == 0) {
+ cidr.u.in.addr = 0;
+ cidr.u.in.mask = 0;
+ net = &value[0];
+
+ } else {
+ 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 (del) {
+ if (ngx_radix32tree_delete(ctx->tree, cidr.u.in.addr,
+ cidr.u.in.mask)
+ != NGX_OK)
+ {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "no network \"%V\" to delete", net);
+ }
+
+ return NGX_CONF_OK;
+ }
+ }
+
+ val = ngx_http_geo_value(cf, ctx, &value[1]);
+
+ if (val == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 2; i; i--) {
+ 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 & cidr.u.in.mask);
+
+ 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;
+ }
+ }
+
+ 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_in_cidr_t *c;
+
+ if (ctx->proxies == NULL) {
+ ctx->proxies = ngx_array_create(ctx->pool, 4, sizeof(ngx_in_cidr_t));
+ if (ctx->proxies == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ c = ngx_array_push(ctx->proxies);
+ if (c == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ c->addr = cidr->u.in.addr;
+ c->mask = cidr->u.in.mask;
+
+ 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->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 (cidr->family != AF_INET) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"geo\" supports IPv4 only");
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "low address bits of %V are meaningless", net);
+ }
+
+ cidr->u.in.addr = ntohl(cidr->u.in.addr);
+ cidr->u.in.mask = ntohl(cidr->u.in.mask);
+
+ 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\" may not 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\" may not be mixed with \"%s\"",
+ name->data, ctx->include_name.data);
+ rc = NGX_ERROR;
+ goto done;
+ }
+
+ if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
+ ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+ ngx_fd_info_n " \"%s\" failed", name->data);
+ goto failed;
+ }
+
+ size = (size_t) ngx_file_size(&fi);
+ mtime = ngx_file_mtime(&fi);
+
+ ch = name->data[name->len - 4];
+ name->data[name->len - 4] = '\0';
+
+ if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) {
+ ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+ ngx_file_info_n " \"%s\" failed", name->data);
+ goto failed;
+ }
+
+ name->data[name->len - 4] = ch;
+
+ if (mtime < ngx_file_mtime(&fi)) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "stale binary geo range base \"%s\"", name->data);
+ goto failed;
+ }
+
+ base = ngx_palloc(ctx->pool, size);
+ if (base == NULL) {
+ goto failed;
+ }
+
+ n = ngx_read_file(&file, base, size, 0);
+
+ if (n == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+ ngx_read_file_n " \"%s\" failed", name->data);
+ goto failed;
+ }
+
+ if ((size_t) n != size) {
+ ngx_conf_log_error(NGX_LOG_CRIT, cf, 0,
+ ngx_read_file_n " \"%s\" returned only %z bytes instead of %z",
+ name->data, n, size);
+ goto failed;
+ }
+
+ header = (ngx_http_geo_header_t *) base;
+
+ if (size < 16 || ngx_memcmp(&ngx_http_geo_header, header, 12) != 0) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "incompatible binary geo range base \"%s\"", name->data);
+ goto failed;
+ }
+
+ ngx_crc32_init(crc32);
+
+ vv = (ngx_http_variable_value_t *) (base + sizeof(ngx_http_geo_header_t));
+
+ while(vv->data) {
+ len = ngx_align(sizeof(ngx_http_variable_value_t) + vv->len,
+ sizeof(void *));
+ ngx_crc32_update(&crc32, (u_char *) vv, len);
+ vv->data += (size_t) base;
+ vv = (ngx_http_variable_value_t *) ((u_char *) vv + len);
+ }
+ ngx_crc32_update(&crc32, (u_char *) vv, sizeof(ngx_http_variable_value_t));
+ vv++;
+
+ ranges = (ngx_http_geo_range_t **) vv;
+
+ for (i = 0; i < 0x10000; i++) {
+ ngx_crc32_update(&crc32, (u_char *) &ranges[i], sizeof(void *));
+ if (ranges[i]) {
+ ranges[i] = (ngx_http_geo_range_t *)
+ ((u_char *) ranges[i] + (size_t) base);
+ }
+ }
+
+ range = (ngx_http_geo_range_t *) &ranges[0x10000];
+
+ while ((u_char *) range < base + size) {
+ while (range->value) {
+ ngx_crc32_update(&crc32, (u_char *) range,
+ sizeof(ngx_http_geo_range_t));
+ range->value = (ngx_http_variable_value_t *)
+ ((u_char *) range->value + (size_t) base);
+ range++;
+ }
+ ngx_crc32_update(&crc32, (u_char *) range, sizeof(void *));
+ range = (ngx_http_geo_range_t *) ((u_char *) range + sizeof(void *));
+ }
+
+ ngx_crc32_final(crc32);
+
+ if (crc32 != header->crc32) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "CRC32 mismatch in binary geo range base \"%s\"", name->data);
+ goto failed;
+ }
+
+ ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0,
+ "using binary geo range base \"%s\"", name->data);
+
+ ctx->include_name = *name;
+ ctx->binary_include = 1;
+ ctx->high.low = ranges;
+ rc = NGX_OK;
+
+ goto done;
+
+failed:
+
+ rc = NGX_DECLINED;
+
+done:
+
+ if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", name->data);
+ }
+
+ return rc;
+}
+
+
+static void
+ngx_http_geo_create_binary_base(ngx_http_geo_conf_ctx_t *ctx)
+{
+ u_char *p;
+ uint32_t hash;
+ ngx_str_t s;
+ ngx_uint_t i;
+ ngx_file_mapping_t fm;
+ ngx_http_geo_range_t *r, *range, **ranges;
+ ngx_http_geo_header_t *header;
+ ngx_http_geo_variable_value_node_t *gvvn;
+
+ fm.name = ngx_pnalloc(ctx->temp_pool, ctx->include_name.len + 5);
+ if (fm.name == NULL) {
+ return;
+ }
+
+ ngx_sprintf(fm.name, "%V.bin%Z", &ctx->include_name);
+
+ fm.size = ctx->data_size;
+ fm.log = ctx->pool->log;
+
+ ngx_log_error(NGX_LOG_NOTICE, fm.log, 0,
+ "creating binary geo range base \"%s\"", fm.name);
+
+ if (ngx_create_file_mapping(&fm) != NGX_OK) {
+ return;
+ }
+
+ p = ngx_cpymem(fm.addr, &ngx_http_geo_header,
+ sizeof(ngx_http_geo_header_t));
+
+ p = ngx_http_geo_copy_values(fm.addr, p, ctx->rbtree.root,
+ ctx->rbtree.sentinel);
+
+ p += sizeof(ngx_http_variable_value_t);
+
+ ranges = (ngx_http_geo_range_t **) p;
+
+ p += 0x10000 * sizeof(ngx_http_geo_range_t *);
+
+ for (i = 0; i < 0x10000; i++) {
+ r = ctx->high.low[i];
+ if (r == NULL) {
+ continue;
+ }
+
+ range = (ngx_http_geo_range_t *) p;
+ ranges[i] = (ngx_http_geo_range_t *) (p - (u_char *) fm.addr);
+
+ do {
+ s.len = r->value->len;
+ s.data = r->value->data;
+ hash = ngx_crc32_long(s.data, s.len);
+ gvvn = (ngx_http_geo_variable_value_node_t *)
+ ngx_str_rbtree_lookup(&ctx->rbtree, &s, hash);
+
+ range->value = (ngx_http_variable_value_t *) gvvn->offset;
+ range->start = r->start;
+ range->end = r->end;
+ range++;
+
+ } while ((++r)->value);
+
+ range->value = NULL;
+
+ p = (u_char *) range + sizeof(void *);
+ }
+
+ header = fm.addr;
+ header->crc32 = ngx_crc32_long((u_char *) fm.addr
+ + sizeof(ngx_http_geo_header_t),
+ fm.size - sizeof(ngx_http_geo_header_t));
+
+ ngx_close_file_mapping(&fm);
+}
+
+
+static u_char *
+ngx_http_geo_copy_values(u_char *base, u_char *p, ngx_rbtree_node_t *node,
+ ngx_rbtree_node_t *sentinel)
+{
+ ngx_http_variable_value_t *vv;
+ ngx_http_geo_variable_value_node_t *gvvn;
+
+ if (node == sentinel) {
+ return p;
+ }
+
+ gvvn = (ngx_http_geo_variable_value_node_t *) node;
+ gvvn->offset = p - base;
+
+ vv = (ngx_http_variable_value_t *) p;
+ *vv = *gvvn->value;
+ p += sizeof(ngx_http_variable_value_t);
+ vv->data = (u_char *) (p - base);
+
+ p = ngx_cpymem(p, gvvn->sn.str.data, gvvn->sn.str.len);
+
+ p = ngx_align_ptr(p, sizeof(void *));
+
+ p = ngx_http_geo_copy_values(base, p, node->left, sentinel);
+
+ return ngx_http_geo_copy_values(base, p, node->right, sentinel);
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_geoip_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_geoip_module.c
new file mode 100644
index 00000000000..311a30ce397
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_geoip_module.c
@@ -0,0 +1,670 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <GeoIP.h>
+#include <GeoIPCity.h>
+
+
+typedef struct {
+ GeoIP *country;
+ GeoIP *org;
+ GeoIP *city;
+} 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);
+
+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_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 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_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 */
+ 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_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,
+ (uintptr_t) GeoIP_country_code_by_ipnum, 0, 0 },
+
+ { ngx_string("geoip_country_code3"), NULL,
+ ngx_http_geoip_country_variable,
+ (uintptr_t) GeoIP_country_code3_by_ipnum, 0, 0 },
+
+ { ngx_string("geoip_country_name"), NULL,
+ ngx_http_geoip_country_variable,
+ (uintptr_t) GeoIP_country_name_by_ipnum, 0, 0 },
+
+ { ngx_string("geoip_org"), NULL,
+ ngx_http_geoip_org_variable,
+ (uintptr_t) GeoIP_name_by_ipnum, 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)
+{
+ struct sockaddr_in *sin;
+#if (NGX_HAVE_INET6)
+ u_char *p;
+ u_long addr;
+ struct sockaddr_in6 *sin6;
+#endif
+
+ switch (r->connection->sockaddr->sa_family) {
+
+ case AF_INET:
+ sin = (struct sockaddr_in *) r->connection->sockaddr;
+ return ntohl(sin->sin_addr.s_addr);
+
+#if (NGX_HAVE_INET6)
+
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) r->connection->sockaddr;
+
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ p = sin6->sin6_addr.s6_addr;
+ addr = p[12] << 24;
+ addr += p[13] << 16;
+ addr += p[14] << 8;
+ addr += p[15];
+
+ return addr;
+ }
+
+#endif
+ }
+
+ return INADDR_NONE;
+}
+
+
+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_variable_handler_pt) data;
+
+ 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;
+ }
+
+ val = handler(gcf->country, ngx_http_geoip_addr(r));
+
+ 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)
+{
+ ngx_http_geoip_variable_handler_pt handler =
+ (ngx_http_geoip_variable_handler_pt) data;
+
+ const 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;
+ }
+
+ val = handler(gcf->org, ngx_http_geoip_addr(r));
+
+ 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_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) {
+ return GeoIP_record_by_ipnum(gcf->city, ngx_http_geoip_addr(r));
+ }
+
+ 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;
+ }
+
+ 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_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:
+ case GEOIP_PROXY_EDITION:
+ case GEOIP_NETSPEED_EDITION:
+
+ return NGX_CONF_OK;
+
+ 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;
+
+ 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;
+
+ default:
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid GeoIP City database \"%V\" type:%d",
+ &value[1], gcf->city->databaseType);
+ return NGX_CONF_ERROR;
+ }
+}
+
+
+static void
+ngx_http_geoip_cleanup(void *data)
+{
+ ngx_http_geoip_conf_t *gcf = data;
+
+ if (gcf->country) {
+ GeoIP_delete(gcf->country);
+ }
+
+ if (gcf->org) {
+ GeoIP_delete(gcf->org);
+ }
+
+ if (gcf->city) {
+ GeoIP_delete(gcf->city);
+ }
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_gzip_filter_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_gzip_filter_module.c
new file mode 100644
index 00000000000..d624e36ff84
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_gzip_filter_module.c
@@ -0,0 +1,1205 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <zlib.h>
+
+
+typedef struct {
+ ngx_flag_t enable;
+ ngx_flag_t no_buffer;
+
+ ngx_hash_t types;
+
+ ngx_bufs_t bufs;
+
+ size_t postpone_gzipping;
+ ngx_int_t level;
+ size_t wbits;
+ size_t memlevel;
+ ssize_t min_length;
+
+ ngx_array_t *types_keys;
+} ngx_http_gzip_conf_t;
+
+
+typedef struct {
+ ngx_chain_t *in;
+ ngx_chain_t *free;
+ ngx_chain_t *busy;
+ ngx_chain_t *out;
+ ngx_chain_t **last_out;
+
+ ngx_chain_t *copied;
+ ngx_chain_t *copy_buf;
+
+ ngx_buf_t *in_buf;
+ ngx_buf_t *out_buf;
+ ngx_int_t bufs;
+
+ void *preallocated;
+ char *free_mem;
+ ngx_uint_t allocated;
+
+ int wbits;
+ int memlevel;
+
+ unsigned flush:4;
+ unsigned redo:1;
+ unsigned done:1;
+ unsigned nomem:1;
+ unsigned gzheader:1;
+ unsigned buffering:1;
+
+ 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);
+
+ 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) {
+ 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(&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(&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--;
+ }
+ }
+
+ ctx->wbits = wbits;
+ ctx->memlevel = memlevel;
+
+ /*
+ * We preallocate a memory for zlib in one buffer (200K-400K), this
+ * decreases a number of malloc() and free() calls and also probably
+ * decreases a number of syscalls (sbrk()/mmap() and so on).
+ * Besides we free the memory as soon as a gzipping will complete
+ * and do not wait while a whole response will be sent to a client.
+ *
+ * 8K is for zlib deflate_state, it takes
+ * *) 5816 bytes on i386 and sparc64 (32-bit mode)
+ * *) 5920 bytes on amd64 and sparc64
+ */
+
+ ctx->allocated = 8192 + (1 << (wbits + 2)) + (1 << (memlevel + 9));
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_buffer(ngx_http_gzip_ctx_t *ctx, ngx_chain_t *in)
+{
+ size_t size, buffered;
+ ngx_buf_t *b, *buf;
+ ngx_chain_t *cl, **ll;
+ ngx_http_request_t *r;
+ ngx_http_gzip_conf_t *conf;
+
+ r = ctx->request;
+
+ r->connection->buffered |= NGX_HTTP_GZIP_BUFFERED;
+
+ buffered = 0;
+ ll = &ctx->in;
+
+ for (cl = ctx->in; cl; cl = cl->next) {
+ buffered += cl->buf->last - cl->buf->pos;
+ ll = &cl->next;
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+ while (in) {
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = in->buf;
+
+ size = b->last - b->pos;
+ buffered += size;
+
+ if (b->flush || b->last_buf || buffered > conf->postpone_gzipping) {
+ ctx->buffering = 0;
+ }
+
+ if (ctx->buffering && size) {
+
+ buf = ngx_create_temp_buf(r->pool, size);
+ if (buf == NULL) {
+ return NGX_ERROR;
+ }
+
+ buf->last = ngx_cpymem(buf->pos, b->pos, size);
+ b->pos = b->last;
+
+ buf->last_buf = b->last_buf;
+ buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module;
+
+ cl->buf = buf;
+
+ } else {
+ cl->buf = b;
+ }
+
+ *ll = cl;
+ ll = &cl->next;
+ in = in->next;
+ }
+
+ *ll = NULL;
+
+ return ctx->buffering ? NGX_OK : NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_deflate_start(ngx_http_request_t *r,
+ ngx_http_gzip_ctx_t *ctx)
+{
+ int rc;
+ ngx_http_gzip_conf_t *conf;
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+ ctx->preallocated = ngx_palloc(r->pool, ctx->allocated);
+ if (ctx->preallocated == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->free_mem = ctx->preallocated;
+
+ ctx->zstream.zalloc = ngx_http_gzip_filter_alloc;
+ ctx->zstream.zfree = ngx_http_gzip_filter_free;
+ ctx->zstream.opaque = ctx;
+
+ rc = deflateInit2(&ctx->zstream, (int) conf->level, Z_DEFLATED,
+ - ctx->wbits, ctx->memlevel, Z_DEFAULT_STRATEGY);
+
+ if (rc != Z_OK) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "deflateInit2() failed: %d", rc);
+ return NGX_ERROR;
+ }
+
+ ctx->last_out = &ctx->out;
+ ctx->crc32 = crc32(0L, Z_NULL, 0);
+ ctx->flush = Z_NO_FLUSH;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_gzheader(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+ static u_char gzheader[10] =
+ { 0x1f, 0x8b, Z_DEFLATED, 0, 0, 0, 0, 0, 0, 3 };
+
+ b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->memory = 1;
+ b->pos = gzheader;
+ b->last = b->pos + 10;
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ cl->next = ctx->out;
+ ctx->out = cl;
+
+ ctx->gzheader = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_add_data(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+ if (ctx->zstream.avail_in || ctx->flush != Z_NO_FLUSH || ctx->redo) {
+ return NGX_OK;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "gzip in: %p", ctx->in);
+
+ if (ctx->in == NULL) {
+ return NGX_DECLINED;
+ }
+
+ if (ctx->copy_buf) {
+
+ /*
+ * to avoid CPU cache trashing we do not free() just quit buf,
+ * but postpone free()ing after zlib compressing and data output
+ */
+
+ ctx->copy_buf->next = ctx->copied;
+ ctx->copied = ctx->copy_buf;
+ ctx->copy_buf = NULL;
+ }
+
+ ctx->in_buf = ctx->in->buf;
+
+ if (ctx->in_buf->tag == (ngx_buf_tag_t) &ngx_http_gzip_filter_module) {
+ ctx->copy_buf = ctx->in;
+ }
+
+ ctx->in = ctx->in->next;
+
+ ctx->zstream.next_in = ctx->in_buf->pos;
+ ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "gzip in_buf:%p ni:%p ai:%ud",
+ ctx->in_buf,
+ ctx->zstream.next_in, ctx->zstream.avail_in);
+
+ if (ctx->in_buf->last_buf) {
+ ctx->flush = Z_FINISH;
+
+ } else if (ctx->in_buf->flush) {
+ ctx->flush = Z_SYNC_FLUSH;
+ }
+
+ if (ctx->zstream.avail_in) {
+
+ ctx->crc32 = crc32(ctx->crc32, ctx->zstream.next_in,
+ ctx->zstream.avail_in);
+
+ } else if (ctx->flush == Z_NO_FLUSH) {
+ return NGX_AGAIN;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_get_buf(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+ ngx_http_gzip_conf_t *conf;
+
+ if (ctx->zstream.avail_out) {
+ return NGX_OK;
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+ if (ctx->free) {
+ ctx->out_buf = ctx->free->buf;
+ ctx->free = ctx->free->next;
+
+ } else if (ctx->bufs < conf->bufs.num) {
+
+ ctx->out_buf = ngx_create_temp_buf(r->pool, conf->bufs.size);
+ if (ctx->out_buf == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module;
+ ctx->out_buf->recycled = 1;
+ ctx->bufs++;
+
+ } else {
+ ctx->nomem = 1;
+ return NGX_DECLINED;
+ }
+
+ ctx->zstream.next_out = ctx->out_buf->pos;
+ ctx->zstream.avail_out = conf->bufs.size;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_deflate(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+ int rc;
+ ngx_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) {
+ 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->zstream.avail_out = 0;
+ ctx->out_buf->flush = 1;
+ ctx->flush = Z_NO_FLUSH;
+
+ 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;
+ }
+
+ 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";
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_gzip_static_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_gzip_static_module.c
new file mode 100644
index 00000000000..9e3b1ad6695
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_gzip_static_module.c
@@ -0,0 +1,298 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_flag_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_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_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_gzip_static_conf_t, enable),
+ NULL },
+
+ 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) {
+ return NGX_DECLINED;
+ }
+
+ rc = ngx_http_gzip_ok(r);
+
+ 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_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:
+
+ 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;
+ }
+
+ 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_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 = 1;
+ 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;
+
+ 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_value(conf->enable, prev->enable, 0);
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_static_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_gzip_static_handler;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_headers_filter_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_headers_filter_module.c
new file mode 100644
index 00000000000..1d409582c9d
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_headers_filter_module.c
@@ -0,0 +1,612 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct ngx_http_header_val_s ngx_http_header_val_t;
+
+typedef ngx_int_t (*ngx_http_set_header_pt)(ngx_http_request_t *r,
+ ngx_http_header_val_t *hv, ngx_str_t *value);
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_uint_t offset;
+ ngx_http_set_header_pt handler;
+} ngx_http_set_header_t;
+
+
+struct ngx_http_header_val_s {
+ ngx_http_complex_value_t value;
+ ngx_uint_t hash;
+ ngx_str_t key;
+ ngx_http_set_header_pt handler;
+ ngx_uint_t offset;
+};
+
+
+#define NGX_HTTP_EXPIRES_OFF 0
+#define NGX_HTTP_EXPIRES_EPOCH 1
+#define NGX_HTTP_EXPIRES_MAX 2
+#define NGX_HTTP_EXPIRES_ACCESS 3
+#define NGX_HTTP_EXPIRES_MODIFIED 4
+#define NGX_HTTP_EXPIRES_DAILY 5
+
+
+typedef struct {
+ ngx_uint_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_set_last_modified(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_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_NO_CONTENT
+ && r->headers_out.status != NGX_HTTP_MOVED_PERMANENTLY
+ && r->headers_out.status != NGX_HTTP_MOVED_TEMPORARILY
+ && r->headers_out.status != NGX_HTTP_NOT_MODIFIED))
+ {
+ 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) {
+ 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_ACCESS
+ || r->headers_out.last_modified_time == -1)
+ {
+ expires_time = now + conf->expires_time;
+ max_age = conf->expires_time;
+
+ } else if (conf->expires == NGX_HTTP_EXPIRES_DAILY) {
+ expires_time = ngx_next_time(conf->expires_time);
+ max_age = expires_time - now;
+
+ } 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 = hv->hash;
+ 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)
+{
+ ngx_table_elt_t *h, **old;
+
+ if (hv->offset) {
+ old = (ngx_table_elt_t **) ((char *) &r->headers_out + hv->offset);
+
+ } else {
+ old = NULL;
+ }
+
+ r->headers_out.last_modified_time = -1;
+
+ if (old == NULL || *old == NULL) {
+
+ if (value->len == 0) {
+ return NGX_OK;
+ }
+
+ h = ngx_list_push(&r->headers_out.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ } else {
+ h = *old;
+
+ if (value->len == 0) {
+ h->hash = 0;
+ return NGX_OK;
+ }
+ }
+
+ h->hash = hv->hash;
+ 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_CONF_UNSET_UINT;
+
+ 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_CONF_UNSET_UINT) {
+ conf->expires = prev->expires;
+ conf->expires_time = prev->expires_time;
+
+ if (conf->expires == NGX_CONF_UNSET_UINT) {
+ 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_CONF_UNSET_UINT) {
+ 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 can not 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 == 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 (hcf->expires_time == NGX_PARSE_LARGE_TIME) {
+ return "value must be less than 68 years";
+ }
+
+ 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->hash = 1;
+ 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;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_image_filter_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_image_filter_module.c
new file mode 100644
index 00000000000..dd3803fe39c
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_image_filter_module.c
@@ -0,0 +1,1414 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <gd.h>
+
+
+#define NGX_HTTP_IMAGE_OFF 0
+#define NGX_HTTP_IMAGE_TEST 1
+#define NGX_HTTP_IMAGE_SIZE 2
+#define NGX_HTTP_IMAGE_RESIZE 3
+#define NGX_HTTP_IMAGE_CROP 4
+#define NGX_HTTP_IMAGE_ROTATE 5
+
+
+#define NGX_HTTP_IMAGE_START 0
+#define NGX_HTTP_IMAGE_READ 1
+#define NGX_HTTP_IMAGE_PROCESS 2
+#define NGX_HTTP_IMAGE_PASS 3
+#define NGX_HTTP_IMAGE_DONE 4
+
+
+#define NGX_HTTP_IMAGE_NONE 0
+#define NGX_HTTP_IMAGE_JPEG 1
+#define NGX_HTTP_IMAGE_GIF 2
+#define NGX_HTTP_IMAGE_PNG 3
+
+
+#define NGX_HTTP_IMAGE_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_flag_t transparency;
+
+ ngx_http_complex_value_t *wcv;
+ ngx_http_complex_value_t *hcv;
+ ngx_http_complex_value_t *acv;
+ ngx_http_complex_value_t *jqcv;
+
+ 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 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_TAKE13|NGX_CONF_TAKE2,
+ 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_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_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,
+ 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 ((ngx_uint_t) (dx * 100 / dy)
+ < ctx->max_width * 100 / 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));
+ }
+
+ 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;
+ }
+
+ conf->filter = NGX_CONF_UNSET_UINT;
+ conf->jpeg_quality = NGX_CONF_UNSET_UINT;
+ conf->angle = NGX_CONF_UNSET_UINT;
+ conf->transparency = 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->wcv = prev->wcv;
+ conf->hcv = prev->hcv;
+ }
+ }
+
+ /* 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;
+ }
+
+ ngx_conf_merge_uint_value(conf->angle, prev->angle, 0);
+ if (conf->acv == NULL) {
+ conf->acv = prev->acv;
+ }
+
+ ngx_conf_merge_value(conf->transparency, prev->transparency, 1);
+
+ 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) {
+ 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 parameter \"%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 ngx_int_t
+ngx_http_image_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_image_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_image_body_filter;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_index_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_index_module.c
new file mode 100644
index 00000000000..15aeaf9c60e
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_index_module.c
@@ -0,0 +1,515 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_array_t *lengths;
+ ngx_array_t *values;
+} ngx_http_index_t;
+
+
+typedef struct {
+ ngx_array_t *indices; /* array of ngx_http_index_t */
+ size_t max_index_len;
+} ngx_http_index_loc_conf_t;
+
+
+#define NGX_HTTP_DEFAULT_INDEX "index.html"
+
+
+static ngx_int_t ngx_http_index_test_dir(ngx_http_request_t *r,
+ ngx_http_core_loc_conf_t *clcf, u_char *path, u_char *last);
+static ngx_int_t ngx_http_index_error(ngx_http_request_t *r,
+ ngx_http_core_loc_conf_t *clcf, u_char *file, ngx_err_t err);
+
+static ngx_int_t ngx_http_index_init(ngx_conf_t *cf);
+static void *ngx_http_index_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_index_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static char *ngx_http_index_set_index(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_command_t ngx_http_index_commands[] = {
+
+ { ngx_string("index"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_index_set_index,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_index_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_index_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_index_create_loc_conf, /* create location configration */
+ ngx_http_index_merge_loc_conf /* merge location configration */
+};
+
+
+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 be much more than invalid ones.
+ * If the file open()/stat() would fail, then the directory stat() should
+ * be more quickly because some data is already cached in the kernel.
+ * 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 contains an usual file in place of 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_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 (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_open_cached_file(clcf->open_file_cache, &dir, &of, r->pool)
+ != NGX_OK)
+ {
+ if (of.err) {
+
+ if (of.err == NGX_ENOENT) {
+ *last = c;
+ return ngx_http_index_error(r, clcf, dir.data, NGX_ENOENT);
+ }
+
+ if (of.err == NGX_EACCES) {
+
+ *last = c;
+
+ /*
+ * ngx_http_index_test_dir() is called after the first index
+ * file testing has returned an error distinct from NGX_EACCES.
+ * This means that directory searching is allowed.
+ */
+
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err,
+ "%s \"%s\" failed", of.failed, dir.data);
+ }
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ *last = c;
+
+ if (of.is_dir) {
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "\"%s\" is not a directory", dir.data);
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_index_error(ngx_http_request_t *r, ngx_http_core_loc_conf_t *clcf,
+ u_char *file, ngx_err_t err)
+{
+ if (err == NGX_EACCES) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, err,
+ "\"%s\" is forbidden", file);
+
+ return NGX_HTTP_FORBIDDEN;
+ }
+
+ if (clcf->log_not_found) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, err,
+ "\"%s\" is not found", file);
+ }
+
+ return NGX_HTTP_NOT_FOUND;
+}
+
+
+static void *
+ngx_http_index_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_index_loc_conf_t *conf;
+
+ conf = ngx_palloc(cf->pool, sizeof(ngx_http_index_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->indices = NULL;
+ conf->max_index_len = 0;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_index_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_index_loc_conf_t *prev = parent;
+ ngx_http_index_loc_conf_t *conf = child;
+
+ ngx_http_index_t *index;
+
+ if (conf->indices == NULL) {
+ conf->indices = prev->indices;
+ conf->max_index_len = prev->max_index_len;
+ }
+
+ if (conf->indices == NULL) {
+ conf->indices = ngx_array_create(cf->pool, 1, sizeof(ngx_http_index_t));
+ if (conf->indices == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ index = ngx_array_push(conf->indices);
+ if (index == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ index->name.len = sizeof(NGX_HTTP_DEFAULT_INDEX);
+ index->name.data = (u_char *) NGX_HTTP_DEFAULT_INDEX;
+ index->lengths = NULL;
+ index->values = NULL;
+
+ conf->max_index_len = sizeof(NGX_HTTP_DEFAULT_INDEX);
+
+ return NGX_CONF_OK;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_index_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_index_handler;
+
+ return NGX_OK;
+}
+
+
+/* TODO: warn about duplicate indices */
+
+static char *
+ngx_http_index_set_index(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_index_loc_conf_t *ilcf = conf;
+
+ ngx_str_t *value;
+ ngx_uint_t i, n;
+ ngx_http_index_t *index;
+ ngx_http_script_compile_t sc;
+
+ if (ilcf->indices == NULL) {
+ ilcf->indices = ngx_array_create(cf->pool, 2, sizeof(ngx_http_index_t));
+ if (ilcf->indices == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+
+ if (value[i].data[0] == '/' && i != cf->args->nelts - 1) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "only the last index in \"index\" directive "
+ "should be absolute");
+ }
+
+ if (value[i].len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "index \"%V\" in \"index\" directive is invalid",
+ &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ index = ngx_array_push(ilcf->indices);
+ if (index == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ index->name.len = value[i].len;
+ index->name.data = value[i].data;
+ index->lengths = NULL;
+ index->values = NULL;
+
+ n = ngx_http_script_variables_count(&value[i]);
+
+ if (n == 0) {
+ if (ilcf->max_index_len < index->name.len) {
+ ilcf->max_index_len = index->name.len;
+ }
+
+ if (index->name.data[0] == '/') {
+ continue;
+ }
+
+ /* include the terminating '\0' to the length to use ngx_memcpy() */
+ index->name.len++;
+
+ continue;
+ }
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = &value[i];
+ sc.lengths = &index->lengths;
+ sc.values = &index->values;
+ sc.variables = n;
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_limit_req_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_limit_req_module.c
new file mode 100644
index 00000000000..718fae8e2e2
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_limit_req_module.c
@@ -0,0 +1,811 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ u_char color;
+ u_char dummy;
+ u_short len;
+ ngx_queue_t queue;
+ ngx_msec_t last;
+ /* integer value, 1 corresponds to 0.001 r/s */
+ ngx_uint_t excess;
+ 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_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 limit_log_level;
+ ngx_uint_t delay_log_level;
+
+ ngx_uint_t nodelay; /* unsigned nodelay:1 */
+} 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_conf_t *lrcf,
+ ngx_uint_t hash, u_char *data, size_t len, ngx_uint_t *ep);
+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_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_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 configration */
+ ngx_http_limit_req_merge_conf /* merge location configration */
+};
+
+
+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, n;
+ uint32_t hash;
+ ngx_int_t rc;
+ ngx_uint_t excess;
+ ngx_time_t *tp;
+ ngx_rbtree_node_t *node;
+ ngx_http_variable_value_t *vv;
+ ngx_http_limit_req_ctx_t *ctx;
+ ngx_http_limit_req_node_t *lr;
+ ngx_http_limit_req_conf_t *lrcf;
+
+ if (r->main->limit_req_set) {
+ return NGX_DECLINED;
+ }
+
+ lrcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module);
+
+ if (lrcf->shm_zone == NULL) {
+ return NGX_DECLINED;
+ }
+
+ ctx = lrcf->shm_zone->data;
+
+ vv = ngx_http_get_indexed_variable(r, ctx->index);
+
+ if (vv == NULL || vv->not_found) {
+ return NGX_DECLINED;
+ }
+
+ len = vv->len;
+
+ if (len == 0) {
+ return NGX_DECLINED;
+ }
+
+ 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);
+ return NGX_DECLINED;
+ }
+
+ r->main->limit_req_set = 1;
+
+ hash = ngx_crc32_short(vv->data, len);
+
+ ngx_shmtx_lock(&ctx->shpool->mutex);
+
+ ngx_http_limit_req_expire(ctx, 1);
+
+ rc = ngx_http_limit_req_lookup(lrcf, hash, vv->data, len, &excess);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "limit_req: %i %ui.%03ui", rc, excess / 1000, excess % 1000);
+
+ if (rc == NGX_DECLINED) {
+
+ n = offsetof(ngx_rbtree_node_t, color)
+ + offsetof(ngx_http_limit_req_node_t, data)
+ + len;
+
+ node = ngx_slab_alloc_locked(ctx->shpool, n);
+ if (node == NULL) {
+
+ ngx_http_limit_req_expire(ctx, 0);
+
+ node = ngx_slab_alloc_locked(ctx->shpool, n);
+ if (node == NULL) {
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+ return NGX_HTTP_SERVICE_UNAVAILABLE;
+ }
+ }
+
+ lr = (ngx_http_limit_req_node_t *) &node->color;
+
+ node->key = hash;
+ lr->len = (u_char) len;
+
+ tp = ngx_timeofday();
+ lr->last = (ngx_msec_t) (tp->sec * 1000 + tp->msec);
+
+ lr->excess = 0;
+ ngx_memcpy(lr->data, vv->data, len);
+
+ ngx_rbtree_insert(&ctx->sh->rbtree, node);
+
+ ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
+
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+
+ return NGX_DECLINED;
+ }
+
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+
+ if (rc == NGX_OK) {
+ return NGX_DECLINED;
+ }
+
+ 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, &lrcf->shm_zone->shm.name);
+
+ return NGX_HTTP_SERVICE_UNAVAILABLE;
+ }
+
+ /* rc == NGX_AGAIN */
+
+ if (lrcf->nodelay) {
+ 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, &lrcf->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,
+ (ngx_msec_t) excess * 1000 / ctx->rate);
+
+ 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_conf_t *lrcf, ngx_uint_t hash,
+ u_char *data, size_t len, ngx_uint_t *ep)
+{
+ 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;
+
+ ctx = lrcf->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 */
+
+ do {
+ 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);
+
+ 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;
+ }
+
+ *ep = excess;
+
+ if ((ngx_uint_t) excess > lrcf->burst) {
+ return NGX_BUSY;
+ }
+
+ lr->excess = excess;
+ lr->last = now;
+
+ if (excess) {
+ return NGX_AGAIN;
+ }
+
+ return NGX_OK;
+ }
+
+ node = (rc < 0) ? node->left : node->right;
+
+ } while (node != sentinel && hash == node->key);
+
+ break;
+ }
+
+ *ep = 0;
+
+ return NGX_DECLINED;
+}
+
+
+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 (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->shm_zone = NULL;
+ * conf->burst = 0;
+ * conf->nodelay = 0;
+ */
+
+ conf->limit_log_level = 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->shm_zone == NULL) {
+ *conf = *prev;
+ }
+
+ 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;
+
+ 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 size, len;
+ 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) {
+ *p = '\0';
+
+ name.len = p - name.data;
+
+ p++;
+
+ s.len = value[i].data + value[i].len - p;
+ s.data = p;
+
+ size = ngx_parse_size(&s);
+ if (size > 8191) {
+ continue;
+ }
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid zone size \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ 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 <= NGX_ERROR) {
+ 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 || size == 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 limit_req_zone \"%V\"",
+ &cmd->name);
+ return NGX_CONF_ERROR;
+ }
+
+ ctx->rate = rate * 1000 / scale;
+
+ shm_zone = ngx_shared_memory_add(cf, &name, size,
+ &ngx_http_limit_req_module);
+ if (shm_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (shm_zone->data) {
+ ctx = shm_zone->data;
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "limit_req_zone \"%V\" is already bound to variable \"%V\"",
+ &value[1], &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;
+
+ if (lrcf->shm_zone) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ burst = 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;
+
+ lrcf->shm_zone = ngx_shared_memory_add(cf, &s, 0,
+ &ngx_http_limit_req_module);
+ if (lrcf->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) {
+ lrcf->nodelay = 1;
+ continue;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (lrcf->shm_zone == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"%V\" must have \"zone\" parameter",
+ &cmd->name);
+ return NGX_CONF_ERROR;
+ }
+
+ if (lrcf->shm_zone->data == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "unknown limit_req_zone \"%V\"",
+ &lrcf->shm_zone->shm.name);
+ return NGX_CONF_ERROR;
+ }
+
+ lrcf->burst = burst * 1000;
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_limit_req_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_limit_req_handler;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_limit_zone_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_limit_zone_module.c
new file mode 100644
index 00000000000..31df316e483
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_limit_zone_module.c
@@ -0,0 +1,556 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ u_char color;
+ u_char len;
+ u_short conn;
+ u_char data[1];
+} ngx_http_limit_zone_node_t;
+
+
+typedef struct {
+ ngx_shm_zone_t *shm_zone;
+ ngx_rbtree_node_t *node;
+} ngx_http_limit_zone_cleanup_t;
+
+
+typedef struct {
+ ngx_rbtree_t *rbtree;
+ ngx_int_t index;
+ ngx_str_t var;
+} ngx_http_limit_zone_ctx_t;
+
+
+typedef struct {
+ ngx_shm_zone_t *shm_zone;
+ ngx_uint_t conn;
+ ngx_uint_t log_level;
+} ngx_http_limit_zone_conf_t;
+
+
+static void ngx_http_limit_zone_cleanup(void *data);
+
+static void *ngx_http_limit_zone_create_conf(ngx_conf_t *cf);
+static char *ngx_http_limit_zone_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+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_zone_init(ngx_conf_t *cf);
+
+
+static ngx_conf_enum_t ngx_http_limit_conn_log_levels[] = {
+ { ngx_string("info"), NGX_LOG_INFO },
+ { ngx_string("notice"), NGX_LOG_NOTICE },
+ { ngx_string("warn"), NGX_LOG_WARN },
+ { ngx_string("error"), NGX_LOG_ERR },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t ngx_http_limit_zone_commands[] = {
+
+ { 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_zone_conf_t, log_level),
+ &ngx_http_limit_conn_log_levels },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_limit_zone_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_limit_zone_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_limit_zone_create_conf, /* create location configration */
+ ngx_http_limit_zone_merge_conf /* merge location configration */
+};
+
+
+ngx_module_t ngx_http_limit_zone_module = {
+ NGX_MODULE_V1,
+ &ngx_http_limit_zone_module_ctx, /* module context */
+ ngx_http_limit_zone_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_limit_zone_handler(ngx_http_request_t *r)
+{
+ size_t len, n;
+ uint32_t hash;
+ ngx_int_t rc;
+ ngx_slab_pool_t *shpool;
+ ngx_rbtree_node_t *node, *sentinel;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_variable_value_t *vv;
+ ngx_http_limit_zone_ctx_t *ctx;
+ ngx_http_limit_zone_node_t *lz;
+ ngx_http_limit_zone_conf_t *lzcf;
+ ngx_http_limit_zone_cleanup_t *lzcln;
+
+ if (r->main->limit_zone_set) {
+ return NGX_DECLINED;
+ }
+
+ lzcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_zone_module);
+
+ if (lzcf->shm_zone == NULL) {
+ return NGX_DECLINED;
+ }
+
+ ctx = lzcf->shm_zone->data;
+
+ vv = ngx_http_get_indexed_variable(r, ctx->index);
+
+ if (vv == NULL || vv->not_found) {
+ return NGX_DECLINED;
+ }
+
+ len = vv->len;
+
+ if (len == 0) {
+ return NGX_DECLINED;
+ }
+
+ 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);
+ return NGX_DECLINED;
+ }
+
+ r->main->limit_zone_set = 1;
+
+ hash = ngx_crc32_short(vv->data, len);
+
+ cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_http_limit_zone_cleanup_t));
+ if (cln == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ shpool = (ngx_slab_pool_t *) lzcf->shm_zone->shm.addr;
+
+ ngx_shmtx_lock(&shpool->mutex);
+
+ node = ctx->rbtree->root;
+ sentinel = ctx->rbtree->sentinel;
+
+ while (node != sentinel) {
+
+ if (hash < node->key) {
+ node = node->left;
+ continue;
+ }
+
+ if (hash > node->key) {
+ node = node->right;
+ continue;
+ }
+
+ /* hash == node->key */
+
+ do {
+ lz = (ngx_http_limit_zone_node_t *) &node->color;
+
+ rc = ngx_memn2cmp(vv->data, lz->data, len, (size_t) lz->len);
+
+ if (rc == 0) {
+ if ((ngx_uint_t) lz->conn < lzcf->conn) {
+ lz->conn++;
+ goto done;
+ }
+
+ ngx_shmtx_unlock(&shpool->mutex);
+
+ ngx_log_error(lzcf->log_level, r->connection->log, 0,
+ "limiting connections by zone \"%V\"",
+ &lzcf->shm_zone->shm.name);
+
+ return NGX_HTTP_SERVICE_UNAVAILABLE;
+ }
+
+ node = (rc < 0) ? node->left : node->right;
+
+ } while (node != sentinel && hash == node->key);
+
+ break;
+ }
+
+ n = offsetof(ngx_rbtree_node_t, color)
+ + offsetof(ngx_http_limit_zone_node_t, data)
+ + len;
+
+ node = ngx_slab_alloc_locked(shpool, n);
+ if (node == NULL) {
+ ngx_shmtx_unlock(&shpool->mutex);
+ return NGX_HTTP_SERVICE_UNAVAILABLE;
+ }
+
+ lz = (ngx_http_limit_zone_node_t *) &node->color;
+
+ node->key = hash;
+ lz->len = (u_char) len;
+ lz->conn = 1;
+ ngx_memcpy(lz->data, vv->data, len);
+
+ ngx_rbtree_insert(ctx->rbtree, node);
+
+done:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "limit zone: %08XD %d", node->key, lz->conn);
+
+ ngx_shmtx_unlock(&shpool->mutex);
+
+ cln->handler = ngx_http_limit_zone_cleanup;
+ lzcln = cln->data;
+
+ lzcln->shm_zone = lzcf->shm_zone;
+ lzcln->node = node;
+
+ return NGX_DECLINED;
+}
+
+
+static void
+ngx_http_limit_zone_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_zone_node_t *lzn, *lznt;
+
+ for ( ;; ) {
+
+ if (node->key < temp->key) {
+
+ p = &temp->left;
+
+ } else if (node->key > temp->key) {
+
+ p = &temp->right;
+
+ } else { /* node->key == temp->key */
+
+ lzn = (ngx_http_limit_zone_node_t *) &node->color;
+ lznt = (ngx_http_limit_zone_node_t *) &temp->color;
+
+ p = (ngx_memn2cmp(lzn->data, lznt->data, lzn->len, lznt->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 void
+ngx_http_limit_zone_cleanup(void *data)
+{
+ ngx_http_limit_zone_cleanup_t *lzcln = data;
+
+ ngx_slab_pool_t *shpool;
+ ngx_rbtree_node_t *node;
+ ngx_http_limit_zone_ctx_t *ctx;
+ ngx_http_limit_zone_node_t *lz;
+
+ ctx = lzcln->shm_zone->data;
+ shpool = (ngx_slab_pool_t *) lzcln->shm_zone->shm.addr;
+ node = lzcln->node;
+ lz = (ngx_http_limit_zone_node_t *) &node->color;
+
+ ngx_shmtx_lock(&shpool->mutex);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, lzcln->shm_zone->shm.log, 0,
+ "limit zone cleanup: %08XD %d", node->key, lz->conn);
+
+ lz->conn--;
+
+ if (lz->conn == 0) {
+ ngx_rbtree_delete(ctx->rbtree, node);
+ ngx_slab_free_locked(shpool, node);
+ }
+
+ ngx_shmtx_unlock(&shpool->mutex);
+}
+
+
+static ngx_int_t
+ngx_http_limit_zone_init_zone(ngx_shm_zone_t *shm_zone, void *data)
+{
+ ngx_http_limit_zone_ctx_t *octx = data;
+
+ size_t len;
+ ngx_slab_pool_t *shpool;
+ ngx_rbtree_node_t *sentinel;
+ ngx_http_limit_zone_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_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_zone_rbtree_insert_value);
+
+ len = sizeof(" in limit_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_zone \"%V\"%Z",
+ &shm_zone->shm.name);
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_limit_zone_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_limit_zone_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_zone_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->shm_zone = NULL;
+ * conf->conn = 0;
+ */
+
+ conf->log_level = NGX_CONF_UNSET_UINT;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_limit_zone_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_limit_zone_conf_t *prev = parent;
+ ngx_http_limit_zone_conf_t *conf = child;
+
+ if (conf->shm_zone == NULL) {
+ *conf = *prev;
+ }
+
+ ngx_conf_merge_uint_value(conf->log_level, prev->log_level, NGX_LOG_ERR);
+
+ 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_zone_ctx_t *ctx;
+
+ 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_zone_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_zone_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_zone_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_http_limit_zone_conf_t *lzcf = conf;
+
+ ngx_int_t n;
+ ngx_str_t *value;
+
+ if (lzcf->shm_zone) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ lzcf->shm_zone = ngx_shared_memory_add(cf, &value[1], 0,
+ &ngx_http_limit_zone_module);
+ if (lzcf->shm_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ 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;
+ }
+
+ lzcf->conn = n;
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_limit_zone_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_zone_handler;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_log_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_log_module.c
new file mode 100644
index 00000000000..e6a7fdbbfec
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_log_module.c
@@ -0,0 +1,1375 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+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 {
+ 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);
+
+static u_char *ngx_http_log_connection(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op);
+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_TAKE123,
+ 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 configration */
+ ngx_http_log_merge_loc_conf /* merge location configration */
+};
+
+
+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("connection"), NGX_ATOMIC_T_LEN, ngx_http_log_connection },
+ { 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"), 3, 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("apache_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 }
+};
+
+
+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_open_file_t *file;
+ ngx_http_log_op_t *op;
+ 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;
+
+ file = log[l].file;
+
+ if (file && file->buffer) {
+
+ if (len > (size_t) (file->last - file->pos)) {
+
+ ngx_http_log_write(r, &log[l], file->buffer,
+ file->pos - file->buffer);
+
+ file->pos = file->buffer;
+ }
+
+ if (len <= (size_t) (file->last - file->pos)) {
+
+ p = file->pos;
+
+ for (i = 0; i < log[l].format->ops->nelts; i++) {
+ p = op[i].run(r, p, &op[i]);
+ }
+
+ ngx_linefeed(p);
+
+ file->pos = p;
+
+ continue;
+ }
+ }
+
+ 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 (log->script == NULL) {
+ name = log->file->name.data;
+ n = ngx_write_fd(log->file->fd, buf, len);
+
+ } 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;
+
+ if (!r->root_tested) {
+
+ /* test root directory existance */
+
+ if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) {
+ /* simulate successfull logging */
+ return len;
+ }
+
+ path.data[root] = '\0';
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ 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_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+ != NGX_OK)
+ {
+ if (of.err == 0) {
+ /* simulate successfull logging */
+ return len;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, of.err,
+ "testing \"%s\" existence failed", path.data);
+
+ /* simulate successfull 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 successfull logging */
+ return len;
+ }
+ }
+
+ if (ngx_http_script_run(r, &log, script->lengths->elts, 1,
+ script->values->elts)
+ == NULL)
+ {
+ /* simulate successfull 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_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 successfull 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;
+}
+
+
+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_connection(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op)
+{
+ return ngx_sprintf(buf, "%ui", r->connection->number);
+}
+
+
+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", 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) {
+ *buf++ = '0';
+ *buf++ = '0';
+ *buf++ = '9';
+ return buf;
+
+ } else {
+ status = 0;
+ }
+
+ return ngx_sprintf(buf, "%ui", 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` */
+ 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
+
+ 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
+ 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
+ 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
+ 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
+ };
+
+
+ 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 buf;
+ ngx_uint_t i, n;
+ ngx_str_t *value, name;
+ ngx_http_log_t *log;
+ 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];
+ goto buffer;
+ }
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "unknown log format \"%V\"", &name);
+ return NGX_CONF_ERROR;
+
+buffer:
+
+ if (cf->args->nelts == 4) {
+ if (ngx_strncmp(value[3].data, "buffer=", 7) != 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[3]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (log->script) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "buffered logs can not have variables in name");
+ return NGX_CONF_ERROR;
+ }
+
+ name.len = value[3].len - 7;
+ name.data = value[3].data + 7;
+
+ buf = ngx_parse_size(&name);
+
+ if (buf == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[3]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (log->file->buffer && log->file->last - log->file->pos != buf) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "access_log \"%V\" already defined "
+ "with different buffer size", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ log->file->buffer = ngx_palloc(cf->pool, buf);
+ if (log->file->buffer == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ log->file->pos = log->file->buffer;
+ log->file->last = log->file->buffer + buf;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_log_main_conf_t *lmcf = conf;
+
+ ngx_str_t *value;
+ ngx_uint_t i;
+ ngx_http_log_fmt_t *fmt;
+
+ value = cf->args->elts;
+
+ fmt = lmcf->formats.elts;
+ for (i = 0; i < lmcf->formats.nelts; i++) {
+ if (fmt[i].name.len == value[1].len
+ && ngx_strcmp(fmt[i].name.data, value[1].data) == 0)
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "duplicate \"log_format\" name \"%V\"",
+ &value[1]);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ fmt = ngx_array_push(&lmcf->formats);
+ if (fmt == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ fmt->name = value[1];
+
+ fmt->flushes = ngx_array_create(cf->pool, 4, sizeof(ngx_int_t));
+ if (fmt->flushes == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_http_log_op_t));
+ if (fmt->ops == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ return ngx_http_log_compile_format(cf, fmt->flushes, fmt->ops, cf->args, 2);
+}
+
+
+static char *
+ngx_http_log_compile_format(ngx_conf_t *cf, ngx_array_t *flushes,
+ ngx_array_t *ops, ngx_array_t *args, ngx_uint_t s)
+{
+ u_char *data, *p, ch;
+ size_t i, len;
+ ngx_str_t *value, var;
+ ngx_int_t *flush;
+ ngx_uint_t bracket;
+ ngx_http_log_op_t *op;
+ ngx_http_log_var_t *v;
+
+ value = args->elts;
+
+ for ( /* void */ ; s < args->nelts; s++) {
+
+ for (i = 0; i < value[s].len; i++) {
+ if (value[s].data[i] != '%') {
+ continue;
+ }
+
+ ch = value[s].data[i + 1];
+
+ if ((ch >= 'A' && ch <= 'Z')
+ || (ch >= 'a' && ch <= 'z')
+ || ch == '{')
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the parameters in the \"%%name\" form are not supported, "
+ "use the \"$variable\" instead");
+
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ i = 0;
+
+ while (i < value[s].len) {
+
+ op = ngx_array_push(ops);
+ if (op == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ data = &value[s].data[i];
+
+ if (value[s].data[i] == '$') {
+
+ if (++i == value[s].len) {
+ goto invalid;
+ }
+
+ if (value[s].data[i] == '{') {
+ bracket = 1;
+
+ if (++i == value[s].len) {
+ goto invalid;
+ }
+
+ var.data = &value[s].data[i];
+
+ } else {
+ bracket = 0;
+ var.data = &value[s].data[i];
+ }
+
+ for (var.len = 0; i < value[s].len; i++, var.len++) {
+ ch = value[s].data[i];
+
+ if (ch == '}' && bracket) {
+ i++;
+ bracket = 0;
+ break;
+ }
+
+ if ((ch >= 'A' && ch <= 'Z')
+ || (ch >= 'a' && ch <= 'z')
+ || (ch >= '0' && ch <= '9')
+ || ch == '_')
+ {
+ continue;
+ }
+
+ break;
+ }
+
+ if (bracket) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the closing bracket in \"%V\" "
+ "variable is missing", &var);
+ return NGX_CONF_ERROR;
+ }
+
+ if (var.len == 0) {
+ goto invalid;
+ }
+
+ if (ngx_strncmp(var.data, "apache_bytes_sent", 17) == 0) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "use \"$body_bytes_sent\" instead of "
+ "\"$apache_bytes_sent\"");
+ }
+
+ 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 < 0) {
+ 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 < 0) {
+ goto failed;
+ }
+
+ continue;
+ }
+
+ if (ngx_strcmp(value[i].data, "off") == 0) {
+
+ llcf->open_file_cache = NULL;
+
+ continue;
+ }
+
+ failed:
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid \"open_log_file_cache\" parameter \"%V\"",
+ &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (llcf->open_file_cache == NULL) {
+ return NGX_CONF_OK;
+ }
+
+ if (max == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"open_log_file_cache\" must have \"max\" parameter");
+ return NGX_CONF_ERROR;
+ }
+
+ llcf->open_file_cache = ngx_open_file_cache_init(cf->pool, max, inactive);
+
+ if (llcf->open_file_cache) {
+
+ llcf->open_file_cache_valid = valid;
+ llcf->open_file_cache_min_uses = min_uses;
+
+ return NGX_CONF_OK;
+ }
+
+ return NGX_CONF_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_log_init(ngx_conf_t *cf)
+{
+ ngx_str_t *value;
+ ngx_array_t a;
+ ngx_http_handler_pt *h;
+ ngx_http_log_fmt_t *fmt;
+ ngx_http_log_main_conf_t *lmcf;
+ ngx_http_core_main_conf_t *cmcf;
+
+ lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_log_module);
+
+ if (lmcf->combined_used) {
+ if (ngx_array_init(&a, cf->pool, 1, sizeof(ngx_str_t)) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ value = ngx_array_push(&a);
+ if (value == NULL) {
+ return NGX_ERROR;
+ }
+
+ *value = ngx_http_combined_fmt;
+ fmt = lmcf->formats.elts;
+
+ if (ngx_http_log_compile_format(cf, NULL, fmt->ops, &a, 0)
+ != NGX_CONF_OK)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_LOG_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_log_handler;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_map_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_map_module.c
new file mode 100644
index 00000000000..6b724b304e6
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_map_module.c
@@ -0,0 +1,574 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_uint_t hash_max_size;
+ ngx_uint_t hash_bucket_size;
+} ngx_http_map_conf_t;
+
+
+typedef struct {
+ ngx_hash_keys_arrays_t keys;
+
+ ngx_array_t *values_hash;
+ 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;
+
+ size_t len;
+ 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;
+ }
+
+ len = val.len;
+
+ if (len && map->hostnames && val.data[len - 1] == '.') {
+ 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];
+ 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(16384, 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;
+
+ 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, file, 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) {
+ file = value[1];
+
+ if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
+
+ return ngx_conf_parse(cf, &file);
+ }
+
+ 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) {
+ goto found;
+ }
+ }
+
+ var = ngx_palloc(ctx->keys.pool, sizeof(ngx_http_variable_value_t));
+ 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;
+
+ vp = ngx_array_push(&ctx->var_values);
+ if (vp == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *vp = var;
+
+ 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;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_memcached_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_memcached_module.c
new file mode 100644
index 00000000000..745f73aa5b9
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_memcached_module.c
@@ -0,0 +1,623 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_http_upstream_conf_t upstream;
+ ngx_int_t index;
+} ngx_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_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 configration */
+ ngx_http_memcached_merge_loc_conf /* merge location configration */
+};
+
+
+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, *len;
+ ngx_str_t line;
+ ngx_http_upstream_t *u;
+ ngx_http_memcached_ctx_t *ctx;
+
+ 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);
+
+ 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;
+ }
+
+ /* skip flags */
+
+ while (*p) {
+ if (*p++ == ' ') {
+ goto length;
+ }
+ }
+
+ goto no_valid;
+
+ length:
+
+ len = p;
+
+ while (*p && *p++ != CR) { /* void */ }
+
+ r->headers_out.content_length_n = ngx_atoof(len, p - len - 1);
+ if (r->headers_out.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;
+
+ 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 == 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;
+
+ 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");
+ }
+
+ ctx->rest -= b->last - last;
+ b->last = last;
+ cl->buf->last = last;
+ u->length = ctx->rest;
+
+ 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.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;
+
+ 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_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;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_memcached_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_memcached_loc_conf_t *mlcf = conf;
+
+ ngx_str_t *value;
+ ngx_url_t u;
+ ngx_http_core_loc_conf_t *clcf;
+
+ if (mlcf->upstream.upstream) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.url = value[1];
+ u.no_resolve = 1;
+
+ mlcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
+ if (mlcf->upstream.upstream == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+ clcf->handler = ngx_http_memcached_handler;
+
+ if (clcf->name.data[clcf->name.len - 1] == '/') {
+ clcf->auto_redirect = 1;
+ }
+
+ mlcf->index = ngx_http_get_variable_index(cf, &ngx_http_memcached_key);
+
+ if (mlcf->index == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_not_modified_filter_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_not_modified_filter_module.c
new file mode 100644
index 00000000000..a778c70dcd9
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_not_modified_filter_module.c
@@ -0,0 +1,142 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_test_precondition(ngx_http_request_t *r);
+static ngx_int_t ngx_http_test_not_modified(ngx_http_request_t *r);
+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) {
+ return ngx_http_test_precondition(r);
+ }
+
+ if (r->headers_in.if_modified_since) {
+ return ngx_http_test_not_modified(r);
+ }
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_test_precondition(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 ngx_http_next_header_filter(r);
+ }
+
+ return ngx_http_filter_finalize_request(r, NULL,
+ NGX_HTTP_PRECONDITION_FAILED);
+}
+
+
+static ngx_int_t
+ngx_http_test_not_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 ngx_http_next_header_filter(r);
+ }
+
+ 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) {
+
+ if (clcf->if_modified_since == NGX_HTTP_IMS_EXACT
+ || ims < r->headers_out.last_modified_time)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+ }
+
+ 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);
+}
+
+
+static ngx_int_t
+ngx_http_not_modified_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_not_modified_header_filter;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_proxy_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_proxy_module.c
new file mode 100644
index 00000000000..495b1743d8b
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_proxy_module.c
@@ -0,0 +1,2812 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct ngx_http_proxy_redirect_s ngx_http_proxy_redirect_t;
+
+typedef ngx_int_t (*ngx_http_proxy_redirect_pt)(ngx_http_request_t *r,
+ ngx_table_elt_t *h, size_t prefix, ngx_http_proxy_redirect_t *pr);
+
+struct ngx_http_proxy_redirect_s {
+ ngx_http_proxy_redirect_pt handler;
+ ngx_str_t redirect;
+
+ union {
+ ngx_str_t text;
+
+ struct {
+ void *lengths;
+ void *values;
+ } vars;
+
+ void *regex;
+ } 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_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 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_proxy_vars_t vars;
+ size_t internal_body_length;
+} 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 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_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_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);
+
+#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 }
+};
+
+
+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_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 },
+
+#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 },
+
+#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 configration */
+ ngx_http_proxy_merge_loc_conf /* merge location configration */
+};
+
+
+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 ngx_keyval_t ngx_http_proxy_headers[] = {
+ { ngx_string("Host"), ngx_string("$proxy_host") },
+ { ngx_string("Connection"), ngx_string("close") },
+ { ngx_string("Keep-Alive"), ngx_string("") },
+ { ngx_string("Expect"), 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("Keep-Alive"), ngx_string("") },
+ { ngx_string("Expect"), 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_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;
+ }
+
+ 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_event_pipe_copy_input_filter;
+
+ 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;
+ }
+
+ } else {
+ url.uri = r->unparsed_uri;
+ }
+
+ 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.len) {
+
+ 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) {
+
+ *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++;
+ }
+
+ len = method.len + sizeof(ngx_http_proxy_version) - 1 + sizeof(CRLF) - 1;
+
+ escape = 0;
+ loc_len = 0;
+ unparsed_uri = 0;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+ if (plcf->proxy_lengths) {
+ 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;
+ }
+
+ 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) {
+ 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;
+
+ 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;
+ }
+
+ b->flush = 1;
+
+ } else {
+ u->request_bufs = cl;
+ }
+
+ 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;
+
+ r->upstream->process_header = ngx_http_proxy_process_status_line;
+ 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;
+
+ 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);
+
+ 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_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_cpystrn(h->key.data, r->header_name_start, h->key.len + 1);
+ ngx_cpystrn(h->value.data, r->header_start, h->value.len + 1);
+
+ 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";
+ }
+
+ 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_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)
+{
+ u_char *p;
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ if (r->headers_in.x_forwarded_for == NULL) {
+ v->len = r->connection->addr_text.len;
+ v->data = r->connection->addr_text.data;
+ return NGX_OK;
+ }
+
+ v->len = r->headers_in.x_forwarded_for->value.len
+ + sizeof(", ") - 1 + r->connection->addr_text.len;
+
+ p = ngx_pnalloc(r->pool, v->len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->data = p;
+
+ p = ngx_copy(p, r->headers_in.x_forwarded_for->value.data,
+ r->headers_in.x_forwarded_for->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) {
+ 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_SIZE_T_LEN);
+
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = ngx_sprintf(v->data, "%uz", 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)
+{
+ ngx_int_t rc;
+ ngx_uint_t i;
+ ngx_http_proxy_loc_conf_t *plcf;
+ ngx_http_proxy_redirect_t *pr;
+
+ plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);
+
+ pr = plcf->redirects->elts;
+
+ if (pr == NULL) {
+ return NGX_DECLINED;
+ }
+
+ for (i = 0; i < plcf->redirects->nelts; i++) {
+ rc = pr[i].handler(r, h, prefix, &pr[i]);
+
+ if (rc != NGX_DECLINED) {
+ return rc;
+ }
+ }
+
+ return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_rewrite_redirect_text(ngx_http_request_t *r, ngx_table_elt_t *h,
+ size_t prefix, ngx_http_proxy_redirect_t *pr)
+{
+ size_t len;
+ u_char *data, *p;
+
+ if (pr->redirect.len > h->value.len - prefix
+ || ngx_rstrncmp(h->value.data + prefix, pr->redirect.data,
+ pr->redirect.len) != 0)
+ {
+ return NGX_DECLINED;
+ }
+
+ len = pr->replacement.text.len + h->value.len - pr->redirect.len;
+
+ data = ngx_pnalloc(r->pool, len);
+ if (data == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_copy(data, h->value.data, prefix);
+
+ if (pr->replacement.text.len) {
+ p = ngx_copy(p, pr->replacement.text.data, pr->replacement.text.len);
+ }
+
+ ngx_memcpy(p, h->value.data + prefix + pr->redirect.len,
+ h->value.len - pr->redirect.len - prefix);
+
+ h->value.len = len;
+ h->value.data = data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_rewrite_redirect_vars(ngx_http_request_t *r, ngx_table_elt_t *h,
+ size_t prefix, ngx_http_proxy_redirect_t *pr)
+{
+ size_t len;
+ u_char *data, *p;
+ ngx_http_script_code_pt code;
+ ngx_http_script_engine_t e;
+ ngx_http_script_len_code_pt lcode;
+
+ if (pr->redirect.len > h->value.len - prefix
+ || ngx_rstrncmp(h->value.data + prefix, pr->redirect.data,
+ pr->redirect.len) != 0)
+ {
+ return NGX_DECLINED;
+ }
+
+ ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+ e.ip = pr->replacement.vars.lengths;
+ e.request = r;
+
+ len = h->value.len - pr->redirect.len;
+
+ while (*(uintptr_t *) e.ip) {
+ lcode = *(ngx_http_script_len_code_pt *) e.ip;
+ len += lcode(&e);
+ }
+
+ data = ngx_pnalloc(r->pool, len);
+ if (data == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_copy(data, h->value.data, prefix);
+
+ e.ip = pr->replacement.vars.values;
+ e.pos = p;
+
+ while (*(uintptr_t *) e.ip) {
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code(&e);
+ }
+
+ ngx_memcpy(e.pos, h->value.data + prefix + pr->redirect.len,
+ h->value.len - pr->redirect.len - prefix);
+
+ h->value.len = len;
+ h->value.data = data;
+
+ 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 = 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.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;
+#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->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_keyval_t *s;
+ ngx_hash_init_t hash;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_proxy_redirect_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_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 or bigger than "
+ "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 or bigger than "
+ "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 "
+ "the temporary files usage or must be equal or bigger than "
+ "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_methods == 0) {
+ conf->upstream.cache_methods = prev->upstream.cache_methods;
+ }
+
+ conf->upstream.cache_methods |= NGX_HTTP_GET|NGX_HTTP_HEAD;
+
+ 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;
+ }
+
+ 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;
+ }
+
+#endif
+
+ if (conf->method.len == 0) {
+ conf->method = prev->method;
+
+ } else {
+ 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_redirect_t));
+ if (conf->redirects == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pr = ngx_array_push(conf->redirects);
+ if (pr == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pr->handler = ngx_http_proxy_rewrite_redirect_text;
+
+ if (conf->vars.uri.len) {
+ pr->redirect = conf->url;
+ pr->replacement.text = conf->location;
+
+ } else {
+ pr->redirect.len = conf->url.len + sizeof("/") - 1;
+
+ p = ngx_pnalloc(cf->pool, pr->redirect.len);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pr->redirect.data = p;
+
+ p = ngx_cpymem(p, conf->url.data, conf->url.len);
+ *p = '/';
+
+ ngx_str_set(&pr->replacement.text, "/");
+ }
+ }
+ }
+
+#if (NGX_HTTP_SSL)
+ if (conf->upstream.ssl == NULL) {
+ conf->upstream.ssl = prev->upstream.ssl;
+ }
+#endif
+
+ 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 (conf->headers_source == NULL) {
+ conf->headers_source = ngx_array_create(cf->pool, 4,
+ sizeof(ngx_keyval_t));
+ if (conf->headers_source == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ s = ngx_array_push(conf->headers_source);
+ if (s == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_str_set(&s->key, "Content-Length");
+ ngx_str_set(&s->value, "$proxy_internal_body_length");
+ }
+
+ 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;
+ 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 (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;
+ }
+
+
+ src = conf->headers_source->elts;
+
+#if (NGX_HTTP_CACHE)
+
+ h = conf->upstream.cache ? ngx_http_proxy_cache_headers:
+ ngx_http_proxy_headers;
+#else
+
+ h = ngx_http_proxy_headers;
+
+#endif
+
+ while (h->key.len) {
+
+ for (i = 0; i < conf->headers_source->nelts; i++) {
+ if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) {
+ goto next;
+ }
+ }
+
+ s = ngx_array_push(conf->headers_source);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ *s = *h;
+
+ src = conf->headers_source->elts;
+
+ next:
+
+ h++;
+ }
+
+
+ src = conf->headers_source->elts;
+ for (i = 0; i < conf->headers_source->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\" may not have URI part in "
+ "location given by regular expression, "
+ "or inside named location, "
+ "or inside the \"if\" statement, "
+ "or inside the \"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_array_t *vars_lengths, *vars_values;
+ ngx_http_script_compile_t sc;
+ ngx_http_proxy_redirect_t *pr;
+
+ if (plcf->redirect == 0) {
+ return NGX_CONF_OK;
+ }
+
+ 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_redirect_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\" may not 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\" must go "
+ "after the \"proxy_pass\" directive");
+ return NGX_CONF_ERROR;
+ }
+
+ pr->handler = ngx_http_proxy_rewrite_redirect_text;
+
+ if (plcf->vars.uri.len) {
+ pr->redirect = plcf->url;
+ pr->replacement.text = plcf->location;
+
+ } else {
+ pr->redirect.len = plcf->url.len + sizeof("/") - 1;
+
+ p = ngx_pnalloc(cf->pool, pr->redirect.len);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pr->redirect.data = p;
+
+ p = ngx_cpymem(p, plcf->url.data, plcf->url.len);
+ *p = '/';
+
+ ngx_str_set(&pr->replacement.text, "/");
+ }
+
+ return NGX_CONF_OK;
+ }
+
+ if (ngx_http_script_variables_count(&value[2]) == 0) {
+ pr->handler = ngx_http_proxy_rewrite_redirect_text;
+ pr->redirect = value[1];
+ pr->replacement.text = value[2];
+
+ return NGX_CONF_OK;
+ }
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ vars_lengths = NULL;
+ vars_values = NULL;
+
+ sc.cf = cf;
+ sc.source = &value[2];
+ sc.lengths = &vars_lengths;
+ sc.values = &vars_values;
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ pr->handler = ngx_http_proxy_rewrite_redirect_vars;
+ pr->redirect = value[1];
+ pr->replacement.vars.lengths = vars_lengths->elts;
+ pr->replacement.vars.values = vars_values->elts;
+
+ return NGX_CONF_OK;
+}
+
+
+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.len) {
+ 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, 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;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_random_index_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_random_index_module.c
new file mode 100644
index 00000000000..02bdc45f9ea
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_random_index_module.c
@@ -0,0 +1,316 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_flag_t enable;
+} ngx_http_random_index_loc_conf_t;
+
+
+#define NGX_HTTP_RANDOM_INDEX_PREALLOCATE 50
+
+
+static ngx_int_t ngx_http_random_index_error(ngx_http_request_t *r,
+ ngx_dir_t *dir, ngx_str_t *name);
+static ngx_int_t ngx_http_random_index_init(ngx_conf_t *cf);
+static void *ngx_http_random_index_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_random_index_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+
+
+static ngx_command_t ngx_http_random_index_commands[] = {
+
+ { ngx_string("random_index"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_random_index_loc_conf_t, enable),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_random_index_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_random_index_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_random_index_create_loc_conf, /* create location configration */
+ ngx_http_random_index_merge_loc_conf /* merge location configration */
+};
+
+
+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;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_range_filter_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_range_filter_module.c
new file mode 100644
index 00000000000..e1124066066
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_range_filter_module.c
@@ -0,0 +1,861 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+/*
+ * the single part format:
+ *
+ * "HTTP/1.0 206 Partial Content" CRLF
+ * ... header ...
+ * "Content-Type: image/jpeg" CRLF
+ * "Content-Length: SIZE" CRLF
+ * "Content-Range: bytes START-END/SIZE" CRLF
+ * CRLF
+ * ... data ...
+ *
+ *
+ * the 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;
+
+
+ngx_int_t ngx_http_range_parse(ngx_http_request_t *r,
+ ngx_http_range_filter_ctx_t *ctx);
+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;
+ ngx_int_t rc;
+ 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);
+ }
+
+ 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 && r->headers_out.last_modified_time != -1) {
+
+ if_range = ngx_http_parse_time(r->headers_in.if_range->value.data,
+ r->headers_in.if_range->value.len);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http ir:%d lm:%d",
+ if_range, r->headers_out.last_modified_time);
+
+ if (if_range != r->headers_out.last_modified_time) {
+ goto next_filter;
+ }
+ }
+
+ 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;
+ }
+
+ rc = ngx_http_range_parse(r, ctx);
+
+ if (rc == 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);
+ }
+
+ if (rc == NGX_HTTP_RANGE_NOT_SATISFIABLE) {
+ return ngx_http_range_not_satisfiable(r);
+ }
+
+ /* rc == NGX_ERROR */
+
+ return rc;
+
+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);
+}
+
+
+ngx_int_t
+ngx_http_range_parse(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx)
+{
+ u_char *p;
+ off_t start, end;
+ ngx_uint_t suffix;
+ ngx_http_range_t *range;
+
+ p = r->headers_in.range->value.data + 6;
+
+ 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;
+ }
+
+ if (start >= r->headers_out.content_length_n) {
+ return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ }
+
+ while (*p == ' ') { p++; }
+
+ if (*p == ',' || *p == '\0') {
+ range = ngx_array_push(&ctx->ranges);
+ if (range == NULL) {
+ return NGX_ERROR;
+ }
+
+ range->start = start;
+ range->end = r->headers_out.content_length_n;
+
+ if (*p++ != ',') {
+ return NGX_OK;
+ }
+
+ continue;
+ }
+
+ } 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 = r->headers_out.content_length_n - end;
+ end = r->headers_out.content_length_n - 1;
+ }
+
+ if (start > end) {
+ return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ }
+
+ range = ngx_array_push(&ctx->ranges);
+ if (range == NULL) {
+ return NGX_ERROR;
+ }
+
+ range->start = start;
+
+ if (end >= r->headers_out.content_length_n) {
+ /*
+ * Download Accelerator sends the last byte position
+ * that equals to the file length
+ */
+ range->end = r->headers_out.content_length_n;
+
+ } else {
+ range->end = end + 1;
+ }
+
+ if (*p++ != ',') {
+ 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) {
+
+ if (buf->in_file) {
+ start = buf->file_pos + ctx->offset;
+ last = buf->file_last + ctx->offset;
+
+ } else {
+ start = buf->pos - buf->start + ctx->offset;
+ last = buf->last - buf->start + ctx->offset;
+ }
+
+ 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)
+{
+ off_t body_start;
+ 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;
+
+#if (NGX_HTTP_CACHE)
+ body_start = r->cached ? r->cache->body_start : 0;
+#else
+ body_start = 0;
+#endif
+
+ 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 = body_start + range[i].start;
+ b->file_last = body_start + range[i].end;
+ }
+
+ if (ngx_buf_in_memory(buf)) {
+ b->pos = buf->start + (size_t) range[i].start;
+ b->last = buf->start + (size_t) range[i].end;
+ }
+
+ dcl = ngx_alloc_chain_link(r->pool);
+ if (dcl == NULL) {
+ return NGX_ERROR;
+ }
+
+ dcl->buf = b;
+
+ *ll = hcl;
+ hcl->next = rcl;
+ rcl->next = dcl;
+ ll = &dcl->next;
+ }
+
+ /* the last boundary CRLF "--0123456789--" CRLF */
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->temporary = 1;
+ b->last_buf = 1;
+
+ b->pos = ngx_pnalloc(r->pool, sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
+ + sizeof("--" CRLF) - 1);
+ if (b->pos == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->last = ngx_cpymem(b->pos, ctx->boundary_header.data,
+ sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN);
+ *b->last++ = '-'; *b->last++ = '-';
+ *b->last++ = CR; *b->last++ = LF;
+
+ hcl = ngx_alloc_chain_link(r->pool);
+ if (hcl == NULL) {
+ return NGX_ERROR;
+ }
+
+ hcl->buf = b;
+ hcl->next = NULL;
+
+ *ll = hcl;
+
+ return ngx_http_next_body_filter(r, out);
+}
+
+
+static ngx_int_t
+ngx_http_range_header_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_range_header_filter;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_range_body_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_range_body_filter;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_realip_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_realip_module.c
new file mode 100644
index 00000000000..b88b122425a
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_realip_module.c
@@ -0,0 +1,475 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_REALIP_XREALIP 0
+#define NGX_HTTP_REALIP_XFWD 1
+#define NGX_HTTP_REALIP_HEADER 2
+
+
+typedef struct {
+ in_addr_t mask;
+ in_addr_t addr;
+} ngx_http_realip_from_t;
+
+
+typedef struct {
+ ngx_array_t *from; /* array of ngx_http_realip_from_t */
+ ngx_uint_t type;
+ ngx_uint_t hash;
+ ngx_str_t header;
+#if (NGX_HAVE_UNIX_DOMAIN)
+ ngx_uint_t unixsock; /* unsigned unixsock:2; */
+#endif
+} 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, u_char *ip,
+ size_t len);
+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_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 *ip, *p;
+ size_t len;
+ ngx_uint_t i, hash;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header;
+ struct sockaddr_in *sin;
+ ngx_connection_t *c;
+ ngx_http_realip_ctx_t *ctx;
+ ngx_http_realip_from_t *from;
+ 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
+#if (NGX_HAVE_UNIX_DOMAIN)
+ && !rlcf->unixsock
+#endif
+ )
+ {
+ return NGX_DECLINED;
+ }
+
+ switch (rlcf->type) {
+
+ case NGX_HTTP_REALIP_XREALIP:
+
+ if (r->headers_in.x_real_ip == NULL) {
+ return NGX_DECLINED;
+ }
+
+ len = r->headers_in.x_real_ip->value.len;
+ ip = r->headers_in.x_real_ip->value.data;
+
+ break;
+
+ case NGX_HTTP_REALIP_XFWD:
+
+ if (r->headers_in.x_forwarded_for == NULL) {
+ return NGX_DECLINED;
+ }
+
+ len = r->headers_in.x_forwarded_for->value.len;
+ ip = r->headers_in.x_forwarded_for->value.data;
+
+ for (p = ip + len - 1; p > ip; p--) {
+ if (*p == ' ' || *p == ',') {
+ p++;
+ len -= p - ip;
+ ip = p;
+ break;
+ }
+ }
+
+ 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)
+ {
+ len = header[i].value.len;
+ ip = header[i].value.data;
+
+ goto found;
+ }
+ }
+
+ return NGX_DECLINED;
+ }
+
+found:
+
+ c = r->connection;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "realip: \"%s\"", ip);
+
+ /* AF_INET only */
+
+ if (c->sockaddr->sa_family == AF_INET) {
+ sin = (struct sockaddr_in *) c->sockaddr;
+
+ from = rlcf->from->elts;
+ for (i = 0; i < rlcf->from->nelts; i++) {
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "realip: %08XD %08XD %08XD",
+ sin->sin_addr.s_addr, from[i].mask, from[i].addr);
+
+ if ((sin->sin_addr.s_addr & from[i].mask) == from[i].addr) {
+ return ngx_http_realip_set_addr(r, ip, len);
+ }
+ }
+ }
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+ if (c->sockaddr->sa_family == AF_UNIX && rlcf->unixsock) {
+ return ngx_http_realip_set_addr(r, ip, len);
+ }
+
+#endif
+
+ return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_realip_set_addr(ngx_http_request_t *r, u_char *ip, size_t len)
+{
+ u_char *p;
+ ngx_int_t rc;
+ ngx_addr_t addr;
+ 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;
+
+ rc = ngx_parse_addr(c->pool, &addr, ip, len);
+
+ switch (rc) {
+ case NGX_DECLINED:
+ return NGX_DECLINED;
+ case NGX_ERROR:
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ default: /* NGX_OK */
+ break;
+ }
+
+ p = ngx_pnalloc(c->pool, len);
+ if (p == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_memcpy(p, ip, 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;
+ ngx_http_realip_from_t *from;
+
+ value = cf->args->elts;
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+ if (ngx_strcmp(value[1].data, "unix:") == 0) {
+ rlcf->unixsock = 1;
+ return NGX_CONF_OK;
+ }
+
+#endif
+
+ if (rlcf->from == NULL) {
+ rlcf->from = ngx_array_create(cf->pool, 2,
+ sizeof(ngx_http_realip_from_t));
+ if (rlcf->from == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ from = ngx_array_push(rlcf->from);
+ if (from == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ 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 (cidr.family != AF_INET) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"set_real_ip_from\" supports IPv4 only");
+ 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]);
+ }
+
+ from->mask = cidr.u.in.mask;
+ from->addr = cidr.u.in.addr;
+
+ 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;
+#if (NGX_HAVE_UNIX_DOMAIN)
+ conf->unixsock = 2;
+#endif
+
+ 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;
+ }
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+ if (conf->unixsock == 2) {
+ conf->unixsock = (prev->unixsock == 2) ? 0 : prev->unixsock;
+ }
+#endif
+
+ ngx_conf_merge_uint_value(conf->type, prev->type, NGX_HTTP_REALIP_XREALIP);
+
+ 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;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_referer_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_referer_module.c
new file mode 100644
index 00000000000..252fb5a93d7
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_referer_module.c
@@ -0,0 +1,612 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#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 = "referers_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);
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_rewrite_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_rewrite_module.c
new file mode 100644
index 00000000000..44b5746ea72
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_rewrite_module.c
@@ -0,0 +1,1006 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_array_t *codes; /* uintptr_t */
+
+ ngx_uint_t stack_size;
+
+ ngx_flag_t log;
+ ngx_flag_t uninitialized_variable_warn;
+} ngx_http_rewrite_loc_conf_t;
+
+
+static void *ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_rewrite_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_rewrite_init(ngx_conf_t *cf);
+static char *ngx_http_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_rewrite_break(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_rewrite_if(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char * ngx_http_rewrite_if_condition(ngx_conf_t *cf,
+ ngx_http_rewrite_loc_conf_t *lcf);
+static char *ngx_http_rewrite_variable(ngx_conf_t *cf,
+ ngx_http_rewrite_loc_conf_t *lcf, ngx_str_t *value);
+static char *ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char * ngx_http_rewrite_value(ngx_conf_t *cf,
+ ngx_http_rewrite_loc_conf_t *lcf, ngx_str_t *value);
+
+
+static ngx_command_t ngx_http_rewrite_commands[] = {
+
+ { ngx_string("rewrite"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_TAKE23,
+ ngx_http_rewrite,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("return"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_TAKE12,
+ ngx_http_rewrite_return,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("break"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_NOARGS,
+ ngx_http_rewrite_break,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("if"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_1MORE,
+ ngx_http_rewrite_if,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("set"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_TAKE2,
+ ngx_http_rewrite_set,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("rewrite_log"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF
+ |NGX_HTTP_LIF_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_rewrite_loc_conf_t, log),
+ NULL },
+
+ { ngx_string("uninitialized_variable_warn"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF
+ |NGX_HTTP_LIF_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_rewrite_loc_conf_t, uninitialized_variable_warn),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_rewrite_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_rewrite_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_rewrite_create_loc_conf, /* create location configration */
+ ngx_http_rewrite_merge_loc_conf /* merge location configration */
+};
+
+
+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_http_script_code_pt code;
+ ngx_http_script_engine_t *e;
+ ngx_http_rewrite_loc_conf_t *rlcf;
+
+ 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_DECLINED) {
+ return NGX_DECLINED;
+ }
+
+ if (r->err_status == 0) {
+ return e->status;
+ }
+
+ return r->err_status;
+}
+
+
+static ngx_int_t
+ngx_http_rewrite_var(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_http_variable_t *var;
+ ngx_http_core_main_conf_t *cmcf;
+ ngx_http_rewrite_loc_conf_t *rlcf;
+
+ rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module);
+
+ if (rlcf->uninitialized_variable_warn == 0) {
+ *v = ngx_http_variable_null_value;
+ return NGX_OK;
+ }
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ var = cmcf->variables.elts;
+
+ /*
+ * the ngx_http_rewrite_module sets variables directly in r->variables,
+ * and they should be handled by ngx_http_get_indexed_variable(),
+ * so the handler is called only if the variable is not initialized
+ */
+
+ ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+ "using uninitialized \"%V\" variable", &var[data].name);
+
+ *v = ngx_http_variable_null_value;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_rewrite_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_rewrite_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->stack_size = NGX_CONF_UNSET_UINT;
+ conf->log = NGX_CONF_UNSET;
+ conf->uninitialized_variable_warn = NGX_CONF_UNSET;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_rewrite_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_rewrite_loc_conf_t *prev = parent;
+ ngx_http_rewrite_loc_conf_t *conf = child;
+
+ uintptr_t *code;
+
+ ngx_conf_merge_value(conf->log, prev->log, 0);
+ ngx_conf_merge_value(conf->uninitialized_variable_warn,
+ prev->uninitialized_variable_warn, 1);
+ ngx_conf_merge_uint_value(conf->stack_size, prev->stack_size, 10);
+
+ if (conf->codes == NULL) {
+ return NGX_CONF_OK;
+ }
+
+ if (conf->codes == prev->codes) {
+ return NGX_CONF_OK;
+ }
+
+ code = ngx_array_push_n(conf->codes, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_rewrite_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_SERVER_REWRITE_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_rewrite_handler;
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_rewrite_handler;
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_http_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_rewrite_loc_conf_t *lcf = conf;
+
+ ngx_str_t *value;
+ ngx_uint_t last;
+ ngx_regex_compile_t rc;
+ ngx_http_script_code_pt *code;
+ ngx_http_script_compile_t sc;
+ ngx_http_script_regex_code_t *regex;
+ ngx_http_script_regex_end_code_t *regex_end;
+ u_char errstr[NGX_MAX_CONF_ERRSTR];
+
+ regex = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(ngx_http_script_regex_code_t));
+ if (regex == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(regex, sizeof(ngx_http_script_regex_code_t));
+
+ value = cf->args->elts;
+
+ ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+ rc.pattern = value[1];
+ rc.err.len = NGX_MAX_CONF_ERRSTR;
+ rc.err.data = errstr;
+
+ /* TODO: NGX_REGEX_CASELESS */
+
+ regex->regex = ngx_http_regex_compile(cf, &rc);
+ if (regex->regex == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ regex->code = ngx_http_script_regex_start_code;
+ regex->uri = 1;
+ regex->name = value[1];
+
+ if (value[2].data[value[2].len - 1] == '?') {
+
+ /* the last "?" drops the original arguments */
+ value[2].len--;
+
+ } else {
+ regex->add_args = 1;
+ }
+
+ last = 0;
+
+ if (ngx_strncmp(value[2].data, "http://", sizeof("http://") - 1) == 0
+ || ngx_strncmp(value[2].data, "https://", sizeof("https://") - 1) == 0
+ || ngx_strncmp(value[2].data, "$scheme", sizeof("$scheme") - 1) == 0)
+ {
+ regex->status = NGX_HTTP_MOVED_TEMPORARILY;
+ regex->redirect = 1;
+ last = 1;
+ }
+
+ if (cf->args->nelts == 4) {
+ if (ngx_strcmp(value[3].data, "last") == 0) {
+ last = 1;
+
+ } else if (ngx_strcmp(value[3].data, "break") == 0) {
+ regex->break_cycle = 1;
+ last = 1;
+
+ } else if (ngx_strcmp(value[3].data, "redirect") == 0) {
+ regex->status = NGX_HTTP_MOVED_TEMPORARILY;
+ regex->redirect = 1;
+ last = 1;
+
+ } else if (ngx_strcmp(value[3].data, "permanent") == 0) {
+ regex->status = NGX_HTTP_MOVED_PERMANENTLY;
+ regex->redirect = 1;
+ last = 1;
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[3]);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = &value[2];
+ sc.lengths = &regex->lengths;
+ sc.values = &lcf->codes;
+ sc.variables = ngx_http_script_variables_count(&value[2]);
+ sc.main = regex;
+ sc.complete_lengths = 1;
+ sc.compile_args = !regex->redirect;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ regex = sc.main;
+
+ regex->size = sc.size;
+ regex->args = sc.args;
+
+ if (sc.variables == 0 && !sc.dup_capture) {
+ regex->lengths = NULL;
+ }
+
+ regex_end = ngx_http_script_add_code(lcf->codes,
+ sizeof(ngx_http_script_regex_end_code_t),
+ &regex);
+ if (regex_end == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ regex_end->code = ngx_http_script_regex_end_code;
+ regex_end->uri = regex->uri;
+ regex_end->args = regex->args;
+ regex_end->add_args = regex->add_args;
+ regex_end->redirect = regex->redirect;
+
+ if (last) {
+ code = ngx_http_script_add_code(lcf->codes, sizeof(uintptr_t), &regex);
+ if (code == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *code = NULL;
+ }
+
+ regex->next = (u_char *) lcf->codes->elts + lcf->codes->nelts
+ - (u_char *) regex;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_rewrite_loc_conf_t *lcf = conf;
+
+ u_char *p;
+ ngx_str_t *value, *v;
+ ngx_http_script_return_code_t *ret;
+ ngx_http_compile_complex_value_t ccv;
+
+ ret = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(ngx_http_script_return_code_t));
+ if (ret == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ ngx_memzero(ret, sizeof(ngx_http_script_return_code_t));
+
+ ret->code = ngx_http_script_return_code;
+
+ p = value[1].data;
+
+ ret->status = ngx_atoi(p, value[1].len);
+
+ if (ret->status == (uintptr_t) NGX_ERROR) {
+
+ if (cf->args->nelts == 2
+ && (ngx_strncmp(p, "http://", sizeof("http://") - 1) == 0
+ || ngx_strncmp(p, "https://", sizeof("https://") - 1) == 0
+ || ngx_strncmp(p, "$scheme", sizeof("$scheme") - 1) == 0))
+ {
+ ret->status = NGX_HTTP_MOVED_TEMPORARILY;
+ v = &value[1];
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid return code \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+
+ if (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;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_scgi_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_scgi_module.c
new file mode 100644
index 00000000000..a4230a956d6
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_scgi_module.c
@@ -0,0 +1,1666 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Manlio Perillo (manlio.perillo@gmail.com)
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+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 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 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_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 },
+
+#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_TAKE2,
+ ngx_conf_set_keyval_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;
+
+ 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_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)
+{
+ u_char ch, *key, *val, *lowcase_key;
+ size_t len, allocated;
+ ngx_buf_t *b;
+ ngx_str_t *content_length;
+ ngx_uint_t i, n, hash, 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;
+ static ngx_str_t zero = ngx_string("0");
+
+ content_length = r->headers_in.content_length ?
+ &r->headers_in.content_length->value : &zero;
+
+ 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;
+ len += lcode(&le);
+
+ while (*(uintptr_t *) le.ip) {
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ len += lcode(&le) + 1;
+ }
+ le.ip += sizeof(uintptr_t);
+ }
+ }
+
+ 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_snprintf(b->last,
+ NGX_SIZE_T_LEN + 1 + sizeof("CONTENT_LENGTH")
+ + NGX_OFF_T_LEN + 1,
+ "%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;
+
+ while (*(uintptr_t *) e.ip) {
+
+#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;
+
+ 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) {
+
+ r->http_version = NGX_HTTP_VERSION_9;
+
+ 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_cpystrn(h->key.data, r->header_name_start, h->key.len + 1);
+ ngx_cpystrn(h->value.data, r->header_start, h->value.len + 1);
+
+ 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");
+
+ if (r->http_version > NGX_HTTP_VERSION_9) {
+ return NGX_OK;
+ }
+
+ u = r->upstream;
+
+ 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;
+ }
+
+ r->http_version = NGX_HTTP_VERSION_10;
+ u->headers_in.status_n = status;
+ u->headers_in.status_line = *status_line;
+
+ } else if (u->headers_in.location) {
+ r->http_version = NGX_HTTP_VERSION_10;
+ u->headers_in.status_n = 302;
+ ngx_str_set(&u->headers_in.status_line,
+ "302 Moved Temporarily");
+
+ } else {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent neither valid HTTP/1.0 header "
+ "nor \"Status\" header line");
+ 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;
+ }
+
+ 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.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;
+#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;
+
+ 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;
+
+ u_char *p;
+ size_t size;
+ uintptr_t *code;
+ ngx_uint_t i;
+ ngx_array_t headers_names;
+ ngx_keyval_t *src;
+ ngx_hash_key_t *hk;
+ ngx_hash_init_t hash;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_script_compile_t sc;
+ ngx_http_script_copy_code_t *copy;
+
+ 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_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 or bigger "
+ "than 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 or bigger than "
+ "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 "
+ "the temporary files usage or must be equal or bigger than "
+ "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_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;
+ }
+
+#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 (conf->params_source == NULL) {
+ conf->flushes = prev->flushes;
+ conf->params_len = prev->params_len;
+ conf->params = prev->params;
+ conf->params_source = prev->params_source;
+ conf->headers_hash = prev->headers_hash;
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->params_source == NULL) {
+
+ if ((conf->upstream.cache == NULL)
+ == (prev->upstream.cache == NULL))
+ {
+ return NGX_CONF_OK;
+ }
+
+ /* 6 is a number of ngx_http_scgi_cache_headers entries */
+ conf->params_source = ngx_array_create(cf->pool, 6,
+ sizeof(ngx_keyval_t));
+ if (conf->params_source == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+#else
+
+ if (conf->params_source == NULL) {
+ return NGX_CONF_OK;
+ }
+
+#endif
+ }
+
+ conf->params_len = ngx_array_create(cf->pool, 64, 1);
+ if (conf->params_len == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->params = ngx_array_create(cf->pool, 512, 1);
+ if (conf->params == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ src = conf->params_source->elts;
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->upstream.cache) {
+ ngx_keyval_t *h, *s;
+
+ for (h = ngx_http_scgi_cache_headers; h->key.len; h++) {
+
+ for (i = 0; i < conf->params_source->nelts; i++) {
+ if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) {
+ goto next;
+ }
+ }
+
+ s = ngx_array_push(conf->params_source);
+ if (s == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *s = *h;
+
+ src = conf->params_source->elts;
+
+ next:
+
+ h++;
+ }
+ }
+
+#endif
+
+ for (i = 0; i < conf->params_source->nelts; 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_CONF_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_CONF_ERROR;
+ }
+
+ copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code;
+ copy->len = src[i].key.len + 1;
+
+
+ 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_CONF_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_CONF_ERROR;
+ }
+
+ code = ngx_array_push_n(conf->params_len, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+
+
+ code = ngx_array_push_n(conf->params, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+ }
+
+ code = ngx_array_push_n(conf->params_len, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+
+ code = ngx_array_push_n(conf->params, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_CONF_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;
+
+ if (ngx_hash_init(&hash, headers_names.elts, headers_names.nelts) != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+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.len) {
+ return "is duplicate";
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &scf->cache_key;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+#endif
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_secure_link_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_secure_link_module.c
new file mode 100644
index 00000000000..6c4be6191da
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_secure_link_module.c
@@ -0,0 +1,354 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_md5.h>
+
+
+typedef struct {
+ ngx_http_complex_value_t *variable;
+ ngx_http_complex_value_t *md5;
+ ngx_str_t secret;
+} ngx_http_secure_link_conf_t;
+
+
+typedef struct {
+ ngx_str_t expires;
+} ngx_http_secure_link_ctx_t;
+
+
+static ngx_int_t ngx_http_secure_link_old_variable(ngx_http_request_t *r,
+ ngx_http_secure_link_conf_t *conf, ngx_http_variable_value_t *v,
+ uintptr_t data);
+static ngx_int_t ngx_http_secure_link_expires_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static void *ngx_http_secure_link_create_conf(ngx_conf_t *cf);
+static char *ngx_http_secure_link_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static ngx_int_t ngx_http_secure_link_add_variables(ngx_conf_t *cf);
+
+
+static ngx_command_t ngx_http_secure_link_commands[] = {
+
+ { ngx_string("secure_link"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_set_complex_value_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_secure_link_conf_t, variable),
+ NULL },
+
+ { ngx_string("secure_link_md5"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_set_complex_value_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_secure_link_conf_t, md5),
+ NULL },
+
+ { ngx_string("secure_link_secret"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_secure_link_conf_t, secret),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_secure_link_module_ctx = {
+ ngx_http_secure_link_add_variables, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_secure_link_create_conf, /* create location configuration */
+ ngx_http_secure_link_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_secure_link_module = {
+ NGX_MODULE_V1,
+ &ngx_http_secure_link_module_ctx, /* module context */
+ ngx_http_secure_link_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_http_secure_link_name = ngx_string("secure_link");
+static ngx_str_t ngx_http_secure_link_expires_name =
+ ngx_string("secure_link_expires");
+
+
+static ngx_int_t
+ngx_http_secure_link_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ u_char *p, *last;
+ ngx_str_t val, hash;
+ time_t expires;
+ ngx_md5_t md5;
+ ngx_http_secure_link_ctx_t *ctx;
+ ngx_http_secure_link_conf_t *conf;
+ u_char hash_buf[16], md5_buf[16];
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_secure_link_module);
+
+ if (conf->secret.len) {
+ 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;
+
+ ngx_conf_merge_str_value(conf->secret, prev->secret, "");
+
+ if (conf->variable == NULL) {
+ conf->variable = prev->variable;
+ }
+
+ if (conf->md5 == NULL) {
+ conf->md5 = prev->md5;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_secure_link_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var;
+
+ var = ngx_http_add_variable(cf, &ngx_http_secure_link_name, 0);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = ngx_http_secure_link_variable;
+
+ var = ngx_http_add_variable(cf, &ngx_http_secure_link_expires_name, 0);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = ngx_http_secure_link_expires_variable;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_split_clients_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_split_clients_module.c
new file mode 100644
index 00000000000..0b726f777a1
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_split_clients_module.c
@@ -0,0 +1,241 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ uint32_t percent;
+ ngx_http_variable_value_t value;
+} ngx_http_split_clients_part_t;
+
+
+typedef struct {
+ ngx_http_complex_value_t value;
+ ngx_array_t parts;
+} ngx_http_split_clients_ctx_t;
+
+
+static char *ngx_conf_split_clients_block(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_split_clients(ngx_conf_t *cf, ngx_command_t *dummy,
+ void *conf);
+
+static ngx_command_t ngx_http_split_clients_commands[] = {
+
+ { ngx_string("split_clients"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2,
+ ngx_conf_split_clients_block,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_split_clients_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_split_clients_module = {
+ NGX_MODULE_V1,
+ &ngx_http_split_clients_module_ctx, /* module context */
+ ngx_http_split_clients_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_split_clients_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_http_split_clients_ctx_t *ctx = (ngx_http_split_clients_ctx_t *) data;
+
+ uint32_t hash;
+ ngx_str_t val;
+ ngx_uint_t i;
+ ngx_http_split_clients_part_t *part;
+
+ *v = ngx_http_variable_null_value;
+
+ if (ngx_http_complex_value(r, &ctx->value, &val) != NGX_OK) {
+ return NGX_OK;
+ }
+
+ hash = ngx_murmur_hash2(val.data, val.len);
+
+ part = ctx->parts.elts;
+
+ for (i = 0; i < ctx->parts.nelts; i++) {
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http split: %uD %uD", hash, part[i].percent);
+
+ if (hash < part[i].percent) {
+ *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;
+ ngx_str_t *value, name;
+ ngx_uint_t i, sum, last;
+ 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];
+ 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 sum is more than 100%%");
+ return NGX_CONF_ERROR;
+ }
+
+ if (part[i].percent) {
+ part[i].percent = (uint32_t)
+ (last + 0xffffffff / 10000 * part[i].percent);
+ } else {
+ part[i].percent = 0xffffffff;
+ }
+
+ last = part[i].percent;
+ }
+
+ 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].data[value[0].len - 1] != '%') {
+ goto invalid;
+ }
+
+ n = ngx_atofp(value[0].data, value[0].len - 1, 2);
+ if (n == NGX_ERROR || n == 0) {
+ goto invalid;
+ }
+
+ part->percent = (uint32_t) n;
+ }
+
+ part->value.len = value[1].len;
+ part->value.valid = 1;
+ part->value.no_cacheable = 0;
+ part->value.not_found = 0;
+ part->value.data = value[1].data;
+
+ return NGX_CONF_OK;
+
+invalid:
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid percent value \"%V\"", &value[0]);
+ return NGX_CONF_ERROR;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_ssi_filter_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_ssi_filter_module.c
new file mode 100644
index 00000000000..75a7156efc8
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_ssi_filter_module.c
@@ -0,0 +1,2799 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#define NGX_HTTP_SSI_ERROR 1
+
+#define NGX_HTTP_SSI_DATE_LEN 2048
+
+#define NGX_HTTP_SSI_ADD_PREFIX 1
+#define NGX_HTTP_SSI_ADD_ZERO 2
+
+
+typedef struct {
+ ngx_flag_t enable;
+ ngx_flag_t silent_errors;
+ ngx_flag_t ignore_recycled_buffers;
+
+ ngx_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_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_FLAG,
+ 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_FLAG,
+ 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[] = "<!--";
+
+static ngx_str_t ngx_http_ssi_none = ngx_string("(none)");
+static ngx_str_t ngx_http_ssi_null_string = ngx_null_string;
+
+
+#define NGX_HTTP_SSI_INCLUDE_VIRTUAL 0
+#define NGX_HTTP_SSI_INCLUDE_FILE 1
+#define NGX_HTTP_SSI_INCLUDE_WAIT 2
+#define NGX_HTTP_SSI_INCLUDE_SET 3
+#define NGX_HTTP_SSI_INCLUDE_STUB 4
+
+#define NGX_HTTP_SSI_ECHO_VAR 0
+#define NGX_HTTP_SSI_ECHO_DEFAULT 1
+#define NGX_HTTP_SSI_ECHO_ENCODING 2
+
+#define NGX_HTTP_SSI_CONFIG_ERRMSG 0
+#define NGX_HTTP_SSI_CONFIG_TIMEFMT 1
+
+#define NGX_HTTP_SSI_SET_VAR 0
+#define NGX_HTTP_SSI_SET_VALUE 1
+
+#define NGX_HTTP_SSI_IF_EXPR 0
+
+#define NGX_HTTP_SSI_BLOCK_NAME 0
+
+
+static ngx_http_ssi_param_t ngx_http_ssi_include_params[] = {
+ { ngx_string("virtual"), NGX_HTTP_SSI_INCLUDE_VIRTUAL, 0, 0 },
+ { ngx_string("file"), NGX_HTTP_SSI_INCLUDE_FILE, 0, 0 },
+ { ngx_string("wait"), NGX_HTTP_SSI_INCLUDE_WAIT, 0, 0 },
+ { ngx_string("set"), NGX_HTTP_SSI_INCLUDE_SET, 0, 0 },
+ { ngx_string("stub"), NGX_HTTP_SSI_INCLUDE_STUB, 0, 0 },
+ { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t ngx_http_ssi_echo_params[] = {
+ { ngx_string("var"), NGX_HTTP_SSI_ECHO_VAR, 1, 0 },
+ { ngx_string("default"), NGX_HTTP_SSI_ECHO_DEFAULT, 0, 0 },
+ { ngx_string("encoding"), NGX_HTTP_SSI_ECHO_ENCODING, 0, 0 },
+ { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t ngx_http_ssi_config_params[] = {
+ { ngx_string("errmsg"), NGX_HTTP_SSI_CONFIG_ERRMSG, 0, 0 },
+ { ngx_string("timefmt"), NGX_HTTP_SSI_CONFIG_TIMEFMT, 0, 0 },
+ { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t ngx_http_ssi_set_params[] = {
+ { ngx_string("var"), NGX_HTTP_SSI_SET_VAR, 1, 0 },
+ { ngx_string("value"), NGX_HTTP_SSI_SET_VALUE, 1, 0 },
+ { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t ngx_http_ssi_if_params[] = {
+ { ngx_string("expr"), NGX_HTTP_SSI_IF_EXPR, 1, 0 },
+ { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t ngx_http_ssi_block_params[] = {
+ { ngx_string("name"), NGX_HTTP_SSI_BLOCK_NAME, 1, 0 },
+ { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t ngx_http_ssi_no_params[] = {
+ { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_command_t ngx_http_ssi_commands[] = {
+ { ngx_string("include"), ngx_http_ssi_include,
+ ngx_http_ssi_include_params, 0, 0, 1 },
+ { ngx_string("echo"), ngx_http_ssi_echo,
+ ngx_http_ssi_echo_params, 0, 0, 0 },
+ { ngx_string("config"), ngx_http_ssi_config,
+ ngx_http_ssi_config_params, 0, 0, 0 },
+ { ngx_string("set"), ngx_http_ssi_set, ngx_http_ssi_set_params, 0, 0, 0 },
+
+ { ngx_string("if"), ngx_http_ssi_if, ngx_http_ssi_if_params, 0, 0, 0 },
+ { ngx_string("elif"), ngx_http_ssi_if, ngx_http_ssi_if_params,
+ NGX_HTTP_SSI_COND_IF, 0, 0 },
+ { ngx_string("else"), ngx_http_ssi_else, ngx_http_ssi_no_params,
+ NGX_HTTP_SSI_COND_IF, 0, 0 },
+ { ngx_string("endif"), ngx_http_ssi_endif, ngx_http_ssi_no_params,
+ NGX_HTTP_SSI_COND_ELSE, 0, 0 },
+
+ { ngx_string("block"), ngx_http_ssi_block,
+ ngx_http_ssi_block_params, 0, 0, 0 },
+ { ngx_string("endblock"), ngx_http_ssi_endblock,
+ ngx_http_ssi_no_params, 0, 1, 0 },
+
+ { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+
+static ngx_http_variable_t ngx_http_ssi_vars[] = {
+
+ { ngx_string("date_local"), NULL, ngx_http_ssi_date_gmt_local_variable, 0,
+ NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("date_gmt"), NULL, ngx_http_ssi_date_gmt_local_variable, 1,
+ NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+
+
+static ngx_int_t
+ngx_http_ssi_header_filter(ngx_http_request_t *r)
+{
+ ngx_http_ssi_ctx_t *ctx;
+ ngx_http_ssi_loc_conf_t *slcf;
+
+ slcf = ngx_http_get_module_loc_conf(r, ngx_http_ssi_filter_module);
+
+ if (!slcf->enable
+ || r->headers_out.content_length_n == 0
+ || ngx_http_test_content_type(r, &slcf->types) == NULL)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_ssi_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_ssi_filter_module);
+
+
+ ctx->value_len = slcf->value_len;
+ ctx->last_out = &ctx->out;
+
+ ctx->encoding = NGX_HTTP_SSI_ENTITY_ENCODING;
+ ctx->output = 1;
+
+ ctx->params.elts = ctx->params_array;
+ ctx->params.size = sizeof(ngx_table_elt_t);
+ ctx->params.nalloc = NGX_HTTP_SSI_PARAMS_N;
+ ctx->params.pool = r->pool;
+
+ ngx_str_set(&ctx->timefmt, "%A, %d-%b-%Y %H:%M:%S %Z");
+ ngx_str_set(&ctx->errmsg,
+ "[an error occurred while processing the directive]");
+
+ r->filter_need_in_memory = 1;
+
+ if (r == r->main) {
+ ngx_http_clear_content_length(r);
+ ngx_http_clear_last_modified(r);
+ ngx_http_clear_accept_ranges(r);
+ }
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_ssi_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ size_t len;
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_uint_t i, index;
+ ngx_chain_t *cl, **ll;
+ ngx_table_elt_t *param;
+ ngx_http_ssi_ctx_t *ctx, *mctx;
+ ngx_http_ssi_block_t *bl;
+ ngx_http_ssi_param_t *prm;
+ ngx_http_ssi_command_t *cmd;
+ ngx_http_ssi_loc_conf_t *slcf;
+ ngx_http_ssi_main_conf_t *smcf;
+ ngx_str_t *params[NGX_HTTP_SSI_MAX_PARAMS + 1];
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_ssi_filter_module);
+
+ if (ctx == NULL
+ || (in == NULL
+ && ctx->buf == NULL
+ && ctx->in == NULL
+ && ctx->busy == NULL))
+ {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ /* add the incoming chain to the chain ctx->in */
+
+ if (in) {
+ if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http ssi filter \"%V?%V\"", &r->uri, &r->args);
+
+ if (ctx->wait) {
+
+ if (r != r->connection->data) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http ssi filter wait \"%V?%V\" non-active",
+ &ctx->wait->uri, &ctx->wait->args);
+
+ return NGX_AGAIN;
+ }
+
+ if (ctx->wait->done) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http ssi filter wait \"%V?%V\" done",
+ &ctx->wait->uri, &ctx->wait->args);
+
+ ctx->wait = NULL;
+
+ } else {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http ssi filter wait \"%V?%V\"",
+ &ctx->wait->uri, &ctx->wait->args);
+
+ return ngx_http_next_body_filter(r, NULL);
+ }
+ }
+
+ slcf = ngx_http_get_module_loc_conf(r, ngx_http_ssi_filter_module);
+
+ while (ctx->in || ctx->buf) {
+
+ if (ctx->buf == NULL) {
+ ctx->buf = ctx->in->buf;
+ ctx->in = ctx->in->next;
+ ctx->pos = ctx->buf->pos;
+ }
+
+ if (ctx->state == ssi_start_state) {
+ ctx->copy_start = ctx->pos;
+ ctx->copy_end = ctx->pos;
+ }
+
+ b = NULL;
+
+ while (ctx->pos < ctx->buf->last) {
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "saved: %d state: %d", ctx->saved, ctx->state);
+
+ rc = ngx_http_ssi_parse(r, ctx);
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "parse: %d, looked: %d %p-%p",
+ rc, ctx->looked, ctx->copy_start, ctx->copy_end);
+
+ if (rc == NGX_ERROR) {
+ return rc;
+ }
+
+ if (ctx->copy_start != ctx->copy_end) {
+
+ if (ctx->output) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "saved: %d", ctx->saved);
+
+ if (ctx->saved) {
+
+ if (ctx->free) {
+ cl = ctx->free;
+ ctx->free = ctx->free->next;
+ b = cl->buf;
+ ngx_memzero(b, sizeof(ngx_buf_t));
+
+ } else {
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ }
+
+ b->memory = 1;
+ b->pos = ngx_http_ssi_string;
+ b->last = ngx_http_ssi_string + ctx->saved;
+
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ ctx->saved = 0;
+ }
+
+ if (ctx->free) {
+ cl = ctx->free;
+ ctx->free = ctx->free->next;
+ b = cl->buf;
+
+ } else {
+ b = ngx_alloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ }
+
+ ngx_memcpy(b, ctx->buf, sizeof(ngx_buf_t));
+
+ b->pos = ctx->copy_start;
+ b->last = ctx->copy_end;
+ b->shadow = NULL;
+ b->last_buf = 0;
+ b->recycled = 0;
+
+ if (b->in_file) {
+ if (slcf->min_file_chunk < (size_t) (b->last - b->pos))
+ {
+ b->file_last = b->file_pos
+ + (b->last - ctx->buf->pos);
+ b->file_pos += b->pos - ctx->buf->pos;
+
+ } else {
+ b->in_file = 0;
+ }
+ }
+
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ } else {
+ if (ctx->block
+ && ctx->saved + (ctx->copy_end - ctx->copy_start))
+ {
+ b = ngx_create_temp_buf(r->pool,
+ ctx->saved + (ctx->copy_end - ctx->copy_start));
+
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ctx->saved) {
+ b->last = ngx_cpymem(b->pos, ngx_http_ssi_string,
+ ctx->saved);
+ }
+
+ b->last = ngx_cpymem(b->last, ctx->copy_start,
+ ctx->copy_end - ctx->copy_start);
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+
+ b = NULL;
+
+ mctx = ngx_http_get_module_ctx(r->main,
+ ngx_http_ssi_filter_module);
+ bl = mctx->blocks->elts;
+ for (ll = &bl[mctx->blocks->nelts - 1].bufs;
+ *ll;
+ ll = &(*ll)->next)
+ {
+ /* void */
+ }
+
+ *ll = cl;
+ }
+
+ ctx->saved = 0;
+ }
+ }
+
+ if (ctx->state == ssi_start_state) {
+ ctx->copy_start = ctx->pos;
+ ctx->copy_end = ctx->pos;
+
+ } else {
+ ctx->copy_start = NULL;
+ ctx->copy_end = NULL;
+ }
+
+ if (rc == NGX_AGAIN) {
+ continue;
+ }
+
+
+ b = NULL;
+
+ if (rc == NGX_OK) {
+
+ smcf = ngx_http_get_module_main_conf(r,
+ ngx_http_ssi_filter_module);
+
+ cmd = ngx_hash_find(&smcf->hash, ctx->key, ctx->command.data,
+ ctx->command.len);
+
+ if (cmd == NULL) {
+ if (ctx->output) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid SSI command: \"%V\"",
+ &ctx->command);
+ goto ssi_error;
+ }
+
+ continue;
+ }
+
+ if (cmd->conditional
+ && (ctx->conditional == 0
+ || ctx->conditional > cmd->conditional))
+ {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid context of SSI command: \"%V\"",
+ &ctx->command);
+ goto ssi_error;
+ }
+
+ if (!ctx->output && !cmd->block) {
+
+ if (ctx->block) {
+
+ /* reconstruct the SSI command text */
+
+ len = 5 + ctx->command.len + 4;
+
+ param = ctx->params.elts;
+ for (i = 0; i < ctx->params.nelts; i++) {
+ len += 1 + param[i].key.len + 2
+ + param[i].value.len + 1;
+ }
+
+ b = ngx_create_temp_buf(r->pool, len);
+
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+
+ *b->last++ = '<';
+ *b->last++ = '!';
+ *b->last++ = '-';
+ *b->last++ = '-';
+ *b->last++ = '#';
+
+ b->last = ngx_cpymem(b->last, ctx->command.data,
+ ctx->command.len);
+
+ for (i = 0; i < ctx->params.nelts; i++) {
+ *b->last++ = ' ';
+ b->last = ngx_cpymem(b->last, param[i].key.data,
+ param[i].key.len);
+ *b->last++ = '=';
+ *b->last++ = '"';
+ b->last = ngx_cpymem(b->last, param[i].value.data,
+ param[i].value.len);
+ *b->last++ = '"';
+ }
+
+ *b->last++ = ' ';
+ *b->last++ = '-';
+ *b->last++ = '-';
+ *b->last++ = '>';
+
+ mctx = ngx_http_get_module_ctx(r->main,
+ ngx_http_ssi_filter_module);
+ bl = mctx->blocks->elts;
+ for (ll = &bl[mctx->blocks->nelts - 1].bufs;
+ *ll;
+ ll = &(*ll)->next)
+ {
+ /* void */
+ }
+
+ *ll = cl;
+
+ b = NULL;
+
+ continue;
+ }
+
+ if (cmd->conditional == 0) {
+ continue;
+ }
+ }
+
+ if (ctx->params.nelts > NGX_HTTP_SSI_MAX_PARAMS) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "too many SSI command paramters: \"%V\"",
+ &ctx->command);
+ goto ssi_error;
+ }
+
+ ngx_memzero(params,
+ (NGX_HTTP_SSI_MAX_PARAMS + 1) * sizeof(ngx_str_t *));
+
+ param = ctx->params.elts;
+
+ for (i = 0; i < ctx->params.nelts; i++) {
+
+ for (prm = cmd->params; prm->name.len; prm++) {
+
+ if (param[i].key.len != prm->name.len
+ || ngx_strncmp(param[i].key.data, prm->name.data,
+ prm->name.len) != 0)
+ {
+ continue;
+ }
+
+ if (!prm->multiple) {
+ if (params[prm->index]) {
+ ngx_log_error(NGX_LOG_ERR,
+ r->connection->log, 0,
+ "duplicate \"%V\" parameter "
+ "in \"%V\" SSI command",
+ &param[i].key, &ctx->command);
+
+ goto ssi_error;
+ }
+
+ params[prm->index] = &param[i].value;
+
+ break;
+ }
+
+ for (index = prm->index; params[index]; index++) {
+ /* void */
+ }
+
+ params[index] = &param[i].value;
+
+ break;
+ }
+
+ if (prm->name.len == 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid parameter name: \"%V\" "
+ "in \"%V\" SSI command",
+ &param[i].key, &ctx->command);
+
+ goto ssi_error;
+ }
+ }
+
+ for (prm = cmd->params; prm->name.len; prm++) {
+ if (prm->mandatory && params[prm->index] == 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "mandatory \"%V\" parameter is absent "
+ "in \"%V\" SSI command",
+ &prm->name, &ctx->command);
+
+ goto ssi_error;
+ }
+ }
+
+ if (cmd->flush && ctx->out) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi flush");
+
+ if (ngx_http_ssi_output(r, ctx) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+
+ rc = cmd->handler(r, ctx, params);
+
+ if (rc == NGX_OK) {
+ continue;
+ }
+
+ if (rc == NGX_DONE || rc == NGX_AGAIN || rc == NGX_ERROR) {
+ ngx_http_ssi_buffered(r, ctx);
+ return rc;
+ }
+ }
+
+
+ /* rc == NGX_HTTP_SSI_ERROR */
+
+ ssi_error:
+
+ if (slcf->silent_errors) {
+ continue;
+ }
+
+ if (ctx->free) {
+ cl = ctx->free;
+ ctx->free = ctx->free->next;
+ b = cl->buf;
+ ngx_memzero(b, sizeof(ngx_buf_t));
+
+ } else {
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ }
+
+ b->memory = 1;
+ b->pos = ctx->errmsg.data;
+ b->last = ctx->errmsg.data + ctx->errmsg.len;
+
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ continue;
+ }
+
+ if (ctx->buf->last_buf || ngx_buf_in_memory(ctx->buf)) {
+ if (b == NULL) {
+ if (ctx->free) {
+ cl = ctx->free;
+ ctx->free = ctx->free->next;
+ b = cl->buf;
+ ngx_memzero(b, sizeof(ngx_buf_t));
+
+ } else {
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ }
+
+ b->sync = 1;
+
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+ }
+
+ b->last_buf = ctx->buf->last_buf;
+ b->shadow = ctx->buf;
+
+ if (slcf->ignore_recycled_buffers == 0) {
+ b->recycled = ctx->buf->recycled;
+ }
+ }
+
+ ctx->buf = NULL;
+
+ ctx->saved = ctx->looked;
+ }
+
+ if (ctx->out == NULL && ctx->busy == NULL) {
+ return NGX_OK;
+ }
+
+ return ngx_http_ssi_output(r, ctx);
+}
+
+
+static ngx_int_t
+ngx_http_ssi_output(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx)
+{
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+
+#if 1
+ b = NULL;
+ for (cl = ctx->out; cl; cl = cl->next) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi out: %p %p", cl->buf, cl->buf->pos);
+ if (cl->buf == b) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "the same buf was used in ssi");
+ ngx_debug_point();
+ return NGX_ERROR;
+ }
+ b = cl->buf;
+ }
+#endif
+
+ rc = ngx_http_next_body_filter(r, ctx->out);
+
+ if (ctx->busy == NULL) {
+ ctx->busy = ctx->out;
+
+ } else {
+ for (cl = ctx->busy; cl->next; cl = cl->next) { /* void */ }
+ cl->next = ctx->out;
+ }
+
+ ctx->out = NULL;
+ ctx->last_out = &ctx->out;
+
+ while (ctx->busy) {
+
+ cl = ctx->busy;
+ b = cl->buf;
+
+ if (ngx_buf_size(b) != 0) {
+ break;
+ }
+
+ if (b->shadow) {
+ b->shadow->pos = b->shadow->last;
+ }
+
+ ctx->busy = cl->next;
+
+ if (ngx_buf_in_memory(b) || b->in_file) {
+ /* add data bufs only to the free buf chain */
+
+ cl->next = ctx->free;
+ ctx->free = cl;
+ }
+ }
+
+ ngx_http_ssi_buffered(r, ctx);
+
+ return rc;
+}
+
+
+static void
+ngx_http_ssi_buffered(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx)
+{
+ if (ctx->in || ctx->buf) {
+ r->buffered |= NGX_HTTP_SSI_BUFFERED;
+
+ } else {
+ r->buffered &= ~NGX_HTTP_SSI_BUFFERED;
+ }
+}
+
+
+static ngx_int_t
+ngx_http_ssi_parse(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx)
+{
+ u_char *p, *value, *last, *copy_end, ch;
+ size_t looked;
+ ngx_http_ssi_state_e state;
+
+ state = ctx->state;
+ looked = ctx->looked;
+ last = ctx->buf->last;
+ copy_end = ctx->copy_end;
+
+ for (p = ctx->pos; p < last; p++) {
+
+ ch = *p;
+
+ if (state == ssi_start_state) {
+
+ /* the tight loop */
+
+ for ( ;; ) {
+ if (ch == '<') {
+ copy_end = p;
+ looked = 1;
+ state = ssi_tag_state;
+
+ goto tag_started;
+ }
+
+ if (++p == last) {
+ break;
+ }
+
+ ch = *p;
+ }
+
+ ctx->state = state;
+ ctx->pos = p;
+ ctx->looked = looked;
+ ctx->copy_end = p;
+
+ if (ctx->copy_start == NULL) {
+ ctx->copy_start = ctx->buf->pos;
+ }
+
+ return NGX_AGAIN;
+
+ tag_started:
+
+ continue;
+ }
+
+ switch (state) {
+
+ case ssi_start_state:
+ break;
+
+ case ssi_tag_state:
+ switch (ch) {
+ case '!':
+ looked = 2;
+ state = ssi_comment0_state;
+ break;
+
+ case '<':
+ copy_end = p;
+ break;
+
+ default:
+ copy_end = p;
+ looked = 0;
+ state = ssi_start_state;
+ break;
+ }
+
+ break;
+
+ case ssi_comment0_state:
+ switch (ch) {
+ case '-':
+ looked = 3;
+ state = ssi_comment1_state;
+ break;
+
+ case '<':
+ copy_end = p;
+ looked = 1;
+ state = ssi_tag_state;
+ break;
+
+ default:
+ copy_end = p;
+ looked = 0;
+ state = ssi_start_state;
+ break;
+ }
+
+ break;
+
+ case ssi_comment1_state:
+ switch (ch) {
+ case '-':
+ looked = 4;
+ state = ssi_sharp_state;
+ break;
+
+ case '<':
+ copy_end = p;
+ looked = 1;
+ state = ssi_tag_state;
+ break;
+
+ default:
+ copy_end = p;
+ looked = 0;
+ state = ssi_start_state;
+ break;
+ }
+
+ break;
+
+ case ssi_sharp_state:
+ switch (ch) {
+ case '#':
+ if (p - ctx->pos < 4) {
+ ctx->saved = 0;
+ }
+ looked = 0;
+ state = ssi_precommand_state;
+ break;
+
+ case '<':
+ copy_end = p;
+ looked = 1;
+ state = ssi_tag_state;
+ break;
+
+ default:
+ copy_end = p;
+ looked = 0;
+ state = ssi_start_state;
+ break;
+ }
+
+ break;
+
+ case ssi_precommand_state:
+ switch (ch) {
+ case ' ':
+ case CR:
+ case LF:
+ case '\t':
+ break;
+
+ default:
+ ctx->command.len = 1;
+ ctx->command.data = ngx_pnalloc(r->pool,
+ NGX_HTTP_SSI_COMMAND_LEN);
+ if (ctx->command.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->command.data[0] = ch;
+
+ ctx->key = 0;
+ ctx->key = ngx_hash(ctx->key, ch);
+
+ ctx->params.nelts = 0;
+
+ state = ssi_command_state;
+ break;
+ }
+
+ break;
+
+ case ssi_command_state:
+ switch (ch) {
+ case ' ':
+ case CR:
+ case LF:
+ case '\t':
+ state = ssi_preparam_state;
+ break;
+
+ case '-':
+ state = ssi_comment_end0_state;
+ break;
+
+ default:
+ if (ctx->command.len == NGX_HTTP_SSI_COMMAND_LEN) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "the \"%V%c...\" SSI command is too long",
+ &ctx->command, ch);
+
+ state = ssi_error_state;
+ break;
+ }
+
+ ctx->command.data[ctx->command.len++] = ch;
+ ctx->key = ngx_hash(ctx->key, ch);
+ }
+
+ break;
+
+ case ssi_preparam_state:
+ switch (ch) {
+ case ' ':
+ case CR:
+ case LF:
+ case '\t':
+ break;
+
+ case '-':
+ state = ssi_comment_end0_state;
+ break;
+
+ default:
+ ctx->param = ngx_array_push(&ctx->params);
+ if (ctx->param == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->param->key.len = 1;
+ ctx->param->key.data = ngx_pnalloc(r->pool,
+ NGX_HTTP_SSI_PARAM_LEN);
+ if (ctx->param->key.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->param->key.data[0] = ch;
+
+ ctx->param->value.len = 0;
+
+ if (ctx->value_buf == NULL) {
+ ctx->param->value.data = ngx_pnalloc(r->pool,
+ ctx->value_len);
+ if (ctx->param->value.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ } else {
+ ctx->param->value.data = ctx->value_buf;
+ }
+
+ state = ssi_param_state;
+ break;
+ }
+
+ break;
+
+ case ssi_param_state:
+ switch (ch) {
+ case ' ':
+ case CR:
+ case LF:
+ case '\t':
+ state = ssi_preequal_state;
+ break;
+
+ case '=':
+ state = ssi_prevalue_state;
+ break;
+
+ case '-':
+ state = ssi_error_end0_state;
+
+ ctx->param->key.data[ctx->param->key.len++] = ch;
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid \"%V\" parameter in \"%V\" SSI command",
+ &ctx->param->key, &ctx->command);
+ break;
+
+ default:
+ if (ctx->param->key.len == NGX_HTTP_SSI_PARAM_LEN) {
+ state = ssi_error_state;
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "too long \"%V%c...\" parameter in "
+ "\"%V\" SSI command",
+ &ctx->param->key, ch, &ctx->command);
+ break;
+ }
+
+ ctx->param->key.data[ctx->param->key.len++] = ch;
+ }
+
+ break;
+
+ case ssi_preequal_state:
+ switch (ch) {
+ case ' ':
+ case CR:
+ case LF:
+ case '\t':
+ break;
+
+ case '=':
+ state = ssi_prevalue_state;
+ break;
+
+ default:
+ if (ch == '-') {
+ state = ssi_error_end0_state;
+ } else {
+ state = ssi_error_state;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "unexpected \"%c\" symbol after \"%V\" "
+ "parameter in \"%V\" SSI command",
+ ch, &ctx->param->key, &ctx->command);
+ break;
+ }
+
+ break;
+
+ case ssi_prevalue_state:
+ switch (ch) {
+ case ' ':
+ case CR:
+ case LF:
+ case '\t':
+ break;
+
+ case '"':
+ state = ssi_double_quoted_value_state;
+ break;
+
+ case '\'':
+ state = ssi_quoted_value_state;
+ break;
+
+ default:
+ if (ch == '-') {
+ state = ssi_error_end0_state;
+ } else {
+ state = ssi_error_state;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "unexpected \"%c\" symbol before value of "
+ "\"%V\" parameter in \"%V\" SSI command",
+ ch, &ctx->param->key, &ctx->command);
+ break;
+ }
+
+ break;
+
+ case ssi_double_quoted_value_state:
+ switch (ch) {
+ case '"':
+ state = ssi_postparam_state;
+ break;
+
+ case '\\':
+ ctx->saved_state = ssi_double_quoted_value_state;
+ state = ssi_quoted_symbol_state;
+
+ /* fall through */
+
+ default:
+ if (ctx->param->value.len == ctx->value_len) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "too long \"%V%c...\" value of \"%V\" "
+ "parameter in \"%V\" SSI command",
+ &ctx->param->value, ch, &ctx->param->key,
+ &ctx->command);
+ state = ssi_error_state;
+ break;
+ }
+
+ ctx->param->value.data[ctx->param->value.len++] = ch;
+ }
+
+ break;
+
+ case ssi_quoted_value_state:
+ switch (ch) {
+ case '\'':
+ state = ssi_postparam_state;
+ break;
+
+ case '\\':
+ ctx->saved_state = ssi_quoted_value_state;
+ state = ssi_quoted_symbol_state;
+
+ /* fall through */
+
+ default:
+ if (ctx->param->value.len == ctx->value_len) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "too long \"%V%c...\" value of \"%V\" "
+ "parameter in \"%V\" SSI command",
+ &ctx->param->value, ch, &ctx->param->key,
+ &ctx->command);
+ state = ssi_error_state;
+ break;
+ }
+
+ ctx->param->value.data[ctx->param->value.len++] = ch;
+ }
+
+ break;
+
+ case ssi_quoted_symbol_state:
+ state = ctx->saved_state;
+
+ ctx->param->value.data[ctx->param->value.len++] = ch;
+
+ break;
+
+ case ssi_postparam_state:
+
+ if (ctx->param->value.len + 1 < ctx->value_len / 2) {
+ value = ngx_pnalloc(r->pool, ctx->param->value.len + 1);
+ if (value == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(value, ctx->param->value.data,
+ ctx->param->value.len);
+
+ ctx->value_buf = ctx->param->value.data;
+ ctx->param->value.data = value;
+
+ } else {
+ ctx->value_buf = NULL;
+ }
+
+ switch (ch) {
+ case ' ':
+ case CR:
+ case LF:
+ case '\t':
+ state = ssi_preparam_state;
+ break;
+
+ case '-':
+ state = ssi_comment_end0_state;
+ break;
+
+ default:
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "unexpected \"%c\" symbol after \"%V\" value "
+ "of \"%V\" parameter in \"%V\" SSI command",
+ ch, &ctx->param->value, &ctx->param->key,
+ &ctx->command);
+ state = ssi_error_state;
+ break;
+ }
+
+ break;
+
+ case ssi_comment_end0_state:
+ switch (ch) {
+ case '-':
+ state = ssi_comment_end1_state;
+ break;
+
+ default:
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "unexpected \"%c\" symbol in \"%V\" SSI command",
+ ch, &ctx->command);
+ state = ssi_error_state;
+ break;
+ }
+
+ break;
+
+ case ssi_comment_end1_state:
+ switch (ch) {
+ case '>':
+ ctx->state = ssi_start_state;
+ ctx->pos = p + 1;
+ ctx->looked = looked;
+ ctx->copy_end = copy_end;
+
+ if (ctx->copy_start == NULL && copy_end) {
+ ctx->copy_start = ctx->buf->pos;
+ }
+
+ return NGX_OK;
+
+ default:
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "unexpected \"%c\" symbol in \"%V\" SSI command",
+ ch, &ctx->command);
+ state = ssi_error_state;
+ break;
+ }
+
+ break;
+
+ case ssi_error_state:
+ switch (ch) {
+ case '-':
+ state = ssi_error_end0_state;
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ case ssi_error_end0_state:
+ switch (ch) {
+ case '-':
+ state = ssi_error_end1_state;
+ break;
+
+ default:
+ state = ssi_error_state;
+ break;
+ }
+
+ break;
+
+ case ssi_error_end1_state:
+ switch (ch) {
+ case '>':
+ ctx->state = ssi_start_state;
+ ctx->pos = p + 1;
+ ctx->looked = looked;
+ ctx->copy_end = copy_end;
+
+ if (ctx->copy_start == NULL && copy_end) {
+ ctx->copy_start = ctx->buf->pos;
+ }
+
+ return NGX_HTTP_SSI_ERROR;
+
+ default:
+ state = ssi_error_state;
+ break;
+ }
+
+ break;
+ }
+ }
+
+ ctx->state = state;
+ ctx->pos = p;
+ ctx->looked = looked;
+
+ ctx->copy_end = (state == ssi_start_state) ? p : copy_end;
+
+ if (ctx->copy_start == NULL && ctx->copy_end) {
+ ctx->copy_start = ctx->buf->pos;
+ }
+
+ return NGX_AGAIN;
+}
+
+
+static ngx_str_t *
+ngx_http_ssi_get_variable(ngx_http_request_t *r, ngx_str_t *name,
+ ngx_uint_t key)
+{
+ ngx_uint_t i;
+ ngx_list_part_t *part;
+ ngx_http_ssi_var_t *var;
+ ngx_http_ssi_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
+
+ if (ctx->variables == NULL) {
+ return NULL;
+ }
+
+ part = &ctx->variables->part;
+ var = part->elts;
+
+ for (i = 0; /* void */ ; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ var = part->elts;
+ i = 0;
+ }
+
+ if (name->len != var[i].name.len) {
+ continue;
+ }
+
+ if (key != var[i].key) {
+ continue;
+ }
+
+ if (ngx_strncmp(name->data, var[i].name.data, name->len) == 0) {
+ return &var[i].value;
+ }
+ }
+
+ return NULL;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_evaluate_string(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t *text, ngx_uint_t flags)
+{
+ u_char ch, *p, **value, *data, *part_data;
+ size_t *size, len, prefix, part_len;
+ ngx_str_t var, *val;
+ ngx_int_t key;
+ ngx_uint_t i, n, bracket, quoted;
+ ngx_array_t lengths, values;
+ ngx_http_variable_value_t *vv;
+
+ n = ngx_http_script_variables_count(text);
+
+ if (n == 0) {
+
+ data = text->data;
+ p = data;
+
+ if ((flags & NGX_HTTP_SSI_ADD_PREFIX) && text->data[0] != '/') {
+
+ for (prefix = r->uri.len; prefix; prefix--) {
+ if (r->uri.data[prefix - 1] == '/') {
+ break;
+ }
+ }
+
+ if (prefix) {
+ len = prefix + text->len;
+
+ data = ngx_pnalloc(r->pool, len);
+ if (data == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_copy(data, r->uri.data, prefix);
+ }
+ }
+
+ quoted = 0;
+
+ for (i = 0; i < text->len; i++) {
+ ch = text->data[i];
+
+ if (!quoted) {
+
+ if (ch == '\\') {
+ quoted = 1;
+ continue;
+ }
+
+ } else {
+ quoted = 0;
+
+ if (ch != '\\' && ch != '\'' && ch != '"' && ch != '$') {
+ *p++ = '\\';
+ }
+ }
+
+ *p++ = ch;
+ }
+
+ text->len = p - data;
+ text->data = data;
+
+ return NGX_OK;
+ }
+
+ if (ngx_array_init(&lengths, r->pool, 8, sizeof(size_t *)) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_array_init(&values, r->pool, 8, sizeof(u_char *)) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ len = 0;
+ i = 0;
+
+ while (i < text->len) {
+
+ if (text->data[i] == '$') {
+
+ var.len = 0;
+
+ if (++i == text->len) {
+ goto invalid_variable;
+ }
+
+ if (text->data[i] == '{') {
+ bracket = 1;
+
+ if (++i == text->len) {
+ goto invalid_variable;
+ }
+
+ var.data = &text->data[i];
+
+ } else {
+ bracket = 0;
+ var.data = &text->data[i];
+ }
+
+ for ( /* void */ ; i < text->len; i++, var.len++) {
+ ch = text->data[i];
+
+ if (ch == '}' && bracket) {
+ i++;
+ bracket = 0;
+ break;
+ }
+
+ if ((ch >= 'A' && ch <= 'Z')
+ || (ch >= 'a' && ch <= 'z')
+ || (ch >= '0' && ch <= '9')
+ || ch == '_')
+ {
+ continue;
+ }
+
+ break;
+ }
+
+ if (bracket) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "the closing bracket in \"%V\" "
+ "variable is missing", &var);
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ if (var.len == 0) {
+ goto invalid_variable;
+ }
+
+ key = ngx_hash_strlow(var.data, var.data, var.len);
+
+ val = ngx_http_ssi_get_variable(r, &var, key);
+
+ if (val == NULL) {
+ vv = ngx_http_get_variable(r, &var, key);
+ if (vv == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (vv->not_found) {
+ continue;
+ }
+
+ part_data = vv->data;
+ part_len = vv->len;
+
+ } else {
+ part_data = val->data;
+ part_len = val->len;
+ }
+
+ } else {
+ part_data = &text->data[i];
+ quoted = 0;
+
+ for (p = part_data; i < text->len; i++) {
+ ch = text->data[i];
+
+ if (!quoted) {
+
+ if (ch == '\\') {
+ quoted = 1;
+ continue;
+ }
+
+ if (ch == '$') {
+ break;
+ }
+
+ } else {
+ quoted = 0;
+
+ if (ch != '\\' && ch != '\'' && ch != '"' && ch != '$') {
+ *p++ = '\\';
+ }
+ }
+
+ *p++ = ch;
+ }
+
+ part_len = p - part_data;
+ }
+
+ len += part_len;
+
+ size = ngx_array_push(&lengths);
+ if (size == NULL) {
+ return NGX_ERROR;
+ }
+
+ *size = part_len;
+
+ value = ngx_array_push(&values);
+ if (value == NULL) {
+ return NGX_ERROR;
+ }
+
+ *value = part_data;
+ }
+
+ prefix = 0;
+
+ size = lengths.elts;
+ value = values.elts;
+
+ if (flags & NGX_HTTP_SSI_ADD_PREFIX) {
+ for (i = 0; i < values.nelts; i++) {
+ if (size[i] != 0) {
+ if (*value[i] != '/') {
+ for (prefix = r->uri.len; prefix; prefix--) {
+ if (r->uri.data[prefix - 1] == '/') {
+ len += prefix;
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+ }
+ }
+
+ p = ngx_pnalloc(r->pool, len + ((flags & NGX_HTTP_SSI_ADD_ZERO) ? 1 : 0));
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ text->len = len;
+ text->data = p;
+
+ p = ngx_copy(p, r->uri.data, prefix);
+
+ for (i = 0; i < values.nelts; i++) {
+ p = ngx_copy(p, value[i], size[i]);
+ }
+
+ return NGX_OK;
+
+invalid_variable:
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid variable name in \"%V\"", text);
+
+ return NGX_HTTP_SSI_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_include(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ u_char *dst, *src;
+ size_t len;
+ ngx_int_t rc, key;
+ ngx_str_t *uri, *file, *wait, *set, *stub, args;
+ ngx_buf_t *b;
+ ngx_uint_t flags, i;
+ ngx_chain_t *cl, *tl, **ll, *out;
+ ngx_http_request_t *sr;
+ ngx_http_ssi_var_t *var;
+ ngx_http_ssi_ctx_t *mctx;
+ ngx_http_ssi_block_t *bl;
+ ngx_http_post_subrequest_t *psr;
+
+ uri = params[NGX_HTTP_SSI_INCLUDE_VIRTUAL];
+ file = params[NGX_HTTP_SSI_INCLUDE_FILE];
+ wait = params[NGX_HTTP_SSI_INCLUDE_WAIT];
+ set = params[NGX_HTTP_SSI_INCLUDE_SET];
+ stub = params[NGX_HTTP_SSI_INCLUDE_STUB];
+
+ if (uri && file) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "inlcusion may be either virtual=\"%V\" or file=\"%V\"",
+ uri, file);
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ if (uri == NULL && file == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "no parameter in \"include\" SSI command");
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ if (set && stub) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "\"set\" and \"stub\" may not be used together "
+ "in \"include\" SSI command");
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ if (wait) {
+ if (uri == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "\"wait\" may not be used with file=\"%V\"", file);
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ if (wait->len == 2
+ && ngx_strncasecmp(wait->data, (u_char *) "no", 2) == 0)
+ {
+ wait = NULL;
+
+ } else if (wait->len != 3
+ || ngx_strncasecmp(wait->data, (u_char *) "yes", 3) != 0)
+ {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid value \"%V\" in the \"wait\" parameter",
+ wait);
+ return NGX_HTTP_SSI_ERROR;
+ }
+ }
+
+ if (uri == NULL) {
+ uri = file;
+ wait = (ngx_str_t *) -1;
+ }
+
+ rc = ngx_http_ssi_evaluate_string(r, ctx, uri, NGX_HTTP_SSI_ADD_PREFIX);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ dst = uri->data;
+ src = uri->data;
+
+ ngx_unescape_uri(&dst, &src, uri->len, NGX_UNESCAPE_URI);
+
+ len = (uri->data + uri->len) - src;
+ if (len) {
+ dst = ngx_movemem(dst, src, len);
+ }
+
+ uri->len = dst - uri->data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi include: \"%V\"", uri);
+
+ ngx_str_null(&args);
+ flags = NGX_HTTP_LOG_UNSAFE;
+
+ if (ngx_http_parse_unsafe_uri(r, uri, &args, &flags) != NGX_OK) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ psr = NULL;
+
+ mctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
+
+ if (stub) {
+ if (mctx->blocks) {
+ bl = mctx->blocks->elts;
+ for (i = 0; i < mctx->blocks->nelts; i++) {
+ if (stub->len == bl[i].name.len
+ && ngx_strncmp(stub->data, bl[i].name.data, stub->len) == 0)
+ {
+ goto found;
+ }
+ }
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "\"stub\"=\"%V\" for \"include\" not found", stub);
+ return NGX_HTTP_SSI_ERROR;
+
+ found:
+
+ psr = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
+ if (psr == NULL) {
+ return NGX_ERROR;
+ }
+
+ psr->handler = ngx_http_ssi_stub_output;
+
+ if (bl[i].count++) {
+
+ out = NULL;
+ ll = &out;
+
+ for (tl = bl[i].bufs; tl; tl = tl->next) {
+
+ if (ctx->free) {
+ cl = ctx->free;
+ ctx->free = ctx->free->next;
+ b = cl->buf;
+
+ } else {
+ b = ngx_alloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ }
+
+ ngx_memcpy(b, tl->buf, sizeof(ngx_buf_t));
+
+ b->pos = b->start;
+
+ *ll = cl;
+ cl->next = NULL;
+ ll = &cl->next;
+ }
+
+ psr->data = out;
+
+ } else {
+ psr->data = bl[i].bufs;
+ }
+ }
+
+ if (wait) {
+ flags |= NGX_HTTP_SUBREQUEST_WAITED;
+ }
+
+ if (set) {
+ key = ngx_hash_strlow(set->data, set->data, set->len);
+
+ psr = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
+ if (psr == NULL) {
+ return NGX_ERROR;
+ }
+
+ psr->handler = ngx_http_ssi_set_variable;
+ psr->data = ngx_http_ssi_get_variable(r, set, key);
+
+ if (psr->data == NULL) {
+
+ if (mctx->variables == NULL) {
+ mctx->variables = ngx_list_create(r->pool, 4,
+ sizeof(ngx_http_ssi_var_t));
+ if (mctx->variables == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ var = ngx_list_push(mctx->variables);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->name = *set;
+ var->key = key;
+ var->value = ngx_http_ssi_null_string;
+ psr->data = &var->value;
+ }
+
+ flags |= NGX_HTTP_SUBREQUEST_IN_MEMORY|NGX_HTTP_SUBREQUEST_WAITED;
+ }
+
+ if (ngx_http_subrequest(r, uri, &args, &sr, psr, flags) != NGX_OK) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ if (wait == NULL && set == NULL) {
+ return NGX_OK;
+ }
+
+ if (ctx->wait == NULL) {
+ ctx->wait = sr;
+
+ return NGX_AGAIN;
+
+ } else {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "only one subrequest may be waited at the same time");
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_stub_output(ngx_http_request_t *r, void *data, ngx_int_t rc)
+{
+ ngx_chain_t *out;
+
+ if (rc == NGX_ERROR || r->connection->error || r->request_output) {
+ return rc;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi stub output: \"%V?%V\"", &r->uri, &r->args);
+
+ out = data;
+
+ if (!r->header_sent) {
+ r->headers_out.content_type_len =
+ r->parent->headers_out.content_type_len;
+ r->headers_out.content_type = r->parent->headers_out.content_type;
+
+ if (ngx_http_send_header(r) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+
+ return ngx_http_output_filter(r, out);
+}
+
+
+static ngx_int_t
+ngx_http_ssi_set_variable(ngx_http_request_t *r, void *data, ngx_int_t rc)
+{
+ ngx_str_t *value = data;
+
+ if (r->upstream) {
+ value->len = r->upstream->buffer.last - r->upstream->buffer.pos;
+ value->data = r->upstream->buffer.pos;
+ }
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_echo(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ u_char *p;
+ uintptr_t len;
+ ngx_int_t key;
+ ngx_buf_t *b;
+ ngx_str_t *var, *value, *enc, text;
+ ngx_chain_t *cl;
+ ngx_http_variable_value_t *vv;
+
+ var = params[NGX_HTTP_SSI_ECHO_VAR];
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi echo \"%V\"", var);
+
+ key = ngx_hash_strlow(var->data, var->data, var->len);
+
+ value = ngx_http_ssi_get_variable(r, var, key);
+
+ if (value == NULL) {
+ vv = ngx_http_get_variable(r, var, key);
+
+ if (vv == NULL) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ if (!vv->not_found) {
+ text.data = vv->data;
+ text.len = vv->len;
+ value = &text;
+ }
+ }
+
+ if (value == NULL) {
+ value = params[NGX_HTTP_SSI_ECHO_DEFAULT];
+
+ if (value == NULL) {
+ value = &ngx_http_ssi_none;
+
+ } else if (value->len == 0) {
+ return NGX_OK;
+ }
+
+ } else {
+ if (value->len == 0) {
+ return NGX_OK;
+ }
+ }
+
+ enc = params[NGX_HTTP_SSI_ECHO_ENCODING];
+
+ if (enc) {
+ if (enc->len == 4 && ngx_strncmp(enc->data, "none", 4) == 0) {
+
+ ctx->encoding = NGX_HTTP_SSI_NO_ENCODING;
+
+ } else if (enc->len == 3 && ngx_strncmp(enc->data, "url", 3) == 0) {
+
+ ctx->encoding = NGX_HTTP_SSI_URL_ENCODING;
+
+ } else if (enc->len == 6 && ngx_strncmp(enc->data, "entity", 6) == 0) {
+
+ ctx->encoding = NGX_HTTP_SSI_ENTITY_ENCODING;
+
+ } else {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "unknown encoding \"%V\" in the \"echo\" command",
+ enc);
+ }
+ }
+
+ p = value->data;
+
+ switch (ctx->encoding) {
+
+ case NGX_HTTP_SSI_URL_ENCODING:
+ len = 2 * ngx_escape_uri(NULL, value->data, value->len,
+ NGX_ESCAPE_HTML);
+
+ if (len) {
+ p = ngx_pnalloc(r->pool, value->len + len);
+ if (p == NULL) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ (void) ngx_escape_uri(p, value->data, value->len, NGX_ESCAPE_HTML);
+ }
+
+ len += value->len;
+ break;
+
+ case NGX_HTTP_SSI_ENTITY_ENCODING:
+ len = ngx_escape_html(NULL, value->data, value->len);
+
+ if (len) {
+ p = ngx_pnalloc(r->pool, value->len + len);
+ if (p == NULL) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ (void) ngx_escape_html(p, value->data, value->len);
+ }
+
+ len += value->len;
+ break;
+
+ default: /* NGX_HTTP_SSI_NO_ENCODING */
+ len = value->len;
+ break;
+ }
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ b->memory = 1;
+ b->pos = p;
+ b->last = p + len;
+
+ cl->buf = b;
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_config(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ ngx_str_t *value;
+
+ value = params[NGX_HTTP_SSI_CONFIG_TIMEFMT];
+
+ if (value) {
+ ctx->timefmt.len = value->len;
+ ctx->timefmt.data = ngx_pnalloc(r->pool, value->len + 1);
+ if (ctx->timefmt.data == NULL) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ ngx_cpystrn(ctx->timefmt.data, value->data, value->len + 1);
+ }
+
+ value = params[NGX_HTTP_SSI_CONFIG_ERRMSG];
+
+ if (value) {
+ ctx->errmsg = *value;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_set(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ ngx_int_t key, rc;
+ ngx_str_t *name, *value, *vv;
+ ngx_http_ssi_var_t *var;
+ ngx_http_ssi_ctx_t *mctx;
+
+ mctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
+
+ if (mctx->variables == NULL) {
+ mctx->variables = ngx_list_create(r->pool, 4,
+ sizeof(ngx_http_ssi_var_t));
+ if (mctx->variables == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ name = params[NGX_HTTP_SSI_SET_VAR];
+ value = params[NGX_HTTP_SSI_SET_VALUE];
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi set \"%V\" \"%V\"", name, value);
+
+ rc = ngx_http_ssi_evaluate_string(r, ctx, value, 0);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ key = ngx_hash_strlow(name->data, name->data, name->len);
+
+ vv = ngx_http_ssi_get_variable(r, name, key);
+
+ if (vv) {
+ *vv = *value;
+ return NGX_OK;
+ }
+
+ var = ngx_list_push(mctx->variables);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->name = *name;
+ var->key = key;
+ var->value = *value;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "set: \"%V\"=\"%V\"", name, value);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_if(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ u_char *p, *last;
+ ngx_str_t *expr, left, right;
+ ngx_int_t rc;
+ ngx_uint_t negative, noregex, flags;
+
+ if (ctx->command.len == 2) {
+ if (ctx->conditional) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "the \"if\" command inside the \"if\" command");
+ return NGX_HTTP_SSI_ERROR;
+ }
+ }
+
+ if (ctx->output_chosen) {
+ ctx->output = 0;
+ return NGX_OK;
+ }
+
+ expr = params[NGX_HTTP_SSI_IF_EXPR];
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi if expr=\"%V\"", expr);
+
+ left.data = expr->data;
+ last = expr->data + expr->len;
+
+ for (p = left.data; p < last; p++) {
+ if (*p >= 'A' && *p <= 'Z') {
+ *p |= 0x20;
+ continue;
+ }
+
+ if ((*p >= 'a' && *p <= 'z')
+ || (*p >= '0' && *p <= '9')
+ || *p == '$' || *p == '{' || *p == '}' || *p == '_'
+ || *p == '"' || *p == '\'')
+ {
+ continue;
+ }
+
+ break;
+ }
+
+ left.len = p - left.data;
+
+ while (p < last && *p == ' ') {
+ p++;
+ }
+
+ flags = 0;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "left: \"%V\"", &left);
+
+ rc = ngx_http_ssi_evaluate_string(r, ctx, &left, flags);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "evaluted left: \"%V\"", &left);
+
+ if (p == last) {
+ if (left.len) {
+ ctx->output = 1;
+ ctx->output_chosen = 1;
+
+ } else {
+ ctx->output = 0;
+ }
+
+ ctx->conditional = NGX_HTTP_SSI_COND_IF;
+
+ return NGX_OK;
+ }
+
+ if (p < last && *p == '=') {
+ negative = 0;
+ p++;
+
+ } else if (p + 1 < last && *p == '!' && *(p + 1) == '=') {
+ negative = 1;
+ p += 2;
+
+ } else {
+ goto invalid_expression;
+ }
+
+ while (p < last && *p == ' ') {
+ p++;
+ }
+
+ if (p < last - 1 && *p == '/') {
+ if (*(last - 1) != '/') {
+ goto invalid_expression;
+ }
+
+ noregex = 0;
+ flags = NGX_HTTP_SSI_ADD_ZERO;
+ last--;
+ p++;
+
+ } else {
+ noregex = 1;
+ flags = 0;
+
+ if (p < last - 1 && p[0] == '\\' && p[1] == '/') {
+ p++;
+ }
+ }
+
+ right.len = last - p;
+ right.data = p;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "right: \"%V\"", &right);
+
+ rc = ngx_http_ssi_evaluate_string(r, ctx, &right, flags);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "evaluted right: \"%V\"", &right);
+
+ if (noregex) {
+ if (left.len != right.len) {
+ rc = -1;
+
+ } else {
+ rc = ngx_strncmp(left.data, right.data, right.len);
+ }
+
+ } else {
+#if (NGX_PCRE)
+ ngx_regex_compile_t rgc;
+ u_char errstr[NGX_MAX_CONF_ERRSTR];
+
+ right.data[right.len] = '\0';
+
+ ngx_memzero(&rgc, sizeof(ngx_regex_compile_t));
+
+ rgc.pattern = right;
+ rgc.pool = r->pool;
+ rgc.err.len = NGX_MAX_CONF_ERRSTR;
+ rgc.err.data = errstr;
+
+ if (ngx_regex_compile(&rgc) != NGX_OK) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "%V", &rgc.err);
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ rc = ngx_regex_exec(rgc.regex, &left, NULL, 0);
+
+ if (rc < NGX_REGEX_NO_MATCHED) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ ngx_regex_exec_n " failed: %i on \"%V\" using \"%V\"",
+ rc, &left, &right);
+ return NGX_HTTP_SSI_ERROR;
+ }
+#else
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "the using of the regex \"%V\" in SSI "
+ "requires PCRE library", &right);
+
+ return NGX_HTTP_SSI_ERROR;
+#endif
+ }
+
+ if ((rc == 0 && !negative) || (rc != 0 && negative)) {
+ ctx->output = 1;
+ ctx->output_chosen = 1;
+
+ } else {
+ ctx->output = 0;
+ }
+
+ ctx->conditional = NGX_HTTP_SSI_COND_IF;
+
+ return NGX_OK;
+
+invalid_expression:
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid expression in \"%V\"", expr);
+
+ return NGX_HTTP_SSI_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_else(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi else");
+
+ if (ctx->output_chosen) {
+ ctx->output = 0;
+ } else {
+ ctx->output = 1;
+ }
+
+ ctx->conditional = NGX_HTTP_SSI_COND_ELSE;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_endif(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi endif");
+
+ ctx->output = 1;
+ ctx->output_chosen = 0;
+ ctx->conditional = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_block(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ ngx_http_ssi_ctx_t *mctx;
+ ngx_http_ssi_block_t *bl;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi block");
+
+ mctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
+
+ if (mctx->blocks == NULL) {
+ mctx->blocks = ngx_array_create(r->pool, 4,
+ sizeof(ngx_http_ssi_block_t));
+ if (mctx->blocks == NULL) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+ }
+
+ bl = ngx_array_push(mctx->blocks);
+ if (bl == NULL) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ bl->name = *params[NGX_HTTP_SSI_BLOCK_NAME];
+ bl->bufs = NULL;
+ bl->count = 0;
+
+ ctx->output = 0;
+ ctx->block = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_endblock(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi endblock");
+
+ ctx->output = 1;
+ ctx->block = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_date_gmt_local_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t gmt)
+{
+ ngx_http_ssi_ctx_t *ctx;
+ ngx_time_t *tp;
+ struct tm tm;
+ char buf[NGX_HTTP_SSI_DATE_LEN];
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ tp = ngx_timeofday();
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_ssi_filter_module);
+
+ if (ctx == NULL
+ || (ctx->timefmt.len == sizeof("%s") - 1
+ && ctx->timefmt.data[0] == '%' && ctx->timefmt.data[1] == 's'))
+ {
+ v->data = ngx_pnalloc(r->pool, NGX_TIME_T_LEN);
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = ngx_sprintf(v->data, "%T", tp->sec) - v->data;
+
+ return NGX_OK;
+ }
+
+ if (gmt) {
+ ngx_libc_gmtime(tp->sec, &tm);
+ } else {
+ ngx_libc_localtime(tp->sec, &tm);
+ }
+
+ v->len = strftime(buf, NGX_HTTP_SSI_DATE_LEN,
+ (char *) ctx->timefmt.data, &tm);
+ if (v->len == 0) {
+ return NGX_ERROR;
+ }
+
+ v->data = ngx_pnalloc(r->pool, v->len);
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(v->data, buf, v->len);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_preconfiguration(ngx_conf_t *cf)
+{
+ ngx_int_t rc;
+ ngx_http_variable_t *var, *v;
+ ngx_http_ssi_command_t *cmd;
+ ngx_http_ssi_main_conf_t *smcf;
+
+ for (v = ngx_http_ssi_vars; v->name.len; v++) {
+ var = ngx_http_add_variable(cf, &v->name, v->flags);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = v->get_handler;
+ var->data = v->data;
+ }
+
+ smcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_ssi_filter_module);
+
+ for (cmd = ngx_http_ssi_commands; cmd->name.len; cmd++) {
+ rc = ngx_hash_add_key(&smcf->commands, &cmd->name, cmd,
+ NGX_HASH_READONLY_KEY);
+
+ if (rc == NGX_OK) {
+ continue;
+ }
+
+ if (rc == NGX_BUSY) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "conflicting SSI command \"%V\"", &cmd->name);
+ }
+
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_ssi_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_ssi_main_conf_t *smcf;
+
+ smcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssi_main_conf_t));
+ if (smcf == NULL) {
+ return NULL;
+ }
+
+ smcf->commands.pool = cf->pool;
+ smcf->commands.temp_pool = cf->temp_pool;
+
+ if (ngx_hash_keys_array_init(&smcf->commands, NGX_HASH_SMALL) != NGX_OK) {
+ return NULL;
+ }
+
+ return smcf;
+}
+
+
+static char *
+ngx_http_ssi_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+ ngx_http_ssi_main_conf_t *smcf = conf;
+
+ ngx_hash_init_t hash;
+
+ hash.hash = &smcf->hash;
+ hash.key = ngx_hash_key;
+ hash.max_size = 1024;
+ hash.bucket_size = ngx_cacheline_size;
+ hash.name = "ssi_command_hash";
+ hash.pool = cf->pool;
+ hash.temp_pool = NULL;
+
+ if (ngx_hash_init(&hash, smcf->commands.keys.elts,
+ smcf->commands.keys.nelts)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_ssi_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_ssi_loc_conf_t *slcf;
+
+ slcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssi_loc_conf_t));
+ if (slcf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->types = { NULL };
+ * conf->types_keys = NULL;
+ */
+
+ slcf->enable = NGX_CONF_UNSET;
+ slcf->silent_errors = NGX_CONF_UNSET;
+ slcf->ignore_recycled_buffers = NGX_CONF_UNSET;
+
+ slcf->min_file_chunk = NGX_CONF_UNSET_SIZE;
+ slcf->value_len = NGX_CONF_UNSET_SIZE;
+
+ return slcf;
+}
+
+
+static char *
+ngx_http_ssi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_ssi_loc_conf_t *prev = parent;
+ ngx_http_ssi_loc_conf_t *conf = child;
+
+ ngx_conf_merge_value(conf->enable, prev->enable, 0);
+ ngx_conf_merge_value(conf->silent_errors, prev->silent_errors, 0);
+ ngx_conf_merge_value(conf->ignore_recycled_buffers,
+ prev->ignore_recycled_buffers, 0);
+
+ ngx_conf_merge_size_value(conf->min_file_chunk, prev->min_file_chunk, 1024);
+ ngx_conf_merge_size_value(conf->value_len, prev->value_len, 256);
+
+ if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
+ &prev->types_keys, &prev->types,
+ ngx_http_html_default_types)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_ssi_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_ssi_body_filter;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_ssi_filter_module.h b/usr.sbin/nginx/src/http/modules/ngx_http_ssi_filter_module.h
new file mode 100644
index 00000000000..6ab18841c82
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_ssi_filter_module.h
@@ -0,0 +1,107 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_SSI_FILTER_H_INCLUDED_
+#define _NGX_HTTP_SSI_FILTER_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_SSI_MAX_PARAMS 16
+
+#define NGX_HTTP_SSI_COMMAND_LEN 32
+#define NGX_HTTP_SSI_PARAM_LEN 32
+#define NGX_HTTP_SSI_PARAMS_N 4
+
+
+#define NGX_HTTP_SSI_COND_IF 1
+#define NGX_HTTP_SSI_COND_ELSE 2
+
+
+#define NGX_HTTP_SSI_NO_ENCODING 0
+#define NGX_HTTP_SSI_URL_ENCODING 1
+#define NGX_HTTP_SSI_ENTITY_ENCODING 2
+
+
+typedef struct {
+ ngx_hash_t hash;
+ ngx_hash_keys_arrays_t commands;
+} ngx_http_ssi_main_conf_t;
+
+
+typedef struct {
+ ngx_buf_t *buf;
+
+ u_char *pos;
+ u_char *copy_start;
+ u_char *copy_end;
+
+ ngx_uint_t key;
+ ngx_str_t command;
+ ngx_array_t params;
+ ngx_table_elt_t *param;
+ ngx_table_elt_t params_array[NGX_HTTP_SSI_PARAMS_N];
+
+ ngx_chain_t *in;
+ ngx_chain_t *out;
+ ngx_chain_t **last_out;
+ ngx_chain_t *busy;
+ ngx_chain_t *free;
+
+ ngx_uint_t state;
+ ngx_uint_t saved_state;
+ size_t saved;
+ size_t looked;
+
+ size_t value_len;
+
+ ngx_list_t *variables;
+ ngx_array_t *blocks;
+
+ unsigned conditional:2;
+ unsigned encoding:2;
+ unsigned block:1;
+ unsigned output:1;
+ unsigned output_chosen:1;
+
+ ngx_http_request_t *wait;
+ void *value_buf;
+ ngx_str_t timefmt;
+ ngx_str_t errmsg;
+} ngx_http_ssi_ctx_t;
+
+
+typedef ngx_int_t (*ngx_http_ssi_command_pt) (ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ctx, ngx_str_t **);
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_uint_t index;
+
+ unsigned mandatory:1;
+ unsigned multiple:1;
+} ngx_http_ssi_param_t;
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_http_ssi_command_pt handler;
+ ngx_http_ssi_param_t *params;
+
+ unsigned conditional:2;
+ unsigned block:1;
+ unsigned flush:1;
+} ngx_http_ssi_command_t;
+
+
+extern ngx_module_t ngx_http_ssi_filter_module;
+
+
+#endif /* _NGX_HTTP_SSI_FILTER_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_ssl_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_ssl_module.c
new file mode 100644
index 00000000000..120a858dfc5
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_ssl_module.c
@@ -0,0 +1,637 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c,
+ ngx_pool_t *pool, ngx_str_t *s);
+
+
+#define NGX_DEFAULT_CIPHERS "HIGH:!aNULL:!MD5"
+#define NGX_DEFAULT_ECDH_CURVE "prime256v1"
+
+
+static ngx_int_t ngx_http_ssl_static_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_ssl_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+
+static ngx_int_t ngx_http_ssl_add_variables(ngx_conf_t *cf);
+static void *ngx_http_ssl_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+
+static char *ngx_http_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_conf_bitmask_t ngx_http_ssl_protocols[] = {
+ { ngx_string("SSLv2"), NGX_SSL_SSLv2 },
+ { ngx_string("SSLv3"), NGX_SSL_SSLv3 },
+ { ngx_string("TLSv1"), NGX_SSL_TLSv1 },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_enum_t ngx_http_ssl_verify[] = {
+ { ngx_string("off"), 0 },
+ { ngx_string("on"), 1 },
+ { ngx_string("optional"), 2 },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t ngx_http_ssl_commands[] = {
+
+ { ngx_string("ssl"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_http_ssl_enable,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, enable),
+ NULL },
+
+ { ngx_string("ssl_certificate"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, certificate),
+ NULL },
+
+ { ngx_string("ssl_certificate_key"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, certificate_key),
+ NULL },
+
+ { ngx_string("ssl_dhparam"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, dhparam),
+ NULL },
+
+ { ngx_string("ssl_ecdh_curve"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, ecdh_curve),
+ NULL },
+
+ { ngx_string("ssl_protocols"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, protocols),
+ &ngx_http_ssl_protocols },
+
+ { ngx_string("ssl_ciphers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, ciphers),
+ NULL },
+
+ { ngx_string("ssl_verify_client"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, verify),
+ &ngx_http_ssl_verify },
+
+ { ngx_string("ssl_verify_depth"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, verify_depth),
+ NULL },
+
+ { ngx_string("ssl_client_certificate"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, client_certificate),
+ NULL },
+
+ { ngx_string("ssl_prefer_server_ciphers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, prefer_server_ciphers),
+ NULL },
+
+ { ngx_string("ssl_session_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE12,
+ ngx_http_ssl_session_cache,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("ssl_session_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_sec_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, session_timeout),
+ NULL },
+
+ { ngx_string("ssl_crl"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, crl),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_ssl_module_ctx = {
+ ngx_http_ssl_add_variables, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_http_ssl_create_srv_conf, /* create server configuration */
+ ngx_http_ssl_merge_srv_conf, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_ssl_module = {
+ NGX_MODULE_V1,
+ &ngx_http_ssl_module_ctx, /* module context */
+ ngx_http_ssl_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_variable_t ngx_http_ssl_vars[] = {
+
+ { ngx_string("ssl_protocol"), NULL, ngx_http_ssl_static_variable,
+ (uintptr_t) ngx_ssl_get_protocol, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_cipher"), NULL, ngx_http_ssl_static_variable,
+ (uintptr_t) ngx_ssl_get_cipher_name, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_session_id"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_session_id, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_cert"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_certificate, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_raw_cert"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_raw_certificate,
+ NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_s_dn"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_subject_dn, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_i_dn"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_issuer_dn, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_serial"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_serial_number, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_verify"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_client_verify, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+
+static ngx_str_t ngx_http_ssl_sess_id_ctx = ngx_string("HTTP");
+
+
+static ngx_int_t
+ngx_http_ssl_static_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_ssl_variable_handler_pt handler = (ngx_ssl_variable_handler_pt) data;
+
+ size_t len;
+ ngx_str_t s;
+
+ if (r->connection->ssl) {
+
+ (void) handler(r->connection, NULL, &s);
+
+ v->data = s.data;
+
+ for (len = 0; v->data[len]; len++) { /* void */ }
+
+ v->len = len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ return NGX_OK;
+ }
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssl_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_ssl_variable_handler_pt handler = (ngx_ssl_variable_handler_pt) data;
+
+ ngx_str_t s;
+
+ if (r->connection->ssl) {
+
+ if (handler(r->connection, r->pool, &s) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ v->len = s.len;
+ v->data = s.data;
+
+ if (v->len) {
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ return NGX_OK;
+ }
+ }
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssl_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var, *v;
+
+ for (v = ngx_http_ssl_vars; v->name.len; v++) {
+ var = ngx_http_add_variable(cf, &v->name, v->flags);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = v->get_handler;
+ var->data = v->data;
+ }
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_ssl_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_http_ssl_srv_conf_t *sscf;
+
+ sscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssl_srv_conf_t));
+ if (sscf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * sscf->protocols = 0;
+ * sscf->certificate = { 0, NULL };
+ * sscf->certificate_key = { 0, NULL };
+ * sscf->dhparam = { 0, NULL };
+ * sscf->ecdh_curve = { 0, NULL };
+ * sscf->client_certificate = { 0, NULL };
+ * sscf->crl = { 0, NULL };
+ * sscf->ciphers = { 0, NULL };
+ * sscf->shm_zone = NULL;
+ */
+
+ sscf->enable = NGX_CONF_UNSET;
+ sscf->prefer_server_ciphers = NGX_CONF_UNSET;
+ sscf->verify = NGX_CONF_UNSET_UINT;
+ sscf->verify_depth = NGX_CONF_UNSET_UINT;
+ sscf->builtin_session_cache = NGX_CONF_UNSET;
+ sscf->session_timeout = NGX_CONF_UNSET;
+
+ return sscf;
+}
+
+
+static char *
+ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_ssl_srv_conf_t *prev = parent;
+ ngx_http_ssl_srv_conf_t *conf = child;
+
+ ngx_pool_cleanup_t *cln;
+
+ ngx_conf_merge_value(conf->enable, prev->enable, 0);
+
+ ngx_conf_merge_value(conf->session_timeout,
+ prev->session_timeout, 300);
+
+ ngx_conf_merge_value(conf->prefer_server_ciphers,
+ prev->prefer_server_ciphers, 0);
+
+ ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols,
+ (NGX_CONF_BITMASK_SET|NGX_SSL_SSLv3|NGX_SSL_TLSv1));
+
+ ngx_conf_merge_uint_value(conf->verify, prev->verify, 0);
+ ngx_conf_merge_uint_value(conf->verify_depth, prev->verify_depth, 1);
+
+ ngx_conf_merge_str_value(conf->certificate, prev->certificate, "");
+ ngx_conf_merge_str_value(conf->certificate_key, prev->certificate_key, "");
+
+ ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, "");
+
+ ngx_conf_merge_str_value(conf->client_certificate, prev->client_certificate,
+ "");
+ ngx_conf_merge_str_value(conf->crl, prev->crl, "");
+
+ ngx_conf_merge_str_value(conf->ecdh_curve, prev->ecdh_curve,
+ NGX_DEFAULT_ECDH_CURVE);
+
+ ngx_conf_merge_str_value(conf->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS);
+
+
+ conf->ssl.log = cf->log;
+
+ if (conf->enable) {
+
+ if (conf->certificate.len == 0) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate\" is defined for "
+ "the \"ssl\" directive in %s:%ui",
+ conf->file, conf->line);
+ return NGX_CONF_ERROR;
+ }
+
+ if (conf->certificate_key.len == 0) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate_key\" is defined for "
+ "the \"ssl\" directive in %s:%ui",
+ conf->file, conf->line);
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+
+ if (conf->certificate.len == 0) {
+ return NGX_CONF_OK;
+ }
+
+ if (conf->certificate_key.len == 0) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate_key\" is defined "
+ "for certificate \"%V\"", &conf->certificate);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (ngx_ssl_create(&conf->ssl, conf->protocols, conf) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+
+ if (SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx,
+ ngx_http_ssl_servername)
+ == 0)
+ {
+ ngx_log_error(NGX_LOG_WARN, cf->log, 0,
+ "nginx was built with SNI support, however, now it is linked "
+ "dynamically to an OpenSSL library which has no tlsext support, "
+ "therefore SNI is not available");
+ }
+
+#endif
+
+ cln = ngx_pool_cleanup_add(cf->pool, 0);
+ if (cln == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ cln->handler = ngx_ssl_cleanup_ctx;
+ cln->data = &conf->ssl;
+
+ if (ngx_ssl_certificate(cf, &conf->ssl, &conf->certificate,
+ &conf->certificate_key)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ if (SSL_CTX_set_cipher_list(conf->ssl.ctx,
+ (const char *) conf->ciphers.data)
+ == 0)
+ {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "SSL_CTX_set_cipher_list(\"%V\") failed",
+ &conf->ciphers);
+ }
+
+ if (conf->verify) {
+
+ if (conf->client_certificate.len == 0) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no ssl_client_certificate for ssl_client_verify");
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_ssl_client_certificate(cf, &conf->ssl,
+ &conf->client_certificate,
+ conf->verify_depth)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_ssl_crl(cf, &conf->ssl, &conf->crl) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (conf->prefer_server_ciphers) {
+ SSL_CTX_set_options(conf->ssl.ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
+ }
+
+ /* a temporary 512-bit RSA key is required for export versions of MSIE */
+ SSL_CTX_set_tmp_rsa_callback(conf->ssl.ctx, ngx_ssl_rsa512_key_callback);
+
+ if (ngx_ssl_dhparam(cf, &conf->ssl, &conf->dhparam) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_ssl_ecdh_curve(cf, &conf->ssl, &conf->ecdh_curve) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_value(conf->builtin_session_cache,
+ prev->builtin_session_cache, NGX_SSL_NONE_SCACHE);
+
+ if (conf->shm_zone == NULL) {
+ conf->shm_zone = prev->shm_zone;
+ }
+
+ if (ngx_ssl_session_cache(&conf->ssl, &ngx_http_ssl_sess_id_ctx,
+ conf->builtin_session_cache,
+ conf->shm_zone, conf->session_timeout)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_ssl_srv_conf_t *sscf = conf;
+
+ char *rv;
+
+ rv = ngx_conf_set_flag_slot(cf, cmd, conf);
+
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+
+ sscf->file = cf->conf_file->file.name.data;
+ sscf->line = cf->conf_file->line;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_ssl_srv_conf_t *sscf = conf;
+
+ size_t len;
+ ngx_str_t *value, name, size;
+ ngx_int_t n;
+ ngx_uint_t i, j;
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+
+ if (ngx_strcmp(value[i].data, "off") == 0) {
+ sscf->builtin_session_cache = NGX_SSL_NO_SCACHE;
+ continue;
+ }
+
+ if (ngx_strcmp(value[i].data, "none") == 0) {
+ sscf->builtin_session_cache = NGX_SSL_NONE_SCACHE;
+ continue;
+ }
+
+ if (ngx_strcmp(value[i].data, "builtin") == 0) {
+ sscf->builtin_session_cache = NGX_SSL_DFLT_BUILTIN_SCACHE;
+ continue;
+ }
+
+ if (value[i].len > sizeof("builtin:") - 1
+ && ngx_strncmp(value[i].data, "builtin:", sizeof("builtin:") - 1)
+ == 0)
+ {
+ n = ngx_atoi(value[i].data + sizeof("builtin:") - 1,
+ value[i].len - (sizeof("builtin:") - 1));
+
+ if (n == NGX_ERROR) {
+ goto invalid;
+ }
+
+ sscf->builtin_session_cache = n;
+
+ continue;
+ }
+
+ if (value[i].len > sizeof("shared:") - 1
+ && ngx_strncmp(value[i].data, "shared:", sizeof("shared:") - 1)
+ == 0)
+ {
+ len = 0;
+
+ for (j = sizeof("shared:") - 1; j < value[i].len; j++) {
+ if (value[i].data[j] == ':') {
+ value[i].data[j] = '\0';
+ break;
+ }
+
+ len++;
+ }
+
+ if (len == 0) {
+ goto invalid;
+ }
+
+ name.len = len;
+ name.data = value[i].data + sizeof("shared:") - 1;
+
+ size.len = value[i].len - j - 1;
+ size.data = name.data + len + 1;
+
+ n = ngx_parse_size(&size);
+
+ if (n == NGX_ERROR) {
+ goto invalid;
+ }
+
+ if (n < (ngx_int_t) (8 * ngx_pagesize)) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "session cache \"%V\" is too small",
+ &value[i]);
+
+ return NGX_CONF_ERROR;
+ }
+
+ sscf->shm_zone = ngx_shared_memory_add(cf, &name, n,
+ &ngx_http_ssl_module);
+ if (sscf->shm_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ goto invalid;
+ }
+
+ if (sscf->shm_zone && sscf->builtin_session_cache == NGX_CONF_UNSET) {
+ sscf->builtin_session_cache = NGX_SSL_NO_BUILTIN_SCACHE;
+ }
+
+ return NGX_CONF_OK;
+
+invalid:
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid session cache \"%V\"", &value[i]);
+
+ return NGX_CONF_ERROR;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_ssl_module.h b/usr.sbin/nginx/src/http/modules/ngx_http_ssl_module.h
new file mode 100644
index 00000000000..0a5dd1d8d35
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_ssl_module.h
@@ -0,0 +1,51 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_SSL_H_INCLUDED_
+#define _NGX_HTTP_SSL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_flag_t enable;
+
+ ngx_ssl_t ssl;
+
+ ngx_flag_t prefer_server_ciphers;
+
+ ngx_uint_t protocols;
+
+ ngx_uint_t verify;
+ ngx_uint_t verify_depth;
+
+ ssize_t builtin_session_cache;
+
+ time_t session_timeout;
+
+ ngx_str_t certificate;
+ ngx_str_t certificate_key;
+ ngx_str_t dhparam;
+ ngx_str_t ecdh_curve;
+ ngx_str_t client_certificate;
+ ngx_str_t crl;
+
+ ngx_str_t ciphers;
+
+ ngx_shm_zone_t *shm_zone;
+
+ u_char *file;
+ ngx_uint_t line;
+} ngx_http_ssl_srv_conf_t;
+
+
+extern ngx_module_t ngx_http_ssl_module;
+
+
+#endif /* _NGX_HTTP_SSL_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_static_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_static_module.c
new file mode 100644
index 00000000000..57b5130d4f9
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_static_module.c
@@ -0,0 +1,275 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_static_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_static_init(ngx_conf_t *cf);
+
+
+ngx_http_module_t ngx_http_static_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_static_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_static_module = {
+ NGX_MODULE_V1,
+ &ngx_http_static_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_static_handler(ngx_http_request_t *r)
+{
+ u_char *last, *location;
+ size_t root, len;
+ ngx_str_t path;
+ ngx_int_t rc;
+ ngx_uint_t level;
+ ngx_log_t *log;
+ ngx_buf_t *b;
+ ngx_chain_t out;
+ ngx_open_file_info_t of;
+ ngx_http_core_loc_conf_t *clcf;
+
+ if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))) {
+ return NGX_HTTP_NOT_ALLOWED;
+ }
+
+ if (r->uri.data[r->uri.len - 1] == '/') {
+ return NGX_DECLINED;
+ }
+
+ log = r->connection->log;
+
+ /*
+ * ngx_http_map_uri_to_path() allocates memory for terminating '\0'
+ * so we do not need to reserve memory for '/' for possible redirect
+ */
+
+ last = ngx_http_map_uri_to_path(r, &path, &root, 0);
+ if (last == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ path.len = last - path.data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "http filename: \"%s\"", path.data);
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+ of.read_ahead = clcf->read_ahead;
+ of.directio = clcf->directio;
+ of.valid = clcf->open_file_cache_valid;
+ of.min_uses = clcf->open_file_cache_min_uses;
+ of.errors = clcf->open_file_cache_errors;
+ of.events = clcf->open_file_cache_events;
+
+ if (ngx_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:
+
+ level = NGX_LOG_ERR;
+ rc = NGX_HTTP_FORBIDDEN;
+ break;
+
+ default:
+
+ level = NGX_LOG_CRIT;
+ rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ }
+
+ if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
+ ngx_log_error(level, log, of.err,
+ "%s \"%s\" failed", of.failed, path.data);
+ }
+
+ return rc;
+ }
+
+ r->root_tested = !r->error_page;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", of.fd);
+
+ if (of.is_dir) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir");
+
+ r->headers_out.location = ngx_palloc(r->pool, sizeof(ngx_table_elt_t));
+ if (r->headers_out.location == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ len = r->uri.len + 1;
+
+ if (!clcf->alias && clcf->root_lengths == NULL && r->args.len == 0) {
+ location = path.data + clcf->root.len;
+
+ *last = '/';
+
+ } else {
+ if (r->args.len) {
+ len += r->args.len + 1;
+ }
+
+ location = ngx_pnalloc(r->pool, len);
+ if (location == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ last = ngx_copy(location, r->uri.data, r->uri.len);
+
+ *last = '/';
+
+ if (r->args.len) {
+ *++last = '?';
+ ngx_memcpy(++last, r->args.data, r->args.len);
+ }
+ }
+
+ /*
+ * 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 = len;
+ r->headers_out.location->value.data = location;
+
+ return NGX_HTTP_MOVED_PERMANENTLY;
+ }
+
+#if !(NGX_WIN32) /* the not regular files are probably Unix specific */
+
+ if (!of.is_file) {
+ ngx_log_error(NGX_LOG_CRIT, log, 0,
+ "\"%s\" is not a regular file", path.data);
+
+ return NGX_HTTP_NOT_FOUND;
+ }
+
+#endif
+
+ if (r->method & NGX_HTTP_POST) {
+ return NGX_HTTP_NOT_ALLOWED;
+ }
+
+ rc = ngx_http_discard_request_body(r);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ log->action = "sending response to client";
+
+ r->headers_out.status = NGX_HTTP_OK;
+ r->headers_out.content_length_n = of.size;
+ r->headers_out.last_modified_time = of.mtime;
+
+ if (ngx_http_set_content_type(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (r != r->main && of.size == 0) {
+ return ngx_http_send_header(r);
+ }
+
+ r->allow_ranges = 1;
+
+ /* we need to allocate all before the header would be sent */
+
+ b = ngx_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 ngx_int_t
+ngx_http_static_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_static_handler;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_stub_status_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_stub_status_module.c
new file mode 100644
index 00000000000..4b74eb8845a
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_stub_status_module.c
@@ -0,0 +1,143 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static char *ngx_http_set_status(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+static ngx_command_t ngx_http_status_commands[] = {
+
+ { ngx_string("stub_status"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_http_set_status,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+
+static ngx_http_module_t ngx_http_stub_status_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_stub_status_module = {
+ NGX_MODULE_V1,
+ &ngx_http_stub_status_module_ctx, /* module context */
+ ngx_http_status_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t ngx_http_status_handler(ngx_http_request_t *r)
+{
+ size_t size;
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_chain_t out;
+ ngx_atomic_int_t ap, hn, ac, rq, rd, wr;
+
+ if (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_HEAD) {
+ return NGX_HTTP_NOT_ALLOWED;
+ }
+
+ rc = ngx_http_discard_request_body(r);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ ngx_str_set(&r->headers_out.content_type, "text/plain");
+
+ if (r->method == NGX_HTTP_HEAD) {
+ r->headers_out.status = NGX_HTTP_OK;
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ return rc;
+ }
+ }
+
+ size = sizeof("Active connections: \n") + NGX_ATOMIC_T_LEN
+ + sizeof("server accepts handled requests\n") - 1
+ + 6 + 3 * NGX_ATOMIC_T_LEN
+ + sizeof("Reading: Writing: Waiting: \n") + 3 * NGX_ATOMIC_T_LEN;
+
+ b = ngx_create_temp_buf(r->pool, size);
+ if (b == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ out.buf = b;
+ out.next = NULL;
+
+ ap = *ngx_stat_accepted;
+ hn = *ngx_stat_handled;
+ ac = *ngx_stat_active;
+ rq = *ngx_stat_requests;
+ rd = *ngx_stat_reading;
+ wr = *ngx_stat_writing;
+
+ b->last = ngx_sprintf(b->last, "Active connections: %uA \n", ac);
+
+ b->last = ngx_cpymem(b->last, "server accepts handled requests\n",
+ sizeof("server accepts handled requests\n") - 1);
+
+ b->last = ngx_sprintf(b->last, " %uA %uA %uA \n", ap, hn, rq);
+
+ b->last = ngx_sprintf(b->last, "Reading: %uA Writing: %uA Waiting: %uA \n",
+ rd, wr, ac - (rd + wr));
+
+ r->headers_out.status = NGX_HTTP_OK;
+ r->headers_out.content_length_n = b->last - b->pos;
+
+ b->last_buf = 1;
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ return rc;
+ }
+
+ return ngx_http_output_filter(r, &out);
+}
+
+
+static char *ngx_http_set_status(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf;
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+ clcf->handler = ngx_http_status_handler;
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_sub_filter_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_sub_filter_module.c
new file mode 100644
index 00000000000..ddc69ba52c3
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_sub_filter_module.c
@@ -0,0 +1,715 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_str_t match;
+ ngx_http_complex_value_t value;
+
+ ngx_hash_t types;
+
+ ngx_flag_t once;
+
+ ngx_array_t *types_keys;
+} ngx_http_sub_loc_conf_t;
+
+
+typedef enum {
+ sub_start_state = 0,
+ sub_match_state,
+} ngx_http_sub_state_e;
+
+
+typedef struct {
+ ngx_str_t match;
+ ngx_str_t saved;
+ ngx_str_t looked;
+
+ ngx_uint_t once; /* unsigned once:1 */
+
+ ngx_buf_t *buf;
+
+ u_char *pos;
+ u_char *copy_start;
+ u_char *copy_end;
+
+ ngx_chain_t *in;
+ ngx_chain_t *out;
+ ngx_chain_t **last_out;
+ ngx_chain_t *busy;
+ ngx_chain_t *free;
+
+ ngx_str_t sub;
+
+ ngx_uint_t state;
+} ngx_http_sub_ctx_t;
+
+
+static ngx_int_t ngx_http_sub_output(ngx_http_request_t *r,
+ ngx_http_sub_ctx_t *ctx);
+static ngx_int_t ngx_http_sub_parse(ngx_http_request_t *r,
+ ngx_http_sub_ctx_t *ctx);
+
+static char * ngx_http_sub_filter(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static void *ngx_http_sub_create_conf(ngx_conf_t *cf);
+static char *ngx_http_sub_merge_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_sub_filter_init(ngx_conf_t *cf);
+
+
+static ngx_command_t ngx_http_sub_filter_commands[] = {
+
+ { ngx_string("sub_filter"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_http_sub_filter,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("sub_filter_types"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_types_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_sub_loc_conf_t, types_keys),
+ &ngx_http_html_default_types[0] },
+
+ { ngx_string("sub_filter_once"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_sub_loc_conf_t, once),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_sub_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_sub_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_sub_create_conf, /* create location configuration */
+ ngx_http_sub_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_sub_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_sub_filter_module_ctx, /* module context */
+ ngx_http_sub_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_sub_header_filter(ngx_http_request_t *r)
+{
+ ngx_http_sub_ctx_t *ctx;
+ ngx_http_sub_loc_conf_t *slcf;
+
+ slcf = ngx_http_get_module_loc_conf(r, ngx_http_sub_filter_module);
+
+ if (slcf->match.len == 0
+ || r->headers_out.content_length_n == 0
+ || ngx_http_test_content_type(r, &slcf->types) == NULL)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_sub_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->saved.data = ngx_pnalloc(r->pool, slcf->match.len);
+ if (ctx->saved.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->looked.data = ngx_pnalloc(r->pool, slcf->match.len);
+ if (ctx->looked.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_sub_filter_module);
+
+ ctx->match = slcf->match;
+ ctx->last_out = &ctx->out;
+
+ r->filter_need_in_memory = 1;
+
+ if (r == r->main) {
+ ngx_http_clear_content_length(r);
+ ngx_http_clear_last_modified(r);
+ }
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_sub_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+ ngx_http_sub_ctx_t *ctx;
+ ngx_http_sub_loc_conf_t *slcf;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_sub_filter_module);
+
+ if (ctx == NULL) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ if ((in == NULL
+ && ctx->buf == NULL
+ && ctx->in == NULL
+ && ctx->busy == NULL))
+ {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ if (ctx->once && (ctx->buf == NULL || ctx->in == NULL)) {
+
+ if (ctx->busy) {
+ if (ngx_http_sub_output(r, ctx) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ /* add the incoming chain to the chain ctx->in */
+
+ if (in) {
+ if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http sub filter \"%V\"", &r->uri);
+
+ while (ctx->in || ctx->buf) {
+
+ if (ctx->buf == NULL) {
+ ctx->buf = ctx->in->buf;
+ ctx->in = ctx->in->next;
+ ctx->pos = ctx->buf->pos;
+ }
+
+ if (ctx->state == sub_start_state) {
+ ctx->copy_start = ctx->pos;
+ ctx->copy_end = ctx->pos;
+ }
+
+ b = NULL;
+
+ while (ctx->pos < ctx->buf->last) {
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "saved: \"%V\" state: %d", &ctx->saved, ctx->state);
+
+ rc = ngx_http_sub_parse(r, ctx);
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "parse: %d, looked: \"%V\" %p-%p",
+ rc, &ctx->looked, ctx->copy_start, ctx->copy_end);
+
+ if (rc == NGX_ERROR) {
+ return rc;
+ }
+
+ if (ctx->copy_start != ctx->copy_end) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "saved: \"%V\"", &ctx->saved);
+
+ if (ctx->saved.len) {
+
+ if (ctx->free) {
+ cl = ctx->free;
+ ctx->free = ctx->free->next;
+ b = cl->buf;
+ ngx_memzero(b, sizeof(ngx_buf_t));
+
+ } else {
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ }
+
+ b->pos = ngx_pnalloc(r->pool, ctx->saved.len);
+ if (b->pos == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(b->pos, ctx->saved.data, ctx->saved.len);
+ b->last = b->pos + ctx->saved.len;
+ b->memory = 1;
+
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ ctx->saved.len = 0;
+ }
+
+ if (ctx->free) {
+ cl = ctx->free;
+ ctx->free = ctx->free->next;
+ b = cl->buf;
+
+ } else {
+ b = ngx_alloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ }
+
+ ngx_memcpy(b, ctx->buf, sizeof(ngx_buf_t));
+
+ b->pos = ctx->copy_start;
+ b->last = ctx->copy_end;
+ b->shadow = NULL;
+ b->last_buf = 0;
+ b->recycled = 0;
+
+ if (b->in_file) {
+ b->file_last = b->file_pos + (b->last - ctx->buf->pos);
+ b->file_pos += b->pos - ctx->buf->pos;
+ }
+
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+ }
+
+ if (ctx->state == sub_start_state) {
+ ctx->copy_start = ctx->pos;
+ ctx->copy_end = ctx->pos;
+
+ } else {
+ ctx->copy_start = NULL;
+ ctx->copy_end = NULL;
+ }
+
+ if (rc == NGX_AGAIN) {
+ continue;
+ }
+
+
+ /* rc == NGX_OK */
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ slcf = ngx_http_get_module_loc_conf(r, ngx_http_sub_filter_module);
+
+ if (ctx->sub.data == NULL) {
+
+ if (ngx_http_complex_value(r, &slcf->value, &ctx->sub)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ if (ctx->sub.len) {
+ b->memory = 1;
+ b->pos = ctx->sub.data;
+ b->last = ctx->sub.data + ctx->sub.len;
+
+ } else {
+ b->sync = 1;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ ctx->once = slcf->once;
+
+ continue;
+ }
+
+ if (ctx->buf->last_buf || ngx_buf_in_memory(ctx->buf)) {
+ if (b == NULL) {
+ if (ctx->free) {
+ cl = ctx->free;
+ ctx->free = ctx->free->next;
+ b = cl->buf;
+ ngx_memzero(b, sizeof(ngx_buf_t));
+
+ } else {
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ }
+
+ b->sync = 1;
+
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+ }
+
+ b->last_buf = ctx->buf->last_buf;
+ b->shadow = ctx->buf;
+
+ b->recycled = ctx->buf->recycled;
+ }
+
+ ctx->buf = NULL;
+
+ ctx->saved.len = ctx->looked.len;
+ ngx_memcpy(ctx->saved.data, ctx->looked.data, ctx->looked.len);
+ }
+
+ if (ctx->out == NULL && ctx->busy == NULL) {
+ return NGX_OK;
+ }
+
+ return ngx_http_sub_output(r, ctx);
+}
+
+
+static ngx_int_t
+ngx_http_sub_output(ngx_http_request_t *r, ngx_http_sub_ctx_t *ctx)
+{
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+
+#if 1
+ b = NULL;
+ for (cl = ctx->out; cl; cl = cl->next) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "sub out: %p %p", cl->buf, cl->buf->pos);
+ if (cl->buf == b) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "the same buf was used in sub");
+ ngx_debug_point();
+ return NGX_ERROR;
+ }
+ b = cl->buf;
+ }
+#endif
+
+ rc = ngx_http_next_body_filter(r, ctx->out);
+
+ if (ctx->busy == NULL) {
+ ctx->busy = ctx->out;
+
+ } else {
+ for (cl = ctx->busy; cl->next; cl = cl->next) { /* void */ }
+ cl->next = ctx->out;
+ }
+
+ ctx->out = NULL;
+ ctx->last_out = &ctx->out;
+
+ while (ctx->busy) {
+
+ cl = ctx->busy;
+ b = cl->buf;
+
+ if (ngx_buf_size(b) != 0) {
+ break;
+ }
+
+ if (b->shadow) {
+ b->shadow->pos = b->shadow->last;
+ }
+
+ ctx->busy = cl->next;
+
+ if (ngx_buf_in_memory(b) || b->in_file) {
+ /* add data bufs only to the free buf chain */
+
+ cl->next = ctx->free;
+ ctx->free = cl;
+ }
+ }
+
+ if (ctx->in || ctx->buf) {
+ r->buffered |= NGX_HTTP_SUB_BUFFERED;
+
+ } else {
+ r->buffered &= ~NGX_HTTP_SUB_BUFFERED;
+ }
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_sub_parse(ngx_http_request_t *r, ngx_http_sub_ctx_t *ctx)
+{
+ u_char *p, *last, *copy_end, ch, match;
+ size_t looked;
+ ngx_http_sub_state_e state;
+
+ if (ctx->once) {
+ ctx->copy_start = ctx->pos;
+ ctx->copy_end = ctx->buf->last;
+ ctx->pos = ctx->buf->last;
+ ctx->looked.len = 0;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "once");
+
+ return NGX_AGAIN;
+ }
+
+ state = ctx->state;
+ looked = ctx->looked.len;
+ last = ctx->buf->last;
+ copy_end = ctx->copy_end;
+
+ for (p = ctx->pos; p < last; p++) {
+
+ ch = *p;
+ ch = ngx_tolower(ch);
+
+ if (state == sub_start_state) {
+
+ /* the tight loop */
+
+ match = ctx->match.data[0];
+
+ for ( ;; ) {
+ if (ch == match) {
+ copy_end = p;
+ ctx->looked.data[0] = *p;
+ looked = 1;
+ state = sub_match_state;
+
+ goto match_started;
+ }
+
+ if (++p == last) {
+ break;
+ }
+
+ ch = *p;
+ ch = ngx_tolower(ch);
+ }
+
+ ctx->state = state;
+ ctx->pos = p;
+ ctx->looked.len = looked;
+ ctx->copy_end = p;
+
+ if (ctx->copy_start == NULL) {
+ ctx->copy_start = ctx->buf->pos;
+ }
+
+ return NGX_AGAIN;
+
+ match_started:
+
+ continue;
+ }
+
+ /* state == sub_match_state */
+
+ if (ch == ctx->match.data[looked]) {
+ ctx->looked.data[looked] = *p;
+ looked++;
+
+ if (looked == ctx->match.len) {
+ if ((size_t) (p - ctx->pos) < looked) {
+ ctx->saved.len = 0;
+ }
+
+ ctx->state = sub_start_state;
+ ctx->pos = p + 1;
+ ctx->looked.len = 0;
+ ctx->copy_end = copy_end;
+
+ if (ctx->copy_start == NULL && copy_end) {
+ ctx->copy_start = ctx->buf->pos;
+ }
+
+ return NGX_OK;
+ }
+
+ } else if (ch == ctx->match.data[0]) {
+ copy_end = p;
+ ctx->looked.data[0] = *p;
+ looked = 1;
+
+ } else {
+ copy_end = p;
+ looked = 0;
+ state = sub_start_state;
+ }
+ }
+
+ ctx->state = state;
+ ctx->pos = p;
+ ctx->looked.len = looked;
+
+ ctx->copy_end = (state == sub_start_state) ? p : copy_end;
+
+ if (ctx->copy_start == NULL && ctx->copy_end) {
+ ctx->copy_start = ctx->buf->pos;
+ }
+
+ return NGX_AGAIN;
+}
+
+
+static char *
+ngx_http_sub_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_sub_loc_conf_t *slcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_compile_complex_value_t ccv;
+
+ if (slcf->match.len) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ ngx_strlow(value[1].data, value[1].data, value[1].len);
+
+ slcf->match = value[1];
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[2];
+ ccv.complex_value = &slcf->value;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_sub_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_sub_loc_conf_t *slcf;
+
+ slcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_sub_loc_conf_t));
+ if (slcf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->match = { 0, NULL };
+ * conf->sub = { 0, NULL };
+ * conf->sub_lengths = NULL;
+ * conf->sub_values = NULL;
+ * conf->types = { NULL };
+ * conf->types_keys = NULL;
+ */
+
+ slcf->once = NGX_CONF_UNSET;
+
+ return slcf;
+}
+
+
+static char *
+ngx_http_sub_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_sub_loc_conf_t *prev = parent;
+ ngx_http_sub_loc_conf_t *conf = child;
+
+ ngx_conf_merge_value(conf->once, prev->once, 1);
+ ngx_conf_merge_str_value(conf->match, prev->match, "");
+
+ if (conf->value.value.len == 0) {
+ conf->value = prev->value;
+ }
+
+ 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_sub_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_sub_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_sub_body_filter;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_upstream_ip_hash_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_upstream_ip_hash_module.c
new file mode 100644
index 00000000000..dffbf22b28f
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_upstream_ip_hash_module.c
@@ -0,0 +1,236 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ /* the round robin data must be first */
+ ngx_http_upstream_rr_peer_data_t rrp;
+
+ ngx_uint_t hash;
+
+ u_char addr[3];
+
+ 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
+};
+
+
+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)
+{
+ u_char *p;
+ struct sockaddr_in *sin;
+ 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;
+
+ /* AF_INET only */
+
+ if (r->connection->sockaddr->sa_family == AF_INET) {
+
+ sin = (struct sockaddr_in *) r->connection->sockaddr;
+ p = (u_char *) &sin->sin_addr.s_addr;
+ iphp->addr[0] = p[0];
+ iphp->addr[1] = p[1];
+ iphp->addr[2] = p[2];
+
+ } else {
+ iphp->addr[0] = 0;
+ iphp->addr[1] = 0;
+ iphp->addr[2] = 0;
+ }
+
+ 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;
+ 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 < 3; i++) {
+ hash = (hash * 113 + iphp->addr[i]) % 6271;
+ }
+
+ p = hash % iphp->rrp.peers->number;
+
+ 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->accessed > peer->fail_timeout) {
+ peer->fails = 0;
+ 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);
+
+ uscf->peer.init_upstream = ngx_http_upstream_init_ip_hash;
+
+ uscf->flags = NGX_HTTP_UPSTREAM_CREATE
+ |NGX_HTTP_UPSTREAM_MAX_FAILS
+ |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
+ |NGX_HTTP_UPSTREAM_DOWN;
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_userid_filter_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_userid_filter_module.c
new file mode 100644
index 00000000000..195d9dc4fa4
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_userid_filter_module.c
@@ -0,0 +1,845 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_USERID_OFF 0
+#define NGX_HTTP_USERID_LOG 1
+#define NGX_HTTP_USERID_V1 2
+#define NGX_HTTP_USERID_ON 3
+
+/* 31 Dec 2037 23:55:55 GMT */
+#define NGX_HTTP_USERID_MAX_EXPIRES 2145916555
+
+
+typedef struct {
+ ngx_uint_t enable;
+
+ ngx_int_t service;
+
+ ngx_str_t name;
+ ngx_str_t domain;
+ ngx_str_t path;
+ ngx_str_t p3p;
+
+ time_t expires;
+
+ u_char mark;
+} ngx_http_userid_conf_t;
+
+
+typedef struct {
+ uint32_t uid_got[4];
+ uint32_t uid_set[4];
+ ngx_str_t cookie;
+ ngx_uint_t reset;
+} ngx_http_userid_ctx_t;
+
+
+static ngx_http_userid_ctx_t *ngx_http_userid_get_uid(ngx_http_request_t *r,
+ ngx_http_userid_conf_t *conf);
+static ngx_int_t ngx_http_userid_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, ngx_str_t *name, uint32_t *uid);
+static ngx_int_t ngx_http_userid_set_uid(ngx_http_request_t *r,
+ ngx_http_userid_ctx_t *ctx, ngx_http_userid_conf_t *conf);
+static ngx_int_t ngx_http_userid_create_uid(ngx_http_request_t *r,
+ ngx_http_userid_ctx_t *ctx, ngx_http_userid_conf_t *conf);
+
+static ngx_int_t ngx_http_userid_add_variables(ngx_conf_t *cf);
+static ngx_int_t ngx_http_userid_init(ngx_conf_t *cf);
+static void *ngx_http_userid_create_conf(ngx_conf_t *cf);
+static char *ngx_http_userid_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_http_userid_domain(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_userid_path(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_userid_expires(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_userid_p3p(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_userid_mark(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static ngx_int_t ngx_http_userid_init_worker(ngx_cycle_t *cycle);
+
+
+
+static uint32_t start_value;
+static uint32_t sequencer_v1 = 1;
+static uint32_t sequencer_v2 = 0x03030302;
+
+
+static u_char expires[] = "; expires=Thu, 31-Dec-37 23:55:55 GMT";
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+
+
+static ngx_conf_enum_t ngx_http_userid_state[] = {
+ { ngx_string("off"), NGX_HTTP_USERID_OFF },
+ { ngx_string("log"), NGX_HTTP_USERID_LOG },
+ { ngx_string("v1"), NGX_HTTP_USERID_V1 },
+ { ngx_string("on"), NGX_HTTP_USERID_ON },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_post_handler_pt ngx_http_userid_domain_p =
+ ngx_http_userid_domain;
+static ngx_conf_post_handler_pt ngx_http_userid_path_p = ngx_http_userid_path;
+static ngx_conf_post_handler_pt ngx_http_userid_p3p_p = ngx_http_userid_p3p;
+
+
+static ngx_command_t ngx_http_userid_commands[] = {
+
+ { ngx_string("userid"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_userid_conf_t, enable),
+ ngx_http_userid_state },
+
+ { ngx_string("userid_service"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_userid_conf_t, service),
+ NULL },
+
+ { ngx_string("userid_name"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_userid_conf_t, name),
+ NULL },
+
+ { ngx_string("userid_domain"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_userid_conf_t, domain),
+ &ngx_http_userid_domain_p },
+
+ { ngx_string("userid_path"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_userid_conf_t, path),
+ &ngx_http_userid_path_p },
+
+ { ngx_string("userid_expires"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_userid_expires,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("userid_p3p"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_userid_conf_t, p3p),
+ &ngx_http_userid_p3p_p },
+
+ { ngx_string("userid_mark"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_userid_mark,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_userid_filter_module_ctx = {
+ ngx_http_userid_add_variables, /* preconfiguration */
+ ngx_http_userid_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_userid_create_conf, /* create location configration */
+ ngx_http_userid_merge_conf /* merge location configration */
+};
+
+
+ngx_module_t ngx_http_userid_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_userid_filter_module_ctx, /* module context */
+ ngx_http_userid_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ ngx_http_userid_init_worker, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_http_userid_got = ngx_string("uid_got");
+static ngx_str_t ngx_http_userid_set = ngx_string("uid_set");
+static ngx_str_t ngx_http_userid_reset = ngx_string("uid_reset");
+static ngx_uint_t ngx_http_userid_reset_index;
+
+
+static ngx_int_t
+ngx_http_userid_filter(ngx_http_request_t *r)
+{
+ ngx_http_userid_ctx_t *ctx;
+ ngx_http_userid_conf_t *conf;
+
+ if (r != r->main) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_userid_filter_module);
+
+ if (conf->enable < NGX_HTTP_USERID_V1) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx = ngx_http_userid_get_uid(r, conf);
+
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_http_userid_set_uid(r, ctx, conf) == NGX_OK) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_userid_got_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_http_userid_ctx_t *ctx;
+ ngx_http_userid_conf_t *conf;
+
+ conf = ngx_http_get_module_loc_conf(r->main, ngx_http_userid_filter_module);
+
+ if (conf->enable == NGX_HTTP_USERID_OFF) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ ctx = ngx_http_userid_get_uid(r->main, conf);
+
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ctx->uid_got[3] != 0) {
+ return ngx_http_userid_variable(r->main, v, &conf->name, ctx->uid_got);
+ }
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_set_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_http_userid_ctx_t *ctx;
+ ngx_http_userid_conf_t *conf;
+
+ conf = ngx_http_get_module_loc_conf(r->main, ngx_http_userid_filter_module);
+
+ if (conf->enable < NGX_HTTP_USERID_V1) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ ctx = ngx_http_userid_get_uid(r->main, conf);
+
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_http_userid_create_uid(r->main, ctx, conf) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ctx->uid_set[3] == 0) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ return ngx_http_userid_variable(r->main, v, &conf->name, ctx->uid_set);
+}
+
+
+static ngx_http_userid_ctx_t *
+ngx_http_userid_get_uid(ngx_http_request_t *r, ngx_http_userid_conf_t *conf)
+{
+ ngx_int_t n;
+ ngx_str_t src, dst;
+ ngx_table_elt_t **cookies;
+ ngx_http_userid_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_userid_filter_module);
+
+ if (ctx) {
+ return ctx;
+ }
+
+ if (ctx == NULL) {
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_userid_ctx_t));
+ if (ctx == NULL) {
+ return NULL;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_userid_filter_module);
+ }
+
+ n = ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &conf->name,
+ &ctx->cookie);
+ if (n == NGX_DECLINED) {
+ return ctx;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "uid cookie: \"%V\"", &ctx->cookie);
+
+ if (ctx->cookie.len < 22) {
+ cookies = r->headers_in.cookies.elts;
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client sent too short userid cookie \"%V\"",
+ &cookies[n]->value);
+ return ctx;
+ }
+
+ src = ctx->cookie;
+
+ /*
+ * we have to limit the encoded string to 22 characters because
+ * 1) cookie may be marked by "userid_mark",
+ * 2) and there are already the millions cookies with a garbage
+ * instead of the correct base64 trail "=="
+ */
+
+ src.len = 22;
+
+ dst.data = (u_char *) ctx->uid_got;
+
+ if (ngx_decode_base64(&dst, &src) == NGX_ERROR) {
+ cookies = r->headers_in.cookies.elts;
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client sent invalid userid cookie \"%V\"",
+ &cookies[n]->value);
+ return ctx;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "uid: %08XD%08XD%08XD%08XD",
+ ctx->uid_got[0], ctx->uid_got[1],
+ ctx->uid_got[2], ctx->uid_got[3]);
+
+ return ctx;
+}
+
+
+static ngx_int_t
+ngx_http_userid_set_uid(ngx_http_request_t *r, ngx_http_userid_ctx_t *ctx,
+ ngx_http_userid_conf_t *conf)
+{
+ u_char *cookie, *p;
+ size_t len;
+ ngx_str_t src, dst;
+ ngx_table_elt_t *set_cookie, *p3p;
+
+ if (ngx_http_userid_create_uid(r, ctx, conf) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ctx->uid_set[3] == 0) {
+ return NGX_OK;
+ }
+
+ len = conf->name.len + 1 + ngx_base64_encoded_length(16) + conf->path.len;
+
+ if (conf->expires) {
+ len += sizeof(expires) - 1 + 2;
+ }
+
+ if (conf->domain.len) {
+ len += conf->domain.len;
+ }
+
+ cookie = ngx_pnalloc(r->pool, len);
+ if (cookie == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_copy(cookie, conf->name.data, conf->name.len);
+ *p++ = '=';
+
+ if (ctx->uid_got[3] == 0 || ctx->reset) {
+ src.len = 16;
+ src.data = (u_char *) ctx->uid_set;
+ dst.data = p;
+
+ ngx_encode_base64(&dst, &src);
+
+ p += dst.len;
+
+ if (conf->mark) {
+ *(p - 2) = conf->mark;
+ }
+
+ } else {
+ p = ngx_cpymem(p, ctx->cookie.data, 22);
+ *p++ = conf->mark;
+ *p++ = '=';
+ }
+
+ if (conf->expires == NGX_HTTP_USERID_MAX_EXPIRES) {
+ p = ngx_cpymem(p, expires, sizeof(expires) - 1);
+
+ } else if (conf->expires) {
+ p = ngx_cpymem(p, expires, sizeof("; expires=") - 1);
+ p = ngx_http_cookie_time(p, ngx_time() + conf->expires);
+ }
+
+ p = ngx_copy(p, conf->domain.data, conf->domain.len);
+
+ p = ngx_copy(p, conf->path.data, conf->path.len);
+
+ set_cookie = ngx_list_push(&r->headers_out.headers);
+ if (set_cookie == NULL) {
+ return NGX_ERROR;
+ }
+
+ set_cookie->hash = 1;
+ ngx_str_set(&set_cookie->key, "Set-Cookie");
+ set_cookie->value.len = p - cookie;
+ set_cookie->value.data = cookie;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "uid cookie: \"%V\"", &set_cookie->value);
+
+ if (conf->p3p.len == 0) {
+ return NGX_OK;
+ }
+
+ p3p = ngx_list_push(&r->headers_out.headers);
+ if (p3p == NULL) {
+ return NGX_ERROR;
+ }
+
+ p3p->hash = 1;
+ ngx_str_set(&p3p->key, "P3P");
+ p3p->value = conf->p3p;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_create_uid(ngx_http_request_t *r, ngx_http_userid_ctx_t *ctx,
+ ngx_http_userid_conf_t *conf)
+{
+ ngx_connection_t *c;
+ struct sockaddr_in *sin;
+ ngx_http_variable_value_t *vv;
+#if (NGX_HAVE_INET6)
+ u_char *p;
+ struct sockaddr_in6 *sin6;
+#endif
+
+ if (ctx->uid_set[3] != 0) {
+ return NGX_OK;
+ }
+
+ if (ctx->uid_got[3] != 0) {
+
+ vv = ngx_http_get_indexed_variable(r, ngx_http_userid_reset_index);
+
+ if (vv->len == 0 || (vv->len == 1 && vv->data[0] == '0')) {
+
+ if (conf->mark == '\0'
+ || (ctx->cookie.len > 23
+ && ctx->cookie.data[22] == conf->mark
+ && ctx->cookie.data[23] == '='))
+ {
+ return NGX_OK;
+ }
+
+ ctx->uid_set[0] = ctx->uid_got[0];
+ ctx->uid_set[1] = ctx->uid_got[1];
+ ctx->uid_set[2] = ctx->uid_got[2];
+ ctx->uid_set[3] = ctx->uid_got[3];
+
+ return NGX_OK;
+
+ } else {
+ ctx->reset = 1;
+
+ if (vv->len == 3 && ngx_strncmp(vv->data, "log", 3) == 0) {
+ ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+ "userid cookie \"%V=%08XD%08XD%08XD%08XD\" was reset",
+ &conf->name, ctx->uid_got[0], ctx->uid_got[1],
+ ctx->uid_got[2], ctx->uid_got[3]);
+ }
+ }
+ }
+
+ /*
+ * TODO: in the threaded mode the sequencers should be in TLS and their
+ * ranges should be divided between threads
+ */
+
+ if (conf->enable == NGX_HTTP_USERID_V1) {
+ if (conf->service == NGX_CONF_UNSET) {
+ ctx->uid_set[0] = 0;
+ } else {
+ ctx->uid_set[0] = conf->service;
+ }
+ ctx->uid_set[1] = (uint32_t) ngx_time();
+ ctx->uid_set[2] = start_value;
+ ctx->uid_set[3] = sequencer_v1;
+ sequencer_v1 += 0x100;
+
+ } else {
+ if (conf->service == NGX_CONF_UNSET) {
+
+ c = r->connection;
+
+ if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ switch (c->local_sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) c->local_sockaddr;
+
+ p = (u_char *) &ctx->uid_set[0];
+
+ *p++ = sin6->sin6_addr.s6_addr[12];
+ *p++ = sin6->sin6_addr.s6_addr[13];
+ *p++ = sin6->sin6_addr.s6_addr[14];
+ *p = sin6->sin6_addr.s6_addr[15];
+
+ break;
+#endif
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) c->local_sockaddr;
+ ctx->uid_set[0] = sin->sin_addr.s_addr;
+ break;
+ }
+
+ } else {
+ ctx->uid_set[0] = htonl(conf->service);
+ }
+
+ ctx->uid_set[1] = htonl((uint32_t) ngx_time());
+ ctx->uid_set[2] = htonl(start_value);
+ ctx->uid_set[3] = htonl(sequencer_v2);
+ sequencer_v2 += 0x100;
+ if (sequencer_v2 < 0x03030302) {
+ sequencer_v2 = 0x03030302;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ ngx_str_t *name, uint32_t *uid)
+{
+ v->len = name->len + sizeof("=00001111222233334444555566667777") - 1;
+ v->data = ngx_pnalloc(r->pool, v->len);
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ ngx_sprintf(v->data, "%V=%08XD%08XD%08XD%08XD",
+ name, uid[0], uid[1], uid[2], uid[3]);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_reset_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ *v = ngx_http_variable_null_value;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_add_variables(ngx_conf_t *cf)
+{
+ ngx_int_t n;
+ ngx_http_variable_t *var;
+
+ var = ngx_http_add_variable(cf, &ngx_http_userid_got, 0);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = ngx_http_userid_got_variable;
+
+ var = ngx_http_add_variable(cf, &ngx_http_userid_set, 0);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = ngx_http_userid_set_variable;
+
+ var = ngx_http_add_variable(cf, &ngx_http_userid_reset,
+ NGX_HTTP_VAR_CHANGEABLE);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = ngx_http_userid_reset_variable;
+
+ n = ngx_http_get_variable_index(cf, &ngx_http_userid_reset);
+ if (n == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_userid_reset_index = n;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_userid_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_userid_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_userid_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->name = { 0, NULL };
+ * conf->domain = { 0, NULL };
+ * conf->path = { 0, NULL };
+ * conf->p3p = { 0, NULL };
+ */
+
+ conf->enable = NGX_CONF_UNSET_UINT;
+ conf->service = NGX_CONF_UNSET;
+ conf->expires = NGX_CONF_UNSET;
+ conf->mark = (u_char) '\xFF';
+
+ return conf;
+}
+
+
+static char *
+ngx_http_userid_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_userid_conf_t *prev = parent;
+ ngx_http_userid_conf_t *conf = child;
+
+ ngx_conf_merge_uint_value(conf->enable, prev->enable,
+ NGX_HTTP_USERID_OFF);
+
+ ngx_conf_merge_str_value(conf->name, prev->name, "uid");
+ ngx_conf_merge_str_value(conf->domain, prev->domain, "");
+ ngx_conf_merge_str_value(conf->path, prev->path, "; path=/");
+ ngx_conf_merge_str_value(conf->p3p, prev->p3p, "");
+
+ ngx_conf_merge_value(conf->service, prev->service, NGX_CONF_UNSET);
+ ngx_conf_merge_sec_value(conf->expires, prev->expires, 0);
+
+ if (conf->mark == (u_char) '\xFF') {
+ if (prev->mark == (u_char) '\xFF') {
+ conf->mark = '\0';
+ } else {
+ conf->mark = prev->mark;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_userid_filter;
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_http_userid_domain(ngx_conf_t *cf, void *post, void *data)
+{
+ ngx_str_t *domain = data;
+
+ u_char *p, *new;
+
+ if (ngx_strcmp(domain->data, "none") == 0) {
+ ngx_str_set(domain, "");
+ return NGX_CONF_OK;
+ }
+
+ new = ngx_pnalloc(cf->pool, sizeof("; domain=") - 1 + domain->len);
+ if (new == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ p = ngx_cpymem(new, "; domain=", sizeof("; domain=") - 1);
+ ngx_memcpy(p, domain->data, domain->len);
+
+ domain->len += sizeof("; domain=") - 1;
+ domain->data = new;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_userid_path(ngx_conf_t *cf, void *post, void *data)
+{
+ ngx_str_t *path = data;
+
+ u_char *p, *new;
+
+ new = ngx_pnalloc(cf->pool, sizeof("; path=") - 1 + path->len);
+ if (new == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ p = ngx_cpymem(new, "; path=", sizeof("; path=") - 1);
+ ngx_memcpy(p, path->data, path->len);
+
+ path->len += sizeof("; path=") - 1;
+ path->data = new;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_userid_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_userid_conf_t *ucf = conf;
+
+ ngx_str_t *value;
+
+ if (ucf->expires != NGX_CONF_UNSET) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "max") == 0) {
+ ucf->expires = NGX_HTTP_USERID_MAX_EXPIRES;
+ return NGX_CONF_OK;
+ }
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ ucf->expires = 0;
+ return NGX_CONF_OK;
+ }
+
+ ucf->expires = ngx_parse_time(&value[1], 1);
+ if (ucf->expires == NGX_ERROR) {
+ return "invalid value";
+ }
+
+ if (ucf->expires == NGX_PARSE_LARGE_TIME) {
+ return "value must be less than 68 years";
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_userid_p3p(ngx_conf_t *cf, void *post, void *data)
+{
+ ngx_str_t *p3p = data;
+
+ if (ngx_strcmp(p3p->data, "none") == 0) {
+ ngx_str_set(p3p, "");
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_userid_mark(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_userid_conf_t *ucf = conf;
+
+ ngx_str_t *value;
+
+ if (ucf->mark != (u_char) '\xFF') {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ ucf->mark = '\0';
+ return NGX_CONF_OK;
+ }
+
+ if (value[1].len != 1
+ || !((value[1].data[0] >= '0' && value[1].data[0] <= '9')
+ || (value[1].data[0] >= 'A' && value[1].data[0] <= 'Z')
+ || (value[1].data[0] >= 'a' && value[1].data[0] <= 'z')
+ || value[1].data[0] == '='))
+ {
+ return "value must be \"off\" or a single letter, digit or \"=\"";
+ }
+
+ ucf->mark = value[1].data[0];
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_init_worker(ngx_cycle_t *cycle)
+{
+ struct timeval tp;
+
+ ngx_gettimeofday(&tp);
+
+ /* use the most significant usec part that fits to 16 bits */
+ start_value = ((tp.tv_usec / 20) << 16) | ngx_pid;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_uwsgi_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_uwsgi_module.c
new file mode 100644
index 00000000000..37b763292f1
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_uwsgi_module.c
@@ -0,0 +1,1718 @@
+
+/*
+ * Copyright (C) Unbit S.a.s. 2009-2010
+ * Copyright (C) 2008 Manlio Perillo (manlio.perillo@gmail.com)
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+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 *uwsgi_lengths;
+ ngx_array_t *uwsgi_values;
+
+#if (NGX_HTTP_CACHE)
+ ngx_http_complex_value_t cache_key;
+#endif
+
+ ngx_str_t uwsgi_string;
+
+ ngx_uint_t modifier1;
+ ngx_uint_t modifier2;
+} ngx_http_uwsgi_loc_conf_t;
+
+
+static ngx_int_t ngx_http_uwsgi_eval(ngx_http_request_t *r,
+ ngx_http_uwsgi_loc_conf_t *uwcf);
+static ngx_int_t ngx_http_uwsgi_create_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_uwsgi_reinit_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_uwsgi_process_status_line(ngx_http_request_t *r);
+static ngx_int_t ngx_http_uwsgi_process_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_uwsgi_process_header(ngx_http_request_t *r);
+static void ngx_http_uwsgi_abort_request(ngx_http_request_t *r);
+static void ngx_http_uwsgi_finalize_request(ngx_http_request_t *r,
+ ngx_int_t rc);
+
+static void *ngx_http_uwsgi_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_uwsgi_merge_loc_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+
+static char *ngx_http_uwsgi_pass(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_uwsgi_store(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+#if (NGX_HTTP_CACHE)
+static ngx_int_t ngx_http_uwsgi_create_key(ngx_http_request_t *r);
+static char *ngx_http_uwsgi_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_uwsgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+#endif
+
+
+static ngx_conf_num_bounds_t ngx_http_uwsgi_modifier_bounds = {
+ ngx_conf_check_num_bounds, 0, 255
+};
+
+
+static ngx_conf_bitmask_t ngx_http_uwsgi_next_upstream_masks[] = {
+ { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
+ { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT },
+ { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER },
+ { ngx_string("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_uwsgi_module;
+
+
+static ngx_command_t ngx_http_uwsgi_commands[] = {
+
+ { ngx_string("uwsgi_pass"),
+ NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
+ ngx_http_uwsgi_pass,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("uwsgi_modifier1"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, modifier1),
+ &ngx_http_uwsgi_modifier_bounds },
+
+ { ngx_string("uwsgi_modifier2"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, modifier2),
+ &ngx_http_uwsgi_modifier_bounds },
+
+ { ngx_string("uwsgi_store"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_uwsgi_store,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("uwsgi_store_access"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
+ ngx_conf_set_access_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.store_access),
+ NULL },
+
+ { ngx_string("uwsgi_ignore_client_abort"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ignore_client_abort),
+ NULL },
+
+ { ngx_string("uwsgi_bind"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_upstream_bind_set_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.local),
+ NULL },
+
+ { ngx_string("uwsgi_connect_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.connect_timeout),
+ NULL },
+
+ { ngx_string("uwsgi_send_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.send_timeout),
+ NULL },
+
+ { ngx_string("uwsgi_buffer_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.buffer_size),
+ NULL },
+
+ { ngx_string("uwsgi_pass_request_headers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.pass_request_headers),
+ NULL },
+
+ { ngx_string("uwsgi_pass_request_body"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.pass_request_body),
+ NULL },
+
+ { ngx_string("uwsgi_intercept_errors"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.intercept_errors),
+ NULL },
+
+ { ngx_string("uwsgi_read_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.read_timeout),
+ NULL },
+
+ { ngx_string("uwsgi_buffers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_conf_set_bufs_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.bufs),
+ NULL },
+
+ { ngx_string("uwsgi_busy_buffers_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.busy_buffers_size_conf),
+ NULL },
+
+#if (NGX_HTTP_CACHE)
+
+ { ngx_string("uwsgi_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_uwsgi_cache,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("uwsgi_cache_key"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_uwsgi_cache_key,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("uwsgi_cache_path"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE,
+ ngx_http_file_cache_set_slot,
+ 0,
+ 0,
+ &ngx_http_uwsgi_module },
+
+ { ngx_string("uwsgi_cache_bypass"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_set_predicate_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_bypass),
+ NULL },
+
+ { ngx_string("uwsgi_no_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_set_predicate_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.no_cache),
+ NULL },
+
+ { ngx_string("uwsgi_cache_valid"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_file_cache_valid_set_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_valid),
+ NULL },
+
+ { ngx_string("uwsgi_cache_min_uses"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_min_uses),
+ NULL },
+
+ { ngx_string("uwsgi_cache_use_stale"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_use_stale),
+ &ngx_http_uwsgi_next_upstream_masks },
+
+ { ngx_string("uwsgi_cache_methods"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_methods),
+ &ngx_http_upstream_cache_method_mask },
+
+#endif
+
+ { ngx_string("uwsgi_temp_path"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
+ ngx_conf_set_path_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.temp_path),
+ NULL },
+
+ { ngx_string("uwsgi_max_temp_file_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.max_temp_file_size_conf),
+ NULL },
+
+ { ngx_string("uwsgi_temp_file_write_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.temp_file_write_size_conf),
+ NULL },
+
+ { ngx_string("uwsgi_next_upstream"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.next_upstream),
+ &ngx_http_uwsgi_next_upstream_masks },
+
+ { ngx_string("uwsgi_param"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_conf_set_keyval_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, params_source),
+ NULL },
+
+ { ngx_string("uwsgi_string"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, uwsgi_string),
+ NULL },
+
+ { ngx_string("uwsgi_pass_header"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.pass_headers),
+ NULL },
+
+ { ngx_string("uwsgi_hide_header"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.hide_headers),
+ NULL },
+
+ { ngx_string("uwsgi_ignore_headers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ignore_headers),
+ &ngx_http_upstream_ignore_headers_masks },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_uwsgi_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_uwsgi_create_loc_conf, /* create location configuration */
+ ngx_http_uwsgi_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_uwsgi_module = {
+ NGX_MODULE_V1,
+ &ngx_http_uwsgi_module_ctx, /* module context */
+ ngx_http_uwsgi_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_http_uwsgi_hide_headers[] = {
+ ngx_string("X-Accel-Expires"),
+ ngx_string("X-Accel-Redirect"),
+ ngx_string("X-Accel-Limit-Rate"),
+ ngx_string("X-Accel-Buffering"),
+ ngx_string("X-Accel-Charset"),
+ ngx_null_string
+};
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_keyval_t ngx_http_uwsgi_cache_headers[] = {
+ { ngx_string("HTTP_IF_MODIFIED_SINCE"), ngx_string("") },
+ { 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_uwsgi_temp_path = {
+ ngx_string(NGX_HTTP_UWSGI_TEMP_PATH), { 1, 2, 0 }
+};
+
+
+static ngx_int_t
+ngx_http_uwsgi_handler(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_http_status_t *status;
+ ngx_http_upstream_t *u;
+ ngx_http_uwsgi_loc_conf_t *uwcf;
+
+ if (r->subrequest_in_memory) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "ngx_http_uwsgi_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_uwsgi_module);
+
+ uwcf = ngx_http_get_module_loc_conf(r, ngx_http_uwsgi_module);
+
+ if (uwcf->uwsgi_lengths) {
+ if (ngx_http_uwsgi_eval(r, uwcf) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ u = r->upstream;
+
+ ngx_str_set(&u->schema, "uwsgi://");
+ u->output.tag = (ngx_buf_tag_t) &ngx_http_uwsgi_module;
+
+ u->conf = &uwcf->upstream;
+
+#if (NGX_HTTP_CACHE)
+ u->create_key = ngx_http_uwsgi_create_key;
+#endif
+ u->create_request = ngx_http_uwsgi_create_request;
+ u->reinit_request = ngx_http_uwsgi_reinit_request;
+ u->process_header = ngx_http_uwsgi_process_status_line;
+ u->abort_request = ngx_http_uwsgi_abort_request;
+ u->finalize_request = ngx_http_uwsgi_finalize_request;
+
+ 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_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_uwsgi_eval(ngx_http_request_t *r, ngx_http_uwsgi_loc_conf_t * uwcf)
+{
+ ngx_url_t url;
+ ngx_http_upstream_t *u;
+
+ ngx_memzero(&url, sizeof(ngx_url_t));
+
+ if (ngx_http_script_run(r, &url.url, uwcf->uwsgi_lengths->elts, 0,
+ uwcf->uwsgi_values->elts)
+ == NULL)
+ {
+ return NGX_ERROR;
+ }
+
+ 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_uwsgi_create_key(ngx_http_request_t *r)
+{
+ ngx_str_t *key;
+ ngx_http_uwsgi_loc_conf_t *uwcf;
+
+ key = ngx_array_push(&r->cache->keys);
+ if (key == NULL) {
+ return NGX_ERROR;
+ }
+
+ uwcf = ngx_http_get_module_loc_conf(r, ngx_http_uwsgi_module);
+
+ if (ngx_http_complex_value(r, &uwcf->cache_key, key) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_uwsgi_create_request(ngx_http_request_t *r)
+{
+ u_char ch, *lowcase_key;
+ size_t key_len, val_len, len, allocated;
+ ngx_uint_t i, n, hash, 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_uwsgi_loc_conf_t *uwcf;
+ ngx_http_script_len_code_pt lcode;
+
+ len = 0;
+ header_params = 0;
+ ignored = NULL;
+
+ uwcf = ngx_http_get_module_loc_conf(r, ngx_http_uwsgi_module);
+
+ if (uwcf->params_len) {
+ ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
+
+ ngx_http_script_flush_no_cacheable_variables(r, uwcf->flushes);
+ le.flushed = 1;
+
+ le.ip = uwcf->params_len->elts;
+ le.request = r;
+
+ while (*(uintptr_t *) le.ip) {
+
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ key_len = lcode(&le);
+
+ for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode (&le)) {
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ }
+ le.ip += sizeof(uintptr_t);
+
+ len += 2 + key_len + 2 + val_len;
+ }
+ }
+
+ if (uwcf->upstream.pass_request_headers) {
+
+ allocated = 0;
+ lowcase_key = NULL;
+
+ if (uwcf->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 (uwcf->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(&uwcf->headers_hash, hash, lowcase_key, n)) {
+ ignored[header_params++] = &header[i];
+ continue;
+ }
+ }
+
+ len += 2 + sizeof("HTTP_") - 1 + header[i].key.len
+ + 2 + header[i].value.len;
+ }
+ }
+
+ len += uwcf->uwsgi_string.len;
+
+#if 0
+ /* allow custom uwsgi packet */
+ if (len > 0 && len < 2) {
+ ngx_log_error (NGX_LOG_ALERT, r->connection->log, 0,
+ "uwsgi request is too little: %uz", len);
+ return NGX_ERROR;
+ }
+#endif
+
+ b = ngx_create_temp_buf(r->pool, len + 4);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+
+ *b->last++ = (u_char) uwcf->modifier1;
+ *b->last++ = (u_char) (len & 0xff);
+ *b->last++ = (u_char) ((len >> 8) & 0xff);
+ *b->last++ = (u_char) uwcf->modifier2;
+
+ if (uwcf->params_len) {
+ ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+ e.ip = uwcf->params->elts;
+ e.pos = b->last;
+ e.request = r;
+ e.flushed = 1;
+
+ le.ip = uwcf->params_len->elts;
+
+ while (*(uintptr_t *) le.ip) {
+
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ key_len = (u_char) 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);
+
+ *e.pos++ = (u_char) (key_len & 0xff);
+ *e.pos++ = (u_char) ((key_len >> 8) & 0xff);
+
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code((ngx_http_script_engine_t *) & e);
+
+ *e.pos++ = (u_char) (val_len & 0xff);
+ *e.pos++ = (u_char) ((val_len >> 8) & 0xff);
+
+ while (*(uintptr_t *) e.ip) {
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code((ngx_http_script_engine_t *) & e);
+ }
+
+ e.ip += sizeof(uintptr_t);
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "uwsgi param: \"%*s: %*s\"",
+ key_len, e.pos - (key_len + 2 + val_len),
+ val_len, e.pos - val_len);
+ }
+
+ b->last = e.pos;
+ }
+
+ if (uwcf->upstream.pass_request_headers) {
+
+ part = &r->headers_in.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */ ; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ for (n = 0; n < header_params; n++) {
+ if (&header[i] == ignored[n]) {
+ goto next;
+ }
+ }
+
+ key_len = sizeof("HTTP_") - 1 + header[i].key.len;
+ *b->last++ = (u_char) (key_len & 0xff);
+ *b->last++ = (u_char) ((key_len >> 8) & 0xff);
+
+ b->last = ngx_cpymem(b->last, "HTTP_", sizeof("HTTP_") - 1);
+ for (n = 0; n < header[i].key.len; n++) {
+ ch = header[i].key.data[n];
+
+ if (ch >= 'a' && ch <= 'z') {
+ ch &= ~0x20;
+
+ } else if (ch == '-') {
+ ch = '_';
+ }
+
+ *b->last++ = ch;
+ }
+
+ val_len = header[i].value.len;
+ *b->last++ = (u_char) (val_len & 0xff);
+ *b->last++ = (u_char) ((val_len >> 8) & 0xff);
+ b->last = ngx_copy(b->last, header[i].value.data, val_len);
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "uwsgi param: \"%*s: %*s\"",
+ key_len, b->last - (key_len + 2 + val_len),
+ val_len, b->last - val_len);
+ next:
+
+ continue;
+ }
+ }
+
+ b->last = ngx_copy(b->last, uwcf->uwsgi_string.data,
+ uwcf->uwsgi_string.len);
+
+ if (uwcf->upstream.pass_request_body) {
+ body = r->upstream->request_bufs;
+ r->upstream->request_bufs = cl;
+
+ while (body) {
+ b = ngx_alloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(b, body->buf, sizeof(ngx_buf_t));
+
+ cl->next = ngx_alloc_chain_link(r->pool);
+ if (cl->next == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = cl->next;
+ cl->buf = b;
+
+ body = body->next;
+ }
+
+ } else {
+ r->upstream->request_bufs = cl;
+ }
+
+ cl->next = NULL;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_uwsgi_reinit_request(ngx_http_request_t *r)
+{
+ ngx_http_status_t *status;
+
+ status = ngx_http_get_module_ctx(r, ngx_http_uwsgi_module);
+
+ if (status == NULL) {
+ return NGX_OK;
+ }
+
+ status->code = 0;
+ status->count = 0;
+ status->start = NULL;
+ status->end = NULL;
+
+ r->upstream->process_header = ngx_http_uwsgi_process_status_line;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_uwsgi_process_status_line(ngx_http_request_t *r)
+{
+ size_t len;
+ ngx_int_t rc;
+ ngx_http_status_t *status;
+ ngx_http_upstream_t *u;
+
+ status = ngx_http_get_module_ctx(r, ngx_http_uwsgi_module);
+
+ if (status == NULL) {
+ return NGX_ERROR;
+ }
+
+ u = r->upstream;
+
+ rc = ngx_http_parse_status_line(r, &u->buffer, status);
+
+ if (rc == NGX_AGAIN) {
+ return rc;
+ }
+
+ if (rc == NGX_ERROR) {
+ r->http_version = NGX_HTTP_VERSION_9;
+
+ u->process_header = ngx_http_uwsgi_process_header;
+
+ return ngx_http_uwsgi_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 uwsgi status %ui \"%V\"",
+ u->headers_in.status_n, &u->headers_in.status_line);
+
+ u->process_header = ngx_http_uwsgi_process_header;
+
+ return ngx_http_uwsgi_process_header(r);
+}
+
+
+static ngx_int_t
+ngx_http_uwsgi_process_header(ngx_http_request_t *r)
+{
+ ngx_str_t *status_line;
+ ngx_int_t rc, status;
+ ngx_table_elt_t *h;
+ ngx_http_upstream_t *u;
+ ngx_http_upstream_header_t *hh;
+ ngx_http_upstream_main_conf_t *umcf;
+
+ umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
+
+ for ( ;; ) {
+
+ rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1);
+
+ if (rc == NGX_OK) {
+
+ /* a header line has been parsed successfully */
+
+ h = ngx_list_push(&r->upstream->headers_in.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->hash = r->header_hash;
+
+ h->key.len = r->header_name_end - r->header_name_start;
+ h->value.len = r->header_end - r->header_start;
+
+ h->key.data = ngx_pnalloc(r->pool,
+ h->key.len + 1 + h->value.len + 1
+ + h->key.len);
+ if (h->key.data == NULL) {
+ 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_cpystrn(h->key.data, r->header_name_start, h->key.len + 1);
+ ngx_cpystrn(h->value.data, r->header_start, h->value.len + 1);
+
+ if (h->key.len == r->lowcase_index) {
+ ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
+
+ } else {
+ ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
+ }
+
+ hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
+ h->lowcase_key, h->key.len);
+
+ if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http uwsgi header: \"%V: %V\"", &h->key, &h->value);
+
+ continue;
+ }
+
+ if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+ /* a whole header has been parsed successfully */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http uwsgi header done");
+
+ if (r->http_version > NGX_HTTP_VERSION_9) {
+ return NGX_OK;
+ }
+
+ u = r->upstream;
+
+ 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;
+ }
+
+ r->http_version = NGX_HTTP_VERSION_10;
+ u->headers_in.status_n = status;
+ u->headers_in.status_line = *status_line;
+
+ } else if (u->headers_in.location) {
+ r->http_version = NGX_HTTP_VERSION_10;
+ u->headers_in.status_n = 302;
+ ngx_str_set(&u->headers_in.status_line,
+ "302 Moved Temporarily");
+
+ } else {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent neither valid HTTP/1.0 header "
+ "nor \"Status\" header line");
+ 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;
+ }
+
+ return NGX_OK;
+ }
+
+ if (rc == NGX_AGAIN) {
+ return NGX_AGAIN;
+ }
+
+ /* there was error while a header line parsing */
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent invalid header");
+
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+ }
+}
+
+
+static void
+ngx_http_uwsgi_abort_request(ngx_http_request_t *r)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "abort http uwsgi request");
+
+ return;
+}
+
+
+static void
+ngx_http_uwsgi_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "finalize http uwsgi request");
+
+ return;
+}
+
+
+static void *
+ngx_http_uwsgi_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_uwsgi_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_uwsgi_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->modifier1 = NGX_CONF_UNSET_UINT;
+ conf->modifier2 = NGX_CONF_UNSET_UINT;
+
+ conf->upstream.store = NGX_CONF_UNSET;
+ conf->upstream.store_access = NGX_CONF_UNSET_UINT;
+ conf->upstream.buffering = NGX_CONF_UNSET;
+ conf->upstream.ignore_client_abort = NGX_CONF_UNSET;
+
+ conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
+
+ conf->upstream.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;
+#endif
+
+ conf->upstream.hide_headers = NGX_CONF_UNSET_PTR;
+ conf->upstream.pass_headers = NGX_CONF_UNSET_PTR;
+
+ conf->upstream.intercept_errors = NGX_CONF_UNSET;
+
+ /* "uwsgi_cyclic_temp_file" is disabled */
+ conf->upstream.cyclic_temp_file = 0;
+
+ ngx_str_set(&conf->upstream.module, "uwsgi");
+
+ return conf;
+}
+
+
+static char *
+ngx_http_uwsgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_uwsgi_loc_conf_t *prev = parent;
+ ngx_http_uwsgi_loc_conf_t *conf = child;
+
+ u_char *p;
+ size_t size;
+ uintptr_t *code;
+ ngx_uint_t i;
+ ngx_array_t headers_names;
+ ngx_keyval_t *src;
+ ngx_hash_key_t *hk;
+ ngx_hash_init_t hash;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_script_compile_t sc;
+ ngx_http_script_copy_code_t *copy;
+
+ 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_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 \"uwsgi_buffers\"");
+ return NGX_CONF_ERROR;
+ }
+
+
+ size = conf->upstream.buffer_size;
+ if (size < conf->upstream.bufs.size) {
+ size = conf->upstream.bufs.size;
+ }
+
+
+ ngx_conf_merge_size_value(conf->upstream.busy_buffers_size_conf,
+ prev->upstream.busy_buffers_size_conf,
+ NGX_CONF_UNSET_SIZE);
+
+ if (conf->upstream.busy_buffers_size_conf == NGX_CONF_UNSET_SIZE) {
+ conf->upstream.busy_buffers_size = 2 * size;
+ } else {
+ conf->upstream.busy_buffers_size =
+ conf->upstream.busy_buffers_size_conf;
+ }
+
+ if (conf->upstream.busy_buffers_size < size) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"uwsgi_busy_buffers_size\" must be equal or bigger "
+ "than maximum of the value of \"uwsgi_buffer_size\" and "
+ "one of the \"uwsgi_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+ if (conf->upstream.busy_buffers_size
+ > (conf->upstream.bufs.num - 1) * conf->upstream.bufs.size)
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"uwsgi_busy_buffers_size\" must be less than "
+ "the size of all \"uwsgi_buffers\" minus one buffer");
+
+ return NGX_CONF_ERROR;
+ }
+
+
+ ngx_conf_merge_size_value(conf->upstream.temp_file_write_size_conf,
+ prev->upstream.temp_file_write_size_conf,
+ NGX_CONF_UNSET_SIZE);
+
+ if (conf->upstream.temp_file_write_size_conf == NGX_CONF_UNSET_SIZE) {
+ conf->upstream.temp_file_write_size = 2 * size;
+ } else {
+ conf->upstream.temp_file_write_size =
+ conf->upstream.temp_file_write_size_conf;
+ }
+
+ if (conf->upstream.temp_file_write_size < size) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"uwsgi_temp_file_write_size\" must be equal or bigger than "
+ "maximum of the value of \"uwsgi_buffer_size\" and "
+ "one of the \"uwsgi_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+
+ ngx_conf_merge_size_value(conf->upstream.max_temp_file_size_conf,
+ prev->upstream.max_temp_file_size_conf,
+ NGX_CONF_UNSET_SIZE);
+
+ if (conf->upstream.max_temp_file_size_conf == NGX_CONF_UNSET_SIZE) {
+ conf->upstream.max_temp_file_size = 1024 * 1024 * 1024;
+ } else {
+ conf->upstream.max_temp_file_size =
+ conf->upstream.max_temp_file_size_conf;
+ }
+
+ if (conf->upstream.max_temp_file_size != 0
+ && conf->upstream.max_temp_file_size < size) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"uwsgi_max_temp_file_size\" must be equal to zero to disable "
+ "the temporary files usage or must be equal or bigger than "
+ "maximum of the value of \"uwsgi_buffer_size\" and "
+ "one of the \"uwsgi_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+
+ ngx_conf_merge_bitmask_value(conf->upstream.ignore_headers,
+ prev->upstream.ignore_headers,
+ NGX_CONF_BITMASK_SET);
+
+
+ ngx_conf_merge_bitmask_value(conf->upstream.next_upstream,
+ prev->upstream.next_upstream,
+ (NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_ERROR
+ |NGX_HTTP_UPSTREAM_FT_TIMEOUT));
+
+ if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) {
+ conf->upstream.next_upstream = NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_OFF;
+ }
+
+ if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path,
+ prev->upstream.temp_path,
+ &ngx_http_uwsgi_temp_path)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ 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,
+ "\"uwsgi_cache\" zone \"%V\" is unknown",
+ &shm_zone->shm.name);
+
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_uint_value(conf->upstream.cache_min_uses,
+ prev->upstream.cache_min_uses, 1);
+
+ ngx_conf_merge_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_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;
+ }
+
+#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_str_value(conf->uwsgi_string, prev->uwsgi_string, "");
+
+ hash.max_size = 512;
+ hash.bucket_size = ngx_align(64, ngx_cacheline_size);
+ hash.name = "uwsgi_hide_headers_hash";
+
+ if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream,
+ &prev->upstream, ngx_http_uwsgi_hide_headers, &hash)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ if (conf->upstream.upstream == NULL) {
+ conf->upstream.upstream = prev->upstream.upstream;
+ }
+
+ if (conf->uwsgi_lengths == NULL) {
+ conf->uwsgi_lengths = prev->uwsgi_lengths;
+ conf->uwsgi_values = prev->uwsgi_values;
+ }
+
+ if (conf->upstream.upstream || conf->uwsgi_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_uwsgi_handler;
+ }
+ }
+
+ ngx_conf_merge_uint_value(conf->modifier1, prev->modifier1, 0);
+ ngx_conf_merge_uint_value(conf->modifier2, prev->modifier2, 0);
+
+ if (conf->params_source == NULL) {
+ conf->flushes = prev->flushes;
+ conf->params_len = prev->params_len;
+ conf->params = prev->params;
+ conf->params_source = prev->params_source;
+ conf->headers_hash = prev->headers_hash;
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->params_source == NULL) {
+
+ if ((conf->upstream.cache == NULL)
+ == (prev->upstream.cache == NULL))
+ {
+ return NGX_CONF_OK;
+ }
+
+ /* 6 is a number of ngx_http_uwsgi_cache_headers entries */
+ conf->params_source = ngx_array_create(cf->pool, 6,
+ sizeof(ngx_keyval_t));
+ if (conf->params_source == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+#else
+
+ if (conf->params_source == NULL) {
+ return NGX_CONF_OK;
+ }
+
+#endif
+ }
+
+ conf->params_len = ngx_array_create(cf->pool, 64, 1);
+ if (conf->params_len == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->params = ngx_array_create(cf->pool, 512, 1);
+ if (conf->params == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ src = conf->params_source->elts;
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->upstream.cache) {
+ ngx_keyval_t *h, *s;
+
+ for (h = ngx_http_uwsgi_cache_headers; h->key.len; h++) {
+
+ for (i = 0; i < conf->params_source->nelts; i++) {
+ if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) {
+ goto next;
+ }
+ }
+
+ s = ngx_array_push(conf->params_source);
+ if (s == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *s = *h;
+
+ src = conf->params_source->elts;
+
+ next:
+
+ h++;
+ }
+ }
+
+#endif
+
+ for (i = 0; i < conf->params_source->nelts; 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_CONF_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_CONF_ERROR;
+ }
+
+ copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code;
+ copy->len = src[i].key.len;
+
+
+ size = (sizeof(ngx_http_script_copy_code_t)
+ + src[i].key.len + sizeof(uintptr_t) - 1)
+ & ~(sizeof(uintptr_t) - 1);
+
+ copy = ngx_array_push_n(conf->params, size);
+ if (copy == NULL) {
+ return NGX_CONF_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_CONF_ERROR;
+ }
+
+ code = ngx_array_push_n(conf->params_len, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+
+
+ code = ngx_array_push_n(conf->params, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+ }
+
+ code = ngx_array_push_n(conf->params_len, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_CONF_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 = "uwsgi_params_hash";
+ hash.pool = cf->pool;
+ hash.temp_pool = NULL;
+
+ if (ngx_hash_init(&hash, headers_names.elts, headers_names.nelts) != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_uwsgi_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_uwsgi_loc_conf_t *uwcf = 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 (uwcf->upstream.upstream || uwcf->uwsgi_lengths) {
+ return "is duplicate";
+ }
+
+ clcf = ngx_http_conf_get_module_loc_conf (cf, ngx_http_core_module);
+ clcf->handler = ngx_http_uwsgi_handler;
+
+ value = cf->args->elts;
+
+ url = &value[1];
+
+ n = ngx_http_script_variables_count(url);
+
+ if (n) {
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = url;
+ sc.lengths = &uwcf->uwsgi_lengths;
+ sc.values = &uwcf->uwsgi_values;
+ sc.variables = n;
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+ }
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.url = value[1];
+ u.no_resolve = 1;
+
+ uwcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
+ if (uwcf->upstream.upstream == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (clcf->name.data[clcf->name.len - 1] == '/') {
+ clcf->auto_redirect = 1;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_uwsgi_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_uwsgi_loc_conf_t *uwcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_script_compile_t sc;
+
+ if (uwcf->upstream.store != NGX_CONF_UNSET || uwcf->upstream.store_lengths)
+ {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ uwcf->upstream.store = 0;
+ return NGX_CONF_OK;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ if (uwcf->upstream.cache != NGX_CONF_UNSET_PTR
+ && uwcf->upstream.cache != NULL)
+ {
+ return "is incompatible with \"uwsgi_cache\"";
+ }
+
+#endif
+
+ if (ngx_strcmp(value[1].data, "on") == 0) {
+ uwcf->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 = &uwcf->upstream.store_lengths;
+ sc.values = &uwcf->upstream.store_values;
+ sc.variables = ngx_http_script_variables_count(&value[1]);;
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static char *
+ngx_http_uwsgi_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_uwsgi_loc_conf_t *uwcf = conf;
+
+ ngx_str_t *value;
+
+ value = cf->args->elts;
+
+ if (uwcf->upstream.cache != NGX_CONF_UNSET_PTR) {
+ return "is duplicate";
+ }
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ uwcf->upstream.cache = NULL;
+ return NGX_CONF_OK;
+ }
+
+ if (uwcf->upstream.store > 0 || uwcf->upstream.store_lengths) {
+ return "is incompatible with \"uwsgi_store\"";
+ }
+
+ uwcf->upstream.cache = ngx_shared_memory_add(cf, &value[1], 0,
+ &ngx_http_uwsgi_module);
+ if (uwcf->upstream.cache == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_uwsgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_uwsgi_loc_conf_t *uwcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ if (uwcf->cache_key.value.len) {
+ return "is duplicate";
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &uwcf->cache_key;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+#endif
diff --git a/usr.sbin/nginx/src/http/modules/ngx_http_xslt_filter_module.c b/usr.sbin/nginx/src/http/modules/ngx_http_xslt_filter_module.c
new file mode 100644
index 00000000000..d67ec804742
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/ngx_http_xslt_filter_module.c
@@ -0,0 +1,983 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxslt/xslt.h>
+#include <libxslt/xsltInternals.h>
+#include <libxslt/transform.h>
+#include <libxslt/xsltutils.h>
+
+#if (NGX_HAVE_EXSLT)
+#include <libexslt/exslt.h>
+#endif
+
+
+#ifndef NGX_HTTP_XSLT_REUSE_DTD
+#define NGX_HTTP_XSLT_REUSE_DTD 1
+#endif
+
+
+typedef struct {
+ u_char *name;
+ void *data;
+} ngx_http_xslt_file_t;
+
+
+typedef struct {
+ ngx_array_t dtd_files; /* ngx_http_xslt_file_t */
+ ngx_array_t sheet_files; /* ngx_http_xslt_file_t */
+} ngx_http_xslt_filter_main_conf_t;
+
+
+typedef struct {
+ xsltStylesheetPtr stylesheet;
+ ngx_array_t params; /* ngx_http_complex_value_t */
+} ngx_http_xslt_sheet_t;
+
+
+typedef struct {
+ xmlDtdPtr dtd;
+ ngx_array_t sheets; /* ngx_http_xslt_sheet_t */
+ ngx_hash_t types;
+ ngx_array_t *types_keys;
+} ngx_http_xslt_filter_loc_conf_t;
+
+
+typedef struct {
+ xmlDocPtr doc;
+ xmlParserCtxtPtr ctxt;
+ ngx_http_request_t *request;
+ ngx_array_t params;
+
+ ngx_uint_t done; /* unsigned done:1; */
+} ngx_http_xslt_filter_ctx_t;
+
+
+static ngx_int_t ngx_http_xslt_send(ngx_http_request_t *r,
+ ngx_http_xslt_filter_ctx_t *ctx, ngx_buf_t *b);
+static ngx_int_t ngx_http_xslt_add_chunk(ngx_http_request_t *r,
+ ngx_http_xslt_filter_ctx_t *ctx, ngx_buf_t *b);
+
+
+static void ngx_http_xslt_sax_external_subset(void *data, const xmlChar *name,
+ const xmlChar *externalId, const xmlChar *systemId);
+static void ngx_cdecl ngx_http_xslt_sax_error(void *data, const char *msg, ...);
+
+
+static ngx_buf_t *ngx_http_xslt_apply_stylesheet(ngx_http_request_t *r,
+ ngx_http_xslt_filter_ctx_t *ctx);
+static ngx_int_t ngx_http_xslt_params(ngx_http_request_t *r,
+ ngx_http_xslt_filter_ctx_t *ctx, ngx_array_t *params);
+static u_char *ngx_http_xslt_content_type(xsltStylesheetPtr s);
+static u_char *ngx_http_xslt_encoding(xsltStylesheetPtr s);
+static void ngx_http_xslt_cleanup(void *data);
+
+static char *ngx_http_xslt_entities(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_xslt_stylesheet(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static void ngx_http_xslt_cleanup_dtd(void *data);
+static void ngx_http_xslt_cleanup_stylesheet(void *data);
+static void *ngx_http_xslt_filter_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_xslt_filter_create_conf(ngx_conf_t *cf);
+static char *ngx_http_xslt_filter_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static ngx_int_t ngx_http_xslt_filter_init(ngx_conf_t *cf);
+static void ngx_http_xslt_filter_exit(ngx_cycle_t *cycle);
+
+
+ngx_str_t ngx_http_xslt_default_types[] = {
+ ngx_string("text/xml"),
+ ngx_null_string
+};
+
+
+static ngx_command_t ngx_http_xslt_filter_commands[] = {
+
+ { ngx_string("xml_entities"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_xslt_entities,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("xslt_stylesheet"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_xslt_stylesheet,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("xslt_types"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_types_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_xslt_filter_loc_conf_t, types_keys),
+ &ngx_http_xslt_default_types[0] },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_xslt_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_xslt_filter_init, /* postconfiguration */
+
+ ngx_http_xslt_filter_create_main_conf, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_xslt_filter_create_conf, /* create location configuration */
+ ngx_http_xslt_filter_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_xslt_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_xslt_filter_module_ctx, /* module context */
+ ngx_http_xslt_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ ngx_http_xslt_filter_exit, /* exit process */
+ ngx_http_xslt_filter_exit, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_xslt_header_filter(ngx_http_request_t *r)
+{
+ ngx_http_xslt_filter_ctx_t *ctx;
+ ngx_http_xslt_filter_loc_conf_t *conf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter header");
+
+ if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module);
+
+ if (conf->sheets.nelts == 0
+ || ngx_http_test_content_type(r, &conf->types) == NULL)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_xslt_filter_module);
+
+ if (ctx) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_xslt_filter_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_xslt_filter_module);
+
+ r->main_filter_need_in_memory = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_xslt_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ int wellFormed;
+ ngx_chain_t *cl;
+ ngx_http_xslt_filter_ctx_t *ctx;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter body");
+
+ if (in == NULL) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_xslt_filter_module);
+
+ if (ctx == NULL || ctx->done) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ for (cl = in; cl; cl = cl->next) {
+
+ if (ngx_http_xslt_add_chunk(r, ctx, cl->buf) != NGX_OK) {
+
+ if (ctx->ctxt->myDoc) {
+
+#if (NGX_HTTP_XSLT_REUSE_DTD)
+ ctx->ctxt->myDoc->extSubset = NULL;
+#endif
+ xmlFreeDoc(ctx->ctxt->myDoc);
+ }
+
+ xmlFreeParserCtxt(ctx->ctxt);
+
+ return ngx_http_xslt_send(r, ctx, NULL);
+ }
+
+ if (cl->buf->last_buf || cl->buf->last_in_chain) {
+
+ ctx->doc = ctx->ctxt->myDoc;
+
+#if (NGX_HTTP_XSLT_REUSE_DTD)
+ ctx->doc->extSubset = NULL;
+#endif
+
+ wellFormed = ctx->ctxt->wellFormed;
+
+ xmlFreeParserCtxt(ctx->ctxt);
+
+ if (wellFormed) {
+ return ngx_http_xslt_send(r, ctx,
+ ngx_http_xslt_apply_stylesheet(r, ctx));
+ }
+
+ xmlFreeDoc(ctx->doc);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "not well formed XML document");
+
+ return ngx_http_xslt_send(r, ctx, NULL);
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_xslt_send(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx,
+ ngx_buf_t *b)
+{
+ ngx_int_t rc;
+ ngx_chain_t out;
+ ngx_pool_cleanup_t *cln;
+
+ ctx->done = 1;
+
+ if (b == NULL) {
+ return ngx_http_filter_finalize_request(r, NULL,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ }
+
+ cln = ngx_pool_cleanup_add(r->pool, 0);
+
+ if (cln == NULL) {
+ ngx_free(b->pos);
+ return ngx_http_filter_finalize_request(r, NULL,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ }
+
+ if (r == r->main) {
+ r->headers_out.content_length_n = b->last - b->pos;
+
+ if (r->headers_out.content_length) {
+ r->headers_out.content_length->hash = 0;
+ r->headers_out.content_length = NULL;
+ }
+
+ ngx_http_clear_last_modified(r);
+ }
+
+ rc = ngx_http_next_header_filter(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ ngx_free(b->pos);
+ return rc;
+ }
+
+ cln->handler = ngx_http_xslt_cleanup;
+ cln->data = b->pos;
+
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_next_body_filter(r, &out);
+}
+
+
+static ngx_int_t
+ngx_http_xslt_add_chunk(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx,
+ ngx_buf_t *b)
+{
+ int err;
+ xmlParserCtxtPtr ctxt;
+
+ if (ctx->ctxt == NULL) {
+
+ ctxt = xmlCreatePushParserCtxt(NULL, NULL, NULL, 0, NULL);
+ if (ctxt == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "xmlCreatePushParserCtxt() failed");
+ return NGX_ERROR;
+ }
+
+ ctxt->sax->externalSubset = ngx_http_xslt_sax_external_subset;
+ ctxt->sax->setDocumentLocator = NULL;
+ ctxt->sax->warning = NULL;
+ ctxt->sax->error = ngx_http_xslt_sax_error;
+ ctxt->sax->fatalError = ngx_http_xslt_sax_error;
+ ctxt->sax->_private = ctx;
+ ctxt->replaceEntities = 1;
+ ctxt->loadsubset = 1;
+
+ ctx->ctxt = ctxt;
+ ctx->request = r;
+ }
+
+ err = xmlParseChunk(ctx->ctxt, (char *) b->pos, (int) (b->last - b->pos),
+ (b->last_buf) || (b->last_in_chain));
+
+ if (err == 0) {
+ b->pos = b->last;
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "xmlParseChunk() failed, error:%d", err);
+
+ return NGX_ERROR;
+}
+
+
+static void
+ngx_http_xslt_sax_external_subset(void *data, const xmlChar *name,
+ const xmlChar *externalId, const xmlChar *systemId)
+{
+ xmlParserCtxtPtr ctxt = data;
+
+ xmlDocPtr doc;
+ xmlDtdPtr dtd;
+ ngx_http_request_t *r;
+ ngx_http_xslt_filter_ctx_t *ctx;
+ ngx_http_xslt_filter_loc_conf_t *conf;
+
+ ctx = ctxt->sax->_private;
+ r = ctx->request;
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter extSubset: \"%s\" \"%s\" \"%s\"",
+ name ? name : (xmlChar *) "",
+ externalId ? externalId : (xmlChar *) "",
+ systemId ? systemId : (xmlChar *) "");
+
+ doc = ctxt->myDoc;
+
+#if (NGX_HTTP_XSLT_REUSE_DTD)
+
+ dtd = conf->dtd;
+
+#else
+
+ dtd = xmlCopyDtd(conf->dtd);
+ if (dtd == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "xmlCopyDtd() failed");
+ return;
+ }
+
+ if (doc->children == NULL) {
+ xmlAddChild((xmlNodePtr) doc, (xmlNodePtr) dtd);
+
+ } else {
+ xmlAddPrevSibling(doc->children, (xmlNodePtr) dtd);
+ }
+
+#endif
+
+ doc->extSubset = dtd;
+}
+
+
+static void ngx_cdecl
+ngx_http_xslt_sax_error(void *data, const char *msg, ...)
+{
+ xmlParserCtxtPtr ctxt = data;
+
+ size_t n;
+ va_list args;
+ ngx_http_xslt_filter_ctx_t *ctx;
+ u_char buf[NGX_MAX_ERROR_STR];
+
+ ctx = ctxt->sax->_private;
+
+ buf[0] = '\0';
+
+ va_start(args, msg);
+ n = (size_t) vsnprintf((char *) buf, NGX_MAX_ERROR_STR, msg, args);
+ va_end(args);
+
+ while (--n && (buf[n] == CR || buf[n] == LF)) { /* void */ }
+
+ ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0,
+ "libxml2 error: \"%*s\"", n + 1, buf);
+}
+
+
+static ngx_buf_t *
+ngx_http_xslt_apply_stylesheet(ngx_http_request_t *r,
+ ngx_http_xslt_filter_ctx_t *ctx)
+{
+ int len, rc, doc_type;
+ u_char *type, *encoding;
+ ngx_buf_t *b;
+ ngx_uint_t i;
+ xmlChar *buf;
+ xmlDocPtr doc, res;
+ ngx_http_xslt_sheet_t *sheet;
+ ngx_http_xslt_filter_loc_conf_t *conf;
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module);
+ sheet = conf->sheets.elts;
+ doc = ctx->doc;
+
+ /* preallocate array for 4 params */
+
+ if (ngx_array_init(&ctx->params, r->pool, 4 * 2 + 1, sizeof(char *))
+ != NGX_OK)
+ {
+ xmlFreeDoc(doc);
+ return NULL;
+ }
+
+ for (i = 0; i < conf->sheets.nelts; i++) {
+
+ if (ngx_http_xslt_params(r, ctx, &sheet[i].params) != NGX_OK) {
+ xmlFreeDoc(doc);
+ return NULL;
+ }
+
+ res = xsltApplyStylesheet(sheet[i].stylesheet, doc, ctx->params.elts);
+
+ xmlFreeDoc(doc);
+
+ if (res == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "xsltApplyStylesheet() failed");
+ return NULL;
+ }
+
+ doc = res;
+
+ /* reset array elements */
+ ctx->params.nelts = 0;
+ }
+
+ /* there must be at least one stylesheet */
+
+ if (r == r->main) {
+ type = ngx_http_xslt_content_type(sheet[i - 1].stylesheet);
+
+ } else {
+ type = NULL;
+ }
+
+ encoding = ngx_http_xslt_encoding(sheet[i - 1].stylesheet);
+ doc_type = doc->type;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter type: %d t:%s e:%s",
+ doc_type, type ? type : (u_char *) "(null)",
+ encoding ? encoding : (u_char *) "(null)");
+
+ rc = xsltSaveResultToString(&buf, &len, doc, sheet[i - 1].stylesheet);
+
+ xmlFreeDoc(doc);
+
+ if (rc != 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "xsltSaveResultToString() failed");
+ return NULL;
+ }
+
+ if (len == 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "xsltSaveResultToString() returned zero-length result");
+ return NULL;
+ }
+
+ b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
+ if (b == NULL) {
+ ngx_free(buf);
+ return NULL;
+ }
+
+ b->pos = buf;
+ b->last = buf + len;
+ b->memory = 1;
+
+ if (encoding) {
+ r->headers_out.charset.len = ngx_strlen(encoding);
+ r->headers_out.charset.data = encoding;
+ }
+
+ if (r != r->main) {
+ return b;
+ }
+
+ b->last_buf = 1;
+
+ if (type) {
+ len = ngx_strlen(type);
+
+ r->headers_out.content_type_len = len;
+ r->headers_out.content_type.len = len;
+ r->headers_out.content_type.data = type;
+
+ } else if (doc_type == XML_HTML_DOCUMENT_NODE) {
+
+ r->headers_out.content_type_len = sizeof("text/html") - 1;
+ ngx_str_set(&r->headers_out.content_type, "text/html");
+ }
+
+ r->headers_out.content_type_lowcase = NULL;
+
+ return b;
+}
+
+
+static ngx_int_t
+ngx_http_xslt_params(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx,
+ ngx_array_t *params)
+{
+ u_char *p, *last, *value, *dst, *src, **s;
+ size_t len;
+ ngx_uint_t i;
+ ngx_str_t string;
+ ngx_http_complex_value_t *param;
+
+ param = params->elts;
+
+ for (i = 0; i < params->nelts; i++) {
+
+ if (ngx_http_complex_value(r, &param[i], &string) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter param: \"%s\"", string.data);
+
+ p = string.data;
+ last = string.data + string.len - 1;
+
+ while (p && *p) {
+
+ value = p;
+ p = (u_char *) ngx_strchr(p, '=');
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid libxslt parameter \"%s\"", value);
+ return NGX_ERROR;
+ }
+ *p++ = '\0';
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter param name: \"%s\"", value);
+
+ s = ngx_array_push(&ctx->params);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ *s = value;
+
+ value = p;
+ p = (u_char *) ngx_strchr(p, ':');
+
+ if (p) {
+ len = p - value;
+ *p++ = '\0';
+
+ } else {
+ len = last - value;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter param value: \"%s\"", value);
+
+ dst = value;
+ src = value;
+
+ ngx_unescape_uri(&dst, &src, len, 0);
+
+ *dst = '\0';
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter param unescaped: \"%s\"", value);
+
+ s = ngx_array_push(&ctx->params);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ *s = value;
+ }
+ }
+
+ s = ngx_array_push(&ctx->params);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ *s = NULL;
+
+ return NGX_OK;
+}
+
+
+static u_char *
+ngx_http_xslt_content_type(xsltStylesheetPtr s)
+{
+ u_char *type;
+
+ if (s->mediaType) {
+ return s->mediaType;
+ }
+
+ for (s = s->imports; s; s = s->next) {
+
+ type = ngx_http_xslt_content_type(s);
+
+ if (type) {
+ return type;
+ }
+ }
+
+ return NULL;
+}
+
+
+static u_char *
+ngx_http_xslt_encoding(xsltStylesheetPtr s)
+{
+ u_char *encoding;
+
+ if (s->encoding) {
+ return s->encoding;
+ }
+
+ for (s = s->imports; s; s = s->next) {
+
+ encoding = ngx_http_xslt_encoding(s);
+
+ if (encoding) {
+ return encoding;
+ }
+ }
+
+ return NULL;
+}
+
+
+static void
+ngx_http_xslt_cleanup(void *data)
+{
+ ngx_free(data);
+}
+
+
+static char *
+ngx_http_xslt_entities(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_xslt_filter_loc_conf_t *xlcf = conf;
+
+ ngx_str_t *value;
+ ngx_uint_t i;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_xslt_file_t *file;
+ ngx_http_xslt_filter_main_conf_t *xmcf;
+
+ if (xlcf->dtd) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ xmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_xslt_filter_module);
+
+ file = xmcf->dtd_files.elts;
+ for (i = 0; i < xmcf->dtd_files.nelts; i++) {
+ if (ngx_strcmp(file[i].name, &value[1].data) == 0) {
+ xlcf->dtd = file[i].data;
+ return NGX_CONF_OK;
+ }
+ }
+
+ cln = ngx_pool_cleanup_add(cf->pool, 0);
+ if (cln == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ xlcf->dtd = xmlParseDTD(NULL, (xmlChar *) value[1].data);
+
+ if (xlcf->dtd == NULL) {
+ ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "xmlParseDTD() failed");
+ return NGX_CONF_ERROR;
+ }
+
+ cln->handler = ngx_http_xslt_cleanup_dtd;
+ cln->data = xlcf->dtd;
+
+ file = ngx_array_push(&xmcf->dtd_files);
+ if (file == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ file->name = value[1].data;
+ file->data = xlcf->dtd;
+
+ return NGX_CONF_OK;
+}
+
+
+
+static char *
+ngx_http_xslt_stylesheet(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_xslt_filter_loc_conf_t *xlcf = conf;
+
+ ngx_str_t *value;
+ ngx_uint_t i, n;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_xslt_file_t *file;
+ ngx_http_xslt_sheet_t *sheet;
+ ngx_http_complex_value_t *param;
+ ngx_http_compile_complex_value_t ccv;
+ ngx_http_xslt_filter_main_conf_t *xmcf;
+
+ value = cf->args->elts;
+
+ if (xlcf->sheets.elts == NULL) {
+ if (ngx_array_init(&xlcf->sheets, cf->pool, 1,
+ sizeof(ngx_http_xslt_sheet_t))
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ sheet = ngx_array_push(&xlcf->sheets);
+ if (sheet == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(sheet, sizeof(ngx_http_xslt_sheet_t));
+
+ if (ngx_conf_full_name(cf->cycle, &value[1], 0) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ xmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_xslt_filter_module);
+
+ file = xmcf->sheet_files.elts;
+ for (i = 0; i < xmcf->sheet_files.nelts; i++) {
+ if (ngx_strcmp(file[i].name, &value[1].data) == 0) {
+ sheet->stylesheet = file[i].data;
+ goto found;
+ }
+ }
+
+ cln = ngx_pool_cleanup_add(cf->pool, 0);
+ if (cln == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ sheet->stylesheet = xsltParseStylesheetFile(value[1].data);
+ if (sheet->stylesheet == NULL) {
+ ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
+ "xsltParseStylesheetFile(\"%s\") failed",
+ value[1].data);
+ return NGX_CONF_ERROR;
+ }
+
+ cln->handler = ngx_http_xslt_cleanup_stylesheet;
+ cln->data = sheet->stylesheet;
+
+ file = ngx_array_push(&xmcf->sheet_files);
+ if (file == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ file->name = value[1].data;
+ file->data = sheet->stylesheet;
+
+found:
+
+ n = cf->args->nelts;
+
+ if (n == 2) {
+ return NGX_CONF_OK;
+ }
+
+ if (ngx_array_init(&sheet->params, cf->pool, n - 2,
+ sizeof(ngx_http_complex_value_t))
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 2; i < n; i++) {
+
+ param = ngx_array_push(&sheet->params);
+ if (param == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[i];
+ ccv.complex_value = param;
+ ccv.zero = 1;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static void
+ngx_http_xslt_cleanup_dtd(void *data)
+{
+ xmlFreeDtd(data);
+}
+
+
+static void
+ngx_http_xslt_cleanup_stylesheet(void *data)
+{
+ xsltFreeStylesheet(data);
+}
+
+
+static void *
+ngx_http_xslt_filter_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_xslt_filter_main_conf_t *conf;
+
+ conf = ngx_palloc(cf->pool, sizeof(ngx_http_xslt_filter_main_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ if (ngx_array_init(&conf->dtd_files, cf->pool, 1,
+ sizeof(ngx_http_xslt_file_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ if (ngx_array_init(&conf->sheet_files, cf->pool, 1,
+ sizeof(ngx_http_xslt_file_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ return conf;
+}
+
+
+static void *
+ngx_http_xslt_filter_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_xslt_filter_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_xslt_filter_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->dtd = NULL;
+ * conf->sheets = { NULL };
+ * conf->types = { NULL };
+ * conf->types_keys = NULL;
+ */
+
+ return conf;
+}
+
+
+static char *
+ngx_http_xslt_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_xslt_filter_loc_conf_t *prev = parent;
+ ngx_http_xslt_filter_loc_conf_t *conf = child;
+
+ if (conf->dtd == NULL) {
+ conf->dtd = prev->dtd;
+ }
+
+ if (conf->sheets.nelts == 0) {
+ conf->sheets = prev->sheets;
+ }
+
+ if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
+ &prev->types_keys, &prev->types,
+ ngx_http_xslt_default_types)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_xslt_filter_init(ngx_conf_t *cf)
+{
+ xmlInitParser();
+
+#if (NGX_HAVE_EXSLT)
+ exsltRegisterAll();
+#endif
+
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_xslt_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_xslt_body_filter;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_xslt_filter_exit(ngx_cycle_t *cycle)
+{
+ xsltCleanupGlobals();
+ xmlCleanupParser();
+}
diff --git a/usr.sbin/nginx/src/http/modules/perl/Makefile.PL b/usr.sbin/nginx/src/http/modules/perl/Makefile.PL
new file mode 100644
index 00000000000..c33b1fc72ff
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/perl/Makefile.PL
@@ -0,0 +1,37 @@
+
+# Copyright (C) Igor Sysoev
+
+use 5.006001;
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+ NAME => 'nginx',
+ VERSION_FROM => 'nginx.pm', # finds $VERSION
+ PREREQ_PM => {}, # e.g., Module::Name => 1.1
+
+ ABSTRACT_FROM => 'nginx.pm', # retrieve abstract from module
+ AUTHOR => 'Igor Sysoev',
+
+ CCFLAGS => "$ENV{NGX_PM_CFLAGS}",
+ OPTIMIZE => '-O',
+
+ INC => "-I ../../../../../src/core " .
+ "-I ../../../../../src/event " .
+ "-I ../../../../../src/os/unix " .
+ "-I ../../../../../src/http " .
+ "-I ../../../../../src/http/modules " .
+ "-I ../../../../../src/http/modules/perl " .
+ "-I ../../../../../$ENV{NGX_OBJS} " .
+ ($ENV{NGX_PCRE} =~ /^(YES|NO)/ ? "" :
+ ($ENV{NGX_PCRE} =~ m#^/# ? "-I $ENV{NGX_PCRE} " :
+ "-I ../../../../../$ENV{NGX_PCRE} ")),
+
+ depend => {
+ 'nginx.c' =>
+ "../../../../../src/http/modules/perl/ngx_http_perl_module.h"
+ },
+
+ PM => {
+ 'nginx.pm' => '$(INST_LIBDIR)/nginx.pm'
+ }
+);
diff --git a/usr.sbin/nginx/src/http/modules/perl/nginx.pm b/usr.sbin/nginx/src/http/modules/perl/nginx.pm
new file mode 100644
index 00000000000..72cedd26a79
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/perl/nginx.pm
@@ -0,0 +1,133 @@
+package nginx;
+
+use 5.006001;
+use strict;
+use warnings;
+
+require Exporter;
+
+our @ISA = qw(Exporter);
+
+our @EXPORT = qw(
+ OK
+ DECLINED
+
+ HTTP_OK
+ HTTP_CREATED
+ HTTP_ACCEPTED
+ HTTP_NO_CONTENT
+ HTTP_PARTIAL_CONTENT
+
+ HTTP_MOVED_PERMANENTLY
+ HTTP_MOVED_TEMPORARILY
+ HTTP_REDIRECT
+ HTTP_NOT_MODIFIED
+
+ HTTP_BAD_REQUEST
+ HTTP_UNAUTHORIZED
+ HTTP_PAYMENT_REQUIRED
+ HTTP_FORBIDDEN
+ HTTP_NOT_FOUND
+ HTTP_NOT_ALLOWED
+ HTTP_NOT_ACCEPTABLE
+ HTTP_REQUEST_TIME_OUT
+ HTTP_CONFLICT
+ HTTP_GONE
+ HTTP_LENGTH_REQUIRED
+ HTTP_REQUEST_ENTITY_TOO_LARGE
+ HTTP_REQUEST_URI_TOO_LARGE
+ HTTP_UNSUPPORTED_MEDIA_TYPE
+ HTTP_RANGE_NOT_SATISFIABLE
+
+ HTTP_INTERNAL_SERVER_ERROR
+ HTTP_SERVER_ERROR
+ HTTP_NOT_IMPLEMENTED
+ HTTP_BAD_GATEWAY
+ HTTP_SERVICE_UNAVAILABLE
+ HTTP_GATEWAY_TIME_OUT
+ HTTP_INSUFFICIENT_STORAGE
+);
+
+our $VERSION = '1.0.6';
+
+require XSLoader;
+XSLoader::load('nginx', $VERSION);
+
+# Preloaded methods go here.
+
+use constant OK => 0;
+use constant DECLINED => -5;
+
+use constant HTTP_OK => 200;
+use constant HTTP_CREATED => 201;
+use constant HTTP_ACCEPTED => 202;
+use constant HTTP_NO_CONTENT => 204;
+use constant HTTP_PARTIAL_CONTENT => 206;
+
+use constant HTTP_MOVED_PERMANENTLY => 301;
+use constant HTTP_MOVED_TEMPORARILY => 302;
+use constant HTTP_REDIRECT => 302;
+use constant HTTP_NOT_MODIFIED => 304;
+
+use constant HTTP_BAD_REQUEST => 400;
+use constant HTTP_UNAUTHORIZED => 401;
+use constant HTTP_PAYMENT_REQUIRED => 402;
+use constant HTTP_FORBIDDEN => 403;
+use constant HTTP_NOT_FOUND => 404;
+use constant HTTP_NOT_ALLOWED => 405;
+use constant HTTP_NOT_ACCEPTABLE => 406;
+use constant HTTP_REQUEST_TIME_OUT => 408;
+use constant HTTP_CONFLICT => 409;
+use constant HTTP_GONE => 410;
+use constant HTTP_LENGTH_REQUIRED => 411;
+use constant HTTP_REQUEST_ENTITY_TOO_LARGE => 413;
+use constant HTTP_REQUEST_URI_TOO_LARGE => 414;
+use constant HTTP_UNSUPPORTED_MEDIA_TYPE => 415;
+use constant HTTP_RANGE_NOT_SATISFIABLE => 416;
+
+use constant HTTP_INTERNAL_SERVER_ERROR => 500;
+use constant HTTP_SERVER_ERROR => 500;
+use constant HTTP_NOT_IMPLEMENTED => 501;
+use constant HTTP_BAD_GATEWAY => 502;
+use constant HTTP_SERVICE_UNAVAILABLE => 503;
+use constant HTTP_GATEWAY_TIME_OUT => 504;
+use constant HTTP_INSUFFICIENT_STORAGE => 507;
+
+
+sub rflush {
+ my $r = shift;
+
+ $r->flush;
+}
+
+
+1;
+__END__
+
+=head1 NAME
+
+nginx - Perl interface to the nginx HTTP server API
+
+=head1 SYNOPSIS
+
+ use nginx;
+
+=head1 DESCRIPTION
+
+This module provides a Perl interface to the nginx HTTP server API.
+
+
+=head1 SEE ALSO
+
+http://sysoev.ru/nginx/docs/http/ngx_http_perl_module.html
+
+=head1 AUTHOR
+
+Igor Sysoev
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) Igor Sysoev
+
+
+=cut
diff --git a/usr.sbin/nginx/src/http/modules/perl/nginx.xs b/usr.sbin/nginx/src/http/modules/perl/nginx.xs
new file mode 100644
index 00000000000..035e261eb7e
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/perl/nginx.xs
@@ -0,0 +1,978 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#define PERL_NO_GET_CONTEXT
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_perl_module.h>
+
+#include "XSUB.h"
+
+
+#define ngx_http_perl_set_request(r) \
+ r = INT2PTR(ngx_http_request_t *, SvIV((SV *) SvRV(ST(0))))
+
+
+#define ngx_http_perl_set_targ(p, len) \
+ \
+ SvUPGRADE(TARG, SVt_PV); \
+ SvPOK_on(TARG); \
+ sv_setpvn(TARG, (char *) p, len)
+
+
+static ngx_int_t
+ngx_http_perl_sv2str(pTHX_ ngx_http_request_t *r, ngx_str_t *s, SV *sv)
+{
+ u_char *p;
+ STRLEN len;
+
+ if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PV) {
+ sv = SvRV(sv);
+ }
+
+ p = (u_char *) SvPV(sv, len);
+
+ s->len = len;
+
+ if (SvREADONLY(sv) && SvPOK(sv)) {
+ s->data = p;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl sv2str: %08XD \"%V\"", sv->sv_flags, s);
+
+ return NGX_OK;
+ }
+
+ s->data = ngx_pnalloc(r->pool, len);
+ if (s->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->data, p, len);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl sv2str: %08XD \"%V\"", sv->sv_flags, s);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_perl_output(ngx_http_request_t *r, ngx_buf_t *b)
+{
+ ngx_chain_t out;
+#if (NGX_HTTP_SSI)
+ ngx_chain_t *cl;
+ ngx_http_perl_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+ if (ctx->ssi) {
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+ *ctx->ssi->last_out = cl;
+ ctx->ssi->last_out = &cl->next;
+
+ return NGX_OK;
+ }
+#endif
+
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_output_filter(r, &out);
+}
+
+
+MODULE = nginx PACKAGE = nginx
+
+
+void
+status(r, code)
+ CODE:
+
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+
+ r->headers_out.status = SvIV(ST(1));
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl status: %d", r->headers_out.status);
+
+ XSRETURN_UNDEF;
+
+
+void
+send_http_header(r, ...)
+ CODE:
+
+ ngx_http_request_t *r;
+ SV *sv;
+
+ ngx_http_perl_set_request(r);
+
+ if (r->headers_out.status == 0) {
+ r->headers_out.status = NGX_HTTP_OK;
+ }
+
+ if (items != 1) {
+ sv = ST(1);
+
+ if (ngx_http_perl_sv2str(aTHX_ r, &r->headers_out.content_type, sv)
+ != NGX_OK)
+ {
+ XSRETURN_EMPTY;
+ }
+
+ r->headers_out.content_type_len = r->headers_out.content_type.len;
+
+ } else {
+ if (ngx_http_set_content_type(r) != NGX_OK) {
+ XSRETURN_EMPTY;
+ }
+ }
+
+ (void) ngx_http_send_header(r);
+
+
+void
+header_only(r)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+
+ sv_upgrade(TARG, SVt_IV);
+ sv_setiv(TARG, r->header_only);
+
+ ST(0) = TARG;
+
+
+void
+uri(r)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+ ngx_http_perl_set_targ(r->uri.data, r->uri.len);
+
+ ST(0) = TARG;
+
+
+void
+args(r)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+ ngx_http_perl_set_targ(r->args.data, r->args.len);
+
+ ST(0) = TARG;
+
+
+void
+request_method(r)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+ ngx_http_perl_set_targ(r->method_name.data, r->method_name.len);
+
+ ST(0) = TARG;
+
+
+void
+remote_addr(r)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+ ngx_http_perl_set_targ(r->connection->addr_text.data,
+ r->connection->addr_text.len);
+
+ ST(0) = TARG;
+
+
+void
+header_in(r, key)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+ SV *key;
+ u_char *p, *lowcase_key, *cookie;
+ STRLEN len;
+ ssize_t size;
+ ngx_uint_t i, n, hash;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *h, **ph;
+ ngx_http_header_t *hh;
+ ngx_http_core_main_conf_t *cmcf;
+
+ ngx_http_perl_set_request(r);
+
+ key = ST(1);
+
+ if (SvROK(key) && SvTYPE(SvRV(key)) == SVt_PV) {
+ key = SvRV(key);
+ }
+
+ p = (u_char *) SvPV(key, len);
+
+ /* look up hashed headers */
+
+ lowcase_key = ngx_pnalloc(r->pool, len);
+ if (lowcase_key == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ hash = ngx_hash_strlow(lowcase_key, p, len);
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ hh = ngx_hash_find(&cmcf->headers_in_hash, hash, lowcase_key, len);
+
+ if (hh) {
+ if (hh->offset) {
+
+ ph = (ngx_table_elt_t **) ((char *) &r->headers_in + hh->offset);
+
+ if (*ph) {
+ ngx_http_perl_set_targ((*ph)->value.data, (*ph)->value.len);
+
+ goto done;
+ }
+
+ XSRETURN_UNDEF;
+ }
+
+ /* Cookie */
+
+ n = r->headers_in.cookies.nelts;
+
+ if (n == 0) {
+ XSRETURN_UNDEF;
+ }
+
+ ph = r->headers_in.cookies.elts;
+
+ if (n == 1) {
+ ngx_http_perl_set_targ((*ph)->value.data, (*ph)->value.len);
+
+ goto done;
+ }
+
+ size = - (ssize_t) (sizeof("; ") - 1);
+
+ for (i = 0; i < n; i++) {
+ size += ph[i]->value.len + sizeof("; ") - 1;
+ }
+
+ cookie = ngx_pnalloc(r->pool, size);
+ if (cookie == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ p = cookie;
+
+ for (i = 0; /* void */ ; i++) {
+ p = ngx_copy(p, ph[i]->value.data, ph[i]->value.len);
+
+ if (i == n - 1) {
+ break;
+ }
+
+ *p++ = ';'; *p++ = ' ';
+ }
+
+ ngx_http_perl_set_targ(cookie, size);
+
+ goto done;
+ }
+
+ /* iterate over all headers */
+
+ part = &r->headers_in.headers.part;
+ h = part->elts;
+
+ for (i = 0; /* void */ ; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ h = part->elts;
+ i = 0;
+ }
+
+ if (len != h[i].key.len
+ || ngx_strcasecmp(p, h[i].key.data) != 0)
+ {
+ continue;
+ }
+
+ ngx_http_perl_set_targ(h[i].value.data, h[i].value.len);
+
+ goto done;
+ }
+
+ XSRETURN_UNDEF;
+
+ done:
+
+ ST(0) = TARG;
+
+
+void
+has_request_body(r, next)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+ ngx_http_perl_ctx_t *ctx;
+
+ ngx_http_perl_set_request(r);
+
+ if (r->headers_in.content_length_n <= 0) {
+ XSRETURN_UNDEF;
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+ ctx->next = SvRV(ST(1));
+
+ r->request_body_in_single_buf = 1;
+ r->request_body_in_persistent_file = 1;
+ r->request_body_in_clean_file = 1;
+
+ if (r->request_body_in_file_only) {
+ r->request_body_file_log_level = 0;
+ }
+
+ ngx_http_read_client_request_body(r, ngx_http_perl_handle_request);
+
+ sv_upgrade(TARG, SVt_IV);
+ sv_setiv(TARG, 1);
+
+ ST(0) = TARG;
+
+
+void
+request_body(r)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+ size_t len;
+
+ ngx_http_perl_set_request(r);
+
+ if (r->request_body == NULL
+ || r->request_body->temp_file
+ || r->request_body->bufs == NULL)
+ {
+ XSRETURN_UNDEF;
+ }
+
+ len = r->request_body->bufs->buf->last - r->request_body->bufs->buf->pos;
+
+ if (len == 0) {
+ XSRETURN_UNDEF;
+ }
+
+ ngx_http_perl_set_targ(r->request_body->bufs->buf->pos, len);
+
+ ST(0) = TARG;
+
+
+void
+request_body_file(r)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+
+ if (r->request_body == NULL || r->request_body->temp_file == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ ngx_http_perl_set_targ(r->request_body->temp_file->file.name.data,
+ r->request_body->temp_file->file.name.len);
+
+ ST(0) = TARG;
+
+
+void
+discard_request_body(r)
+ CODE:
+
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+
+ ngx_http_discard_request_body(r);
+
+
+void
+header_out(r, key, value)
+ CODE:
+
+ ngx_http_request_t *r;
+ SV *key;
+ SV *value;
+ ngx_table_elt_t *header;
+
+ ngx_http_perl_set_request(r);
+
+ key = ST(1);
+ value = ST(2);
+
+ header = ngx_list_push(&r->headers_out.headers);
+ if (header == NULL) {
+ XSRETURN_EMPTY;
+ }
+
+ header->hash = 1;
+
+ if (ngx_http_perl_sv2str(aTHX_ r, &header->key, key) != NGX_OK) {
+ XSRETURN_EMPTY;
+ }
+
+ if (ngx_http_perl_sv2str(aTHX_ r, &header->value, value) != NGX_OK) {
+ XSRETURN_EMPTY;
+ }
+
+ if (header->key.len == sizeof("Content-Length") - 1
+ && ngx_strncasecmp(header->key.data, (u_char *) "Content-Length",
+ sizeof("Content-Length") - 1) == 0)
+ {
+ r->headers_out.content_length_n = (off_t) SvIV(value);
+ r->headers_out.content_length = header;
+ }
+
+
+void
+filename(r)
+ CODE:
+
+ dXSTARG;
+ size_t root;
+ ngx_http_request_t *r;
+ ngx_http_perl_ctx_t *ctx;
+
+ ngx_http_perl_set_request(r);
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+ if (ctx->filename.data) {
+ goto done;
+ }
+
+ if (ngx_http_map_uri_to_path(r, &ctx->filename, &root, 0) == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ ctx->filename.len--;
+ sv_setpv(PL_statname, (char *) ctx->filename.data);
+
+ done:
+
+ ngx_http_perl_set_targ(ctx->filename.data, ctx->filename.len);
+
+ ST(0) = TARG;
+
+
+void
+print(r, ...)
+ CODE:
+
+ ngx_http_request_t *r;
+ SV *sv;
+ int i;
+ u_char *p;
+ size_t size;
+ STRLEN len;
+ ngx_buf_t *b;
+
+ ngx_http_perl_set_request(r);
+
+ if (items == 2) {
+
+ /*
+ * do zero copy for prolate single read-only SV:
+ * $r->print("some text\n");
+ */
+
+ sv = ST(1);
+
+ if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PV) {
+ sv = SvRV(sv);
+ }
+
+ if (SvREADONLY(sv) && SvPOK(sv)) {
+
+ p = (u_char *) SvPV(sv, len);
+
+ if (len == 0) {
+ XSRETURN_EMPTY;
+ }
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ XSRETURN_EMPTY;
+ }
+
+ b->memory = 1;
+ b->pos = p;
+ b->last = p + len;
+ b->start = p;
+ b->end = b->last;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "$r->print: read-only SV: %z", len);
+
+ goto out;
+ }
+ }
+
+ size = 0;
+
+ for (i = 1; i < items; i++) {
+
+ sv = ST(i);
+
+ if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PV) {
+ sv = SvRV(sv);
+ }
+
+ (void) SvPV(sv, len);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "$r->print: copy SV: %z", len);
+
+ size += len;
+ }
+
+ if (size == 0) {
+ XSRETURN_EMPTY;
+ }
+
+ b = ngx_create_temp_buf(r->pool, size);
+ if (b == NULL) {
+ XSRETURN_EMPTY;
+ }
+
+ for (i = 1; i < items; i++) {
+ sv = ST(i);
+
+ if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PV) {
+ sv = SvRV(sv);
+ }
+
+ p = (u_char *) SvPV(sv, len);
+ b->last = ngx_cpymem(b->last, p, len);
+ }
+
+ out:
+
+ (void) ngx_http_perl_output(r, b);
+
+
+void
+sendfile(r, filename, offset = -1, bytes = 0)
+ CODE:
+
+ ngx_http_request_t *r;
+ char *filename;
+ off_t offset;
+ size_t bytes;
+ ngx_str_t path;
+ ngx_buf_t *b;
+ ngx_open_file_info_t of;
+ ngx_http_core_loc_conf_t *clcf;
+
+ ngx_http_perl_set_request(r);
+
+ filename = SvPV_nolen(ST(1));
+
+ if (filename == NULL) {
+ croak("sendfile(): NULL filename");
+ }
+
+ offset = items < 3 ? -1 : SvIV(ST(2));
+ bytes = items < 4 ? 0 : SvIV(ST(3));
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ XSRETURN_EMPTY;
+ }
+
+ b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
+ if (b->file == NULL) {
+ XSRETURN_EMPTY;
+ }
+
+ path.len = ngx_strlen(filename);
+
+ path.data = ngx_pnalloc(r->pool, path.len + 1);
+ if (path.data == NULL) {
+ XSRETURN_EMPTY;
+ }
+
+ (void) ngx_cpystrn(path.data, (u_char *) filename, path.len + 1);
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+ of.read_ahead = clcf->read_ahead;
+ of.directio = clcf->directio;
+ of.valid = clcf->open_file_cache_valid;
+ of.min_uses = clcf->open_file_cache_min_uses;
+ of.errors = clcf->open_file_cache_errors;
+ of.events = clcf->open_file_cache_events;
+
+ if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+ != NGX_OK)
+ {
+ if (of.err == 0) {
+ XSRETURN_EMPTY;
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+ "%s \"%s\" failed", of.failed, filename);
+ XSRETURN_EMPTY;
+ }
+
+ if (offset == -1) {
+ offset = 0;
+ }
+
+ if (bytes == 0) {
+ bytes = of.size - offset;
+ }
+
+ b->in_file = 1;
+
+ b->file_pos = offset;
+ b->file_last = offset + bytes;
+
+ b->file->fd = of.fd;
+ b->file->log = r->connection->log;
+ b->file->directio = of.is_directio;
+
+ (void) ngx_http_perl_output(r, b);
+
+
+void
+flush(r)
+ CODE:
+
+ ngx_http_request_t *r;
+ ngx_buf_t *b;
+
+ ngx_http_perl_set_request(r);
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ XSRETURN_EMPTY;
+ }
+
+ b->flush = 1;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "$r->flush");
+
+ (void) ngx_http_perl_output(r, b);
+
+ XSRETURN_EMPTY;
+
+
+void
+internal_redirect(r, uri)
+ CODE:
+
+ ngx_http_request_t *r;
+ SV *uri;
+ ngx_uint_t i;
+ ngx_http_perl_ctx_t *ctx;
+
+ ngx_http_perl_set_request(r);
+
+ uri = ST(1);
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+ if (ngx_http_perl_sv2str(aTHX_ r, &ctx->redirect_uri, uri) != NGX_OK) {
+ XSRETURN_EMPTY;
+ }
+
+ for (i = 0; i < ctx->redirect_uri.len; i++) {
+ if (ctx->redirect_uri.data[i] == '?') {
+
+ ctx->redirect_args.len = ctx->redirect_uri.len - (i + 1);
+ ctx->redirect_args.data = &ctx->redirect_uri.data[i + 1];
+ ctx->redirect_uri.len = i;
+
+ XSRETURN_EMPTY;
+ }
+ }
+
+
+void
+allow_ranges(r)
+ CODE:
+
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+
+ r->allow_ranges = 1;
+
+
+void
+unescape(r, text, type = 0)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+ SV *text;
+ int type;
+ u_char *p, *dst, *src;
+ STRLEN len;
+
+ ngx_http_perl_set_request(r);
+
+ text = ST(1);
+
+ src = (u_char *) SvPV(text, len);
+
+ p = ngx_pnalloc(r->pool, len + 1);
+ if (p == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ dst = p;
+
+ type = items < 3 ? 0 : SvIV(ST(2));
+
+ ngx_unescape_uri(&dst, &src, len, (ngx_uint_t) type);
+ *dst = '\0';
+
+ ngx_http_perl_set_targ(p, dst - p);
+
+ ST(0) = TARG;
+
+
+void
+variable(r, name, value = NULL)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+ SV *name, *value;
+ u_char *p, *lowcase;
+ STRLEN len;
+ ngx_str_t var, val;
+ ngx_uint_t i, hash;
+ ngx_http_perl_var_t *v;
+ ngx_http_perl_ctx_t *ctx;
+ ngx_http_variable_value_t *vv;
+
+ ngx_http_perl_set_request(r);
+
+ name = ST(1);
+
+ if (SvROK(name) && SvTYPE(SvRV(name)) == SVt_PV) {
+ name = SvRV(name);
+ }
+
+ if (items == 2) {
+ value = NULL;
+
+ } else {
+ value = ST(2);
+
+ if (SvROK(value) && SvTYPE(SvRV(value)) == SVt_PV) {
+ value = SvRV(value);
+ }
+
+ if (ngx_http_perl_sv2str(aTHX_ r, &val, value) != NGX_OK) {
+ XSRETURN_UNDEF;
+ }
+ }
+
+ p = (u_char *) SvPV(name, len);
+
+ lowcase = ngx_pnalloc(r->pool, len);
+ if (lowcase == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ hash = ngx_hash_strlow(lowcase, p, len);
+
+ var.len = len;
+ var.data = lowcase;
+
+ #if (NGX_LOG_DEBUG)
+
+ if (value) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl variable: \"%V\"=\"%V\"", &var, &val);
+ } else {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl variable: \"%V\"", &var);
+ }
+
+ #endif
+
+ vv = ngx_http_get_variable(r, &var, hash);
+ if (vv == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ if (vv->not_found) {
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+ if (ctx->variables) {
+
+ v = ctx->variables->elts;
+ for (i = 0; i < ctx->variables->nelts; i++) {
+
+ if (hash != v[i].hash
+ || len != v[i].name.len
+ || ngx_strncmp(lowcase, v[i].name.data, len) != 0)
+ {
+ continue;
+ }
+
+ if (value) {
+ v[i].value = val;
+ XSRETURN_UNDEF;
+ }
+
+ ngx_http_perl_set_targ(v[i].value.data, v[i].value.len);
+
+ goto done;
+ }
+ }
+
+ if (value) {
+ if (ctx->variables == NULL) {
+ ctx->variables = ngx_array_create(r->pool, 1,
+ sizeof(ngx_http_perl_var_t));
+ if (ctx->variables == NULL) {
+ XSRETURN_UNDEF;
+ }
+ }
+
+ v = ngx_array_push(ctx->variables);
+ if (v == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ v->hash = hash;
+ v->name.len = len;
+ v->name.data = lowcase;
+ v->value = val;
+
+ XSRETURN_UNDEF;
+ }
+
+ XSRETURN_UNDEF;
+ }
+
+ if (value) {
+ vv->len = val.len;
+ vv->valid = 1;
+ vv->no_cacheable = 0;
+ vv->not_found = 0;
+ vv->data = val.data;
+
+ XSRETURN_UNDEF;
+ }
+
+ ngx_http_perl_set_targ(vv->data, vv->len);
+
+ done:
+
+ ST(0) = TARG;
+
+
+void
+sleep(r, sleep, next)
+ CODE:
+
+ ngx_http_request_t *r;
+ ngx_msec_t sleep;
+ ngx_http_perl_ctx_t *ctx;
+
+ ngx_http_perl_set_request(r);
+
+ sleep = (ngx_msec_t) SvIV(ST(1));
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl sleep: %M", sleep);
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+ ctx->next = SvRV(ST(2));
+
+ ngx_add_timer(r->connection->write, sleep);
+
+ r->write_event_handler = ngx_http_perl_sleep_handler;
+ r->main->count++;
+
+
+void
+log_error(r, err, msg)
+ CODE:
+
+ ngx_http_request_t *r;
+ SV *err, *msg;
+ u_char *p;
+ STRLEN len;
+ ngx_err_t e;
+
+ ngx_http_perl_set_request(r);
+
+ err = ST(1);
+
+ if (SvROK(err) && SvTYPE(SvRV(err)) == SVt_PV) {
+ err = SvRV(err);
+ }
+
+ e = SvIV(err);
+
+ msg = ST(2);
+
+ if (SvROK(msg) && SvTYPE(SvRV(msg)) == SVt_PV) {
+ msg = SvRV(msg);
+ }
+
+ p = (u_char *) SvPV(msg, len);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, e, "perl: %s", p);
diff --git a/usr.sbin/nginx/src/http/modules/perl/ngx_http_perl_module.c b/usr.sbin/nginx/src/http/modules/perl/ngx_http_perl_module.c
new file mode 100644
index 00000000000..d89e96a5722
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/perl/ngx_http_perl_module.c
@@ -0,0 +1,1075 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_perl_module.h>
+
+
+typedef struct {
+ PerlInterpreter *perl;
+ HV *nginx;
+ ngx_array_t *modules;
+ ngx_array_t *requires;
+} ngx_http_perl_main_conf_t;
+
+
+typedef struct {
+ SV *sub;
+ ngx_str_t handler;
+} ngx_http_perl_loc_conf_t;
+
+
+typedef struct {
+ SV *sub;
+ ngx_str_t handler;
+} ngx_http_perl_variable_t;
+
+
+#if (NGX_HTTP_SSI)
+static ngx_int_t ngx_http_perl_ssi(ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ssi_ctx, ngx_str_t **params);
+#endif
+
+static char *ngx_http_perl_init_interpreter(ngx_conf_t *cf,
+ ngx_http_perl_main_conf_t *pmcf);
+static PerlInterpreter *ngx_http_perl_create_interpreter(ngx_conf_t *cf,
+ ngx_http_perl_main_conf_t *pmcf);
+static ngx_int_t ngx_http_perl_run_requires(pTHX_ ngx_array_t *requires,
+ ngx_log_t *log);
+static ngx_int_t ngx_http_perl_call_handler(pTHX_ ngx_http_request_t *r,
+ HV *nginx, SV *sub, SV **args, ngx_str_t *handler, ngx_str_t *rv);
+static void ngx_http_perl_eval_anon_sub(pTHX_ ngx_str_t *handler, SV **sv);
+
+static ngx_int_t ngx_http_perl_preconfiguration(ngx_conf_t *cf);
+static void *ngx_http_perl_create_main_conf(ngx_conf_t *cf);
+static char *ngx_http_perl_init_main_conf(ngx_conf_t *cf, void *conf);
+static void *ngx_http_perl_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_perl_merge_loc_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_http_perl(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_perl_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+static void ngx_http_perl_cleanup_perl(void *data);
+#endif
+
+static ngx_int_t ngx_http_perl_init_worker(ngx_cycle_t *cycle);
+static void ngx_http_perl_exit(ngx_cycle_t *cycle);
+
+
+static ngx_command_t ngx_http_perl_commands[] = {
+
+ { ngx_string("perl_modules"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ offsetof(ngx_http_perl_main_conf_t, modules),
+ NULL },
+
+ { ngx_string("perl_require"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ offsetof(ngx_http_perl_main_conf_t, requires),
+ NULL },
+
+ { ngx_string("perl"),
+ NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1,
+ ngx_http_perl,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("perl_set"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE2,
+ ngx_http_perl_set,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_perl_module_ctx = {
+ ngx_http_perl_preconfiguration, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ ngx_http_perl_create_main_conf, /* create main configuration */
+ ngx_http_perl_init_main_conf, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_perl_create_loc_conf, /* create location configuration */
+ ngx_http_perl_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_perl_module = {
+ NGX_MODULE_V1,
+ &ngx_http_perl_module_ctx, /* module context */
+ ngx_http_perl_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ ngx_http_perl_init_worker, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ ngx_http_perl_exit, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+#if (NGX_HTTP_SSI)
+
+#define NGX_HTTP_PERL_SSI_SUB 0
+#define NGX_HTTP_PERL_SSI_ARG 1
+
+
+static ngx_http_ssi_param_t ngx_http_perl_ssi_params[] = {
+ { ngx_string("sub"), NGX_HTTP_PERL_SSI_SUB, 1, 0 },
+ { ngx_string("arg"), NGX_HTTP_PERL_SSI_ARG, 0, 1 },
+ { ngx_null_string, 0, 0, 0 }
+};
+
+static ngx_http_ssi_command_t ngx_http_perl_ssi_command = {
+ ngx_string("perl"), ngx_http_perl_ssi, ngx_http_perl_ssi_params, 0, 0, 1
+};
+
+#endif
+
+
+static ngx_str_t ngx_null_name = ngx_null_string;
+static HV *nginx_stash;
+
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+static ngx_uint_t ngx_perl_term;
+#else
+static PerlInterpreter *perl;
+#endif
+
+
+static void
+ngx_http_perl_xs_init(pTHX)
+{
+ newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, __FILE__);
+
+ nginx_stash = gv_stashpv("nginx", TRUE);
+}
+
+
+static ngx_int_t
+ngx_http_perl_handler(ngx_http_request_t *r)
+{
+ r->main->count++;
+
+ ngx_http_perl_handle_request(r);
+
+ return NGX_DONE;
+}
+
+
+void
+ngx_http_perl_handle_request(ngx_http_request_t *r)
+{
+ SV *sub;
+ ngx_int_t rc;
+ ngx_str_t uri, args, *handler;
+ ngx_http_perl_ctx_t *ctx;
+ ngx_http_perl_loc_conf_t *plcf;
+ ngx_http_perl_main_conf_t *pmcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "perl handler");
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+ if (ctx == NULL) {
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_perl_ctx_t));
+ if (ctx == NULL) {
+ ngx_http_finalize_request(r, NGX_ERROR);
+ return;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_perl_module);
+ }
+
+ pmcf = ngx_http_get_module_main_conf(r, ngx_http_perl_module);
+
+ {
+
+ dTHXa(pmcf->perl);
+ PERL_SET_CONTEXT(pmcf->perl);
+
+ if (ctx->next == NULL) {
+ plcf = ngx_http_get_module_loc_conf(r, ngx_http_perl_module);
+ sub = plcf->sub;
+ handler = &plcf->handler;
+
+ } else {
+ sub = ctx->next;
+ handler = &ngx_null_name;
+ ctx->next = NULL;
+ }
+
+ rc = ngx_http_perl_call_handler(aTHX_ r, pmcf->nginx, sub, NULL, handler,
+ NULL);
+
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl handler done: %i", rc);
+
+ if (rc == NGX_DONE) {
+ ngx_http_finalize_request(r, rc);
+ return;
+ }
+
+ if (rc > 600) {
+ rc = NGX_OK;
+ }
+
+ if (ctx->redirect_uri.len) {
+ uri = ctx->redirect_uri;
+ args = ctx->redirect_args;
+
+ } else {
+ uri.len = 0;
+ }
+
+ ctx->filename.data = NULL;
+ ctx->redirect_uri.len = 0;
+
+ if (ctx->done || ctx->next) {
+ ngx_http_finalize_request(r, NGX_DONE);
+ return;
+ }
+
+ if (uri.len) {
+ ngx_http_internal_redirect(r, &uri, &args);
+ ngx_http_finalize_request(r, NGX_DONE);
+ return;
+ }
+
+ if (rc == NGX_OK || rc == NGX_HTTP_OK) {
+ ngx_http_send_special(r, NGX_HTTP_LAST);
+ ctx->done = 1;
+ }
+
+ ngx_http_finalize_request(r, rc);
+}
+
+
+void
+ngx_http_perl_sleep_handler(ngx_http_request_t *r)
+{
+ ngx_event_t *wev;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl sleep handler");
+
+ wev = r->connection->write;
+
+ if (wev->timedout) {
+ wev->timedout = 0;
+ ngx_http_perl_handle_request(r);
+ return;
+ }
+
+ if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ }
+}
+
+
+static ngx_int_t
+ngx_http_perl_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_http_perl_variable_t *pv = (ngx_http_perl_variable_t *) data;
+
+ ngx_int_t rc;
+ ngx_str_t value;
+ ngx_http_perl_ctx_t *ctx;
+ ngx_http_perl_main_conf_t *pmcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl variable handler");
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+ if (ctx == NULL) {
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_perl_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_perl_module);
+ }
+
+ pmcf = ngx_http_get_module_main_conf(r, ngx_http_perl_module);
+
+ value.data = NULL;
+
+ {
+
+ dTHXa(pmcf->perl);
+ PERL_SET_CONTEXT(pmcf->perl);
+
+ rc = ngx_http_perl_call_handler(aTHX_ r, pmcf->nginx, pv->sub, NULL,
+ &pv->handler, &value);
+
+ }
+
+ if (value.data) {
+ v->len = value.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = value.data;
+
+ } else {
+ v->not_found = 1;
+ }
+
+ ctx->filename.data = NULL;
+ ctx->redirect_uri.len = 0;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl variable done");
+
+ return rc;
+}
+
+
+#if (NGX_HTTP_SSI)
+
+static ngx_int_t
+ngx_http_perl_ssi(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ssi_ctx,
+ ngx_str_t **params)
+{
+ SV *sv, **asv;
+ ngx_int_t rc;
+ ngx_str_t *handler, **args;
+ ngx_uint_t i;
+ ngx_http_perl_ctx_t *ctx;
+ ngx_http_perl_main_conf_t *pmcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl ssi handler");
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+ if (ctx == NULL) {
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_perl_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_perl_module);
+ }
+
+ pmcf = ngx_http_get_module_main_conf(r, ngx_http_perl_module);
+
+ ctx->ssi = ssi_ctx;
+
+ handler = params[NGX_HTTP_PERL_SSI_SUB];
+ handler->data[handler->len] = '\0';
+
+ {
+
+ dTHXa(pmcf->perl);
+ PERL_SET_CONTEXT(pmcf->perl);
+
+#if 0
+
+ /* the code is disabled to force the precompiled perl code using only */
+
+ ngx_http_perl_eval_anon_sub(aTHX_ handler, &sv);
+
+ if (sv == &PL_sv_undef) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "eval_pv(\"%V\") failed", handler);
+ return NGX_ERROR;
+ }
+
+ if (sv == NULL) {
+ sv = newSVpvn((char *) handler->data, handler->len);
+ }
+
+#endif
+
+ sv = newSVpvn((char *) handler->data, handler->len);
+
+ args = &params[NGX_HTTP_PERL_SSI_ARG];
+
+ if (args) {
+
+ for (i = 0; args[i]; i++) { /* void */ }
+
+ asv = ngx_pcalloc(r->pool, (i + 1) * sizeof(SV *));
+
+ if (asv == NULL) {
+ SvREFCNT_dec(sv);
+ return NGX_ERROR;
+ }
+
+ asv[0] = (SV *) i;
+
+ for (i = 0; args[i]; i++) {
+ asv[i + 1] = newSVpvn((char *) args[i]->data, args[i]->len);
+ }
+
+ } else {
+ asv = NULL;
+ }
+
+ rc = ngx_http_perl_call_handler(aTHX_ r, pmcf->nginx, sv, asv, handler,
+ NULL);
+
+ SvREFCNT_dec(sv);
+
+ }
+
+ ctx->filename.data = NULL;
+ ctx->redirect_uri.len = 0;
+ ctx->ssi = NULL;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "perl ssi done");
+
+ return rc;
+}
+
+#endif
+
+
+static char *
+ngx_http_perl_init_interpreter(ngx_conf_t *cf, ngx_http_perl_main_conf_t *pmcf)
+{
+ ngx_str_t *m;
+ ngx_uint_t i;
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+ ngx_pool_cleanup_t *cln;
+
+ cln = ngx_pool_cleanup_add(cf->pool, 0);
+ if (cln == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+#endif
+
+#ifdef NGX_PERL_MODULES
+ if (pmcf->modules == NGX_CONF_UNSET_PTR) {
+
+ pmcf->modules = ngx_array_create(cf->pool, 1, sizeof(ngx_str_t));
+ if (pmcf->modules == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ m = ngx_array_push(pmcf->modules);
+ if (m == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_str_set(m, NGX_PERL_MODULES);
+ }
+#endif
+
+ if (pmcf->modules != NGX_CONF_UNSET_PTR) {
+ m = pmcf->modules->elts;
+ for (i = 0; i < pmcf->modules->nelts; i++) {
+ if (ngx_conf_full_name(cf->cycle, &m[i], 0) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+ }
+
+#if !(NGX_HAVE_PERL_MULTIPLICITY)
+
+ if (perl) {
+
+ if (ngx_set_environment(cf->cycle, NULL) == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_http_perl_run_requires(aTHX_ pmcf->requires, cf->log)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ pmcf->perl = perl;
+ pmcf->nginx = nginx_stash;
+
+ return NGX_CONF_OK;
+ }
+
+#endif
+
+ if (nginx_stash == NULL) {
+ PERL_SYS_INIT(&ngx_argc, &ngx_argv);
+ }
+
+ pmcf->perl = ngx_http_perl_create_interpreter(cf, pmcf);
+
+ if (pmcf->perl == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pmcf->nginx = nginx_stash;
+
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+
+ cln->handler = ngx_http_perl_cleanup_perl;
+ cln->data = pmcf->perl;
+
+#else
+
+ perl = pmcf->perl;
+
+#endif
+
+ return NGX_CONF_OK;
+}
+
+
+static PerlInterpreter *
+ngx_http_perl_create_interpreter(ngx_conf_t *cf,
+ ngx_http_perl_main_conf_t *pmcf)
+{
+ int n;
+ STRLEN len;
+ SV *sv;
+ char *ver, **embedding;
+ ngx_str_t *m;
+ ngx_uint_t i;
+ PerlInterpreter *perl;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, "create perl interpreter");
+
+ if (ngx_set_environment(cf->cycle, NULL) == NULL) {
+ return NULL;
+ }
+
+ perl = perl_alloc();
+ if (perl == NULL) {
+ ngx_log_error(NGX_LOG_ALERT, cf->log, 0, "perl_alloc() failed");
+ return NULL;
+ }
+
+ {
+
+ dTHXa(perl);
+ PERL_SET_CONTEXT(perl);
+
+ perl_construct(perl);
+
+#ifdef PERL_EXIT_DESTRUCT_END
+ PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
+#endif
+
+ n = (pmcf->modules != NGX_CONF_UNSET_PTR) ? pmcf->modules->nelts * 2 : 0;
+
+ embedding = ngx_palloc(cf->pool, (4 + n) * sizeof(char *));
+ if (embedding == NULL) {
+ goto fail;
+ }
+
+ embedding[0] = "";
+
+ if (n++) {
+ m = pmcf->modules->elts;
+ for (i = 0; i < pmcf->modules->nelts; i++) {
+ embedding[2 * i + 1] = "-I";
+ embedding[2 * i + 2] = (char *) m[i].data;
+ }
+ }
+
+ embedding[n++] = "-Mnginx";
+ embedding[n++] = "-e";
+ embedding[n++] = "0";
+
+ n = perl_parse(perl, ngx_http_perl_xs_init, n, embedding, NULL);
+
+ if (n != 0) {
+ ngx_log_error(NGX_LOG_ALERT, cf->log, 0, "perl_parse() failed: %d", n);
+ goto fail;
+ }
+
+ sv = get_sv("nginx::VERSION", FALSE);
+ ver = SvPV(sv, len);
+
+ if (ngx_strcmp(ver, NGINX_VERSION) != 0) {
+ ngx_log_error(NGX_LOG_ALERT, cf->log, 0,
+ "version " NGINX_VERSION " of nginx.pm is required, "
+ "but %s was found", ver);
+ goto fail;
+ }
+
+ if (ngx_http_perl_run_requires(aTHX_ pmcf->requires, cf->log) != NGX_OK) {
+ goto fail;
+ }
+
+ }
+
+ return perl;
+
+fail:
+
+ (void) perl_destruct(perl);
+
+ perl_free(perl);
+
+ return NULL;
+}
+
+
+static ngx_int_t
+ngx_http_perl_run_requires(pTHX_ ngx_array_t *requires, ngx_log_t *log)
+{
+ u_char *err;
+ STRLEN len;
+ ngx_str_t *script;
+ ngx_uint_t i;
+
+ if (requires == NGX_CONF_UNSET_PTR) {
+ return NGX_OK;
+ }
+
+ script = requires->elts;
+ for (i = 0; i < requires->nelts; i++) {
+
+ require_pv((char *) script[i].data);
+
+ if (SvTRUE(ERRSV)) {
+
+ err = (u_char *) SvPV(ERRSV, len);
+ while (--len && (err[len] == CR || err[len] == LF)) { /* void */ }
+
+ ngx_log_error(NGX_LOG_EMERG, log, 0,
+ "require_pv(\"%s\") failed: \"%*s\"",
+ script[i].data, len + 1, err);
+
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_perl_call_handler(pTHX_ ngx_http_request_t *r, HV *nginx, SV *sub,
+ SV **args, ngx_str_t *handler, ngx_str_t *rv)
+{
+ SV *sv;
+ int n, status;
+ char *line;
+ u_char *err;
+ STRLEN len, n_a;
+ ngx_uint_t i;
+ ngx_connection_t *c;
+
+ dSP;
+
+ status = 0;
+
+ ENTER;
+ SAVETMPS;
+
+ PUSHMARK(sp);
+
+ sv = sv_2mortal(sv_bless(newRV_noinc(newSViv(PTR2IV(r))), nginx));
+ XPUSHs(sv);
+
+ if (args) {
+ EXTEND(sp, (intptr_t) args[0]);
+
+ for (i = 1; i <= (ngx_uint_t) args[0]; i++) {
+ PUSHs(sv_2mortal(args[i]));
+ }
+ }
+
+ PUTBACK;
+
+ c = r->connection;
+
+ n = call_sv(sub, G_EVAL);
+
+ SPAGAIN;
+
+ if (n) {
+ if (rv == NULL) {
+ status = POPi;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "call_sv: %d", status);
+
+ } else {
+ line = SvPVx(POPs, n_a);
+ rv->len = n_a;
+
+ rv->data = ngx_pnalloc(r->pool, n_a);
+ if (rv->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(rv->data, line, n_a);
+ }
+ }
+
+ PUTBACK;
+
+ FREETMPS;
+ LEAVE;
+
+ /* check $@ */
+
+ if (SvTRUE(ERRSV)) {
+
+ err = (u_char *) SvPV(ERRSV, len);
+ while (--len && (err[len] == CR || err[len] == LF)) { /* void */ }
+
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "call_sv(\"%V\") failed: \"%*s\"", handler, len + 1, err);
+
+ if (rv) {
+ return NGX_ERROR;
+ }
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (n != 1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "call_sv(\"%V\") returned %d results", handler, n);
+ status = NGX_OK;
+ }
+
+ if (rv) {
+ return NGX_OK;
+ }
+
+ return (ngx_int_t) status;
+}
+
+
+static void
+ngx_http_perl_eval_anon_sub(pTHX_ ngx_str_t *handler, SV **sv)
+{
+ u_char *p;
+
+ for (p = handler->data; *p; p++) {
+ if (*p != ' ' && *p != '\t' && *p != CR && *p != LF) {
+ break;
+ }
+ }
+
+ if (ngx_strncmp(p, "sub ", 4) == 0
+ || ngx_strncmp(p, "sub{", 4) == 0
+ || ngx_strncmp(p, "use ", 4) == 0)
+ {
+ *sv = eval_pv((char *) p, FALSE);
+
+ /* eval_pv() does not set ERRSV on failure */
+
+ return;
+ }
+
+ *sv = NULL;
+}
+
+
+static void *
+ngx_http_perl_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_perl_main_conf_t *pmcf;
+
+ pmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_perl_main_conf_t));
+ if (pmcf == NULL) {
+ return NULL;
+ }
+
+ pmcf->modules = NGX_CONF_UNSET_PTR;
+ pmcf->requires = NGX_CONF_UNSET_PTR;
+
+ return pmcf;
+}
+
+
+static char *
+ngx_http_perl_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+ ngx_http_perl_main_conf_t *pmcf = conf;
+
+ if (pmcf->perl == NULL) {
+ if (ngx_http_perl_init_interpreter(cf, pmcf) != NGX_CONF_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+
+static void
+ngx_http_perl_cleanup_perl(void *data)
+{
+ PerlInterpreter *perl = data;
+
+ PERL_SET_CONTEXT(perl);
+
+ (void) perl_destruct(perl);
+
+ perl_free(perl);
+
+ if (ngx_perl_term) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, "perl term");
+
+ PERL_SYS_TERM();
+ }
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_perl_preconfiguration(ngx_conf_t *cf)
+{
+#if (NGX_HTTP_SSI)
+ ngx_int_t rc;
+ ngx_http_ssi_main_conf_t *smcf;
+
+ smcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_ssi_filter_module);
+
+ rc = ngx_hash_add_key(&smcf->commands, &ngx_http_perl_ssi_command.name,
+ &ngx_http_perl_ssi_command, NGX_HASH_READONLY_KEY);
+
+ if (rc != NGX_OK) {
+ if (rc == NGX_BUSY) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "conflicting SSI command \"%V\"",
+ &ngx_http_perl_ssi_command.name);
+ }
+
+ return NGX_ERROR;
+ }
+#endif
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_perl_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_perl_loc_conf_t *plcf;
+
+ plcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_perl_loc_conf_t));
+ if (plcf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * plcf->handler = { 0, NULL };
+ */
+
+ return plcf;
+}
+
+
+static char *
+ngx_http_perl_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_perl_loc_conf_t *prev = parent;
+ ngx_http_perl_loc_conf_t *conf = child;
+
+ if (conf->sub == NULL) {
+ conf->sub = prev->sub;
+ conf->handler = prev->handler;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_perl(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_perl_loc_conf_t *plcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_perl_main_conf_t *pmcf;
+
+ value = cf->args->elts;
+
+ if (plcf->handler.data) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "duplicate perl handler \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ pmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_perl_module);
+
+ if (pmcf->perl == NULL) {
+ if (ngx_http_perl_init_interpreter(cf, pmcf) != NGX_CONF_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ plcf->handler = value[1];
+
+ {
+
+ dTHXa(pmcf->perl);
+ PERL_SET_CONTEXT(pmcf->perl);
+
+ ngx_http_perl_eval_anon_sub(aTHX_ &value[1], &plcf->sub);
+
+ if (plcf->sub == &PL_sv_undef) {
+ ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
+ "eval_pv(\"%V\") failed", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (plcf->sub == NULL) {
+ plcf->sub = newSVpvn((char *) value[1].data, value[1].len);
+ }
+
+ }
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+ clcf->handler = ngx_http_perl_handler;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_perl_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_int_t index;
+ ngx_str_t *value;
+ ngx_http_variable_t *v;
+ ngx_http_perl_variable_t *pv;
+ ngx_http_perl_main_conf_t *pmcf;
+
+ value = cf->args->elts;
+
+ if (value[1].data[0] != '$') {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid variable name \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ value[1].len--;
+ value[1].data++;
+
+ v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE);
+ if (v == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pv = ngx_palloc(cf->pool, sizeof(ngx_http_perl_variable_t));
+ if (pv == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ index = ngx_http_get_variable_index(cf, &value[1]);
+ if (index == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ pmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_perl_module);
+
+ if (pmcf->perl == NULL) {
+ if (ngx_http_perl_init_interpreter(cf, pmcf) != NGX_CONF_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ pv->handler = value[2];
+
+ {
+
+ dTHXa(pmcf->perl);
+ PERL_SET_CONTEXT(pmcf->perl);
+
+ ngx_http_perl_eval_anon_sub(aTHX_ &value[2], &pv->sub);
+
+ if (pv->sub == &PL_sv_undef) {
+ ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
+ "eval_pv(\"%V\") failed", &value[2]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (pv->sub == NULL) {
+ pv->sub = newSVpvn((char *) value[2].data, value[2].len);
+ }
+
+ }
+
+ v->get_handler = ngx_http_perl_variable;
+ v->data = (uintptr_t) pv;
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_perl_init_worker(ngx_cycle_t *cycle)
+{
+ ngx_http_perl_main_conf_t *pmcf;
+
+ pmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_perl_module);
+
+ if (pmcf) {
+ dTHXa(pmcf->perl);
+ PERL_SET_CONTEXT(pmcf->perl);
+
+ /* set worker's $$ */
+
+ sv_setiv(GvSV(gv_fetchpv("$", TRUE, SVt_PV)), (I32) ngx_pid);
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_perl_exit(ngx_cycle_t *cycle)
+{
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+
+ /*
+ * the master exit hook is run before global pool cleanup,
+ * therefore just set flag here
+ */
+
+ ngx_perl_term = 1;
+
+#else
+
+ if (nginx_stash) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cycle->log, 0, "perl term");
+
+ (void) perl_destruct(perl);
+
+ perl_free(perl);
+
+ PERL_SYS_TERM();
+ }
+
+#endif
+}
diff --git a/usr.sbin/nginx/src/http/modules/perl/ngx_http_perl_module.h b/usr.sbin/nginx/src/http/modules/perl/ngx_http_perl_module.h
new file mode 100644
index 00000000000..23969bcd485
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/perl/ngx_http_perl_module.h
@@ -0,0 +1,66 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_PERL_MODULE_H_INCLUDED_
+#define _NGX_HTTP_PERL_MODULE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+#include <EXTERN.h>
+#include <perl.h>
+
+
+typedef ngx_http_request_t *nginx;
+
+typedef struct {
+ ngx_str_t filename;
+ ngx_str_t redirect_uri;
+ ngx_str_t redirect_args;
+
+ SV *next;
+
+ ngx_uint_t done; /* unsigned done:1; */
+
+ ngx_array_t *variables; /* array of ngx_http_perl_var_t */
+
+#if (NGX_HTTP_SSI)
+ ngx_http_ssi_ctx_t *ssi;
+#endif
+} ngx_http_perl_ctx_t;
+
+
+typedef struct {
+ ngx_uint_t hash;
+ ngx_str_t name;
+ ngx_str_t value;
+} ngx_http_perl_var_t;
+
+
+extern ngx_module_t ngx_http_perl_module;
+
+
+/*
+ * workaround for "unused variable `Perl___notused'" warning
+ * when building with perl 5.6.1
+ */
+#ifndef PERL_IMPLICIT_CONTEXT
+#undef dTHXa
+#define dTHXa(a)
+#endif
+
+
+extern void boot_DynaLoader(pTHX_ CV* cv);
+
+
+void ngx_http_perl_handle_request(ngx_http_request_t *r);
+void ngx_http_perl_sleep_handler(ngx_http_request_t *r);
+
+
+#endif /* _NGX_HTTP_PERL_MODULE_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/http/modules/perl/typemap b/usr.sbin/nginx/src/http/modules/perl/typemap
new file mode 100644
index 00000000000..e2f1a4c6af6
--- /dev/null
+++ b/usr.sbin/nginx/src/http/modules/perl/typemap
@@ -0,0 +1,3 @@
+TYPEMAP
+
+nginx T_PTROBJ
diff --git a/usr.sbin/nginx/src/http/ngx_http.c b/usr.sbin/nginx/src/http/ngx_http.c
new file mode 100644
index 00000000000..5ca9fed3cb6
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http.c
@@ -0,0 +1,2070 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static ngx_int_t ngx_http_init_phases(ngx_conf_t *cf,
+ ngx_http_core_main_conf_t *cmcf);
+static ngx_int_t ngx_http_init_headers_in_hash(ngx_conf_t *cf,
+ ngx_http_core_main_conf_t *cmcf);
+static ngx_int_t ngx_http_init_phase_handlers(ngx_conf_t *cf,
+ ngx_http_core_main_conf_t *cmcf);
+
+static ngx_int_t ngx_http_add_addresses(ngx_conf_t *cf,
+ ngx_http_core_srv_conf_t *cscf, ngx_http_conf_port_t *port,
+ ngx_http_listen_opt_t *lsopt);
+static ngx_int_t ngx_http_add_address(ngx_conf_t *cf,
+ ngx_http_core_srv_conf_t *cscf, ngx_http_conf_port_t *port,
+ ngx_http_listen_opt_t *lsopt);
+static ngx_int_t ngx_http_add_server(ngx_conf_t *cf,
+ ngx_http_core_srv_conf_t *cscf, ngx_http_conf_addr_t *addr);
+
+static char *ngx_http_merge_servers(ngx_conf_t *cf,
+ ngx_http_core_main_conf_t *cmcf, ngx_http_module_t *module,
+ ngx_uint_t ctx_index);
+static char *ngx_http_merge_locations(ngx_conf_t *cf,
+ ngx_queue_t *locations, void **loc_conf, ngx_http_module_t *module,
+ ngx_uint_t ctx_index);
+static ngx_int_t ngx_http_init_locations(ngx_conf_t *cf,
+ ngx_http_core_srv_conf_t *cscf, ngx_http_core_loc_conf_t *pclcf);
+static ngx_int_t ngx_http_init_static_location_trees(ngx_conf_t *cf,
+ ngx_http_core_loc_conf_t *pclcf);
+static ngx_int_t ngx_http_cmp_locations(const ngx_queue_t *one,
+ const ngx_queue_t *two);
+static ngx_int_t ngx_http_join_exact_locations(ngx_conf_t *cf,
+ ngx_queue_t *locations);
+static void ngx_http_create_locations_list(ngx_queue_t *locations,
+ ngx_queue_t *q);
+static ngx_http_location_tree_node_t *
+ ngx_http_create_locations_tree(ngx_conf_t *cf, ngx_queue_t *locations,
+ size_t prefix);
+
+static ngx_int_t ngx_http_optimize_servers(ngx_conf_t *cf,
+ ngx_http_core_main_conf_t *cmcf, ngx_array_t *ports);
+static ngx_int_t ngx_http_server_names(ngx_conf_t *cf,
+ ngx_http_core_main_conf_t *cmcf, ngx_http_conf_addr_t *addr);
+static ngx_int_t ngx_http_cmp_conf_addrs(const void *one, const void *two);
+static int ngx_libc_cdecl ngx_http_cmp_dns_wildcards(const void *one,
+ const void *two);
+
+static ngx_int_t ngx_http_init_listening(ngx_conf_t *cf,
+ ngx_http_conf_port_t *port);
+static ngx_listening_t *ngx_http_add_listening(ngx_conf_t *cf,
+ ngx_http_conf_addr_t *addr);
+static ngx_int_t ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport,
+ ngx_http_conf_addr_t *addr);
+#if (NGX_HAVE_INET6)
+static ngx_int_t ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport,
+ ngx_http_conf_addr_t *addr);
+#endif
+
+ngx_uint_t ngx_http_max_module;
+
+
+ngx_int_t (*ngx_http_top_header_filter) (ngx_http_request_t *r);
+ngx_int_t (*ngx_http_top_body_filter) (ngx_http_request_t *r, ngx_chain_t *ch);
+
+
+ngx_str_t ngx_http_html_default_types[] = {
+ ngx_string("text/html"),
+ ngx_null_string
+};
+
+
+static ngx_command_t ngx_http_commands[] = {
+
+ { ngx_string("http"),
+ NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+ ngx_http_block,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_core_module_t ngx_http_module_ctx = {
+ ngx_string("http"),
+ NULL,
+ NULL
+};
+
+
+ngx_module_t ngx_http_module = {
+ NGX_MODULE_V1,
+ &ngx_http_module_ctx, /* module context */
+ ngx_http_commands, /* module directives */
+ NGX_CORE_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static char *
+ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *rv;
+ ngx_uint_t mi, m, s;
+ ngx_conf_t pcf;
+ ngx_http_module_t *module;
+ ngx_http_conf_ctx_t *ctx;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_core_srv_conf_t **cscfp;
+ ngx_http_core_main_conf_t *cmcf;
+
+ /* the main http context */
+
+ ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
+ if (ctx == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *(ngx_http_conf_ctx_t **) conf = ctx;
+
+
+ /* count the number of the http modules and set up their indices */
+
+ ngx_http_max_module = 0;
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
+ continue;
+ }
+
+ ngx_modules[m]->ctx_index = ngx_http_max_module++;
+ }
+
+
+ /* the http main_conf context, it is the same in the all http contexts */
+
+ ctx->main_conf = ngx_pcalloc(cf->pool,
+ sizeof(void *) * ngx_http_max_module);
+ if (ctx->main_conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+
+ /*
+ * the http null srv_conf context, it is used to merge
+ * the server{}s' srv_conf's
+ */
+
+ ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+ if (ctx->srv_conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+
+ /*
+ * the http null loc_conf context, it is used to merge
+ * the server{}s' loc_conf's
+ */
+
+ ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+ if (ctx->loc_conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+
+ /*
+ * create the main_conf's, the null srv_conf's, and the null loc_conf's
+ * of the all http modules
+ */
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+ mi = ngx_modules[m]->ctx_index;
+
+ if (module->create_main_conf) {
+ ctx->main_conf[mi] = module->create_main_conf(cf);
+ if (ctx->main_conf[mi] == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (module->create_srv_conf) {
+ ctx->srv_conf[mi] = module->create_srv_conf(cf);
+ if (ctx->srv_conf[mi] == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (module->create_loc_conf) {
+ ctx->loc_conf[mi] = module->create_loc_conf(cf);
+ if (ctx->loc_conf[mi] == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+ }
+
+ pcf = *cf;
+ cf->ctx = ctx;
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+
+ if (module->preconfiguration) {
+ if (module->preconfiguration(cf) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+ }
+
+ /* parse inside the http{} block */
+
+ cf->module_type = NGX_HTTP_MODULE;
+ cf->cmd_type = NGX_HTTP_MAIN_CONF;
+ rv = ngx_conf_parse(cf, NULL);
+
+ if (rv != NGX_CONF_OK) {
+ goto failed;
+ }
+
+ /*
+ * init http{} main_conf's, merge the server{}s' srv_conf's
+ * and its location{}s' loc_conf's
+ */
+
+ cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
+ cscfp = cmcf->servers.elts;
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+ mi = ngx_modules[m]->ctx_index;
+
+ /* init http{} main_conf's */
+
+ if (module->init_main_conf) {
+ rv = module->init_main_conf(cf, ctx->main_conf[mi]);
+ if (rv != NGX_CONF_OK) {
+ goto failed;
+ }
+ }
+
+ rv = ngx_http_merge_servers(cf, cmcf, module, mi);
+ if (rv != NGX_CONF_OK) {
+ goto failed;
+ }
+ }
+
+
+ /* create location trees */
+
+ for (s = 0; s < cmcf->servers.nelts; s++) {
+
+ clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
+
+ if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+
+ if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_http_init_headers_in_hash(cf, cmcf) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+
+ if (module->postconfiguration) {
+ if (module->postconfiguration(cf) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+ }
+
+ if (ngx_http_variables_init_vars(cf) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ /*
+ * http{}'s cf->ctx was needed while the configuration merging
+ * and in postconfiguration process
+ */
+
+ *cf = pcf;
+
+
+ if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+
+ /* optimize the lists of ports, addresses and server names */
+
+ if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+
+failed:
+
+ *cf = pcf;
+
+ return rv;
+}
+
+
+static ngx_int_t
+ngx_http_init_phases(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
+{
+ if (ngx_array_init(&cmcf->phases[NGX_HTTP_POST_READ_PHASE].handlers,
+ cf->pool, 1, sizeof(ngx_http_handler_pt))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ if (ngx_array_init(&cmcf->phases[NGX_HTTP_SERVER_REWRITE_PHASE].handlers,
+ cf->pool, 1, sizeof(ngx_http_handler_pt))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ if (ngx_array_init(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers,
+ cf->pool, 1, sizeof(ngx_http_handler_pt))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ if (ngx_array_init(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers,
+ cf->pool, 1, sizeof(ngx_http_handler_pt))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ if (ngx_array_init(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers,
+ cf->pool, 2, sizeof(ngx_http_handler_pt))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ if (ngx_array_init(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers,
+ cf->pool, 4, sizeof(ngx_http_handler_pt))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ if (ngx_array_init(&cmcf->phases[NGX_HTTP_LOG_PHASE].handlers,
+ cf->pool, 1, sizeof(ngx_http_handler_pt))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_init_headers_in_hash(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
+{
+ ngx_array_t headers_in;
+ ngx_hash_key_t *hk;
+ ngx_hash_init_t hash;
+ ngx_http_header_t *header;
+
+ if (ngx_array_init(&headers_in, cf->temp_pool, 32, sizeof(ngx_hash_key_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ for (header = ngx_http_headers_in; header->name.len; header++) {
+ hk = ngx_array_push(&headers_in);
+ if (hk == NULL) {
+ return NGX_ERROR;
+ }
+
+ hk->key = header->name;
+ hk->key_hash = ngx_hash_key_lc(header->name.data, header->name.len);
+ hk->value = header;
+ }
+
+ hash.hash = &cmcf->headers_in_hash;
+ hash.key = ngx_hash_key_lc;
+ hash.max_size = 512;
+ hash.bucket_size = ngx_align(64, ngx_cacheline_size);
+ hash.name = "headers_in_hash";
+ hash.pool = cf->pool;
+ hash.temp_pool = NULL;
+
+ if (ngx_hash_init(&hash, headers_in.elts, headers_in.nelts) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
+{
+ ngx_int_t j;
+ ngx_uint_t i, n;
+ ngx_uint_t find_config_index, use_rewrite, use_access;
+ ngx_http_handler_pt *h;
+ ngx_http_phase_handler_t *ph;
+ ngx_http_phase_handler_pt checker;
+
+ cmcf->phase_engine.server_rewrite_index = (ngx_uint_t) -1;
+ cmcf->phase_engine.location_rewrite_index = (ngx_uint_t) -1;
+ find_config_index = 0;
+ use_rewrite = cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers.nelts ? 1 : 0;
+ use_access = cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers.nelts ? 1 : 0;
+
+ n = use_rewrite + use_access + cmcf->try_files + 1 /* find config phase */;
+
+ for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
+ n += cmcf->phases[i].handlers.nelts;
+ }
+
+ ph = ngx_pcalloc(cf->pool,
+ n * sizeof(ngx_http_phase_handler_t) + sizeof(void *));
+ if (ph == NULL) {
+ return NGX_ERROR;
+ }
+
+ cmcf->phase_engine.handlers = ph;
+ n = 0;
+
+ for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
+ h = cmcf->phases[i].handlers.elts;
+
+ switch (i) {
+
+ case NGX_HTTP_SERVER_REWRITE_PHASE:
+ if (cmcf->phase_engine.server_rewrite_index == (ngx_uint_t) -1) {
+ cmcf->phase_engine.server_rewrite_index = n;
+ }
+ checker = ngx_http_core_rewrite_phase;
+
+ break;
+
+ case NGX_HTTP_FIND_CONFIG_PHASE:
+ find_config_index = n;
+
+ ph->checker = ngx_http_core_find_config_phase;
+ n++;
+ ph++;
+
+ continue;
+
+ case NGX_HTTP_REWRITE_PHASE:
+ if (cmcf->phase_engine.location_rewrite_index == (ngx_uint_t) -1) {
+ cmcf->phase_engine.location_rewrite_index = n;
+ }
+ checker = ngx_http_core_rewrite_phase;
+
+ break;
+
+ case NGX_HTTP_POST_REWRITE_PHASE:
+ if (use_rewrite) {
+ ph->checker = ngx_http_core_post_rewrite_phase;
+ ph->next = find_config_index;
+ n++;
+ ph++;
+ }
+
+ continue;
+
+ case NGX_HTTP_ACCESS_PHASE:
+ checker = ngx_http_core_access_phase;
+ n++;
+ break;
+
+ case NGX_HTTP_POST_ACCESS_PHASE:
+ if (use_access) {
+ ph->checker = ngx_http_core_post_access_phase;
+ ph->next = n;
+ ph++;
+ }
+
+ continue;
+
+ case NGX_HTTP_TRY_FILES_PHASE:
+ if (cmcf->try_files) {
+ ph->checker = ngx_http_core_try_files_phase;
+ n++;
+ ph++;
+ }
+
+ continue;
+
+ case NGX_HTTP_CONTENT_PHASE:
+ checker = ngx_http_core_content_phase;
+ break;
+
+ default:
+ checker = ngx_http_core_generic_phase;
+ }
+
+ n += cmcf->phases[i].handlers.nelts;
+
+ for (j = cmcf->phases[i].handlers.nelts - 1; j >=0; j--) {
+ ph->checker = checker;
+ ph->handler = h[j];
+ ph->next = n;
+ ph++;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_http_merge_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
+ ngx_http_module_t *module, ngx_uint_t ctx_index)
+{
+ char *rv;
+ ngx_uint_t s;
+ ngx_http_conf_ctx_t *ctx, saved;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_core_srv_conf_t **cscfp;
+
+ cscfp = cmcf->servers.elts;
+ ctx = (ngx_http_conf_ctx_t *) cf->ctx;
+ saved = *ctx;
+ rv = NGX_CONF_OK;
+
+ for (s = 0; s < cmcf->servers.nelts; s++) {
+
+ /* merge the server{}s' srv_conf's */
+
+ ctx->srv_conf = cscfp[s]->ctx->srv_conf;
+
+ if (module->merge_srv_conf) {
+ rv = module->merge_srv_conf(cf, saved.srv_conf[ctx_index],
+ cscfp[s]->ctx->srv_conf[ctx_index]);
+ if (rv != NGX_CONF_OK) {
+ goto failed;
+ }
+ }
+
+ if (module->merge_loc_conf) {
+
+ /* merge the server{}'s loc_conf */
+
+ ctx->loc_conf = cscfp[s]->ctx->loc_conf;
+
+ rv = module->merge_loc_conf(cf, saved.loc_conf[ctx_index],
+ cscfp[s]->ctx->loc_conf[ctx_index]);
+ if (rv != NGX_CONF_OK) {
+ goto failed;
+ }
+
+ /* merge the locations{}' loc_conf's */
+
+ clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
+
+ rv = ngx_http_merge_locations(cf, clcf->locations,
+ cscfp[s]->ctx->loc_conf,
+ module, ctx_index);
+ if (rv != NGX_CONF_OK) {
+ goto failed;
+ }
+ }
+ }
+
+failed:
+
+ *ctx = saved;
+
+ return rv;
+}
+
+
+static char *
+ngx_http_merge_locations(ngx_conf_t *cf, ngx_queue_t *locations,
+ void **loc_conf, ngx_http_module_t *module, ngx_uint_t ctx_index)
+{
+ char *rv;
+ ngx_queue_t *q;
+ ngx_http_conf_ctx_t *ctx, saved;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_location_queue_t *lq;
+
+ if (locations == NULL) {
+ return NGX_CONF_OK;
+ }
+
+ ctx = (ngx_http_conf_ctx_t *) cf->ctx;
+ saved = *ctx;
+
+ for (q = ngx_queue_head(locations);
+ q != ngx_queue_sentinel(locations);
+ q = ngx_queue_next(q))
+ {
+ lq = (ngx_http_location_queue_t *) q;
+
+ clcf = lq->exact ? lq->exact : lq->inclusive;
+ ctx->loc_conf = clcf->loc_conf;
+
+ rv = module->merge_loc_conf(cf, loc_conf[ctx_index],
+ clcf->loc_conf[ctx_index]);
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+
+ rv = ngx_http_merge_locations(cf, clcf->locations, clcf->loc_conf,
+ module, ctx_index);
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+ }
+
+ *ctx = saved;
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_init_locations(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
+ ngx_http_core_loc_conf_t *pclcf)
+{
+ ngx_uint_t n;
+ ngx_queue_t *q, *locations, *named, tail;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_location_queue_t *lq;
+ ngx_http_core_loc_conf_t **clcfp;
+#if (NGX_PCRE)
+ ngx_uint_t r;
+ ngx_queue_t *regex;
+#endif
+
+ locations = pclcf->locations;
+
+ if (locations == NULL) {
+ return NGX_OK;
+ }
+
+ ngx_queue_sort(locations, ngx_http_cmp_locations);
+
+ named = NULL;
+ n = 0;
+#if (NGX_PCRE)
+ regex = NULL;
+ r = 0;
+#endif
+
+ for (q = ngx_queue_head(locations);
+ q != ngx_queue_sentinel(locations);
+ q = ngx_queue_next(q))
+ {
+ lq = (ngx_http_location_queue_t *) q;
+
+ clcf = lq->exact ? lq->exact : lq->inclusive;
+
+ if (ngx_http_init_locations(cf, NULL, clcf) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+#if (NGX_PCRE)
+
+ if (clcf->regex) {
+ r++;
+
+ if (regex == NULL) {
+ regex = q;
+ }
+
+ continue;
+ }
+
+#endif
+
+ if (clcf->named) {
+ n++;
+
+ if (named == NULL) {
+ named = q;
+ }
+
+ continue;
+ }
+
+ if (clcf->noname) {
+ break;
+ }
+ }
+
+ if (q != ngx_queue_sentinel(locations)) {
+ ngx_queue_split(locations, q, &tail);
+ }
+
+ if (named) {
+ clcfp = ngx_palloc(cf->pool,
+ (n + 1) * sizeof(ngx_http_core_loc_conf_t **));
+ if (clcfp == NULL) {
+ return NGX_ERROR;
+ }
+
+ cscf->named_locations = clcfp;
+
+ for (q = named;
+ q != ngx_queue_sentinel(locations);
+ q = ngx_queue_next(q))
+ {
+ lq = (ngx_http_location_queue_t *) q;
+
+ *(clcfp++) = lq->exact;
+ }
+
+ *clcfp = NULL;
+
+ ngx_queue_split(locations, named, &tail);
+ }
+
+#if (NGX_PCRE)
+
+ if (regex) {
+
+ clcfp = ngx_palloc(cf->pool,
+ (r + 1) * sizeof(ngx_http_core_loc_conf_t **));
+ if (clcfp == NULL) {
+ return NGX_ERROR;
+ }
+
+ pclcf->regex_locations = clcfp;
+
+ for (q = regex;
+ q != ngx_queue_sentinel(locations);
+ q = ngx_queue_next(q))
+ {
+ lq = (ngx_http_location_queue_t *) q;
+
+ *(clcfp++) = lq->exact;
+ }
+
+ *clcfp = NULL;
+
+ ngx_queue_split(locations, regex, &tail);
+ }
+
+#endif
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_init_static_location_trees(ngx_conf_t *cf,
+ ngx_http_core_loc_conf_t *pclcf)
+{
+ ngx_queue_t *q, *locations;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_location_queue_t *lq;
+
+ locations = pclcf->locations;
+
+ if (locations == NULL) {
+ return NGX_OK;
+ }
+
+ if (ngx_queue_empty(locations)) {
+ return NGX_OK;
+ }
+
+ for (q = ngx_queue_head(locations);
+ q != ngx_queue_sentinel(locations);
+ q = ngx_queue_next(q))
+ {
+ lq = (ngx_http_location_queue_t *) q;
+
+ clcf = lq->exact ? lq->exact : lq->inclusive;
+
+ if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ if (ngx_http_join_exact_locations(cf, locations) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_create_locations_list(locations, ngx_queue_head(locations));
+
+ pclcf->static_locations = ngx_http_create_locations_tree(cf, locations, 0);
+ if (pclcf->static_locations == NULL) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_add_location(ngx_conf_t *cf, ngx_queue_t **locations,
+ ngx_http_core_loc_conf_t *clcf)
+{
+ ngx_http_location_queue_t *lq;
+
+ if (*locations == NULL) {
+ *locations = ngx_palloc(cf->temp_pool,
+ sizeof(ngx_http_location_queue_t));
+ if (*locations == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_queue_init(*locations);
+ }
+
+ lq = ngx_palloc(cf->temp_pool, sizeof(ngx_http_location_queue_t));
+ if (lq == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (clcf->exact_match
+#if (NGX_PCRE)
+ || clcf->regex
+#endif
+ || clcf->named || clcf->noname)
+ {
+ lq->exact = clcf;
+ lq->inclusive = NULL;
+
+ } else {
+ lq->exact = NULL;
+ lq->inclusive = clcf;
+ }
+
+ lq->name = &clcf->name;
+ lq->file_name = cf->conf_file->file.name.data;
+ lq->line = cf->conf_file->line;
+
+ ngx_queue_init(&lq->list);
+
+ ngx_queue_insert_tail(*locations, &lq->queue);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_cmp_locations(const ngx_queue_t *one, const ngx_queue_t *two)
+{
+ ngx_int_t rc;
+ ngx_http_core_loc_conf_t *first, *second;
+ ngx_http_location_queue_t *lq1, *lq2;
+
+ lq1 = (ngx_http_location_queue_t *) one;
+ lq2 = (ngx_http_location_queue_t *) two;
+
+ first = lq1->exact ? lq1->exact : lq1->inclusive;
+ second = lq2->exact ? lq2->exact : lq2->inclusive;
+
+ if (first->noname && !second->noname) {
+ /* shift no named locations to the end */
+ return 1;
+ }
+
+ if (!first->noname && second->noname) {
+ /* shift no named locations to the end */
+ return -1;
+ }
+
+ if (first->noname || second->noname) {
+ /* do not sort no named locations */
+ return 0;
+ }
+
+ if (first->named && !second->named) {
+ /* shift named locations to the end */
+ return 1;
+ }
+
+ if (!first->named && second->named) {
+ /* shift named locations to the end */
+ return -1;
+ }
+
+ if (first->named && second->named) {
+ return ngx_strcmp(first->name.data, second->name.data);
+ }
+
+#if (NGX_PCRE)
+
+ if (first->regex && !second->regex) {
+ /* shift the regex matches to the end */
+ return 1;
+ }
+
+ if (!first->regex && second->regex) {
+ /* shift the regex matches to the end */
+ return -1;
+ }
+
+ if (first->regex || second->regex) {
+ /* do not sort the regex matches */
+ return 0;
+ }
+
+#endif
+
+ rc = ngx_strcmp(first->name.data, second->name.data);
+
+ if (rc == 0 && !first->exact_match && second->exact_match) {
+ /* an exact match must be before the same inclusive one */
+ return 1;
+ }
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_join_exact_locations(ngx_conf_t *cf, ngx_queue_t *locations)
+{
+ ngx_queue_t *q, *x;
+ ngx_http_location_queue_t *lq, *lx;
+
+ q = ngx_queue_head(locations);
+
+ while (q != ngx_queue_last(locations)) {
+
+ x = ngx_queue_next(q);
+
+ lq = (ngx_http_location_queue_t *) q;
+ lx = (ngx_http_location_queue_t *) x;
+
+ if (ngx_strcmp(lq->name->data, lx->name->data) == 0) {
+
+ if ((lq->exact && lx->exact) || (lq->inclusive && lx->inclusive)) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "duplicate location \"%V\" in %s:%ui",
+ lx->name, lx->file_name, lx->line);
+
+ return NGX_ERROR;
+ }
+
+ lq->inclusive = lx->inclusive;
+
+ ngx_queue_remove(x);
+
+ continue;
+ }
+
+ q = ngx_queue_next(q);
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_create_locations_list(ngx_queue_t *locations, ngx_queue_t *q)
+{
+ u_char *name;
+ size_t len;
+ ngx_queue_t *x, tail;
+ ngx_http_location_queue_t *lq, *lx;
+
+ if (q == ngx_queue_last(locations)) {
+ return;
+ }
+
+ lq = (ngx_http_location_queue_t *) q;
+
+ if (lq->inclusive == NULL) {
+ ngx_http_create_locations_list(locations, ngx_queue_next(q));
+ return;
+ }
+
+ len = lq->name->len;
+ name = lq->name->data;
+
+ for (x = ngx_queue_next(q);
+ x != ngx_queue_sentinel(locations);
+ x = ngx_queue_next(x))
+ {
+ lx = (ngx_http_location_queue_t *) x;
+
+ if (len > lx->name->len
+ || (ngx_strncmp(name, lx->name->data, len) != 0))
+ {
+ break;
+ }
+ }
+
+ q = ngx_queue_next(q);
+
+ if (q == x) {
+ ngx_http_create_locations_list(locations, x);
+ return;
+ }
+
+ ngx_queue_split(locations, q, &tail);
+ ngx_queue_add(&lq->list, &tail);
+
+ if (x == ngx_queue_sentinel(locations)) {
+ ngx_http_create_locations_list(&lq->list, ngx_queue_head(&lq->list));
+ return;
+ }
+
+ ngx_queue_split(&lq->list, x, &tail);
+ ngx_queue_add(locations, &tail);
+
+ ngx_http_create_locations_list(&lq->list, ngx_queue_head(&lq->list));
+
+ ngx_http_create_locations_list(locations, x);
+}
+
+
+/*
+ * to keep cache locality for left leaf nodes, allocate nodes in following
+ * order: node, left subtree, right subtree, inclusive subtree
+ */
+
+static ngx_http_location_tree_node_t *
+ngx_http_create_locations_tree(ngx_conf_t *cf, ngx_queue_t *locations,
+ size_t prefix)
+{
+ size_t len;
+ ngx_queue_t *q, tail;
+ ngx_http_location_queue_t *lq;
+ ngx_http_location_tree_node_t *node;
+
+ q = ngx_queue_middle(locations);
+
+ lq = (ngx_http_location_queue_t *) q;
+ len = lq->name->len - prefix;
+
+ node = ngx_palloc(cf->pool,
+ offsetof(ngx_http_location_tree_node_t, name) + len);
+ if (node == NULL) {
+ return NULL;
+ }
+
+ node->left = NULL;
+ node->right = NULL;
+ node->tree = NULL;
+ node->exact = lq->exact;
+ node->inclusive = lq->inclusive;
+
+ node->auto_redirect = (u_char) ((lq->exact && lq->exact->auto_redirect)
+ || (lq->inclusive && lq->inclusive->auto_redirect));
+
+ node->len = (u_char) len;
+ ngx_memcpy(node->name, &lq->name->data[prefix], len);
+
+ ngx_queue_split(locations, q, &tail);
+
+ if (ngx_queue_empty(locations)) {
+ /*
+ * ngx_queue_split() insures that if left part is empty,
+ * then right one is empty too
+ */
+ goto inclusive;
+ }
+
+ node->left = ngx_http_create_locations_tree(cf, locations, prefix);
+ if (node->left == NULL) {
+ return NULL;
+ }
+
+ ngx_queue_remove(q);
+
+ if (ngx_queue_empty(&tail)) {
+ goto inclusive;
+ }
+
+ node->right = ngx_http_create_locations_tree(cf, &tail, prefix);
+ if (node->right == NULL) {
+ return NULL;
+ }
+
+inclusive:
+
+ if (ngx_queue_empty(&lq->list)) {
+ return node;
+ }
+
+ node->tree = ngx_http_create_locations_tree(cf, &lq->list, prefix + len);
+ if (node->tree == NULL) {
+ return NULL;
+ }
+
+ return node;
+}
+
+
+ngx_int_t
+ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
+ ngx_http_listen_opt_t *lsopt)
+{
+ in_port_t p;
+ ngx_uint_t i;
+ struct sockaddr *sa;
+ struct sockaddr_in *sin;
+ ngx_http_conf_port_t *port;
+ ngx_http_core_main_conf_t *cmcf;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+#endif
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ if (cmcf->ports == NULL) {
+ cmcf->ports = ngx_array_create(cf->temp_pool, 2,
+ sizeof(ngx_http_conf_port_t));
+ if (cmcf->ports == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ sa = &lsopt->u.sockaddr;
+
+ switch (sa->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = &lsopt->u.sockaddr_in6;
+ p = sin6->sin6_port;
+ break;
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+ case AF_UNIX:
+ p = 0;
+ break;
+#endif
+
+ default: /* AF_INET */
+ sin = &lsopt->u.sockaddr_in;
+ p = sin->sin_port;
+ break;
+ }
+
+ port = cmcf->ports->elts;
+ for (i = 0; i < cmcf->ports->nelts; i++) {
+
+ if (p != port[i].port || sa->sa_family != port[i].family) {
+ continue;
+ }
+
+ /* a port is already in the port list */
+
+ return ngx_http_add_addresses(cf, cscf, &port[i], lsopt);
+ }
+
+ /* add a port to the port list */
+
+ port = ngx_array_push(cmcf->ports);
+ if (port == NULL) {
+ return NGX_ERROR;
+ }
+
+ port->family = sa->sa_family;
+ port->port = p;
+ port->addrs.elts = NULL;
+
+ return ngx_http_add_address(cf, cscf, port, lsopt);
+}
+
+
+static ngx_int_t
+ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
+ ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
+{
+ u_char *p;
+ size_t len, off;
+ ngx_uint_t i, default_server;
+ struct sockaddr *sa;
+ ngx_http_conf_addr_t *addr;
+#if (NGX_HAVE_UNIX_DOMAIN)
+ struct sockaddr_un *saun;
+#endif
+#if (NGX_HTTP_SSL)
+ ngx_uint_t ssl;
+#endif
+
+ /*
+ * we can not compare whole sockaddr struct's as kernel
+ * may fill some fields in inherited sockaddr struct's
+ */
+
+ sa = &lsopt->u.sockaddr;
+
+ switch (sa->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ off = offsetof(struct sockaddr_in6, sin6_addr);
+ len = 16;
+ break;
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+ case AF_UNIX:
+ off = offsetof(struct sockaddr_un, sun_path);
+ len = sizeof(saun->sun_path);
+ break;
+#endif
+
+ default: /* AF_INET */
+ off = offsetof(struct sockaddr_in, sin_addr);
+ len = 4;
+ break;
+ }
+
+ p = lsopt->u.sockaddr_data + off;
+
+ addr = port->addrs.elts;
+
+ for (i = 0; i < port->addrs.nelts; i++) {
+
+ if (ngx_memcmp(p, addr[i].opt.u.sockaddr_data + off, len) != 0) {
+ continue;
+ }
+
+ /* the address is already in the address list */
+
+ if (ngx_http_add_server(cf, cscf, &addr[i]) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ /* preserve default_server bit during listen options overwriting */
+ default_server = addr[i].opt.default_server;
+
+#if (NGX_HTTP_SSL)
+ ssl = lsopt->ssl || addr[i].opt.ssl;
+#endif
+
+ if (lsopt->set) {
+
+ if (addr[i].opt.set) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "a duplicate listen options for %s", addr[i].opt.addr);
+ return NGX_ERROR;
+ }
+
+ addr[i].opt = *lsopt;
+ }
+
+ /* check the duplicate "default" server for this address:port */
+
+ if (lsopt->default_server) {
+
+ if (default_server) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "a duplicate default server for %s", addr[i].opt.addr);
+ return NGX_ERROR;
+ }
+
+ default_server = 1;
+ addr[i].default_server = cscf;
+ }
+
+ addr[i].opt.default_server = default_server;
+#if (NGX_HTTP_SSL)
+ addr[i].opt.ssl = ssl;
+#endif
+
+ return NGX_OK;
+ }
+
+ /* add the address to the addresses list that bound to this port */
+
+ return ngx_http_add_address(cf, cscf, port, lsopt);
+}
+
+
+/*
+ * add the server address, the server names and the server core module
+ * configurations to the port list
+ */
+
+static ngx_int_t
+ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
+ ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
+{
+ ngx_http_conf_addr_t *addr;
+
+ if (port->addrs.elts == NULL) {
+ if (ngx_array_init(&port->addrs, cf->temp_pool, 4,
+ sizeof(ngx_http_conf_addr_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ addr = ngx_array_push(&port->addrs);
+ if (addr == NULL) {
+ return NGX_ERROR;
+ }
+
+ addr->opt = *lsopt;
+ addr->hash.buckets = NULL;
+ addr->hash.size = 0;
+ addr->wc_head = NULL;
+ addr->wc_tail = NULL;
+#if (NGX_PCRE)
+ addr->nregex = 0;
+ addr->regex = NULL;
+#endif
+ addr->default_server = cscf;
+ addr->servers.elts = NULL;
+
+ return ngx_http_add_server(cf, cscf, addr);
+}
+
+
+/* add the server core module configuration to the address:port */
+
+static ngx_int_t
+ngx_http_add_server(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
+ ngx_http_conf_addr_t *addr)
+{
+ ngx_uint_t i;
+ ngx_http_core_srv_conf_t **server;
+
+ if (addr->servers.elts == NULL) {
+ if (ngx_array_init(&addr->servers, cf->temp_pool, 4,
+ sizeof(ngx_http_core_srv_conf_t *))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ } else {
+ server = addr->servers.elts;
+ for (i = 0; i < addr->servers.nelts; i++) {
+ if (server[i] == cscf) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "a duplicate listen %s", addr->opt.addr);
+ return NGX_ERROR;
+ }
+ }
+ }
+
+ server = ngx_array_push(&addr->servers);
+ if (server == NULL) {
+ return NGX_ERROR;
+ }
+
+ *server = cscf;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_optimize_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
+ ngx_array_t *ports)
+{
+ ngx_uint_t p, a;
+ ngx_http_conf_port_t *port;
+ ngx_http_conf_addr_t *addr;
+
+ if (ports == NULL) {
+ return NGX_OK;
+ }
+
+ port = ports->elts;
+ for (p = 0; p < ports->nelts; p++) {
+
+ ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
+ sizeof(ngx_http_conf_addr_t), ngx_http_cmp_conf_addrs);
+
+ /*
+ * check whether all name-based servers have the same
+ * configuraiton as a default server for given address:port
+ */
+
+ addr = port[p].addrs.elts;
+ for (a = 0; a < port[p].addrs.nelts; a++) {
+
+ if (addr[a].servers.nelts > 1
+#if (NGX_PCRE)
+ || addr[a].default_server->captures
+#endif
+ )
+ {
+ if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+ }
+
+ if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_server_names(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
+ ngx_http_conf_addr_t *addr)
+{
+ ngx_int_t rc;
+ ngx_uint_t n, s;
+ ngx_hash_init_t hash;
+ ngx_hash_keys_arrays_t ha;
+ ngx_http_server_name_t *name;
+ ngx_http_core_srv_conf_t **cscfp;
+#if (NGX_PCRE)
+ ngx_uint_t regex, i;
+
+ regex = 0;
+#endif
+
+ ngx_memzero(&ha, sizeof(ngx_hash_keys_arrays_t));
+
+ ha.temp_pool = ngx_create_pool(16384, cf->log);
+ if (ha.temp_pool == NULL) {
+ return NGX_ERROR;
+ }
+
+ ha.pool = cf->pool;
+
+ if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK) {
+ goto failed;
+ }
+
+ cscfp = addr->servers.elts;
+
+ for (s = 0; s < addr->servers.nelts; s++) {
+
+ name = cscfp[s]->server_names.elts;
+
+ for (n = 0; n < cscfp[s]->server_names.nelts; n++) {
+
+#if (NGX_PCRE)
+ if (name[n].regex) {
+ regex++;
+ continue;
+ }
+#endif
+
+ rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server,
+ NGX_HASH_WILDCARD_KEY);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DECLINED) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "invalid server name or wildcard \"%V\" on %s",
+ &name[n].name, addr->opt.addr);
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_BUSY) {
+ ngx_log_error(NGX_LOG_WARN, cf->log, 0,
+ "conflicting server name \"%V\" on %s, ignored",
+ &name[n].name, addr->opt.addr);
+ }
+ }
+ }
+
+ hash.key = ngx_hash_key_lc;
+ hash.max_size = cmcf->server_names_hash_max_size;
+ hash.bucket_size = cmcf->server_names_hash_bucket_size;
+ hash.name = "server_names_hash";
+ hash.pool = cf->pool;
+
+ if (ha.keys.nelts) {
+ hash.hash = &addr->hash;
+ hash.temp_pool = NULL;
+
+ if (ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK) {
+ goto failed;
+ }
+ }
+
+ if (ha.dns_wc_head.nelts) {
+
+ ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts,
+ sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards);
+
+ hash.hash = NULL;
+ hash.temp_pool = ha.temp_pool;
+
+ if (ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts,
+ ha.dns_wc_head.nelts)
+ != NGX_OK)
+ {
+ goto failed;
+ }
+
+ addr->wc_head = (ngx_hash_wildcard_t *) hash.hash;
+ }
+
+ if (ha.dns_wc_tail.nelts) {
+
+ ngx_qsort(ha.dns_wc_tail.elts, (size_t) ha.dns_wc_tail.nelts,
+ sizeof(ngx_hash_key_t), ngx_http_cmp_dns_wildcards);
+
+ hash.hash = NULL;
+ hash.temp_pool = ha.temp_pool;
+
+ if (ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts,
+ ha.dns_wc_tail.nelts)
+ != NGX_OK)
+ {
+ goto failed;
+ }
+
+ addr->wc_tail = (ngx_hash_wildcard_t *) hash.hash;
+ }
+
+ ngx_destroy_pool(ha.temp_pool);
+
+#if (NGX_PCRE)
+
+ if (regex == 0) {
+ return NGX_OK;
+ }
+
+ addr->nregex = regex;
+ addr->regex = ngx_palloc(cf->pool, regex * sizeof(ngx_http_server_name_t));
+ if (addr->regex == NULL) {
+ return NGX_ERROR;
+ }
+
+ i = 0;
+
+ for (s = 0; s < addr->servers.nelts; s++) {
+
+ name = cscfp[s]->server_names.elts;
+
+ for (n = 0; n < cscfp[s]->server_names.nelts; n++) {
+ if (name[n].regex) {
+ addr->regex[i++] = name[n];
+ }
+ }
+ }
+
+#endif
+
+ return NGX_OK;
+
+failed:
+
+ ngx_destroy_pool(ha.temp_pool);
+
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_cmp_conf_addrs(const void *one, const void *two)
+{
+ ngx_http_conf_addr_t *first, *second;
+
+ first = (ngx_http_conf_addr_t *) one;
+ second = (ngx_http_conf_addr_t *) two;
+
+ if (first->opt.wildcard) {
+ /* a wildcard address must be the last resort, shift it to the end */
+ return 1;
+ }
+
+ if (first->opt.bind && !second->opt.bind) {
+ /* shift explicit bind()ed addresses to the start */
+ return -1;
+ }
+
+ if (!first->opt.bind && second->opt.bind) {
+ /* shift explicit bind()ed addresses to the start */
+ return 1;
+ }
+
+ /* do not sort by default */
+
+ return 0;
+}
+
+
+static int ngx_libc_cdecl
+ngx_http_cmp_dns_wildcards(const void *one, const void *two)
+{
+ ngx_hash_key_t *first, *second;
+
+ first = (ngx_hash_key_t *) one;
+ second = (ngx_hash_key_t *) two;
+
+ return ngx_dns_strcmp(first->key.data, second->key.data);
+}
+
+
+static ngx_int_t
+ngx_http_init_listening(ngx_conf_t *cf, ngx_http_conf_port_t *port)
+{
+ ngx_uint_t i, last, bind_wildcard;
+ ngx_listening_t *ls;
+ ngx_http_port_t *hport;
+ ngx_http_conf_addr_t *addr;
+
+ addr = port->addrs.elts;
+ last = port->addrs.nelts;
+
+ /*
+ * If there is a binding to an "*:port" then we need to bind() to
+ * the "*:port" only and ignore other implicit bindings. The bindings
+ * have been already sorted: explicit bindings are on the start, then
+ * implicit bindings go, and wildcard binding is in the end.
+ */
+
+ if (addr[last - 1].opt.wildcard) {
+ addr[last - 1].opt.bind = 1;
+ bind_wildcard = 1;
+
+ } else {
+ bind_wildcard = 0;
+ }
+
+ i = 0;
+
+ while (i < last) {
+
+ if (bind_wildcard && !addr[i].opt.bind) {
+ i++;
+ continue;
+ }
+
+ ls = ngx_http_add_listening(cf, &addr[i]);
+ if (ls == NULL) {
+ return NGX_ERROR;
+ }
+
+ hport = ngx_pcalloc(cf->pool, sizeof(ngx_http_port_t));
+ if (hport == NULL) {
+ return NGX_ERROR;
+ }
+
+ ls->servers = hport;
+
+ if (i == last - 1) {
+ hport->naddrs = last;
+
+ } else {
+ hport->naddrs = 1;
+ i = 0;
+ }
+
+ switch (ls->sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ if (ngx_http_add_addrs6(cf, hport, addr) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ break;
+#endif
+ default: /* AF_INET */
+ if (ngx_http_add_addrs(cf, hport, addr) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ break;
+ }
+
+ addr++;
+ last--;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_listening_t *
+ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
+{
+ ngx_listening_t *ls;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_core_srv_conf_t *cscf;
+
+ ls = ngx_create_listening(cf, &addr->opt.u.sockaddr, addr->opt.socklen);
+ if (ls == NULL) {
+ return NULL;
+ }
+
+ ls->addr_ntop = 1;
+
+ ls->handler = ngx_http_init_connection;
+
+ cscf = addr->default_server;
+ ls->pool_size = cscf->connection_pool_size;
+ ls->post_accept_timeout = cscf->client_header_timeout;
+
+ clcf = cscf->ctx->loc_conf[ngx_http_core_module.ctx_index];
+
+ ls->logp = clcf->error_log;
+ ls->log.data = &ls->addr_text;
+ ls->log.handler = ngx_accept_log_error;
+
+#if (NGX_WIN32)
+ {
+ ngx_iocp_conf_t *iocpcf;
+
+ iocpcf = ngx_event_get_conf(cf->cycle->conf_ctx, ngx_iocp_module);
+ if (iocpcf->acceptex_read) {
+ ls->post_accept_buffer_size = cscf->client_header_buffer_size;
+ }
+ }
+#endif
+
+ ls->backlog = addr->opt.backlog;
+ ls->rcvbuf = addr->opt.rcvbuf;
+ ls->sndbuf = addr->opt.sndbuf;
+
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
+ ls->accept_filter = addr->opt.accept_filter;
+#endif
+
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
+ ls->deferred_accept = addr->opt.deferred_accept;
+#endif
+
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+ ls->ipv6only = addr->opt.ipv6only;
+#endif
+
+#if (NGX_HAVE_SETFIB)
+ ls->setfib = addr->opt.setfib;
+#endif
+
+ return ls;
+}
+
+
+static ngx_int_t
+ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport,
+ ngx_http_conf_addr_t *addr)
+{
+ ngx_uint_t i;
+ ngx_http_in_addr_t *addrs;
+ struct sockaddr_in *sin;
+ ngx_http_virtual_names_t *vn;
+
+ hport->addrs = ngx_pcalloc(cf->pool,
+ hport->naddrs * sizeof(ngx_http_in_addr_t));
+ if (hport->addrs == NULL) {
+ return NGX_ERROR;
+ }
+
+ addrs = hport->addrs;
+
+ for (i = 0; i < hport->naddrs; i++) {
+
+ sin = &addr[i].opt.u.sockaddr_in;
+ addrs[i].addr = sin->sin_addr.s_addr;
+ addrs[i].conf.default_server = addr[i].default_server;
+#if (NGX_HTTP_SSL)
+ addrs[i].conf.ssl = addr[i].opt.ssl;
+#endif
+
+ if (addr[i].hash.buckets == NULL
+ && (addr[i].wc_head == NULL
+ || addr[i].wc_head->hash.buckets == NULL)
+ && (addr[i].wc_tail == NULL
+ || addr[i].wc_tail->hash.buckets == NULL)
+#if (NGX_PCRE)
+ && addr[i].nregex == 0
+#endif
+ )
+ {
+ continue;
+ }
+
+ vn = ngx_palloc(cf->pool, sizeof(ngx_http_virtual_names_t));
+ if (vn == NULL) {
+ return NGX_ERROR;
+ }
+
+ addrs[i].conf.virtual_names = vn;
+
+ vn->names.hash = addr[i].hash;
+ vn->names.wc_head = addr[i].wc_head;
+ vn->names.wc_tail = addr[i].wc_tail;
+#if (NGX_PCRE)
+ vn->nregex = addr[i].nregex;
+ vn->regex = addr[i].regex;
+#endif
+ }
+
+ return NGX_OK;
+}
+
+
+#if (NGX_HAVE_INET6)
+
+static ngx_int_t
+ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport,
+ ngx_http_conf_addr_t *addr)
+{
+ ngx_uint_t i;
+ ngx_http_in6_addr_t *addrs6;
+ struct sockaddr_in6 *sin6;
+ ngx_http_virtual_names_t *vn;
+
+ hport->addrs = ngx_pcalloc(cf->pool,
+ hport->naddrs * sizeof(ngx_http_in6_addr_t));
+ if (hport->addrs == NULL) {
+ return NGX_ERROR;
+ }
+
+ addrs6 = hport->addrs;
+
+ for (i = 0; i < hport->naddrs; i++) {
+
+ sin6 = &addr[i].opt.u.sockaddr_in6;
+ addrs6[i].addr6 = sin6->sin6_addr;
+ addrs6[i].conf.default_server = addr[i].default_server;
+#if (NGX_HTTP_SSL)
+ addrs6[i].conf.ssl = addr[i].opt.ssl;
+#endif
+
+ if (addr[i].hash.buckets == NULL
+ && (addr[i].wc_head == NULL
+ || addr[i].wc_head->hash.buckets == NULL)
+ && (addr[i].wc_tail == NULL
+ || addr[i].wc_tail->hash.buckets == NULL)
+#if (NGX_PCRE)
+ && addr[i].nregex == 0
+#endif
+ )
+ {
+ continue;
+ }
+
+ vn = ngx_palloc(cf->pool, sizeof(ngx_http_virtual_names_t));
+ if (vn == NULL) {
+ return NGX_ERROR;
+ }
+
+ addrs6[i].conf.virtual_names = vn;
+
+ vn->names.hash = addr[i].hash;
+ vn->names.wc_head = addr[i].wc_head;
+ vn->names.wc_tail = addr[i].wc_tail;
+#if (NGX_PCRE)
+ vn->nregex = addr[i].nregex;
+ vn->regex = addr[i].regex;
+#endif
+ }
+
+ return NGX_OK;
+}
+
+#endif
+
+
+char *
+ngx_http_types_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *p = conf;
+
+ ngx_array_t **types;
+ ngx_str_t *value, *default_type;
+ ngx_uint_t i, n, hash;
+ ngx_hash_key_t *type;
+
+ types = (ngx_array_t **) (p + cmd->offset);
+
+ if (*types == (void *) -1) {
+ return NGX_CONF_OK;
+ }
+
+ default_type = cmd->post;
+
+ if (*types == NULL) {
+ *types = ngx_array_create(cf->temp_pool, 1, sizeof(ngx_hash_key_t));
+ if (*types == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (default_type) {
+ type = ngx_array_push(*types);
+ if (type == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ type->key = *default_type;
+ type->key_hash = ngx_hash_key(default_type->data,
+ default_type->len);
+ type->value = (void *) 4;
+ }
+ }
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+
+ if (value[i].len == 1 && value[i].data[0] == '*') {
+ *types = (void *) -1;
+ return NGX_CONF_OK;
+ }
+
+ hash = ngx_hash_strlow(value[i].data, value[i].data, value[i].len);
+ value[i].data[value[i].len] = '\0';
+
+ type = (*types)->elts;
+ for (n = 0; n < (*types)->nelts; n++) {
+
+ if (ngx_strcmp(value[i].data, type[n].key.data) == 0) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "duplicate MIME type \"%V\"", &value[i]);
+ continue;
+ }
+ }
+
+ type = ngx_array_push(*types);
+ if (type == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ type->key = value[i];
+ type->key_hash = hash;
+ type->value = (void *) 4;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+char *
+ngx_http_merge_types(ngx_conf_t *cf, ngx_array_t **keys, ngx_hash_t *types_hash,
+ ngx_array_t **prev_keys, ngx_hash_t *prev_types_hash,
+ ngx_str_t *default_types)
+{
+ ngx_hash_init_t hash;
+
+ if (*keys) {
+
+ if (*keys == (void *) -1) {
+ return NGX_CONF_OK;
+ }
+
+ hash.hash = types_hash;
+ hash.key = NULL;
+ hash.max_size = 2048;
+ hash.bucket_size = 64;
+ hash.name = "test_types_hash";
+ hash.pool = cf->pool;
+ hash.temp_pool = NULL;
+
+ if (ngx_hash_init(&hash, (*keys)->elts, (*keys)->nelts) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+ }
+
+ if (prev_types_hash->buckets == NULL) {
+
+ if (*prev_keys == NULL) {
+
+ if (ngx_http_set_default_types(cf, prev_keys, default_types)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ } else if (*prev_keys == (void *) -1) {
+ *keys = *prev_keys;
+ return NGX_CONF_OK;
+ }
+
+ hash.hash = prev_types_hash;
+ hash.key = NULL;
+ hash.max_size = 2048;
+ hash.bucket_size = 64;
+ hash.name = "test_types_hash";
+ hash.pool = cf->pool;
+ hash.temp_pool = NULL;
+
+ if (ngx_hash_init(&hash, (*prev_keys)->elts, (*prev_keys)->nelts)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ *types_hash = *prev_types_hash;
+
+ return NGX_CONF_OK;
+
+}
+
+
+ngx_int_t
+ngx_http_set_default_types(ngx_conf_t *cf, ngx_array_t **types,
+ ngx_str_t *default_type)
+{
+ ngx_hash_key_t *type;
+
+ *types = ngx_array_create(cf->temp_pool, 1, sizeof(ngx_hash_key_t));
+ if (*types == NULL) {
+ return NGX_ERROR;
+ }
+
+ while (default_type->len) {
+
+ type = ngx_array_push(*types);
+ if (type == NULL) {
+ return NGX_ERROR;
+ }
+
+ type->key = *default_type;
+ type->key_hash = ngx_hash_key(default_type->data,
+ default_type->len);
+ type->value = (void *) 4;
+
+ default_type++;
+ }
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/ngx_http.h b/usr.sbin/nginx/src/http/ngx_http.h
new file mode 100644
index 00000000000..e3619e148b4
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http.h
@@ -0,0 +1,159 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_H_INCLUDED_
+#define _NGX_HTTP_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct ngx_http_request_s ngx_http_request_t;
+typedef struct ngx_http_upstream_s ngx_http_upstream_t;
+typedef struct ngx_http_cache_s ngx_http_cache_t;
+typedef struct ngx_http_file_cache_s ngx_http_file_cache_t;
+typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t;
+
+typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+typedef u_char *(*ngx_http_log_handler_pt)(ngx_http_request_t *r,
+ ngx_http_request_t *sr, u_char *buf, size_t len);
+
+
+#include <ngx_http_variables.h>
+#include <ngx_http_request.h>
+#include <ngx_http_upstream.h>
+#include <ngx_http_upstream_round_robin.h>
+#include <ngx_http_config.h>
+#include <ngx_http_busy_lock.h>
+#include <ngx_http_script.h>
+#include <ngx_http_core_module.h>
+
+#if (NGX_HTTP_CACHE)
+#include <ngx_http_cache.h>
+#endif
+#if (NGX_HTTP_SSI)
+#include <ngx_http_ssi_filter_module.h>
+#endif
+#if (NGX_HTTP_SSL)
+#include <ngx_http_ssl_module.h>
+#endif
+
+
+struct ngx_http_log_ctx_s {
+ ngx_connection_t *connection;
+ ngx_http_request_t *request;
+ ngx_http_request_t *current_request;
+};
+
+
+typedef struct {
+ ngx_uint_t code;
+ ngx_uint_t count;
+ u_char *start;
+ u_char *end;
+} ngx_http_status_t;
+
+
+#define ngx_http_get_module_ctx(r, module) (r)->ctx[module.ctx_index]
+#define ngx_http_set_ctx(r, c, module) r->ctx[module.ctx_index] = c;
+
+
+ngx_int_t ngx_http_add_location(ngx_conf_t *cf, ngx_queue_t **locations,
+ ngx_http_core_loc_conf_t *clcf);
+ngx_int_t ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
+ ngx_http_listen_opt_t *lsopt);
+
+
+void ngx_http_init_connection(ngx_connection_t *c);
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+int ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg);
+#endif
+
+ngx_int_t ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b);
+ngx_int_t ngx_http_parse_complex_uri(ngx_http_request_t *r,
+ ngx_uint_t merge_slashes);
+ngx_int_t ngx_http_parse_status_line(ngx_http_request_t *r, ngx_buf_t *b,
+ ngx_http_status_t *status);
+ngx_int_t ngx_http_parse_unsafe_uri(ngx_http_request_t *r, ngx_str_t *uri,
+ ngx_str_t *args, ngx_uint_t *flags);
+ngx_int_t ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b,
+ ngx_uint_t allow_underscores);
+ngx_int_t ngx_http_parse_multi_header_lines(ngx_array_t *headers,
+ ngx_str_t *name, ngx_str_t *value);
+ngx_int_t ngx_http_arg(ngx_http_request_t *r, u_char *name, size_t len,
+ ngx_str_t *value);
+void ngx_http_split_args(ngx_http_request_t *r, ngx_str_t *uri,
+ ngx_str_t *args);
+
+
+ngx_int_t ngx_http_find_server_conf(ngx_http_request_t *r);
+void ngx_http_update_location_config(ngx_http_request_t *r);
+void ngx_http_handler(ngx_http_request_t *r);
+void ngx_http_run_posted_requests(ngx_connection_t *c);
+ngx_int_t ngx_http_post_request(ngx_http_request_t *r,
+ ngx_http_posted_request_t *pr);
+void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc);
+
+void ngx_http_empty_handler(ngx_event_t *wev);
+void ngx_http_request_empty_handler(ngx_http_request_t *r);
+
+
+#define ngx_http_ephemeral(r) (void *) (&r->uri_start)
+
+
+#define NGX_HTTP_LAST 1
+#define NGX_HTTP_FLUSH 2
+
+ngx_int_t ngx_http_send_special(ngx_http_request_t *r, ngx_uint_t flags);
+
+
+ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r,
+ ngx_http_client_body_handler_pt post_handler);
+
+ngx_int_t ngx_http_send_header(ngx_http_request_t *r);
+ngx_int_t ngx_http_special_response_handler(ngx_http_request_t *r,
+ ngx_int_t error);
+ngx_int_t ngx_http_filter_finalize_request(ngx_http_request_t *r,
+ ngx_module_t *m, ngx_int_t error);
+void ngx_http_clean_header(ngx_http_request_t *r);
+
+
+time_t ngx_http_parse_time(u_char *value, size_t len);
+size_t ngx_http_get_time(char *buf, time_t t);
+
+
+
+ngx_int_t ngx_http_discard_request_body(ngx_http_request_t *r);
+void ngx_http_discarded_request_body_handler(ngx_http_request_t *r);
+void ngx_http_block_reading(ngx_http_request_t *r);
+void ngx_http_test_reading(ngx_http_request_t *r);
+
+
+char *ngx_http_types_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_http_merge_types(ngx_conf_t *cf, ngx_array_t **keys,
+ ngx_hash_t *types_hash, ngx_array_t **prev_keys,
+ ngx_hash_t *prev_types_hash, ngx_str_t *default_types);
+ngx_int_t ngx_http_set_default_types(ngx_conf_t *cf, ngx_array_t **types,
+ ngx_str_t *default_type);
+
+#if (NGX_HTTP_DEGRADATION)
+ngx_uint_t ngx_http_degraded(ngx_http_request_t *);
+#endif
+
+
+extern ngx_module_t ngx_http_module;
+
+extern ngx_str_t ngx_http_html_default_types[];
+
+
+extern ngx_http_output_header_filter_pt ngx_http_top_header_filter;
+extern ngx_http_output_body_filter_pt ngx_http_top_body_filter;
+
+
+#endif /* _NGX_HTTP_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/http/ngx_http_busy_lock.c b/usr.sbin/nginx/src/http/ngx_http_busy_lock.c
new file mode 100644
index 00000000000..fd382433117
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_busy_lock.c
@@ -0,0 +1,306 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+
+static int ngx_http_busy_lock_look_cacheable(ngx_http_busy_lock_t *bl,
+ ngx_http_busy_lock_ctx_t *bc,
+ int lock);
+
+
+int ngx_http_busy_lock(ngx_http_busy_lock_t *bl, ngx_http_busy_lock_ctx_t *bc)
+{
+ if (bl->busy < bl->max_busy) {
+ bl->busy++;
+
+ if (bc->time) {
+ bc->time = 0;
+ bl->waiting--;
+ }
+
+ return NGX_OK;
+ }
+
+ if (bc->time) {
+ if (bc->time < bl->timeout) {
+ ngx_add_timer(bc->event, 1000);
+ return NGX_AGAIN;
+ }
+
+ bl->waiting--;
+ return NGX_DONE;
+
+ }
+
+ if (bl->timeout == 0) {
+ return NGX_DONE;
+ }
+
+ if (bl->waiting < bl->max_waiting) {
+ bl->waiting++;
+
+#if 0
+ ngx_add_timer(bc->event, 1000);
+ bc->event->event_handler = bc->event_handler;
+#endif
+
+ /* TODO: ngx_handle_level_read_event() */
+
+ return NGX_AGAIN;
+ }
+
+ return NGX_ERROR;
+}
+
+
+int ngx_http_busy_lock_cacheable(ngx_http_busy_lock_t *bl,
+ ngx_http_busy_lock_ctx_t *bc, int lock)
+{
+ int rc;
+
+ rc = ngx_http_busy_lock_look_cacheable(bl, bc, lock);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, bc->event->log, 0,
+ "http busylock: %d w:%d mw::%d",
+ rc, bl->waiting, bl->max_waiting);
+
+ if (rc == NGX_OK) { /* no the same request, there's free slot */
+ return NGX_OK;
+ }
+
+ if (rc == NGX_ERROR && !lock) { /* no the same request, no free slot */
+ return NGX_OK;
+ }
+
+ /* rc == NGX_AGAIN: the same request */
+
+ if (bc->time) {
+ if (bc->time < bl->timeout) {
+ ngx_add_timer(bc->event, 1000);
+ return NGX_AGAIN;
+ }
+
+ bl->waiting--;
+ return NGX_DONE;
+
+ }
+
+ if (bl->timeout == 0) {
+ return NGX_DONE;
+ }
+
+ if (bl->waiting < bl->max_waiting) {
+#if 0
+ bl->waiting++;
+ ngx_add_timer(bc->event, 1000);
+ bc->event->event_handler = bc->event_handler;
+#endif
+
+ /* TODO: ngx_handle_level_read_event() */
+
+ return NGX_AGAIN;
+ }
+
+ return NGX_ERROR;
+}
+
+
+void ngx_http_busy_unlock(ngx_http_busy_lock_t *bl,
+ ngx_http_busy_lock_ctx_t *bc)
+{
+ if (bl == NULL) {
+ return;
+ }
+
+ if (bl->md5) {
+ bl->md5_mask[bc->slot / 8] &= ~(1 << (bc->slot & 7));
+ bl->cacheable--;
+ }
+
+ bl->busy--;
+}
+
+
+static int ngx_http_busy_lock_look_cacheable(ngx_http_busy_lock_t *bl,
+ ngx_http_busy_lock_ctx_t *bc,
+ int lock)
+{
+ int i, b, cacheable, free;
+ u_int mask;
+
+ b = 0;
+ cacheable = 0;
+ free = -1;
+
+#if (NGX_SUPPRESS_WARN)
+ mask = 0;
+#endif
+
+ for (i = 0; i < bl->max_busy; i++) {
+
+ if ((b & 7) == 0) {
+ mask = bl->md5_mask[i / 8];
+ }
+
+ if (mask & 1) {
+ if (ngx_memcmp(&bl->md5[i * 16], bc->md5, 16) == 0) {
+ return NGX_AGAIN;
+ }
+ cacheable++;
+
+ } else if (free == -1) {
+ free = i;
+ }
+
+#if 1
+ if (cacheable == bl->cacheable) {
+ if (free == -1 && cacheable < bl->max_busy) {
+ free = i + 1;
+ }
+
+ break;
+ }
+#endif
+
+ mask >>= 1;
+ b++;
+ }
+
+ if (free == -1) {
+ return NGX_ERROR;
+ }
+
+ if (lock) {
+ if (bl->busy == bl->max_busy) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(&bl->md5[free * 16], bc->md5, 16);
+ bl->md5_mask[free / 8] |= 1 << (free & 7);
+ bc->slot = free;
+
+ bl->cacheable++;
+ bl->busy++;
+ }
+
+ return NGX_OK;
+}
+
+
+char *ngx_http_set_busy_lock_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf)
+{
+ char *p = conf;
+
+ ngx_uint_t i, dup, invalid;
+ ngx_str_t *value, line;
+ ngx_http_busy_lock_t *bl, **blp;
+
+ blp = (ngx_http_busy_lock_t **) (p + cmd->offset);
+ if (*blp) {
+ return "is duplicate";
+ }
+
+ /* ngx_calloc_shared() */
+ bl = ngx_pcalloc(cf->pool, sizeof(ngx_http_busy_lock_t));
+ if (bl == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ *blp = bl;
+
+ /* ngx_calloc_shared() */
+ bl->mutex = ngx_pcalloc(cf->pool, sizeof(ngx_event_mutex_t));
+ if (bl->mutex == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ dup = 0;
+ invalid = 0;
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+
+ if (value[i].data[1] != '=') {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid value \"%s\"", value[i].data);
+ return NGX_CONF_ERROR;
+ }
+
+ switch (value[i].data[0]) {
+
+ case 'b':
+ if (bl->max_busy) {
+ dup = 1;
+ break;
+ }
+
+ bl->max_busy = ngx_atoi(value[i].data + 2, value[i].len - 2);
+ if (bl->max_busy == NGX_ERROR) {
+ invalid = 1;
+ break;
+ }
+
+ continue;
+
+ case 'w':
+ if (bl->max_waiting) {
+ dup = 1;
+ break;
+ }
+
+ bl->max_waiting = ngx_atoi(value[i].data + 2, value[i].len - 2);
+ if (bl->max_waiting == NGX_ERROR) {
+ invalid = 1;
+ break;
+ }
+
+ continue;
+
+ case 't':
+ if (bl->timeout) {
+ dup = 1;
+ break;
+ }
+
+ line.len = value[i].len - 2;
+ line.data = value[i].data + 2;
+
+ bl->timeout = ngx_parse_time(&line, 1);
+ if (bl->timeout == NGX_ERROR) {
+ invalid = 1;
+ break;
+ }
+
+ continue;
+
+ default:
+ invalid = 1;
+ }
+
+ if (dup) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "duplicate value \"%s\"", value[i].data);
+ return NGX_CONF_ERROR;
+ }
+
+ if (invalid) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid value \"%s\"", value[i].data);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (bl->timeout == 0 && bl->max_waiting) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "busy lock waiting is useless with zero timeout, ignoring");
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/http/ngx_http_busy_lock.h b/usr.sbin/nginx/src/http/ngx_http_busy_lock.h
new file mode 100644
index 00000000000..d793e296641
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_busy_lock.h
@@ -0,0 +1,53 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_BUSY_LOCK_H_INCLUDED_
+#define _NGX_HTTP_BUSY_LOCK_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ u_char *md5_mask;
+ char *md5;
+ int cacheable;
+
+ int busy;
+ int max_busy;
+
+ int waiting;
+ int max_waiting;
+
+ time_t timeout;
+
+ ngx_event_mutex_t *mutex;
+} ngx_http_busy_lock_t;
+
+
+typedef struct {
+ time_t time;
+ ngx_event_t *event;
+ void (*event_handler)(ngx_event_t *ev);
+ u_char *md5;
+ int slot;
+} ngx_http_busy_lock_ctx_t;
+
+
+int ngx_http_busy_lock(ngx_http_busy_lock_t *bl, ngx_http_busy_lock_ctx_t *bc);
+int ngx_http_busy_lock_cacheable(ngx_http_busy_lock_t *bl,
+ ngx_http_busy_lock_ctx_t *bc, int lock);
+void ngx_http_busy_unlock(ngx_http_busy_lock_t *bl,
+ ngx_http_busy_lock_ctx_t *bc);
+
+char *ngx_http_set_busy_lock_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+#endif /* _NGX_HTTP_BUSY_LOCK_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/http/ngx_http_cache.h b/usr.sbin/nginx/src/http/ngx_http_cache.h
new file mode 100644
index 00000000000..3cf113f4a0a
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_cache.h
@@ -0,0 +1,147 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_CACHE_H_INCLUDED_
+#define _NGX_HTTP_CACHE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_CACHE_MISS 1
+#define NGX_HTTP_CACHE_BYPASS 2
+#define NGX_HTTP_CACHE_EXPIRED 3
+#define NGX_HTTP_CACHE_STALE 4
+#define NGX_HTTP_CACHE_UPDATING 5
+#define NGX_HTTP_CACHE_HIT 6
+#define NGX_HTTP_CACHE_SCARCE 7
+
+#define NGX_HTTP_CACHE_KEY_LEN 16
+
+
+typedef struct {
+ ngx_uint_t status;
+ time_t valid;
+} ngx_http_cache_valid_t;
+
+
+typedef struct {
+ ngx_rbtree_node_t node;
+ ngx_queue_t queue;
+
+ u_char key[NGX_HTTP_CACHE_KEY_LEN
+ - sizeof(ngx_rbtree_key_t)];
+
+ unsigned count:20;
+ unsigned uses:10;
+ unsigned valid_msec:10;
+ unsigned error:10;
+ unsigned exists:1;
+ unsigned updating:1;
+ unsigned deleting:1;
+ /* 11 unused bits */
+
+ ngx_file_uniq_t uniq;
+ time_t expire;
+ time_t valid_sec;
+ size_t body_start;
+ off_t fs_size;
+} ngx_http_file_cache_node_t;
+
+
+struct ngx_http_cache_s {
+ ngx_file_t file;
+ ngx_array_t keys;
+ uint32_t crc32;
+ u_char key[NGX_HTTP_CACHE_KEY_LEN];
+
+ ngx_file_uniq_t uniq;
+ time_t valid_sec;
+ time_t last_modified;
+ time_t date;
+
+ size_t header_start;
+ size_t body_start;
+ off_t length;
+ off_t fs_size;
+
+ ngx_uint_t min_uses;
+ ngx_uint_t error;
+ ngx_uint_t valid_msec;
+
+ ngx_buf_t *buf;
+
+ ngx_http_file_cache_t *file_cache;
+ ngx_http_file_cache_node_t *node;
+
+ unsigned updated:1;
+ unsigned updating:1;
+ unsigned exists:1;
+ unsigned temp_file:1;
+};
+
+
+typedef struct {
+ time_t valid_sec;
+ time_t last_modified;
+ time_t date;
+ uint32_t crc32;
+ u_short valid_msec;
+ u_short header_start;
+ u_short body_start;
+} ngx_http_file_cache_header_t;
+
+
+typedef struct {
+ ngx_rbtree_t rbtree;
+ ngx_rbtree_node_t sentinel;
+ ngx_queue_t queue;
+ ngx_atomic_t cold;
+ ngx_atomic_t loading;
+ off_t size;
+} ngx_http_file_cache_sh_t;
+
+
+struct ngx_http_file_cache_s {
+ ngx_http_file_cache_sh_t *sh;
+ ngx_slab_pool_t *shpool;
+
+ ngx_path_t *path;
+
+ off_t max_size;
+ size_t bsize;
+
+ time_t inactive;
+
+ ngx_msec_t last;
+ ngx_uint_t files;
+
+ ngx_shm_zone_t *shm_zone;
+};
+
+
+ngx_int_t ngx_http_file_cache_new(ngx_http_request_t *r);
+ngx_int_t ngx_http_file_cache_create(ngx_http_request_t *r);
+void ngx_http_file_cache_create_key(ngx_http_request_t *r);
+ngx_int_t ngx_http_file_cache_open(ngx_http_request_t *r);
+void ngx_http_file_cache_set_header(ngx_http_request_t *r, u_char *buf);
+void ngx_http_file_cache_update(ngx_http_request_t *r, ngx_temp_file_t *tf);
+ngx_int_t ngx_http_cache_send(ngx_http_request_t *);
+void ngx_http_file_cache_free(ngx_http_cache_t *c, ngx_temp_file_t *tf);
+time_t ngx_http_file_cache_valid(ngx_array_t *cache_valid, ngx_uint_t status);
+
+char *ngx_http_file_cache_set_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+char *ngx_http_file_cache_valid_set_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+extern ngx_str_t ngx_http_cache_status[];
+
+
+#endif /* _NGX_HTTP_CACHE_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/http/ngx_http_config.h b/usr.sbin/nginx/src/http/ngx_http_config.h
new file mode 100644
index 00000000000..435ce9a84ca
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_config.h
@@ -0,0 +1,74 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_CONFIG_H_INCLUDED_
+#define _NGX_HTTP_CONFIG_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ void **main_conf;
+ void **srv_conf;
+ void **loc_conf;
+} ngx_http_conf_ctx_t;
+
+
+typedef struct {
+ ngx_int_t (*preconfiguration)(ngx_conf_t *cf);
+ ngx_int_t (*postconfiguration)(ngx_conf_t *cf);
+
+ void *(*create_main_conf)(ngx_conf_t *cf);
+ char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
+
+ void *(*create_srv_conf)(ngx_conf_t *cf);
+ char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
+
+ void *(*create_loc_conf)(ngx_conf_t *cf);
+ char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
+} ngx_http_module_t;
+
+
+#define NGX_HTTP_MODULE 0x50545448 /* "HTTP" */
+
+#define NGX_HTTP_MAIN_CONF 0x02000000
+#define NGX_HTTP_SRV_CONF 0x04000000
+#define NGX_HTTP_LOC_CONF 0x08000000
+#define NGX_HTTP_UPS_CONF 0x10000000
+#define NGX_HTTP_SIF_CONF 0x20000000
+#define NGX_HTTP_LIF_CONF 0x40000000
+#define NGX_HTTP_LMT_CONF 0x80000000
+
+
+#define NGX_HTTP_MAIN_CONF_OFFSET offsetof(ngx_http_conf_ctx_t, main_conf)
+#define NGX_HTTP_SRV_CONF_OFFSET offsetof(ngx_http_conf_ctx_t, srv_conf)
+#define NGX_HTTP_LOC_CONF_OFFSET offsetof(ngx_http_conf_ctx_t, loc_conf)
+
+
+#define ngx_http_get_module_main_conf(r, module) \
+ (r)->main_conf[module.ctx_index]
+#define ngx_http_get_module_srv_conf(r, module) (r)->srv_conf[module.ctx_index]
+#define ngx_http_get_module_loc_conf(r, module) (r)->loc_conf[module.ctx_index]
+
+
+#define ngx_http_conf_get_module_main_conf(cf, module) \
+ ((ngx_http_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]
+#define ngx_http_conf_get_module_srv_conf(cf, module) \
+ ((ngx_http_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index]
+#define ngx_http_conf_get_module_loc_conf(cf, module) \
+ ((ngx_http_conf_ctx_t *) cf->ctx)->loc_conf[module.ctx_index]
+
+#define ngx_http_cycle_get_module_main_conf(cycle, module) \
+ (cycle->conf_ctx[ngx_http_module.index] ? \
+ ((ngx_http_conf_ctx_t *) cycle->conf_ctx[ngx_http_module.index]) \
+ ->main_conf[module.ctx_index]: \
+ NULL)
+
+
+#endif /* _NGX_HTTP_CONFIG_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/http/ngx_http_copy_filter_module.c b/usr.sbin/nginx/src/http/ngx_http_copy_filter_module.c
new file mode 100644
index 00000000000..2eb6487d81b
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_copy_filter_module.c
@@ -0,0 +1,294 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_bufs_t bufs;
+} ngx_http_copy_filter_conf_t;
+
+
+#if (NGX_HAVE_FILE_AIO)
+static void ngx_http_copy_aio_handler(ngx_output_chain_ctx_t *ctx,
+ ngx_file_t *file);
+static void ngx_http_copy_aio_event_handler(ngx_event_t *ev);
+#if (NGX_HAVE_AIO_SENDFILE)
+static void ngx_http_copy_aio_sendfile_event_handler(ngx_event_t *ev);
+#endif
+#endif
+
+static void *ngx_http_copy_filter_create_conf(ngx_conf_t *cf);
+static char *ngx_http_copy_filter_merge_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_copy_filter_init(ngx_conf_t *cf);
+
+
+static ngx_command_t ngx_http_copy_filter_commands[] = {
+
+ { ngx_string("output_buffers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_conf_set_bufs_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_copy_filter_conf_t, bufs),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_copy_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_copy_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_copy_filter_create_conf, /* create location configuration */
+ ngx_http_copy_filter_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_copy_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_copy_filter_module_ctx, /* module context */
+ ngx_http_copy_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_body_filter_pt ngx_http_next_filter;
+
+
+static ngx_int_t
+ngx_http_copy_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ ngx_int_t rc;
+ ngx_connection_t *c;
+ ngx_output_chain_ctx_t *ctx;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_copy_filter_conf_t *conf;
+
+ c = r->connection;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http copy filter: \"%V?%V\"", &r->uri, &r->args);
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_copy_filter_module);
+
+ if (ctx == NULL) {
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_output_chain_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_copy_filter_module);
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_copy_filter_module);
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ctx->sendfile = c->sendfile;
+ ctx->need_in_memory = r->main_filter_need_in_memory
+ || r->filter_need_in_memory;
+ ctx->need_in_temp = r->filter_need_temporary;
+
+ ctx->alignment = clcf->directio_alignment;
+
+ ctx->pool = r->pool;
+ ctx->bufs = conf->bufs;
+ ctx->tag = (ngx_buf_tag_t) &ngx_http_copy_filter_module;
+
+ ctx->output_filter = (ngx_output_chain_filter_pt) ngx_http_next_filter;
+ ctx->filter_ctx = r;
+
+#if (NGX_HAVE_FILE_AIO)
+ if (ngx_file_aio) {
+ if (clcf->aio) {
+ ctx->aio_handler = ngx_http_copy_aio_handler;
+ }
+#if (NGX_HAVE_AIO_SENDFILE)
+ c->aio_sendfile = (clcf->aio == NGX_HTTP_AIO_SENDFILE);
+#endif
+ }
+#endif
+
+ if (in && in->buf && ngx_buf_size(in->buf)) {
+ r->request_output = 1;
+ }
+ }
+
+#if (NGX_HAVE_FILE_AIO)
+ ctx->aio = r->aio;
+#endif
+
+ for ( ;; ) {
+ rc = ngx_output_chain(ctx, in);
+
+ if (ctx->in == NULL) {
+ r->buffered &= ~NGX_HTTP_COPY_BUFFERED;
+
+ } else {
+ r->buffered |= NGX_HTTP_COPY_BUFFERED;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http copy filter: %i \"%V?%V\"", rc, &r->uri, &r->args);
+
+#if (NGX_HAVE_FILE_AIO && NGX_HAVE_AIO_SENDFILE)
+
+ if (c->busy_sendfile) {
+ ssize_t n;
+ off_t offset;
+ ngx_file_t *file;
+ ngx_http_ephemeral_t *e;
+
+ file = c->busy_sendfile->file;
+ offset = c->busy_sendfile->file_pos;
+
+ if (file->aio) {
+ c->aio_sendfile = (offset != file->aio->last_offset);
+ file->aio->last_offset = offset;
+
+ if (c->aio_sendfile == 0) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "sendfile(%V) returned busy again",
+ &file->name);
+ }
+ }
+
+ c->busy_sendfile = NULL;
+ e = (ngx_http_ephemeral_t *) &r->uri_start;
+
+ n = ngx_file_aio_read(file, &e->aio_preload, 1, offset, r->pool);
+
+ if (n > 0) {
+ in = NULL;
+ continue;
+ }
+
+ rc = n;
+
+ if (file->aio) {
+ file->aio->data = r;
+ file->aio->handler = ngx_http_copy_aio_sendfile_event_handler;
+
+ r->main->blocked++;
+ r->aio = 1;
+ }
+ }
+#endif
+
+ return rc;
+ }
+}
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+static void
+ngx_http_copy_aio_handler(ngx_output_chain_ctx_t *ctx, ngx_file_t *file)
+{
+ ngx_http_request_t *r;
+
+ r = ctx->filter_ctx;
+
+ file->aio->data = r;
+ file->aio->handler = ngx_http_copy_aio_event_handler;
+
+ r->main->blocked++;
+ r->aio = 1;
+ ctx->aio = 1;
+}
+
+
+static void
+ngx_http_copy_aio_event_handler(ngx_event_t *ev)
+{
+ ngx_event_aio_t *aio;
+ ngx_http_request_t *r;
+
+ aio = ev->data;
+ r = aio->data;
+
+ r->main->blocked--;
+ r->aio = 0;
+
+ r->connection->write->handler(r->connection->write);
+}
+
+
+#if (NGX_HAVE_AIO_SENDFILE)
+
+static void
+ngx_http_copy_aio_sendfile_event_handler(ngx_event_t *ev)
+{
+ ngx_event_aio_t *aio;
+ ngx_http_request_t *r;
+
+ aio = ev->data;
+ r = aio->data;
+
+ r->main->blocked--;
+ r->aio = 0;
+ ev->complete = 0;
+
+ r->connection->write->handler(r->connection->write);
+}
+
+#endif
+#endif
+
+
+static void *
+ngx_http_copy_filter_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_copy_filter_conf_t *conf;
+
+ conf = ngx_palloc(cf->pool, sizeof(ngx_http_copy_filter_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->bufs.num = 0;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_copy_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_copy_filter_conf_t *prev = parent;
+ ngx_http_copy_filter_conf_t *conf = child;
+
+ ngx_conf_merge_bufs_value(conf->bufs, prev->bufs, 1, 32768);
+
+ return NULL;
+}
+
+
+static ngx_int_t
+ngx_http_copy_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_copy_filter;
+
+ return NGX_OK;
+}
+
diff --git a/usr.sbin/nginx/src/http/ngx_http_core_module.c b/usr.sbin/nginx/src/http/ngx_http_core_module.c
new file mode 100644
index 00000000000..bbb9311cf0f
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_core_module.c
@@ -0,0 +1,4703 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ u_char *name;
+ uint32_t method;
+} ngx_http_method_name_t;
+
+
+#define NGX_HTTP_REQUEST_BODY_FILE_OFF 0
+#define NGX_HTTP_REQUEST_BODY_FILE_ON 1
+#define NGX_HTTP_REQUEST_BODY_FILE_CLEAN 2
+
+
+static ngx_int_t ngx_http_core_find_location(ngx_http_request_t *r);
+static ngx_int_t ngx_http_core_find_static_location(ngx_http_request_t *r,
+ ngx_http_location_tree_node_t *node);
+
+static ngx_int_t ngx_http_core_preconfiguration(ngx_conf_t *cf);
+static void *ngx_http_core_create_main_conf(ngx_conf_t *cf);
+static char *ngx_http_core_init_main_conf(ngx_conf_t *cf, void *conf);
+static void *ngx_http_core_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_core_merge_srv_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static void *ngx_http_core_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_core_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+
+static char *ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *dummy);
+static char *ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *dummy);
+static ngx_int_t ngx_http_core_regex_location(ngx_conf_t *cf,
+ ngx_http_core_loc_conf_t *clcf, ngx_str_t *regex, ngx_uint_t caseless);
+
+static char *ngx_http_core_types(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_core_type(ngx_conf_t *cf, ngx_command_t *dummy,
+ void *conf);
+
+static char *ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_core_root(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_core_limit_except(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_core_directio(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_core_error_page(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_core_try_files(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_core_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_core_error_log(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_core_keepalive(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_core_internal(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+#if (NGX_HTTP_GZIP)
+static ngx_int_t ngx_http_gzip_accept_encoding(ngx_str_t *ae);
+static ngx_uint_t ngx_http_gzip_quantity(u_char *p, u_char *last);
+static char *ngx_http_gzip_disable(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+#endif
+
+static char *ngx_http_core_lowat_check(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_core_pool_size(ngx_conf_t *cf, void *post, void *data);
+
+static ngx_conf_post_t ngx_http_core_lowat_post =
+ { ngx_http_core_lowat_check };
+
+static ngx_conf_post_handler_pt ngx_http_core_pool_size_p =
+ ngx_http_core_pool_size;
+
+static ngx_conf_deprecated_t ngx_conf_deprecated_optimize_server_names = {
+ ngx_conf_deprecated, "optimize_server_names", "server_name_in_redirect"
+};
+
+static ngx_conf_deprecated_t ngx_conf_deprecated_open_file_cache_retest = {
+ ngx_conf_deprecated, "open_file_cache_retest", "open_file_cache_valid"
+};
+
+static ngx_conf_deprecated_t ngx_conf_deprecated_satisfy_any = {
+ ngx_conf_deprecated, "satisfy_any", "satisfy"
+};
+
+
+static ngx_conf_enum_t ngx_http_core_request_body_in_file[] = {
+ { ngx_string("off"), NGX_HTTP_REQUEST_BODY_FILE_OFF },
+ { ngx_string("on"), NGX_HTTP_REQUEST_BODY_FILE_ON },
+ { ngx_string("clean"), NGX_HTTP_REQUEST_BODY_FILE_CLEAN },
+ { ngx_null_string, 0 }
+};
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+static ngx_conf_enum_t ngx_http_core_aio[] = {
+ { ngx_string("off"), NGX_HTTP_AIO_OFF },
+ { ngx_string("on"), NGX_HTTP_AIO_ON },
+#if (NGX_HAVE_AIO_SENDFILE)
+ { ngx_string("sendfile"), NGX_HTTP_AIO_SENDFILE },
+#endif
+ { ngx_null_string, 0 }
+};
+
+#endif
+
+
+static ngx_conf_enum_t ngx_http_core_satisfy[] = {
+ { ngx_string("all"), NGX_HTTP_SATISFY_ALL },
+ { ngx_string("any"), NGX_HTTP_SATISFY_ANY },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_enum_t ngx_http_core_lingering_close[] = {
+ { ngx_string("off"), NGX_HTTP_LINGERING_OFF },
+ { ngx_string("on"), NGX_HTTP_LINGERING_ON },
+ { ngx_string("always"), NGX_HTTP_LINGERING_ALWAYS },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_enum_t ngx_http_core_if_modified_since[] = {
+ { ngx_string("off"), NGX_HTTP_IMS_OFF },
+ { ngx_string("exact"), NGX_HTTP_IMS_EXACT },
+ { ngx_string("before"), NGX_HTTP_IMS_BEFORE },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_enum_t ngx_http_core_keepalive_disable[] = {
+ { ngx_string("none"), NGX_HTTP_KEEPALIVE_DISABLE_NONE },
+ { ngx_string("msie6"), NGX_HTTP_KEEPALIVE_DISABLE_MSIE6 },
+ { ngx_string("safari"), NGX_HTTP_KEEPALIVE_DISABLE_SAFARI },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_path_init_t ngx_http_client_temp_path = {
+ ngx_string(NGX_HTTP_CLIENT_TEMP_PATH), { 0, 0, 0 }
+};
+
+
+#if (NGX_HTTP_GZIP)
+
+static ngx_conf_enum_t ngx_http_gzip_http_version[] = {
+ { ngx_string("1.0"), NGX_HTTP_VERSION_10 },
+ { ngx_string("1.1"), NGX_HTTP_VERSION_11 },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_bitmask_t ngx_http_gzip_proxied_mask[] = {
+ { ngx_string("off"), NGX_HTTP_GZIP_PROXIED_OFF },
+ { ngx_string("expired"), NGX_HTTP_GZIP_PROXIED_EXPIRED },
+ { ngx_string("no-cache"), NGX_HTTP_GZIP_PROXIED_NO_CACHE },
+ { ngx_string("no-store"), NGX_HTTP_GZIP_PROXIED_NO_STORE },
+ { ngx_string("private"), NGX_HTTP_GZIP_PROXIED_PRIVATE },
+ { ngx_string("no_last_modified"), NGX_HTTP_GZIP_PROXIED_NO_LM },
+ { ngx_string("no_etag"), NGX_HTTP_GZIP_PROXIED_NO_ETAG },
+ { ngx_string("auth"), NGX_HTTP_GZIP_PROXIED_AUTH },
+ { ngx_string("any"), NGX_HTTP_GZIP_PROXIED_ANY },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_str_t ngx_http_gzip_no_cache = ngx_string("no-cache");
+static ngx_str_t ngx_http_gzip_no_store = ngx_string("no-store");
+static ngx_str_t ngx_http_gzip_private = ngx_string("private");
+
+#endif
+
+
+static ngx_command_t ngx_http_core_commands[] = {
+
+ { ngx_string("variables_hash_max_size"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ offsetof(ngx_http_core_main_conf_t, variables_hash_max_size),
+ NULL },
+
+ { ngx_string("variables_hash_bucket_size"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ offsetof(ngx_http_core_main_conf_t, variables_hash_bucket_size),
+ NULL },
+
+ { ngx_string("server_names_hash_max_size"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ offsetof(ngx_http_core_main_conf_t, server_names_hash_max_size),
+ NULL },
+
+ { ngx_string("server_names_hash_bucket_size"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ offsetof(ngx_http_core_main_conf_t, server_names_hash_bucket_size),
+ NULL },
+
+ { ngx_string("server"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_MULTI|NGX_CONF_NOARGS,
+ ngx_http_core_server,
+ 0,
+ 0,
+ NULL },
+
+ { ngx_string("connection_pool_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_core_srv_conf_t, connection_pool_size),
+ &ngx_http_core_pool_size_p },
+
+ { ngx_string("request_pool_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_core_srv_conf_t, request_pool_size),
+ &ngx_http_core_pool_size_p },
+
+ { ngx_string("client_header_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_core_srv_conf_t, client_header_timeout),
+ NULL },
+
+ { ngx_string("client_header_buffer_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_core_srv_conf_t, client_header_buffer_size),
+ NULL },
+
+ { ngx_string("large_client_header_buffers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE2,
+ ngx_conf_set_bufs_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_core_srv_conf_t, large_client_header_buffers),
+ NULL },
+
+ { ngx_string("optimize_server_names"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, server_name_in_redirect),
+ &ngx_conf_deprecated_optimize_server_names },
+
+ { ngx_string("ignore_invalid_headers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_core_srv_conf_t, ignore_invalid_headers),
+ NULL },
+
+ { ngx_string("merge_slashes"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_core_srv_conf_t, merge_slashes),
+ NULL },
+
+ { ngx_string("underscores_in_headers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_core_srv_conf_t, underscores_in_headers),
+ NULL },
+
+ { ngx_string("location"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
+ ngx_http_core_location,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("listen"),
+ NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
+ ngx_http_core_listen,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("server_name"),
+ NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
+ ngx_http_core_server_name,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("types_hash_max_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, types_hash_max_size),
+ NULL },
+
+ { ngx_string("types_hash_bucket_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, types_hash_bucket_size),
+ NULL },
+
+ { ngx_string("types"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
+ |NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+ ngx_http_core_types,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("default_type"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, default_type),
+ NULL },
+
+ { ngx_string("root"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_TAKE1,
+ ngx_http_core_root,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("alias"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_core_root,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("limit_except"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_1MORE,
+ ngx_http_core_limit_except,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("client_max_body_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_off_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, client_max_body_size),
+ NULL },
+
+ { ngx_string("client_body_buffer_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, client_body_buffer_size),
+ NULL },
+
+ { ngx_string("client_body_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, client_body_timeout),
+ NULL },
+
+ { ngx_string("client_body_temp_path"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
+ ngx_conf_set_path_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, client_body_temp_path),
+ NULL },
+
+ { ngx_string("client_body_in_file_only"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, client_body_in_file_only),
+ &ngx_http_core_request_body_in_file },
+
+ { ngx_string("client_body_in_single_buffer"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, client_body_in_single_buffer),
+ NULL },
+
+ { ngx_string("sendfile"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_TAKE1,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, sendfile),
+ NULL },
+
+ { ngx_string("sendfile_max_chunk"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, sendfile_max_chunk),
+ NULL },
+
+#if (NGX_HAVE_FILE_AIO)
+
+ { ngx_string("aio"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, aio),
+ &ngx_http_core_aio },
+
+#endif
+
+ { ngx_string("read_ahead"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, read_ahead),
+ NULL },
+
+ { ngx_string("directio"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_core_directio,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("directio_alignment"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_off_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, directio_alignment),
+ NULL },
+
+ { ngx_string("tcp_nopush"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, tcp_nopush),
+ NULL },
+
+ { ngx_string("tcp_nodelay"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, tcp_nodelay),
+ NULL },
+
+ { ngx_string("send_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, send_timeout),
+ NULL },
+
+ { ngx_string("send_lowat"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, send_lowat),
+ &ngx_http_core_lowat_post },
+
+ { ngx_string("postpone_output"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, postpone_output),
+ NULL },
+
+ { ngx_string("limit_rate"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, limit_rate),
+ NULL },
+
+ { ngx_string("limit_rate_after"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, limit_rate_after),
+ NULL },
+
+ { ngx_string("keepalive_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+ ngx_http_core_keepalive,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("keepalive_requests"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, keepalive_requests),
+ NULL },
+
+ { ngx_string("keepalive_disable"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, keepalive_disable),
+ &ngx_http_core_keepalive_disable },
+
+ { ngx_string("satisfy"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, satisfy),
+ &ngx_http_core_satisfy },
+
+ { ngx_string("satisfy_any"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, satisfy),
+ &ngx_conf_deprecated_satisfy_any },
+
+ { ngx_string("internal"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
+ ngx_http_core_internal,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("lingering_close"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, lingering_close),
+ &ngx_http_core_lingering_close },
+
+ { ngx_string("lingering_time"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, lingering_time),
+ NULL },
+
+ { ngx_string("lingering_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, lingering_timeout),
+ NULL },
+
+ { ngx_string("reset_timedout_connection"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, reset_timedout_connection),
+ NULL },
+
+ { ngx_string("server_name_in_redirect"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, server_name_in_redirect),
+ NULL },
+
+ { ngx_string("port_in_redirect"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, port_in_redirect),
+ NULL },
+
+ { ngx_string("msie_padding"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, msie_padding),
+ NULL },
+
+ { ngx_string("msie_refresh"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, msie_refresh),
+ NULL },
+
+ { ngx_string("log_not_found"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, log_not_found),
+ NULL },
+
+ { ngx_string("log_subrequest"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, log_subrequest),
+ NULL },
+
+ { ngx_string("recursive_error_pages"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, recursive_error_pages),
+ NULL },
+
+ { ngx_string("server_tokens"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, server_tokens),
+ NULL },
+
+ { ngx_string("if_modified_since"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, if_modified_since),
+ &ngx_http_core_if_modified_since },
+
+ { ngx_string("chunked_transfer_encoding"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, chunked_transfer_encoding),
+ NULL },
+
+ { ngx_string("error_page"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_2MORE,
+ ngx_http_core_error_page,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("try_files"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_2MORE,
+ ngx_http_core_try_files,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("post_action"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, post_action),
+ NULL },
+
+ { ngx_string("error_log"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_core_error_log,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("open_file_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+ ngx_http_core_open_file_cache,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, open_file_cache),
+ NULL },
+
+ { ngx_string("open_file_cache_valid"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_sec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, open_file_cache_valid),
+ NULL },
+
+ { ngx_string("open_file_cache_retest"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_sec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, open_file_cache_valid),
+ &ngx_conf_deprecated_open_file_cache_retest },
+
+ { ngx_string("open_file_cache_min_uses"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, open_file_cache_min_uses),
+ NULL },
+
+ { ngx_string("open_file_cache_errors"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, open_file_cache_errors),
+ NULL },
+
+ { ngx_string("open_file_cache_events"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, open_file_cache_events),
+ NULL },
+
+ { ngx_string("resolver"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_core_resolver,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("resolver_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, resolver_timeout),
+ NULL },
+
+#if (NGX_HTTP_GZIP)
+
+ { ngx_string("gzip_vary"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, gzip_vary),
+ NULL },
+
+ { ngx_string("gzip_http_version"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, gzip_http_version),
+ &ngx_http_gzip_http_version },
+
+ { ngx_string("gzip_proxied"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, gzip_proxied),
+ &ngx_http_gzip_proxied_mask },
+
+ { ngx_string("gzip_disable"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_gzip_disable,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+#endif
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_core_module_ctx = {
+ ngx_http_core_preconfiguration, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ ngx_http_core_create_main_conf, /* create main configuration */
+ ngx_http_core_init_main_conf, /* init main configuration */
+
+ ngx_http_core_create_srv_conf, /* create server configuration */
+ ngx_http_core_merge_srv_conf, /* merge server configuration */
+
+ ngx_http_core_create_loc_conf, /* create location configuration */
+ ngx_http_core_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_core_module = {
+ NGX_MODULE_V1,
+ &ngx_http_core_module_ctx, /* module context */
+ ngx_http_core_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+ngx_str_t ngx_http_core_get_method = { 3, (u_char *) "GET " };
+
+
+void
+ngx_http_handler(ngx_http_request_t *r)
+{
+ ngx_http_core_main_conf_t *cmcf;
+
+ r->connection->log->action = NULL;
+
+ r->connection->unexpected_eof = 0;
+
+ if (!r->internal) {
+ switch (r->headers_in.connection_type) {
+ case 0:
+ r->keepalive = (r->http_version > NGX_HTTP_VERSION_10);
+ break;
+
+ case NGX_HTTP_CONNECTION_CLOSE:
+ r->keepalive = 0;
+ break;
+
+ case NGX_HTTP_CONNECTION_KEEP_ALIVE:
+ r->keepalive = 1;
+ break;
+ }
+
+ r->lingering_close = (r->headers_in.content_length_n > 0);
+ r->phase_handler = 0;
+
+ } else {
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+ r->phase_handler = cmcf->phase_engine.server_rewrite_index;
+ }
+
+ r->valid_location = 1;
+#if (NGX_HTTP_GZIP)
+ r->gzip_tested = 0;
+ r->gzip_ok = 0;
+ r->gzip_vary = 0;
+#endif
+
+ r->write_event_handler = ngx_http_core_run_phases;
+ ngx_http_core_run_phases(r);
+}
+
+
+void
+ngx_http_core_run_phases(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_http_phase_handler_t *ph;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ ph = cmcf->phase_engine.handlers;
+
+ while (ph[r->phase_handler].checker) {
+
+ rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
+
+ if (rc == NGX_OK) {
+ return;
+ }
+ }
+}
+
+
+ngx_int_t
+ngx_http_core_generic_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
+{
+ ngx_int_t rc;
+
+ /*
+ * generic phase checker,
+ * used by the post read and pre-access phases
+ */
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "generic phase: %ui", r->phase_handler);
+
+ rc = ph->handler(r);
+
+ if (rc == NGX_OK) {
+ r->phase_handler = ph->next;
+ return NGX_AGAIN;
+ }
+
+ if (rc == NGX_DECLINED) {
+ r->phase_handler++;
+ return NGX_AGAIN;
+ }
+
+ if (rc == NGX_AGAIN || rc == NGX_DONE) {
+ return NGX_OK;
+ }
+
+ /* rc == NGX_ERROR || rc == NGX_HTTP_... */
+
+ ngx_http_finalize_request(r, rc);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_core_rewrite_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
+{
+ ngx_int_t rc;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "rewrite phase: %ui", r->phase_handler);
+
+ rc = ph->handler(r);
+
+ if (rc == NGX_DECLINED) {
+ r->phase_handler++;
+ return NGX_AGAIN;
+ }
+
+ if (rc == NGX_DONE) {
+ return NGX_OK;
+ }
+
+ /* NGX_OK, NGX_AGAIN, NGX_ERROR, NGX_HTTP_... */
+
+ ngx_http_finalize_request(r, rc);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_core_find_config_phase(ngx_http_request_t *r,
+ ngx_http_phase_handler_t *ph)
+{
+ u_char *p;
+ size_t len;
+ ngx_int_t rc;
+ ngx_http_core_loc_conf_t *clcf;
+
+ r->content_handler = NULL;
+ r->uri_changed = 0;
+
+ rc = ngx_http_core_find_location(r);
+
+ if (rc == NGX_ERROR) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_OK;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (!r->internal && clcf->internal) {
+ ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
+ return NGX_OK;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "using configuration \"%s%V\"",
+ (clcf->noname ? "*" : (clcf->exact_match ? "=" : "")),
+ &clcf->name);
+
+ ngx_http_update_location_config(r);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http cl:%O max:%O",
+ r->headers_in.content_length_n, clcf->client_max_body_size);
+
+ if (r->headers_in.content_length_n != -1
+ && !r->discard_body
+ && clcf->client_max_body_size
+ && clcf->client_max_body_size < r->headers_in.content_length_n)
+ {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client intended to send too large body: %O bytes",
+ r->headers_in.content_length_n);
+
+ (void) ngx_http_discard_request_body(r);
+ ngx_http_finalize_request(r, NGX_HTTP_REQUEST_ENTITY_TOO_LARGE);
+ return NGX_OK;
+ }
+
+ if (rc == NGX_DONE) {
+ r->headers_out.location = ngx_list_push(&r->headers_out.headers);
+ if (r->headers_out.location == NULL) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_OK;
+ }
+
+ /*
+ * we do not need to set the r->headers_out.location->hash and
+ * r->headers_out.location->key fields
+ */
+
+ if (r->args.len == 0) {
+ r->headers_out.location->value = clcf->name;
+
+ } else {
+ len = clcf->name.len + 1 + r->args.len;
+ p = ngx_pnalloc(r->pool, len);
+
+ if (p == NULL) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_OK;
+ }
+
+ r->headers_out.location->value.len = len;
+ r->headers_out.location->value.data = p;
+
+ p = ngx_cpymem(p, clcf->name.data, clcf->name.len);
+ *p++ = '?';
+ ngx_memcpy(p, r->args.data, r->args.len);
+ }
+
+ ngx_http_finalize_request(r, NGX_HTTP_MOVED_PERMANENTLY);
+ return NGX_OK;
+ }
+
+ r->phase_handler++;
+ return NGX_AGAIN;
+}
+
+
+ngx_int_t
+ngx_http_core_post_rewrite_phase(ngx_http_request_t *r,
+ ngx_http_phase_handler_t *ph)
+{
+ ngx_http_core_srv_conf_t *cscf;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "post rewrite phase: %ui", r->phase_handler);
+
+ if (!r->uri_changed) {
+ r->phase_handler++;
+ return NGX_AGAIN;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "uri changes: %d", r->uri_changes);
+
+ /*
+ * gcc before 3.3 compiles the broken code for
+ * if (r->uri_changes-- == 0)
+ * if the r->uri_changes is defined as
+ * unsigned uri_changes:4
+ */
+
+ r->uri_changes--;
+
+ if (r->uri_changes == 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "rewrite or internal redirection cycle "
+ "while processing \"%V\"", &r->uri);
+
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_OK;
+ }
+
+ r->phase_handler = ph->next;
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+ r->loc_conf = cscf->ctx->loc_conf;
+
+ return NGX_AGAIN;
+}
+
+
+ngx_int_t
+ngx_http_core_access_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
+{
+ ngx_int_t rc;
+ ngx_http_core_loc_conf_t *clcf;
+
+ if (r != r->main) {
+ r->phase_handler = ph->next;
+ return NGX_AGAIN;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "access phase: %ui", r->phase_handler);
+
+ rc = ph->handler(r);
+
+ if (rc == NGX_DECLINED) {
+ r->phase_handler++;
+ return NGX_AGAIN;
+ }
+
+ if (rc == NGX_AGAIN || rc == NGX_DONE) {
+ return NGX_OK;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->satisfy == NGX_HTTP_SATISFY_ALL) {
+
+ if (rc == NGX_OK) {
+ r->phase_handler++;
+ return NGX_AGAIN;
+ }
+
+ } else {
+ if (rc == NGX_OK) {
+ r->access_code = 0;
+
+ if (r->headers_out.www_authenticate) {
+ r->headers_out.www_authenticate->hash = 0;
+ }
+
+ r->phase_handler = ph->next;
+ return NGX_AGAIN;
+ }
+
+ if (rc == NGX_HTTP_FORBIDDEN || rc == NGX_HTTP_UNAUTHORIZED) {
+ r->access_code = rc;
+
+ r->phase_handler++;
+ return NGX_AGAIN;
+ }
+ }
+
+ /* rc == NGX_ERROR || rc == NGX_HTTP_... */
+
+ ngx_http_finalize_request(r, rc);
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_core_post_access_phase(ngx_http_request_t *r,
+ ngx_http_phase_handler_t *ph)
+{
+ ngx_int_t access_code;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "post access phase: %ui", r->phase_handler);
+
+ access_code = r->access_code;
+
+ if (access_code) {
+ if (access_code == NGX_HTTP_FORBIDDEN) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "access forbidden by rule");
+ }
+
+ r->access_code = 0;
+ ngx_http_finalize_request(r, access_code);
+ return NGX_OK;
+ }
+
+ r->phase_handler++;
+ return NGX_AGAIN;
+}
+
+
+ngx_int_t
+ngx_http_core_try_files_phase(ngx_http_request_t *r,
+ ngx_http_phase_handler_t *ph)
+{
+ size_t len, root, alias, reserve, allocated;
+ u_char *p, *name;
+ ngx_str_t path, args;
+ ngx_uint_t test_dir;
+ ngx_http_try_file_t *tf;
+ ngx_open_file_info_t of;
+ ngx_http_script_code_pt code;
+ ngx_http_script_engine_t e;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_script_len_code_pt lcode;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "try files phase: %ui", r->phase_handler);
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->try_files == NULL) {
+ r->phase_handler++;
+ return NGX_AGAIN;
+ }
+
+ allocated = 0;
+ root = 0;
+ name = NULL;
+ /* suppress MSVC warning */
+ path.data = NULL;
+
+ tf = clcf->try_files;
+
+ alias = clcf->alias;
+
+ for ( ;; ) {
+
+ if (tf->lengths) {
+ ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+ e.ip = tf->lengths->elts;
+ e.request = r;
+
+ /* 1 is for terminating '\0' as in static names */
+ len = 1;
+
+ while (*(uintptr_t *) e.ip) {
+ lcode = *(ngx_http_script_len_code_pt *) e.ip;
+ len += lcode(&e);
+ }
+
+ } else {
+ len = tf->name.len;
+ }
+
+ /* 16 bytes are preallocation */
+ reserve = ngx_abs((ssize_t) (len - r->uri.len)) + alias + 16;
+
+ if (reserve > allocated) {
+
+ /* we just need to allocate path and to copy a root */
+
+ if (ngx_http_map_uri_to_path(r, &path, &root, reserve) == NULL) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_OK;
+ }
+
+ name = path.data + root;
+ allocated = path.len - root - (r->uri.len - alias);
+ }
+
+ if (tf->values == NULL) {
+
+ /* tf->name.len includes the terminating '\0' */
+
+ ngx_memcpy(name, tf->name.data, tf->name.len);
+
+ path.len = (name + tf->name.len - 1) - path.data;
+
+ } else {
+ e.ip = tf->values->elts;
+ e.pos = name;
+ e.flushed = 1;
+
+ while (*(uintptr_t *) e.ip) {
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code((ngx_http_script_engine_t *) &e);
+ }
+
+ path.len = e.pos - path.data;
+
+ *e.pos = '\0';
+
+ if (alias && ngx_strncmp(name, clcf->name.data, alias) == 0) {
+ ngx_memmove(name, name + alias, len - alias);
+ path.len -= alias;
+ }
+ }
+
+ test_dir = tf->test_dir;
+
+ tf++;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "try to use %s: \"%s\" \"%s\"",
+ test_dir ? "dir" : "file", name, path.data);
+
+ if (tf->lengths == NULL && tf->name.len == 0) {
+
+ if (tf->code) {
+ ngx_http_finalize_request(r, tf->code);
+ return NGX_OK;
+ }
+
+ path.len -= root;
+ path.data += root;
+
+ if (path.data[0] == '@') {
+ (void) ngx_http_named_location(r, &path);
+
+ } else {
+ ngx_http_split_args(r, &path, &args);
+
+ (void) ngx_http_internal_redirect(r, &path, &args);
+ }
+
+ ngx_http_finalize_request(r, NGX_DONE);
+ return NGX_OK;
+ }
+
+ ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+ 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_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+ != NGX_OK)
+ {
+ if (of.err != NGX_ENOENT
+ && of.err != NGX_ENOTDIR
+ && of.err != NGX_ENAMETOOLONG)
+ {
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err,
+ "%s \"%s\" failed", of.failed, path.data);
+ }
+
+ continue;
+ }
+
+ if (of.is_dir && !test_dir) {
+ continue;
+ }
+
+ path.len -= root;
+ path.data += root;
+
+ if (!alias) {
+ r->uri = path;
+
+#if (NGX_PCRE)
+ } else if (clcf->regex) {
+ if (!test_dir) {
+ r->uri = path;
+ r->add_uri_to_alias = 1;
+ }
+#endif
+ } else {
+ r->uri.len = alias + path.len;
+ r->uri.data = ngx_pnalloc(r->pool, r->uri.len);
+ if (r->uri.data == NULL) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_OK;
+ }
+
+ p = ngx_copy(r->uri.data, clcf->name.data, alias);
+ ngx_memcpy(p, name, path.len);
+ }
+
+ ngx_http_set_exten(r);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "try file uri: \"%V\"", &r->uri);
+
+ r->phase_handler++;
+ return NGX_AGAIN;
+ }
+
+ /* not reached */
+}
+
+
+ngx_int_t
+ngx_http_core_content_phase(ngx_http_request_t *r,
+ ngx_http_phase_handler_t *ph)
+{
+ size_t root;
+ ngx_int_t rc;
+ ngx_str_t path;
+
+ if (r->content_handler) {
+ r->write_event_handler = ngx_http_request_empty_handler;
+ ngx_http_finalize_request(r, r->content_handler(r));
+ return NGX_OK;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "content phase: %ui", r->phase_handler);
+
+ rc = ph->handler(r);
+
+ if (rc != NGX_DECLINED) {
+ ngx_http_finalize_request(r, rc);
+ return NGX_OK;
+ }
+
+ /* rc == NGX_DECLINED */
+
+ ph++;
+
+ if (ph->checker) {
+ r->phase_handler++;
+ return NGX_AGAIN;
+ }
+
+ /* no content handler was found */
+
+ if (r->uri.data[r->uri.len - 1] == '/') {
+
+ if (ngx_http_map_uri_to_path(r, &path, &root, 0) != NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "directory index of \"%s\" is forbidden", path.data);
+ }
+
+ ngx_http_finalize_request(r, NGX_HTTP_FORBIDDEN);
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no handler found");
+
+ ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
+ return NGX_OK;
+}
+
+
+void
+ngx_http_update_location_config(ngx_http_request_t *r)
+{
+ ngx_http_core_loc_conf_t *clcf;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (r->method & clcf->limit_except) {
+ r->loc_conf = clcf->limit_except_loc_conf;
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+ }
+
+ if (r == r->main) {
+ r->connection->log->file = clcf->error_log->file;
+
+ if (!(r->connection->log->log_level & NGX_LOG_DEBUG_CONNECTION)) {
+ r->connection->log->log_level = clcf->error_log->log_level;
+ }
+ }
+
+ if ((ngx_io.flags & NGX_IO_SENDFILE) && clcf->sendfile) {
+ r->connection->sendfile = 1;
+
+ } else {
+ r->connection->sendfile = 0;
+ }
+
+ if (clcf->client_body_in_file_only) {
+ r->request_body_in_file_only = 1;
+ r->request_body_in_persistent_file = 1;
+ r->request_body_in_clean_file =
+ clcf->client_body_in_file_only == NGX_HTTP_REQUEST_BODY_FILE_CLEAN;
+ r->request_body_file_log_level = NGX_LOG_NOTICE;
+
+ } else {
+ r->request_body_file_log_level = NGX_LOG_WARN;
+ }
+
+ r->request_body_in_single_buf = clcf->client_body_in_single_buffer;
+
+ if (r->keepalive) {
+ if (clcf->keepalive_timeout == 0) {
+ r->keepalive = 0;
+
+ } else if (r->connection->requests >= clcf->keepalive_requests) {
+ r->keepalive = 0;
+
+ } else if (r->headers_in.msie6
+ && r->method == NGX_HTTP_POST
+ && (clcf->keepalive_disable
+ & NGX_HTTP_KEEPALIVE_DISABLE_MSIE6))
+ {
+ /*
+ * MSIE may wait for some time if an response for
+ * a POST request was sent over a keepalive connection
+ */
+ r->keepalive = 0;
+
+ } else if (r->headers_in.safari
+ && (clcf->keepalive_disable
+ & NGX_HTTP_KEEPALIVE_DISABLE_SAFARI))
+ {
+ /*
+ * Safari may send a POST request to a closed keepalive
+ * connection and may stall for some time, see
+ * https://bugs.webkit.org/show_bug.cgi?id=5760
+ */
+ r->keepalive = 0;
+ }
+ }
+
+ if (!clcf->tcp_nopush) {
+ /* disable TCP_NOPUSH/TCP_CORK use */
+ r->connection->tcp_nopush = NGX_TCP_NOPUSH_DISABLED;
+ }
+
+ if (r->limit_rate == 0) {
+ r->limit_rate = clcf->limit_rate;
+ }
+
+ if (clcf->handler) {
+ r->content_handler = clcf->handler;
+ }
+}
+
+
+/*
+ * NGX_OK - exact or regex match
+ * NGX_DONE - auto redirect
+ * NGX_AGAIN - inclusive match
+ * NGX_ERROR - regex error
+ * NGX_DECLINED - no match
+ */
+
+static ngx_int_t
+ngx_http_core_find_location(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_http_core_loc_conf_t *pclcf;
+#if (NGX_PCRE)
+ ngx_int_t n;
+ ngx_uint_t noregex;
+ ngx_http_core_loc_conf_t *clcf, **clcfp;
+
+ noregex = 0;
+#endif
+
+ pclcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ rc = ngx_http_core_find_static_location(r, pclcf->static_locations);
+
+ if (rc == NGX_AGAIN) {
+
+#if (NGX_PCRE)
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ noregex = clcf->noregex;
+#endif
+
+ /* look up nested locations */
+
+ rc = ngx_http_core_find_location(r);
+ }
+
+ if (rc == NGX_OK || rc == NGX_DONE) {
+ return rc;
+ }
+
+ /* rc == NGX_DECLINED or rc == NGX_AGAIN in nested location */
+
+#if (NGX_PCRE)
+
+ if (noregex == 0 && pclcf->regex_locations) {
+
+ for (clcfp = pclcf->regex_locations; *clcfp; clcfp++) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "test location: ~ \"%V\"", &(*clcfp)->name);
+
+ n = ngx_http_regex_exec(r, (*clcfp)->regex, &r->uri);
+
+ if (n == NGX_OK) {
+ r->loc_conf = (*clcfp)->loc_conf;
+
+ /* look up nested locations */
+
+ rc = ngx_http_core_find_location(r);
+
+ return (rc == NGX_ERROR) ? rc : NGX_OK;
+ }
+
+ if (n == NGX_DECLINED) {
+ continue;
+ }
+
+ return NGX_ERROR;
+ }
+ }
+#endif
+
+ return rc;
+}
+
+
+/*
+ * NGX_OK - exact match
+ * NGX_DONE - auto redirect
+ * NGX_AGAIN - inclusive match
+ * NGX_DECLINED - no match
+ */
+
+static ngx_int_t
+ngx_http_core_find_static_location(ngx_http_request_t *r,
+ ngx_http_location_tree_node_t *node)
+{
+ u_char *uri;
+ size_t len, n;
+ ngx_int_t rc, rv;
+
+ len = r->uri.len;
+ uri = r->uri.data;
+
+ rv = NGX_DECLINED;
+
+ for ( ;; ) {
+
+ if (node == NULL) {
+ return rv;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "test location: \"%*s\"", node->len, node->name);
+
+ n = (len <= (size_t) node->len) ? len : node->len;
+
+ rc = ngx_filename_cmp(uri, node->name, n);
+
+ if (rc != 0) {
+ node = (rc < 0) ? node->left : node->right;
+
+ continue;
+ }
+
+ if (len > (size_t) node->len) {
+
+ if (node->inclusive) {
+
+ r->loc_conf = node->inclusive->loc_conf;
+ rv = NGX_AGAIN;
+
+ node = node->tree;
+ uri += n;
+ len -= n;
+
+ continue;
+ }
+
+ /* exact only */
+
+ node = node->right;
+
+ continue;
+ }
+
+ if (len == (size_t) node->len) {
+
+ if (node->exact) {
+ r->loc_conf = node->exact->loc_conf;
+ return NGX_OK;
+
+ } else {
+ r->loc_conf = node->inclusive->loc_conf;
+ return NGX_AGAIN;
+ }
+ }
+
+ /* len < node->len */
+
+ if (len + 1 == (size_t) node->len && node->auto_redirect) {
+
+ r->loc_conf = (node->exact) ? node->exact->loc_conf:
+ node->inclusive->loc_conf;
+ rv = NGX_DONE;
+ }
+
+ node = node->left;
+ }
+}
+
+
+void *
+ngx_http_test_content_type(ngx_http_request_t *r, ngx_hash_t *types_hash)
+{
+ u_char c, *lowcase;
+ size_t len;
+ ngx_uint_t i, hash;
+
+ if (types_hash->size == 0) {
+ return (void *) 4;
+ }
+
+ if (r->headers_out.content_type.len == 0) {
+ return NULL;
+ }
+
+ len = r->headers_out.content_type_len;
+
+ if (r->headers_out.content_type_lowcase == NULL) {
+
+ lowcase = ngx_pnalloc(r->pool, len);
+ if (lowcase == NULL) {
+ return NULL;
+ }
+
+ r->headers_out.content_type_lowcase = lowcase;
+
+ hash = 0;
+
+ for (i = 0; i < len; i++) {
+ c = ngx_tolower(r->headers_out.content_type.data[i]);
+ hash = ngx_hash(hash, c);
+ lowcase[i] = c;
+ }
+
+ r->headers_out.content_type_hash = hash;
+ }
+
+ return ngx_hash_find(types_hash, r->headers_out.content_type_hash,
+ r->headers_out.content_type_lowcase, len);
+}
+
+
+ngx_int_t
+ngx_http_set_content_type(ngx_http_request_t *r)
+{
+ u_char c, *exten;
+ ngx_str_t *type;
+ ngx_uint_t i, hash;
+ ngx_http_core_loc_conf_t *clcf;
+
+ if (r->headers_out.content_type.len) {
+ return NGX_OK;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (r->exten.len) {
+
+ hash = 0;
+
+ for (i = 0; i < r->exten.len; i++) {
+ c = r->exten.data[i];
+
+ if (c >= 'A' && c <= 'Z') {
+
+ exten = ngx_pnalloc(r->pool, r->exten.len);
+ if (exten == NULL) {
+ return NGX_ERROR;
+ }
+
+ hash = ngx_hash_strlow(exten, r->exten.data, r->exten.len);
+
+ r->exten.data = exten;
+
+ break;
+ }
+
+ hash = ngx_hash(hash, c);
+ }
+
+ type = ngx_hash_find(&clcf->types_hash, hash,
+ r->exten.data, r->exten.len);
+
+ if (type) {
+ r->headers_out.content_type_len = type->len;
+ r->headers_out.content_type = *type;
+
+ return NGX_OK;
+ }
+ }
+
+ r->headers_out.content_type_len = clcf->default_type.len;
+ r->headers_out.content_type = clcf->default_type;
+
+ return NGX_OK;
+}
+
+
+void
+ngx_http_set_exten(ngx_http_request_t *r)
+{
+ ngx_int_t i;
+
+ ngx_str_null(&r->exten);
+
+ for (i = r->uri.len - 1; i > 1; i--) {
+ if (r->uri.data[i] == '.' && r->uri.data[i - 1] != '/') {
+
+ r->exten.len = r->uri.len - i - 1;
+ r->exten.data = &r->uri.data[i + 1];
+
+ return;
+
+ } else if (r->uri.data[i] == '/') {
+ return;
+ }
+ }
+
+ return;
+}
+
+
+ngx_int_t
+ngx_http_send_response(ngx_http_request_t *r, ngx_uint_t status,
+ ngx_str_t *ct, ngx_http_complex_value_t *cv)
+{
+ ngx_int_t rc;
+ ngx_str_t val;
+ ngx_buf_t *b;
+ ngx_chain_t out;
+
+ r->headers_out.status = status;
+
+ if (status == NGX_HTTP_NO_CONTENT) {
+ r->header_only = 1;
+ return ngx_http_send_header(r);
+ }
+
+ if (ngx_http_complex_value(r, cv, &val) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (status >= NGX_HTTP_MOVED_PERMANENTLY && status <= NGX_HTTP_SEE_OTHER) {
+
+ r->headers_out.location = ngx_list_push(&r->headers_out.headers);
+ if (r->headers_out.location == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ r->headers_out.location->hash = 1;
+ ngx_str_set(&r->headers_out.location->key, "Location");
+ r->headers_out.location->value = val;
+
+ return status;
+ }
+
+ r->headers_out.content_length_n = val.len;
+
+ if (ct) {
+ r->headers_out.content_type_len = ct->len;
+ r->headers_out.content_type = *ct;
+
+ } else {
+ if (ngx_http_set_content_type(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ if (r->method == NGX_HTTP_HEAD || (r != r->main && val.len == 0)) {
+ return ngx_http_send_header(r);
+ }
+
+ b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
+ if (b == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ b->pos = val.data;
+ b->last = val.data + val.len;
+ b->memory = val.len ? 1 : 0;
+ b->last_buf = (r == r->main) ? 1 : 0;
+ b->last_in_chain = 1;
+
+ out.buf = b;
+ out.next = NULL;
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ return rc;
+ }
+
+ return ngx_http_output_filter(r, &out);
+}
+
+
+ngx_int_t
+ngx_http_send_header(ngx_http_request_t *r)
+{
+ if (r->err_status) {
+ r->headers_out.status = r->err_status;
+ r->headers_out.status_line.len = 0;
+ }
+
+ return ngx_http_top_header_filter(r);
+}
+
+
+ngx_int_t
+ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ ngx_int_t rc;
+ ngx_connection_t *c;
+
+ c = r->connection;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http output filter \"%V?%V\"", &r->uri, &r->args);
+
+ rc = ngx_http_top_body_filter(r, in);
+
+ if (rc == NGX_ERROR) {
+ /* NGX_ERROR may be returned by any filter */
+ c->error = 1;
+ }
+
+ return rc;
+}
+
+
+u_char *
+ngx_http_map_uri_to_path(ngx_http_request_t *r, ngx_str_t *path,
+ size_t *root_length, size_t reserved)
+{
+ u_char *last;
+ size_t alias;
+ ngx_http_core_loc_conf_t *clcf;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ alias = clcf->alias;
+
+ if (alias && !r->valid_location) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "\"alias\" could not be used in location \"%V\" "
+ "where URI was rewritten", &clcf->name);
+ return NULL;
+ }
+
+ if (clcf->root_lengths == NULL) {
+
+ *root_length = clcf->root.len;
+
+ path->len = clcf->root.len + reserved + r->uri.len - alias + 1;
+
+ path->data = ngx_pnalloc(r->pool, path->len);
+ if (path->data == NULL) {
+ return NULL;
+ }
+
+ last = ngx_copy(path->data, clcf->root.data, clcf->root.len);
+
+ } else {
+
+#if (NGX_PCRE)
+ ngx_uint_t captures;
+
+ captures = alias && clcf->regex;
+
+ reserved += captures ? r->add_uri_to_alias ? r->uri.len + 1 : 1
+ : r->uri.len - alias + 1;
+#else
+ reserved += r->uri.len - alias + 1;
+#endif
+
+ if (ngx_http_script_run(r, path, clcf->root_lengths->elts, reserved,
+ clcf->root_values->elts)
+ == NULL)
+ {
+ return NULL;
+ }
+
+ if (ngx_conf_full_name((ngx_cycle_t *) ngx_cycle, path, 0) != NGX_OK) {
+ return NULL;
+ }
+
+ *root_length = path->len - reserved;
+ last = path->data + *root_length;
+
+#if (NGX_PCRE)
+ if (captures) {
+ if (!r->add_uri_to_alias) {
+ *last = '\0';
+ return last;
+ }
+
+ alias = 0;
+ }
+#endif
+ }
+
+ last = ngx_cpystrn(last, r->uri.data + alias, r->uri.len - alias + 1);
+
+ return last;
+}
+
+
+ngx_int_t
+ngx_http_auth_basic_user(ngx_http_request_t *r)
+{
+ ngx_str_t auth, encoded;
+ ngx_uint_t len;
+
+ if (r->headers_in.user.len == 0 && r->headers_in.user.data != NULL) {
+ return NGX_DECLINED;
+ }
+
+ if (r->headers_in.authorization == NULL) {
+ r->headers_in.user.data = (u_char *) "";
+ return NGX_DECLINED;
+ }
+
+ encoded = r->headers_in.authorization->value;
+
+ if (encoded.len < sizeof("Basic ") - 1
+ || ngx_strncasecmp(encoded.data, (u_char *) "Basic ",
+ sizeof("Basic ") - 1)
+ != 0)
+ {
+ r->headers_in.user.data = (u_char *) "";
+ return NGX_DECLINED;
+ }
+
+ encoded.len -= sizeof("Basic ") - 1;
+ encoded.data += sizeof("Basic ") - 1;
+
+ while (encoded.len && encoded.data[0] == ' ') {
+ encoded.len--;
+ encoded.data++;
+ }
+
+ if (encoded.len == 0) {
+ r->headers_in.user.data = (u_char *) "";
+ return NGX_DECLINED;
+ }
+
+ auth.len = ngx_base64_decoded_length(encoded.len);
+ auth.data = ngx_pnalloc(r->pool, auth.len + 1);
+ if (auth.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_decode_base64(&auth, &encoded) != NGX_OK) {
+ r->headers_in.user.data = (u_char *) "";
+ return NGX_DECLINED;
+ }
+
+ auth.data[auth.len] = '\0';
+
+ for (len = 0; len < auth.len; len++) {
+ if (auth.data[len] == ':') {
+ break;
+ }
+ }
+
+ if (len == 0 || len == auth.len) {
+ r->headers_in.user.data = (u_char *) "";
+ return NGX_DECLINED;
+ }
+
+ r->headers_in.user.len = len;
+ r->headers_in.user.data = auth.data;
+ r->headers_in.passwd.len = auth.len - len - 1;
+ r->headers_in.passwd.data = &auth.data[len + 1];
+
+ return NGX_OK;
+}
+
+
+#if (NGX_HTTP_GZIP)
+
+ngx_int_t
+ngx_http_gzip_ok(ngx_http_request_t *r)
+{
+ time_t date, expires;
+ ngx_uint_t p;
+ ngx_array_t *cc;
+ ngx_table_elt_t *e, *d, *ae;
+ ngx_http_core_loc_conf_t *clcf;
+
+ r->gzip_tested = 1;
+
+ if (r != r->main) {
+ return NGX_DECLINED;
+ }
+
+ ae = r->headers_in.accept_encoding;
+ if (ae == NULL) {
+ return NGX_DECLINED;
+ }
+
+ if (ae->value.len < sizeof("gzip") - 1) {
+ return NGX_DECLINED;
+ }
+
+ /*
+ * test first for the most common case "gzip,...":
+ * MSIE: "gzip, deflate"
+ * Firefox: "gzip,deflate"
+ * Chrome: "gzip,deflate,sdch"
+ * Safari: "gzip, deflate"
+ * Opera: "gzip, deflate"
+ */
+
+ if (ngx_memcmp(ae->value.data, "gzip,", 5) != 0
+ && ngx_http_gzip_accept_encoding(&ae->value) != NGX_OK)
+ {
+ return NGX_DECLINED;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (r->headers_in.msie6 && clcf->gzip_disable_msie6) {
+ return NGX_DECLINED;
+ }
+
+ if (r->http_version < clcf->gzip_http_version) {
+ return NGX_DECLINED;
+ }
+
+ if (r->headers_in.via == NULL) {
+ goto ok;
+ }
+
+ p = clcf->gzip_proxied;
+
+ if (p & NGX_HTTP_GZIP_PROXIED_OFF) {
+ return NGX_DECLINED;
+ }
+
+ if (p & NGX_HTTP_GZIP_PROXIED_ANY) {
+ goto ok;
+ }
+
+ if (r->headers_in.authorization && (p & NGX_HTTP_GZIP_PROXIED_AUTH)) {
+ goto ok;
+ }
+
+ e = r->headers_out.expires;
+
+ if (e) {
+
+ if (!(p & NGX_HTTP_GZIP_PROXIED_EXPIRED)) {
+ return NGX_DECLINED;
+ }
+
+ expires = ngx_http_parse_time(e->value.data, e->value.len);
+ if (expires == NGX_ERROR) {
+ return NGX_DECLINED;
+ }
+
+ d = r->headers_out.date;
+
+ if (d) {
+ date = ngx_http_parse_time(d->value.data, d->value.len);
+ if (date == NGX_ERROR) {
+ return NGX_DECLINED;
+ }
+
+ } else {
+ date = ngx_time();
+ }
+
+ if (expires < date) {
+ goto ok;
+ }
+
+ return NGX_DECLINED;
+ }
+
+ cc = &r->headers_out.cache_control;
+
+ if (cc->elts) {
+
+ if ((p & NGX_HTTP_GZIP_PROXIED_NO_CACHE)
+ && ngx_http_parse_multi_header_lines(cc, &ngx_http_gzip_no_cache,
+ NULL)
+ >= 0)
+ {
+ goto ok;
+ }
+
+ if ((p & NGX_HTTP_GZIP_PROXIED_NO_STORE)
+ && ngx_http_parse_multi_header_lines(cc, &ngx_http_gzip_no_store,
+ NULL)
+ >= 0)
+ {
+ goto ok;
+ }
+
+ if ((p & NGX_HTTP_GZIP_PROXIED_PRIVATE)
+ && ngx_http_parse_multi_header_lines(cc, &ngx_http_gzip_private,
+ NULL)
+ >= 0)
+ {
+ goto ok;
+ }
+
+ return NGX_DECLINED;
+ }
+
+ if ((p & NGX_HTTP_GZIP_PROXIED_NO_LM) && r->headers_out.last_modified) {
+ return NGX_DECLINED;
+ }
+
+ if ((p & NGX_HTTP_GZIP_PROXIED_NO_ETAG) && r->headers_out.etag) {
+ return NGX_DECLINED;
+ }
+
+ok:
+
+#if (NGX_PCRE)
+
+ if (clcf->gzip_disable && r->headers_in.user_agent) {
+
+ if (ngx_regex_exec_array(clcf->gzip_disable,
+ &r->headers_in.user_agent->value,
+ r->connection->log)
+ != NGX_DECLINED)
+ {
+ return NGX_DECLINED;
+ }
+ }
+
+#endif
+
+ r->gzip_ok = 1;
+
+ return NGX_OK;
+}
+
+
+/*
+ * gzip is enabled for the following quantities:
+ * "gzip; q=0.001" ... "gzip; q=1.000"
+ * gzip is disabled for the following quantities:
+ * "gzip; q=0" ... "gzip; q=0.000", and for any invalid cases
+ */
+
+static ngx_int_t
+ngx_http_gzip_accept_encoding(ngx_str_t *ae)
+{
+ u_char *p, *start, *last;
+
+ start = ae->data;
+ last = start + ae->len;
+
+ for ( ;; ) {
+ p = ngx_strcasestrn(start, "gzip", 4 - 1);
+ if (p == NULL) {
+ return NGX_DECLINED;
+ }
+
+ if (p == start || (*(p - 1) == ',' || *(p - 1) == ' ')) {
+ break;
+ }
+
+ start = p + 4;
+ }
+
+ p += 4;
+
+ while (p < last) {
+ switch(*p++) {
+ case ',':
+ return NGX_OK;
+ case ';':
+ goto quantity;
+ case ' ':
+ continue;
+ default:
+ return NGX_DECLINED;
+ }
+ }
+
+ return NGX_OK;
+
+quantity:
+
+ while (p < last) {
+ switch(*p++) {
+ case 'q':
+ case 'Q':
+ goto equal;
+ case ' ':
+ continue;
+ default:
+ return NGX_DECLINED;
+ }
+ }
+
+ return NGX_OK;
+
+equal:
+
+ if (p + 2 > last || *p++ != '=') {
+ return NGX_DECLINED;
+ }
+
+ if (ngx_http_gzip_quantity(p, last) == 0) {
+ return NGX_DECLINED;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_uint_t
+ngx_http_gzip_quantity(u_char *p, u_char *last)
+{
+ u_char c;
+ ngx_uint_t n, q;
+
+ c = *p++;
+
+ if (c != '0' && c != '1') {
+ return 0;
+ }
+
+ q = (c - '0') * 100;
+
+ if (p == last) {
+ return q;
+ }
+
+ c = *p++;
+
+ if (c == ',' || c == ' ') {
+ return q;
+ }
+
+ if (c != '.') {
+ return 0;
+ }
+
+ n = 0;
+
+ while (p < last) {
+ c = *p++;
+
+ if (c == ',' || c == ' ') {
+ break;
+ }
+
+ if (c >= '0' && c <= '9') {
+ q += c - '0';
+ n++;
+ continue;
+ }
+
+ return 0;
+ }
+
+ if (q > 100 || n > 3) {
+ return 0;
+ }
+
+ return q;
+}
+
+#endif
+
+
+ngx_int_t
+ngx_http_subrequest(ngx_http_request_t *r,
+ ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr,
+ ngx_http_post_subrequest_t *ps, ngx_uint_t flags)
+{
+ ngx_time_t *tp;
+ ngx_connection_t *c;
+ ngx_http_request_t *sr;
+ ngx_http_core_srv_conf_t *cscf;
+ ngx_http_postponed_request_t *pr, *p;
+
+ r->main->subrequests--;
+
+ if (r->main->subrequests == 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "subrequests cycle while processing \"%V\"", uri);
+ r->main->subrequests = 1;
+ return NGX_ERROR;
+ }
+
+ sr = ngx_pcalloc(r->pool, sizeof(ngx_http_request_t));
+ if (sr == NULL) {
+ return NGX_ERROR;
+ }
+
+ sr->signature = NGX_HTTP_MODULE;
+
+ c = r->connection;
+ sr->connection = c;
+
+ sr->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
+ if (sr->ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_list_init(&sr->headers_out.headers, r->pool, 20,
+ sizeof(ngx_table_elt_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+ sr->main_conf = cscf->ctx->main_conf;
+ sr->srv_conf = cscf->ctx->srv_conf;
+ sr->loc_conf = cscf->ctx->loc_conf;
+
+ sr->pool = r->pool;
+
+ sr->headers_in = r->headers_in;
+
+ ngx_http_clear_content_length(sr);
+ ngx_http_clear_accept_ranges(sr);
+ ngx_http_clear_last_modified(sr);
+
+ sr->request_body = r->request_body;
+
+ sr->method = NGX_HTTP_GET;
+ sr->http_version = r->http_version;
+
+ sr->request_line = r->request_line;
+ sr->uri = *uri;
+
+ if (args) {
+ sr->args = *args;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http subrequest \"%V?%V\"", uri, &sr->args);
+
+ sr->subrequest_in_memory = (flags & NGX_HTTP_SUBREQUEST_IN_MEMORY) != 0;
+ sr->waited = (flags & NGX_HTTP_SUBREQUEST_WAITED) != 0;
+
+ sr->unparsed_uri = r->unparsed_uri;
+ sr->method_name = ngx_http_core_get_method;
+ sr->http_protocol = r->http_protocol;
+
+ ngx_http_set_exten(sr);
+
+ sr->main = r->main;
+ sr->parent = r;
+ sr->post_subrequest = ps;
+ sr->read_event_handler = ngx_http_request_empty_handler;
+ sr->write_event_handler = ngx_http_handler;
+
+ if (c->data == r && r->postponed == NULL) {
+ c->data = sr;
+ }
+
+ sr->variables = r->variables;
+
+ sr->log_handler = r->log_handler;
+
+ pr = ngx_palloc(r->pool, sizeof(ngx_http_postponed_request_t));
+ if (pr == NULL) {
+ return NGX_ERROR;
+ }
+
+ pr->request = sr;
+ pr->out = NULL;
+ pr->next = NULL;
+
+ if (r->postponed) {
+ for (p = r->postponed; p->next; p = p->next) { /* void */ }
+ p->next = pr;
+
+ } else {
+ r->postponed = pr;
+ }
+
+ sr->internal = 1;
+
+ sr->discard_body = r->discard_body;
+ sr->expect_tested = 1;
+ sr->main_filter_need_in_memory = r->main_filter_need_in_memory;
+
+ sr->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;
+
+ tp = ngx_timeofday();
+ sr->start_sec = tp->sec;
+ sr->start_msec = tp->msec;
+
+ r->main->subrequests++;
+ r->main->count++;
+
+ *psr = sr;
+
+ return ngx_http_post_request(sr, NULL);
+}
+
+
+ngx_int_t
+ngx_http_internal_redirect(ngx_http_request_t *r,
+ ngx_str_t *uri, ngx_str_t *args)
+{
+ ngx_http_core_srv_conf_t *cscf;
+
+ r->uri_changes--;
+
+ if (r->uri_changes == 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "rewrite or internal redirection cycle "
+ "while internal redirect to \"%V\"", uri);
+
+ r->main->count++;
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_DONE;
+ }
+
+ r->uri = *uri;
+
+ if (args) {
+ r->args = *args;
+
+ } else {
+ ngx_str_null(&r->args);
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "internal redirect: \"%V?%V\"", uri, &r->args);
+
+ ngx_http_set_exten(r);
+
+ /* clear the modules contexts */
+ ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module);
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+ r->loc_conf = cscf->ctx->loc_conf;
+
+ ngx_http_update_location_config(r);
+
+#if (NGX_HTTP_CACHE)
+ r->cache = NULL;
+#endif
+
+ r->internal = 1;
+ r->add_uri_to_alias = 0;
+ r->main->count++;
+
+ ngx_http_handler(r);
+
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_named_location(ngx_http_request_t *r, ngx_str_t *name)
+{
+ ngx_http_core_srv_conf_t *cscf;
+ ngx_http_core_loc_conf_t **clcfp;
+ ngx_http_core_main_conf_t *cmcf;
+
+ r->main->count++;
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ if (cscf->named_locations) {
+
+ for (clcfp = cscf->named_locations; *clcfp; clcfp++) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "test location: \"%V\"", &(*clcfp)->name);
+
+ if (name->len != (*clcfp)->name.len
+ || ngx_strncmp(name->data, (*clcfp)->name.data, name->len) != 0)
+ {
+ continue;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "using location: %V \"%V?%V\"",
+ name, &r->uri, &r->args);
+
+ r->internal = 1;
+ r->content_handler = NULL;
+ r->loc_conf = (*clcfp)->loc_conf;
+
+ ngx_http_update_location_config(r);
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ r->phase_handler = cmcf->phase_engine.location_rewrite_index;
+
+ ngx_http_core_run_phases(r);
+
+ return NGX_DONE;
+ }
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "could not find named location \"%V\"", name);
+
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+ return NGX_DONE;
+}
+
+
+ngx_http_cleanup_t *
+ngx_http_cleanup_add(ngx_http_request_t *r, size_t size)
+{
+ ngx_http_cleanup_t *cln;
+
+ r = r->main;
+
+ cln = ngx_palloc(r->pool, sizeof(ngx_http_cleanup_t));
+ if (cln == NULL) {
+ return NULL;
+ }
+
+ if (size) {
+ cln->data = ngx_palloc(r->pool, size);
+ if (cln->data == NULL) {
+ return NULL;
+ }
+
+ } else {
+ cln->data = NULL;
+ }
+
+ cln->handler = NULL;
+ cln->next = r->cleanup;
+
+ r->cleanup = cln;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http cleanup add: %p", cln);
+
+ return cln;
+}
+
+
+static char *
+ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
+{
+ char *rv;
+ void *mconf;
+ ngx_uint_t i;
+ ngx_conf_t pcf;
+ ngx_http_module_t *module;
+ struct sockaddr_in *sin;
+ ngx_http_conf_ctx_t *ctx, *http_ctx;
+ ngx_http_listen_opt_t lsopt;
+ ngx_http_core_srv_conf_t *cscf, **cscfp;
+ ngx_http_core_main_conf_t *cmcf;
+
+ ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
+ if (ctx == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ http_ctx = cf->ctx;
+ ctx->main_conf = http_ctx->main_conf;
+
+ /* the server{}'s srv_conf */
+
+ ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+ if (ctx->srv_conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ /* the server{}'s loc_conf */
+
+ ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+ if (ctx->loc_conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 0; ngx_modules[i]; i++) {
+ if (ngx_modules[i]->type != NGX_HTTP_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[i]->ctx;
+
+ if (module->create_srv_conf) {
+ mconf = module->create_srv_conf(cf);
+ if (mconf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ctx->srv_conf[ngx_modules[i]->ctx_index] = mconf;
+ }
+
+ if (module->create_loc_conf) {
+ mconf = module->create_loc_conf(cf);
+ if (mconf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ctx->loc_conf[ngx_modules[i]->ctx_index] = mconf;
+ }
+ }
+
+
+ /* the server configuration context */
+
+ cscf = ctx->srv_conf[ngx_http_core_module.ctx_index];
+ cscf->ctx = ctx;
+
+
+ cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
+
+ cscfp = ngx_array_push(&cmcf->servers);
+ if (cscfp == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *cscfp = cscf;
+
+
+ /* parse inside server{} */
+
+ pcf = *cf;
+ cf->ctx = ctx;
+ cf->cmd_type = NGX_HTTP_SRV_CONF;
+
+ rv = ngx_conf_parse(cf, NULL);
+
+ *cf = pcf;
+
+ if (rv == NGX_CONF_OK && !cscf->listen) {
+ ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
+
+ sin = &lsopt.u.sockaddr_in;
+
+ sin->sin_family = AF_INET;
+#if (NGX_WIN32)
+ sin->sin_port = htons(80);
+#else
+ sin->sin_port = htons((getuid() == 0) ? 80 : 8000);
+#endif
+ sin->sin_addr.s_addr = INADDR_ANY;
+
+ lsopt.socklen = sizeof(struct sockaddr_in);
+
+ lsopt.backlog = NGX_LISTEN_BACKLOG;
+ lsopt.rcvbuf = -1;
+ lsopt.sndbuf = -1;
+#if (NGX_HAVE_SETFIB)
+ lsopt.setfib = -1;
+#endif
+ lsopt.wildcard = 1;
+
+ (void) ngx_sock_ntop(&lsopt.u.sockaddr, lsopt.addr,
+ NGX_SOCKADDR_STRLEN, 1);
+
+ if (ngx_http_add_listen(cf, cscf, &lsopt) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ return rv;
+}
+
+
+static char *
+ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
+{
+ char *rv;
+ u_char *mod;
+ size_t len;
+ ngx_str_t *value, *name;
+ ngx_uint_t i;
+ ngx_conf_t save;
+ ngx_http_module_t *module;
+ ngx_http_conf_ctx_t *ctx, *pctx;
+ ngx_http_core_loc_conf_t *clcf, *pclcf;
+
+ ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
+ if (ctx == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pctx = cf->ctx;
+ ctx->main_conf = pctx->main_conf;
+ ctx->srv_conf = pctx->srv_conf;
+
+ ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+ if (ctx->loc_conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 0; ngx_modules[i]; i++) {
+ if (ngx_modules[i]->type != NGX_HTTP_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[i]->ctx;
+
+ if (module->create_loc_conf) {
+ ctx->loc_conf[ngx_modules[i]->ctx_index] =
+ module->create_loc_conf(cf);
+ if (ctx->loc_conf[ngx_modules[i]->ctx_index] == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+ }
+
+ clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
+ clcf->loc_conf = ctx->loc_conf;
+
+ value = cf->args->elts;
+
+ if (cf->args->nelts == 3) {
+
+ len = value[1].len;
+ mod = value[1].data;
+ name = &value[2];
+
+ if (len == 1 && mod[0] == '=') {
+
+ clcf->name = *name;
+ clcf->exact_match = 1;
+
+ } else if (len == 2 && mod[0] == '^' && mod[1] == '~') {
+
+ clcf->name = *name;
+ clcf->noregex = 1;
+
+ } else if (len == 1 && mod[0] == '~') {
+
+ if (ngx_http_core_regex_location(cf, clcf, name, 0) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ } else if (len == 2 && mod[0] == '~' && mod[1] == '*') {
+
+ if (ngx_http_core_regex_location(cf, clcf, name, 1) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid location modifier \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+
+ name = &value[1];
+
+ if (name->data[0] == '=') {
+
+ clcf->name.len = name->len - 1;
+ clcf->name.data = name->data + 1;
+ clcf->exact_match = 1;
+
+ } else if (name->data[0] == '^' && name->data[1] == '~') {
+
+ clcf->name.len = name->len - 2;
+ clcf->name.data = name->data + 2;
+ clcf->noregex = 1;
+
+ } else if (name->data[0] == '~') {
+
+ name->len--;
+ name->data++;
+
+ if (name->data[0] == '*') {
+
+ name->len--;
+ name->data++;
+
+ if (ngx_http_core_regex_location(cf, clcf, name, 1) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+ if (ngx_http_core_regex_location(cf, clcf, name, 0) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ } else {
+
+ clcf->name = *name;
+
+ if (name->data[0] == '@') {
+ clcf->named = 1;
+ }
+ }
+ }
+
+ pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index];
+
+ if (pclcf->name.len) {
+
+ /* nested location */
+
+#if 0
+ clcf->prev_location = pclcf;
+#endif
+
+ if (pclcf->exact_match) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "location \"%V\" could not be inside "
+ "the exact location \"%V\"",
+ &clcf->name, &pclcf->name);
+ return NGX_CONF_ERROR;
+ }
+
+ if (pclcf->named) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "location \"%V\" could not be inside "
+ "the named location \"%V\"",
+ &clcf->name, &pclcf->name);
+ return NGX_CONF_ERROR;
+ }
+
+ if (clcf->named) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "named location \"%V\" must be "
+ "on server level only",
+ &clcf->name);
+ return NGX_CONF_ERROR;
+ }
+
+ len = pclcf->name.len;
+
+#if (NGX_PCRE)
+ if (clcf->regex == NULL
+ && ngx_strncmp(clcf->name.data, pclcf->name.data, len) != 0)
+#else
+ if (ngx_strncmp(clcf->name.data, pclcf->name.data, len) != 0)
+#endif
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "location \"%V\" is outside location \"%V\"",
+ &clcf->name, &pclcf->name);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ save = *cf;
+ cf->ctx = ctx;
+ cf->cmd_type = NGX_HTTP_LOC_CONF;
+
+ rv = ngx_conf_parse(cf, NULL);
+
+ *cf = save;
+
+ return rv;
+}
+
+
+static ngx_int_t
+ngx_http_core_regex_location(ngx_conf_t *cf, ngx_http_core_loc_conf_t *clcf,
+ ngx_str_t *regex, ngx_uint_t caseless)
+{
+#if (NGX_PCRE)
+ ngx_regex_compile_t rc;
+ u_char errstr[NGX_MAX_CONF_ERRSTR];
+
+ ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+ rc.pattern = *regex;
+ rc.err.len = NGX_MAX_CONF_ERRSTR;
+ rc.err.data = errstr;
+
+#if (NGX_HAVE_CASELESS_FILESYSTEM)
+ rc.options = NGX_REGEX_CASELESS;
+#else
+ rc.options = caseless;
+#endif
+
+ clcf->regex = ngx_http_regex_compile(cf, &rc);
+ if (clcf->regex == NULL) {
+ return NGX_ERROR;
+ }
+
+ clcf->name = *regex;
+
+ return NGX_OK;
+
+#else
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the using of the regex \"%V\" requires PCRE library",
+ regex);
+ return NGX_ERROR;
+
+#endif
+}
+
+
+static char *
+ngx_http_core_types(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf = conf;
+
+ char *rv;
+ ngx_conf_t save;
+
+ if (clcf->types == NULL) {
+ clcf->types = ngx_array_create(cf->pool, 64, sizeof(ngx_hash_key_t));
+ if (clcf->types == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ save = *cf;
+ cf->handler = ngx_http_core_type;
+ cf->handler_conf = conf;
+
+ rv = ngx_conf_parse(cf, NULL);
+
+ *cf = save;
+
+ return rv;
+}
+
+
+static char *
+ngx_http_core_type(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf = conf;
+
+ ngx_str_t *value, *content_type, *old, file;
+ ngx_uint_t i, n, hash;
+ ngx_hash_key_t *type;
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[0].data, "include") == 0) {
+ file = value[1];
+
+ if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
+
+ return ngx_conf_parse(cf, &file);
+ }
+
+ content_type = ngx_palloc(cf->pool, sizeof(ngx_str_t));
+ if (content_type == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *content_type = value[0];
+
+ for (i = 1; i < cf->args->nelts; i++) {
+
+ hash = ngx_hash_strlow(value[i].data, value[i].data, value[i].len);
+
+ type = clcf->types->elts;
+ for (n = 0; n < clcf->types->nelts; n++) {
+ if (ngx_strcmp(value[i].data, type[n].key.data) == 0) {
+ old = type[n].value;
+ type[n].value = content_type;
+
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "duplicate extention \"%V\", "
+ "content type: \"%V\", "
+ "old content type: \"%V\"",
+ &value[i], content_type, old);
+ continue;
+ }
+ }
+
+
+ type = ngx_array_push(clcf->types);
+ if (type == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ type->key = value[i];
+ type->key_hash = hash;
+ type->value = content_type;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_core_preconfiguration(ngx_conf_t *cf)
+{
+ return ngx_http_variables_add_core_vars(cf);
+}
+
+
+static void *
+ngx_http_core_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_core_main_conf_t));
+ if (cmcf == NULL) {
+ return NULL;
+ }
+
+ if (ngx_array_init(&cmcf->servers, cf->pool, 4,
+ sizeof(ngx_http_core_srv_conf_t *))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ cmcf->server_names_hash_max_size = NGX_CONF_UNSET_UINT;
+ cmcf->server_names_hash_bucket_size = NGX_CONF_UNSET_UINT;
+
+ cmcf->variables_hash_max_size = NGX_CONF_UNSET_UINT;
+ cmcf->variables_hash_bucket_size = NGX_CONF_UNSET_UINT;
+
+ return cmcf;
+}
+
+
+static char *
+ngx_http_core_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+ ngx_http_core_main_conf_t *cmcf = conf;
+
+ if (cmcf->server_names_hash_max_size == NGX_CONF_UNSET_UINT) {
+ cmcf->server_names_hash_max_size = 512;
+ }
+
+ if (cmcf->server_names_hash_bucket_size == NGX_CONF_UNSET_UINT) {
+ cmcf->server_names_hash_bucket_size = ngx_cacheline_size;
+ }
+
+ cmcf->server_names_hash_bucket_size =
+ ngx_align(cmcf->server_names_hash_bucket_size, ngx_cacheline_size);
+
+
+ if (cmcf->variables_hash_max_size == NGX_CONF_UNSET_UINT) {
+ cmcf->variables_hash_max_size = 512;
+ }
+
+ if (cmcf->variables_hash_bucket_size == NGX_CONF_UNSET_UINT) {
+ cmcf->variables_hash_bucket_size = 64;
+ }
+
+ cmcf->variables_hash_bucket_size =
+ ngx_align(cmcf->variables_hash_bucket_size, ngx_cacheline_size);
+
+ if (cmcf->ncaptures) {
+ cmcf->ncaptures = (cmcf->ncaptures + 1) * 3;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_core_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_http_core_srv_conf_t *cscf;
+
+ cscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_core_srv_conf_t));
+ if (cscf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->client_large_buffers.num = 0;
+ */
+
+ if (ngx_array_init(&cscf->server_names, cf->temp_pool, 4,
+ sizeof(ngx_http_server_name_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ cscf->connection_pool_size = NGX_CONF_UNSET_SIZE;
+ cscf->request_pool_size = NGX_CONF_UNSET_SIZE;
+ cscf->client_header_timeout = NGX_CONF_UNSET_MSEC;
+ cscf->client_header_buffer_size = NGX_CONF_UNSET_SIZE;
+ cscf->ignore_invalid_headers = NGX_CONF_UNSET;
+ cscf->merge_slashes = NGX_CONF_UNSET;
+ cscf->underscores_in_headers = NGX_CONF_UNSET;
+
+ return cscf;
+}
+
+
+static char *
+ngx_http_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_core_srv_conf_t *prev = parent;
+ ngx_http_core_srv_conf_t *conf = child;
+
+ ngx_str_t name;
+ ngx_http_server_name_t *sn;
+
+ /* TODO: it does not merge, it inits only */
+
+ ngx_conf_merge_size_value(conf->connection_pool_size,
+ prev->connection_pool_size, 256);
+ ngx_conf_merge_size_value(conf->request_pool_size,
+ prev->request_pool_size, 4096);
+ ngx_conf_merge_msec_value(conf->client_header_timeout,
+ prev->client_header_timeout, 60000);
+ ngx_conf_merge_size_value(conf->client_header_buffer_size,
+ prev->client_header_buffer_size, 1024);
+ ngx_conf_merge_bufs_value(conf->large_client_header_buffers,
+ prev->large_client_header_buffers,
+ 4, 8192);
+
+ if (conf->large_client_header_buffers.size < conf->connection_pool_size) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the \"large_client_header_buffers\" size must be "
+ "equal to or bigger than \"connection_pool_size\"");
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_value(conf->ignore_invalid_headers,
+ prev->ignore_invalid_headers, 1);
+
+ ngx_conf_merge_value(conf->merge_slashes, prev->merge_slashes, 1);
+
+ ngx_conf_merge_value(conf->underscores_in_headers,
+ prev->underscores_in_headers, 0);
+
+ if (conf->server_names.nelts == 0) {
+ /* the array has 4 empty preallocated elements, so push can not fail */
+ sn = ngx_array_push(&conf->server_names);
+#if (NGX_PCRE)
+ sn->regex = NULL;
+#endif
+ sn->server = conf;
+ ngx_str_set(&sn->name, "");
+ }
+
+ sn = conf->server_names.elts;
+ name = sn[0].name;
+
+#if (NGX_PCRE)
+ if (sn->regex) {
+ name.len++;
+ name.data--;
+ } else
+#endif
+
+ if (name.data[0] == '.') {
+ name.len--;
+ name.data++;
+ }
+
+ conf->server_name.len = name.len;
+ conf->server_name.data = ngx_pstrdup(cf->pool, &name);
+ if (conf->server_name.data == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_core_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_core_loc_conf_t *clcf;
+
+ clcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_core_loc_conf_t));
+ if (clcf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * clcf->root = { 0, NULL };
+ * clcf->limit_except = 0;
+ * clcf->post_action = { 0, NULL };
+ * clcf->types = NULL;
+ * clcf->default_type = { 0, NULL };
+ * clcf->error_log = NULL;
+ * clcf->error_pages = NULL;
+ * clcf->try_files = NULL;
+ * clcf->client_body_path = NULL;
+ * clcf->regex = NULL;
+ * clcf->exact_match = 0;
+ * clcf->auto_redirect = 0;
+ * clcf->alias = 0;
+ * clcf->gzip_proxied = 0;
+ */
+
+ clcf->client_max_body_size = NGX_CONF_UNSET;
+ clcf->client_body_buffer_size = NGX_CONF_UNSET_SIZE;
+ clcf->client_body_timeout = NGX_CONF_UNSET_MSEC;
+ clcf->keepalive_disable = NGX_CONF_UNSET_UINT;
+ clcf->satisfy = NGX_CONF_UNSET_UINT;
+ clcf->if_modified_since = NGX_CONF_UNSET_UINT;
+ clcf->client_body_in_file_only = NGX_CONF_UNSET_UINT;
+ clcf->client_body_in_single_buffer = NGX_CONF_UNSET;
+ clcf->internal = NGX_CONF_UNSET;
+ clcf->sendfile = NGX_CONF_UNSET;
+ clcf->sendfile_max_chunk = NGX_CONF_UNSET_SIZE;
+#if (NGX_HAVE_FILE_AIO)
+ clcf->aio = NGX_CONF_UNSET;
+#endif
+ clcf->read_ahead = NGX_CONF_UNSET_SIZE;
+ clcf->directio = NGX_CONF_UNSET;
+ clcf->directio_alignment = NGX_CONF_UNSET;
+ clcf->tcp_nopush = NGX_CONF_UNSET;
+ clcf->tcp_nodelay = NGX_CONF_UNSET;
+ clcf->send_timeout = NGX_CONF_UNSET_MSEC;
+ clcf->send_lowat = NGX_CONF_UNSET_SIZE;
+ clcf->postpone_output = NGX_CONF_UNSET_SIZE;
+ clcf->limit_rate = NGX_CONF_UNSET_SIZE;
+ clcf->limit_rate_after = NGX_CONF_UNSET_SIZE;
+ clcf->keepalive_timeout = NGX_CONF_UNSET_MSEC;
+ clcf->keepalive_header = NGX_CONF_UNSET;
+ clcf->keepalive_requests = NGX_CONF_UNSET_UINT;
+ clcf->lingering_close = NGX_CONF_UNSET_UINT;
+ clcf->lingering_time = NGX_CONF_UNSET_MSEC;
+ clcf->lingering_timeout = NGX_CONF_UNSET_MSEC;
+ clcf->resolver_timeout = NGX_CONF_UNSET_MSEC;
+ clcf->reset_timedout_connection = NGX_CONF_UNSET;
+ clcf->server_name_in_redirect = NGX_CONF_UNSET;
+ clcf->port_in_redirect = NGX_CONF_UNSET;
+ clcf->msie_padding = NGX_CONF_UNSET;
+ clcf->msie_refresh = NGX_CONF_UNSET;
+ clcf->log_not_found = NGX_CONF_UNSET;
+ clcf->log_subrequest = NGX_CONF_UNSET;
+ clcf->recursive_error_pages = NGX_CONF_UNSET;
+ clcf->server_tokens = NGX_CONF_UNSET;
+ clcf->chunked_transfer_encoding = NGX_CONF_UNSET;
+ clcf->types_hash_max_size = NGX_CONF_UNSET_UINT;
+ clcf->types_hash_bucket_size = NGX_CONF_UNSET_UINT;
+
+ clcf->open_file_cache = NGX_CONF_UNSET_PTR;
+ clcf->open_file_cache_valid = NGX_CONF_UNSET;
+ clcf->open_file_cache_min_uses = NGX_CONF_UNSET_UINT;
+ clcf->open_file_cache_errors = NGX_CONF_UNSET;
+ clcf->open_file_cache_events = NGX_CONF_UNSET;
+
+#if (NGX_HTTP_GZIP)
+ clcf->gzip_vary = NGX_CONF_UNSET;
+ clcf->gzip_http_version = NGX_CONF_UNSET_UINT;
+#if (NGX_PCRE)
+ clcf->gzip_disable = NGX_CONF_UNSET_PTR;
+#endif
+ clcf->gzip_disable_msie6 = 3;
+#if (NGX_HTTP_DEGRADATION)
+ clcf->gzip_disable_degradation = 3;
+#endif
+#endif
+
+ return clcf;
+}
+
+
+static ngx_str_t ngx_http_core_text_html_type = ngx_string("text/html");
+static ngx_str_t ngx_http_core_image_gif_type = ngx_string("image/gif");
+static ngx_str_t ngx_http_core_image_jpeg_type = ngx_string("image/jpeg");
+
+static ngx_hash_key_t ngx_http_core_default_types[] = {
+ { ngx_string("html"), 0, &ngx_http_core_text_html_type },
+ { ngx_string("gif"), 0, &ngx_http_core_image_gif_type },
+ { ngx_string("jpg"), 0, &ngx_http_core_image_jpeg_type },
+ { ngx_null_string, 0, NULL }
+};
+
+
+static char *
+ngx_http_core_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_core_loc_conf_t *prev = parent;
+ ngx_http_core_loc_conf_t *conf = child;
+
+ ngx_uint_t i;
+ ngx_hash_key_t *type;
+ ngx_hash_init_t types_hash;
+
+ if (conf->root.data == NULL) {
+
+ conf->alias = prev->alias;
+ conf->root = prev->root;
+ conf->root_lengths = prev->root_lengths;
+ conf->root_values = prev->root_values;
+
+ if (prev->root.data == NULL) {
+ ngx_str_set(&conf->root, "html");
+
+ if (ngx_conf_full_name(cf->cycle, &conf->root, 0) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+ }
+
+ if (conf->post_action.data == NULL) {
+ conf->post_action = prev->post_action;
+ }
+
+ ngx_conf_merge_uint_value(conf->types_hash_max_size,
+ prev->types_hash_max_size, 1024);
+
+ ngx_conf_merge_uint_value(conf->types_hash_bucket_size,
+ prev->types_hash_bucket_size,
+ ngx_cacheline_size);
+
+ conf->types_hash_bucket_size = ngx_align(conf->types_hash_bucket_size,
+ ngx_cacheline_size);
+
+ /*
+ * the special handling the "types" directive in the "http" section
+ * to inherit the http's conf->types_hash to all servers
+ */
+
+ if (prev->types && prev->types_hash.buckets == NULL) {
+
+ types_hash.hash = &prev->types_hash;
+ types_hash.key = ngx_hash_key_lc;
+ types_hash.max_size = conf->types_hash_max_size;
+ types_hash.bucket_size = conf->types_hash_bucket_size;
+ types_hash.name = "types_hash";
+ types_hash.pool = cf->pool;
+ types_hash.temp_pool = NULL;
+
+ if (ngx_hash_init(&types_hash, prev->types->elts, prev->types->nelts)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (conf->types == NULL) {
+ conf->types = prev->types;
+ conf->types_hash = prev->types_hash;
+ }
+
+ if (conf->types == NULL) {
+ conf->types = ngx_array_create(cf->pool, 4, sizeof(ngx_hash_key_t));
+ if (conf->types == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 0; ngx_http_core_default_types[i].key.len; i++) {
+ type = ngx_array_push(conf->types);
+ if (type == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ type->key = ngx_http_core_default_types[i].key;
+ type->key_hash =
+ ngx_hash_key_lc(ngx_http_core_default_types[i].key.data,
+ ngx_http_core_default_types[i].key.len);
+ type->value = ngx_http_core_default_types[i].value;
+ }
+ }
+
+ if (conf->types_hash.buckets == NULL) {
+
+ types_hash.hash = &conf->types_hash;
+ types_hash.key = ngx_hash_key_lc;
+ types_hash.max_size = conf->types_hash_max_size;
+ types_hash.bucket_size = conf->types_hash_bucket_size;
+ types_hash.name = "mime_types_hash";
+ types_hash.pool = cf->pool;
+ types_hash.temp_pool = NULL;
+
+ if (ngx_hash_init(&types_hash, conf->types->elts, conf->types->nelts)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (conf->error_log == NULL) {
+ if (prev->error_log) {
+ conf->error_log = prev->error_log;
+ } else {
+ conf->error_log = &cf->cycle->new_log;
+ }
+ }
+
+ if (conf->error_pages == NULL && prev->error_pages) {
+ conf->error_pages = prev->error_pages;
+ }
+
+ ngx_conf_merge_str_value(conf->default_type,
+ prev->default_type, "text/plain");
+
+ ngx_conf_merge_off_value(conf->client_max_body_size,
+ prev->client_max_body_size, 1 * 1024 * 1024);
+ ngx_conf_merge_size_value(conf->client_body_buffer_size,
+ prev->client_body_buffer_size,
+ (size_t) 2 * ngx_pagesize);
+ ngx_conf_merge_msec_value(conf->client_body_timeout,
+ prev->client_body_timeout, 60000);
+
+ ngx_conf_merge_uint_value(conf->keepalive_disable, prev->keepalive_disable,
+ NGX_HTTP_KEEPALIVE_DISABLE_MSIE6
+ |NGX_HTTP_KEEPALIVE_DISABLE_SAFARI);
+ ngx_conf_merge_uint_value(conf->satisfy, prev->satisfy,
+ NGX_HTTP_SATISFY_ALL);
+ ngx_conf_merge_uint_value(conf->if_modified_since, prev->if_modified_since,
+ NGX_HTTP_IMS_EXACT);
+ ngx_conf_merge_uint_value(conf->client_body_in_file_only,
+ prev->client_body_in_file_only, 0);
+ ngx_conf_merge_value(conf->client_body_in_single_buffer,
+ prev->client_body_in_single_buffer, 0);
+ ngx_conf_merge_value(conf->internal, prev->internal, 0);
+ ngx_conf_merge_value(conf->sendfile, prev->sendfile, 0);
+ ngx_conf_merge_size_value(conf->sendfile_max_chunk,
+ prev->sendfile_max_chunk, 0);
+#if (NGX_HAVE_FILE_AIO)
+ ngx_conf_merge_value(conf->aio, prev->aio, 0);
+#endif
+ ngx_conf_merge_size_value(conf->read_ahead, prev->read_ahead, 0);
+ ngx_conf_merge_off_value(conf->directio, prev->directio,
+ NGX_MAX_OFF_T_VALUE);
+ ngx_conf_merge_off_value(conf->directio_alignment, prev->directio_alignment,
+ 512);
+ ngx_conf_merge_value(conf->tcp_nopush, prev->tcp_nopush, 0);
+ ngx_conf_merge_value(conf->tcp_nodelay, prev->tcp_nodelay, 1);
+
+ ngx_conf_merge_msec_value(conf->send_timeout, prev->send_timeout, 60000);
+ ngx_conf_merge_size_value(conf->send_lowat, prev->send_lowat, 0);
+ ngx_conf_merge_size_value(conf->postpone_output, prev->postpone_output,
+ 1460);
+ ngx_conf_merge_size_value(conf->limit_rate, prev->limit_rate, 0);
+ ngx_conf_merge_size_value(conf->limit_rate_after, prev->limit_rate_after,
+ 0);
+ ngx_conf_merge_msec_value(conf->keepalive_timeout,
+ prev->keepalive_timeout, 75000);
+ ngx_conf_merge_sec_value(conf->keepalive_header,
+ prev->keepalive_header, 0);
+ ngx_conf_merge_uint_value(conf->keepalive_requests,
+ prev->keepalive_requests, 100);
+ ngx_conf_merge_uint_value(conf->lingering_close,
+ prev->lingering_close, NGX_HTTP_LINGERING_ON);
+ ngx_conf_merge_msec_value(conf->lingering_time,
+ prev->lingering_time, 30000);
+ ngx_conf_merge_msec_value(conf->lingering_timeout,
+ prev->lingering_timeout, 5000);
+ ngx_conf_merge_msec_value(conf->resolver_timeout,
+ prev->resolver_timeout, 30000);
+
+ if (conf->resolver == NULL) {
+
+ if (prev->resolver == NULL) {
+
+ /*
+ * create dummy resolver in http {} context
+ * to inherit it in all servers
+ */
+
+ prev->resolver = ngx_resolver_create(cf, NULL);
+ if (prev->resolver == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ conf->resolver = prev->resolver;
+ }
+
+ if (ngx_conf_merge_path_value(cf, &conf->client_body_temp_path,
+ prev->client_body_temp_path,
+ &ngx_http_client_temp_path)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_value(conf->reset_timedout_connection,
+ prev->reset_timedout_connection, 0);
+ ngx_conf_merge_value(conf->server_name_in_redirect,
+ prev->server_name_in_redirect, 0);
+ ngx_conf_merge_value(conf->port_in_redirect, prev->port_in_redirect, 1);
+ ngx_conf_merge_value(conf->msie_padding, prev->msie_padding, 1);
+ ngx_conf_merge_value(conf->msie_refresh, prev->msie_refresh, 0);
+ ngx_conf_merge_value(conf->log_not_found, prev->log_not_found, 1);
+ ngx_conf_merge_value(conf->log_subrequest, prev->log_subrequest, 0);
+ ngx_conf_merge_value(conf->recursive_error_pages,
+ prev->recursive_error_pages, 0);
+ ngx_conf_merge_value(conf->server_tokens, prev->server_tokens, 1);
+ ngx_conf_merge_value(conf->chunked_transfer_encoding,
+ prev->chunked_transfer_encoding, 1);
+
+ ngx_conf_merge_ptr_value(conf->open_file_cache,
+ prev->open_file_cache, NULL);
+
+ ngx_conf_merge_sec_value(conf->open_file_cache_valid,
+ prev->open_file_cache_valid, 60);
+
+ ngx_conf_merge_uint_value(conf->open_file_cache_min_uses,
+ prev->open_file_cache_min_uses, 1);
+
+ ngx_conf_merge_sec_value(conf->open_file_cache_errors,
+ prev->open_file_cache_errors, 0);
+
+ ngx_conf_merge_sec_value(conf->open_file_cache_events,
+ prev->open_file_cache_events, 0);
+#if (NGX_HTTP_GZIP)
+
+ ngx_conf_merge_value(conf->gzip_vary, prev->gzip_vary, 0);
+ ngx_conf_merge_uint_value(conf->gzip_http_version, prev->gzip_http_version,
+ NGX_HTTP_VERSION_11);
+ ngx_conf_merge_bitmask_value(conf->gzip_proxied, prev->gzip_proxied,
+ (NGX_CONF_BITMASK_SET|NGX_HTTP_GZIP_PROXIED_OFF));
+
+#if (NGX_PCRE)
+ ngx_conf_merge_ptr_value(conf->gzip_disable, prev->gzip_disable, NULL);
+#endif
+
+ if (conf->gzip_disable_msie6 == 3) {
+ conf->gzip_disable_msie6 =
+ (prev->gzip_disable_msie6 == 3) ? 0 : prev->gzip_disable_msie6;
+ }
+
+#if (NGX_HTTP_DEGRADATION)
+
+ if (conf->gzip_disable_degradation == 3) {
+ conf->gzip_disable_degradation =
+ (prev->gzip_disable_degradation == 3) ?
+ 0 : prev->gzip_disable_degradation;
+ }
+
+#endif
+#endif
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_srv_conf_t *cscf = conf;
+
+ ngx_str_t *value, size;
+ ngx_url_t u;
+ ngx_uint_t n;
+ ngx_http_listen_opt_t lsopt;
+
+ cscf->listen = 1;
+
+ value = cf->args->elts;
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.url = value[1];
+ u.listen = 1;
+ u.default_port = 80;
+
+ if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+ if (u.err) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "%s in \"%V\" of the \"listen\" directive",
+ u.err, &u.url);
+ }
+
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
+
+ ngx_memcpy(&lsopt.u.sockaddr, u.sockaddr, u.socklen);
+
+ lsopt.socklen = u.socklen;
+ lsopt.backlog = NGX_LISTEN_BACKLOG;
+ lsopt.rcvbuf = -1;
+ lsopt.sndbuf = -1;
+#if (NGX_HAVE_SETFIB)
+ lsopt.setfib = -1;
+#endif
+ lsopt.wildcard = u.wildcard;
+
+ (void) ngx_sock_ntop(&lsopt.u.sockaddr, lsopt.addr,
+ NGX_SOCKADDR_STRLEN, 1);
+
+ for (n = 2; n < cf->args->nelts; n++) {
+
+ if (ngx_strcmp(value[n].data, "default_server") == 0
+ || ngx_strcmp(value[n].data, "default") == 0)
+ {
+ lsopt.default_server = 1;
+ continue;
+ }
+
+ if (ngx_strcmp(value[n].data, "bind") == 0) {
+ lsopt.set = 1;
+ lsopt.bind = 1;
+ continue;
+ }
+
+#if (NGX_HAVE_SETFIB)
+ if (ngx_strncmp(value[n].data, "setfib=", 7) == 0) {
+ lsopt.setfib = ngx_atoi(value[n].data + 7, value[n].len - 7);
+
+ if (lsopt.setfib == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid setfib \"%V\"", &value[n]);
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+#endif
+ if (ngx_strncmp(value[n].data, "backlog=", 8) == 0) {
+ lsopt.backlog = ngx_atoi(value[n].data + 8, value[n].len - 8);
+ lsopt.set = 1;
+ lsopt.bind = 1;
+
+ if (lsopt.backlog == NGX_ERROR || lsopt.backlog == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid backlog \"%V\"", &value[n]);
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[n].data, "rcvbuf=", 7) == 0) {
+ size.len = value[n].len - 7;
+ size.data = value[n].data + 7;
+
+ lsopt.rcvbuf = ngx_parse_size(&size);
+ lsopt.set = 1;
+ lsopt.bind = 1;
+
+ if (lsopt.rcvbuf == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid rcvbuf \"%V\"", &value[n]);
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[n].data, "sndbuf=", 7) == 0) {
+ size.len = value[n].len - 7;
+ size.data = value[n].data + 7;
+
+ lsopt.sndbuf = ngx_parse_size(&size);
+ lsopt.set = 1;
+ lsopt.bind = 1;
+
+ if (lsopt.sndbuf == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid sndbuf \"%V\"", &value[n]);
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[n].data, "accept_filter=", 14) == 0) {
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
+ lsopt.accept_filter = (char *) &value[n].data[14];
+ lsopt.set = 1;
+ lsopt.bind = 1;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "accept filters \"%V\" are not supported "
+ "on this platform, ignored",
+ &value[n]);
+#endif
+ continue;
+ }
+
+ if (ngx_strcmp(value[n].data, "deferred") == 0) {
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
+ lsopt.deferred_accept = 1;
+ lsopt.set = 1;
+ lsopt.bind = 1;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the deferred accept is not supported "
+ "on this platform, ignored");
+#endif
+ continue;
+ }
+
+ if (ngx_strncmp(value[n].data, "ipv6only=o", 10) == 0) {
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+ struct sockaddr *sa;
+
+ sa = &lsopt.u.sockaddr;
+
+ if (sa->sa_family == AF_INET6) {
+
+ if (ngx_strcmp(&value[n].data[10], "n") == 0) {
+ lsopt.ipv6only = 1;
+
+ } else if (ngx_strcmp(&value[n].data[10], "ff") == 0) {
+ lsopt.ipv6only = 2;
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid ipv6only flags \"%s\"",
+ &value[n].data[9]);
+ return NGX_CONF_ERROR;
+ }
+
+ lsopt.set = 1;
+ lsopt.bind = 1;
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "ipv6only is not supported "
+ "on addr \"%s\", ignored", lsopt.addr);
+ }
+
+ continue;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "bind ipv6only is not supported "
+ "on this platform");
+ return NGX_CONF_ERROR;
+#endif
+ }
+
+ if (ngx_strcmp(value[n].data, "ssl") == 0) {
+#if (NGX_HTTP_SSL)
+ lsopt.ssl = 1;
+ continue;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the \"ssl\" parameter requires "
+ "ngx_http_ssl_module");
+ return NGX_CONF_ERROR;
+#endif
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the invalid \"%V\" parameter", &value[n]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_http_add_listen(cf, cscf, &lsopt) == NGX_OK) {
+ return NGX_CONF_OK;
+ }
+
+ return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_http_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_srv_conf_t *cscf = conf;
+
+ u_char ch;
+ ngx_str_t *value;
+ ngx_uint_t i;
+ ngx_http_server_name_t *sn;
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+
+ ch = value[i].data[0];
+
+ if ((ch == '*' && (value[i].len < 3 || value[i].data[1] != '.'))
+ || (ch == '.' && value[i].len < 2))
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "server name \"%V\" is invalid", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_strchr(value[i].data, '/')) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "server name \"%V\" has strange symbols",
+ &value[i]);
+ }
+
+ if (value[i].len == 1 && ch == '*') {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"server_name *\" is unsupported, use "
+ "\"server_name_in_redirect off\" instead");
+ return NGX_CONF_ERROR;
+ }
+
+ sn = ngx_array_push(&cscf->server_names);
+ if (sn == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+#if (NGX_PCRE)
+ sn->regex = NULL;
+#endif
+ sn->server = cscf;
+
+ if (ngx_strcasecmp(value[i].data, (u_char *) "$hostname") == 0) {
+ sn->name = cf->cycle->hostname;
+
+ } else {
+ sn->name = value[i];
+ }
+
+ if (value[i].data[0] != '~') {
+ ngx_strlow(sn->name.data, sn->name.data, sn->name.len);
+ continue;
+ }
+
+#if (NGX_PCRE)
+ {
+ u_char *p;
+ ngx_regex_compile_t rc;
+ u_char errstr[NGX_MAX_CONF_ERRSTR];
+
+ if (value[i].len == 1) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "empty regex in server name \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ value[i].len--;
+ value[i].data++;
+
+ ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+ rc.pattern = value[i];
+ rc.err.len = NGX_MAX_CONF_ERRSTR;
+ rc.err.data = errstr;
+
+ for (p = value[i].data; p < value[i].data + value[i].len; p++) {
+ if (*p >= 'A' && *p <= 'Z') {
+ rc.options = NGX_REGEX_CASELESS;
+ break;
+ }
+ }
+
+ sn->regex = ngx_http_regex_compile(cf, &rc);
+ if (sn->regex == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ sn->name = value[i];
+ cscf->captures = (rc.captures > 0);
+ }
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the using of the regex \"%V\" "
+ "requires PCRE library", &value[i]);
+
+ return NGX_CONF_ERROR;
+#endif
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_core_root(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf = conf;
+
+ ngx_str_t *value;
+ ngx_int_t alias;
+ ngx_uint_t n;
+ ngx_http_script_compile_t sc;
+
+ alias = (cmd->name.len == sizeof("alias") - 1) ? 1 : 0;
+
+ if (clcf->root.data) {
+
+ if ((clcf->alias != 0) == alias) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"%V\" directive is duplicate",
+ &cmd->name);
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"%V\" directive is duplicate, "
+ "\"%s\" directive is specified before",
+ &cmd->name, clcf->alias ? "alias" : "root");
+ }
+
+ return NGX_CONF_ERROR;
+ }
+
+ if (clcf->named && alias) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the \"alias\" directive may not be used "
+ "inside named location");
+
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ if (ngx_strstr(value[1].data, "$document_root")
+ || ngx_strstr(value[1].data, "${document_root}"))
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the $document_root variable may not be used "
+ "in the \"%V\" directive",
+ &cmd->name);
+
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_strstr(value[1].data, "$realpath_root")
+ || ngx_strstr(value[1].data, "${realpath_root}"))
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the $realpath_root variable may not be used "
+ "in the \"%V\" directive",
+ &cmd->name);
+
+ return NGX_CONF_ERROR;
+ }
+
+ clcf->alias = alias ? clcf->name.len : 0;
+ clcf->root = value[1];
+
+ if (!alias && clcf->root.data[clcf->root.len - 1] == '/') {
+ clcf->root.len--;
+ }
+
+ if (clcf->root.data[0] != '$') {
+ if (ngx_conf_full_name(cf->cycle, &clcf->root, 0) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ n = ngx_http_script_variables_count(&clcf->root);
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+ sc.variables = n;
+
+#if (NGX_PCRE)
+ if (alias && clcf->regex) {
+ n = 1;
+ }
+#endif
+
+ if (n) {
+ sc.cf = cf;
+ sc.source = &clcf->root;
+ sc.lengths = &clcf->root_lengths;
+ sc.values = &clcf->root_values;
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_http_method_name_t ngx_methods_names[] = {
+ { (u_char *) "GET", (uint32_t) ~NGX_HTTP_GET },
+ { (u_char *) "HEAD", (uint32_t) ~NGX_HTTP_HEAD },
+ { (u_char *) "POST", (uint32_t) ~NGX_HTTP_POST },
+ { (u_char *) "PUT", (uint32_t) ~NGX_HTTP_PUT },
+ { (u_char *) "DELETE", (uint32_t) ~NGX_HTTP_DELETE },
+ { (u_char *) "MKCOL", (uint32_t) ~NGX_HTTP_MKCOL },
+ { (u_char *) "COPY", (uint32_t) ~NGX_HTTP_COPY },
+ { (u_char *) "MOVE", (uint32_t) ~NGX_HTTP_MOVE },
+ { (u_char *) "OPTIONS", (uint32_t) ~NGX_HTTP_OPTIONS },
+ { (u_char *) "PROPFIND" , (uint32_t) ~NGX_HTTP_PROPFIND },
+ { (u_char *) "PROPPATCH", (uint32_t) ~NGX_HTTP_PROPPATCH },
+ { (u_char *) "LOCK", (uint32_t) ~NGX_HTTP_LOCK },
+ { (u_char *) "UNLOCK", (uint32_t) ~NGX_HTTP_UNLOCK },
+ { (u_char *) "PATCH", (uint32_t) ~NGX_HTTP_PATCH },
+ { NULL, 0 }
+};
+
+
+static char *
+ngx_http_core_limit_except(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *pclcf = conf;
+
+ char *rv;
+ void *mconf;
+ ngx_str_t *value;
+ ngx_uint_t i;
+ ngx_conf_t save;
+ ngx_http_module_t *module;
+ ngx_http_conf_ctx_t *ctx, *pctx;
+ ngx_http_method_name_t *name;
+ ngx_http_core_loc_conf_t *clcf;
+
+ if (pclcf->limit_except) {
+ return "duplicate";
+ }
+
+ pclcf->limit_except = 0xffffffff;
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+ for (name = ngx_methods_names; name->name; name++) {
+
+ if (ngx_strcasecmp(value[i].data, name->name) == 0) {
+ pclcf->limit_except &= name->method;
+ goto next;
+ }
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid method \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+
+ next:
+ continue;
+ }
+
+ if (!(pclcf->limit_except & NGX_HTTP_GET)) {
+ pclcf->limit_except &= (uint32_t) ~NGX_HTTP_HEAD;
+ }
+
+ ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
+ if (ctx == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pctx = cf->ctx;
+ ctx->main_conf = pctx->main_conf;
+ ctx->srv_conf = pctx->srv_conf;
+
+ ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+ if (ctx->loc_conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 0; 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;
+ }
+ }
+
+
+ clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
+ pclcf->limit_except_loc_conf = ctx->loc_conf;
+ clcf->loc_conf = ctx->loc_conf;
+ clcf->name = pclcf->name;
+ clcf->noname = 1;
+ clcf->lmt_excpt = 1;
+
+ if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ save = *cf;
+ cf->ctx = ctx;
+ cf->cmd_type = NGX_HTTP_LMT_CONF;
+
+ rv = ngx_conf_parse(cf, NULL);
+
+ *cf = save;
+
+ return rv;
+}
+
+
+static char *
+ngx_http_core_directio(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf = conf;
+
+ ngx_str_t *value;
+
+ if (clcf->directio != NGX_CONF_UNSET) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ clcf->directio = NGX_OPEN_FILE_DIRECTIO_OFF;
+ return NGX_CONF_OK;
+ }
+
+ clcf->directio = ngx_parse_offset(&value[1]);
+ if (clcf->directio == (off_t) NGX_ERROR) {
+ return "invalid value";
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_core_error_page(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf = conf;
+
+ u_char *p;
+ ngx_int_t overwrite;
+ ngx_str_t *value, uri, args;
+ ngx_uint_t i, n;
+ ngx_http_err_page_t *err;
+ ngx_http_complex_value_t cv;
+ ngx_http_compile_complex_value_t ccv;
+
+ if (clcf->error_pages == NULL) {
+ clcf->error_pages = ngx_array_create(cf->pool, 4,
+ sizeof(ngx_http_err_page_t));
+ if (clcf->error_pages == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ value = cf->args->elts;
+
+ i = cf->args->nelts - 2;
+
+ if (value[i].data[0] == '=') {
+ if (i == 1) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid value \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (value[i].len > 1) {
+ overwrite = ngx_atoi(&value[i].data[1], value[i].len - 1);
+
+ if (overwrite == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid value \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+ overwrite = 0;
+ }
+
+ n = 2;
+
+ } else {
+ overwrite = -1;
+ n = 1;
+ }
+
+ uri = value[cf->args->nelts - 1];
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &uri;
+ ccv.complex_value = &cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_str_null(&args);
+
+ if (cv.lengths == NULL && uri.data[0] == '/') {
+ p = (u_char *) ngx_strchr(uri.data, '?');
+
+ if (p) {
+ cv.value.len = p - uri.data;
+ cv.value.data = uri.data;
+ p++;
+ args.len = (uri.data + uri.len) - p;
+ args.data = p;
+ }
+ }
+
+ for (i = 1; i < cf->args->nelts - n; i++) {
+ err = ngx_array_push(clcf->error_pages);
+ if (err == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ err->status = ngx_atoi(value[i].data, value[i].len);
+
+ if (err->status == NGX_ERROR || err->status == 499) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid value \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (err->status < 300 || err->status > 599) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "value \"%V\" must be between 300 and 599",
+ &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ err->overwrite = overwrite;
+
+ if (overwrite == -1) {
+ switch (err->status) {
+ case NGX_HTTP_TO_HTTPS:
+ case NGX_HTTPS_CERT_ERROR:
+ case NGX_HTTPS_NO_CERT:
+ err->overwrite = NGX_HTTP_BAD_REQUEST;
+ default:
+ break;
+ }
+ }
+
+ err->value = cv;
+ err->args = args;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_core_try_files(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf = conf;
+
+ ngx_str_t *value;
+ ngx_int_t code;
+ ngx_uint_t i, n;
+ ngx_http_try_file_t *tf;
+ ngx_http_script_compile_t sc;
+ ngx_http_core_main_conf_t *cmcf;
+
+ if (clcf->try_files) {
+ return "is duplicate";
+ }
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ cmcf->try_files = 1;
+
+ tf = ngx_pcalloc(cf->pool, cf->args->nelts * sizeof(ngx_http_try_file_t));
+ if (tf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ clcf->try_files = tf;
+
+ value = cf->args->elts;
+
+ for (i = 0; i < cf->args->nelts - 1; i++) {
+
+ tf[i].name = value[i + 1];
+
+ if (tf[i].name.data[tf[i].name.len - 1] == '/') {
+ tf[i].test_dir = 1;
+ tf[i].name.len--;
+ tf[i].name.data[tf[i].name.len] = '\0';
+ }
+
+ n = ngx_http_script_variables_count(&tf[i].name);
+
+ if (n) {
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = &tf[i].name;
+ sc.lengths = &tf[i].lengths;
+ sc.values = &tf[i].values;
+ sc.variables = n;
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+ /* add trailing '\0' to length */
+ tf[i].name.len++;
+ }
+ }
+
+ if (tf[i - 1].name.data[0] == '=') {
+
+ code = ngx_atoi(tf[i - 1].name.data + 1, tf[i - 1].name.len - 2);
+
+ if (code == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid code \"%*s\"",
+ tf[i - 1].name.len - 1, tf[i - 1].name.data);
+ return NGX_CONF_ERROR;
+ }
+
+ tf[i].code = code;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_core_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf = conf;
+
+ time_t inactive;
+ ngx_str_t *value, s;
+ ngx_int_t max;
+ ngx_uint_t i;
+
+ if (clcf->open_file_cache != NGX_CONF_UNSET_PTR) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ max = 0;
+ inactive = 60;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+
+ if (ngx_strncmp(value[i].data, "max=", 4) == 0) {
+
+ max = ngx_atoi(value[i].data + 4, value[i].len - 4);
+ if (max == 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 < 0) {
+ goto failed;
+ }
+
+ continue;
+ }
+
+ if (ngx_strcmp(value[i].data, "off") == 0) {
+
+ clcf->open_file_cache = NULL;
+
+ continue;
+ }
+
+ failed:
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid \"open_file_cache\" parameter \"%V\"",
+ &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (clcf->open_file_cache == NULL) {
+ return NGX_CONF_OK;
+ }
+
+ if (max == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"open_file_cache\" must have \"max\" parameter");
+ return NGX_CONF_ERROR;
+ }
+
+ clcf->open_file_cache = ngx_open_file_cache_init(cf->pool, max, inactive);
+ if (clcf->open_file_cache) {
+ return NGX_CONF_OK;
+ }
+
+ return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_http_core_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf = conf;
+
+ ngx_str_t *value;
+
+ if (clcf->error_log) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ clcf->error_log = ngx_log_create(cf->cycle, &value[1]);
+ if (clcf->error_log == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cf->args->nelts == 2) {
+ clcf->error_log->log_level = NGX_LOG_ERR;
+ return NGX_CONF_OK;
+ }
+
+ return ngx_log_set_levels(cf, clcf->error_log);
+}
+
+
+static char *
+ngx_http_core_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf = conf;
+
+ ngx_str_t *value;
+
+ if (clcf->keepalive_timeout != NGX_CONF_UNSET_MSEC) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ clcf->keepalive_timeout = ngx_parse_time(&value[1], 0);
+
+ if (clcf->keepalive_timeout == (ngx_msec_t) NGX_ERROR) {
+ return "invalid value";
+ }
+
+ if (clcf->keepalive_timeout == (ngx_msec_t) NGX_PARSE_LARGE_TIME) {
+ return "value must be less than 597 hours";
+ }
+
+ if (cf->args->nelts == 2) {
+ return NGX_CONF_OK;
+ }
+
+ clcf->keepalive_header = ngx_parse_time(&value[2], 1);
+
+ if (clcf->keepalive_header == NGX_ERROR) {
+ return "invalid value";
+ }
+
+ if (clcf->keepalive_header == NGX_PARSE_LARGE_TIME) {
+ return "value must be less than 68 years";
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_core_internal(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf = conf;
+
+ if (clcf->internal != NGX_CONF_UNSET) {
+ return "is duplicate";
+ }
+
+ clcf->internal = 1;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf = conf;
+
+ ngx_url_t u;
+ ngx_str_t *value;
+
+ if (clcf->resolver) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.host = value[1];
+ u.port = 53;
+
+ if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V: %s", &u.host, u.err);
+ return NGX_CONF_ERROR;
+ }
+
+ clcf->resolver = ngx_resolver_create(cf, &u.addrs[0]);
+ if (clcf->resolver == NULL) {
+ return NGX_OK;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+#if (NGX_HTTP_GZIP)
+
+static char *
+ngx_http_gzip_disable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf = conf;
+
+#if (NGX_PCRE)
+
+ ngx_str_t *value;
+ ngx_uint_t i;
+ ngx_regex_elt_t *re;
+ ngx_regex_compile_t rc;
+ u_char errstr[NGX_MAX_CONF_ERRSTR];
+
+ if (clcf->gzip_disable == NGX_CONF_UNSET_PTR) {
+ clcf->gzip_disable = ngx_array_create(cf->pool, 2,
+ sizeof(ngx_regex_elt_t));
+ if (clcf->gzip_disable == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ value = cf->args->elts;
+
+ ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+ rc.pool = cf->pool;
+ rc.err.len = NGX_MAX_CONF_ERRSTR;
+ rc.err.data = errstr;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+
+ if (ngx_strcmp(value[i].data, "msie6") == 0) {
+ clcf->gzip_disable_msie6 = 1;
+ continue;
+ }
+
+#if (NGX_HTTP_DEGRADATION)
+
+ if (ngx_strcmp(value[i].data, "degradation") == 0) {
+ clcf->gzip_disable_degradation = 1;
+ continue;
+ }
+
+#endif
+
+ re = ngx_array_push(clcf->gzip_disable);
+ if (re == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ rc.pattern = value[i];
+ rc.options = NGX_REGEX_CASELESS;
+
+ if (ngx_regex_compile(&rc) != NGX_OK) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
+ return NGX_CONF_ERROR;
+ }
+
+ re->regex = rc.regex;
+ re->name = value[i].data;
+ }
+
+ return NGX_CONF_OK;
+
+#else
+ ngx_str_t *value;
+ ngx_uint_t i;
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+ if (ngx_strcmp(value[i].data, "msie6") == 0) {
+ clcf->gzip_disable_msie6 = 1;
+ continue;
+ }
+
+#if (NGX_HTTP_DEGRADATION)
+
+ if (ngx_strcmp(value[i].data, "degradation") == 0) {
+ clcf->gzip_disable_degradation = 1;
+ continue;
+ }
+
+#endif
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "without PCRE library \"gzip_disable\" supports "
+ "builtin \"msie6\" and \"degradation\" mask only");
+
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+
+#endif
+}
+
+#endif
+
+
+static char *
+ngx_http_core_lowat_check(ngx_conf_t *cf, void *post, void *data)
+{
+#if (NGX_FREEBSD)
+ ssize_t *np = data;
+
+ if ((u_long) *np >= ngx_freebsd_net_inet_tcp_sendspace) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"send_lowat\" must be less than %d "
+ "(sysctl net.inet.tcp.sendspace)",
+ ngx_freebsd_net_inet_tcp_sendspace);
+
+ return NGX_CONF_ERROR;
+ }
+
+#elif !(NGX_HAVE_SO_SNDLOWAT)
+ ssize_t *np = data;
+
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "\"send_lowat\" is not supported, ignored");
+
+ *np = 0;
+
+#endif
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_core_pool_size(ngx_conf_t *cf, void *post, void *data)
+{
+ size_t *sp = data;
+
+ if (*sp < NGX_MIN_POOL_SIZE) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the pool size must be no less than %uz",
+ NGX_MIN_POOL_SIZE);
+ return NGX_CONF_ERROR;
+ }
+
+ if (*sp % NGX_POOL_ALIGNMENT) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the pool size must be a multiple of %uz",
+ NGX_POOL_ALIGNMENT);
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/http/ngx_http_core_module.h b/usr.sbin/nginx/src/http/ngx_http_core_module.h
new file mode 100644
index 00000000000..165e7c051d7
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_core_module.h
@@ -0,0 +1,532 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_CORE_H_INCLUDED_
+#define _NGX_HTTP_CORE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_GZIP_PROXIED_OFF 0x0002
+#define NGX_HTTP_GZIP_PROXIED_EXPIRED 0x0004
+#define NGX_HTTP_GZIP_PROXIED_NO_CACHE 0x0008
+#define NGX_HTTP_GZIP_PROXIED_NO_STORE 0x0010
+#define NGX_HTTP_GZIP_PROXIED_PRIVATE 0x0020
+#define NGX_HTTP_GZIP_PROXIED_NO_LM 0x0040
+#define NGX_HTTP_GZIP_PROXIED_NO_ETAG 0x0080
+#define NGX_HTTP_GZIP_PROXIED_AUTH 0x0100
+#define NGX_HTTP_GZIP_PROXIED_ANY 0x0200
+
+
+#define NGX_HTTP_AIO_OFF 0
+#define NGX_HTTP_AIO_ON 1
+#define NGX_HTTP_AIO_SENDFILE 2
+
+
+#define NGX_HTTP_SATISFY_ALL 0
+#define NGX_HTTP_SATISFY_ANY 1
+
+
+#define NGX_HTTP_LINGERING_OFF 0
+#define NGX_HTTP_LINGERING_ON 1
+#define NGX_HTTP_LINGERING_ALWAYS 2
+
+
+#define NGX_HTTP_IMS_OFF 0
+#define NGX_HTTP_IMS_EXACT 1
+#define NGX_HTTP_IMS_BEFORE 2
+
+
+#define NGX_HTTP_KEEPALIVE_DISABLE_NONE 0x0002
+#define NGX_HTTP_KEEPALIVE_DISABLE_MSIE6 0x0004
+#define NGX_HTTP_KEEPALIVE_DISABLE_SAFARI 0x0008
+
+
+typedef struct ngx_http_location_tree_node_s ngx_http_location_tree_node_t;
+typedef struct ngx_http_core_loc_conf_s ngx_http_core_loc_conf_t;
+
+
+typedef struct {
+ union {
+ struct sockaddr sockaddr;
+ struct sockaddr_in sockaddr_in;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 sockaddr_in6;
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+ struct sockaddr_un sockaddr_un;
+#endif
+ u_char sockaddr_data[NGX_SOCKADDRLEN];
+ } u;
+
+ socklen_t socklen;
+
+ unsigned set:1;
+ unsigned default_server:1;
+ unsigned bind:1;
+ unsigned wildcard:1;
+#if (NGX_HTTP_SSL)
+ unsigned ssl:1;
+#endif
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+ unsigned ipv6only:2;
+#endif
+
+ int backlog;
+ int rcvbuf;
+ int sndbuf;
+#if (NGX_HAVE_SETFIB)
+ int setfib;
+#endif
+
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
+ char *accept_filter;
+#endif
+#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
+ ngx_uint_t deferred_accept;
+#endif
+
+ u_char addr[NGX_SOCKADDR_STRLEN + 1];
+} ngx_http_listen_opt_t;
+
+
+typedef enum {
+ NGX_HTTP_POST_READ_PHASE = 0,
+
+ NGX_HTTP_SERVER_REWRITE_PHASE,
+
+ NGX_HTTP_FIND_CONFIG_PHASE,
+ NGX_HTTP_REWRITE_PHASE,
+ NGX_HTTP_POST_REWRITE_PHASE,
+
+ NGX_HTTP_PREACCESS_PHASE,
+
+ NGX_HTTP_ACCESS_PHASE,
+ NGX_HTTP_POST_ACCESS_PHASE,
+
+ NGX_HTTP_TRY_FILES_PHASE,
+ NGX_HTTP_CONTENT_PHASE,
+
+ NGX_HTTP_LOG_PHASE
+} ngx_http_phases;
+
+typedef struct ngx_http_phase_handler_s ngx_http_phase_handler_t;
+
+typedef ngx_int_t (*ngx_http_phase_handler_pt)(ngx_http_request_t *r,
+ ngx_http_phase_handler_t *ph);
+
+struct ngx_http_phase_handler_s {
+ ngx_http_phase_handler_pt checker;
+ ngx_http_handler_pt handler;
+ ngx_uint_t next;
+};
+
+
+typedef struct {
+ ngx_http_phase_handler_t *handlers;
+ ngx_uint_t server_rewrite_index;
+ ngx_uint_t location_rewrite_index;
+} ngx_http_phase_engine_t;
+
+
+typedef struct {
+ ngx_array_t handlers;
+} ngx_http_phase_t;
+
+
+typedef struct {
+ ngx_array_t servers; /* ngx_http_core_srv_conf_t */
+
+ ngx_http_phase_engine_t phase_engine;
+
+ ngx_hash_t headers_in_hash;
+
+ ngx_hash_t variables_hash;
+
+ ngx_array_t variables; /* ngx_http_variable_t */
+ ngx_uint_t ncaptures;
+
+ ngx_uint_t server_names_hash_max_size;
+ ngx_uint_t server_names_hash_bucket_size;
+
+ ngx_uint_t variables_hash_max_size;
+ ngx_uint_t variables_hash_bucket_size;
+
+ ngx_hash_keys_arrays_t *variables_keys;
+
+ ngx_array_t *ports;
+
+ ngx_uint_t try_files; /* unsigned try_files:1 */
+
+ ngx_http_phase_t phases[NGX_HTTP_LOG_PHASE + 1];
+} ngx_http_core_main_conf_t;
+
+
+typedef struct {
+ /* array of the ngx_http_server_name_t, "server_name" directive */
+ ngx_array_t server_names;
+
+ /* server ctx */
+ ngx_http_conf_ctx_t *ctx;
+
+ ngx_str_t server_name;
+
+ size_t connection_pool_size;
+ size_t request_pool_size;
+ size_t client_header_buffer_size;
+
+ ngx_bufs_t large_client_header_buffers;
+
+ ngx_msec_t client_header_timeout;
+
+ ngx_flag_t ignore_invalid_headers;
+ ngx_flag_t merge_slashes;
+ ngx_flag_t underscores_in_headers;
+
+ unsigned listen:1;
+#if (NGX_PCRE)
+ unsigned captures:1;
+#endif
+
+ ngx_http_core_loc_conf_t **named_locations;
+} ngx_http_core_srv_conf_t;
+
+
+/* list of structures to find core_srv_conf quickly at run time */
+
+
+typedef struct {
+ /* the default server configuration for this address:port */
+ ngx_http_core_srv_conf_t *default_server;
+
+ ngx_http_virtual_names_t *virtual_names;
+
+#if (NGX_HTTP_SSL)
+ ngx_uint_t ssl; /* unsigned ssl:1; */
+#endif
+} ngx_http_addr_conf_t;
+
+
+typedef struct {
+ in_addr_t addr;
+ ngx_http_addr_conf_t conf;
+} ngx_http_in_addr_t;
+
+
+#if (NGX_HAVE_INET6)
+
+typedef struct {
+ struct in6_addr addr6;
+ ngx_http_addr_conf_t conf;
+} ngx_http_in6_addr_t;
+
+#endif
+
+
+typedef struct {
+ /* ngx_http_in_addr_t or ngx_http_in6_addr_t */
+ void *addrs;
+ ngx_uint_t naddrs;
+} ngx_http_port_t;
+
+
+typedef struct {
+ ngx_int_t family;
+ in_port_t port;
+ ngx_array_t addrs; /* array of ngx_http_conf_addr_t */
+} ngx_http_conf_port_t;
+
+
+typedef struct {
+ ngx_http_listen_opt_t opt;
+
+ ngx_hash_t hash;
+ ngx_hash_wildcard_t *wc_head;
+ ngx_hash_wildcard_t *wc_tail;
+
+#if (NGX_PCRE)
+ ngx_uint_t nregex;
+ ngx_http_server_name_t *regex;
+#endif
+
+ /* the default server configuration for this address:port */
+ ngx_http_core_srv_conf_t *default_server;
+ ngx_array_t servers; /* array of ngx_http_core_srv_conf_t */
+} ngx_http_conf_addr_t;
+
+
+struct ngx_http_server_name_s {
+#if (NGX_PCRE)
+ ngx_http_regex_t *regex;
+#endif
+ ngx_http_core_srv_conf_t *server; /* virtual name server conf */
+ ngx_str_t name;
+};
+
+
+typedef struct {
+ ngx_int_t status;
+ ngx_int_t overwrite;
+ ngx_http_complex_value_t value;
+ ngx_str_t args;
+} ngx_http_err_page_t;
+
+
+typedef struct {
+ ngx_array_t *lengths;
+ ngx_array_t *values;
+ ngx_str_t name;
+
+ unsigned code:10;
+ unsigned test_dir:1;
+} ngx_http_try_file_t;
+
+
+struct ngx_http_core_loc_conf_s {
+ ngx_str_t name; /* location name */
+
+#if (NGX_PCRE)
+ ngx_http_regex_t *regex;
+#endif
+
+ unsigned noname:1; /* "if () {}" block or limit_except */
+ unsigned lmt_excpt:1;
+ unsigned named:1;
+
+ unsigned exact_match:1;
+ unsigned noregex:1;
+
+ unsigned auto_redirect:1;
+#if (NGX_HTTP_GZIP)
+ unsigned gzip_disable_msie6:2;
+#if (NGX_HTTP_DEGRADATION)
+ unsigned gzip_disable_degradation:2;
+#endif
+#endif
+
+ ngx_http_location_tree_node_t *static_locations;
+#if (NGX_PCRE)
+ ngx_http_core_loc_conf_t **regex_locations;
+#endif
+
+ /* pointer to the modules' loc_conf */
+ void **loc_conf;
+
+ uint32_t limit_except;
+ void **limit_except_loc_conf;
+
+ ngx_http_handler_pt handler;
+
+ /* location name length for inclusive location with inherited alias */
+ size_t alias;
+ ngx_str_t root; /* root, alias */
+ ngx_str_t post_action;
+
+ ngx_array_t *root_lengths;
+ ngx_array_t *root_values;
+
+ ngx_array_t *types;
+ ngx_hash_t types_hash;
+ ngx_str_t default_type;
+
+ off_t client_max_body_size; /* client_max_body_size */
+ off_t directio; /* directio */
+ off_t directio_alignment; /* directio_alignment */
+
+ size_t client_body_buffer_size; /* client_body_buffer_size */
+ size_t send_lowat; /* send_lowat */
+ size_t postpone_output; /* postpone_output */
+ size_t limit_rate; /* limit_rate */
+ size_t limit_rate_after; /* limit_rate_after */
+ size_t sendfile_max_chunk; /* sendfile_max_chunk */
+ size_t read_ahead; /* read_ahead */
+
+ ngx_msec_t client_body_timeout; /* client_body_timeout */
+ ngx_msec_t send_timeout; /* send_timeout */
+ ngx_msec_t keepalive_timeout; /* keepalive_timeout */
+ ngx_msec_t lingering_time; /* lingering_time */
+ ngx_msec_t lingering_timeout; /* lingering_timeout */
+ ngx_msec_t resolver_timeout; /* resolver_timeout */
+
+ ngx_resolver_t *resolver; /* resolver */
+
+ time_t keepalive_header; /* keepalive_timeout */
+
+ ngx_uint_t keepalive_requests; /* keepalive_requests */
+ ngx_uint_t keepalive_disable; /* keepalive_disable */
+ ngx_uint_t satisfy; /* satisfy */
+ ngx_uint_t lingering_close; /* lingering_close */
+ ngx_uint_t if_modified_since; /* if_modified_since */
+ ngx_uint_t client_body_in_file_only; /* client_body_in_file_only */
+
+ ngx_flag_t client_body_in_single_buffer;
+ /* client_body_in_singe_buffer */
+ ngx_flag_t internal; /* internal */
+ ngx_flag_t sendfile; /* sendfile */
+#if (NGX_HAVE_FILE_AIO)
+ ngx_flag_t aio; /* aio */
+#endif
+ ngx_flag_t tcp_nopush; /* tcp_nopush */
+ ngx_flag_t tcp_nodelay; /* tcp_nodelay */
+ ngx_flag_t reset_timedout_connection; /* reset_timedout_connection */
+ ngx_flag_t server_name_in_redirect; /* server_name_in_redirect */
+ ngx_flag_t port_in_redirect; /* port_in_redirect */
+ ngx_flag_t msie_padding; /* msie_padding */
+ ngx_flag_t msie_refresh; /* msie_refresh */
+ ngx_flag_t log_not_found; /* log_not_found */
+ ngx_flag_t log_subrequest; /* log_subrequest */
+ ngx_flag_t recursive_error_pages; /* recursive_error_pages */
+ ngx_flag_t server_tokens; /* server_tokens */
+ ngx_flag_t chunked_transfer_encoding; /* chunked_transfer_encoding */
+
+#if (NGX_HTTP_GZIP)
+ ngx_flag_t gzip_vary; /* gzip_vary */
+
+ ngx_uint_t gzip_http_version; /* gzip_http_version */
+ ngx_uint_t gzip_proxied; /* gzip_proxied */
+
+#if (NGX_PCRE)
+ ngx_array_t *gzip_disable; /* gzip_disable */
+#endif
+#endif
+
+ ngx_array_t *error_pages; /* error_page */
+ ngx_http_try_file_t *try_files; /* try_files */
+
+ ngx_path_t *client_body_temp_path; /* client_body_temp_path */
+
+ ngx_open_file_cache_t *open_file_cache;
+ time_t open_file_cache_valid;
+ ngx_uint_t open_file_cache_min_uses;
+ ngx_flag_t open_file_cache_errors;
+ ngx_flag_t open_file_cache_events;
+
+ ngx_log_t *error_log;
+
+ ngx_uint_t types_hash_max_size;
+ ngx_uint_t types_hash_bucket_size;
+
+ ngx_queue_t *locations;
+
+#if 0
+ ngx_http_core_loc_conf_t *prev_location;
+#endif
+};
+
+
+typedef struct {
+ ngx_queue_t queue;
+ ngx_http_core_loc_conf_t *exact;
+ ngx_http_core_loc_conf_t *inclusive;
+ ngx_str_t *name;
+ u_char *file_name;
+ ngx_uint_t line;
+ ngx_queue_t list;
+} ngx_http_location_queue_t;
+
+
+struct ngx_http_location_tree_node_s {
+ ngx_http_location_tree_node_t *left;
+ ngx_http_location_tree_node_t *right;
+ ngx_http_location_tree_node_t *tree;
+
+ ngx_http_core_loc_conf_t *exact;
+ ngx_http_core_loc_conf_t *inclusive;
+
+ u_char auto_redirect;
+ u_char len;
+ u_char name[1];
+};
+
+
+void ngx_http_core_run_phases(ngx_http_request_t *r);
+ngx_int_t ngx_http_core_generic_phase(ngx_http_request_t *r,
+ ngx_http_phase_handler_t *ph);
+ngx_int_t ngx_http_core_rewrite_phase(ngx_http_request_t *r,
+ ngx_http_phase_handler_t *ph);
+ngx_int_t ngx_http_core_find_config_phase(ngx_http_request_t *r,
+ ngx_http_phase_handler_t *ph);
+ngx_int_t ngx_http_core_post_rewrite_phase(ngx_http_request_t *r,
+ ngx_http_phase_handler_t *ph);
+ngx_int_t ngx_http_core_access_phase(ngx_http_request_t *r,
+ ngx_http_phase_handler_t *ph);
+ngx_int_t ngx_http_core_post_access_phase(ngx_http_request_t *r,
+ ngx_http_phase_handler_t *ph);
+ngx_int_t ngx_http_core_try_files_phase(ngx_http_request_t *r,
+ ngx_http_phase_handler_t *ph);
+ngx_int_t ngx_http_core_content_phase(ngx_http_request_t *r,
+ ngx_http_phase_handler_t *ph);
+
+
+void *ngx_http_test_content_type(ngx_http_request_t *r, ngx_hash_t *types_hash);
+ngx_int_t ngx_http_set_content_type(ngx_http_request_t *r);
+void ngx_http_set_exten(ngx_http_request_t *r);
+ngx_int_t ngx_http_send_response(ngx_http_request_t *r, ngx_uint_t status,
+ ngx_str_t *ct, ngx_http_complex_value_t *cv);
+u_char *ngx_http_map_uri_to_path(ngx_http_request_t *r, ngx_str_t *name,
+ size_t *root_length, size_t reserved);
+ngx_int_t ngx_http_auth_basic_user(ngx_http_request_t *r);
+#if (NGX_HTTP_GZIP)
+ngx_int_t ngx_http_gzip_ok(ngx_http_request_t *r);
+#endif
+
+
+ngx_int_t ngx_http_subrequest(ngx_http_request_t *r,
+ ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **sr,
+ ngx_http_post_subrequest_t *psr, ngx_uint_t flags);
+ngx_int_t ngx_http_internal_redirect(ngx_http_request_t *r,
+ ngx_str_t *uri, ngx_str_t *args);
+ngx_int_t ngx_http_named_location(ngx_http_request_t *r, ngx_str_t *name);
+
+
+ngx_http_cleanup_t *ngx_http_cleanup_add(ngx_http_request_t *r, size_t size);
+
+
+typedef ngx_int_t (*ngx_http_output_header_filter_pt)(ngx_http_request_t *r);
+typedef ngx_int_t (*ngx_http_output_body_filter_pt)
+ (ngx_http_request_t *r, ngx_chain_t *chain);
+
+
+ngx_int_t ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *chain);
+ngx_int_t ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *chain);
+
+
+extern ngx_module_t ngx_http_core_module;
+
+extern ngx_uint_t ngx_http_max_module;
+
+extern ngx_str_t ngx_http_core_get_method;
+
+
+#define ngx_http_clear_content_length(r) \
+ \
+ r->headers_out.content_length_n = -1; \
+ if (r->headers_out.content_length) { \
+ r->headers_out.content_length->hash = 0; \
+ r->headers_out.content_length = NULL; \
+ }
+ \
+#define ngx_http_clear_accept_ranges(r) \
+ \
+ r->allow_ranges = 0; \
+ if (r->headers_out.accept_ranges) { \
+ r->headers_out.accept_ranges->hash = 0; \
+ r->headers_out.accept_ranges = NULL; \
+ }
+
+#define ngx_http_clear_last_modified(r) \
+ \
+ r->headers_out.last_modified_time = -1; \
+ if (r->headers_out.last_modified) { \
+ r->headers_out.last_modified->hash = 0; \
+ r->headers_out.last_modified = NULL; \
+ }
+
+
+#endif /* _NGX_HTTP_CORE_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/http/ngx_http_file_cache.c b/usr.sbin/nginx/src/http/ngx_http_file_cache.c
new file mode 100644
index 00000000000..baf1144c24d
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_file_cache.c
@@ -0,0 +1,1776 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_md5.h>
+
+
+static ngx_int_t ngx_http_file_cache_read(ngx_http_request_t *r,
+ ngx_http_cache_t *c);
+static ssize_t ngx_http_file_cache_aio_read(ngx_http_request_t *r,
+ ngx_http_cache_t *c);
+#if (NGX_HAVE_FILE_AIO)
+static void ngx_http_cache_aio_event_handler(ngx_event_t *ev);
+#endif
+static ngx_int_t ngx_http_file_cache_exists(ngx_http_file_cache_t *cache,
+ ngx_http_cache_t *c);
+static ngx_int_t ngx_http_file_cache_name(ngx_http_request_t *r,
+ ngx_path_t *path);
+static ngx_http_file_cache_node_t *
+ ngx_http_file_cache_lookup(ngx_http_file_cache_t *cache, u_char *key);
+static void ngx_http_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp,
+ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+static void ngx_http_file_cache_cleanup(void *data);
+static time_t ngx_http_file_cache_forced_expire(ngx_http_file_cache_t *cache);
+static time_t ngx_http_file_cache_expire(ngx_http_file_cache_t *cache);
+static void ngx_http_file_cache_delete(ngx_http_file_cache_t *cache,
+ ngx_queue_t *q, u_char *name);
+static ngx_int_t
+ ngx_http_file_cache_loader_sleep(ngx_http_file_cache_t *cache);
+static ngx_int_t ngx_http_file_cache_noop(ngx_tree_ctx_t *ctx,
+ ngx_str_t *path);
+static ngx_int_t ngx_http_file_cache_manage_file(ngx_tree_ctx_t *ctx,
+ ngx_str_t *path);
+static ngx_int_t ngx_http_file_cache_add_file(ngx_tree_ctx_t *ctx,
+ ngx_str_t *path);
+static ngx_int_t ngx_http_file_cache_add(ngx_http_file_cache_t *cache,
+ ngx_http_cache_t *c);
+static ngx_int_t ngx_http_file_cache_delete_file(ngx_tree_ctx_t *ctx,
+ ngx_str_t *path);
+
+
+ngx_str_t ngx_http_cache_status[] = {
+ ngx_string("MISS"),
+ ngx_string("BYPASS"),
+ ngx_string("EXPIRED"),
+ ngx_string("STALE"),
+ ngx_string("UPDATING"),
+ ngx_string("HIT")
+};
+
+
+static u_char ngx_http_file_cache_key[] = { LF, 'K', 'E', 'Y', ':', ' ' };
+
+
+static ngx_int_t
+ngx_http_file_cache_init(ngx_shm_zone_t *shm_zone, void *data)
+{
+ ngx_http_file_cache_t *ocache = data;
+
+ size_t len;
+ ngx_uint_t n;
+ ngx_http_file_cache_t *cache;
+
+ cache = shm_zone->data;
+
+ if (ocache) {
+ if (ngx_strcmp(cache->path->name.data, ocache->path->name.data) != 0) {
+ ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
+ "cache \"%V\" uses the \"%V\" cache path "
+ "while previously it used the \"%V\" cache path",
+ &shm_zone->shm.name, &cache->path->name,
+ &ocache->path->name);
+
+ return NGX_ERROR;
+ }
+
+ for (n = 0; n < 3; n++) {
+ if (cache->path->level[n] != ocache->path->level[n]) {
+ ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
+ "cache \"%V\" had previously different levels",
+ &shm_zone->shm.name);
+ return NGX_ERROR;
+ }
+ }
+
+ cache->sh = ocache->sh;
+
+ cache->shpool = ocache->shpool;
+ cache->bsize = ocache->bsize;
+
+ cache->max_size /= cache->bsize;
+
+ if (!cache->sh->cold || cache->sh->loading) {
+ cache->path->loader = NULL;
+ }
+
+ return NGX_OK;
+ }
+
+ cache->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+ if (shm_zone->shm.exists) {
+ cache->sh = cache->shpool->data;
+ cache->bsize = ngx_fs_bsize(cache->path->name.data);
+
+ return NGX_OK;
+ }
+
+ cache->sh = ngx_slab_alloc(cache->shpool, sizeof(ngx_http_file_cache_sh_t));
+ if (cache->sh == NULL) {
+ return NGX_ERROR;
+ }
+
+ cache->shpool->data = cache->sh;
+
+ ngx_rbtree_init(&cache->sh->rbtree, &cache->sh->sentinel,
+ ngx_http_file_cache_rbtree_insert_value);
+
+ ngx_queue_init(&cache->sh->queue);
+
+ cache->sh->cold = 1;
+ cache->sh->loading = 0;
+ cache->sh->size = 0;
+
+ cache->bsize = ngx_fs_bsize(cache->path->name.data);
+
+ cache->max_size /= cache->bsize;
+
+ len = sizeof(" in cache keys zone \"\"") + shm_zone->shm.name.len;
+
+ cache->shpool->log_ctx = ngx_slab_alloc(cache->shpool, len);
+ if (cache->shpool->log_ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_sprintf(cache->shpool->log_ctx, " in cache keys zone \"%V\"%Z",
+ &shm_zone->shm.name);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_file_cache_new(ngx_http_request_t *r)
+{
+ ngx_http_cache_t *c;
+
+ c = ngx_pcalloc(r->pool, sizeof(ngx_http_cache_t));
+ if (c == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_array_init(&c->keys, r->pool, 4, sizeof(ngx_str_t)) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ r->cache = c;
+ c->file.log = r->connection->log;
+ c->file.fd = NGX_INVALID_FILE;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_file_cache_create(ngx_http_request_t *r)
+{
+ ngx_http_cache_t *c;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_file_cache_t *cache;
+
+ c = r->cache;
+ cache = c->file_cache;
+
+ cln = ngx_pool_cleanup_add(r->pool, 0);
+ if (cln == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_http_file_cache_exists(cache, c) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ cln->handler = ngx_http_file_cache_cleanup;
+ cln->data = c;
+
+ if (ngx_http_file_cache_name(r, cache->path) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+void
+ngx_http_file_cache_create_key(ngx_http_request_t *r)
+{
+ size_t len;
+ ngx_str_t *key;
+ ngx_uint_t i;
+ ngx_md5_t md5;
+ ngx_http_cache_t *c;
+
+ c = r->cache;
+
+ len = 0;
+
+ ngx_crc32_init(c->crc32);
+ ngx_md5_init(&md5);
+
+ key = c->keys.elts;
+ for (i = 0; i < c->keys.nelts; i++) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http cache key: \"%V\"", &key[i]);
+
+ len += key[i].len;
+
+ ngx_crc32_update(&c->crc32, key[i].data, key[i].len);
+ ngx_md5_update(&md5, key[i].data, key[i].len);
+ }
+
+ c->header_start = sizeof(ngx_http_file_cache_header_t)
+ + sizeof(ngx_http_file_cache_key) + len + 1;
+
+ ngx_crc32_final(c->crc32);
+ ngx_md5_final(c->key, &md5);
+}
+
+
+ngx_int_t
+ngx_http_file_cache_open(ngx_http_request_t *r)
+{
+ ngx_int_t rc, rv;
+ ngx_uint_t cold, test;
+ ngx_http_cache_t *c;
+ ngx_pool_cleanup_t *cln;
+ ngx_open_file_info_t of;
+ ngx_http_file_cache_t *cache;
+ ngx_http_core_loc_conf_t *clcf;
+
+ c = r->cache;
+
+ if (c->buf) {
+ return ngx_http_file_cache_read(r, c);
+ }
+
+ cache = c->file_cache;
+
+ cln = ngx_pool_cleanup_add(r->pool, 0);
+ if (cln == NULL) {
+ return NGX_ERROR;
+ }
+
+ rc = ngx_http_file_cache_exists(cache, c);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http file cache exists: %i e:%d", rc, c->exists);
+
+ if (rc == NGX_ERROR) {
+ return rc;
+ }
+
+ cln->handler = ngx_http_file_cache_cleanup;
+ cln->data = c;
+
+ if (rc == NGX_AGAIN) {
+ return NGX_HTTP_CACHE_SCARCE;
+ }
+
+ cold = cache->sh->cold;
+
+ if (rc == NGX_OK) {
+
+ if (c->error) {
+ return c->error;
+ }
+
+ c->temp_file = 1;
+ test = c->exists ? 1 : 0;
+ rv = NGX_DECLINED;
+
+ } else { /* rc == NGX_DECLINED */
+
+ if (c->min_uses > 1) {
+
+ if (!cold) {
+ return NGX_HTTP_CACHE_SCARCE;
+ }
+
+ test = 1;
+ rv = NGX_HTTP_CACHE_SCARCE;
+
+ } else {
+ c->temp_file = 1;
+ test = cold ? 1 : 0;
+ rv = NGX_DECLINED;
+ }
+ }
+
+ if (ngx_http_file_cache_name(r, cache->path) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (!test) {
+ return NGX_DECLINED;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+ of.uniq = c->uniq;
+ of.valid = clcf->open_file_cache_valid;
+ of.min_uses = clcf->open_file_cache_min_uses;
+ of.events = clcf->open_file_cache_events;
+ of.directio = NGX_OPEN_FILE_DIRECTIO_OFF;
+ of.read_ahead = clcf->read_ahead;
+
+ if (ngx_open_cached_file(clcf->open_file_cache, &c->file.name, &of, r->pool)
+ != NGX_OK)
+ {
+ switch (of.err) {
+
+ case 0:
+ return NGX_ERROR;
+
+ case NGX_ENOENT:
+ case NGX_ENOTDIR:
+ return rv;
+
+ default:
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err,
+ ngx_open_file_n " \"%s\" failed", c->file.name.data);
+ return NGX_ERROR;
+ }
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http file cache fd: %d", of.fd);
+
+ c->file.fd = of.fd;
+ c->file.log = r->connection->log;
+ c->uniq = of.uniq;
+ c->length = of.size;
+ c->fs_size = (of.fs_size + cache->bsize - 1) / cache->bsize;
+
+ c->buf = ngx_create_temp_buf(r->pool, c->body_start);
+ if (c->buf == NULL) {
+ return NGX_ERROR;
+ }
+
+ return ngx_http_file_cache_read(r, c);
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_read(ngx_http_request_t *r, ngx_http_cache_t *c)
+{
+ time_t now;
+ ssize_t n;
+ ngx_int_t rc;
+ ngx_http_file_cache_t *cache;
+ ngx_http_file_cache_header_t *h;
+
+ n = ngx_http_file_cache_aio_read(r, c);
+
+ if (n < 0) {
+ return n;
+ }
+
+ if ((size_t) n < c->header_start) {
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
+ "cache file \"%s\" is too small", c->file.name.data);
+ return NGX_DECLINED;
+ }
+
+ h = (ngx_http_file_cache_header_t *) c->buf->pos;
+
+ if (h->crc32 != c->crc32) {
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
+ "cache file \"%s\" has md5 collision", c->file.name.data);
+ return NGX_DECLINED;
+ }
+
+ c->buf->last += n;
+
+ c->valid_sec = h->valid_sec;
+ c->last_modified = h->last_modified;
+ c->date = h->date;
+ c->valid_msec = h->valid_msec;
+ c->header_start = h->header_start;
+ c->body_start = h->body_start;
+
+ r->cached = 1;
+
+ cache = c->file_cache;
+
+ if (cache->sh->cold) {
+
+ ngx_shmtx_lock(&cache->shpool->mutex);
+
+ if (!c->node->exists) {
+ c->node->uses = 1;
+ c->node->body_start = c->body_start;
+ c->node->exists = 1;
+ c->node->uniq = c->uniq;
+ c->node->fs_size = c->fs_size;
+
+ cache->sh->size += c->fs_size;
+ }
+
+ ngx_shmtx_unlock(&cache->shpool->mutex);
+ }
+
+ now = ngx_time();
+
+ if (c->valid_sec < now) {
+
+ ngx_shmtx_lock(&cache->shpool->mutex);
+
+ if (c->node->updating) {
+ rc = NGX_HTTP_CACHE_UPDATING;
+
+ } else {
+ c->node->updating = 1;
+ c->updating = 1;
+ rc = NGX_HTTP_CACHE_STALE;
+ }
+
+ ngx_shmtx_unlock(&cache->shpool->mutex);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http file cache expired: %i %T %T",
+ rc, c->valid_sec, now);
+
+ return rc;
+ }
+
+ return NGX_OK;
+}
+
+
+static ssize_t
+ngx_http_file_cache_aio_read(ngx_http_request_t *r, ngx_http_cache_t *c)
+{
+#if (NGX_HAVE_FILE_AIO)
+ ssize_t n;
+ ngx_http_core_loc_conf_t *clcf;
+
+ if (!ngx_file_aio) {
+ goto noaio;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (!clcf->aio) {
+ goto noaio;
+ }
+
+ n = ngx_file_aio_read(&c->file, c->buf->pos, c->body_start, 0, r->pool);
+
+ if (n != NGX_AGAIN) {
+ return n;
+ }
+
+ c->file.aio->data = r;
+ c->file.aio->handler = ngx_http_cache_aio_event_handler;
+
+ r->main->blocked++;
+ r->aio = 1;
+
+ return NGX_AGAIN;
+
+noaio:
+
+#endif
+
+ return ngx_read_file(&c->file, c->buf->pos, c->body_start, 0);
+}
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+static void
+ngx_http_cache_aio_event_handler(ngx_event_t *ev)
+{
+ ngx_event_aio_t *aio;
+ ngx_http_request_t *r;
+
+ aio = ev->data;
+ r = aio->data;
+
+ r->main->blocked--;
+ r->aio = 0;
+
+ r->connection->write->handler(r->connection->write);
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_file_cache_exists(ngx_http_file_cache_t *cache, ngx_http_cache_t *c)
+{
+ ngx_int_t rc;
+ ngx_http_file_cache_node_t *fcn;
+
+ ngx_shmtx_lock(&cache->shpool->mutex);
+
+ fcn = ngx_http_file_cache_lookup(cache, c->key);
+
+ if (fcn) {
+ ngx_queue_remove(&fcn->queue);
+
+ fcn->uses++;
+ fcn->count++;
+
+ if (fcn->error) {
+
+ if (fcn->valid_sec < ngx_time()) {
+ goto renew;
+ }
+
+ rc = NGX_OK;
+
+ goto done;
+ }
+
+ if (fcn->exists || fcn->uses >= c->min_uses) {
+
+ c->exists = fcn->exists;
+ if (fcn->body_start) {
+ c->body_start = fcn->body_start;
+ }
+
+ rc = NGX_OK;
+
+ goto done;
+ }
+
+ rc = NGX_AGAIN;
+
+ goto done;
+ }
+
+ fcn = ngx_slab_alloc_locked(cache->shpool,
+ sizeof(ngx_http_file_cache_node_t));
+ if (fcn == NULL) {
+ ngx_shmtx_unlock(&cache->shpool->mutex);
+
+ (void) ngx_http_file_cache_forced_expire(cache);
+
+ ngx_shmtx_lock(&cache->shpool->mutex);
+
+ fcn = ngx_slab_alloc_locked(cache->shpool,
+ sizeof(ngx_http_file_cache_node_t));
+ if (fcn == NULL) {
+ rc = NGX_ERROR;
+ goto failed;
+ }
+ }
+
+ ngx_memcpy((u_char *) &fcn->node.key, c->key, sizeof(ngx_rbtree_key_t));
+
+ ngx_memcpy(fcn->key, &c->key[sizeof(ngx_rbtree_key_t)],
+ NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t));
+
+ ngx_rbtree_insert(&cache->sh->rbtree, &fcn->node);
+
+ fcn->uses = 1;
+ fcn->count = 1;
+ fcn->updating = 0;
+ fcn->deleting = 0;
+
+renew:
+
+ rc = NGX_DECLINED;
+
+ fcn->valid_msec = 0;
+ fcn->error = 0;
+ fcn->exists = 0;
+ fcn->valid_sec = 0;
+ fcn->uniq = 0;
+ fcn->body_start = 0;
+ fcn->fs_size = 0;
+
+done:
+
+ fcn->expire = ngx_time() + cache->inactive;
+
+ ngx_queue_insert_head(&cache->sh->queue, &fcn->queue);
+
+ c->uniq = fcn->uniq;
+ c->error = fcn->error;
+ c->node = fcn;
+
+failed:
+
+ ngx_shmtx_unlock(&cache->shpool->mutex);
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_name(ngx_http_request_t *r, ngx_path_t *path)
+{
+ u_char *p;
+ ngx_http_cache_t *c;
+
+ c = r->cache;
+
+ c->file.name.len = path->name.len + 1 + path->len
+ + 2 * NGX_HTTP_CACHE_KEY_LEN;
+
+ c->file.name.data = ngx_pnalloc(r->pool, c->file.name.len + 1);
+ if (c->file.name.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(c->file.name.data, path->name.data, path->name.len);
+
+ p = c->file.name.data + path->name.len + 1 + path->len;
+ p = ngx_hex_dump(p, c->key, NGX_HTTP_CACHE_KEY_LEN);
+ *p = '\0';
+
+ ngx_create_hashed_filename(path, c->file.name.data, c->file.name.len);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "cache file: \"%s\"", c->file.name.data);
+
+ return NGX_OK;
+}
+
+
+static ngx_http_file_cache_node_t *
+ngx_http_file_cache_lookup(ngx_http_file_cache_t *cache, u_char *key)
+{
+ ngx_int_t rc;
+ ngx_rbtree_key_t node_key;
+ ngx_rbtree_node_t *node, *sentinel;
+ ngx_http_file_cache_node_t *fcn;
+
+ ngx_memcpy((u_char *) &node_key, key, sizeof(ngx_rbtree_key_t));
+
+ node = cache->sh->rbtree.root;
+ sentinel = cache->sh->rbtree.sentinel;
+
+ while (node != sentinel) {
+
+ if (node_key < node->key) {
+ node = node->left;
+ continue;
+ }
+
+ if (node_key > node->key) {
+ node = node->right;
+ continue;
+ }
+
+ /* node_key == node->key */
+
+ do {
+ fcn = (ngx_http_file_cache_node_t *) node;
+
+ rc = ngx_memcmp(&key[sizeof(ngx_rbtree_key_t)], fcn->key,
+ NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t));
+
+ if (rc == 0) {
+ return fcn;
+ }
+
+ node = (rc < 0) ? node->left : node->right;
+
+ } while (node != sentinel && node_key == node->key);
+
+ break;
+ }
+
+ /* not found */
+
+ return NULL;
+}
+
+
+static void
+ngx_http_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp,
+ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+ ngx_rbtree_node_t **p;
+ ngx_http_file_cache_node_t *cn, *cnt;
+
+ for ( ;; ) {
+
+ if (node->key < temp->key) {
+
+ p = &temp->left;
+
+ } else if (node->key > temp->key) {
+
+ p = &temp->right;
+
+ } else { /* node->key == temp->key */
+
+ cn = (ngx_http_file_cache_node_t *) node;
+ cnt = (ngx_http_file_cache_node_t *) temp;
+
+ p = (ngx_memcmp(cn->key, cnt->key,
+ NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t))
+ < 0)
+ ? &temp->left : &temp->right;
+ }
+
+ if (*p == sentinel) {
+ break;
+ }
+
+ temp = *p;
+ }
+
+ *p = node;
+ node->parent = temp;
+ node->left = sentinel;
+ node->right = sentinel;
+ ngx_rbt_red(node);
+}
+
+
+void
+ngx_http_file_cache_set_header(ngx_http_request_t *r, u_char *buf)
+{
+ ngx_http_file_cache_header_t *h = (ngx_http_file_cache_header_t *) buf;
+
+ u_char *p;
+ ngx_str_t *key;
+ ngx_uint_t i;
+ ngx_http_cache_t *c;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http file cache set header");
+
+ c = r->cache;
+
+ h->valid_sec = c->valid_sec;
+ h->last_modified = c->last_modified;
+ h->date = c->date;
+ h->crc32 = c->crc32;
+ h->valid_msec = (u_short) c->valid_msec;
+ h->header_start = (u_short) c->header_start;
+ h->body_start = (u_short) c->body_start;
+
+ p = buf + sizeof(ngx_http_file_cache_header_t);
+
+ p = ngx_cpymem(p, ngx_http_file_cache_key, sizeof(ngx_http_file_cache_key));
+
+ key = c->keys.elts;
+ for (i = 0; i < c->keys.nelts; i++) {
+ p = ngx_copy(p, key[i].data, key[i].len);
+ }
+
+ *p = LF;
+}
+
+
+void
+ngx_http_file_cache_update(ngx_http_request_t *r, ngx_temp_file_t *tf)
+{
+ off_t fs_size;
+ ngx_int_t rc;
+ ngx_file_uniq_t uniq;
+ ngx_file_info_t fi;
+ ngx_http_cache_t *c;
+ ngx_ext_rename_file_t ext;
+ ngx_http_file_cache_t *cache;
+
+ c = r->cache;
+
+ if (c->updated) {
+ return;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http file cache update");
+
+ c->updated = 1;
+ c->updating = 0;
+
+ cache = c->file_cache;
+
+ uniq = 0;
+ fs_size = 0;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http file cache rename: \"%s\" to \"%s\"",
+ tf->file.name.data, c->file.name.data);
+
+ ext.access = NGX_FILE_OWNER_ACCESS;
+ ext.path_access = NGX_FILE_OWNER_ACCESS;
+ ext.time = -1;
+ ext.create_path = 1;
+ ext.delete_file = 1;
+ ext.log = r->connection->log;
+
+ rc = ngx_ext_rename_file(&tf->file.name, &c->file.name, &ext);
+
+ if (rc == NGX_OK) {
+
+ if (ngx_fd_info(tf->file.fd, &fi) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+ ngx_fd_info_n " \"%s\" failed", tf->file.name.data);
+
+ rc = NGX_ERROR;
+
+ } else {
+ uniq = ngx_file_uniq(&fi);
+ fs_size = (ngx_file_fs_size(&fi) + cache->bsize - 1) / cache->bsize;
+ }
+ }
+
+ ngx_shmtx_lock(&cache->shpool->mutex);
+
+ c->node->count--;
+ c->node->uniq = uniq;
+ c->node->body_start = c->body_start;
+
+ cache->sh->size += fs_size - c->node->fs_size;
+ c->node->fs_size = fs_size;
+
+ if (rc == NGX_OK) {
+ c->node->exists = 1;
+ }
+
+ c->node->updating = 0;
+
+ ngx_shmtx_unlock(&cache->shpool->mutex);
+}
+
+
+ngx_int_t
+ngx_http_cache_send(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_chain_t out;
+ ngx_http_cache_t *c;
+
+ c = r->cache;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http file cache send: %s", c->file.name.data);
+
+ /* 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;
+ }
+
+ r->header_only = (c->length - c->body_start) == 0;
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ return rc;
+ }
+
+ b->file_pos = c->body_start;
+ b->file_last = c->length;
+
+ b->in_file = 1;
+ b->last_buf = (r == r->main) ? 1: 0;
+ b->last_in_chain = 1;
+
+ b->file->fd = c->file.fd;
+ b->file->name = c->file.name;
+ b->file->log = r->connection->log;
+
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_output_filter(r, &out);
+}
+
+
+void
+ngx_http_file_cache_free(ngx_http_cache_t *c, ngx_temp_file_t *tf)
+{
+ ngx_http_file_cache_t *cache;
+ ngx_http_file_cache_node_t *fcn;
+
+ if (c->updated || c->node == NULL) {
+ return;
+ }
+
+ cache = c->file_cache;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->file.log, 0,
+ "http file cache free, fd: %d", c->file.fd);
+
+ ngx_shmtx_lock(&cache->shpool->mutex);
+
+ fcn = c->node;
+ fcn->count--;
+
+ if (c->updating) {
+ fcn->updating = 0;
+ }
+
+ if (c->error) {
+ fcn->error = c->error;
+
+ if (c->valid_sec) {
+ fcn->valid_sec = c->valid_sec;
+ fcn->valid_msec = c->valid_msec;
+ }
+
+ } else if (!fcn->exists && fcn->count == 0 && c->min_uses == 1) {
+ ngx_queue_remove(&fcn->queue);
+ ngx_rbtree_delete(&cache->sh->rbtree, &fcn->node);
+ ngx_slab_free_locked(cache->shpool, fcn);
+ c->node = NULL;
+ }
+
+ ngx_shmtx_unlock(&cache->shpool->mutex);
+
+ c->updated = 1;
+ c->updating = 0;
+
+ if (c->temp_file) {
+ if (tf && tf->file.fd != NGX_INVALID_FILE) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->file.log, 0,
+ "http file cache incomplete: \"%s\"",
+ tf->file.name.data);
+
+ if (ngx_delete_file(tf->file.name.data) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_CRIT, c->file.log, ngx_errno,
+ ngx_delete_file_n " \"%s\" failed",
+ tf->file.name.data);
+ }
+ }
+ }
+}
+
+
+static void
+ngx_http_file_cache_cleanup(void *data)
+{
+ ngx_http_cache_t *c = data;
+
+ if (c->updated) {
+ return;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->file.log, 0,
+ "http file cache cleanup");
+
+ if (c->updating) {
+ ngx_log_error(NGX_LOG_ALERT, c->file.log, 0,
+ "stalled cache updating, error:%ui", c->error);
+ }
+
+ ngx_http_file_cache_free(c, NULL);
+}
+
+
+static time_t
+ngx_http_file_cache_forced_expire(ngx_http_file_cache_t *cache)
+{
+ u_char *name;
+ size_t len;
+ time_t wait;
+ ngx_uint_t tries;
+ ngx_path_t *path;
+ ngx_queue_t *q;
+ ngx_http_file_cache_node_t *fcn;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+ "http file cache forced expire");
+
+ path = cache->path;
+ len = path->name.len + 1 + path->len + 2 * NGX_HTTP_CACHE_KEY_LEN;
+
+ name = ngx_alloc(len + 1, ngx_cycle->log);
+ if (name == NULL) {
+ return 10;
+ }
+
+ ngx_memcpy(name, path->name.data, path->name.len);
+
+ wait = 10;
+ tries = 20;
+
+ ngx_shmtx_lock(&cache->shpool->mutex);
+
+ for (q = ngx_queue_last(&cache->sh->queue);
+ q != ngx_queue_sentinel(&cache->sh->queue);
+ q = ngx_queue_prev(q))
+ {
+ fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);
+
+ ngx_log_debug6(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+ "http file cache forced expire: #%d %d %02xd%02xd%02xd%02xd",
+ fcn->count, fcn->exists,
+ fcn->key[0], fcn->key[1], fcn->key[2], fcn->key[3]);
+
+ if (fcn->count == 0) {
+ ngx_http_file_cache_delete(cache, q, name);
+ wait = 0;
+
+ } else {
+ if (--tries) {
+ continue;
+ }
+
+ wait = 1;
+ }
+
+ break;
+ }
+
+ ngx_shmtx_unlock(&cache->shpool->mutex);
+
+ ngx_free(name);
+
+ return wait;
+}
+
+
+static time_t
+ngx_http_file_cache_expire(ngx_http_file_cache_t *cache)
+{
+ u_char *name, *p;
+ size_t len;
+ time_t now, wait;
+ ngx_path_t *path;
+ ngx_queue_t *q;
+ ngx_http_file_cache_node_t *fcn;
+ u_char key[2 * NGX_HTTP_CACHE_KEY_LEN];
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+ "http file cache expire");
+
+ path = cache->path;
+ len = path->name.len + 1 + path->len + 2 * NGX_HTTP_CACHE_KEY_LEN;
+
+ name = ngx_alloc(len + 1, ngx_cycle->log);
+ if (name == NULL) {
+ return 10;
+ }
+
+ ngx_memcpy(name, path->name.data, path->name.len);
+
+ now = ngx_time();
+
+ ngx_shmtx_lock(&cache->shpool->mutex);
+
+ for ( ;; ) {
+
+ if (ngx_queue_empty(&cache->sh->queue)) {
+ wait = 10;
+ break;
+ }
+
+ q = ngx_queue_last(&cache->sh->queue);
+
+ fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);
+
+ wait = fcn->expire - now;
+
+ if (wait > 0) {
+ wait = wait > 10 ? 10 : wait;
+ break;
+ }
+
+ ngx_log_debug6(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+ "http file cache expire: #%d %d %02xd%02xd%02xd%02xd",
+ fcn->count, fcn->exists,
+ fcn->key[0], fcn->key[1], fcn->key[2], fcn->key[3]);
+
+ if (fcn->count == 0) {
+ ngx_http_file_cache_delete(cache, q, name);
+ continue;
+ }
+
+ if (fcn->deleting) {
+ wait = 1;
+ break;
+ }
+
+ p = ngx_hex_dump(key, (u_char *) &fcn->node.key,
+ sizeof(ngx_rbtree_key_t));
+ len = NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t);
+ (void) ngx_hex_dump(p, fcn->key, len);
+
+ /*
+ * abnormally exited workers may leave locked cache entries,
+ * and although it may be safe to remove them completely,
+ * we prefer to remove them from inactive queue and rbtree
+ * only, and to allow other leaks
+ */
+
+ ngx_queue_remove(q);
+ ngx_rbtree_delete(&cache->sh->rbtree, &fcn->node);
+
+ ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+ "ignore long locked inactive cache entry %*s, count:%d",
+ 2 * NGX_HTTP_CACHE_KEY_LEN, key, fcn->count);
+ }
+
+ ngx_shmtx_unlock(&cache->shpool->mutex);
+
+ ngx_free(name);
+
+ return wait;
+}
+
+
+static void
+ngx_http_file_cache_delete(ngx_http_file_cache_t *cache, ngx_queue_t *q,
+ u_char *name)
+{
+ u_char *p;
+ size_t len;
+ ngx_path_t *path;
+ ngx_http_file_cache_node_t *fcn;
+
+ fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);
+
+ if (fcn->exists) {
+ cache->sh->size -= fcn->fs_size;
+
+ path = cache->path;
+ p = name + path->name.len + 1 + path->len;
+ p = ngx_hex_dump(p, (u_char *) &fcn->node.key,
+ sizeof(ngx_rbtree_key_t));
+ len = NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t);
+ p = ngx_hex_dump(p, fcn->key, len);
+ *p = '\0';
+
+ fcn->count++;
+ fcn->deleting = 1;
+ ngx_shmtx_unlock(&cache->shpool->mutex);
+
+ len = path->name.len + 1 + path->len + 2 * NGX_HTTP_CACHE_KEY_LEN;
+ ngx_create_hashed_filename(path, name, len);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+ "http file cache expire: \"%s\"", name);
+
+ if (ngx_delete_file(name) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno,
+ ngx_delete_file_n " \"%s\" failed", name);
+ }
+
+ ngx_shmtx_lock(&cache->shpool->mutex);
+ fcn->count--;
+ fcn->deleting = 0;
+ }
+
+ if (fcn->count == 0) {
+ ngx_queue_remove(q);
+ ngx_rbtree_delete(&cache->sh->rbtree, &fcn->node);
+ ngx_slab_free_locked(cache->shpool, fcn);
+ }
+}
+
+
+static time_t
+ngx_http_file_cache_manager(void *data)
+{
+ ngx_http_file_cache_t *cache = data;
+
+ off_t size;
+ time_t next, wait;
+
+ next = ngx_http_file_cache_expire(cache);
+
+ cache->last = ngx_current_msec;
+ cache->files = 0;
+
+ for ( ;; ) {
+ ngx_shmtx_lock(&cache->shpool->mutex);
+
+ size = cache->sh->size;
+
+ ngx_shmtx_unlock(&cache->shpool->mutex);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+ "http file cache size: %O", size);
+
+ if (size < cache->max_size) {
+ return next;
+ }
+
+ wait = ngx_http_file_cache_forced_expire(cache);
+
+ if (wait > 0) {
+ return wait;
+ }
+
+ if (ngx_quit || ngx_terminate) {
+ return next;
+ }
+ }
+}
+
+
+static void
+ngx_http_file_cache_loader(void *data)
+{
+ ngx_http_file_cache_t *cache = data;
+
+ ngx_tree_ctx_t tree;
+
+ if (!cache->sh->cold || cache->sh->loading) {
+ return;
+ }
+
+ if (!ngx_atomic_cmp_set(&cache->sh->loading, 0, ngx_pid)) {
+ return;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+ "http file cache loader");
+
+ tree.init_handler = NULL;
+ tree.file_handler = ngx_http_file_cache_manage_file;
+ tree.pre_tree_handler = ngx_http_file_cache_noop;
+ tree.post_tree_handler = ngx_http_file_cache_noop;
+ tree.spec_handler = ngx_http_file_cache_delete_file;
+ tree.data = cache;
+ tree.alloc = 0;
+ tree.log = ngx_cycle->log;
+
+ cache->last = ngx_current_msec;
+ cache->files = 0;
+
+ if (ngx_walk_tree(&tree, &cache->path->name) == NGX_ABORT) {
+ cache->sh->loading = 0;
+ return;
+ }
+
+ cache->sh->cold = 0;
+ cache->sh->loading = 0;
+
+ ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
+ "http file cache: %V %.3fM, bsize: %uz",
+ &cache->path->name,
+ ((double) cache->sh->size * cache->bsize) / (1024 * 1024),
+ cache->bsize);
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_loader_sleep(ngx_http_file_cache_t *cache)
+{
+ ngx_msec_t elapsed;
+
+ if (cache->files++ > 100) {
+
+ ngx_time_update();
+
+ elapsed = ngx_abs((ngx_msec_int_t) (ngx_current_msec - cache->last));
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+ "http file cache manager time: %M", elapsed);
+
+ if (elapsed > 200) {
+
+ /*
+ * if processing 100 files takes more than 200ms,
+ * it seems that many operations require disk i/o,
+ * therefore sleep 200ms
+ */
+
+ ngx_msleep(200);
+
+ ngx_time_update();
+ }
+
+ cache->last = ngx_current_msec;
+ cache->files = 0;
+ }
+
+ return (ngx_quit || ngx_terminate) ? NGX_ABORT : NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_manage_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+ ngx_http_file_cache_t *cache;
+
+ cache = ctx->data;
+
+ if (ngx_http_file_cache_add_file(ctx, path) != NGX_OK) {
+ (void) ngx_http_file_cache_delete_file(ctx, path);
+ }
+
+ return ngx_http_file_cache_loader_sleep(cache);
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_add_file(ngx_tree_ctx_t *ctx, ngx_str_t *name)
+{
+ u_char *p;
+ ngx_int_t n;
+ ngx_uint_t i;
+ ngx_http_cache_t c;
+ ngx_http_file_cache_t *cache;
+
+ if (name->len < 2 * NGX_HTTP_CACHE_KEY_LEN) {
+ return NGX_ERROR;
+ }
+
+ if (ctx->size < (off_t) sizeof(ngx_http_file_cache_header_t)) {
+ ngx_log_error(NGX_LOG_CRIT, ctx->log, 0,
+ "cache file \"%s\" is too small", name->data);
+ return NGX_ERROR;
+ }
+
+ ngx_memzero(&c, sizeof(ngx_http_cache_t));
+ cache = ctx->data;
+
+ c.length = ctx->size;
+ c.fs_size = (ctx->fs_size + cache->bsize - 1) / cache->bsize;
+
+ p = &name->data[name->len - 2 * NGX_HTTP_CACHE_KEY_LEN];
+
+ for (i = 0; i < NGX_HTTP_CACHE_KEY_LEN; i++) {
+ n = ngx_hextoi(p, 2);
+
+ if (n == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ p += 2;
+
+ c.key[i] = (u_char) n;
+ }
+
+ return ngx_http_file_cache_add(cache, &c);
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_add(ngx_http_file_cache_t *cache, ngx_http_cache_t *c)
+{
+ ngx_http_file_cache_node_t *fcn;
+
+ ngx_shmtx_lock(&cache->shpool->mutex);
+
+ fcn = ngx_http_file_cache_lookup(cache, c->key);
+
+ if (fcn == NULL) {
+
+ fcn = ngx_slab_alloc_locked(cache->shpool,
+ sizeof(ngx_http_file_cache_node_t));
+ if (fcn == NULL) {
+ ngx_shmtx_unlock(&cache->shpool->mutex);
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy((u_char *) &fcn->node.key, c->key, sizeof(ngx_rbtree_key_t));
+
+ ngx_memcpy(fcn->key, &c->key[sizeof(ngx_rbtree_key_t)],
+ NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t));
+
+ ngx_rbtree_insert(&cache->sh->rbtree, &fcn->node);
+
+ fcn->uses = 1;
+ fcn->count = 0;
+ fcn->valid_msec = 0;
+ fcn->error = 0;
+ fcn->exists = 1;
+ fcn->updating = 0;
+ fcn->deleting = 0;
+ fcn->uniq = 0;
+ fcn->valid_sec = 0;
+ fcn->body_start = 0;
+ fcn->fs_size = c->fs_size;
+
+ cache->sh->size += c->fs_size;
+
+ } else {
+ ngx_queue_remove(&fcn->queue);
+ }
+
+ fcn->expire = ngx_time() + cache->inactive;
+
+ ngx_queue_insert_head(&cache->sh->queue, &fcn->queue);
+
+ ngx_shmtx_unlock(&cache->shpool->mutex);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "http file cache delete: \"%s\"", path->data);
+
+ if (ngx_delete_file(path->data) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+ ngx_delete_file_n " \"%s\" failed", path->data);
+ }
+
+ return NGX_OK;
+}
+
+
+time_t
+ngx_http_file_cache_valid(ngx_array_t *cache_valid, ngx_uint_t status)
+{
+ ngx_uint_t i;
+ ngx_http_cache_valid_t *valid;
+
+ if (cache_valid == NULL) {
+ return 0;
+ }
+
+ valid = cache_valid->elts;
+ for (i = 0; i < cache_valid->nelts; i++) {
+
+ if (valid[i].status == 0) {
+ return valid[i].valid;
+ }
+
+ if (valid[i].status == status) {
+ return valid[i].valid;
+ }
+ }
+
+ return 0;
+}
+
+
+char *
+ngx_http_file_cache_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ off_t max_size;
+ u_char *last, *p;
+ time_t inactive;
+ ssize_t size;
+ ngx_str_t s, name, *value;
+ ngx_uint_t i, n;
+ ngx_http_file_cache_t *cache;
+
+ cache = ngx_pcalloc(cf->pool, sizeof(ngx_http_file_cache_t));
+ if (cache == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ cache->path = ngx_pcalloc(cf->pool, sizeof(ngx_path_t));
+ if (cache->path == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ inactive = 600;
+
+ name.len = 0;
+ size = 0;
+ max_size = NGX_MAX_OFF_T_VALUE;
+
+ value = cf->args->elts;
+
+ cache->path->name = value[1];
+
+ if (cache->path->name.data[cache->path->name.len - 1] == '/') {
+ cache->path->name.len--;
+ }
+
+ if (ngx_conf_full_name(cf->cycle, &cache->path->name, 0) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 2; i < cf->args->nelts; i++) {
+
+ if (ngx_strncmp(value[i].data, "levels=", 7) == 0) {
+
+ p = value[i].data + 7;
+ last = value[i].data + value[i].len;
+
+ for (n = 0; n < 3 && p < last; n++) {
+
+ if (*p > '0' && *p < '3') {
+
+ cache->path->level[n] = *p++ - '0';
+ cache->path->len += cache->path->level[n] + 1;
+
+ if (p == last) {
+ break;
+ }
+
+ if (*p++ == ':' && n < 2 && p != last) {
+ continue;
+ }
+
+ goto invalid_levels;
+ }
+
+ goto invalid_levels;
+ }
+
+ if (cache->path->len < 10 + 3) {
+ continue;
+ }
+
+ invalid_levels:
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid \"levels\" \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_strncmp(value[i].data, "keys_zone=", 10) == 0) {
+
+ name.data = value[i].data + 10;
+
+ p = (u_char *) ngx_strchr(name.data, ':');
+
+ if (p) {
+ *p = '\0';
+
+ name.len = p - name.data;
+
+ p++;
+
+ s.len = value[i].data + value[i].len - p;
+ s.data = p;
+
+ size = ngx_parse_size(&s);
+ if (size > 8191) {
+ continue;
+ }
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid keys zone size \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) {
+
+ s.len = value[i].len - 9;
+ s.data = value[i].data + 9;
+
+ inactive = ngx_parse_time(&s, 1);
+ if (inactive < 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid inactive value \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "max_size=", 9) == 0) {
+
+ s.len = value[i].len - 9;
+ s.data = value[i].data + 9;
+
+ max_size = ngx_parse_offset(&s);
+ if (max_size < 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid max_size value \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (name.len == 0 || size == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"%V\" must have \"keys_zone\" parameter",
+ &cmd->name);
+ return NGX_CONF_ERROR;
+ }
+
+ cache->path->manager = ngx_http_file_cache_manager;
+ cache->path->loader = ngx_http_file_cache_loader;
+ cache->path->data = cache;
+ cache->path->conf_file = cf->conf_file->file.name.data;
+ cache->path->line = cf->conf_file->line;
+
+ if (ngx_add_path(cf, &cache->path) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ cache->shm_zone = ngx_shared_memory_add(cf, &name, size, cmd->post);
+ if (cache->shm_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cache->shm_zone->data) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "duplicate zone \"%V\"", &name);
+ return NGX_CONF_ERROR;
+ }
+
+
+ cache->shm_zone->init = ngx_http_file_cache_init;
+ cache->shm_zone->data = cache;
+
+ cache->inactive = inactive;
+ cache->max_size = max_size;
+
+ return NGX_CONF_OK;
+}
+
+
+char *
+ngx_http_file_cache_valid_set_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf)
+{
+ char *p = conf;
+
+ time_t valid;
+ ngx_str_t *value;
+ ngx_uint_t i, n, status;
+ ngx_array_t **a;
+ ngx_http_cache_valid_t *v;
+ static ngx_uint_t statuses[] = { 200, 301, 302 };
+
+ a = (ngx_array_t **) (p + cmd->offset);
+
+ if (*a == NGX_CONF_UNSET_PTR) {
+ *a = ngx_array_create(cf->pool, 1, sizeof(ngx_http_cache_valid_t));
+ if (*a == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ value = cf->args->elts;
+ n = cf->args->nelts - 1;
+
+ valid = ngx_parse_time(&value[n], 1);
+ if (valid < 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid time value \"%V\"", &value[n]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (n == 1) {
+
+ for (i = 0; i < 3; i++) {
+ v = ngx_array_push(*a);
+ if (v == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ v->status = statuses[i];
+ v->valid = valid;
+ }
+
+ return NGX_CONF_OK;
+ }
+
+ for (i = 1; i < n; i++) {
+
+ if (ngx_strcmp(value[i].data, "any") == 0) {
+
+ status = 0;
+
+ } else {
+
+ status = ngx_atoi(value[i].data, value[i].len);
+ if (status < 100) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid status \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ v = ngx_array_push(*a);
+ if (v == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ v->status = status;
+ v->valid = valid;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+ngx_int_t
+ngx_http_cache(ngx_http_request_t *r, ngx_array_t *no_cache)
+{
+ ngx_str_t val;
+ ngx_uint_t i;
+ ngx_http_complex_value_t *cv;
+
+ cv = no_cache->elts;
+
+ for (i = 0; i < no_cache->nelts; i++) {
+ if (ngx_http_complex_value(r, &cv[i], &val) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (val.len && val.data[0] != '0') {
+ return NGX_DECLINED;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+char *
+ngx_http_no_cache_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *p = conf;
+
+ ngx_str_t *value;
+ ngx_uint_t i;
+ ngx_array_t **a;
+ ngx_http_complex_value_t *cv;
+ ngx_http_compile_complex_value_t ccv;
+
+ a = (ngx_array_t **) (p + cmd->offset);
+
+ if (*a == NGX_CONF_UNSET_PTR) {
+ *a = ngx_array_create(cf->pool, 1, sizeof(ngx_http_complex_value_t));
+ if (*a == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+ cv = ngx_array_push(*a);
+ if (cv == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[i];
+ ccv.complex_value = cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/http/ngx_http_header_filter_module.c b/usr.sbin/nginx/src/http/ngx_http_header_filter_module.c
new file mode 100644
index 00000000000..bccf63624fa
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_header_filter_module.c
@@ -0,0 +1,626 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+
+static ngx_int_t ngx_http_header_filter_init(ngx_conf_t *cf);
+static ngx_int_t ngx_http_header_filter(ngx_http_request_t *r);
+
+
+static ngx_http_module_t ngx_http_header_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_header_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL, /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_header_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_header_filter_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static char ngx_http_server_string[] = "Server: nginx" CRLF;
+static char ngx_http_server_full_string[] = "Server: " NGINX_VER CRLF;
+
+
+static ngx_str_t ngx_http_status_lines[] = {
+
+ ngx_string("200 OK"),
+ ngx_string("201 Created"),
+ ngx_string("202 Accepted"),
+ ngx_null_string, /* "203 Non-Authoritative Information" */
+ ngx_string("204 No Content"),
+ ngx_null_string, /* "205 Reset Content" */
+ ngx_string("206 Partial Content"),
+
+ /* ngx_null_string, */ /* "207 Multi-Status" */
+
+#define NGX_HTTP_LAST_LEVEL_200 207
+#define NGX_HTTP_LEVEL_200 (NGX_HTTP_LAST_LEVEL_200 - 200)
+
+ /* ngx_null_string, */ /* "300 Multiple Choices" */
+
+ ngx_string("301 Moved Permanently"),
+ ngx_string("302 Moved Temporarily"),
+ ngx_string("303 See Other"),
+ ngx_string("304 Not Modified"),
+
+ /* ngx_null_string, */ /* "305 Use Proxy" */
+ /* ngx_null_string, */ /* "306 unused" */
+ /* ngx_null_string, */ /* "307 Temporary Redirect" */
+
+#define NGX_HTTP_LAST_LEVEL_300 305
+#define NGX_HTTP_LEVEL_300 (NGX_HTTP_LAST_LEVEL_300 - 301)
+
+ ngx_string("400 Bad Request"),
+ ngx_string("401 Unauthorized"),
+ ngx_string("402 Payment Required"),
+ ngx_string("403 Forbidden"),
+ ngx_string("404 Not Found"),
+ ngx_string("405 Not Allowed"),
+ ngx_string("406 Not Acceptable"),
+ ngx_null_string, /* "407 Proxy Authentication Required" */
+ ngx_string("408 Request Time-out"),
+ ngx_string("409 Conflict"),
+ ngx_string("410 Gone"),
+ ngx_string("411 Length Required"),
+ ngx_string("412 Precondition Failed"),
+ ngx_string("413 Request Entity Too Large"),
+ ngx_null_string, /* "414 Request-URI Too Large", but we never send it
+ * because we treat such requests as the HTTP/0.9
+ * requests and send only a body without a header
+ */
+ ngx_string("415 Unsupported Media Type"),
+ ngx_string("416 Requested Range Not Satisfiable"),
+
+ /* ngx_null_string, */ /* "417 Expectation Failed" */
+ /* ngx_null_string, */ /* "418 unused" */
+ /* ngx_null_string, */ /* "419 unused" */
+ /* ngx_null_string, */ /* "420 unused" */
+ /* ngx_null_string, */ /* "421 unused" */
+ /* ngx_null_string, */ /* "422 Unprocessable Entity" */
+ /* ngx_null_string, */ /* "423 Locked" */
+ /* ngx_null_string, */ /* "424 Failed Dependency" */
+
+#define NGX_HTTP_LAST_LEVEL_400 417
+#define NGX_HTTP_LEVEL_400 (NGX_HTTP_LAST_LEVEL_400 - 400)
+
+ ngx_string("500 Internal Server Error"),
+ ngx_string("501 Method Not Implemented"),
+ ngx_string("502 Bad Gateway"),
+ ngx_string("503 Service Temporarily Unavailable"),
+ ngx_string("504 Gateway Time-out"),
+
+ ngx_null_string, /* "505 HTTP Version Not Supported" */
+ ngx_null_string, /* "506 Variant Also Negotiates" */
+ ngx_string("507 Insufficient Storage"),
+ /* ngx_null_string, */ /* "508 unused" */
+ /* ngx_null_string, */ /* "509 unused" */
+ /* ngx_null_string, */ /* "510 Not Extended" */
+
+#define NGX_HTTP_LAST_LEVEL_500 508
+
+};
+
+
+ngx_http_header_out_t ngx_http_headers_out[] = {
+ { ngx_string("Server"), offsetof(ngx_http_headers_out_t, server) },
+ { ngx_string("Date"), offsetof(ngx_http_headers_out_t, date) },
+ { ngx_string("Content-Length"),
+ offsetof(ngx_http_headers_out_t, content_length) },
+ { ngx_string("Content-Encoding"),
+ offsetof(ngx_http_headers_out_t, content_encoding) },
+ { ngx_string("Location"), offsetof(ngx_http_headers_out_t, location) },
+ { ngx_string("Last-Modified"),
+ offsetof(ngx_http_headers_out_t, last_modified) },
+ { ngx_string("Accept-Ranges"),
+ offsetof(ngx_http_headers_out_t, accept_ranges) },
+ { ngx_string("Expires"), offsetof(ngx_http_headers_out_t, expires) },
+ { ngx_string("Cache-Control"),
+ offsetof(ngx_http_headers_out_t, cache_control) },
+ { ngx_string("ETag"), offsetof(ngx_http_headers_out_t, etag) },
+
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_int_t
+ngx_http_header_filter(ngx_http_request_t *r)
+{
+ u_char *p;
+ size_t len;
+ ngx_str_t host, *status_line;
+ ngx_buf_t *b;
+ ngx_uint_t status, i, port;
+ ngx_chain_t out;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header;
+ ngx_connection_t *c;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_core_srv_conf_t *cscf;
+ struct sockaddr_in *sin;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+#endif
+ u_char addr[NGX_SOCKADDR_STRLEN];
+
+ if (r->header_sent) {
+ return NGX_OK;
+ }
+
+ r->header_sent = 1;
+
+ if (r != r->main) {
+ return NGX_OK;
+ }
+
+ if (r->http_version < NGX_HTTP_VERSION_10) {
+ return NGX_OK;
+ }
+
+ if (r->method == NGX_HTTP_HEAD) {
+ r->header_only = 1;
+ }
+
+ if (r->headers_out.last_modified_time != -1) {
+ if (r->headers_out.status != NGX_HTTP_OK
+ && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT
+ && r->headers_out.status != NGX_HTTP_NOT_MODIFIED)
+ {
+ r->headers_out.last_modified_time = -1;
+ r->headers_out.last_modified = NULL;
+ }
+ }
+
+ len = sizeof("HTTP/1.x ") - 1 + sizeof(CRLF) - 1
+ /* the end of the header */
+ + sizeof(CRLF) - 1;
+
+ /* status line */
+
+ if (r->headers_out.status_line.len) {
+ len += r->headers_out.status_line.len;
+ status_line = &r->headers_out.status_line;
+#if (NGX_SUPPRESS_WARN)
+ status = 0;
+#endif
+
+ } else {
+
+ status = r->headers_out.status;
+
+ if (status >= NGX_HTTP_OK
+ && status < NGX_HTTP_LAST_LEVEL_200)
+ {
+ /* 2XX */
+
+ if (status == NGX_HTTP_NO_CONTENT) {
+ r->header_only = 1;
+ ngx_str_null(&r->headers_out.content_type);
+ r->headers_out.last_modified_time = -1;
+ r->headers_out.last_modified = NULL;
+ r->headers_out.content_length = NULL;
+ r->headers_out.content_length_n = -1;
+ }
+
+ status -= NGX_HTTP_OK;
+ status_line = &ngx_http_status_lines[status];
+ len += ngx_http_status_lines[status].len;
+
+ } else if (status >= NGX_HTTP_MOVED_PERMANENTLY
+ && status < NGX_HTTP_LAST_LEVEL_300)
+ {
+ /* 3XX */
+
+ if (status == NGX_HTTP_NOT_MODIFIED) {
+ r->header_only = 1;
+ }
+
+ status = status - NGX_HTTP_MOVED_PERMANENTLY + NGX_HTTP_LEVEL_200;
+ status_line = &ngx_http_status_lines[status];
+ len += ngx_http_status_lines[status].len;
+
+ } else if (status >= NGX_HTTP_BAD_REQUEST
+ && status < NGX_HTTP_LAST_LEVEL_400)
+ {
+ /* 4XX */
+ status = status - NGX_HTTP_BAD_REQUEST
+ + NGX_HTTP_LEVEL_200
+ + NGX_HTTP_LEVEL_300;
+
+ status_line = &ngx_http_status_lines[status];
+ len += ngx_http_status_lines[status].len;
+
+ } else if (status >= NGX_HTTP_INTERNAL_SERVER_ERROR
+ && status < NGX_HTTP_LAST_LEVEL_500)
+ {
+ /* 5XX */
+ status = status - NGX_HTTP_INTERNAL_SERVER_ERROR
+ + NGX_HTTP_LEVEL_200
+ + NGX_HTTP_LEVEL_300
+ + NGX_HTTP_LEVEL_400;
+
+ status_line = &ngx_http_status_lines[status];
+ len += ngx_http_status_lines[status].len;
+
+ } else {
+ len += NGX_INT_T_LEN;
+ status_line = NULL;
+ }
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (r->headers_out.server == NULL) {
+ len += clcf->server_tokens ? sizeof(ngx_http_server_full_string) - 1:
+ sizeof(ngx_http_server_string) - 1;
+ }
+
+ if (r->headers_out.date == NULL) {
+ len += sizeof("Date: Mon, 28 Sep 1970 06:00:00 GMT" CRLF) - 1;
+ }
+
+ if (r->headers_out.content_type.len) {
+ len += sizeof("Content-Type: ") - 1
+ + r->headers_out.content_type.len + 2;
+
+ if (r->headers_out.content_type_len == r->headers_out.content_type.len
+ && r->headers_out.charset.len)
+ {
+ len += sizeof("; charset=") - 1 + r->headers_out.charset.len;
+ }
+ }
+
+ if (r->headers_out.content_length == NULL
+ && r->headers_out.content_length_n >= 0)
+ {
+ len += sizeof("Content-Length: ") - 1 + NGX_OFF_T_LEN + 2;
+ }
+
+ if (r->headers_out.last_modified == NULL
+ && r->headers_out.last_modified_time != -1)
+ {
+ len += sizeof("Last-Modified: Mon, 28 Sep 1970 06:00:00 GMT" CRLF) - 1;
+ }
+
+ c = r->connection;
+
+ if (r->headers_out.location
+ && r->headers_out.location->value.len
+ && r->headers_out.location->value.data[0] == '/')
+ {
+ r->headers_out.location->hash = 0;
+
+ if (clcf->server_name_in_redirect) {
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+ host = cscf->server_name;
+
+ } else if (r->headers_in.server.len) {
+ host = r->headers_in.server;
+
+ } else {
+ host.len = NGX_SOCKADDR_STRLEN;
+ host.data = addr;
+
+ if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ switch (c->local_sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) c->local_sockaddr;
+ port = ntohs(sin6->sin6_port);
+ break;
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+ case AF_UNIX:
+ port = 0;
+ break;
+#endif
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) c->local_sockaddr;
+ port = ntohs(sin->sin_port);
+ break;
+ }
+
+ len += sizeof("Location: https://") - 1
+ + host.len
+ + r->headers_out.location->value.len + 2;
+
+ if (clcf->port_in_redirect) {
+
+#if (NGX_HTTP_SSL)
+ if (c->ssl)
+ port = (port == 443) ? 0 : port;
+ else
+#endif
+ port = (port == 80) ? 0 : port;
+
+ } else {
+ port = 0;
+ }
+
+ if (port) {
+ len += sizeof(":65535") - 1;
+ }
+
+ } else {
+ ngx_str_null(&host);
+ port = 0;
+ }
+
+ if (r->chunked) {
+ len += sizeof("Transfer-Encoding: chunked" CRLF) - 1;
+ }
+
+ if (r->keepalive) {
+ len += sizeof("Connection: keep-alive" CRLF) - 1;
+
+ /*
+ * MSIE and Opera ignore the "Keep-Alive: timeout=<N>" header.
+ * MSIE keeps the connection alive for about 60-65 seconds.
+ * Opera keeps the connection alive very long.
+ * Mozilla keeps the connection alive for N plus about 1-10 seconds.
+ * Konqueror keeps the connection alive for about N seconds.
+ */
+
+ if (clcf->keepalive_header) {
+ len += sizeof("Keep-Alive: timeout=") - 1 + NGX_TIME_T_LEN + 2;
+ }
+
+ } else {
+ len += sizeof("Connection: closed" CRLF) - 1;
+ }
+
+#if (NGX_HTTP_GZIP)
+ if (r->gzip_vary) {
+ if (clcf->gzip_vary) {
+ len += sizeof("Vary: Accept-Encoding" CRLF) - 1;
+
+ } else {
+ r->gzip_vary = 0;
+ }
+ }
+#endif
+
+ part = &r->headers_out.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (header[i].hash == 0) {
+ continue;
+ }
+
+ len += header[i].key.len + sizeof(": ") - 1 + header[i].value.len
+ + sizeof(CRLF) - 1;
+ }
+
+ b = ngx_create_temp_buf(r->pool, len);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ /* "HTTP/1.x " */
+ b->last = ngx_cpymem(b->last, "HTTP/1.1 ", sizeof("HTTP/1.x ") - 1);
+
+ /* status line */
+ if (status_line) {
+ b->last = ngx_copy(b->last, status_line->data, status_line->len);
+
+ } else {
+ b->last = ngx_sprintf(b->last, "%ui", status);
+ }
+ *b->last++ = CR; *b->last++ = LF;
+
+ if (r->headers_out.server == NULL) {
+ if (clcf->server_tokens) {
+ p = (u_char *) ngx_http_server_full_string;
+ len = sizeof(ngx_http_server_full_string) - 1;
+
+ } else {
+ p = (u_char *) ngx_http_server_string;
+ len = sizeof(ngx_http_server_string) - 1;
+ }
+
+ b->last = ngx_cpymem(b->last, p, len);
+ }
+
+ if (r->headers_out.date == NULL) {
+ b->last = ngx_cpymem(b->last, "Date: ", sizeof("Date: ") - 1);
+ b->last = ngx_cpymem(b->last, ngx_cached_http_time.data,
+ ngx_cached_http_time.len);
+
+ *b->last++ = CR; *b->last++ = LF;
+ }
+
+ if (r->headers_out.content_type.len) {
+ b->last = ngx_cpymem(b->last, "Content-Type: ",
+ sizeof("Content-Type: ") - 1);
+ p = b->last;
+ b->last = ngx_copy(b->last, r->headers_out.content_type.data,
+ r->headers_out.content_type.len);
+
+ if (r->headers_out.content_type_len == r->headers_out.content_type.len
+ && r->headers_out.charset.len)
+ {
+ b->last = ngx_cpymem(b->last, "; charset=",
+ sizeof("; charset=") - 1);
+ b->last = ngx_copy(b->last, r->headers_out.charset.data,
+ r->headers_out.charset.len);
+
+ /* update r->headers_out.content_type for possible logging */
+
+ r->headers_out.content_type.len = b->last - p;
+ r->headers_out.content_type.data = p;
+ }
+
+ *b->last++ = CR; *b->last++ = LF;
+ }
+
+ if (r->headers_out.content_length == NULL
+ && r->headers_out.content_length_n >= 0)
+ {
+ b->last = ngx_sprintf(b->last, "Content-Length: %O" CRLF,
+ r->headers_out.content_length_n);
+ }
+
+ if (r->headers_out.last_modified == NULL
+ && r->headers_out.last_modified_time != -1)
+ {
+ b->last = ngx_cpymem(b->last, "Last-Modified: ",
+ sizeof("Last-Modified: ") - 1);
+ b->last = ngx_http_time(b->last, r->headers_out.last_modified_time);
+
+ *b->last++ = CR; *b->last++ = LF;
+ }
+
+ if (host.data) {
+
+ p = b->last + sizeof("Location: ") - 1;
+
+ b->last = ngx_cpymem(b->last, "Location: http",
+ sizeof("Location: http") - 1);
+
+#if (NGX_HTTP_SSL)
+ if (c->ssl) {
+ *b->last++ ='s';
+ }
+#endif
+
+ *b->last++ = ':'; *b->last++ = '/'; *b->last++ = '/';
+ b->last = ngx_copy(b->last, host.data, host.len);
+
+ if (port) {
+ b->last = ngx_sprintf(b->last, ":%ui", port);
+ }
+
+ b->last = ngx_copy(b->last, r->headers_out.location->value.data,
+ r->headers_out.location->value.len);
+
+ /* update r->headers_out.location->value for possible logging */
+
+ r->headers_out.location->value.len = b->last - p;
+ r->headers_out.location->value.data = p;
+ ngx_str_set(&r->headers_out.location->key, "Location");
+
+ *b->last++ = CR; *b->last++ = LF;
+ }
+
+ if (r->chunked) {
+ b->last = ngx_cpymem(b->last, "Transfer-Encoding: chunked" CRLF,
+ sizeof("Transfer-Encoding: chunked" CRLF) - 1);
+ }
+
+ if (r->keepalive) {
+ b->last = ngx_cpymem(b->last, "Connection: keep-alive" CRLF,
+ sizeof("Connection: keep-alive" CRLF) - 1);
+
+ if (clcf->keepalive_header) {
+ b->last = ngx_sprintf(b->last, "Keep-Alive: timeout=%T" CRLF,
+ clcf->keepalive_header);
+ }
+
+ } else {
+ b->last = ngx_cpymem(b->last, "Connection: close" CRLF,
+ sizeof("Connection: close" CRLF) - 1);
+ }
+
+#if (NGX_HTTP_GZIP)
+ if (r->gzip_vary) {
+ b->last = ngx_cpymem(b->last, "Vary: Accept-Encoding" CRLF,
+ sizeof("Vary: Accept-Encoding" CRLF) - 1);
+ }
+#endif
+
+ part = &r->headers_out.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (header[i].hash == 0) {
+ continue;
+ }
+
+ b->last = ngx_copy(b->last, header[i].key.data, header[i].key.len);
+ *b->last++ = ':'; *b->last++ = ' ';
+
+ b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len);
+ *b->last++ = CR; *b->last++ = LF;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "%*s", (size_t) (b->last - b->pos), b->pos);
+
+ /* the end of HTTP header */
+ *b->last++ = CR; *b->last++ = LF;
+
+ r->header_size = b->last - b->pos;
+
+ if (r->header_only) {
+ b->last_buf = 1;
+ }
+
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_write_filter(r, &out);
+}
+
+
+static ngx_int_t
+ngx_http_header_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_top_header_filter = ngx_http_header_filter;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/ngx_http_parse.c b/usr.sbin/nginx/src/http/ngx_http_parse.c
new file mode 100644
index 00000000000..949006b47a2
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_parse.c
@@ -0,0 +1,1719 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static uint32_t usual[] = {
+ 0xffffdbfe, /* 1111 1111 1111 1111 1101 1011 1111 1110 */
+
+ /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
+ 0x7fff37d6, /* 0111 1111 1111 1111 0011 0111 1101 0110 */
+
+ /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
+#if (NGX_WIN32)
+ 0xefffffff, /* 1110 1111 1111 1111 1111 1111 1111 1111 */
+#else
+ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
+#endif
+
+ /* ~}| {zyx wvut srqp onml kjih gfed cba` */
+ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
+
+ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
+ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
+ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
+ 0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */
+};
+
+
+#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
+
+#define ngx_str3_cmp(m, c0, c1, c2, c3) \
+ *(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)
+
+#define ngx_str3Ocmp(m, c0, c1, c2, c3) \
+ *(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)
+
+#define ngx_str4cmp(m, c0, c1, c2, c3) \
+ *(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)
+
+#define ngx_str5cmp(m, c0, c1, c2, c3, c4) \
+ *(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \
+ && m[4] == c4
+
+#define ngx_str6cmp(m, c0, c1, c2, c3, c4, c5) \
+ *(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \
+ && (((uint32_t *) m)[1] & 0xffff) == ((c5 << 8) | c4)
+
+#define ngx_str7_cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \
+ *(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \
+ && ((uint32_t *) m)[1] == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4)
+
+#define ngx_str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \
+ *(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \
+ && ((uint32_t *) m)[1] == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4)
+
+#define ngx_str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) \
+ *(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \
+ && ((uint32_t *) m)[1] == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4) \
+ && m[8] == c8
+
+#else /* !(NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED) */
+
+#define ngx_str3_cmp(m, c0, c1, c2, c3) \
+ m[0] == c0 && m[1] == c1 && m[2] == c2
+
+#define ngx_str3Ocmp(m, c0, c1, c2, c3) \
+ m[0] == c0 && m[2] == c2 && m[3] == c3
+
+#define ngx_str4cmp(m, c0, c1, c2, c3) \
+ m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3
+
+#define ngx_str5cmp(m, c0, c1, c2, c3, c4) \
+ m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 && m[4] == c4
+
+#define ngx_str6cmp(m, c0, c1, c2, c3, c4, c5) \
+ m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 \
+ && m[4] == c4 && m[5] == c5
+
+#define ngx_str7_cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \
+ m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 \
+ && m[4] == c4 && m[5] == c5 && m[6] == c6
+
+#define ngx_str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \
+ m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 \
+ && m[4] == c4 && m[5] == c5 && m[6] == c6 && m[7] == c7
+
+#define ngx_str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) \
+ m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 \
+ && m[4] == c4 && m[5] == c5 && m[6] == c6 && m[7] == c7 && m[8] == c8
+
+#endif
+
+
+/* gcc, icc, msvc and others compile these switches as an jump table */
+
+ngx_int_t
+ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
+{
+ u_char c, ch, *p, *m;
+ enum {
+ sw_start = 0,
+ sw_method,
+ sw_spaces_before_uri,
+ sw_schema,
+ sw_schema_slash,
+ sw_schema_slash_slash,
+ sw_host,
+ sw_port,
+ sw_host_http_09,
+ sw_after_slash_in_uri,
+ sw_check_uri,
+ sw_check_uri_http_09,
+ sw_uri,
+ sw_http_09,
+ sw_http_H,
+ sw_http_HT,
+ sw_http_HTT,
+ sw_http_HTTP,
+ sw_first_major_digit,
+ sw_major_digit,
+ sw_first_minor_digit,
+ sw_minor_digit,
+ sw_spaces_after_digit,
+ sw_almost_done
+ } state;
+
+ state = r->state;
+
+ for (p = b->pos; p < b->last; p++) {
+ ch = *p;
+
+ switch (state) {
+
+ /* HTTP methods: GET, HEAD, POST */
+ case sw_start:
+ r->request_start = p;
+
+ if (ch == CR || ch == LF) {
+ break;
+ }
+
+ if ((ch < 'A' || ch > 'Z') && ch != '_') {
+ return NGX_HTTP_PARSE_INVALID_METHOD;
+ }
+
+ state = sw_method;
+ break;
+
+ case sw_method:
+ if (ch == ' ') {
+ r->method_end = p - 1;
+ m = r->request_start;
+
+ switch (p - m) {
+
+ case 3:
+ if (ngx_str3_cmp(m, 'G', 'E', 'T', ' ')) {
+ r->method = NGX_HTTP_GET;
+ break;
+ }
+
+ if (ngx_str3_cmp(m, 'P', 'U', 'T', ' ')) {
+ r->method = NGX_HTTP_PUT;
+ break;
+ }
+
+ break;
+
+ case 4:
+ if (m[1] == 'O') {
+
+ if (ngx_str3Ocmp(m, 'P', 'O', 'S', 'T')) {
+ r->method = NGX_HTTP_POST;
+ break;
+ }
+
+ if (ngx_str3Ocmp(m, 'C', 'O', 'P', 'Y')) {
+ r->method = NGX_HTTP_COPY;
+ break;
+ }
+
+ if (ngx_str3Ocmp(m, 'M', 'O', 'V', 'E')) {
+ r->method = NGX_HTTP_MOVE;
+ break;
+ }
+
+ if (ngx_str3Ocmp(m, 'L', 'O', 'C', 'K')) {
+ r->method = NGX_HTTP_LOCK;
+ break;
+ }
+
+ } else {
+
+ if (ngx_str4cmp(m, 'H', 'E', 'A', 'D')) {
+ r->method = NGX_HTTP_HEAD;
+ break;
+ }
+ }
+
+ break;
+
+ case 5:
+ if (ngx_str5cmp(m, 'M', 'K', 'C', 'O', 'L')) {
+ r->method = NGX_HTTP_MKCOL;
+ }
+
+ if (ngx_str5cmp(m, 'P', 'A', 'T', 'C', 'H')) {
+ r->method = NGX_HTTP_PATCH;
+ }
+
+ if (ngx_str5cmp(m, 'T', 'R', 'A', 'C', 'E')) {
+ r->method = NGX_HTTP_TRACE;
+ }
+
+ break;
+
+ case 6:
+ if (ngx_str6cmp(m, 'D', 'E', 'L', 'E', 'T', 'E')) {
+ r->method = NGX_HTTP_DELETE;
+ break;
+ }
+
+ if (ngx_str6cmp(m, 'U', 'N', 'L', 'O', 'C', 'K')) {
+ r->method = NGX_HTTP_UNLOCK;
+ break;
+ }
+
+ break;
+
+ case 7:
+ if (ngx_str7_cmp(m, 'O', 'P', 'T', 'I', 'O', 'N', 'S', ' '))
+ {
+ r->method = NGX_HTTP_OPTIONS;
+ }
+
+ break;
+
+ case 8:
+ if (ngx_str8cmp(m, 'P', 'R', 'O', 'P', 'F', 'I', 'N', 'D'))
+ {
+ r->method = NGX_HTTP_PROPFIND;
+ }
+
+ break;
+
+ case 9:
+ if (ngx_str9cmp(m,
+ 'P', 'R', 'O', 'P', 'P', 'A', 'T', 'C', 'H'))
+ {
+ r->method = NGX_HTTP_PROPPATCH;
+ }
+
+ break;
+ }
+
+ state = sw_spaces_before_uri;
+ break;
+ }
+
+ if ((ch < 'A' || ch > 'Z') && ch != '_') {
+ return NGX_HTTP_PARSE_INVALID_METHOD;
+ }
+
+ break;
+
+ /* space* before URI */
+ case sw_spaces_before_uri:
+
+ if (ch == '/') {
+ r->uri_start = p;
+ state = sw_after_slash_in_uri;
+ break;
+ }
+
+ c = (u_char) (ch | 0x20);
+ if (c >= 'a' && c <= 'z') {
+ r->schema_start = p;
+ state = sw_schema;
+ break;
+ }
+
+ switch (ch) {
+ case ' ':
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+ case sw_schema:
+
+ c = (u_char) (ch | 0x20);
+ if (c >= 'a' && c <= 'z') {
+ break;
+ }
+
+ switch (ch) {
+ case ':':
+ r->schema_end = p;
+ state = sw_schema_slash;
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+ case sw_schema_slash:
+ switch (ch) {
+ case '/':
+ state = sw_schema_slash_slash;
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+ case sw_schema_slash_slash:
+ switch (ch) {
+ case '/':
+ r->host_start = p + 1;
+ state = sw_host;
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+ case sw_host:
+
+ c = (u_char) (ch | 0x20);
+ if (c >= 'a' && c <= 'z') {
+ break;
+ }
+
+ if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') {
+ break;
+ }
+
+ r->host_end = p;
+
+ switch (ch) {
+ case ':':
+ state = sw_port;
+ break;
+ case '/':
+ r->uri_start = p;
+ state = sw_after_slash_in_uri;
+ break;
+ case ' ':
+ /*
+ * use single "/" from request line to preserve pointers,
+ * if request line will be copied to large client buffer
+ */
+ r->uri_start = r->schema_end + 1;
+ r->uri_end = r->schema_end + 2;
+ state = sw_host_http_09;
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+ case sw_port:
+ if (ch >= '0' && ch <= '9') {
+ break;
+ }
+
+ switch (ch) {
+ case '/':
+ r->port_end = p;
+ r->uri_start = p;
+ state = sw_after_slash_in_uri;
+ break;
+ case ' ':
+ r->port_end = p;
+ /*
+ * use single "/" from request line to preserve pointers,
+ * if request line will be copied to large client buffer
+ */
+ r->uri_start = r->schema_end + 1;
+ r->uri_end = r->schema_end + 2;
+ state = sw_host_http_09;
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+ /* space+ after "http://host[:port] " */
+ case sw_host_http_09:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ r->http_minor = 9;
+ state = sw_almost_done;
+ break;
+ case LF:
+ r->http_minor = 9;
+ goto done;
+ case 'H':
+ r->http_protocol.data = p;
+ state = sw_http_H;
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+
+ /* check "/.", "//", "%", and "\" (Win32) in URI */
+ case sw_after_slash_in_uri:
+
+ if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
+ state = sw_check_uri;
+ break;
+ }
+
+ switch (ch) {
+ case ' ':
+ r->uri_end = p;
+ state = sw_check_uri_http_09;
+ break;
+ case CR:
+ r->uri_end = p;
+ r->http_minor = 9;
+ state = sw_almost_done;
+ break;
+ case LF:
+ r->uri_end = p;
+ r->http_minor = 9;
+ goto done;
+ case '.':
+ r->complex_uri = 1;
+ state = sw_uri;
+ break;
+ case '%':
+ r->quoted_uri = 1;
+ state = sw_uri;
+ break;
+ case '/':
+ r->complex_uri = 1;
+ state = sw_uri;
+ break;
+#if (NGX_WIN32)
+ case '\\':
+ r->complex_uri = 1;
+ state = sw_uri;
+ break;
+#endif
+ case '?':
+ r->args_start = p + 1;
+ state = sw_uri;
+ break;
+ case '#':
+ r->complex_uri = 1;
+ state = sw_uri;
+ break;
+ case '+':
+ r->plus_in_uri = 1;
+ break;
+ case '\0':
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ default:
+ state = sw_check_uri;
+ break;
+ }
+ break;
+
+ /* check "/", "%" and "\" (Win32) in URI */
+ case sw_check_uri:
+
+ if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
+ break;
+ }
+
+ switch (ch) {
+ case '/':
+ r->uri_ext = NULL;
+ state = sw_after_slash_in_uri;
+ break;
+ case '.':
+ r->uri_ext = p + 1;
+ break;
+ case ' ':
+ r->uri_end = p;
+ state = sw_check_uri_http_09;
+ break;
+ case CR:
+ r->uri_end = p;
+ r->http_minor = 9;
+ state = sw_almost_done;
+ break;
+ case LF:
+ r->uri_end = p;
+ r->http_minor = 9;
+ goto done;
+#if (NGX_WIN32)
+ case '\\':
+ r->complex_uri = 1;
+ state = sw_after_slash_in_uri;
+ break;
+#endif
+ case '%':
+ r->quoted_uri = 1;
+ state = sw_uri;
+ break;
+ case '?':
+ r->args_start = p + 1;
+ state = sw_uri;
+ break;
+ case '#':
+ r->complex_uri = 1;
+ state = sw_uri;
+ break;
+ case '+':
+ r->plus_in_uri = 1;
+ break;
+ case '\0':
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+ /* space+ after URI */
+ case sw_check_uri_http_09:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ r->http_minor = 9;
+ state = sw_almost_done;
+ break;
+ case LF:
+ r->http_minor = 9;
+ goto done;
+ case 'H':
+ r->http_protocol.data = p;
+ state = sw_http_H;
+ break;
+ default:
+ r->space_in_uri = 1;
+ state = sw_check_uri;
+ break;
+ }
+ break;
+
+
+ /* URI */
+ case sw_uri:
+
+ if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
+ break;
+ }
+
+ switch (ch) {
+ case ' ':
+ r->uri_end = p;
+ state = sw_http_09;
+ break;
+ case CR:
+ r->uri_end = p;
+ r->http_minor = 9;
+ state = sw_almost_done;
+ break;
+ case LF:
+ r->uri_end = p;
+ r->http_minor = 9;
+ goto done;
+ case '#':
+ r->complex_uri = 1;
+ break;
+ case '\0':
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+ /* space+ after URI */
+ case sw_http_09:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ r->http_minor = 9;
+ state = sw_almost_done;
+ break;
+ case LF:
+ r->http_minor = 9;
+ goto done;
+ case 'H':
+ r->http_protocol.data = p;
+ state = sw_http_H;
+ break;
+ default:
+ r->space_in_uri = 1;
+ state = sw_uri;
+ break;
+ }
+ break;
+
+ case sw_http_H:
+ switch (ch) {
+ case 'T':
+ state = sw_http_HT;
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+ case sw_http_HT:
+ switch (ch) {
+ case 'T':
+ state = sw_http_HTT;
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+ case sw_http_HTT:
+ switch (ch) {
+ case 'P':
+ state = sw_http_HTTP;
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+ case sw_http_HTTP:
+ switch (ch) {
+ case '/':
+ state = sw_first_major_digit;
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+ /* first digit of major HTTP version */
+ case sw_first_major_digit:
+ if (ch < '1' || ch > '9') {
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+
+ r->http_major = ch - '0';
+ state = sw_major_digit;
+ break;
+
+ /* major HTTP version or dot */
+ case sw_major_digit:
+ if (ch == '.') {
+ state = sw_first_minor_digit;
+ break;
+ }
+
+ if (ch < '0' || ch > '9') {
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+
+ r->http_major = r->http_major * 10 + ch - '0';
+ break;
+
+ /* first digit of minor HTTP version */
+ case sw_first_minor_digit:
+ if (ch < '0' || ch > '9') {
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+
+ r->http_minor = ch - '0';
+ state = sw_minor_digit;
+ break;
+
+ /* minor HTTP version or end of request line */
+ case sw_minor_digit:
+ if (ch == CR) {
+ state = sw_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ goto done;
+ }
+
+ if (ch == ' ') {
+ state = sw_spaces_after_digit;
+ break;
+ }
+
+ if (ch < '0' || ch > '9') {
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+
+ r->http_minor = r->http_minor * 10 + ch - '0';
+ break;
+
+ case sw_spaces_after_digit:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ default:
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+ /* end of request line */
+ case sw_almost_done:
+ r->request_end = p - 1;
+ switch (ch) {
+ case LF:
+ goto done;
+ default:
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ }
+ }
+
+ b->pos = p;
+ r->state = state;
+
+ return NGX_AGAIN;
+
+done:
+
+ b->pos = p + 1;
+
+ if (r->request_end == NULL) {
+ r->request_end = p;
+ }
+
+ r->http_version = r->http_major * 1000 + r->http_minor;
+ r->state = sw_start;
+
+ if (r->http_version == 9 && r->method != NGX_HTTP_GET) {
+ return NGX_HTTP_PARSE_INVALID_09_METHOD;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b,
+ ngx_uint_t allow_underscores)
+{
+ u_char c, ch, *p;
+ ngx_uint_t hash, i;
+ enum {
+ sw_start = 0,
+ sw_name,
+ sw_space_before_value,
+ sw_value,
+ sw_space_after_value,
+ sw_ignore_line,
+ sw_almost_done,
+ sw_header_almost_done
+ } state;
+
+ /* the last '\0' is not needed because string is zero terminated */
+
+ static u_char lowcase[] =
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0-\0\0" "0123456789\0\0\0\0\0\0"
+ "\0abcdefghijklmnopqrstuvwxyz\0\0\0\0\0"
+ "\0abcdefghijklmnopqrstuvwxyz\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+
+ state = r->state;
+ hash = r->header_hash;
+ i = r->lowcase_index;
+
+ for (p = b->pos; p < b->last; p++) {
+ ch = *p;
+
+ switch (state) {
+
+ /* first char */
+ case sw_start:
+ r->header_name_start = p;
+ r->invalid_header = 0;
+
+ switch (ch) {
+ case CR:
+ r->header_end = p;
+ state = sw_header_almost_done;
+ break;
+ case LF:
+ r->header_end = p;
+ goto header_done;
+ default:
+ state = sw_name;
+
+ c = lowcase[ch];
+
+ if (c) {
+ hash = ngx_hash(0, c);
+ r->lowcase_header[0] = c;
+ i = 1;
+ break;
+ }
+
+ r->invalid_header = 1;
+
+ break;
+
+ }
+ break;
+
+ /* header name */
+ case sw_name:
+ c = lowcase[ch];
+
+ if (c) {
+ hash = ngx_hash(hash, c);
+ r->lowcase_header[i++] = c;
+ i &= (NGX_HTTP_LC_HEADER_LEN - 1);
+ break;
+ }
+
+ if (ch == '_') {
+ if (allow_underscores) {
+ hash = ngx_hash(hash, ch);
+ r->lowcase_header[i++] = ch;
+ i &= (NGX_HTTP_LC_HEADER_LEN - 1);
+
+ } else {
+ r->invalid_header = 1;
+ }
+
+ break;
+ }
+
+ if (ch == ':') {
+ r->header_name_end = p;
+ state = sw_space_before_value;
+ break;
+ }
+
+ if (ch == CR) {
+ r->header_name_end = p;
+ r->header_start = p;
+ r->header_end = p;
+ state = sw_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ r->header_name_end = p;
+ r->header_start = p;
+ r->header_end = p;
+ goto done;
+ }
+
+ /* IIS may send the duplicate "HTTP/1.1 ..." lines */
+ if (ch == '/'
+ && r->upstream
+ && p - r->header_name_start == 4
+ && ngx_strncmp(r->header_name_start, "HTTP", 4) == 0)
+ {
+ state = sw_ignore_line;
+ break;
+ }
+
+ r->invalid_header = 1;
+
+ break;
+
+ /* space* before header value */
+ case sw_space_before_value:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ r->header_start = p;
+ r->header_end = p;
+ state = sw_almost_done;
+ break;
+ case LF:
+ r->header_start = p;
+ r->header_end = p;
+ goto done;
+ default:
+ r->header_start = p;
+ state = sw_value;
+ break;
+ }
+ break;
+
+ /* header value */
+ case sw_value:
+ switch (ch) {
+ case ' ':
+ r->header_end = p;
+ state = sw_space_after_value;
+ break;
+ case CR:
+ r->header_end = p;
+ state = sw_almost_done;
+ break;
+ case LF:
+ r->header_end = p;
+ goto done;
+ }
+ break;
+
+ /* space* before end of header line */
+ case sw_space_after_value:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ default:
+ state = sw_value;
+ break;
+ }
+ break;
+
+ /* ignore header line */
+ case sw_ignore_line:
+ switch (ch) {
+ case LF:
+ state = sw_start;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ /* end of header line */
+ case sw_almost_done:
+ switch (ch) {
+ case LF:
+ goto done;
+ case CR:
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_HEADER;
+ }
+ break;
+
+ /* end of header */
+ case sw_header_almost_done:
+ switch (ch) {
+ case LF:
+ goto header_done;
+ default:
+ return NGX_HTTP_PARSE_INVALID_HEADER;
+ }
+ }
+ }
+
+ b->pos = p;
+ r->state = state;
+ r->header_hash = hash;
+ r->lowcase_index = i;
+
+ return NGX_AGAIN;
+
+done:
+
+ b->pos = p + 1;
+ r->state = sw_start;
+ r->header_hash = hash;
+ r->lowcase_index = i;
+
+ return NGX_OK;
+
+header_done:
+
+ b->pos = p + 1;
+ r->state = sw_start;
+
+ return NGX_HTTP_PARSE_HEADER_DONE;
+}
+
+
+ngx_int_t
+ngx_http_parse_complex_uri(ngx_http_request_t *r, ngx_uint_t merge_slashes)
+{
+ u_char c, ch, decoded, *p, *u;
+ enum {
+ sw_usual = 0,
+ sw_slash,
+ sw_dot,
+ sw_dot_dot,
+ sw_quoted,
+ sw_quoted_second
+ } state, quoted_state;
+
+#if (NGX_SUPPRESS_WARN)
+ decoded = '\0';
+ quoted_state = sw_usual;
+#endif
+
+ state = sw_usual;
+ p = r->uri_start;
+ u = r->uri.data;
+ r->uri_ext = NULL;
+ r->args_start = NULL;
+
+ ch = *p++;
+
+ while (p <= r->uri_end) {
+
+ /*
+ * we use "ch = *p++" inside the cycle, but this operation is safe,
+ * because after the URI there is always at least one charcter:
+ * the line feed
+ */
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "s:%d in:'%Xd:%c', out:'%c'", state, ch, ch, *u);
+
+ switch (state) {
+
+ case sw_usual:
+
+ if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
+ *u++ = ch;
+ ch = *p++;
+ break;
+ }
+
+ switch(ch) {
+#if (NGX_WIN32)
+ case '\\':
+ r->uri_ext = NULL;
+
+ if (p == r->uri_start + r->uri.len) {
+
+ /*
+ * we omit the last "\" to cause redirect because
+ * the browsers do not treat "\" as "/" in relative URL path
+ */
+
+ break;
+ }
+
+ state = sw_slash;
+ *u++ = '/';
+ break;
+#endif
+ case '/':
+ r->uri_ext = NULL;
+ state = sw_slash;
+ *u++ = ch;
+ break;
+ case '%':
+ quoted_state = state;
+ state = sw_quoted;
+ break;
+ case '?':
+ r->args_start = p;
+ goto args;
+ case '#':
+ goto done;
+ case '.':
+ r->uri_ext = u + 1;
+ *u++ = ch;
+ break;
+ case '+':
+ r->plus_in_uri = 1;
+ default:
+ *u++ = ch;
+ break;
+ }
+
+ ch = *p++;
+ break;
+
+ case sw_slash:
+
+ if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
+ state = sw_usual;
+ *u++ = ch;
+ ch = *p++;
+ break;
+ }
+
+ switch(ch) {
+#if (NGX_WIN32)
+ case '\\':
+ break;
+#endif
+ case '/':
+ if (!merge_slashes) {
+ *u++ = ch;
+ }
+ break;
+ case '.':
+ state = sw_dot;
+ *u++ = ch;
+ break;
+ case '%':
+ quoted_state = state;
+ state = sw_quoted;
+ break;
+ case '?':
+ r->args_start = p;
+ goto args;
+ case '#':
+ goto done;
+ case '+':
+ r->plus_in_uri = 1;
+ default:
+ state = sw_usual;
+ *u++ = ch;
+ break;
+ }
+
+ ch = *p++;
+ break;
+
+ case sw_dot:
+
+ if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
+ state = sw_usual;
+ *u++ = ch;
+ ch = *p++;
+ break;
+ }
+
+ switch(ch) {
+#if (NGX_WIN32)
+ case '\\':
+#endif
+ case '/':
+ state = sw_slash;
+ u--;
+ break;
+ case '.':
+ state = sw_dot_dot;
+ *u++ = ch;
+ break;
+ case '%':
+ quoted_state = state;
+ state = sw_quoted;
+ break;
+ case '?':
+ r->args_start = p;
+ goto args;
+ case '#':
+ goto done;
+ case '+':
+ r->plus_in_uri = 1;
+ default:
+ state = sw_usual;
+ *u++ = ch;
+ break;
+ }
+
+ ch = *p++;
+ break;
+
+ case sw_dot_dot:
+
+ if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
+ state = sw_usual;
+ *u++ = ch;
+ ch = *p++;
+ break;
+ }
+
+ switch(ch) {
+#if (NGX_WIN32)
+ case '\\':
+#endif
+ case '/':
+ state = sw_slash;
+ u -= 5;
+ for ( ;; ) {
+ if (u < r->uri.data) {
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ if (*u == '/') {
+ u++;
+ break;
+ }
+ u--;
+ }
+ break;
+ case '%':
+ quoted_state = state;
+ state = sw_quoted;
+ break;
+ case '?':
+ r->args_start = p;
+ goto args;
+ case '#':
+ goto done;
+ case '+':
+ r->plus_in_uri = 1;
+ default:
+ state = sw_usual;
+ *u++ = ch;
+ break;
+ }
+
+ ch = *p++;
+ break;
+
+ case sw_quoted:
+ r->quoted_uri = 1;
+
+ if (ch >= '0' && ch <= '9') {
+ decoded = (u_char) (ch - '0');
+ state = sw_quoted_second;
+ ch = *p++;
+ break;
+ }
+
+ c = (u_char) (ch | 0x20);
+ if (c >= 'a' && c <= 'f') {
+ decoded = (u_char) (c - 'a' + 10);
+ state = sw_quoted_second;
+ ch = *p++;
+ break;
+ }
+
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+
+ case sw_quoted_second:
+ if (ch >= '0' && ch <= '9') {
+ ch = (u_char) ((decoded << 4) + ch - '0');
+
+ if (ch == '%' || ch == '#') {
+ state = sw_usual;
+ *u++ = ch;
+ ch = *p++;
+ break;
+
+ } else if (ch == '\0') {
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+
+ state = quoted_state;
+ break;
+ }
+
+ c = (u_char) (ch | 0x20);
+ if (c >= 'a' && c <= 'f') {
+ ch = (u_char) ((decoded << 4) + c - 'a' + 10);
+
+ if (ch == '?') {
+ state = sw_usual;
+ *u++ = ch;
+ ch = *p++;
+ break;
+
+ } else if (ch == '+') {
+ r->plus_in_uri = 1;
+ }
+
+ state = quoted_state;
+ break;
+ }
+
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ }
+
+done:
+
+ r->uri.len = u - r->uri.data;
+
+ if (r->uri_ext) {
+ r->exten.len = u - r->uri_ext;
+ r->exten.data = r->uri_ext;
+ }
+
+ r->uri_ext = NULL;
+
+ return NGX_OK;
+
+args:
+
+ while (p < r->uri_end) {
+ if (*p++ != '#') {
+ continue;
+ }
+
+ r->args.len = p - 1 - r->args_start;
+ r->args.data = r->args_start;
+ r->args_start = NULL;
+
+ break;
+ }
+
+ r->uri.len = u - r->uri.data;
+
+ if (r->uri_ext) {
+ r->exten.len = u - r->uri_ext;
+ r->exten.data = r->uri_ext;
+ }
+
+ r->uri_ext = NULL;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_parse_status_line(ngx_http_request_t *r, ngx_buf_t *b,
+ ngx_http_status_t *status)
+{
+ u_char ch;
+ u_char *p;
+ enum {
+ sw_start = 0,
+ sw_H,
+ sw_HT,
+ sw_HTT,
+ sw_HTTP,
+ sw_first_major_digit,
+ sw_major_digit,
+ sw_first_minor_digit,
+ sw_minor_digit,
+ sw_status,
+ sw_space_after_status,
+ sw_status_text,
+ sw_almost_done
+ } state;
+
+ state = r->state;
+
+ for (p = b->pos; p < b->last; p++) {
+ ch = *p;
+
+ switch (state) {
+
+ /* "HTTP/" */
+ case sw_start:
+ switch (ch) {
+ case 'H':
+ state = sw_H;
+ break;
+ default:
+ return NGX_ERROR;
+ }
+ break;
+
+ case sw_H:
+ switch (ch) {
+ case 'T':
+ state = sw_HT;
+ break;
+ default:
+ return NGX_ERROR;
+ }
+ break;
+
+ case sw_HT:
+ switch (ch) {
+ case 'T':
+ state = sw_HTT;
+ break;
+ default:
+ return NGX_ERROR;
+ }
+ break;
+
+ case sw_HTT:
+ switch (ch) {
+ case 'P':
+ state = sw_HTTP;
+ break;
+ default:
+ return NGX_ERROR;
+ }
+ break;
+
+ case sw_HTTP:
+ switch (ch) {
+ case '/':
+ state = sw_first_major_digit;
+ break;
+ default:
+ return NGX_ERROR;
+ }
+ break;
+
+ /* the first digit of major HTTP version */
+ case sw_first_major_digit:
+ if (ch < '1' || ch > '9') {
+ return NGX_ERROR;
+ }
+
+ state = sw_major_digit;
+ break;
+
+ /* the major HTTP version or dot */
+ case sw_major_digit:
+ if (ch == '.') {
+ state = sw_first_minor_digit;
+ break;
+ }
+
+ if (ch < '0' || ch > '9') {
+ return NGX_ERROR;
+ }
+
+ break;
+
+ /* the first digit of minor HTTP version */
+ case sw_first_minor_digit:
+ if (ch < '0' || ch > '9') {
+ return NGX_ERROR;
+ }
+
+ state = sw_minor_digit;
+ break;
+
+ /* the minor HTTP version or the end of the request line */
+ case sw_minor_digit:
+ if (ch == ' ') {
+ state = sw_status;
+ break;
+ }
+
+ if (ch < '0' || ch > '9') {
+ return NGX_ERROR;
+ }
+
+ break;
+
+ /* HTTP status code */
+ case sw_status:
+ if (ch == ' ') {
+ break;
+ }
+
+ if (ch < '0' || ch > '9') {
+ return NGX_ERROR;
+ }
+
+ status->code = status->code * 10 + ch - '0';
+
+ if (++status->count == 3) {
+ state = sw_space_after_status;
+ status->start = p - 2;
+ }
+
+ break;
+
+ /* space or end of line */
+ case sw_space_after_status:
+ switch (ch) {
+ case ' ':
+ state = sw_status_text;
+ break;
+ case '.': /* IIS may send 403.1, 403.2, etc */
+ state = sw_status_text;
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ default:
+ return NGX_ERROR;
+ }
+ break;
+
+ /* any text until end of line */
+ case sw_status_text:
+ switch (ch) {
+ case CR:
+ state = sw_almost_done;
+
+ break;
+ case LF:
+ goto done;
+ }
+ break;
+
+ /* end of status line */
+ case sw_almost_done:
+ status->end = p - 1;
+ switch (ch) {
+ case LF:
+ goto done;
+ default:
+ return NGX_ERROR;
+ }
+ }
+ }
+
+ b->pos = p;
+ r->state = state;
+
+ return NGX_AGAIN;
+
+done:
+
+ b->pos = p + 1;
+
+ if (status->end == NULL) {
+ status->end = p;
+ }
+
+ r->state = sw_start;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_parse_unsafe_uri(ngx_http_request_t *r, ngx_str_t *uri,
+ ngx_str_t *args, ngx_uint_t *flags)
+{
+ u_char ch, *p;
+ size_t len;
+
+ len = uri->len;
+ p = uri->data;
+
+ if (len == 0 || p[0] == '?') {
+ goto unsafe;
+ }
+
+ if (p[0] == '.' && len == 3 && p[1] == '.' && (ngx_path_separator(p[2]))) {
+ goto unsafe;
+ }
+
+ for ( /* void */ ; len; len--) {
+
+ ch = *p++;
+
+ if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
+ continue;
+ }
+
+ if (ch == '?') {
+ args->len = len - 1;
+ args->data = p;
+ uri->len -= len;
+
+ return NGX_OK;
+ }
+
+ if (ch == '\0') {
+ goto unsafe;
+ }
+
+ if (ngx_path_separator(ch) && len > 2) {
+
+ /* detect "/../" */
+
+ if (p[0] == '.' && p[1] == '.' && ngx_path_separator(p[2])) {
+ goto unsafe;
+ }
+ }
+ }
+
+ return NGX_OK;
+
+unsafe:
+
+ if (*flags & NGX_HTTP_LOG_UNSAFE) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "unsafe URI \"%V\" was detected", uri);
+ }
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_parse_multi_header_lines(ngx_array_t *headers, ngx_str_t *name,
+ ngx_str_t *value)
+{
+ ngx_uint_t i;
+ u_char *start, *last, *end, ch;
+ ngx_table_elt_t **h;
+
+ h = headers->elts;
+
+ for (i = 0; i < headers->nelts; i++) {
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, headers->pool->log, 0,
+ "parse header: \"%V: %V\"", &h[i]->key, &h[i]->value);
+
+ if (name->len > h[i]->value.len) {
+ continue;
+ }
+
+ start = h[i]->value.data;
+ end = h[i]->value.data + h[i]->value.len;
+
+ while (start < end) {
+
+ if (ngx_strncasecmp(start, name->data, name->len) != 0) {
+ goto skip;
+ }
+
+ for (start += name->len; start < end && *start == ' '; start++) {
+ /* void */
+ }
+
+ if (value == NULL) {
+ if (start == end || *start == ',') {
+ return i;
+ }
+
+ goto skip;
+ }
+
+ if (start == end || *start++ != '=') {
+ /* the invalid header value */
+ goto skip;
+ }
+
+ while (start < end && *start == ' ') { start++; }
+
+ for (last = start; last < end && *last != ';'; last++) {
+ /* void */
+ }
+
+ value->len = last - start;
+ value->data = start;
+
+ return i;
+
+ skip:
+
+ while (start < end) {
+ ch = *start++;
+ if (ch == ';' || ch == ',') {
+ break;
+ }
+ }
+
+ while (start < end && *start == ' ') { start++; }
+ }
+ }
+
+ return NGX_DECLINED;
+}
+
+
+ngx_int_t
+ngx_http_arg(ngx_http_request_t *r, u_char *name, size_t len, ngx_str_t *value)
+{
+ u_char *p, *last;
+
+ if (r->args.len == 0) {
+ return NGX_DECLINED;
+ }
+
+ p = r->args.data;
+ last = p + r->args.len;
+
+ for ( /* void */ ; p < last; p++) {
+
+ /* we need '=' after name, so drop one char from last */
+
+ p = ngx_strlcasestrn(p, last - 1, name, len - 1);
+
+ if (p == NULL) {
+ return NGX_DECLINED;
+ }
+
+ if ((p == r->args.data || *(p - 1) == '&') && *(p + len) == '=') {
+
+ value->data = p + len + 1;
+
+ p = ngx_strlchr(p, last, '&');
+
+ if (p == NULL) {
+ p = r->args.data + r->args.len;
+ }
+
+ value->len = p - value->data;
+
+ return NGX_OK;
+ }
+ }
+
+ return NGX_DECLINED;
+}
+
+
+void
+ngx_http_split_args(ngx_http_request_t *r, ngx_str_t *uri, ngx_str_t *args)
+{
+ u_char *p, *last;
+
+ last = uri->data + uri->len;
+
+ p = ngx_strlchr(uri->data, last, '?');
+
+ if (p) {
+ uri->len = p - uri->data;
+ p++;
+ args->len = last - p;
+ args->data = p;
+
+ } else {
+ args->len = 0;
+ }
+}
diff --git a/usr.sbin/nginx/src/http/ngx_http_parse_time.c b/usr.sbin/nginx/src/http/ngx_http_parse_time.c
new file mode 100644
index 00000000000..2e7b40addf7
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_parse_time.c
@@ -0,0 +1,275 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+static ngx_uint_t mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+time_t
+ngx_http_parse_time(u_char *value, size_t len)
+{
+ u_char *p, *end;
+ ngx_int_t month;
+ ngx_uint_t day, year, hour, min, sec;
+ uint64_t time;
+ enum {
+ no = 0,
+ rfc822, /* Tue, 10 Nov 2002 23:50:13 */
+ rfc850, /* Tuesday, 10-Dec-02 23:50:13 */
+ isoc /* Tue Dec 10 23:50:13 2002 */
+ } fmt;
+
+ fmt = 0;
+ end = value + len;
+
+#if (NGX_SUPPRESS_WARN)
+ day = 32;
+ year = 2038;
+#endif
+
+ for (p = value; p < end; p++) {
+ if (*p == ',') {
+ break;
+ }
+
+ if (*p == ' ') {
+ fmt = isoc;
+ break;
+ }
+ }
+
+ for (p++; p < end; p++)
+ if (*p != ' ') {
+ break;
+ }
+
+ if (end - p < 18) {
+ return NGX_ERROR;
+ }
+
+ if (fmt != isoc) {
+ if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') {
+ return NGX_ERROR;
+ }
+
+ day = (*p - '0') * 10 + *(p + 1) - '0';
+ p += 2;
+
+ if (*p == ' ') {
+ if (end - p < 18) {
+ return NGX_ERROR;
+ }
+ fmt = rfc822;
+
+ } else if (*p == '-') {
+ fmt = rfc850;
+
+ } else {
+ return NGX_ERROR;
+ }
+
+ p++;
+ }
+
+ switch (*p) {
+
+ case 'J':
+ month = *(p + 1) == 'a' ? 0 : *(p + 2) == 'n' ? 5 : 6;
+ break;
+
+ case 'F':
+ month = 1;
+ break;
+
+ case 'M':
+ month = *(p + 2) == 'r' ? 2 : 4;
+ break;
+
+ case 'A':
+ month = *(p + 1) == 'p' ? 3 : 7;
+ break;
+
+ case 'S':
+ month = 8;
+ break;
+
+ case 'O':
+ month = 9;
+ break;
+
+ case 'N':
+ month = 10;
+ break;
+
+ case 'D':
+ month = 11;
+ break;
+
+ default:
+ return NGX_ERROR;
+ }
+
+ p += 3;
+
+ if ((fmt == rfc822 && *p != ' ') || (fmt == rfc850 && *p != '-')) {
+ return NGX_ERROR;
+ }
+
+ p++;
+
+ if (fmt == rfc822) {
+ if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9'
+ || *(p + 2) < '0' || *(p + 2) > '9'
+ || *(p + 3) < '0' || *(p + 3) > '9')
+ {
+ return NGX_ERROR;
+ }
+
+ year = (*p - '0') * 1000 + (*(p + 1) - '0') * 100
+ + (*(p + 2) - '0') * 10 + *(p + 3) - '0';
+ p += 4;
+
+ } else if (fmt == rfc850) {
+ if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') {
+ return NGX_ERROR;
+ }
+
+ year = (*p - '0') * 10 + *(p + 1) - '0';
+ year += (year < 70) ? 2000 : 1900;
+ p += 2;
+ }
+
+ if (fmt == isoc) {
+ if (*p == ' ') {
+ p++;
+ }
+
+ if (*p < '0' || *p > '9') {
+ return NGX_ERROR;
+ }
+
+ day = *p++ - '0';
+
+ if (*p != ' ') {
+ if (*p < '0' || *p > '9') {
+ return NGX_ERROR;
+ }
+
+ day = day * 10 + *p++ - '0';
+ }
+
+ if (end - p < 14) {
+ return NGX_ERROR;
+ }
+ }
+
+ if (*p++ != ' ') {
+ return NGX_ERROR;
+ }
+
+ if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') {
+ return NGX_ERROR;
+ }
+
+ hour = (*p - '0') * 10 + *(p + 1) - '0';
+ p += 2;
+
+ if (*p++ != ':') {
+ return NGX_ERROR;
+ }
+
+ if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') {
+ return NGX_ERROR;
+ }
+
+ min = (*p - '0') * 10 + *(p + 1) - '0';
+ p += 2;
+
+ if (*p++ != ':') {
+ return NGX_ERROR;
+ }
+
+ if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') {
+ return NGX_ERROR;
+ }
+
+ sec = (*p - '0') * 10 + *(p + 1) - '0';
+
+ if (fmt == isoc) {
+ p += 2;
+
+ if (*p++ != ' ') {
+ return NGX_ERROR;
+ }
+
+ if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9'
+ || *(p + 2) < '0' || *(p + 2) > '9'
+ || *(p + 3) < '0' || *(p + 3) > '9')
+ {
+ return NGX_ERROR;
+ }
+
+ year = (*p - '0') * 1000 + (*(p + 1) - '0') * 100
+ + (*(p + 2) - '0') * 10 + *(p + 3) - '0';
+ }
+
+ if (hour > 23 || min > 59 || sec > 59) {
+ return NGX_ERROR;
+ }
+
+ if (day == 29 && month == 1) {
+ if ((year & 3) || ((year % 100 == 0) && (year % 400) != 0)) {
+ return NGX_ERROR;
+ }
+
+ } else if (day > mday[month]) {
+ return NGX_ERROR;
+ }
+
+ /*
+ * shift new year to March 1 and start months from 1 (not 0),
+ * it is needed for Gauss' formula
+ */
+
+ if (--month <= 0) {
+ month += 12;
+ year -= 1;
+ }
+
+ /* Gauss' formula for Grigorian days since March 1, 1 BC */
+
+ time = (uint64_t) (
+ /* days in years including leap years since March 1, 1 BC */
+
+ 365 * year + year / 4 - year / 100 + year / 400
+
+ /* days before the month */
+
+ + 367 * month / 12 - 30
+
+ /* days before the day */
+
+ + day - 1
+
+ /*
+ * 719527 days were between March 1, 1 BC and March 1, 1970,
+ * 31 and 28 days were in January and February 1970
+ */
+
+ - 719527 + 31 + 28) * 86400 + hour * 3600 + min * 60 + sec;
+
+#if (NGX_TIME_T_SIZE <= 4)
+
+ if (time > 0x7fffffff) {
+ return NGX_ERROR;
+ }
+
+#endif
+
+ return (time_t) time;
+}
diff --git a/usr.sbin/nginx/src/http/ngx_http_postpone_filter_module.c b/usr.sbin/nginx/src/http/ngx_http_postpone_filter_module.c
new file mode 100644
index 00000000000..156401b3292
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_postpone_filter_module.c
@@ -0,0 +1,177 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_postpone_filter_add(ngx_http_request_t *r,
+ ngx_chain_t *in);
+static ngx_int_t ngx_http_postpone_filter_init(ngx_conf_t *cf);
+
+
+static ngx_http_module_t ngx_http_postpone_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_postpone_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_postpone_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_postpone_filter_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_body_filter_pt ngx_http_next_filter;
+
+
+static ngx_int_t
+ngx_http_postpone_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ ngx_connection_t *c;
+ ngx_http_postponed_request_t *pr;
+
+ c = r->connection;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http postpone filter \"%V?%V\" %p", &r->uri, &r->args, in);
+
+ if (r != c->data) {
+
+ if (in) {
+ ngx_http_postpone_filter_add(r, in);
+ return NGX_OK;
+ }
+
+#if 0
+ /* TODO: SSI may pass NULL */
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "http postpone filter NULL inactive request",
+ &r->uri, &r->args);
+#endif
+
+ return NGX_OK;
+ }
+
+ if (r->postponed == NULL) {
+
+ if (in || c->buffered) {
+ return ngx_http_next_filter(r->main, in);
+ }
+
+ return NGX_OK;
+ }
+
+ if (in) {
+ ngx_http_postpone_filter_add(r, in);
+ }
+
+ do {
+ pr = r->postponed;
+
+ if (pr->request) {
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http postpone filter wake \"%V?%V\"",
+ &pr->request->uri, &pr->request->args);
+
+ r->postponed = pr->next;
+
+ c->data = pr->request;
+
+ return ngx_http_post_request(pr->request, NULL);
+ }
+
+ if (pr->out == NULL) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "http postpone filter NULL output",
+ &r->uri, &r->args);
+
+ } else {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http postpone filter output \"%V?%V\"",
+ &r->uri, &r->args);
+
+ if (ngx_http_next_filter(r->main, pr->out) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+
+ r->postponed = pr->next;
+
+ } while (r->postponed);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_postpone_filter_add(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ ngx_http_postponed_request_t *pr, **ppr;
+
+ if (r->postponed) {
+ for (pr = r->postponed; pr->next; pr = pr->next) { /* void */ }
+
+ if (pr->request == NULL) {
+ goto found;
+ }
+
+ ppr = &pr->next;
+
+ } else {
+ ppr = &r->postponed;
+ }
+
+ pr = ngx_palloc(r->pool, sizeof(ngx_http_postponed_request_t));
+ if (pr == NULL) {
+ return NGX_ERROR;
+ }
+
+ *ppr = pr;
+
+ pr->request = NULL;
+ pr->out = NULL;
+ pr->next = NULL;
+
+found:
+
+ if (ngx_chain_add_copy(r->pool, &pr->out, in) == NGX_OK) {
+ return NGX_OK;
+ }
+
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_postpone_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_postpone_filter;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/http/ngx_http_request.c b/usr.sbin/nginx/src/http/ngx_http_request.c
new file mode 100644
index 00000000000..d11b13e4add
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_request.c
@@ -0,0 +1,3156 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static void ngx_http_init_request(ngx_event_t *ev);
+static void ngx_http_process_request_line(ngx_event_t *rev);
+static void ngx_http_process_request_headers(ngx_event_t *rev);
+static ssize_t ngx_http_read_request_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_alloc_large_header_buffer(ngx_http_request_t *r,
+ ngx_uint_t request_line);
+
+static ngx_int_t ngx_http_process_header_line(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_process_unique_header_line(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_process_host(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_process_connection(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_process_cookie(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+
+static ngx_int_t ngx_http_process_request_header(ngx_http_request_t *r);
+static void ngx_http_process_request(ngx_http_request_t *r);
+static ssize_t ngx_http_validate_host(ngx_http_request_t *r, u_char **host,
+ size_t len, ngx_uint_t alloc);
+static ngx_int_t ngx_http_find_virtual_server(ngx_http_request_t *r,
+ u_char *host, size_t len);
+
+static void ngx_http_request_handler(ngx_event_t *ev);
+static void ngx_http_terminate_request(ngx_http_request_t *r, ngx_int_t rc);
+static void ngx_http_terminate_handler(ngx_http_request_t *r);
+static void ngx_http_finalize_connection(ngx_http_request_t *r);
+static ngx_int_t ngx_http_set_write_handler(ngx_http_request_t *r);
+static void ngx_http_writer(ngx_http_request_t *r);
+static void ngx_http_request_finalizer(ngx_http_request_t *r);
+
+static void ngx_http_set_keepalive(ngx_http_request_t *r);
+static void ngx_http_keepalive_handler(ngx_event_t *ev);
+static void ngx_http_set_lingering_close(ngx_http_request_t *r);
+static void ngx_http_lingering_close_handler(ngx_event_t *ev);
+static ngx_int_t ngx_http_post_action(ngx_http_request_t *r);
+static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error);
+static void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t error);
+static void ngx_http_log_request(ngx_http_request_t *r);
+static void ngx_http_close_connection(ngx_connection_t *c);
+
+static u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len);
+static u_char *ngx_http_log_error_handler(ngx_http_request_t *r,
+ ngx_http_request_t *sr, u_char *buf, size_t len);
+
+#if (NGX_HTTP_SSL)
+static void ngx_http_ssl_handshake(ngx_event_t *rev);
+static void ngx_http_ssl_handshake_handler(ngx_connection_t *c);
+#endif
+
+
+static char *ngx_http_client_errors[] = {
+
+ /* NGX_HTTP_PARSE_INVALID_METHOD */
+ "client sent invalid method",
+
+ /* NGX_HTTP_PARSE_INVALID_REQUEST */
+ "client sent invalid request",
+
+ /* NGX_HTTP_PARSE_INVALID_09_METHOD */
+ "client sent invalid method in HTTP/0.9 request"
+};
+
+
+ngx_http_header_t ngx_http_headers_in[] = {
+ { ngx_string("Host"), offsetof(ngx_http_headers_in_t, host),
+ ngx_http_process_host },
+
+ { ngx_string("Connection"), offsetof(ngx_http_headers_in_t, connection),
+ ngx_http_process_connection },
+
+ { ngx_string("If-Modified-Since"),
+ offsetof(ngx_http_headers_in_t, if_modified_since),
+ ngx_http_process_unique_header_line },
+
+ { ngx_string("If-Unmodified-Since"),
+ offsetof(ngx_http_headers_in_t, if_unmodified_since),
+ ngx_http_process_unique_header_line },
+
+ { ngx_string("User-Agent"), offsetof(ngx_http_headers_in_t, user_agent),
+ ngx_http_process_user_agent },
+
+ { ngx_string("Referer"), offsetof(ngx_http_headers_in_t, referer),
+ ngx_http_process_header_line },
+
+ { ngx_string("Content-Length"),
+ offsetof(ngx_http_headers_in_t, content_length),
+ ngx_http_process_unique_header_line },
+
+ { ngx_string("Content-Type"),
+ offsetof(ngx_http_headers_in_t, content_type),
+ ngx_http_process_header_line },
+
+ { ngx_string("Range"), offsetof(ngx_http_headers_in_t, range),
+ ngx_http_process_header_line },
+
+ { ngx_string("If-Range"),
+ offsetof(ngx_http_headers_in_t, if_range),
+ ngx_http_process_unique_header_line },
+
+ { ngx_string("Transfer-Encoding"),
+ offsetof(ngx_http_headers_in_t, transfer_encoding),
+ ngx_http_process_header_line },
+
+ { ngx_string("Expect"),
+ offsetof(ngx_http_headers_in_t, expect),
+ ngx_http_process_unique_header_line },
+
+#if (NGX_HTTP_GZIP)
+ { ngx_string("Accept-Encoding"),
+ offsetof(ngx_http_headers_in_t, accept_encoding),
+ ngx_http_process_header_line },
+
+ { ngx_string("Via"), offsetof(ngx_http_headers_in_t, via),
+ ngx_http_process_header_line },
+#endif
+
+ { ngx_string("Authorization"),
+ offsetof(ngx_http_headers_in_t, authorization),
+ ngx_http_process_unique_header_line },
+
+ { ngx_string("Keep-Alive"), offsetof(ngx_http_headers_in_t, keep_alive),
+ ngx_http_process_header_line },
+
+#if (NGX_HTTP_PROXY || NGX_HTTP_REALIP || NGX_HTTP_GEO)
+ { ngx_string("X-Forwarded-For"),
+ offsetof(ngx_http_headers_in_t, x_forwarded_for),
+ ngx_http_process_header_line },
+#endif
+
+#if (NGX_HTTP_REALIP)
+ { ngx_string("X-Real-IP"),
+ offsetof(ngx_http_headers_in_t, x_real_ip),
+ ngx_http_process_header_line },
+#endif
+
+#if (NGX_HTTP_HEADERS)
+ { ngx_string("Accept"), offsetof(ngx_http_headers_in_t, accept),
+ ngx_http_process_header_line },
+
+ { ngx_string("Accept-Language"),
+ offsetof(ngx_http_headers_in_t, accept_language),
+ ngx_http_process_header_line },
+#endif
+
+#if (NGX_HTTP_DAV)
+ { ngx_string("Depth"), offsetof(ngx_http_headers_in_t, depth),
+ ngx_http_process_header_line },
+
+ { ngx_string("Destination"), offsetof(ngx_http_headers_in_t, destination),
+ ngx_http_process_header_line },
+
+ { ngx_string("Overwrite"), offsetof(ngx_http_headers_in_t, overwrite),
+ ngx_http_process_header_line },
+
+ { ngx_string("Date"), offsetof(ngx_http_headers_in_t, date),
+ ngx_http_process_header_line },
+#endif
+
+ { ngx_string("Cookie"), 0, ngx_http_process_cookie },
+
+ { ngx_null_string, 0, NULL }
+};
+
+
+void
+ngx_http_init_connection(ngx_connection_t *c)
+{
+ ngx_event_t *rev;
+ ngx_http_log_ctx_t *ctx;
+
+ ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
+ if (ctx == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ ctx->connection = c;
+ ctx->request = NULL;
+ ctx->current_request = NULL;
+
+ c->log->connection = c->number;
+ c->log->handler = ngx_http_log_error;
+ c->log->data = ctx;
+ c->log->action = "reading client request line";
+
+ c->log_error = NGX_ERROR_INFO;
+
+ rev = c->read;
+ rev->handler = ngx_http_init_request;
+ c->write->handler = ngx_http_empty_handler;
+
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_reading, 1);
+#endif
+
+ if (rev->ready) {
+ /* the deferred accept(), rtsig, aio, iocp */
+
+ if (ngx_use_accept_mutex) {
+ ngx_post_event(rev, &ngx_posted_events);
+ return;
+ }
+
+ ngx_http_init_request(rev);
+ return;
+ }
+
+ ngx_add_timer(rev, c->listening->post_accept_timeout);
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_reading, -1);
+#endif
+ ngx_http_close_connection(c);
+ return;
+ }
+}
+
+
+static void
+ngx_http_init_request(ngx_event_t *rev)
+{
+ ngx_time_t *tp;
+ ngx_uint_t i;
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+ struct sockaddr_in *sin;
+ ngx_http_port_t *port;
+ ngx_http_in_addr_t *addr;
+ ngx_http_log_ctx_t *ctx;
+ ngx_http_addr_conf_t *addr_conf;
+ ngx_http_connection_t *hc;
+ ngx_http_core_srv_conf_t *cscf;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_core_main_conf_t *cmcf;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+ ngx_http_in6_addr_t *addr6;
+#endif
+
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_reading, -1);
+#endif
+
+ c = rev->data;
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ c->requests++;
+
+ hc = c->data;
+
+ if (hc == NULL) {
+ hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
+ if (hc == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+ }
+
+ r = hc->request;
+
+ if (r) {
+ ngx_memzero(r, sizeof(ngx_http_request_t));
+
+ r->pipeline = hc->pipeline;
+
+ if (hc->nbusy) {
+ r->header_in = hc->busy[0];
+ }
+
+ } else {
+ r = ngx_pcalloc(c->pool, sizeof(ngx_http_request_t));
+ if (r == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ hc->request = r;
+ }
+
+ c->data = r;
+ r->http_connection = hc;
+
+ c->sent = 0;
+ r->signature = NGX_HTTP_MODULE;
+
+ /* find the server configuration for the address:port */
+
+ port = c->listening->servers;
+
+ r->connection = c;
+
+ if (port->naddrs > 1) {
+
+ /*
+ * there are several addresses on this port and one of them
+ * is an "*:port" wildcard so getsockname() in ngx_http_server_addr()
+ * is required to determine a server address
+ */
+
+ if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ switch (c->local_sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) c->local_sockaddr;
+
+ addr6 = port->addrs;
+
+ /* the last address is "*" */
+
+ for (i = 0; i < port->naddrs - 1; i++) {
+ if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) {
+ break;
+ }
+ }
+
+ addr_conf = &addr6[i].conf;
+
+ break;
+#endif
+
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) c->local_sockaddr;
+
+ addr = port->addrs;
+
+ /* the last address is "*" */
+
+ for (i = 0; i < port->naddrs - 1; i++) {
+ if (addr[i].addr == sin->sin_addr.s_addr) {
+ break;
+ }
+ }
+
+ addr_conf = &addr[i].conf;
+
+ break;
+ }
+
+ } else {
+
+ switch (c->local_sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ addr6 = port->addrs;
+ addr_conf = &addr6[0].conf;
+ break;
+#endif
+
+ default: /* AF_INET */
+ addr = port->addrs;
+ addr_conf = &addr[0].conf;
+ break;
+ }
+ }
+
+ r->virtual_names = addr_conf->virtual_names;
+
+ /* the default server configuration for the address:port */
+ cscf = addr_conf->default_server;
+
+ r->main_conf = cscf->ctx->main_conf;
+ r->srv_conf = cscf->ctx->srv_conf;
+ r->loc_conf = cscf->ctx->loc_conf;
+
+ rev->handler = ngx_http_process_request_line;
+ r->read_event_handler = ngx_http_block_reading;
+
+#if (NGX_HTTP_SSL)
+
+ {
+ ngx_http_ssl_srv_conf_t *sscf;
+
+ sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module);
+ if (sscf->enable || addr_conf->ssl) {
+
+ if (c->ssl == NULL) {
+
+ c->log->action = "SSL handshaking";
+
+ if (addr_conf->ssl && sscf->ssl.ctx == NULL) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "no \"ssl_certificate\" is defined "
+ "in server listening on SSL port");
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ if (ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER)
+ != NGX_OK)
+ {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ rev->handler = ngx_http_ssl_handshake;
+ }
+
+ r->main_filter_need_in_memory = 1;
+ }
+ }
+
+#endif
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+ c->log->file = clcf->error_log->file;
+ if (!(c->log->log_level & NGX_LOG_DEBUG_CONNECTION)) {
+ c->log->log_level = clcf->error_log->log_level;
+ }
+
+ if (c->buffer == NULL) {
+ c->buffer = ngx_create_temp_buf(c->pool,
+ cscf->client_header_buffer_size);
+ if (c->buffer == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+ }
+
+ if (r->header_in == NULL) {
+ r->header_in = c->buffer;
+ }
+
+ r->pool = ngx_create_pool(cscf->request_pool_size, c->log);
+ if (r->pool == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+
+ if (ngx_list_init(&r->headers_out.headers, r->pool, 20,
+ sizeof(ngx_table_elt_t))
+ != NGX_OK)
+ {
+ ngx_destroy_pool(r->pool);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
+ if (r->ctx == NULL) {
+ ngx_destroy_pool(r->pool);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ r->variables = ngx_pcalloc(r->pool, cmcf->variables.nelts
+ * sizeof(ngx_http_variable_value_t));
+ if (r->variables == NULL) {
+ ngx_destroy_pool(r->pool);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ c->single_connection = 1;
+ c->destroyed = 0;
+
+ r->main = r;
+ r->count = 1;
+
+ tp = ngx_timeofday();
+ r->start_sec = tp->sec;
+ r->start_msec = tp->msec;
+
+ r->method = NGX_HTTP_UNKNOWN;
+
+ r->headers_in.content_length_n = -1;
+ r->headers_in.keep_alive_n = -1;
+ r->headers_out.content_length_n = -1;
+ r->headers_out.last_modified_time = -1;
+
+ r->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;
+ r->subrequests = NGX_HTTP_MAX_SUBREQUESTS + 1;
+
+ r->http_state = NGX_HTTP_READING_REQUEST_STATE;
+
+ ctx = c->log->data;
+ ctx->request = r;
+ ctx->current_request = r;
+ r->log_handler = ngx_http_log_error_handler;
+
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_reading, 1);
+ r->stat_reading = 1;
+ (void) ngx_atomic_fetch_add(ngx_stat_requests, 1);
+#endif
+
+ rev->handler(rev);
+}
+
+
+#if (NGX_HTTP_SSL)
+
+static void
+ngx_http_ssl_handshake(ngx_event_t *rev)
+{
+ u_char buf[1];
+ ssize_t n;
+ ngx_int_t rc;
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+
+ c = rev->data;
+ r = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+ "http check ssl handshake");
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
+ return;
+ }
+
+ n = recv(c->fd, (char *) buf, 1, MSG_PEEK);
+
+ if (n == -1 && ngx_socket_errno == NGX_EAGAIN) {
+
+ if (!rev->timer_set) {
+ ngx_add_timer(rev, c->listening->post_accept_timeout);
+ }
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ }
+
+ return;
+ }
+
+ if (n == 1) {
+ if (buf[0] & 0x80 /* SSLv2 */ || buf[0] == 0x16 /* SSLv3/TLSv1 */) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+ "https ssl handshake: 0x%02Xd", buf[0]);
+
+ rc = ngx_ssl_handshake(c);
+
+ if (rc == NGX_AGAIN) {
+
+ if (!rev->timer_set) {
+ ngx_add_timer(rev, c->listening->post_accept_timeout);
+ }
+
+ c->ssl->handler = ngx_http_ssl_handshake_handler;
+ return;
+ }
+
+ ngx_http_ssl_handshake_handler(c);
+
+ return;
+
+ } else {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+ "plain http");
+
+ r->plain_http = 1;
+ }
+ }
+
+ c->log->action = "reading client request line";
+
+ rev->handler = ngx_http_process_request_line;
+ ngx_http_process_request_line(rev);
+}
+
+
+static void
+ngx_http_ssl_handshake_handler(ngx_connection_t *c)
+{
+ ngx_http_request_t *r;
+
+ if (c->ssl->handshaked) {
+
+ /*
+ * The majority of browsers do not send the "close notify" alert.
+ * Among them are MSIE, old Mozilla, Netscape 4, Konqueror,
+ * and Links. And what is more, MSIE ignores the server's alert.
+ *
+ * Opera and recent Mozilla send the alert.
+ */
+
+ c->ssl->no_wait_shutdown = 1;
+
+ c->read->handler = ngx_http_process_request_line;
+ /* STUB: epoll edge */ c->write->handler = ngx_http_empty_handler;
+
+ ngx_http_process_request_line(c->read);
+
+ return;
+ }
+
+ r = c->data;
+
+ ngx_http_close_request(r, NGX_HTTP_BAD_REQUEST);
+
+ return;
+}
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+
+int
+ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg)
+{
+ size_t len;
+ u_char *host;
+ const char *servername;
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+ ngx_http_ssl_srv_conf_t *sscf;
+
+ servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name);
+
+ if (servername == NULL) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ c = ngx_ssl_get_connection(ssl_conn);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "SSL server name: \"%s\"", servername);
+
+ len = ngx_strlen(servername);
+
+ if (len == 0) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ r = c->data;
+
+ host = (u_char *) servername;
+
+ len = ngx_http_validate_host(r, &host, len, 1);
+
+ if (len <= 0) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ if (ngx_http_find_virtual_server(r, host, len) != NGX_OK) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module);
+
+ SSL_set_SSL_CTX(ssl_conn, sscf->ssl.ctx);
+
+ return SSL_TLSEXT_ERR_OK;
+}
+
+#endif
+
+#endif
+
+
+static void
+ngx_http_process_request_line(ngx_event_t *rev)
+{
+ u_char *host;
+ ssize_t n;
+ ngx_int_t rc, rv;
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+ ngx_http_core_srv_conf_t *cscf;
+
+ c = rev->data;
+ r = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+ "http process request line");
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
+ return;
+ }
+
+ rc = NGX_AGAIN;
+
+ for ( ;; ) {
+
+ if (rc == NGX_AGAIN) {
+ n = ngx_http_read_request_header(r);
+
+ if (n == NGX_AGAIN || n == NGX_ERROR) {
+ return;
+ }
+ }
+
+ rc = ngx_http_parse_request_line(r, r->header_in);
+
+ if (rc == NGX_OK) {
+
+ /* the request line has been parsed successfully */
+
+ r->request_line.len = r->request_end - r->request_start;
+ r->request_line.data = r->request_start;
+
+
+ if (r->args_start) {
+ r->uri.len = r->args_start - 1 - r->uri_start;
+ } else {
+ r->uri.len = r->uri_end - r->uri_start;
+ }
+
+
+ if (r->complex_uri || r->quoted_uri) {
+
+ r->uri.data = ngx_pnalloc(r->pool, r->uri.len + 1);
+ if (r->uri.data == NULL) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ rc = ngx_http_parse_complex_uri(r, cscf->merge_slashes);
+
+ if (rc == NGX_HTTP_PARSE_INVALID_REQUEST) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid request");
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return;
+ }
+
+ } else {
+ r->uri.data = r->uri_start;
+ }
+
+
+ r->unparsed_uri.len = r->uri_end - r->uri_start;
+ r->unparsed_uri.data = r->uri_start;
+
+ r->valid_unparsed_uri = r->space_in_uri ? 0 : 1;
+
+ r->method_name.len = r->method_end - r->request_start + 1;
+ r->method_name.data = r->request_line.data;
+
+
+ if (r->http_protocol.data) {
+ r->http_protocol.len = r->request_end - r->http_protocol.data;
+ }
+
+
+ if (r->uri_ext) {
+ if (r->args_start) {
+ r->exten.len = r->args_start - 1 - r->uri_ext;
+ } else {
+ r->exten.len = r->uri_end - r->uri_ext;
+ }
+
+ r->exten.data = r->uri_ext;
+ }
+
+
+ if (r->args_start && r->uri_end > r->args_start) {
+ r->args.len = r->uri_end - r->args_start;
+ r->args.data = r->args_start;
+ }
+
+#if (NGX_WIN32)
+ {
+ u_char *p;
+
+ p = r->uri.data + r->uri.len - 1;
+
+ while (p > r->uri.data) {
+
+ if (*p == ' ') {
+ p--;
+ continue;
+ }
+
+ if (*p == '.') {
+ p--;
+ continue;
+ }
+
+ if (ngx_strncasecmp(p - 6, (u_char *) "::$data", 7) == 0) {
+ p -= 7;
+ continue;
+ }
+
+ break;
+ }
+
+ if (p != r->uri.data + r->uri.len - 1) {
+ r->uri.len = p + 1 - r->uri.data;
+ ngx_http_set_exten(r);
+ }
+
+ }
+#endif
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http request line: \"%V\"", &r->request_line);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http uri: \"%V\"", &r->uri);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http args: \"%V\"", &r->args);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http exten: \"%V\"", &r->exten);
+
+ if (r->host_start && r->host_end) {
+
+ host = r->host_start;
+ n = ngx_http_validate_host(r, &host,
+ r->host_end - r->host_start, 0);
+
+ if (n == 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid host in request line");
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return;
+ }
+
+ if (n < 0) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ r->headers_in.server.len = n;
+ r->headers_in.server.data = host;
+ }
+
+ if (r->http_version < NGX_HTTP_VERSION_10) {
+
+ if (ngx_http_find_virtual_server(r, r->headers_in.server.data,
+ r->headers_in.server.len)
+ == NGX_ERROR)
+ {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ ngx_http_process_request(r);
+ return;
+ }
+
+
+ if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
+ sizeof(ngx_table_elt_t))
+ != NGX_OK)
+ {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+
+ if (ngx_array_init(&r->headers_in.cookies, r->pool, 2,
+ sizeof(ngx_table_elt_t *))
+ != NGX_OK)
+ {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ c->log->action = "reading client request headers";
+
+ rev->handler = ngx_http_process_request_headers;
+ ngx_http_process_request_headers(rev);
+
+ return;
+ }
+
+ if (rc != NGX_AGAIN) {
+
+ /* there was error while a request line parsing */
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ ngx_http_client_errors[rc - NGX_HTTP_CLIENT_ERROR]);
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return;
+ }
+
+ /* NGX_AGAIN: a request line parsing is still incomplete */
+
+ if (r->header_in->pos == r->header_in->end) {
+
+ rv = ngx_http_alloc_large_header_buffer(r, 1);
+
+ if (rv == NGX_ERROR) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ if (rv == NGX_DECLINED) {
+ r->request_line.len = r->header_in->end - r->request_start;
+ r->request_line.data = r->request_start;
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent too long URI");
+ ngx_http_finalize_request(r, NGX_HTTP_REQUEST_URI_TOO_LARGE);
+ return;
+ }
+ }
+ }
+}
+
+
+static void
+ngx_http_process_request_headers(ngx_event_t *rev)
+{
+ u_char *p;
+ size_t len;
+ ssize_t n;
+ ngx_int_t rc, rv;
+ ngx_table_elt_t *h;
+ ngx_connection_t *c;
+ ngx_http_header_t *hh;
+ ngx_http_request_t *r;
+ ngx_http_core_srv_conf_t *cscf;
+ ngx_http_core_main_conf_t *cmcf;
+
+ c = rev->data;
+ r = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+ "http process request header line");
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
+ return;
+ }
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ rc = NGX_AGAIN;
+
+ for ( ;; ) {
+
+ if (rc == NGX_AGAIN) {
+
+ if (r->header_in->pos == r->header_in->end) {
+
+ rv = ngx_http_alloc_large_header_buffer(r, 0);
+
+ if (rv == NGX_ERROR) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ if (rv == NGX_DECLINED) {
+ p = r->header_name_start;
+
+ r->lingering_close = 1;
+
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent too large request");
+ ngx_http_finalize_request(r,
+ NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
+ return;
+ }
+
+ len = r->header_in->end - p;
+
+ if (len > NGX_MAX_ERROR_STR - 300) {
+ len = NGX_MAX_ERROR_STR - 300;
+ p[len++] = '.'; p[len++] = '.'; p[len++] = '.';
+ }
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent too long header line: \"%*s\"",
+ len, r->header_name_start);
+
+ ngx_http_finalize_request(r,
+ NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
+ return;
+ }
+ }
+
+ n = ngx_http_read_request_header(r);
+
+ if (n == NGX_AGAIN || n == NGX_ERROR) {
+ return;
+ }
+ }
+
+ rc = ngx_http_parse_header_line(r, r->header_in,
+ cscf->underscores_in_headers);
+
+ if (rc == NGX_OK) {
+
+ if (r->invalid_header && cscf->ignore_invalid_headers) {
+
+ /* there was error while a header line parsing */
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid header line: \"%*s\"",
+ r->header_end - r->header_name_start,
+ r->header_name_start);
+ continue;
+ }
+
+ /* a header line has been parsed successfully */
+
+ h = ngx_list_push(&r->headers_in.headers);
+ if (h == NULL) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ h->hash = r->header_hash;
+
+ h->key.len = r->header_name_end - r->header_name_start;
+ h->key.data = r->header_name_start;
+ h->key.data[h->key.len] = '\0';
+
+ h->value.len = r->header_end - r->header_start;
+ h->value.data = r->header_start;
+ h->value.data[h->value.len] = '\0';
+
+ h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
+ if (h->lowcase_key == NULL) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ if (h->key.len == r->lowcase_index) {
+ ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
+
+ } else {
+ ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
+ }
+
+ hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
+ h->lowcase_key, h->key.len);
+
+ if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+ return;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http header: \"%V: %V\"",
+ &h->key, &h->value);
+
+ continue;
+ }
+
+ if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+ /* a whole header has been parsed successfully */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http header done");
+
+ r->request_length += r->header_in->pos - r->header_in->start;
+
+ r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
+
+ rc = ngx_http_process_request_header(r);
+
+ if (rc != NGX_OK) {
+ return;
+ }
+
+ ngx_http_process_request(r);
+
+ return;
+ }
+
+ if (rc == NGX_AGAIN) {
+
+ /* a header line parsing is still not complete */
+
+ continue;
+ }
+
+ /* rc == NGX_HTTP_PARSE_INVALID_HEADER: "\r" is not followed by "\n" */
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid header line: \"%*s\\r...\"",
+ r->header_end - r->header_name_start,
+ r->header_name_start);
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return;
+ }
+}
+
+
+static ssize_t
+ngx_http_read_request_header(ngx_http_request_t *r)
+{
+ ssize_t n;
+ ngx_event_t *rev;
+ ngx_connection_t *c;
+ ngx_http_core_srv_conf_t *cscf;
+
+ c = r->connection;
+ rev = c->read;
+
+ n = r->header_in->last - r->header_in->pos;
+
+ if (n > 0) {
+ return n;
+ }
+
+ if (rev->ready) {
+ n = c->recv(c, r->header_in->last,
+ r->header_in->end - r->header_in->last);
+ } else {
+ n = NGX_AGAIN;
+ }
+
+ if (n == NGX_AGAIN) {
+ if (!rev->timer_set) {
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+ ngx_add_timer(rev, cscf->client_header_timeout);
+ }
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ if (n == 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client closed prematurely connection");
+ }
+
+ if (n == 0 || n == NGX_ERROR) {
+ c->error = 1;
+ c->log->action = "reading client request headers";
+
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return NGX_ERROR;
+ }
+
+ r->header_in->last += n;
+
+ return n;
+}
+
+
+static ngx_int_t
+ngx_http_alloc_large_header_buffer(ngx_http_request_t *r,
+ ngx_uint_t request_line)
+{
+ u_char *old, *new;
+ ngx_buf_t *b;
+ ngx_http_connection_t *hc;
+ ngx_http_core_srv_conf_t *cscf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http alloc large header buffer");
+
+ if (request_line && r->state == 0) {
+
+ /* the client fills up the buffer with "\r\n" */
+
+ r->request_length += r->header_in->end - r->header_in->start;
+
+ r->header_in->pos = r->header_in->start;
+ r->header_in->last = r->header_in->start;
+
+ return NGX_OK;
+ }
+
+ old = request_line ? r->request_start : r->header_name_start;
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ if (r->state != 0
+ && (size_t) (r->header_in->pos - old)
+ >= cscf->large_client_header_buffers.size)
+ {
+ return NGX_DECLINED;
+ }
+
+ hc = r->http_connection;
+
+ if (hc->nfree) {
+ b = hc->free[--hc->nfree];
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http large header free: %p %uz",
+ b->pos, b->end - b->last);
+
+ } else if (hc->nbusy < cscf->large_client_header_buffers.num) {
+
+ if (hc->busy == NULL) {
+ hc->busy = ngx_palloc(r->connection->pool,
+ cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));
+ if (hc->busy == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ b = ngx_create_temp_buf(r->connection->pool,
+ cscf->large_client_header_buffers.size);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http large header alloc: %p %uz",
+ b->pos, b->end - b->last);
+
+ } else {
+ return NGX_DECLINED;
+ }
+
+ hc->busy[hc->nbusy++] = b;
+
+ if (r->state == 0) {
+ /*
+ * r->state == 0 means that a header line was parsed successfully
+ * and we do not need to copy incomplete header line and
+ * to relocate the parser header pointers
+ */
+
+ r->request_length += r->header_in->end - r->header_in->start;
+
+ r->header_in = b;
+
+ return NGX_OK;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http large header copy: %d", r->header_in->pos - old);
+
+ r->request_length += old - r->header_in->start;
+
+ new = b->start;
+
+ ngx_memcpy(new, old, r->header_in->pos - old);
+
+ b->pos = new + (r->header_in->pos - old);
+ b->last = new + (r->header_in->pos - old);
+
+ if (request_line) {
+ r->request_start = new;
+
+ if (r->request_end) {
+ r->request_end = new + (r->request_end - old);
+ }
+
+ r->method_end = new + (r->method_end - old);
+
+ r->uri_start = new + (r->uri_start - old);
+ r->uri_end = new + (r->uri_end - old);
+
+ if (r->schema_start) {
+ r->schema_start = new + (r->schema_start - old);
+ r->schema_end = new + (r->schema_end - old);
+ }
+
+ if (r->host_start) {
+ r->host_start = new + (r->host_start - old);
+ if (r->host_end) {
+ r->host_end = new + (r->host_end - old);
+ }
+ }
+
+ if (r->port_start) {
+ r->port_start = new + (r->port_start - old);
+ r->port_end = new + (r->port_end - old);
+ }
+
+ if (r->uri_ext) {
+ r->uri_ext = new + (r->uri_ext - old);
+ }
+
+ if (r->args_start) {
+ r->args_start = new + (r->args_start - old);
+ }
+
+ if (r->http_protocol.data) {
+ r->http_protocol.data = new + (r->http_protocol.data - old);
+ }
+
+ } else {
+ r->header_name_start = new;
+ r->header_name_end = new + (r->header_name_end - old);
+ r->header_start = new + (r->header_start - old);
+ r->header_end = new + (r->header_end - old);
+ }
+
+ r->header_in = b;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ ngx_table_elt_t **ph;
+
+ ph = (ngx_table_elt_t **) ((char *) &r->headers_in + offset);
+
+ if (*ph == NULL) {
+ *ph = h;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_process_unique_header_line(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ ngx_table_elt_t **ph;
+
+ ph = (ngx_table_elt_t **) ((char *) &r->headers_in + offset);
+
+ if (*ph == NULL) {
+ *ph = h;
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent duplicate header line: \"%V: %V\", "
+ "previous value: \"%V: %V\"",
+ &h->key, &h->value, &(*ph)->key, &(*ph)->value);
+
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_process_host(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ u_char *host;
+ ssize_t len;
+
+ if (r->headers_in.host == NULL) {
+ r->headers_in.host = h;
+ }
+
+ host = h->value.data;
+ len = ngx_http_validate_host(r, &host, h->value.len, 0);
+
+ if (len == 0) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent invalid host header");
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return NGX_ERROR;
+ }
+
+ if (len < 0) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_ERROR;
+ }
+
+ if (r->headers_in.server.len) {
+ return NGX_OK;
+ }
+
+ r->headers_in.server.len = len;
+ r->headers_in.server.data = host;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ if (ngx_strcasestrn(h->value.data, "close", 5 - 1)) {
+ r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
+
+ } else if (ngx_strcasestrn(h->value.data, "keep-alive", 10 - 1)) {
+ r->headers_in.connection_type = NGX_HTTP_CONNECTION_KEEP_ALIVE;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_process_user_agent(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ u_char *user_agent, *msie;
+
+ if (r->headers_in.user_agent) {
+ return NGX_OK;
+ }
+
+ r->headers_in.user_agent = h;
+
+ /* check some widespread browsers while the header is in CPU cache */
+
+ user_agent = h->value.data;
+
+ msie = ngx_strstrn(user_agent, "MSIE ", 5 - 1);
+
+ if (msie && msie + 7 < user_agent + h->value.len) {
+
+ r->headers_in.msie = 1;
+
+ if (msie[6] == '.') {
+
+ switch (msie[5]) {
+ case '4':
+ case '5':
+ r->headers_in.msie6 = 1;
+ break;
+ case '6':
+ if (ngx_strstrn(msie + 8, "SV1", 3 - 1) == NULL) {
+ r->headers_in.msie6 = 1;
+ }
+ break;
+ }
+ }
+
+#if 0
+ /* MSIE ignores the SSL "close notify" alert */
+ if (c->ssl) {
+ c->ssl->no_send_shutdown = 1;
+ }
+#endif
+ }
+
+ if (ngx_strstrn(user_agent, "Opera", 5 - 1)) {
+ r->headers_in.opera = 1;
+ r->headers_in.msie = 0;
+ r->headers_in.msie6 = 0;
+ }
+
+ if (!r->headers_in.msie && !r->headers_in.opera) {
+
+ if (ngx_strstrn(user_agent, "Gecko/", 6 - 1)) {
+ r->headers_in.gecko = 1;
+
+ } else if (ngx_strstrn(user_agent, "Chrome/", 7 - 1)) {
+ r->headers_in.chrome = 1;
+
+ } else if (ngx_strstrn(user_agent, "Safari/", 7 - 1)) {
+ r->headers_in.safari = 1;
+
+ } else if (ngx_strstrn(user_agent, "Konqueror", 9 - 1)) {
+ r->headers_in.konqueror = 1;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_process_cookie(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ ngx_table_elt_t **cookie;
+
+ cookie = ngx_array_push(&r->headers_in.cookies);
+ if (cookie) {
+ *cookie = h;
+ return NGX_OK;
+ }
+
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_process_request_header(ngx_http_request_t *r)
+{
+ if (ngx_http_find_virtual_server(r, r->headers_in.server.data,
+ r->headers_in.server.len)
+ == NGX_ERROR)
+ {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_ERROR;
+ }
+
+ if (r->headers_in.host == NULL && r->http_version > NGX_HTTP_VERSION_10) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent HTTP/1.1 request without \"Host\" header");
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return NGX_ERROR;
+ }
+
+ if (r->headers_in.content_length) {
+ r->headers_in.content_length_n =
+ ngx_atoof(r->headers_in.content_length->value.data,
+ r->headers_in.content_length->value.len);
+
+ if (r->headers_in.content_length_n == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent invalid \"Content-Length\" header");
+ ngx_http_finalize_request(r, NGX_HTTP_LENGTH_REQUIRED);
+ return NGX_ERROR;
+ }
+ }
+
+ if (r->method & NGX_HTTP_PUT && r->headers_in.content_length_n == -1) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent %V method without \"Content-Length\" header",
+ &r->method_name);
+ ngx_http_finalize_request(r, NGX_HTTP_LENGTH_REQUIRED);
+ return NGX_ERROR;
+ }
+
+ if (r->method & NGX_HTTP_TRACE) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent TRACE method");
+ ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED);
+ return NGX_ERROR;
+ }
+
+ if (r->headers_in.transfer_encoding
+ && ngx_strcasestrn(r->headers_in.transfer_encoding->value.data,
+ "chunked", 7 - 1))
+ {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent \"Transfer-Encoding: chunked\" header");
+ ngx_http_finalize_request(r, NGX_HTTP_LENGTH_REQUIRED);
+ return NGX_ERROR;
+ }
+
+ if (r->headers_in.connection_type == NGX_HTTP_CONNECTION_KEEP_ALIVE) {
+ if (r->headers_in.keep_alive) {
+ r->headers_in.keep_alive_n =
+ ngx_atotm(r->headers_in.keep_alive->value.data,
+ r->headers_in.keep_alive->value.len);
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_process_request(ngx_http_request_t *r)
+{
+ ngx_connection_t *c;
+
+ c = r->connection;
+
+ if (r->plain_http) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent plain HTTP request to HTTPS port");
+ ngx_http_finalize_request(r, NGX_HTTP_TO_HTTPS);
+ return;
+ }
+
+#if (NGX_HTTP_SSL)
+
+ if (c->ssl) {
+ long rc;
+ X509 *cert;
+ ngx_http_ssl_srv_conf_t *sscf;
+
+ sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module);
+
+ if (sscf->verify) {
+ rc = SSL_get_verify_result(c->ssl->connection);
+
+ if (rc != X509_V_OK) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client SSL certificate verify error: (%l:%s)",
+ rc, X509_verify_cert_error_string(rc));
+
+ ngx_ssl_remove_cached_session(sscf->ssl.ctx,
+ (SSL_get0_session(c->ssl->connection)));
+
+ ngx_http_finalize_request(r, NGX_HTTPS_CERT_ERROR);
+ return;
+ }
+
+ if (sscf->verify == 1) {
+ cert = SSL_get_peer_certificate(c->ssl->connection);
+
+ if (cert == NULL) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent no required SSL certificate");
+
+ ngx_ssl_remove_cached_session(sscf->ssl.ctx,
+ (SSL_get0_session(c->ssl->connection)));
+
+ ngx_http_finalize_request(r, NGX_HTTPS_NO_CERT);
+ return;
+ }
+
+ X509_free(cert);
+ }
+ }
+ }
+
+#endif
+
+ if (c->read->timer_set) {
+ ngx_del_timer(c->read);
+ }
+
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_reading, -1);
+ r->stat_reading = 0;
+ (void) ngx_atomic_fetch_add(ngx_stat_writing, 1);
+ r->stat_writing = 1;
+#endif
+
+ c->read->handler = ngx_http_request_handler;
+ c->write->handler = ngx_http_request_handler;
+ r->read_event_handler = ngx_http_block_reading;
+
+ ngx_http_handler(r);
+
+ ngx_http_run_posted_requests(c);
+}
+
+
+static ssize_t
+ngx_http_validate_host(ngx_http_request_t *r, u_char **host, size_t len,
+ ngx_uint_t alloc)
+{
+ u_char *h, ch;
+ size_t i, last;
+ ngx_uint_t dot;
+
+ last = len;
+ h = *host;
+ dot = 0;
+
+ for (i = 0; i < len; i++) {
+ ch = h[i];
+
+ if (ch == '.') {
+ if (dot) {
+ return 0;
+ }
+
+ dot = 1;
+ continue;
+ }
+
+ dot = 0;
+
+ if (ch == ':') {
+ last = i;
+ continue;
+ }
+
+ if (ngx_path_separator(ch) || ch == '\0') {
+ return 0;
+ }
+
+ if (ch >= 'A' || ch < 'Z') {
+ alloc = 1;
+ }
+ }
+
+ if (dot) {
+ last--;
+ }
+
+ if (alloc) {
+ *host = ngx_pnalloc(r->pool, last) ;
+ if (*host == NULL) {
+ return -1;
+ }
+
+ ngx_strlow(*host, h, last);
+ }
+
+ return last;
+}
+
+
+static ngx_int_t
+ngx_http_find_virtual_server(ngx_http_request_t *r, u_char *host, size_t len)
+{
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_core_srv_conf_t *cscf;
+
+ if (r->virtual_names == NULL) {
+ return NGX_DECLINED;
+ }
+
+ cscf = ngx_hash_find_combined(&r->virtual_names->names,
+ ngx_hash_key(host, len), host, len);
+
+ if (cscf) {
+ goto found;
+ }
+
+#if (NGX_PCRE)
+
+ if (len && r->virtual_names->nregex) {
+ ngx_int_t n;
+ ngx_uint_t i;
+ ngx_str_t name;
+ ngx_http_server_name_t *sn;
+
+ name.len = len;
+ name.data = host;
+
+ sn = r->virtual_names->regex;
+
+ for (i = 0; i < r->virtual_names->nregex; i++) {
+
+ n = ngx_http_regex_exec(r, sn[i].regex, &name);
+
+ if (n == NGX_OK) {
+ cscf = sn[i].server;
+ goto found;
+ }
+
+ if (n == NGX_DECLINED) {
+ continue;
+ }
+
+ return NGX_ERROR;
+ }
+ }
+
+#endif
+
+ return NGX_OK;
+
+found:
+
+ r->srv_conf = cscf->ctx->srv_conf;
+ r->loc_conf = cscf->ctx->loc_conf;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+ r->connection->log->file = clcf->error_log->file;
+
+ if (!(r->connection->log->log_level & NGX_LOG_DEBUG_CONNECTION)) {
+ r->connection->log->log_level = clcf->error_log->log_level;
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_request_handler(ngx_event_t *ev)
+{
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+ ngx_http_log_ctx_t *ctx;
+
+ c = ev->data;
+ r = c->data;
+
+ ctx = c->log->data;
+ ctx->current_request = r;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http run request: \"%V?%V\"", &r->uri, &r->args);
+
+ if (ev->write) {
+ r->write_event_handler(r);
+
+ } else {
+ r->read_event_handler(r);
+ }
+
+ ngx_http_run_posted_requests(c);
+}
+
+
+void
+ngx_http_run_posted_requests(ngx_connection_t *c)
+{
+ ngx_http_request_t *r;
+ ngx_http_log_ctx_t *ctx;
+ ngx_http_posted_request_t *pr;
+
+ for ( ;; ) {
+
+ if (c->destroyed) {
+ return;
+ }
+
+ r = c->data;
+ pr = r->main->posted_requests;
+
+ if (pr == NULL) {
+ return;
+ }
+
+ r->main->posted_requests = pr->next;
+
+ r = pr->request;
+
+ ctx = c->log->data;
+ ctx->current_request = r;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http posted request: \"%V?%V\"", &r->uri, &r->args);
+
+ r->write_event_handler(r);
+ }
+}
+
+
+ngx_int_t
+ngx_http_post_request(ngx_http_request_t *r, ngx_http_posted_request_t *pr)
+{
+ ngx_http_posted_request_t **p;
+
+ if (pr == NULL) {
+ pr = ngx_palloc(r->pool, sizeof(ngx_http_posted_request_t));
+ if (pr == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ pr->request = r;
+ pr->next = NULL;
+
+ for (p = &r->main->posted_requests; *p; p = &(*p)->next) { /* void */ }
+
+ *p = pr;
+
+ return NGX_OK;
+}
+
+
+void
+ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+ ngx_connection_t *c;
+ ngx_http_request_t *pr;
+ ngx_http_core_loc_conf_t *clcf;
+
+ c = r->connection;
+
+ ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http finalize request: %d, \"%V?%V\" a:%d, c:%d",
+ rc, &r->uri, &r->args, r == c->data, r->main->count);
+
+ if (rc == NGX_DONE) {
+ ngx_http_finalize_connection(r);
+ return;
+ }
+
+ if (rc == NGX_OK && r->filter_finalize) {
+ c->error = 1;
+ return;
+ }
+
+ if (rc == NGX_DECLINED) {
+ r->content_handler = NULL;
+ r->write_event_handler = ngx_http_core_run_phases;
+ ngx_http_core_run_phases(r);
+ return;
+ }
+
+ if (r != r->main && r->post_subrequest) {
+ rc = r->post_subrequest->handler(r, r->post_subrequest->data, rc);
+ }
+
+ if (rc == NGX_ERROR
+ || rc == NGX_HTTP_REQUEST_TIME_OUT
+ || rc == NGX_HTTP_CLIENT_CLOSED_REQUEST
+ || c->error)
+ {
+ if (ngx_http_post_action(r) == NGX_OK) {
+ return;
+ }
+
+ if (r->main->blocked) {
+ r->write_event_handler = ngx_http_request_finalizer;
+ }
+
+ ngx_http_terminate_request(r, rc);
+ return;
+ }
+
+ if (rc >= NGX_HTTP_SPECIAL_RESPONSE
+ || rc == NGX_HTTP_CREATED
+ || rc == NGX_HTTP_NO_CONTENT)
+ {
+ if (rc == NGX_HTTP_CLOSE) {
+ ngx_http_terminate_request(r, rc);
+ return;
+ }
+
+ if (r == r->main) {
+ if (c->read->timer_set) {
+ ngx_del_timer(c->read);
+ }
+
+ if (c->write->timer_set) {
+ ngx_del_timer(c->write);
+ }
+ }
+
+ c->read->handler = ngx_http_request_handler;
+ c->write->handler = ngx_http_request_handler;
+
+ ngx_http_finalize_request(r, ngx_http_special_response_handler(r, rc));
+ return;
+ }
+
+ if (r != r->main) {
+
+ if (r->buffered || r->postponed) {
+
+ if (ngx_http_set_write_handler(r) != NGX_OK) {
+ ngx_http_terminate_request(r, 0);
+ }
+
+ return;
+ }
+
+#if (NGX_DEBUG)
+ if (r != c->data) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http finalize non-active request: \"%V?%V\"",
+ &r->uri, &r->args);
+ }
+#endif
+
+ pr = r->parent;
+
+ if (r == c->data) {
+
+ r->main->count--;
+
+ if (!r->logged) {
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->log_subrequest) {
+ ngx_http_log_request(r);
+ }
+
+ r->logged = 1;
+
+ } else {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "subrequest: \"%V?%V\" logged again",
+ &r->uri, &r->args);
+ }
+
+ r->done = 1;
+
+ if (pr->postponed && pr->postponed->request == r) {
+ pr->postponed = pr->postponed->next;
+ }
+
+ c->data = pr;
+
+ } else {
+
+ r->write_event_handler = ngx_http_request_finalizer;
+
+ if (r->waited) {
+ r->done = 1;
+ }
+ }
+
+ if (ngx_http_post_request(pr, NULL) != NGX_OK) {
+ r->main->count++;
+ ngx_http_terminate_request(r, 0);
+ return;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http wake parent request: \"%V?%V\"",
+ &pr->uri, &pr->args);
+
+ return;
+ }
+
+ if (r->buffered || c->buffered || r->postponed || r->blocked) {
+
+ if (ngx_http_set_write_handler(r) != NGX_OK) {
+ ngx_http_terminate_request(r, 0);
+ }
+
+ return;
+ }
+
+ if (r != c->data) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "http finalize non-active request: \"%V?%V\"",
+ &r->uri, &r->args);
+ return;
+ }
+
+ r->done = 1;
+ r->write_event_handler = ngx_http_request_empty_handler;
+
+ if (!r->post_action) {
+ r->request_complete = 1;
+ }
+
+ if (ngx_http_post_action(r) == NGX_OK) {
+ return;
+ }
+
+ if (c->read->timer_set) {
+ ngx_del_timer(c->read);
+ }
+
+ if (c->write->timer_set) {
+ c->write->delayed = 0;
+ ngx_del_timer(c->write);
+ }
+
+ if (c->read->eof) {
+ ngx_http_close_request(r, 0);
+ return;
+ }
+
+ ngx_http_finalize_connection(r);
+}
+
+
+static void
+ngx_http_terminate_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+ ngx_http_cleanup_t *cln;
+ ngx_http_request_t *mr;
+ ngx_http_ephemeral_t *e;
+
+ mr = r->main;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http terminate request count:%d", mr->count);
+
+ if (rc > 0 && (mr->headers_out.status == 0 || mr->connection->sent == 0)) {
+ mr->headers_out.status = rc;
+ }
+
+ cln = mr->cleanup;
+ mr->cleanup = NULL;
+
+ while (cln) {
+ if (cln->handler) {
+ cln->handler(cln->data);
+ }
+
+ cln = cln->next;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http terminate cleanup count:%d blk:%d",
+ mr->count, mr->blocked);
+
+ if (mr->write_event_handler) {
+
+ if (mr->blocked) {
+ return;
+ }
+
+ e = ngx_http_ephemeral(mr);
+ mr->posted_requests = NULL;
+ mr->write_event_handler = ngx_http_terminate_handler;
+ (void) ngx_http_post_request(mr, &e->terminal_posted_request);
+ return;
+ }
+
+ ngx_http_close_request(mr, rc);
+}
+
+
+static void
+ngx_http_terminate_handler(ngx_http_request_t *r)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http terminate handler count:%d", r->count);
+
+ r->count = 1;
+
+ ngx_http_close_request(r, 0);
+}
+
+
+static void
+ngx_http_finalize_connection(ngx_http_request_t *r)
+{
+ ngx_http_core_loc_conf_t *clcf;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (r->main->count != 1) {
+
+ if (r->discard_body) {
+ r->read_event_handler = ngx_http_discarded_request_body_handler;
+
+ if (r->lingering_time == 0) {
+ r->lingering_time = ngx_time()
+ + (time_t) (clcf->lingering_time / 1000);
+ ngx_add_timer(r->connection->read, clcf->lingering_timeout);
+ }
+ }
+
+ ngx_http_close_request(r, 0);
+ return;
+ }
+
+ if (!ngx_terminate
+ && !ngx_exiting
+ && r->keepalive
+ && clcf->keepalive_timeout > 0)
+ {
+ ngx_http_set_keepalive(r);
+ return;
+ }
+
+ if (clcf->lingering_close == NGX_HTTP_LINGERING_ALWAYS
+ || (clcf->lingering_close == NGX_HTTP_LINGERING_ON
+ && (r->lingering_close
+ || r->header_in->pos < r->header_in->last
+ || r->connection->read->ready)))
+ {
+ ngx_http_set_lingering_close(r);
+ return;
+ }
+
+ ngx_http_close_request(r, 0);
+}
+
+
+static ngx_int_t
+ngx_http_set_write_handler(ngx_http_request_t *r)
+{
+ ngx_event_t *wev;
+ ngx_http_core_loc_conf_t *clcf;
+
+ r->http_state = NGX_HTTP_WRITING_REQUEST_STATE;
+
+ r->read_event_handler = r->discard_body ?
+ ngx_http_discarded_request_body_handler:
+ ngx_http_test_reading;
+ r->write_event_handler = ngx_http_writer;
+
+ wev = r->connection->write;
+
+ if (wev->ready && wev->delayed) {
+ return NGX_OK;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+ if (!wev->delayed) {
+ ngx_add_timer(wev, clcf->send_timeout);
+ }
+
+ if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
+ ngx_http_close_request(r, 0);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_writer(ngx_http_request_t *r)
+{
+ int rc;
+ ngx_event_t *wev;
+ ngx_connection_t *c;
+ ngx_http_core_loc_conf_t *clcf;
+
+ c = r->connection;
+ wev = c->write;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, wev->log, 0,
+ "http writer handler: \"%V?%V\"", &r->uri, &r->args);
+
+ clcf = ngx_http_get_module_loc_conf(r->main, ngx_http_core_module);
+
+ if (wev->timedout) {
+ if (!wev->delayed) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+ "client timed out");
+ c->timedout = 1;
+
+ ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
+ return;
+ }
+
+ wev->timedout = 0;
+ wev->delayed = 0;
+
+ if (!wev->ready) {
+ ngx_add_timer(wev, clcf->send_timeout);
+
+ if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
+ ngx_http_close_request(r, 0);
+ }
+
+ return;
+ }
+
+ } else {
+ if (wev->delayed || r->aio) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0,
+ "http writer delayed");
+
+ if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
+ ngx_http_close_request(r, 0);
+ }
+
+ return;
+ }
+ }
+
+ rc = ngx_http_output_filter(r, NULL);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http writer output filter: %d, \"%V?%V\"",
+ rc, &r->uri, &r->args);
+
+ if (rc == NGX_ERROR) {
+ ngx_http_finalize_request(r, rc);
+ return;
+ }
+
+ if (r->buffered || r->postponed || (r == r->main && c->buffered)) {
+
+ if (!wev->ready && !wev->delayed) {
+ ngx_add_timer(wev, clcf->send_timeout);
+ }
+
+ if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
+ ngx_http_close_request(r, 0);
+ }
+
+ return;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, wev->log, 0,
+ "http writer done: \"%V?%V\"", &r->uri, &r->args);
+
+ r->write_event_handler = ngx_http_request_empty_handler;
+
+ ngx_http_finalize_request(r, rc);
+}
+
+
+static void
+ngx_http_request_finalizer(ngx_http_request_t *r)
+{
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http finalizer done: \"%V?%V\"", &r->uri, &r->args);
+
+ ngx_http_finalize_request(r, 0);
+}
+
+
+void
+ngx_http_block_reading(ngx_http_request_t *r)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http reading blocked");
+
+ /* aio does not call this handler */
+
+ if ((ngx_event_flags & NGX_USE_LEVEL_EVENT)
+ && r->connection->read->active)
+ {
+ if (ngx_del_event(r->connection->read, NGX_READ_EVENT, 0) != NGX_OK) {
+ ngx_http_close_request(r, 0);
+ }
+ }
+}
+
+
+void
+ngx_http_test_reading(ngx_http_request_t *r)
+{
+ int n;
+ char buf[1];
+ ngx_err_t err;
+ ngx_event_t *rev;
+ ngx_connection_t *c;
+
+ c = r->connection;
+ rev = c->read;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http test reading");
+
+#if (NGX_HAVE_KQUEUE)
+
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+
+ if (!rev->pending_eof) {
+ return;
+ }
+
+ rev->eof = 1;
+ c->error = 1;
+ err = rev->kq_errno;
+
+ goto closed;
+ }
+
+#endif
+
+ n = recv(c->fd, buf, 1, MSG_PEEK);
+
+ if (n == 0) {
+ rev->eof = 1;
+ c->error = 1;
+ err = 0;
+
+ goto closed;
+
+ } else if (n == -1) {
+ err = ngx_socket_errno;
+
+ if (err != NGX_EAGAIN) {
+ rev->eof = 1;
+ c->error = 1;
+
+ goto closed;
+ }
+ }
+
+ /* aio does not call this handler */
+
+ if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && rev->active) {
+
+ if (ngx_del_event(rev, NGX_READ_EVENT, 0) != NGX_OK) {
+ ngx_http_close_request(r, 0);
+ }
+ }
+
+ return;
+
+closed:
+
+ if (err) {
+ rev->error = 1;
+ }
+
+ ngx_log_error(NGX_LOG_INFO, c->log, err,
+ "client closed prematurely connection");
+
+ ngx_http_finalize_request(r, 0);
+}
+
+
+static void
+ngx_http_set_keepalive(ngx_http_request_t *r)
+{
+ int tcp_nodelay;
+ ngx_int_t i;
+ ngx_buf_t *b, *f;
+ ngx_event_t *rev, *wev;
+ ngx_connection_t *c;
+ ngx_http_connection_t *hc;
+ ngx_http_core_srv_conf_t *cscf;
+ ngx_http_core_loc_conf_t *clcf;
+
+ c = r->connection;
+ rev = c->read;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "set http keepalive handler");
+
+ if (r->discard_body) {
+ r->write_event_handler = ngx_http_request_empty_handler;
+ r->lingering_time = ngx_time() + (time_t) (clcf->lingering_time / 1000);
+ ngx_add_timer(rev, clcf->lingering_timeout);
+ return;
+ }
+
+ c->log->action = "closing request";
+
+ hc = r->http_connection;
+ b = r->header_in;
+
+ if (b->pos < b->last) {
+
+ /* the pipelined request */
+
+ if (b != c->buffer) {
+
+ /*
+ * If the large header buffers were allocated while the previous
+ * request processing then we do not use c->buffer for
+ * the pipelined request (see ngx_http_init_request()).
+ *
+ * Now we would move the large header buffers to the free list.
+ */
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ if (hc->free == NULL) {
+ hc->free = ngx_palloc(c->pool,
+ cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));
+
+ if (hc->free == NULL) {
+ ngx_http_close_request(r, 0);
+ return;
+ }
+ }
+
+ for (i = 0; i < hc->nbusy - 1; i++) {
+ f = hc->busy[i];
+ hc->free[hc->nfree++] = f;
+ f->pos = f->start;
+ f->last = f->start;
+ }
+
+ hc->busy[0] = b;
+ hc->nbusy = 1;
+ }
+ }
+
+ r->keepalive = 0;
+
+ ngx_http_free_request(r, 0);
+
+ c->data = hc;
+
+ ngx_add_timer(rev, clcf->keepalive_timeout);
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ wev = c->write;
+ wev->handler = ngx_http_empty_handler;
+
+ if (b->pos < b->last) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "pipelined request");
+
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_reading, 1);
+#endif
+
+ hc->pipeline = 1;
+ c->log->action = "reading client pipelined request line";
+
+ rev->handler = ngx_http_init_request;
+ ngx_post_event(rev, &ngx_posted_events);
+ return;
+ }
+
+ hc->pipeline = 0;
+
+ /*
+ * To keep a memory footprint as small as possible for an idle
+ * keepalive connection we try to free the ngx_http_request_t and
+ * c->buffer's memory if they were allocated outside the c->pool.
+ * The large header buffers are always allocated outside the c->pool and
+ * are freed too.
+ */
+
+ if (ngx_pfree(c->pool, r) == NGX_OK) {
+ hc->request = NULL;
+ }
+
+ b = c->buffer;
+
+ if (ngx_pfree(c->pool, b->start) == NGX_OK) {
+
+ /*
+ * the special note for ngx_http_keepalive_handler() that
+ * c->buffer's memory was freed
+ */
+
+ b->pos = NULL;
+
+ } else {
+ b->pos = b->start;
+ b->last = b->start;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "hc free: %p %d",
+ hc->free, hc->nfree);
+
+ if (hc->free) {
+ for (i = 0; i < hc->nfree; i++) {
+ ngx_pfree(c->pool, hc->free[i]->start);
+ hc->free[i] = NULL;
+ }
+
+ hc->nfree = 0;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "hc busy: %p %d",
+ hc->busy, hc->nbusy);
+
+ if (hc->busy) {
+ for (i = 0; i < hc->nbusy; i++) {
+ ngx_pfree(c->pool, hc->busy[i]->start);
+ hc->busy[i] = NULL;
+ }
+
+ hc->nbusy = 0;
+ }
+
+#if (NGX_HTTP_SSL)
+ if (c->ssl) {
+ ngx_ssl_free_buffer(c);
+ }
+#endif
+
+ rev->handler = ngx_http_keepalive_handler;
+
+ if (wev->active && (ngx_event_flags & NGX_USE_LEVEL_EVENT)) {
+ if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) != NGX_OK) {
+ ngx_http_close_connection(c);
+ return;
+ }
+ }
+
+ c->log->action = "keepalive";
+
+ if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) {
+ if (ngx_tcp_push(c->fd) == -1) {
+ ngx_connection_error(c, ngx_socket_errno, ngx_tcp_push_n " failed");
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ c->tcp_nopush = NGX_TCP_NOPUSH_UNSET;
+ tcp_nodelay = ngx_tcp_nodelay_and_tcp_nopush ? 1 : 0;
+
+ } else {
+ tcp_nodelay = 1;
+ }
+
+ if (tcp_nodelay
+ && clcf->tcp_nodelay
+ && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET)
+ {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay");
+
+ if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,
+ (const void *) &tcp_nodelay, sizeof(int))
+ == -1)
+ {
+#if (NGX_SOLARIS)
+ /* Solaris returns EINVAL if a socket has been shut down */
+ c->log_error = NGX_ERROR_IGNORE_EINVAL;
+#endif
+
+ ngx_connection_error(c, ngx_socket_errno,
+ "setsockopt(TCP_NODELAY) failed");
+
+ c->log_error = NGX_ERROR_INFO;
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ c->tcp_nodelay = NGX_TCP_NODELAY_SET;
+ }
+
+#if 0
+ /* if ngx_http_request_t was freed then we need some other place */
+ r->http_state = NGX_HTTP_KEEPALIVE_STATE;
+#endif
+
+ c->idle = 1;
+ ngx_reusable_connection(c, 1);
+
+ if (rev->ready) {
+ ngx_post_event(rev, &ngx_posted_events);
+ }
+}
+
+
+static void
+ngx_http_keepalive_handler(ngx_event_t *rev)
+{
+ size_t size;
+ ssize_t n;
+ ngx_buf_t *b;
+ ngx_connection_t *c;
+
+ c = rev->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http keepalive handler");
+
+ if (rev->timedout || c->close) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+#if (NGX_HAVE_KQUEUE)
+
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+ if (rev->pending_eof) {
+ c->log->handler = NULL;
+ ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno,
+ "kevent() reported that client %V closed "
+ "keepalive connection", &c->addr_text);
+#if (NGX_HTTP_SSL)
+ if (c->ssl) {
+ c->ssl->no_send_shutdown = 1;
+ }
+#endif
+ ngx_http_close_connection(c);
+ return;
+ }
+ }
+
+#endif
+
+ b = c->buffer;
+ size = b->end - b->start;
+
+ if (b->pos == NULL) {
+
+ /*
+ * The c->buffer's memory was freed by ngx_http_set_keepalive().
+ * However, the c->buffer->start and c->buffer->end were not changed
+ * to keep the buffer size.
+ */
+
+ b->pos = ngx_palloc(c->pool, size);
+ if (b->pos == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ b->start = b->pos;
+ b->last = b->pos;
+ b->end = b->pos + size;
+ }
+
+ /*
+ * MSIE closes a keepalive connection with RST flag
+ * so we ignore ECONNRESET here.
+ */
+
+ c->log_error = NGX_ERROR_IGNORE_ECONNRESET;
+ ngx_set_socket_errno(0);
+
+ n = c->recv(c, b->last, size);
+ c->log_error = NGX_ERROR_INFO;
+
+ if (n == NGX_AGAIN) {
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_http_close_connection(c);
+ }
+
+ return;
+ }
+
+ if (n == NGX_ERROR) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ c->log->handler = NULL;
+
+ if (n == 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, ngx_socket_errno,
+ "client %V closed keepalive connection", &c->addr_text);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ b->last += n;
+
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_reading, 1);
+#endif
+
+ c->log->handler = ngx_http_log_error;
+ c->log->action = "reading client request line";
+
+ c->idle = 0;
+ ngx_reusable_connection(c, 0);
+
+ ngx_http_init_request(rev);
+}
+
+
+static void
+ngx_http_set_lingering_close(ngx_http_request_t *r)
+{
+ ngx_event_t *rev, *wev;
+ ngx_connection_t *c;
+ ngx_http_core_loc_conf_t *clcf;
+
+ c = r->connection;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ rev = c->read;
+ rev->handler = ngx_http_lingering_close_handler;
+
+ r->lingering_time = ngx_time() + (time_t) (clcf->lingering_time / 1000);
+ ngx_add_timer(rev, clcf->lingering_timeout);
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_http_close_request(r, 0);
+ return;
+ }
+
+ wev = c->write;
+ wev->handler = ngx_http_empty_handler;
+
+ if (wev->active && (ngx_event_flags & NGX_USE_LEVEL_EVENT)) {
+ if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) != NGX_OK) {
+ ngx_http_close_request(r, 0);
+ return;
+ }
+ }
+
+ if (ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN) == -1) {
+ ngx_connection_error(c, ngx_socket_errno,
+ ngx_shutdown_socket_n " failed");
+ ngx_http_close_request(r, 0);
+ return;
+ }
+
+ if (rev->ready) {
+ ngx_http_lingering_close_handler(rev);
+ }
+}
+
+
+static void
+ngx_http_lingering_close_handler(ngx_event_t *rev)
+{
+ ssize_t n;
+ ngx_msec_t timer;
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+ ngx_http_core_loc_conf_t *clcf;
+ u_char buffer[NGX_HTTP_LINGERING_BUFFER_SIZE];
+
+ c = rev->data;
+ r = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http lingering close handler");
+
+ if (rev->timedout) {
+ ngx_http_close_request(r, 0);
+ return;
+ }
+
+ timer = (ngx_msec_t) (r->lingering_time - ngx_time());
+ if (timer <= 0) {
+ ngx_http_close_request(r, 0);
+ return;
+ }
+
+ do {
+ n = c->recv(c, buffer, NGX_HTTP_LINGERING_BUFFER_SIZE);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "lingering read: %d", n);
+
+ if (n == NGX_ERROR || n == 0) {
+ ngx_http_close_request(r, 0);
+ return;
+ }
+
+ } while (rev->ready);
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_http_close_request(r, 0);
+ return;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ timer *= 1000;
+
+ if (timer > clcf->lingering_timeout) {
+ timer = clcf->lingering_timeout;
+ }
+
+ ngx_add_timer(rev, timer);
+}
+
+
+void
+ngx_http_empty_handler(ngx_event_t *wev)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0, "http empty handler");
+
+ return;
+}
+
+
+void
+ngx_http_request_empty_handler(ngx_http_request_t *r)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http request empty handler");
+
+ return;
+}
+
+
+ngx_int_t
+ngx_http_send_special(ngx_http_request_t *r, ngx_uint_t flags)
+{
+ ngx_buf_t *b;
+ ngx_chain_t out;
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (flags & NGX_HTTP_LAST) {
+
+ if (r == r->main && !r->post_action) {
+ b->last_buf = 1;
+
+ } else {
+ b->sync = 1;
+ b->last_in_chain = 1;
+ }
+ }
+
+ if (flags & NGX_HTTP_FLUSH) {
+ b->flush = 1;
+ }
+
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_output_filter(r, &out);
+}
+
+
+static ngx_int_t
+ngx_http_post_action(ngx_http_request_t *r)
+{
+ ngx_http_core_loc_conf_t *clcf;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->post_action.data == NULL) {
+ return NGX_DECLINED;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "post action: \"%V\"", &clcf->post_action);
+
+ r->main->count--;
+
+ r->http_version = NGX_HTTP_VERSION_9;
+ r->header_only = 1;
+ r->post_action = 1;
+
+ r->read_event_handler = ngx_http_block_reading;
+
+ if (clcf->post_action.data[0] == '/') {
+ ngx_http_internal_redirect(r, &clcf->post_action, NULL);
+
+ } else {
+ ngx_http_named_location(r, &clcf->post_action);
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+ ngx_connection_t *c;
+
+ r = r->main;
+ c = r->connection;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http request count:%d blk:%d", r->count, r->blocked);
+
+ if (r->count == 0) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0, "http request count is zero");
+ }
+
+ r->count--;
+
+ if (r->count || r->blocked) {
+ return;
+ }
+
+ ngx_http_free_request(r, rc);
+ ngx_http_close_connection(c);
+}
+
+
+static void
+ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+ ngx_log_t *log;
+ struct linger linger;
+ ngx_http_cleanup_t *cln;
+ ngx_http_log_ctx_t *ctx;
+ ngx_http_core_loc_conf_t *clcf;
+
+ log = r->connection->log;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http close request");
+
+ if (r->pool == NULL) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0, "http request already closed");
+ return;
+ }
+
+ for (cln = r->cleanup; cln; cln = cln->next) {
+ if (cln->handler) {
+ cln->handler(cln->data);
+ }
+ }
+
+#if (NGX_STAT_STUB)
+
+ if (r->stat_reading) {
+ (void) ngx_atomic_fetch_add(ngx_stat_reading, -1);
+ }
+
+ if (r->stat_writing) {
+ (void) ngx_atomic_fetch_add(ngx_stat_writing, -1);
+ }
+
+#endif
+
+ if (rc > 0 && (r->headers_out.status == 0 || r->connection->sent == 0)) {
+ r->headers_out.status = rc;
+ }
+
+ log->action = "logging request";
+
+ ngx_http_log_request(r);
+
+ log->action = "closing request";
+
+ if (r->connection->timedout) {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->reset_timedout_connection) {
+ linger.l_onoff = 1;
+ linger.l_linger = 0;
+
+ if (setsockopt(r->connection->fd, SOL_SOCKET, SO_LINGER,
+ (const void *) &linger, sizeof(struct linger)) == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
+ "setsockopt(SO_LINGER) failed");
+ }
+ }
+ }
+
+ /* the various request strings were allocated from r->pool */
+ ctx = log->data;
+ ctx->request = NULL;
+
+ r->request_line.len = 0;
+
+ r->connection->destroyed = 1;
+
+ ngx_destroy_pool(r->pool);
+}
+
+
+static void
+ngx_http_log_request(ngx_http_request_t *r)
+{
+ ngx_uint_t i, n;
+ ngx_http_handler_pt *log_handler;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ log_handler = cmcf->phases[NGX_HTTP_LOG_PHASE].handlers.elts;
+ n = cmcf->phases[NGX_HTTP_LOG_PHASE].handlers.nelts;
+
+ for (i = 0; i < n; i++) {
+ log_handler[i](r);
+ }
+}
+
+
+static void
+ngx_http_close_connection(ngx_connection_t *c)
+{
+ ngx_pool_t *pool;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "close http connection: %d", c->fd);
+
+#if (NGX_HTTP_SSL)
+
+ if (c->ssl) {
+ if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
+ c->ssl->handler = ngx_http_close_connection;
+ return;
+ }
+ }
+
+#endif
+
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
+#endif
+
+ c->destroyed = 1;
+
+ pool = c->pool;
+
+ ngx_close_connection(c);
+
+ ngx_destroy_pool(pool);
+}
+
+
+static u_char *
+ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len)
+{
+ u_char *p;
+ ngx_http_request_t *r;
+ ngx_http_log_ctx_t *ctx;
+
+ if (log->action) {
+ p = ngx_snprintf(buf, len, " while %s", log->action);
+ len -= p - buf;
+ buf = p;
+ }
+
+ ctx = log->data;
+
+ p = ngx_snprintf(buf, len, ", client: %V", &ctx->connection->addr_text);
+ len -= p - buf;
+
+ r = ctx->request;
+
+ if (r) {
+ return r->log_handler(r, ctx->current_request, p, len);
+
+ } else {
+ p = ngx_snprintf(p, len, ", server: %V",
+ &ctx->connection->listening->addr_text);
+ }
+
+ return p;
+}
+
+
+static u_char *
+ngx_http_log_error_handler(ngx_http_request_t *r, ngx_http_request_t *sr,
+ u_char *buf, size_t len)
+{
+ char *uri_separator;
+ u_char *p;
+ ngx_http_upstream_t *u;
+ ngx_http_core_srv_conf_t *cscf;
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ p = ngx_snprintf(buf, len, ", server: %V", &cscf->server_name);
+ len -= p - buf;
+ buf = p;
+
+ if (r->request_line.data == NULL && r->request_start) {
+ for (p = r->request_start; p < r->header_in->last; p++) {
+ if (*p == CR || *p == LF) {
+ break;
+ }
+ }
+
+ r->request_line.len = p - r->request_start;
+ r->request_line.data = r->request_start;
+ }
+
+ if (r->request_line.len) {
+ p = ngx_snprintf(buf, len, ", request: \"%V\"", &r->request_line);
+ len -= p - buf;
+ buf = p;
+ }
+
+ if (r != sr) {
+ p = ngx_snprintf(buf, len, ", subrequest: \"%V\"", &sr->uri);
+ len -= p - buf;
+ buf = p;
+ }
+
+ u = sr->upstream;
+
+ if (u && u->peer.name) {
+
+ uri_separator = "";
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+ if (u->peer.sockaddr && u->peer.sockaddr->sa_family == AF_UNIX) {
+ uri_separator = ":";
+ }
+#endif
+
+ p = ngx_snprintf(buf, len, ", upstream: \"%V%V%s%V\"",
+ &u->schema, u->peer.name,
+ uri_separator, &u->uri);
+ len -= p - buf;
+ buf = p;
+ }
+
+ if (r->headers_in.host) {
+ p = ngx_snprintf(buf, len, ", host: \"%V\"",
+ &r->headers_in.host->value);
+ len -= p - buf;
+ buf = p;
+ }
+
+ if (r->headers_in.referer) {
+ p = ngx_snprintf(buf, len, ", referrer: \"%V\"",
+ &r->headers_in.referer->value);
+ buf = p;
+ }
+
+ return buf;
+}
diff --git a/usr.sbin/nginx/src/http/ngx_http_request.h b/usr.sbin/nginx/src/http/ngx_http_request.h
new file mode 100644
index 00000000000..6198d7ef2ab
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_request.h
@@ -0,0 +1,571 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_REQUEST_H_INCLUDED_
+#define _NGX_HTTP_REQUEST_H_INCLUDED_
+
+
+#define NGX_HTTP_MAX_URI_CHANGES 10
+#define NGX_HTTP_MAX_SUBREQUESTS 50
+
+/* must be 2^n */
+#define NGX_HTTP_LC_HEADER_LEN 32
+
+
+#define NGX_HTTP_DISCARD_BUFFER_SIZE 4096
+#define NGX_HTTP_LINGERING_BUFFER_SIZE 4096
+
+
+#define NGX_HTTP_VERSION_9 9
+#define NGX_HTTP_VERSION_10 1000
+#define NGX_HTTP_VERSION_11 1001
+
+#define NGX_HTTP_UNKNOWN 0x0001
+#define NGX_HTTP_GET 0x0002
+#define NGX_HTTP_HEAD 0x0004
+#define NGX_HTTP_POST 0x0008
+#define NGX_HTTP_PUT 0x0010
+#define NGX_HTTP_DELETE 0x0020
+#define NGX_HTTP_MKCOL 0x0040
+#define NGX_HTTP_COPY 0x0080
+#define NGX_HTTP_MOVE 0x0100
+#define NGX_HTTP_OPTIONS 0x0200
+#define NGX_HTTP_PROPFIND 0x0400
+#define NGX_HTTP_PROPPATCH 0x0800
+#define NGX_HTTP_LOCK 0x1000
+#define NGX_HTTP_UNLOCK 0x2000
+#define NGX_HTTP_PATCH 0x4000
+#define NGX_HTTP_TRACE 0x8000
+
+#define NGX_HTTP_CONNECTION_CLOSE 1
+#define NGX_HTTP_CONNECTION_KEEP_ALIVE 2
+
+
+#define NGX_NONE 1
+
+
+#define NGX_HTTP_PARSE_HEADER_DONE 1
+
+#define NGX_HTTP_CLIENT_ERROR 10
+#define NGX_HTTP_PARSE_INVALID_METHOD 10
+#define NGX_HTTP_PARSE_INVALID_REQUEST 11
+#define NGX_HTTP_PARSE_INVALID_09_METHOD 12
+
+#define NGX_HTTP_PARSE_INVALID_HEADER 13
+
+
+/* unused 1 */
+#define NGX_HTTP_SUBREQUEST_IN_MEMORY 2
+#define NGX_HTTP_SUBREQUEST_WAITED 4
+#define NGX_HTTP_LOG_UNSAFE 8
+
+
+#define NGX_HTTP_OK 200
+#define NGX_HTTP_CREATED 201
+#define NGX_HTTP_ACCEPTED 202
+#define NGX_HTTP_NO_CONTENT 204
+#define NGX_HTTP_PARTIAL_CONTENT 206
+
+#define NGX_HTTP_SPECIAL_RESPONSE 300
+#define NGX_HTTP_MOVED_PERMANENTLY 301
+#define NGX_HTTP_MOVED_TEMPORARILY 302
+#define NGX_HTTP_SEE_OTHER 303
+#define NGX_HTTP_NOT_MODIFIED 304
+
+#define NGX_HTTP_BAD_REQUEST 400
+#define NGX_HTTP_UNAUTHORIZED 401
+#define NGX_HTTP_FORBIDDEN 403
+#define NGX_HTTP_NOT_FOUND 404
+#define NGX_HTTP_NOT_ALLOWED 405
+#define NGX_HTTP_REQUEST_TIME_OUT 408
+#define NGX_HTTP_CONFLICT 409
+#define NGX_HTTP_LENGTH_REQUIRED 411
+#define NGX_HTTP_PRECONDITION_FAILED 412
+#define NGX_HTTP_REQUEST_ENTITY_TOO_LARGE 413
+#define NGX_HTTP_REQUEST_URI_TOO_LARGE 414
+#define NGX_HTTP_UNSUPPORTED_MEDIA_TYPE 415
+#define NGX_HTTP_RANGE_NOT_SATISFIABLE 416
+
+
+/* Our own HTTP codes */
+
+/* The special code to close connection without any response */
+#define NGX_HTTP_CLOSE 444
+
+#define NGX_HTTP_NGINX_CODES 494
+
+#define NGX_HTTP_REQUEST_HEADER_TOO_LARGE 494
+
+#define NGX_HTTPS_CERT_ERROR 495
+#define NGX_HTTPS_NO_CERT 496
+
+/*
+ * We use the special code for the plain HTTP requests that are sent to
+ * HTTPS port to distinguish it from 4XX in an error page redirection
+ */
+#define NGX_HTTP_TO_HTTPS 497
+
+/* 498 is the canceled code for the requests with invalid host name */
+
+/*
+ * HTTP does not define the code for the case when a client closed
+ * the connection while we are processing its request so we introduce
+ * own code to log such situation when a client has closed the connection
+ * before we even try to send the HTTP header to it
+ */
+#define NGX_HTTP_CLIENT_CLOSED_REQUEST 499
+
+
+#define NGX_HTTP_INTERNAL_SERVER_ERROR 500
+#define NGX_HTTP_NOT_IMPLEMENTED 501
+#define NGX_HTTP_BAD_GATEWAY 502
+#define NGX_HTTP_SERVICE_UNAVAILABLE 503
+#define NGX_HTTP_GATEWAY_TIME_OUT 504
+#define NGX_HTTP_INSUFFICIENT_STORAGE 507
+
+
+#define NGX_HTTP_LOWLEVEL_BUFFERED 0xf0
+#define NGX_HTTP_WRITE_BUFFERED 0x10
+#define NGX_HTTP_GZIP_BUFFERED 0x20
+#define NGX_HTTP_SSI_BUFFERED 0x01
+#define NGX_HTTP_SUB_BUFFERED 0x02
+#define NGX_HTTP_COPY_BUFFERED 0x04
+
+
+typedef enum {
+ NGX_HTTP_INITING_REQUEST_STATE = 0,
+ NGX_HTTP_READING_REQUEST_STATE,
+ NGX_HTTP_PROCESS_REQUEST_STATE,
+
+ NGX_HTTP_CONNECT_UPSTREAM_STATE,
+ NGX_HTTP_WRITING_UPSTREAM_STATE,
+ NGX_HTTP_READING_UPSTREAM_STATE,
+
+ NGX_HTTP_WRITING_REQUEST_STATE,
+ NGX_HTTP_LINGERING_CLOSE_STATE,
+ NGX_HTTP_KEEPALIVE_STATE
+} ngx_http_state_e;
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_uint_t offset;
+ ngx_http_header_handler_pt handler;
+} ngx_http_header_t;
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_uint_t offset;
+} ngx_http_header_out_t;
+
+
+typedef struct {
+ ngx_list_t headers;
+
+ ngx_table_elt_t *host;
+ ngx_table_elt_t *connection;
+ ngx_table_elt_t *if_modified_since;
+ ngx_table_elt_t *if_unmodified_since;
+ ngx_table_elt_t *user_agent;
+ ngx_table_elt_t *referer;
+ ngx_table_elt_t *content_length;
+ ngx_table_elt_t *content_type;
+
+ ngx_table_elt_t *range;
+ ngx_table_elt_t *if_range;
+
+ ngx_table_elt_t *transfer_encoding;
+ ngx_table_elt_t *expect;
+
+#if (NGX_HTTP_GZIP)
+ ngx_table_elt_t *accept_encoding;
+ ngx_table_elt_t *via;
+#endif
+
+ ngx_table_elt_t *authorization;
+
+ ngx_table_elt_t *keep_alive;
+
+#if (NGX_HTTP_PROXY || NGX_HTTP_REALIP || NGX_HTTP_GEO)
+ ngx_table_elt_t *x_forwarded_for;
+#endif
+
+#if (NGX_HTTP_REALIP)
+ ngx_table_elt_t *x_real_ip;
+#endif
+
+#if (NGX_HTTP_HEADERS)
+ ngx_table_elt_t *accept;
+ ngx_table_elt_t *accept_language;
+#endif
+
+#if (NGX_HTTP_DAV)
+ ngx_table_elt_t *depth;
+ ngx_table_elt_t *destination;
+ ngx_table_elt_t *overwrite;
+ ngx_table_elt_t *date;
+#endif
+
+ ngx_str_t user;
+ ngx_str_t passwd;
+
+ ngx_array_t cookies;
+
+ ngx_str_t server;
+ off_t content_length_n;
+ time_t keep_alive_n;
+
+ unsigned connection_type:2;
+ unsigned msie:1;
+ unsigned msie6:1;
+ unsigned opera:1;
+ unsigned gecko:1;
+ unsigned chrome:1;
+ unsigned safari:1;
+ unsigned konqueror:1;
+} ngx_http_headers_in_t;
+
+
+typedef struct {
+ ngx_list_t headers;
+
+ ngx_uint_t status;
+ ngx_str_t status_line;
+
+ ngx_table_elt_t *server;
+ ngx_table_elt_t *date;
+ ngx_table_elt_t *content_length;
+ ngx_table_elt_t *content_encoding;
+ ngx_table_elt_t *location;
+ ngx_table_elt_t *refresh;
+ ngx_table_elt_t *last_modified;
+ ngx_table_elt_t *content_range;
+ ngx_table_elt_t *accept_ranges;
+ ngx_table_elt_t *www_authenticate;
+ ngx_table_elt_t *expires;
+ ngx_table_elt_t *etag;
+
+ ngx_str_t *override_charset;
+
+ size_t content_type_len;
+ ngx_str_t content_type;
+ ngx_str_t charset;
+ u_char *content_type_lowcase;
+ ngx_uint_t content_type_hash;
+
+ ngx_array_t cache_control;
+
+ off_t content_length_n;
+ time_t date_time;
+ time_t last_modified_time;
+} ngx_http_headers_out_t;
+
+
+typedef void (*ngx_http_client_body_handler_pt)(ngx_http_request_t *r);
+
+typedef struct {
+ ngx_temp_file_t *temp_file;
+ ngx_chain_t *bufs;
+ ngx_buf_t *buf;
+ off_t rest;
+ ngx_chain_t *to_write;
+ ngx_http_client_body_handler_pt post_handler;
+} ngx_http_request_body_t;
+
+
+typedef struct {
+ ngx_http_request_t *request;
+
+ ngx_buf_t **busy;
+ ngx_int_t nbusy;
+
+ ngx_buf_t **free;
+ ngx_int_t nfree;
+
+ ngx_uint_t pipeline; /* unsigned pipeline:1; */
+} ngx_http_connection_t;
+
+
+typedef struct ngx_http_server_name_s ngx_http_server_name_t;
+
+
+typedef struct {
+ ngx_hash_combined_t names;
+
+ ngx_uint_t nregex;
+ ngx_http_server_name_t *regex;
+} ngx_http_virtual_names_t;
+
+
+typedef void (*ngx_http_cleanup_pt)(void *data);
+
+typedef struct ngx_http_cleanup_s ngx_http_cleanup_t;
+
+struct ngx_http_cleanup_s {
+ ngx_http_cleanup_pt handler;
+ void *data;
+ ngx_http_cleanup_t *next;
+};
+
+
+typedef ngx_int_t (*ngx_http_post_subrequest_pt)(ngx_http_request_t *r,
+ void *data, ngx_int_t rc);
+
+typedef struct {
+ ngx_http_post_subrequest_pt handler;
+ void *data;
+} ngx_http_post_subrequest_t;
+
+
+typedef struct ngx_http_postponed_request_s ngx_http_postponed_request_t;
+
+struct ngx_http_postponed_request_s {
+ ngx_http_request_t *request;
+ ngx_chain_t *out;
+ ngx_http_postponed_request_t *next;
+};
+
+
+typedef struct ngx_http_posted_request_s ngx_http_posted_request_t;
+
+struct ngx_http_posted_request_s {
+ ngx_http_request_t *request;
+ ngx_http_posted_request_t *next;
+};
+
+
+typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);
+typedef void (*ngx_http_event_handler_pt)(ngx_http_request_t *r);
+
+
+struct ngx_http_request_s {
+ uint32_t signature; /* "HTTP" */
+
+ ngx_connection_t *connection;
+
+ void **ctx;
+ void **main_conf;
+ void **srv_conf;
+ void **loc_conf;
+
+ ngx_http_event_handler_pt read_event_handler;
+ ngx_http_event_handler_pt write_event_handler;
+
+#if (NGX_HTTP_CACHE)
+ ngx_http_cache_t *cache;
+#endif
+
+ ngx_http_upstream_t *upstream;
+ ngx_array_t *upstream_states;
+ /* of ngx_http_upstream_state_t */
+
+ ngx_pool_t *pool;
+ ngx_buf_t *header_in;
+
+ ngx_http_headers_in_t headers_in;
+ ngx_http_headers_out_t headers_out;
+
+ ngx_http_request_body_t *request_body;
+
+ time_t lingering_time;
+ time_t start_sec;
+ ngx_msec_t start_msec;
+
+ ngx_uint_t method;
+ ngx_uint_t http_version;
+
+ ngx_str_t request_line;
+ ngx_str_t uri;
+ ngx_str_t args;
+ ngx_str_t exten;
+ ngx_str_t unparsed_uri;
+
+ ngx_str_t method_name;
+ ngx_str_t http_protocol;
+
+ ngx_chain_t *out;
+ ngx_http_request_t *main;
+ ngx_http_request_t *parent;
+ ngx_http_postponed_request_t *postponed;
+ ngx_http_post_subrequest_t *post_subrequest;
+ ngx_http_posted_request_t *posted_requests;
+
+ ngx_http_virtual_names_t *virtual_names;
+
+ ngx_int_t phase_handler;
+ ngx_http_handler_pt content_handler;
+ ngx_uint_t access_code;
+
+ ngx_http_variable_value_t *variables;
+
+#if (NGX_PCRE)
+ ngx_uint_t ncaptures;
+ int *captures;
+ u_char *captures_data;
+#endif
+
+ size_t limit_rate;
+
+ /* used to learn the Apache compatible response length without a header */
+ size_t header_size;
+
+ off_t request_length;
+
+ ngx_uint_t err_status;
+
+ ngx_http_connection_t *http_connection;
+
+ ngx_http_log_handler_pt log_handler;
+
+ ngx_http_cleanup_t *cleanup;
+
+ unsigned subrequests:8;
+ unsigned count:8;
+ unsigned blocked:8;
+
+ unsigned aio:1;
+
+ unsigned http_state:4;
+
+ /* URI with "/." and on Win32 with "//" */
+ unsigned complex_uri:1;
+
+ /* URI with "%" */
+ unsigned quoted_uri:1;
+
+ /* URI with "+" */
+ unsigned plus_in_uri:1;
+
+ /* URI with " " */
+ unsigned space_in_uri:1;
+
+ unsigned invalid_header:1;
+
+ unsigned add_uri_to_alias:1;
+ unsigned valid_location:1;
+ unsigned valid_unparsed_uri:1;
+ unsigned uri_changed:1;
+ unsigned uri_changes:4;
+
+ unsigned request_body_in_single_buf:1;
+ unsigned request_body_in_file_only:1;
+ unsigned request_body_in_persistent_file:1;
+ unsigned request_body_in_clean_file:1;
+ unsigned request_body_file_group_access:1;
+ unsigned request_body_file_log_level:3;
+
+ unsigned subrequest_in_memory:1;
+ unsigned waited:1;
+
+#if (NGX_HTTP_CACHE)
+ unsigned cached:1;
+#endif
+
+#if (NGX_HTTP_GZIP)
+ unsigned gzip_tested:1;
+ unsigned gzip_ok:1;
+ unsigned gzip_vary:1;
+#endif
+
+ unsigned proxy:1;
+ unsigned bypass_cache:1;
+ unsigned no_cache:1;
+
+ /*
+ * instead of using the request context data in
+ * ngx_http_limit_zone_module and ngx_http_limit_req_module
+ * we use the single bits in the request structure
+ */
+ unsigned limit_zone_set:1;
+ unsigned limit_req_set:1;
+
+#if 0
+ unsigned cacheable:1;
+#endif
+
+ unsigned pipeline:1;
+ unsigned plain_http:1;
+ unsigned chunked:1;
+ unsigned header_only:1;
+ unsigned keepalive:1;
+ unsigned lingering_close:1;
+ unsigned discard_body:1;
+ unsigned internal:1;
+ unsigned error_page:1;
+ unsigned ignore_content_encoding:1;
+ unsigned filter_finalize:1;
+ unsigned post_action:1;
+ unsigned request_complete:1;
+ unsigned request_output:1;
+ unsigned header_sent:1;
+ unsigned expect_tested:1;
+ unsigned root_tested:1;
+ unsigned done:1;
+ unsigned logged:1;
+
+ unsigned buffered:4;
+
+ unsigned main_filter_need_in_memory:1;
+ unsigned filter_need_in_memory:1;
+ unsigned filter_need_temporary:1;
+ unsigned allow_ranges:1;
+
+#if (NGX_STAT_STUB)
+ unsigned stat_reading:1;
+ unsigned stat_writing:1;
+#endif
+
+ /* used to parse HTTP headers */
+
+ ngx_uint_t state;
+
+ ngx_uint_t header_hash;
+ ngx_uint_t lowcase_index;
+ u_char lowcase_header[NGX_HTTP_LC_HEADER_LEN];
+
+ u_char *header_name_start;
+ u_char *header_name_end;
+ u_char *header_start;
+ u_char *header_end;
+
+ /*
+ * a memory that can be reused after parsing a request line
+ * via ngx_http_ephemeral_t
+ */
+
+ u_char *uri_start;
+ u_char *uri_end;
+ u_char *uri_ext;
+ u_char *args_start;
+ u_char *request_start;
+ u_char *request_end;
+ u_char *method_end;
+ u_char *schema_start;
+ u_char *schema_end;
+ u_char *host_start;
+ u_char *host_end;
+ u_char *port_start;
+ u_char *port_end;
+
+ unsigned http_minor:16;
+ unsigned http_major:16;
+};
+
+
+typedef struct {
+ ngx_http_posted_request_t terminal_posted_request;
+#if (NGX_HAVE_AIO_SENDFILE)
+ u_char aio_preload;
+#endif
+} ngx_http_ephemeral_t;
+
+
+extern ngx_http_header_t ngx_http_headers_in[];
+extern ngx_http_header_out_t ngx_http_headers_out[];
+
+
+#endif /* _NGX_HTTP_REQUEST_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/http/ngx_http_request_body.c b/usr.sbin/nginx/src/http/ngx_http_request_body.c
new file mode 100644
index 00000000000..be311a6129f
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_request_body.c
@@ -0,0 +1,638 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static void ngx_http_read_client_request_body_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_do_read_client_request_body(ngx_http_request_t *r);
+static ngx_int_t ngx_http_write_request_body(ngx_http_request_t *r,
+ ngx_chain_t *body);
+static ngx_int_t ngx_http_read_discarded_request_body(ngx_http_request_t *r);
+static ngx_int_t ngx_http_test_expect(ngx_http_request_t *r);
+
+
+/*
+ * on completion ngx_http_read_client_request_body() adds to
+ * r->request_body->bufs one or two bufs:
+ * *) one memory buf that was preread in r->header_in;
+ * *) one memory or file buf that contains the rest of the body
+ */
+
+ngx_int_t
+ngx_http_read_client_request_body(ngx_http_request_t *r,
+ ngx_http_client_body_handler_pt post_handler)
+{
+ size_t preread;
+ ssize_t size;
+ ngx_buf_t *b;
+ ngx_chain_t *cl, **next;
+ ngx_temp_file_t *tf;
+ ngx_http_request_body_t *rb;
+ ngx_http_core_loc_conf_t *clcf;
+
+ r->main->count++;
+
+ if (r->request_body || r->discard_body) {
+ post_handler(r);
+ return NGX_OK;
+ }
+
+ if (ngx_http_test_expect(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
+ if (rb == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ r->request_body = rb;
+
+ if (r->headers_in.content_length_n < 0) {
+ post_handler(r);
+ return NGX_OK;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (r->headers_in.content_length_n == 0) {
+
+ if (r->request_body_in_file_only) {
+ tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));
+ if (tf == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ tf->file.fd = NGX_INVALID_FILE;
+ tf->file.log = r->connection->log;
+ tf->path = clcf->client_body_temp_path;
+ tf->pool = r->pool;
+ tf->warn = "a client request body is buffered to a temporary file";
+ tf->log_level = r->request_body_file_log_level;
+ tf->persistent = r->request_body_in_persistent_file;
+ tf->clean = r->request_body_in_clean_file;
+
+ if (r->request_body_file_group_access) {
+ tf->access = 0660;
+ }
+
+ rb->temp_file = tf;
+
+ if (ngx_create_temp_file(&tf->file, tf->path, tf->pool,
+ tf->persistent, tf->clean, tf->access)
+ != NGX_OK)
+ {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ post_handler(r);
+
+ return NGX_OK;
+ }
+
+ rb->post_handler = post_handler;
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * rb->bufs = NULL;
+ * rb->buf = NULL;
+ * rb->rest = 0;
+ */
+
+ preread = r->header_in->last - r->header_in->pos;
+
+ if (preread) {
+
+ /* there is the pre-read part of the request body */
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http client request body preread %uz", preread);
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ b->temporary = 1;
+ b->start = r->header_in->pos;
+ b->pos = r->header_in->pos;
+ b->last = r->header_in->last;
+ b->end = r->header_in->end;
+
+ rb->bufs = ngx_alloc_chain_link(r->pool);
+ if (rb->bufs == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ rb->bufs->buf = b;
+ rb->bufs->next = NULL;
+
+ rb->buf = b;
+
+ if ((off_t) preread >= r->headers_in.content_length_n) {
+
+ /* the whole request body was pre-read */
+
+ r->header_in->pos += (size_t) r->headers_in.content_length_n;
+ r->request_length += r->headers_in.content_length_n;
+
+ if (r->request_body_in_file_only) {
+ if (ngx_http_write_request_body(r, rb->bufs) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ post_handler(r);
+
+ return NGX_OK;
+ }
+
+ /*
+ * to not consider the body as pipelined request in
+ * ngx_http_set_keepalive()
+ */
+ r->header_in->pos = r->header_in->last;
+
+ r->request_length += preread;
+
+ rb->rest = r->headers_in.content_length_n - preread;
+
+ if (rb->rest <= (off_t) (b->end - b->last)) {
+
+ /* the whole request body may be placed in r->header_in */
+
+ rb->to_write = rb->bufs;
+
+ r->read_event_handler = ngx_http_read_client_request_body_handler;
+
+ return ngx_http_do_read_client_request_body(r);
+ }
+
+ next = &rb->bufs->next;
+
+ } else {
+ b = NULL;
+ rb->rest = r->headers_in.content_length_n;
+ next = &rb->bufs;
+ }
+
+ size = clcf->client_body_buffer_size;
+ size += size >> 2;
+
+ if (rb->rest < size) {
+ size = (ssize_t) rb->rest;
+
+ if (r->request_body_in_single_buf) {
+ size += preread;
+ }
+
+ } else {
+ size = clcf->client_body_buffer_size;
+
+ /* disable copying buffer for r->request_body_in_single_buf */
+ b = NULL;
+ }
+
+ rb->buf = ngx_create_temp_buf(r->pool, size);
+ if (rb->buf == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ cl->buf = rb->buf;
+ cl->next = NULL;
+
+ if (b && r->request_body_in_single_buf) {
+ size = b->last - b->pos;
+ ngx_memcpy(rb->buf->pos, b->pos, size);
+ rb->buf->last += size;
+
+ next = &rb->bufs;
+ }
+
+ *next = cl;
+
+ if (r->request_body_in_file_only || r->request_body_in_single_buf) {
+ rb->to_write = rb->bufs;
+
+ } else {
+ rb->to_write = rb->bufs->next ? rb->bufs->next : rb->bufs;
+ }
+
+ r->read_event_handler = ngx_http_read_client_request_body_handler;
+
+ return ngx_http_do_read_client_request_body(r);
+}
+
+
+static void
+ngx_http_read_client_request_body_handler(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+
+ if (r->connection->read->timedout) {
+ r->connection->timedout = 1;
+ ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
+ return;
+ }
+
+ rc = ngx_http_do_read_client_request_body(r);
+
+ if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+ ngx_http_finalize_request(r, rc);
+ }
+}
+
+
+static ngx_int_t
+ngx_http_do_read_client_request_body(ngx_http_request_t *r)
+{
+ size_t size;
+ ssize_t n;
+ ngx_buf_t *b;
+ ngx_connection_t *c;
+ ngx_http_request_body_t *rb;
+ ngx_http_core_loc_conf_t *clcf;
+
+ c = r->connection;
+ rb = r->request_body;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http read client request body");
+
+ for ( ;; ) {
+ for ( ;; ) {
+ if (rb->buf->last == rb->buf->end) {
+
+ if (ngx_http_write_request_body(r, rb->to_write) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ rb->to_write = rb->bufs->next ? rb->bufs->next : rb->bufs;
+ rb->buf->last = rb->buf->start;
+ }
+
+ size = rb->buf->end - rb->buf->last;
+
+ if ((off_t) size > rb->rest) {
+ size = (size_t) rb->rest;
+ }
+
+ n = c->recv(c, rb->buf->last, size);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http client request body recv %z", n);
+
+ if (n == NGX_AGAIN) {
+ break;
+ }
+
+ if (n == 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client closed prematurely connection");
+ }
+
+ if (n == 0 || n == NGX_ERROR) {
+ c->error = 1;
+ return NGX_HTTP_BAD_REQUEST;
+ }
+
+ rb->buf->last += n;
+ rb->rest -= n;
+ r->request_length += n;
+
+ if (rb->rest == 0) {
+ break;
+ }
+
+ if (rb->buf->last < rb->buf->end) {
+ break;
+ }
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http client request body rest %O", rb->rest);
+
+ if (rb->rest == 0) {
+ break;
+ }
+
+ if (!c->read->ready) {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+ ngx_add_timer(c->read, clcf->client_body_timeout);
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+ }
+
+ if (c->read->timer_set) {
+ ngx_del_timer(c->read);
+ }
+
+ if (rb->temp_file || r->request_body_in_file_only) {
+
+ /* save the last part */
+
+ if (ngx_http_write_request_body(r, rb->to_write) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ b->in_file = 1;
+ b->file_pos = 0;
+ b->file_last = rb->temp_file->file.offset;
+ b->file = &rb->temp_file->file;
+
+ if (rb->bufs->next) {
+ rb->bufs->next->buf = b;
+
+ } else {
+ rb->bufs->buf = b;
+ }
+ }
+
+ if (r->request_body_in_file_only && rb->bufs->next) {
+ rb->bufs = rb->bufs->next;
+ }
+
+ rb->post_handler(r);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_write_request_body(ngx_http_request_t *r, ngx_chain_t *body)
+{
+ ssize_t n;
+ ngx_temp_file_t *tf;
+ ngx_http_request_body_t *rb;
+ ngx_http_core_loc_conf_t *clcf;
+
+ rb = r->request_body;
+
+ if (rb->temp_file == NULL) {
+ tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));
+ if (tf == NULL) {
+ return NGX_ERROR;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ tf->file.fd = NGX_INVALID_FILE;
+ tf->file.log = r->connection->log;
+ tf->path = clcf->client_body_temp_path;
+ tf->pool = r->pool;
+ tf->warn = "a client request body is buffered to a temporary file";
+ tf->log_level = r->request_body_file_log_level;
+ tf->persistent = r->request_body_in_persistent_file;
+ tf->clean = r->request_body_in_clean_file;
+
+ if (r->request_body_file_group_access) {
+ tf->access = 0660;
+ }
+
+ rb->temp_file = tf;
+ }
+
+ n = ngx_write_chain_to_temp_file(rb->temp_file, body);
+
+ /* TODO: n == 0 or not complete and level event */
+
+ if (n == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ rb->temp_file->offset += n;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_discard_request_body(ngx_http_request_t *r)
+{
+ ssize_t size;
+ ngx_event_t *rev;
+
+ if (r != r->main || r->discard_body) {
+ return NGX_OK;
+ }
+
+ if (ngx_http_test_expect(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ rev = r->connection->read;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http set discard body");
+
+ if (rev->timer_set) {
+ ngx_del_timer(rev);
+ }
+
+ if (r->headers_in.content_length_n <= 0 || r->request_body) {
+ return NGX_OK;
+ }
+
+ size = r->header_in->last - r->header_in->pos;
+
+ if (size) {
+ if (r->headers_in.content_length_n > size) {
+ r->header_in->pos += size;
+ r->headers_in.content_length_n -= size;
+
+ } else {
+ r->header_in->pos += (size_t) r->headers_in.content_length_n;
+ r->headers_in.content_length_n = 0;
+ return NGX_OK;
+ }
+ }
+
+ r->read_event_handler = ngx_http_discarded_request_body_handler;
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_http_read_discarded_request_body(r) == NGX_OK) {
+ r->lingering_close = 0;
+
+ } else {
+ r->count++;
+ r->discard_body = 1;
+ }
+
+ return NGX_OK;
+}
+
+
+void
+ngx_http_discarded_request_body_handler(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_msec_t timer;
+ ngx_event_t *rev;
+ ngx_connection_t *c;
+ ngx_http_core_loc_conf_t *clcf;
+
+ c = r->connection;
+ rev = c->read;
+
+ if (rev->timedout) {
+ c->timedout = 1;
+ c->error = 1;
+ ngx_http_finalize_request(r, NGX_ERROR);
+ return;
+ }
+
+ if (r->lingering_time) {
+ timer = (ngx_msec_t) (r->lingering_time - ngx_time());
+
+ if (timer <= 0) {
+ r->discard_body = 0;
+ r->lingering_close = 0;
+ ngx_http_finalize_request(r, NGX_ERROR);
+ return;
+ }
+
+ } else {
+ timer = 0;
+ }
+
+ rc = ngx_http_read_discarded_request_body(r);
+
+ if (rc == NGX_OK) {
+ r->discard_body = 0;
+ r->lingering_close = 0;
+ ngx_http_finalize_request(r, NGX_DONE);
+ return;
+ }
+
+ /* rc == NGX_AGAIN */
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ c->error = 1;
+ ngx_http_finalize_request(r, NGX_ERROR);
+ return;
+ }
+
+ if (timer) {
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ timer *= 1000;
+
+ if (timer > clcf->lingering_timeout) {
+ timer = clcf->lingering_timeout;
+ }
+
+ ngx_add_timer(rev, timer);
+ }
+}
+
+
+static ngx_int_t
+ngx_http_read_discarded_request_body(ngx_http_request_t *r)
+{
+ size_t size;
+ ssize_t n;
+ u_char buffer[NGX_HTTP_DISCARD_BUFFER_SIZE];
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http read discarded body");
+
+ for ( ;; ) {
+ if (r->headers_in.content_length_n == 0) {
+ r->read_event_handler = ngx_http_block_reading;
+ return NGX_OK;
+ }
+
+ if (!r->connection->read->ready) {
+ return NGX_AGAIN;
+ }
+
+ size = (r->headers_in.content_length_n > NGX_HTTP_DISCARD_BUFFER_SIZE) ?
+ NGX_HTTP_DISCARD_BUFFER_SIZE:
+ (size_t) r->headers_in.content_length_n;
+
+ n = r->connection->recv(r->connection, buffer, size);
+
+ if (n == NGX_ERROR) {
+ r->connection->error = 1;
+ return NGX_OK;
+ }
+
+ if (n == NGX_AGAIN) {
+ return NGX_AGAIN;
+ }
+
+ if (n == 0) {
+ return NGX_OK;
+ }
+
+ r->headers_in.content_length_n -= n;
+ }
+}
+
+
+static ngx_int_t
+ngx_http_test_expect(ngx_http_request_t *r)
+{
+ ngx_int_t n;
+ ngx_str_t *expect;
+
+ if (r->expect_tested
+ || r->headers_in.expect == NULL
+ || r->http_version < NGX_HTTP_VERSION_11)
+ {
+ return NGX_OK;
+ }
+
+ r->expect_tested = 1;
+
+ expect = &r->headers_in.expect->value;
+
+ if (expect->len != sizeof("100-continue") - 1
+ || ngx_strncasecmp(expect->data, (u_char *) "100-continue",
+ sizeof("100-continue") - 1)
+ != 0)
+ {
+ return NGX_OK;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "send 100 Continue");
+
+ n = r->connection->send(r->connection,
+ (u_char *) "HTTP/1.1 100 Continue" CRLF CRLF,
+ sizeof("HTTP/1.1 100 Continue" CRLF CRLF) - 1);
+
+ if (n == sizeof("HTTP/1.1 100 Continue" CRLF CRLF) - 1) {
+ return NGX_OK;
+ }
+
+ /* we assume that such small packet should be send successfully */
+
+ return NGX_ERROR;
+}
diff --git a/usr.sbin/nginx/src/http/ngx_http_script.c b/usr.sbin/nginx/src/http/ngx_http_script.c
new file mode 100644
index 00000000000..a703f083789
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_script.c
@@ -0,0 +1,1749 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_script_init_arrays(ngx_http_script_compile_t *sc);
+static ngx_int_t ngx_http_script_done(ngx_http_script_compile_t *sc);
+static ngx_int_t ngx_http_script_add_copy_code(ngx_http_script_compile_t *sc,
+ ngx_str_t *value, ngx_uint_t last);
+static ngx_int_t ngx_http_script_add_var_code(ngx_http_script_compile_t *sc,
+ ngx_str_t *name);
+static ngx_int_t ngx_http_script_add_args_code(ngx_http_script_compile_t *sc);
+#if (NGX_PCRE)
+static ngx_int_t ngx_http_script_add_capture_code(ngx_http_script_compile_t *sc,
+ ngx_uint_t n);
+#endif
+static ngx_int_t
+ ngx_http_script_add_full_name_code(ngx_http_script_compile_t *sc);
+static size_t ngx_http_script_full_name_len_code(ngx_http_script_engine_t *e);
+static void ngx_http_script_full_name_code(ngx_http_script_engine_t *e);
+
+
+#define ngx_http_script_exit (u_char *) &ngx_http_script_exit_code
+
+static uintptr_t ngx_http_script_exit_code = (uintptr_t) NULL;
+
+
+void
+ngx_http_script_flush_complex_value(ngx_http_request_t *r,
+ ngx_http_complex_value_t *val)
+{
+ ngx_uint_t *index;
+
+ index = val->flushes;
+
+ if (index) {
+ while (*index != (ngx_uint_t) -1) {
+
+ if (r->variables[*index].no_cacheable) {
+ r->variables[*index].valid = 0;
+ r->variables[*index].not_found = 0;
+ }
+
+ index++;
+ }
+ }
+}
+
+
+ngx_int_t
+ngx_http_complex_value(ngx_http_request_t *r, ngx_http_complex_value_t *val,
+ ngx_str_t *value)
+{
+ size_t len;
+ ngx_http_script_code_pt code;
+ ngx_http_script_len_code_pt lcode;
+ ngx_http_script_engine_t e;
+
+ if (val->lengths == NULL) {
+ *value = val->value;
+ return NGX_OK;
+ }
+
+ ngx_http_script_flush_complex_value(r, val);
+
+ ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+ e.ip = val->lengths;
+ e.request = r;
+ e.flushed = 1;
+
+ len = 0;
+
+ while (*(uintptr_t *) e.ip) {
+ lcode = *(ngx_http_script_len_code_pt *) e.ip;
+ len += lcode(&e);
+ }
+
+ value->len = len;
+ value->data = ngx_pnalloc(r->pool, len);
+ if (value->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ e.ip = val->values;
+ e.pos = value->data;
+ e.buf = *value;
+
+ while (*(uintptr_t *) e.ip) {
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code((ngx_http_script_engine_t *) &e);
+ }
+
+ *value = e.buf;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_compile_complex_value(ngx_http_compile_complex_value_t *ccv)
+{
+ ngx_str_t *v;
+ ngx_uint_t i, n, nv, nc;
+ ngx_array_t flushes, lengths, values, *pf, *pl, *pv;
+ ngx_http_script_compile_t sc;
+
+ v = ccv->value;
+
+ if (v->len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, ccv->cf, 0, "empty parameter");
+ return NGX_ERROR;
+ }
+
+ nv = 0;
+ nc = 0;
+
+ for (i = 0; i < v->len; i++) {
+ if (v->data[i] == '$') {
+ if (v->data[i + 1] >= '1' && v->data[i + 1] <= '9') {
+ nc++;
+
+ } else {
+ nv++;
+ }
+ }
+ }
+
+ if (v->data[0] != '$' && (ccv->conf_prefix || ccv->root_prefix)) {
+
+ if (ngx_conf_full_name(ccv->cf->cycle, v, ccv->conf_prefix) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ccv->conf_prefix = 0;
+ ccv->root_prefix = 0;
+ }
+
+ ccv->complex_value->value = *v;
+ ccv->complex_value->flushes = NULL;
+ ccv->complex_value->lengths = NULL;
+ ccv->complex_value->values = NULL;
+
+ if (nv == 0 && nc == 0) {
+ return NGX_OK;
+ }
+
+ n = nv + 1;
+
+ if (ngx_array_init(&flushes, ccv->cf->pool, n, sizeof(ngx_uint_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ n = nv * (2 * sizeof(ngx_http_script_copy_code_t)
+ + sizeof(ngx_http_script_var_code_t))
+ + sizeof(uintptr_t);
+
+ if (ngx_array_init(&lengths, ccv->cf->pool, n, 1) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ n = (nv * (2 * sizeof(ngx_http_script_copy_code_t)
+ + sizeof(ngx_http_script_var_code_t))
+ + sizeof(uintptr_t)
+ + v->len
+ + sizeof(uintptr_t) - 1)
+ & ~(sizeof(uintptr_t) - 1);
+
+ if (ngx_array_init(&values, ccv->cf->pool, n, 1) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ pf = &flushes;
+ pl = &lengths;
+ pv = &values;
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = ccv->cf;
+ sc.source = v;
+ sc.flushes = &pf;
+ sc.lengths = &pl;
+ sc.values = &pv;
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+ sc.zero = ccv->zero;
+ sc.conf_prefix = ccv->conf_prefix;
+ sc.root_prefix = ccv->root_prefix;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (flushes.nelts) {
+ ccv->complex_value->flushes = flushes.elts;
+ ccv->complex_value->flushes[flushes.nelts] = (ngx_uint_t) -1;
+ }
+
+ ccv->complex_value->lengths = lengths.elts;
+ ccv->complex_value->values = values.elts;
+
+ return NGX_OK;
+}
+
+
+char *
+ngx_http_set_complex_value_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *p = conf;
+
+ ngx_str_t *value;
+ ngx_http_complex_value_t **cv;
+ ngx_http_compile_complex_value_t ccv;
+
+ cv = (ngx_http_complex_value_t **) (p + cmd->offset);
+
+ if (*cv != NULL) {
+ return "duplicate";
+ }
+
+ *cv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
+ if (*cv == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = *cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+ngx_int_t
+ngx_http_test_predicates(ngx_http_request_t *r, ngx_array_t *predicates)
+{
+ ngx_str_t val;
+ ngx_uint_t i;
+ ngx_http_complex_value_t *cv;
+
+ if (predicates == NULL) {
+ return NGX_OK;
+ }
+
+ cv = predicates->elts;
+
+ for (i = 0; i < predicates->nelts; i++) {
+ if (ngx_http_complex_value(r, &cv[i], &val) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (val.len && (val.len != 1 || val.data[0] != '0')) {
+ return NGX_DECLINED;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+char *
+ngx_http_set_predicate_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *p = conf;
+
+ ngx_str_t *value;
+ ngx_uint_t i;
+ ngx_array_t **a;
+ ngx_http_complex_value_t *cv;
+ ngx_http_compile_complex_value_t ccv;
+
+ a = (ngx_array_t **) (p + cmd->offset);
+
+ if (*a == NGX_CONF_UNSET_PTR) {
+ *a = ngx_array_create(cf->pool, 1, sizeof(ngx_http_complex_value_t));
+ if (*a == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+ cv = ngx_array_push(*a);
+ if (cv == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[i];
+ ccv.complex_value = cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+ngx_uint_t
+ngx_http_script_variables_count(ngx_str_t *value)
+{
+ ngx_uint_t i, n;
+
+ for (n = 0, i = 0; i < value->len; i++) {
+ if (value->data[i] == '$') {
+ n++;
+ }
+ }
+
+ return n;
+}
+
+
+ngx_int_t
+ngx_http_script_compile(ngx_http_script_compile_t *sc)
+{
+ u_char ch;
+ ngx_str_t name;
+ ngx_uint_t i, bracket;
+
+ if (ngx_http_script_init_arrays(sc) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ for (i = 0; i < sc->source->len; /* void */ ) {
+
+ name.len = 0;
+
+ if (sc->source->data[i] == '$') {
+
+ if (++i == sc->source->len) {
+ goto invalid_variable;
+ }
+
+#if (NGX_PCRE)
+ {
+ ngx_uint_t n;
+
+ if (sc->source->data[i] >= '1' && sc->source->data[i] <= '9') {
+
+ n = sc->source->data[i] - '0';
+
+ if (sc->captures_mask & (1 << n)) {
+ sc->dup_capture = 1;
+ }
+
+ sc->captures_mask |= 1 << n;
+
+ if (ngx_http_script_add_capture_code(sc, n) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ i++;
+
+ continue;
+ }
+ }
+#endif
+
+ if (sc->source->data[i] == '{') {
+ bracket = 1;
+
+ if (++i == sc->source->len) {
+ goto invalid_variable;
+ }
+
+ name.data = &sc->source->data[i];
+
+ } else {
+ bracket = 0;
+ name.data = &sc->source->data[i];
+ }
+
+ for ( /* void */ ; i < sc->source->len; i++, name.len++) {
+ ch = sc->source->data[i];
+
+ if (ch == '}' && bracket) {
+ i++;
+ bracket = 0;
+ break;
+ }
+
+ if ((ch >= 'A' && ch <= 'Z')
+ || (ch >= 'a' && ch <= 'z')
+ || (ch >= '0' && ch <= '9')
+ || ch == '_')
+ {
+ continue;
+ }
+
+ break;
+ }
+
+ if (bracket) {
+ ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0,
+ "the closing bracket in \"%V\" "
+ "variable is missing", &name);
+ return NGX_ERROR;
+ }
+
+ if (name.len == 0) {
+ goto invalid_variable;
+ }
+
+ sc->variables++;
+
+ if (ngx_http_script_add_var_code(sc, &name) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ continue;
+ }
+
+ if (sc->source->data[i] == '?' && sc->compile_args) {
+ sc->args = 1;
+ sc->compile_args = 0;
+
+ if (ngx_http_script_add_args_code(sc) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ i++;
+
+ continue;
+ }
+
+ name.data = &sc->source->data[i];
+
+ while (i < sc->source->len) {
+
+ if (sc->source->data[i] == '$') {
+ break;
+ }
+
+ if (sc->source->data[i] == '?') {
+
+ sc->args = 1;
+
+ if (sc->compile_args) {
+ break;
+ }
+ }
+
+ i++;
+ name.len++;
+ }
+
+ sc->size += name.len;
+
+ if (ngx_http_script_add_copy_code(sc, &name, (i == sc->source->len))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ return ngx_http_script_done(sc);
+
+invalid_variable:
+
+ ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0, "invalid variable name");
+
+ return NGX_ERROR;
+}
+
+
+u_char *
+ngx_http_script_run(ngx_http_request_t *r, ngx_str_t *value,
+ void *code_lengths, size_t len, void *code_values)
+{
+ ngx_uint_t i;
+ ngx_http_script_code_pt code;
+ ngx_http_script_len_code_pt lcode;
+ ngx_http_script_engine_t e;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ for (i = 0; i < cmcf->variables.nelts; i++) {
+ if (r->variables[i].no_cacheable) {
+ r->variables[i].valid = 0;
+ r->variables[i].not_found = 0;
+ }
+ }
+
+ ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+ e.ip = code_lengths;
+ e.request = r;
+ e.flushed = 1;
+
+ while (*(uintptr_t *) e.ip) {
+ lcode = *(ngx_http_script_len_code_pt *) e.ip;
+ len += lcode(&e);
+ }
+
+
+ value->len = len;
+ value->data = ngx_pnalloc(r->pool, len);
+ if (value->data == NULL) {
+ return NULL;
+ }
+
+ e.ip = code_values;
+ e.pos = value->data;
+
+ while (*(uintptr_t *) e.ip) {
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code((ngx_http_script_engine_t *) &e);
+ }
+
+ return e.pos;
+}
+
+
+void
+ngx_http_script_flush_no_cacheable_variables(ngx_http_request_t *r,
+ ngx_array_t *indices)
+{
+ ngx_uint_t n, *index;
+
+ if (indices) {
+ index = indices->elts;
+ for (n = 0; n < indices->nelts; n++) {
+ if (r->variables[index[n]].no_cacheable) {
+ r->variables[index[n]].valid = 0;
+ r->variables[index[n]].not_found = 0;
+ }
+ }
+ }
+}
+
+
+static ngx_int_t
+ngx_http_script_init_arrays(ngx_http_script_compile_t *sc)
+{
+ ngx_uint_t n;
+
+ if (sc->flushes && *sc->flushes == NULL) {
+ n = sc->variables ? sc->variables : 1;
+ *sc->flushes = ngx_array_create(sc->cf->pool, n, sizeof(ngx_uint_t));
+ if (*sc->flushes == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ if (*sc->lengths == NULL) {
+ n = sc->variables * (2 * sizeof(ngx_http_script_copy_code_t)
+ + sizeof(ngx_http_script_var_code_t))
+ + sizeof(uintptr_t);
+
+ *sc->lengths = ngx_array_create(sc->cf->pool, n, 1);
+ if (*sc->lengths == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ if (*sc->values == NULL) {
+ n = (sc->variables * (2 * sizeof(ngx_http_script_copy_code_t)
+ + sizeof(ngx_http_script_var_code_t))
+ + sizeof(uintptr_t)
+ + sc->source->len
+ + sizeof(uintptr_t) - 1)
+ & ~(sizeof(uintptr_t) - 1);
+
+ *sc->values = ngx_array_create(sc->cf->pool, n, 1);
+ if (*sc->values == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ sc->variables = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_script_done(ngx_http_script_compile_t *sc)
+{
+ ngx_str_t zero;
+ uintptr_t *code;
+
+ if (sc->zero) {
+
+ zero.len = 1;
+ zero.data = (u_char *) "\0";
+
+ if (ngx_http_script_add_copy_code(sc, &zero, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ if (sc->conf_prefix || sc->root_prefix) {
+ if (ngx_http_script_add_full_name_code(sc) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ if (sc->complete_lengths) {
+ code = ngx_http_script_add_code(*sc->lengths, sizeof(uintptr_t), NULL);
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+ }
+
+ if (sc->complete_values) {
+ code = ngx_http_script_add_code(*sc->values, sizeof(uintptr_t),
+ &sc->main);
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+ }
+
+ return NGX_OK;
+}
+
+
+void *
+ngx_http_script_start_code(ngx_pool_t *pool, ngx_array_t **codes, size_t size)
+{
+ if (*codes == NULL) {
+ *codes = ngx_array_create(pool, 256, 1);
+ if (*codes == NULL) {
+ return NULL;
+ }
+ }
+
+ return ngx_array_push_n(*codes, size);
+}
+
+
+void *
+ngx_http_script_add_code(ngx_array_t *codes, size_t size, void *code)
+{
+ u_char *elts, **p;
+ void *new;
+
+ elts = codes->elts;
+
+ new = ngx_array_push_n(codes, size);
+ if (new == NULL) {
+ return NULL;
+ }
+
+ if (code) {
+ if (elts != codes->elts) {
+ p = code;
+ *p += (u_char *) codes->elts - elts;
+ }
+ }
+
+ return new;
+}
+
+
+static ngx_int_t
+ngx_http_script_add_copy_code(ngx_http_script_compile_t *sc, ngx_str_t *value,
+ ngx_uint_t last)
+{
+ u_char *p;
+ size_t size, len, zero;
+ ngx_http_script_copy_code_t *code;
+
+ zero = (sc->zero && last);
+ len = value->len + zero;
+
+ code = ngx_http_script_add_code(*sc->lengths,
+ sizeof(ngx_http_script_copy_code_t), NULL);
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ code->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code;
+ code->len = len;
+
+ size = (sizeof(ngx_http_script_copy_code_t) + len + sizeof(uintptr_t) - 1)
+ & ~(sizeof(uintptr_t) - 1);
+
+ code = ngx_http_script_add_code(*sc->values, size, &sc->main);
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ code->code = ngx_http_script_copy_code;
+ code->len = len;
+
+ p = ngx_cpymem((u_char *) code + sizeof(ngx_http_script_copy_code_t),
+ value->data, value->len);
+
+ if (zero) {
+ *p = '\0';
+ sc->zero = 0;
+ }
+
+ return NGX_OK;
+}
+
+
+size_t
+ngx_http_script_copy_len_code(ngx_http_script_engine_t *e)
+{
+ ngx_http_script_copy_code_t *code;
+
+ code = (ngx_http_script_copy_code_t *) e->ip;
+
+ e->ip += sizeof(ngx_http_script_copy_code_t);
+
+ return code->len;
+}
+
+
+void
+ngx_http_script_copy_code(ngx_http_script_engine_t *e)
+{
+ u_char *p;
+ ngx_http_script_copy_code_t *code;
+
+ code = (ngx_http_script_copy_code_t *) e->ip;
+
+ p = e->pos;
+
+ if (!e->skip) {
+ e->pos = ngx_copy(p, e->ip + sizeof(ngx_http_script_copy_code_t),
+ code->len);
+ }
+
+ e->ip += sizeof(ngx_http_script_copy_code_t)
+ + ((code->len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1));
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+ "http script copy: \"%*s\"", e->pos - p, p);
+}
+
+
+static ngx_int_t
+ngx_http_script_add_var_code(ngx_http_script_compile_t *sc, ngx_str_t *name)
+{
+ ngx_int_t index, *p;
+ ngx_http_script_var_code_t *code;
+
+ index = ngx_http_get_variable_index(sc->cf, name);
+
+ if (index == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (sc->flushes) {
+ p = ngx_array_push(*sc->flushes);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ *p = index;
+ }
+
+ code = ngx_http_script_add_code(*sc->lengths,
+ sizeof(ngx_http_script_var_code_t), NULL);
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ code->code = (ngx_http_script_code_pt) ngx_http_script_copy_var_len_code;
+ code->index = (uintptr_t) index;
+
+ code = ngx_http_script_add_code(*sc->values,
+ sizeof(ngx_http_script_var_code_t),
+ &sc->main);
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ code->code = ngx_http_script_copy_var_code;
+ code->index = (uintptr_t) index;
+
+ return NGX_OK;
+}
+
+
+size_t
+ngx_http_script_copy_var_len_code(ngx_http_script_engine_t *e)
+{
+ ngx_http_variable_value_t *value;
+ ngx_http_script_var_code_t *code;
+
+ code = (ngx_http_script_var_code_t *) e->ip;
+
+ e->ip += sizeof(ngx_http_script_var_code_t);
+
+ if (e->flushed) {
+ value = ngx_http_get_indexed_variable(e->request, code->index);
+
+ } else {
+ value = ngx_http_get_flushed_variable(e->request, code->index);
+ }
+
+ if (value && !value->not_found) {
+ return value->len;
+ }
+
+ return 0;
+}
+
+
+void
+ngx_http_script_copy_var_code(ngx_http_script_engine_t *e)
+{
+ u_char *p;
+ ngx_http_variable_value_t *value;
+ ngx_http_script_var_code_t *code;
+
+ code = (ngx_http_script_var_code_t *) e->ip;
+
+ e->ip += sizeof(ngx_http_script_var_code_t);
+
+ if (!e->skip) {
+
+ if (e->flushed) {
+ value = ngx_http_get_indexed_variable(e->request, code->index);
+
+ } else {
+ value = ngx_http_get_flushed_variable(e->request, code->index);
+ }
+
+ if (value && !value->not_found) {
+ p = e->pos;
+ e->pos = ngx_copy(p, value->data, value->len);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP,
+ e->request->connection->log, 0,
+ "http script var: \"%*s\"", e->pos - p, p);
+ }
+ }
+}
+
+
+static ngx_int_t
+ngx_http_script_add_args_code(ngx_http_script_compile_t *sc)
+{
+ uintptr_t *code;
+
+ code = ngx_http_script_add_code(*sc->lengths, sizeof(uintptr_t), NULL);
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ *code = (uintptr_t) ngx_http_script_mark_args_code;
+
+ code = ngx_http_script_add_code(*sc->values, sizeof(uintptr_t), &sc->main);
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ *code = (uintptr_t) ngx_http_script_start_args_code;
+
+ return NGX_OK;
+}
+
+
+size_t
+ngx_http_script_mark_args_code(ngx_http_script_engine_t *e)
+{
+ e->is_args = 1;
+ e->ip += sizeof(uintptr_t);
+
+ return 1;
+}
+
+
+void
+ngx_http_script_start_args_code(ngx_http_script_engine_t *e)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+ "http script args");
+
+ e->is_args = 1;
+ e->args = e->pos;
+ e->ip += sizeof(uintptr_t);
+}
+
+
+#if (NGX_PCRE)
+
+void
+ngx_http_script_regex_start_code(ngx_http_script_engine_t *e)
+{
+ size_t len;
+ ngx_int_t rc;
+ ngx_uint_t n;
+ ngx_http_request_t *r;
+ ngx_http_script_engine_t le;
+ ngx_http_script_len_code_pt lcode;
+ ngx_http_script_regex_code_t *code;
+
+ code = (ngx_http_script_regex_code_t *) e->ip;
+
+ r = e->request;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http script regex: \"%V\"", &code->name);
+
+ if (code->uri) {
+ e->line = r->uri;
+ } else {
+ e->sp--;
+ e->line.len = e->sp->len;
+ e->line.data = e->sp->data;
+ }
+
+ rc = ngx_http_regex_exec(r, code->regex, &e->line);
+
+ if (rc == NGX_DECLINED) {
+ if (e->log || (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP)) {
+ ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+ "\"%V\" does not match \"%V\"",
+ &code->name, &e->line);
+ }
+
+ r->ncaptures = 0;
+
+ if (code->test) {
+ if (code->negative_test) {
+ e->sp->len = 1;
+ e->sp->data = (u_char *) "1";
+
+ } else {
+ e->sp->len = 0;
+ e->sp->data = (u_char *) "";
+ }
+
+ e->sp++;
+
+ e->ip += sizeof(ngx_http_script_regex_code_t);
+ return;
+ }
+
+ e->ip += code->next;
+ return;
+ }
+
+ if (rc == NGX_ERROR) {
+ e->ip = ngx_http_script_exit;
+ e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ return;
+ }
+
+ if (e->log || (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP)) {
+ ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+ "\"%V\" matches \"%V\"", &code->name, &e->line);
+ }
+
+ if (code->test) {
+ if (code->negative_test) {
+ e->sp->len = 0;
+ e->sp->data = (u_char *) "";
+
+ } else {
+ e->sp->len = 1;
+ e->sp->data = (u_char *) "1";
+ }
+
+ e->sp++;
+
+ e->ip += sizeof(ngx_http_script_regex_code_t);
+ return;
+ }
+
+ if (code->status) {
+ e->status = code->status;
+
+ if (!code->redirect) {
+ e->ip = ngx_http_script_exit;
+ return;
+ }
+ }
+
+ if (code->uri) {
+ r->internal = 1;
+ r->valid_unparsed_uri = 0;
+
+ if (code->break_cycle) {
+ r->valid_location = 0;
+ r->uri_changed = 0;
+
+ } else {
+ r->uri_changed = 1;
+ }
+ }
+
+ if (code->lengths == NULL) {
+ e->buf.len = code->size;
+
+ if (code->uri) {
+ if (r->ncaptures && (r->quoted_uri || r->plus_in_uri)) {
+ e->buf.len += 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len,
+ NGX_ESCAPE_ARGS);
+ }
+ }
+
+ for (n = 2; n < r->ncaptures; n += 2) {
+ e->buf.len += r->captures[n + 1] - r->captures[n];
+ }
+
+ } else {
+ ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
+
+ le.ip = code->lengths->elts;
+ le.line = e->line;
+ le.request = r;
+ le.quote = code->redirect;
+
+ len = 0;
+
+ while (*(uintptr_t *) le.ip) {
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ len += lcode(&le);
+ }
+
+ e->buf.len = len;
+ e->is_args = le.is_args;
+ }
+
+ if (code->add_args && r->args.len) {
+ e->buf.len += r->args.len + 1;
+ }
+
+ e->buf.data = ngx_pnalloc(r->pool, e->buf.len);
+ if (e->buf.data == NULL) {
+ e->ip = ngx_http_script_exit;
+ e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ return;
+ }
+
+ e->quote = code->redirect;
+
+ e->pos = e->buf.data;
+
+ e->ip += sizeof(ngx_http_script_regex_code_t);
+}
+
+
+void
+ngx_http_script_regex_end_code(ngx_http_script_engine_t *e)
+{
+ u_char *dst, *src;
+ ngx_http_request_t *r;
+ ngx_http_script_regex_end_code_t *code;
+
+ code = (ngx_http_script_regex_end_code_t *) e->ip;
+
+ r = e->request;
+
+ e->quote = 0;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http script regex end");
+
+ if (code->redirect) {
+
+ dst = e->buf.data;
+ src = e->buf.data;
+
+ ngx_unescape_uri(&dst, &src, e->pos - e->buf.data,
+ NGX_UNESCAPE_REDIRECT);
+
+ if (src < e->pos) {
+ dst = ngx_movemem(dst, src, e->pos - src);
+ }
+
+ e->pos = dst;
+
+ if (code->add_args && r->args.len) {
+ *e->pos++ = (u_char) (code->args ? '&' : '?');
+ e->pos = ngx_copy(e->pos, r->args.data, r->args.len);
+ }
+
+ e->buf.len = e->pos - e->buf.data;
+
+ if (e->log || (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP)) {
+ ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+ "rewritten redirect: \"%V\"", &e->buf);
+ }
+
+ r->headers_out.location = ngx_list_push(&r->headers_out.headers);
+ if (r->headers_out.location == NULL) {
+ e->ip = ngx_http_script_exit;
+ e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ return;
+ }
+
+ r->headers_out.location->hash = 1;
+ ngx_str_set(&r->headers_out.location->key, "Location");
+ r->headers_out.location->value = e->buf;
+
+ e->ip += sizeof(ngx_http_script_regex_end_code_t);
+ return;
+ }
+
+ if (e->args) {
+ e->buf.len = e->args - e->buf.data;
+
+ if (code->add_args && r->args.len) {
+ *e->pos++ = '&';
+ e->pos = ngx_copy(e->pos, r->args.data, r->args.len);
+ }
+
+ r->args.len = e->pos - e->args;
+ r->args.data = e->args;
+
+ e->args = NULL;
+
+ } else {
+ e->buf.len = e->pos - e->buf.data;
+
+ if (!code->add_args) {
+ r->args.len = 0;
+ }
+ }
+
+ if (e->log || (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP)) {
+ ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+ "rewritten data: \"%V\", args: \"%V\"",
+ &e->buf, &r->args);
+ }
+
+ if (code->uri) {
+ r->uri = e->buf;
+
+ if (r->uri.len == 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "the rewritten URI has a zero length");
+ e->ip = ngx_http_script_exit;
+ e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ return;
+ }
+
+ ngx_http_set_exten(r);
+ }
+
+ e->ip += sizeof(ngx_http_script_regex_end_code_t);
+}
+
+
+static ngx_int_t
+ngx_http_script_add_capture_code(ngx_http_script_compile_t *sc, ngx_uint_t n)
+{
+ ngx_http_script_copy_capture_code_t *code;
+
+ code = ngx_http_script_add_code(*sc->lengths,
+ sizeof(ngx_http_script_copy_capture_code_t),
+ NULL);
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ code->code = (ngx_http_script_code_pt)
+ ngx_http_script_copy_capture_len_code;
+ code->n = 2 * n;
+
+
+ code = ngx_http_script_add_code(*sc->values,
+ sizeof(ngx_http_script_copy_capture_code_t),
+ &sc->main);
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ code->code = ngx_http_script_copy_capture_code;
+ code->n = 2 * n;
+
+ if (sc->ncaptures < n) {
+ sc->ncaptures = n;
+ }
+
+ return NGX_OK;
+}
+
+
+size_t
+ngx_http_script_copy_capture_len_code(ngx_http_script_engine_t *e)
+{
+ int *cap;
+ u_char *p;
+ ngx_uint_t n;
+ ngx_http_request_t *r;
+ ngx_http_script_copy_capture_code_t *code;
+
+ r = e->request;
+
+ code = (ngx_http_script_copy_capture_code_t *) e->ip;
+
+ e->ip += sizeof(ngx_http_script_copy_capture_code_t);
+
+ n = code->n;
+
+ if (n < r->ncaptures) {
+
+ cap = r->captures;
+
+ if ((e->is_args || e->quote)
+ && (e->request->quoted_uri || e->request->plus_in_uri))
+ {
+ p = r->captures_data;
+
+ return cap[n + 1] - cap[n]
+ + 2 * ngx_escape_uri(NULL, &p[cap[n]], cap[n + 1] - cap[n],
+ NGX_ESCAPE_ARGS);
+ } else {
+ return cap[n + 1] - cap[n];
+ }
+ }
+
+ return 0;
+}
+
+
+void
+ngx_http_script_copy_capture_code(ngx_http_script_engine_t *e)
+{
+ int *cap;
+ u_char *p, *pos;
+ ngx_uint_t n;
+ ngx_http_request_t *r;
+ ngx_http_script_copy_capture_code_t *code;
+
+ r = e->request;
+
+ code = (ngx_http_script_copy_capture_code_t *) e->ip;
+
+ e->ip += sizeof(ngx_http_script_copy_capture_code_t);
+
+ n = code->n;
+
+ pos = e->pos;
+
+ if (n < r->ncaptures) {
+
+ cap = r->captures;
+ p = r->captures_data;
+
+ if ((e->is_args || e->quote)
+ && (e->request->quoted_uri || e->request->plus_in_uri))
+ {
+ e->pos = (u_char *) ngx_escape_uri(pos, &p[cap[n]],
+ cap[n + 1] - cap[n],
+ NGX_ESCAPE_ARGS);
+ } else {
+ e->pos = ngx_copy(pos, &p[cap[n]], cap[n + 1] - cap[n]);
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+ "http script capture: \"%*s\"", e->pos - pos, pos);
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_script_add_full_name_code(ngx_http_script_compile_t *sc)
+{
+ ngx_http_script_full_name_code_t *code;
+
+ code = ngx_http_script_add_code(*sc->lengths,
+ sizeof(ngx_http_script_full_name_code_t),
+ NULL);
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ code->code = (ngx_http_script_code_pt) ngx_http_script_full_name_len_code;
+ code->conf_prefix = sc->conf_prefix;
+
+ code = ngx_http_script_add_code(*sc->values,
+ sizeof(ngx_http_script_full_name_code_t),
+ &sc->main);
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ code->code = ngx_http_script_full_name_code;
+ code->conf_prefix = sc->conf_prefix;
+
+ return NGX_OK;
+}
+
+
+static size_t
+ngx_http_script_full_name_len_code(ngx_http_script_engine_t *e)
+{
+ ngx_http_script_full_name_code_t *code;
+
+ code = (ngx_http_script_full_name_code_t *) e->ip;
+
+ e->ip += sizeof(ngx_http_script_full_name_code_t);
+
+ return code->conf_prefix ? ngx_cycle->conf_prefix.len:
+ ngx_cycle->prefix.len;
+}
+
+
+static void
+ngx_http_script_full_name_code(ngx_http_script_engine_t *e)
+{
+ ngx_http_script_full_name_code_t *code;
+
+ ngx_str_t value;
+
+ code = (ngx_http_script_full_name_code_t *) e->ip;
+
+ value.data = e->buf.data;
+ value.len = e->pos - e->buf.data;
+
+ if (ngx_conf_full_name((ngx_cycle_t *) ngx_cycle, &value, code->conf_prefix)
+ != NGX_OK)
+ {
+ e->ip = ngx_http_script_exit;
+ e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ return;
+ }
+
+ e->buf = value;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+ "http script fullname: \"%V\"", &value);
+
+ e->ip += sizeof(ngx_http_script_full_name_code_t);
+}
+
+
+void
+ngx_http_script_return_code(ngx_http_script_engine_t *e)
+{
+ ngx_http_script_return_code_t *code;
+
+ code = (ngx_http_script_return_code_t *) e->ip;
+
+ if (code->status < NGX_HTTP_BAD_REQUEST
+ || code->text.value.len
+ || code->text.lengths)
+ {
+ e->status = ngx_http_send_response(e->request, code->status, NULL,
+ &code->text);
+ } else {
+ e->status = code->status;
+ }
+
+ e->ip = ngx_http_script_exit;
+}
+
+
+void
+ngx_http_script_break_code(ngx_http_script_engine_t *e)
+{
+ e->request->uri_changed = 0;
+
+ e->ip = ngx_http_script_exit;
+}
+
+
+void
+ngx_http_script_if_code(ngx_http_script_engine_t *e)
+{
+ ngx_http_script_if_code_t *code;
+
+ code = (ngx_http_script_if_code_t *) e->ip;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+ "http script if");
+
+ e->sp--;
+
+ if (e->sp->len && (e->sp->len !=1 || e->sp->data[0] != '0')) {
+ if (code->loc_conf) {
+ e->request->loc_conf = code->loc_conf;
+ ngx_http_update_location_config(e->request);
+ }
+
+ e->ip += sizeof(ngx_http_script_if_code_t);
+ return;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+ "http script if: false");
+
+ e->ip += code->next;
+}
+
+
+void
+ngx_http_script_equal_code(ngx_http_script_engine_t *e)
+{
+ ngx_http_variable_value_t *val, *res;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+ "http script equal");
+
+ e->sp--;
+ val = e->sp;
+ res = e->sp - 1;
+
+ e->ip += sizeof(uintptr_t);
+
+ if (val->len == res->len
+ && ngx_strncmp(val->data, res->data, res->len) == 0)
+ {
+ *res = ngx_http_variable_true_value;
+ return;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+ "http script equal: no");
+
+ *res = ngx_http_variable_null_value;
+}
+
+
+void
+ngx_http_script_not_equal_code(ngx_http_script_engine_t *e)
+{
+ ngx_http_variable_value_t *val, *res;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+ "http script not equal");
+
+ e->sp--;
+ val = e->sp;
+ res = e->sp - 1;
+
+ e->ip += sizeof(uintptr_t);
+
+ if (val->len == res->len
+ && ngx_strncmp(val->data, res->data, res->len) == 0)
+ {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+ "http script not equal: no");
+
+ *res = ngx_http_variable_null_value;
+ return;
+ }
+
+ *res = ngx_http_variable_true_value;
+}
+
+
+void
+ngx_http_script_file_code(ngx_http_script_engine_t *e)
+{
+ ngx_str_t path;
+ ngx_http_request_t *r;
+ ngx_open_file_info_t of;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_variable_value_t *value;
+ ngx_http_script_file_code_t *code;
+
+ value = e->sp - 1;
+
+ code = (ngx_http_script_file_code_t *) e->ip;
+ e->ip += sizeof(ngx_http_script_file_code_t);
+
+ path.len = value->len - 1;
+ path.data = value->data;
+
+ r = e->request;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http script file op %p \"%V\"", code->op, &path);
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+ of.read_ahead = clcf->read_ahead;
+ of.directio = clcf->directio;
+ of.valid = clcf->open_file_cache_valid;
+ of.min_uses = clcf->open_file_cache_min_uses;
+ of.test_only = 1;
+ of.errors = clcf->open_file_cache_errors;
+ of.events = clcf->open_file_cache_events;
+
+ if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+ != NGX_OK)
+ {
+ if (of.err != NGX_ENOENT
+ && of.err != NGX_ENOTDIR
+ && of.err != NGX_ENAMETOOLONG)
+ {
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err,
+ "%s \"%s\" failed", of.failed, value->data);
+ }
+
+ switch (code->op) {
+
+ case ngx_http_script_file_plain:
+ case ngx_http_script_file_dir:
+ case ngx_http_script_file_exists:
+ case ngx_http_script_file_exec:
+ goto false_value;
+
+ case ngx_http_script_file_not_plain:
+ case ngx_http_script_file_not_dir:
+ case ngx_http_script_file_not_exists:
+ case ngx_http_script_file_not_exec:
+ goto true_value;
+ }
+
+ goto false_value;
+ }
+
+ switch (code->op) {
+ case ngx_http_script_file_plain:
+ if (of.is_file) {
+ goto true_value;
+ }
+ goto false_value;
+
+ case ngx_http_script_file_not_plain:
+ if (of.is_file) {
+ goto false_value;
+ }
+ goto true_value;
+
+ case ngx_http_script_file_dir:
+ if (of.is_dir) {
+ goto true_value;
+ }
+ goto false_value;
+
+ case ngx_http_script_file_not_dir:
+ if (of.is_dir) {
+ goto false_value;
+ }
+ goto true_value;
+
+ case ngx_http_script_file_exists:
+ if (of.is_file || of.is_dir || of.is_link) {
+ goto true_value;
+ }
+ goto false_value;
+
+ case ngx_http_script_file_not_exists:
+ if (of.is_file || of.is_dir || of.is_link) {
+ goto false_value;
+ }
+ goto true_value;
+
+ case ngx_http_script_file_exec:
+ if (of.is_exec) {
+ goto true_value;
+ }
+ goto false_value;
+
+ case ngx_http_script_file_not_exec:
+ if (of.is_exec) {
+ goto false_value;
+ }
+ goto true_value;
+ }
+
+false_value:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http script file op false");
+
+ *value = ngx_http_variable_null_value;
+ return;
+
+true_value:
+
+ *value = ngx_http_variable_true_value;
+ return;
+}
+
+
+void
+ngx_http_script_complex_value_code(ngx_http_script_engine_t *e)
+{
+ size_t len;
+ ngx_http_script_engine_t le;
+ ngx_http_script_len_code_pt lcode;
+ ngx_http_script_complex_value_code_t *code;
+
+ code = (ngx_http_script_complex_value_code_t *) e->ip;
+
+ e->ip += sizeof(ngx_http_script_complex_value_code_t);
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+ "http script complex value");
+
+ ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
+
+ le.ip = code->lengths->elts;
+ le.line = e->line;
+ le.request = e->request;
+ le.quote = e->quote;
+
+ for (len = 0; *(uintptr_t *) le.ip; len += lcode(&le)) {
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ }
+
+ e->buf.len = len;
+ e->buf.data = ngx_pnalloc(e->request->pool, len);
+ if (e->buf.data == NULL) {
+ e->ip = ngx_http_script_exit;
+ e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ return;
+ }
+
+ e->pos = e->buf.data;
+
+ e->sp->len = e->buf.len;
+ e->sp->data = e->buf.data;
+ e->sp++;
+}
+
+
+void
+ngx_http_script_value_code(ngx_http_script_engine_t *e)
+{
+ ngx_http_script_value_code_t *code;
+
+ code = (ngx_http_script_value_code_t *) e->ip;
+
+ e->ip += sizeof(ngx_http_script_value_code_t);
+
+ e->sp->len = code->text_len;
+ e->sp->data = (u_char *) code->text_data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+ "http script value: \"%v\"", e->sp);
+
+ e->sp++;
+}
+
+
+void
+ngx_http_script_set_var_code(ngx_http_script_engine_t *e)
+{
+ ngx_http_request_t *r;
+ ngx_http_script_var_code_t *code;
+
+ code = (ngx_http_script_var_code_t *) e->ip;
+
+ e->ip += sizeof(ngx_http_script_var_code_t);
+
+ r = e->request;
+
+ e->sp--;
+
+ r->variables[code->index].len = e->sp->len;
+ r->variables[code->index].valid = 1;
+ r->variables[code->index].no_cacheable = 0;
+ r->variables[code->index].not_found = 0;
+ r->variables[code->index].data = e->sp->data;
+
+#if (NGX_DEBUG)
+ {
+ ngx_http_variable_t *v;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ v = cmcf->variables.elts;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+ "http script set $%V", &v[code->index].name);
+ }
+#endif
+}
+
+
+void
+ngx_http_script_var_set_handler_code(ngx_http_script_engine_t *e)
+{
+ ngx_http_script_var_handler_code_t *code;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+ "http script set var handler");
+
+ code = (ngx_http_script_var_handler_code_t *) e->ip;
+
+ e->ip += sizeof(ngx_http_script_var_handler_code_t);
+
+ e->sp--;
+
+ code->handler(e->request, e->sp, code->data);
+}
+
+
+void
+ngx_http_script_var_code(ngx_http_script_engine_t *e)
+{
+ ngx_http_variable_value_t *value;
+ ngx_http_script_var_code_t *code;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+ "http script var");
+
+ code = (ngx_http_script_var_code_t *) e->ip;
+
+ e->ip += sizeof(ngx_http_script_var_code_t);
+
+ value = ngx_http_get_flushed_variable(e->request, code->index);
+
+ if (value && !value->not_found) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
+ "http script var: \"%v\"", value);
+
+ *e->sp = *value;
+ e->sp++;
+
+ return;
+ }
+
+ *e->sp = ngx_http_variable_null_value;
+ e->sp++;
+}
+
+
+void
+ngx_http_script_nop_code(ngx_http_script_engine_t *e)
+{
+ e->ip += sizeof(uintptr_t);
+}
diff --git a/usr.sbin/nginx/src/http/ngx_http_script.h b/usr.sbin/nginx/src/http/ngx_http_script.h
new file mode 100644
index 00000000000..c5b1e406694
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_script.h
@@ -0,0 +1,256 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_SCRIPT_H_INCLUDED_
+#define _NGX_HTTP_SCRIPT_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ u_char *ip;
+ u_char *pos;
+ ngx_http_variable_value_t *sp;
+
+ ngx_str_t buf;
+ ngx_str_t line;
+
+ /* the start of the rewritten arguments */
+ u_char *args;
+
+ unsigned flushed:1;
+ unsigned skip:1;
+ unsigned quote:1;
+ unsigned is_args:1;
+ unsigned log:1;
+
+ ngx_int_t status;
+ ngx_http_request_t *request;
+} ngx_http_script_engine_t;
+
+
+typedef struct {
+ ngx_conf_t *cf;
+ ngx_str_t *source;
+
+ ngx_array_t **flushes;
+ ngx_array_t **lengths;
+ ngx_array_t **values;
+
+ ngx_uint_t variables;
+ ngx_uint_t ncaptures;
+ ngx_uint_t captures_mask;
+ ngx_uint_t size;
+
+ void *main;
+
+ unsigned compile_args:1;
+ unsigned complete_lengths:1;
+ unsigned complete_values:1;
+ unsigned zero:1;
+ unsigned conf_prefix:1;
+ unsigned root_prefix:1;
+
+ unsigned dup_capture:1;
+ unsigned args:1;
+} ngx_http_script_compile_t;
+
+
+typedef struct {
+ ngx_str_t value;
+ ngx_uint_t *flushes;
+ void *lengths;
+ void *values;
+} ngx_http_complex_value_t;
+
+
+typedef struct {
+ ngx_conf_t *cf;
+ ngx_str_t *value;
+ ngx_http_complex_value_t *complex_value;
+
+ unsigned zero:1;
+ unsigned conf_prefix:1;
+ unsigned root_prefix:1;
+} ngx_http_compile_complex_value_t;
+
+
+typedef void (*ngx_http_script_code_pt) (ngx_http_script_engine_t *e);
+typedef size_t (*ngx_http_script_len_code_pt) (ngx_http_script_engine_t *e);
+
+
+typedef struct {
+ ngx_http_script_code_pt code;
+ uintptr_t len;
+} ngx_http_script_copy_code_t;
+
+
+typedef struct {
+ ngx_http_script_code_pt code;
+ uintptr_t index;
+} ngx_http_script_var_code_t;
+
+
+typedef struct {
+ ngx_http_script_code_pt code;
+ ngx_http_set_variable_pt handler;
+ uintptr_t data;
+} ngx_http_script_var_handler_code_t;
+
+
+typedef struct {
+ ngx_http_script_code_pt code;
+ uintptr_t n;
+} ngx_http_script_copy_capture_code_t;
+
+
+#if (NGX_PCRE)
+
+typedef struct {
+ ngx_http_script_code_pt code;
+ ngx_http_regex_t *regex;
+ ngx_array_t *lengths;
+ uintptr_t size;
+ uintptr_t status;
+ uintptr_t next;
+
+ uintptr_t test:1;
+ uintptr_t negative_test:1;
+ uintptr_t uri:1;
+ uintptr_t args:1;
+
+ /* add the r->args to the new arguments */
+ uintptr_t add_args:1;
+
+ uintptr_t redirect:1;
+ uintptr_t break_cycle:1;
+
+ ngx_str_t name;
+} ngx_http_script_regex_code_t;
+
+
+typedef struct {
+ ngx_http_script_code_pt code;
+
+ uintptr_t uri:1;
+ uintptr_t args:1;
+
+ /* add the r->args to the new arguments */
+ uintptr_t add_args:1;
+
+ uintptr_t redirect:1;
+} ngx_http_script_regex_end_code_t;
+
+#endif
+
+
+typedef struct {
+ ngx_http_script_code_pt code;
+ uintptr_t conf_prefix;
+} ngx_http_script_full_name_code_t;
+
+
+typedef struct {
+ ngx_http_script_code_pt code;
+ uintptr_t status;
+ ngx_http_complex_value_t text;
+} ngx_http_script_return_code_t;
+
+
+typedef enum {
+ ngx_http_script_file_plain = 0,
+ ngx_http_script_file_not_plain,
+ ngx_http_script_file_dir,
+ ngx_http_script_file_not_dir,
+ ngx_http_script_file_exists,
+ ngx_http_script_file_not_exists,
+ ngx_http_script_file_exec,
+ ngx_http_script_file_not_exec
+} ngx_http_script_file_op_e;
+
+
+typedef struct {
+ ngx_http_script_code_pt code;
+ uintptr_t op;
+} ngx_http_script_file_code_t;
+
+
+typedef struct {
+ ngx_http_script_code_pt code;
+ uintptr_t next;
+ void **loc_conf;
+} ngx_http_script_if_code_t;
+
+
+typedef struct {
+ ngx_http_script_code_pt code;
+ ngx_array_t *lengths;
+} ngx_http_script_complex_value_code_t;
+
+
+typedef struct {
+ ngx_http_script_code_pt code;
+ uintptr_t value;
+ uintptr_t text_len;
+ uintptr_t text_data;
+} ngx_http_script_value_code_t;
+
+
+void ngx_http_script_flush_complex_value(ngx_http_request_t *r,
+ ngx_http_complex_value_t *val);
+ngx_int_t ngx_http_complex_value(ngx_http_request_t *r,
+ ngx_http_complex_value_t *val, ngx_str_t *value);
+ngx_int_t ngx_http_compile_complex_value(ngx_http_compile_complex_value_t *ccv);
+char *ngx_http_set_complex_value_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+ngx_int_t ngx_http_test_predicates(ngx_http_request_t *r,
+ ngx_array_t *predicates);
+char *ngx_http_set_predicate_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+ngx_uint_t ngx_http_script_variables_count(ngx_str_t *value);
+ngx_int_t ngx_http_script_compile(ngx_http_script_compile_t *sc);
+u_char *ngx_http_script_run(ngx_http_request_t *r, ngx_str_t *value,
+ void *code_lengths, size_t reserved, void *code_values);
+void ngx_http_script_flush_no_cacheable_variables(ngx_http_request_t *r,
+ ngx_array_t *indices);
+
+void *ngx_http_script_start_code(ngx_pool_t *pool, ngx_array_t **codes,
+ size_t size);
+void *ngx_http_script_add_code(ngx_array_t *codes, size_t size, void *code);
+
+size_t ngx_http_script_copy_len_code(ngx_http_script_engine_t *e);
+void ngx_http_script_copy_code(ngx_http_script_engine_t *e);
+size_t ngx_http_script_copy_var_len_code(ngx_http_script_engine_t *e);
+void ngx_http_script_copy_var_code(ngx_http_script_engine_t *e);
+size_t ngx_http_script_copy_capture_len_code(ngx_http_script_engine_t *e);
+void ngx_http_script_copy_capture_code(ngx_http_script_engine_t *e);
+size_t ngx_http_script_mark_args_code(ngx_http_script_engine_t *e);
+void ngx_http_script_start_args_code(ngx_http_script_engine_t *e);
+#if (NGX_PCRE)
+void ngx_http_script_regex_start_code(ngx_http_script_engine_t *e);
+void ngx_http_script_regex_end_code(ngx_http_script_engine_t *e);
+#endif
+void ngx_http_script_return_code(ngx_http_script_engine_t *e);
+void ngx_http_script_break_code(ngx_http_script_engine_t *e);
+void ngx_http_script_if_code(ngx_http_script_engine_t *e);
+void ngx_http_script_equal_code(ngx_http_script_engine_t *e);
+void ngx_http_script_not_equal_code(ngx_http_script_engine_t *e);
+void ngx_http_script_file_code(ngx_http_script_engine_t *e);
+void ngx_http_script_complex_value_code(ngx_http_script_engine_t *e);
+void ngx_http_script_value_code(ngx_http_script_engine_t *e);
+void ngx_http_script_set_var_code(ngx_http_script_engine_t *e);
+void ngx_http_script_var_set_handler_code(ngx_http_script_engine_t *e);
+void ngx_http_script_var_code(ngx_http_script_engine_t *e);
+void ngx_http_script_nop_code(ngx_http_script_engine_t *e);
+
+
+#endif /* _NGX_HTTP_SCRIPT_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/http/ngx_http_special_response.c b/usr.sbin/nginx/src/http/ngx_http_special_response.c
new file mode 100644
index 00000000000..0f08d987e53
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_special_response.c
@@ -0,0 +1,779 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+
+static ngx_int_t ngx_http_send_error_page(ngx_http_request_t *r,
+ ngx_http_err_page_t *err_page);
+static ngx_int_t ngx_http_send_special_response(ngx_http_request_t *r,
+ ngx_http_core_loc_conf_t *clcf, ngx_uint_t err);
+static ngx_int_t ngx_http_send_refresh(ngx_http_request_t *r);
+
+
+static u_char ngx_http_error_full_tail[] =
+"<hr><center>" NGINX_VER "</center>" CRLF
+"</body>" CRLF
+"</html>" CRLF
+;
+
+
+static u_char ngx_http_error_tail[] =
+"<hr><center>nginx</center>" CRLF
+"</body>" CRLF
+"</html>" CRLF
+;
+
+
+static u_char ngx_http_msie_padding[] =
+"<!-- a padding to disable MSIE and Chrome friendly error page -->" CRLF
+"<!-- a padding to disable MSIE and Chrome friendly error page -->" CRLF
+"<!-- a padding to disable MSIE and Chrome friendly error page -->" CRLF
+"<!-- a padding to disable MSIE and Chrome friendly error page -->" CRLF
+"<!-- a padding to disable MSIE and Chrome friendly error page -->" CRLF
+"<!-- a padding to disable MSIE and Chrome friendly error page -->" CRLF
+;
+
+
+static u_char ngx_http_msie_refresh_head[] =
+"<html><head><meta http-equiv=\"Refresh\" content=\"0; URL=";
+
+
+static u_char ngx_http_msie_refresh_tail[] =
+"\"></head><body></body></html>" CRLF;
+
+
+static char ngx_http_error_301_page[] =
+"<html>" CRLF
+"<head><title>301 Moved Permanently</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>301 Moved Permanently</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_302_page[] =
+"<html>" CRLF
+"<head><title>302 Found</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>302 Found</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_303_page[] =
+"<html>" CRLF
+"<head><title>303 See Other</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>303 See Other</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_400_page[] =
+"<html>" CRLF
+"<head><title>400 Bad Request</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>400 Bad Request</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_401_page[] =
+"<html>" CRLF
+"<head><title>401 Authorization Required</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>401 Authorization Required</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_402_page[] =
+"<html>" CRLF
+"<head><title>402 Payment Required</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>402 Payment Required</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_403_page[] =
+"<html>" CRLF
+"<head><title>403 Forbidden</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>403 Forbidden</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_404_page[] =
+"<html>" CRLF
+"<head><title>404 Not Found</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>404 Not Found</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_405_page[] =
+"<html>" CRLF
+"<head><title>405 Not Allowed</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>405 Not Allowed</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_406_page[] =
+"<html>" CRLF
+"<head><title>406 Not Acceptable</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>406 Not Acceptable</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_408_page[] =
+"<html>" CRLF
+"<head><title>408 Request Time-out</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>408 Request Time-out</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_409_page[] =
+"<html>" CRLF
+"<head><title>409 Conflict</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>409 Conflict</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_410_page[] =
+"<html>" CRLF
+"<head><title>410 Gone</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>410 Gone</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_411_page[] =
+"<html>" CRLF
+"<head><title>411 Length Required</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>411 Length Required</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_412_page[] =
+"<html>" CRLF
+"<head><title>412 Precondition Failed</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>412 Precondition Failed</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_413_page[] =
+"<html>" CRLF
+"<head><title>413 Request Entity Too Large</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>413 Request Entity Too Large</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_414_page[] =
+"<html>" CRLF
+"<head><title>414 Request-URI Too Large</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>414 Request-URI Too Large</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_415_page[] =
+"<html>" CRLF
+"<head><title>415 Unsupported Media Type</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>415 Unsupported Media Type</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_416_page[] =
+"<html>" CRLF
+"<head><title>416 Requested Range Not Satisfiable</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>416 Requested Range Not Satisfiable</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_494_page[] =
+"<html>" CRLF
+"<head><title>400 Request Header Or Cookie Too Large</title></head>"
+CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>400 Bad Request</h1></center>" CRLF
+"<center>Request Header Or Cookie Too Large</center>" CRLF
+;
+
+
+static char ngx_http_error_495_page[] =
+"<html>" CRLF
+"<head><title>400 The SSL certificate error</title></head>"
+CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>400 Bad Request</h1></center>" CRLF
+"<center>The SSL certificate error</center>" CRLF
+;
+
+
+static char ngx_http_error_496_page[] =
+"<html>" CRLF
+"<head><title>400 No required SSL certificate was sent</title></head>"
+CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>400 Bad Request</h1></center>" CRLF
+"<center>No required SSL certificate was sent</center>" CRLF
+;
+
+
+static char ngx_http_error_497_page[] =
+"<html>" CRLF
+"<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>"
+CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>400 Bad Request</h1></center>" CRLF
+"<center>The plain HTTP request was sent to HTTPS port</center>" CRLF
+;
+
+
+static char ngx_http_error_500_page[] =
+"<html>" CRLF
+"<head><title>500 Internal Server Error</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>500 Internal Server Error</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_501_page[] =
+"<html>" CRLF
+"<head><title>501 Method Not Implemented</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>501 Method Not Implemented</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_502_page[] =
+"<html>" CRLF
+"<head><title>502 Bad Gateway</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>502 Bad Gateway</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_503_page[] =
+"<html>" CRLF
+"<head><title>503 Service Temporarily Unavailable</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>503 Service Temporarily Unavailable</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_504_page[] =
+"<html>" CRLF
+"<head><title>504 Gateway Time-out</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>504 Gateway Time-out</h1></center>" CRLF
+;
+
+
+static char ngx_http_error_507_page[] =
+"<html>" CRLF
+"<head><title>507 Insufficient Storage</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>507 Insufficient Storage</h1></center>" CRLF
+;
+
+
+static ngx_str_t ngx_http_error_pages[] = {
+
+ ngx_null_string, /* 201, 204 */
+
+#define NGX_HTTP_LAST_LEVEL_200 202
+#define NGX_HTTP_LEVEL_200 (NGX_HTTP_LAST_LEVEL_200 - 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),
+
+#define NGX_HTTP_LAST_LEVEL_300 304
+#define NGX_HTTP_LEVEL_300 (NGX_HTTP_LAST_LEVEL_300 - 301)
+
+ 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_LEVEL_400 417
+#define NGX_HTTP_LEVEL_400 (NGX_HTTP_LAST_LEVEL_400 - 400)
+
+ 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_LEVEL_500 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:
+ r->keepalive = 0;
+ }
+ }
+
+ if (r->lingering_close == 1) {
+ 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) {
+ error = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ 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;
+ r->header_only = 1;
+
+ } else if (error == NGX_HTTP_NO_CONTENT) {
+ /* 204 */
+ err = 0;
+
+ } else if (error >= NGX_HTTP_MOVED_PERMANENTLY
+ && error < NGX_HTTP_LAST_LEVEL_300)
+ {
+ /* 3XX */
+ err = error - NGX_HTTP_MOVED_PERMANENTLY + NGX_HTTP_LEVEL_200;
+
+ } else if (error >= NGX_HTTP_BAD_REQUEST
+ && error < NGX_HTTP_LAST_LEVEL_400)
+ {
+ /* 4XX */
+ err = error - NGX_HTTP_BAD_REQUEST + NGX_HTTP_LEVEL_200
+ + NGX_HTTP_LEVEL_300;
+
+ } else if (error >= NGX_HTTP_NGINX_CODES
+ && error < NGX_HTTP_LAST_LEVEL_500)
+ {
+ /* 49X, 5XX */
+ err = error - NGX_HTTP_NGINX_CODES + NGX_HTTP_LEVEL_200
+ + NGX_HTTP_LEVEL_300
+ + NGX_HTTP_LEVEL_400;
+ 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_SEE_OTHER)
+ {
+ r->err_status = overwrite;
+
+ } else {
+ r->err_status = NGX_HTTP_MOVED_TEMPORARILY;
+ }
+
+ location->hash = 1;
+ ngx_str_set(&location->key, "Location");
+ location->value = uri;
+
+ 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_LEVEL_200);
+}
+
+
+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_LEVEL_300)
+ {
+ 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 = -1;
+ }
+
+ 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);
+
+ 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_OK;
+ }
+
+ 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);
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || r->header_only) {
+ return rc;
+ }
+
+ b = ngx_create_temp_buf(r->pool, size);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_cpymem(b->pos, ngx_http_msie_refresh_head,
+ sizeof(ngx_http_msie_refresh_head) - 1);
+
+ if (escape == 0) {
+ p = ngx_cpymem(p, location, len);
+
+ } else {
+ p = (u_char *) ngx_escape_uri(p, location, len, NGX_ESCAPE_REFRESH);
+ }
+
+ b->last = ngx_cpymem(p, ngx_http_msie_refresh_tail,
+ sizeof(ngx_http_msie_refresh_tail) - 1);
+
+ b->last_buf = 1;
+ b->last_in_chain = 1;
+
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_output_filter(r, &out);
+}
diff --git a/usr.sbin/nginx/src/http/ngx_http_upstream.c b/usr.sbin/nginx/src/http/ngx_http_upstream.c
new file mode 100644
index 00000000000..29432dc14a3
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_upstream.c
@@ -0,0 +1,4529 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#if (NGX_HTTP_CACHE)
+static ngx_int_t ngx_http_upstream_cache(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_cache_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_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_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_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_content_length(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_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 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_header_line,
+ offsetof(ngx_http_upstream_headers_in_t, content_length),
+ ngx_http_upstream_copy_content_length, 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_copy_header_line, 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_ignore_header_line, 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 },
+
+#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("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
+
+ 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 (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 = 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->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;
+ }
+
+ 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;
+ }
+
+ u->cacheable = 1;
+
+ 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;
+
+ 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));
+
+ 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_http_request_t *r;
+ ngx_http_upstream_t *u;
+ ngx_http_upstream_resolved_t *ur;
+
+ r = ctx->data;
+
+ 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);
+ return;
+ }
+
+ 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);
+ return;
+ }
+
+ ngx_resolve_name_done(ctx);
+ ur->ctx = NULL;
+
+ ngx_http_upstream_connect(r, u);
+}
+
+
+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_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 closed prematurely "
+ "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 closed "
+ "prematurely 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 closed prematurely 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 closed prematurely 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";
+
+ r->connection->single_connection = 0;
+
+ 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;
+
+ c->pool = r->pool;
+ c->log = r->connection->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 temporay 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;
+ }
+
+ ngx_memzero(&u->headers_in, sizeof(ngx_http_upstream_headers_in_t));
+
+ 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.pos == 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->log->action = "connecting to upstream";
+ (void) ngx_connection_error(c, c->write->kq_errno,
+ "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;
+ }
+
+ r->valid_unparsed_uri = 0;
+
+ 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;
+
+ u->headers_in.content_length_n = r->headers_out.content_length_n;
+
+ if (r->headers_out.content_length_n != -1) {
+ u->length = (size_t) r->headers_out.content_length_n;
+
+ } else {
+ u->length = NGX_MAX_SIZE_T_VALUE;
+ }
+
+ 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 (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;
+ }
+
+ 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) {
+ 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;
+ }
+
+ u->cacheable = 1;
+ }
+
+ 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->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 currupt 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;
+
+ 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_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(&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 > u->length) {
+ size = u->length;
+ }
+
+ 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 == NGX_MAX_SIZE_T_VALUE) {
+ 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_uint_t del;
+ 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) {
+
+ del = p->upstream_error;
+
+ tf = u->pipe->temp_file;
+
+ if (p->upstream_eof || p->upstream_done) {
+
+ 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);
+
+ } else {
+ del = 1;
+ }
+ }
+
+ if (del && tf->file.fd != NGX_INVALID_FILE) {
+
+ if (ngx_delete_file(tf->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 (u->cacheable) {
+
+ if (p->upstream_done) {
+ ngx_http_file_cache_update(r, u->pipe->temp_file);
+
+ } else if (p->upstream_eof) {
+
+ /* TODO: check length & update cache */
+
+ ngx_http_file_cache_update(r, u->pipe->temp_file);
+
+ } 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 (ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_404) {
+ state = NGX_PEER_NEXT;
+ } else {
+ state = NGX_PEER_FAILED;
+ }
+
+ if (ft_type != NGX_HTTP_UPSTREAM_FT_NOLIVE) {
+ u->peer.free(&u->peer, u->peer.data, state);
+ }
+
+ 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;
+
+ } 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
+
+ ngx_close_connection(u->peer.connection);
+ }
+
+#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_http_upstream_t *u;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "cleanup http upstream request: \"%V\"", &r->uri);
+
+ u = r->upstream;
+
+ if (u->resolved && u->resolved->ctx) {
+ ngx_resolve_name_done(u->resolved->ctx);
+ u->resolved->ctx = NULL;
+ }
+
+ ngx_http_upstream_finalize_request(r, u, 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->state->response_length = u->pipe->read_length;
+ }
+ }
+
+ u->finalize_request(r, rc);
+
+ if (u->peer.free) {
+ u->peer.free(&u->peer, u->peer.data, 0);
+ }
+
+ 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);
+
+ 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 (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) {
+ 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_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;
+ 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;
+
+ r->upstream->headers_in.x_accel_limit_rate = h;
+
+ 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;
+
+ if (r->upstream->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') {
+ r->upstream->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') {
+ r->upstream->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)
+{
+ r->headers_out.override_charset = &h->value;
+
+ 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_content_length(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_length = ho;
+ r->headers_out.content_length_n = ngx_atoof(h->value.data, h->value.len);
+
+ 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_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;
+
+ 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 == 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->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]->port) {
+ ngx_log_error(NGX_LOG_WARN, cf->log, 0,
+ "upstream \"%V\" may not have port %d in %s:%ui",
+ &u->host, uscfp[i]->port,
+ uscfp[i]->file_name, uscfp[i]->line);
+ return NULL;
+ }
+
+ if (uscfp[i]->port != u->port) {
+ continue;
+ }
+
+ if (uscfp[i]->default_port && u->default_port
+ && uscfp[i]->default_port != u->default_port)
+ {
+ continue;
+ }
+
+ 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;
+
+ if (u->naddrs == 1) {
+ uscf->servers = ngx_array_create(cf->pool, 1,
+ 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));
+
+ us->addrs = u->addrs;
+ us->naddrs = u->naddrs;
+ }
+
+ 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_addr_t **paddr;
+
+ paddr = (ngx_addr_t **) (p + cmd->offset);
+
+ *paddr = ngx_palloc(cf->pool, sizeof(ngx_addr_t));
+ if (*paddr == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ rc = ngx_parse_addr(cf->pool, *paddr, value[1].data, value[1].len);
+
+ switch (rc) {
+ case NGX_OK:
+ (*paddr)->name = value[1];
+ return NGX_CONF_OK;
+
+ case NGX_DECLINED:
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid address \"%V\"", &value[1]);
+ default:
+ return NGX_CONF_ERROR;
+ }
+}
+
+
+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_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;
+ }
+
+ conf->hide_headers = prev->hide_headers;
+ conf->pass_headers = prev->pass_headers;
+
+ } else {
+ if (conf->hide_headers == NGX_CONF_UNSET_PTR) {
+ conf->hide_headers = prev->hide_headers;
+ }
+
+ if (conf->pass_headers == NGX_CONF_UNSET_PTR) {
+ conf->pass_headers = prev->pass_headers;
+ }
+ }
+
+ if (ngx_array_init(&hide_headers, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ for (h = default_hide_headers; h->len; h++) {
+ hk = ngx_array_push(&hide_headers);
+ if (hk == NULL) {
+ return NGX_ERROR;
+ }
+
+ hk->key = *h;
+ hk->key_hash = ngx_hash_key_lc(h->data, h->len);
+ hk->value = (void *) 1;
+ }
+
+ if (conf->hide_headers != NGX_CONF_UNSET_PTR) {
+
+ h = conf->hide_headers->elts;
+
+ for (i = 0; i < conf->hide_headers->nelts; i++) {
+
+ hk = hide_headers.elts;
+
+ for (j = 0; j < hide_headers.nelts; j++) {
+ if (ngx_strcasecmp(h[i].data, hk[j].key.data) == 0) {
+ goto exist;
+ }
+ }
+
+ hk = ngx_array_push(&hide_headers);
+ if (hk == NULL) {
+ return NGX_ERROR;
+ }
+
+ hk->key = h[i];
+ hk->key_hash = ngx_hash_key_lc(h[i].data, h[i].len);
+ hk->value = (void *) 1;
+
+ exist:
+
+ continue;
+ }
+ }
+
+ if (conf->pass_headers != NGX_CONF_UNSET_PTR) {
+
+ h = conf->pass_headers->elts;
+ hk = hide_headers.elts;
+
+ for (i = 0; i < conf->pass_headers->nelts; i++) {
+ for (j = 0; j < hide_headers.nelts; j++) {
+
+ if (hk[j].key.data == NULL) {
+ continue;
+ }
+
+ if (ngx_strcasecmp(h[i].data, hk[j].key.data) == 0) {
+ hk[j].key.data = NULL;
+ break;
+ }
+ }
+ }
+ }
+
+ hash->hash = &conf->hide_headers_hash;
+ hash->key = ngx_hash_key_lc;
+ hash->pool = cf->pool;
+ hash->temp_pool = NULL;
+
+ return ngx_hash_init(hash, hide_headers.elts, hide_headers.nelts);
+}
+
+
+static void *
+ngx_http_upstream_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_upstream_main_conf_t *umcf;
+
+ umcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_main_conf_t));
+ if (umcf == NULL) {
+ return NULL;
+ }
+
+ if (ngx_array_init(&umcf->upstreams, cf->pool, 4,
+ sizeof(ngx_http_upstream_srv_conf_t *))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ return umcf;
+}
+
+
+static char *
+ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+ ngx_http_upstream_main_conf_t *umcf = conf;
+
+ ngx_uint_t i;
+ ngx_array_t headers_in;
+ ngx_hash_key_t *hk;
+ ngx_hash_init_t hash;
+ ngx_http_upstream_init_pt init;
+ ngx_http_upstream_header_t *header;
+ ngx_http_upstream_srv_conf_t **uscfp;
+
+ uscfp = umcf->upstreams.elts;
+
+ for (i = 0; i < umcf->upstreams.nelts; i++) {
+
+ init = uscfp[i]->peer.init_upstream ? uscfp[i]->peer.init_upstream:
+ ngx_http_upstream_init_round_robin;
+
+ if (init(cf, uscfp[i]) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+
+ /* upstream_headers_in_hash */
+
+ if (ngx_array_init(&headers_in, cf->temp_pool, 32, sizeof(ngx_hash_key_t))
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ for (header = ngx_http_upstream_headers_in; header->name.len; header++) {
+ hk = ngx_array_push(&headers_in);
+ if (hk == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ hk->key = header->name;
+ hk->key_hash = ngx_hash_key_lc(header->name.data, header->name.len);
+ hk->value = header;
+ }
+
+ hash.hash = &umcf->headers_in_hash;
+ hash.key = ngx_hash_key_lc;
+ hash.max_size = 512;
+ hash.bucket_size = ngx_align(64, ngx_cacheline_size);
+ hash.name = "upstream_headers_in_hash";
+ hash.pool = cf->pool;
+ hash.temp_pool = NULL;
+
+ if (ngx_hash_init(&hash, headers_in.elts, headers_in.nelts) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/http/ngx_http_upstream.h b/usr.sbin/nginx/src/http/ngx_http_upstream.h
new file mode 100644
index 00000000000..fa848c0d3c5
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_upstream.h
@@ -0,0 +1,346 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_UPSTREAM_H_INCLUDED_
+#define _NGX_HTTP_UPSTREAM_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+#include <ngx_event_pipe.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_UPSTREAM_FT_ERROR 0x00000002
+#define NGX_HTTP_UPSTREAM_FT_TIMEOUT 0x00000004
+#define NGX_HTTP_UPSTREAM_FT_INVALID_HEADER 0x00000008
+#define NGX_HTTP_UPSTREAM_FT_HTTP_500 0x00000010
+#define NGX_HTTP_UPSTREAM_FT_HTTP_502 0x00000020
+#define NGX_HTTP_UPSTREAM_FT_HTTP_503 0x00000040
+#define NGX_HTTP_UPSTREAM_FT_HTTP_504 0x00000080
+#define NGX_HTTP_UPSTREAM_FT_HTTP_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
+
+
+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;
+};
+
+
+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_addr_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_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;
+
+#if (NGX_HTTP_GZIP)
+ ngx_table_elt_t *content_encoding;
+#endif
+
+ off_t content_length_n;
+
+ ngx_array_t cache_control;
+} 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 buffer;
+ size_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_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 request_sent:1;
+ unsigned header_sent:1;
+};
+
+
+typedef struct {
+ ngx_uint_t status;
+ ngx_uint_t mask;
+} ngx_http_upstream_next_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);
+ngx_int_t ngx_http_upstream_hide_headers_hash(ngx_conf_t *cf,
+ ngx_http_upstream_conf_t *conf, ngx_http_upstream_conf_t *prev,
+ ngx_str_t *default_hide_headers, ngx_hash_init_t *hash);
+
+
+#define ngx_http_conf_upstream_srv_conf(uscf, module) \
+ uscf->srv_conf[module.ctx_index]
+
+
+extern ngx_module_t ngx_http_upstream_module;
+extern ngx_conf_bitmask_t ngx_http_upstream_cache_method_mask[];
+extern ngx_conf_bitmask_t ngx_http_upstream_ignore_headers_masks[];
+
+
+#endif /* _NGX_HTTP_UPSTREAM_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/http/ngx_http_upstream_round_robin.c b/usr.sbin/nginx/src/http/ngx_http_upstream_round_robin.c
new file mode 100644
index 00000000000..de34b2884c5
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_upstream_round_robin.c
@@ -0,0 +1,781 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_upstream_cmp_servers(const void *one,
+ const void *two);
+static ngx_uint_t
+ngx_http_upstream_get_peer(ngx_http_upstream_rr_peers_t *peers);
+
+#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;
+ 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;
+
+ for (i = 0; i < us->servers->nelts; i++) {
+ if (server[i].backup) {
+ continue;
+ }
+
+ n += server[i].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->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].down ? 0 : server[i].weight;
+ peers->peer[n].current_weight = peers->peer[n].weight;
+ n++;
+ }
+ }
+
+ us->peer.data = peers;
+
+ ngx_sort(&peers->peer[0], (size_t) n,
+ sizeof(ngx_http_upstream_rr_peer_t),
+ ngx_http_upstream_cmp_servers);
+
+ /* backup servers */
+
+ n = 0;
+
+ for (i = 0; i < us->servers->nelts; i++) {
+ if (!server[i].backup) {
+ continue;
+ }
+
+ n += server[i].naddrs;
+ }
+
+ 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->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].current_weight = server[i].weight;
+ 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;
+
+ ngx_sort(&backup->peer[0], (size_t) n,
+ sizeof(ngx_http_upstream_rr_peer_t),
+ ngx_http_upstream_cmp_servers);
+
+ return NGX_OK;
+ }
+
+
+ /* an upstream implicitly defined by proxy_pass, etc. */
+
+ if (us->port == 0 && us->default_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 = (in_port_t) (us->port ? us->port : us->default_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->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].current_weight = 1;
+ 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;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_cmp_servers(const void *one, const void *two)
+{
+ ngx_http_upstream_rr_peer_t *first, *second;
+
+ first = (ngx_http_upstream_rr_peer_t *) one;
+ second = (ngx_http_upstream_rr_peer_t *) two;
+
+ return (first->weight < second->weight);
+}
+
+
+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;
+
+ 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_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].current_weight = 1;
+ 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].current_weight = 1;
+ 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;
+
+ time_t now;
+ uintptr_t m;
+ ngx_int_t rc;
+ ngx_uint_t i, n;
+ ngx_connection_t *c;
+ 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);
+
+ now = ngx_time();
+
+ /* ngx_lock_mutex(rrp->peers->mutex); */
+
+ if (rrp->peers->last_cached) {
+
+ /* cached connection */
+
+ c = rrp->peers->cached[rrp->peers->last_cached];
+ rrp->peers->last_cached--;
+
+ /* ngx_unlock_mutex(ppr->peers->mutex); */
+
+#if (NGX_THREADS)
+ c->read->lock = c->read->own_lock;
+ c->write->lock = c->write->own_lock;
+#endif
+
+ pc->connection = c;
+ pc->cached = 1;
+
+ return NGX_OK;
+ }
+
+ pc->cached = 0;
+ pc->connection = NULL;
+
+ if (rrp->peers->single) {
+ peer = &rrp->peers->peer[0];
+
+ } else {
+
+ /* there are several peers */
+
+ if (pc->tries == rrp->peers->number) {
+
+ /* it's a first try - get a current peer */
+
+ i = pc->tries;
+
+ for ( ;; ) {
+ rrp->current = ngx_http_upstream_get_peer(rrp->peers);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "get rr peer, current: %ui %i",
+ rrp->current,
+ rrp->peers->peer[rrp->current].current_weight);
+
+ n = rrp->current / (8 * sizeof(uintptr_t));
+ m = (uintptr_t) 1 << rrp->current % (8 * sizeof(uintptr_t));
+
+ if (!(rrp->tried[n] & m)) {
+ peer = &rrp->peers->peer[rrp->current];
+
+ if (!peer->down) {
+
+ if (peer->max_fails == 0
+ || peer->fails < peer->max_fails)
+ {
+ break;
+ }
+
+ if (now - peer->accessed > peer->fail_timeout) {
+ peer->fails = 0;
+ break;
+ }
+
+ peer->current_weight = 0;
+
+ } else {
+ rrp->tried[n] |= m;
+ }
+
+ pc->tries--;
+ }
+
+ if (pc->tries == 0) {
+ goto failed;
+ }
+
+ if (--i == 0) {
+ ngx_log_error(NGX_LOG_ALERT, pc->log, 0,
+ "round robin upstream stuck on %ui tries",
+ pc->tries);
+ goto failed;
+ }
+ }
+
+ peer->current_weight--;
+
+ } else {
+
+ i = pc->tries;
+
+ for ( ;; ) {
+ n = rrp->current / (8 * sizeof(uintptr_t));
+ m = (uintptr_t) 1 << rrp->current % (8 * sizeof(uintptr_t));
+
+ if (!(rrp->tried[n] & m)) {
+
+ peer = &rrp->peers->peer[rrp->current];
+
+ if (!peer->down) {
+
+ if (peer->max_fails == 0
+ || peer->fails < peer->max_fails)
+ {
+ break;
+ }
+
+ if (now - peer->accessed > peer->fail_timeout) {
+ peer->fails = 0;
+ break;
+ }
+
+ peer->current_weight = 0;
+
+ } else {
+ rrp->tried[n] |= m;
+ }
+
+ pc->tries--;
+ }
+
+ rrp->current++;
+
+ if (rrp->current >= rrp->peers->number) {
+ rrp->current = 0;
+ }
+
+ if (pc->tries == 0) {
+ goto failed;
+ }
+
+ if (--i == 0) {
+ ngx_log_error(NGX_LOG_ALERT, pc->log, 0,
+ "round robin upstream stuck on %ui tries",
+ pc->tries);
+ goto failed;
+ }
+ }
+
+ peer->current_weight--;
+ }
+
+ rrp->tried[n] |= m;
+ }
+
+ 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;
+
+ n = rrp->peers->next->number / (8 * sizeof(uintptr_t)) + 1;
+ for (i = 0; i < n; i++) {
+ rrp->tried[i] = 0;
+ }
+ }
+
+ 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;
+ 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_uint_t
+ngx_http_upstream_get_peer(ngx_http_upstream_rr_peers_t *peers)
+{
+ ngx_uint_t i, n;
+ ngx_http_upstream_rr_peer_t *peer;
+
+ peer = &peers->peer[0];
+
+ for ( ;; ) {
+
+ for (i = 0; i < peers->number; i++) {
+
+ if (peer[i].current_weight <= 0) {
+ continue;
+ }
+
+ n = i;
+
+ while (i < peers->number - 1) {
+
+ i++;
+
+ if (peer[i].current_weight <= 0) {
+ continue;
+ }
+
+ if (peer[n].current_weight * 1000 / peer[i].current_weight
+ > peer[n].weight * 1000 / peer[i].weight)
+ {
+ return n;
+ }
+
+ n = i;
+ }
+
+ if (peer[i].current_weight > 0) {
+ n = i;
+ }
+
+ return n;
+ }
+
+ for (i = 0; i < peers->number; i++) {
+ peer[i].current_weight = peer[i].weight;
+ }
+ }
+}
+
+
+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);
+
+ if (state == 0 && pc->tries == 0) {
+ return;
+ }
+
+ /* TODO: NGX_PEER_KEEPALIVE */
+
+ if (rrp->peers->single) {
+ pc->tries = 0;
+ return;
+ }
+
+ if (state & NGX_PEER_FAILED) {
+ now = ngx_time();
+
+ peer = &rrp->peers->peer[rrp->current];
+
+ /* ngx_lock_mutex(rrp->peers->mutex); */
+
+ peer->fails++;
+ peer->accessed = now;
+
+ if (peer->max_fails) {
+ peer->current_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->current_weight);
+
+ if (peer->current_weight < 0) {
+ peer->current_weight = 0;
+ }
+
+ /* ngx_unlock_mutex(rrp->peers->mutex); */
+ }
+
+ rrp->current++;
+
+ if (rrp->current >= rrp->peers->number) {
+ rrp->current = 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
diff --git a/usr.sbin/nginx/src/http/ngx_http_upstream_round_robin.h b/usr.sbin/nginx/src/http/ngx_http_upstream_round_robin.h
new file mode 100644
index 00000000000..a9cb257c74f
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_upstream_round_robin.h
@@ -0,0 +1,84 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_UPSTREAM_ROUND_ROBIN_H_INCLUDED_
+#define _NGX_HTTP_UPSTREAM_ROUND_ROBIN_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ struct sockaddr *sockaddr;
+ socklen_t socklen;
+ ngx_str_t name;
+
+ ngx_int_t current_weight;
+ ngx_int_t weight;
+
+ ngx_uint_t fails;
+ time_t accessed;
+
+ 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 single; /* unsigned single:1; */
+ ngx_uint_t number;
+ ngx_uint_t last_cached;
+
+ /* ngx_mutex_t *mutex; */
+ ngx_connection_t **cached;
+
+ 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_ */
diff --git a/usr.sbin/nginx/src/http/ngx_http_variables.c b/usr.sbin/nginx/src/http/ngx_http_variables.c
new file mode 100644
index 00000000000..4afd884059f
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_variables.c
@@ -0,0 +1,2035 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+
+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_headers(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+
+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);
+
+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_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_body_bytes_sent(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_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_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);
+
+/*
+ * 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, $http_via,
+ * and $http_x_forwarded_for variables may be handled by generic
+ * ngx_http_variable_unknown_header_in(), but for perfomance 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_PROXY || NGX_HTTP_REALIP)
+ { ngx_string("http_x_forwarded_for"), NULL, ngx_http_variable_header,
+ offsetof(ngx_http_request_t, headers_in.x_forwarded_for), 0, 0 },
+#endif
+
+ { ngx_string("http_cookie"), NULL, ngx_http_variable_headers,
+ offsetof(ngx_http_request_t, headers_in.cookies), 0, 0 },
+
+ { ngx_string("content_length"), NULL, ngx_http_variable_header,
+ offsetof(ngx_http_request_t, headers_in.content_length), 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("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("body_bytes_sent"), NULL, ngx_http_variable_body_bytes_sent,
+ 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("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("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_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;
+
+ 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;
+
+ 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 cmcf->variables.nelts - 1;
+}
+
+
+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) {
+ 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_headers(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ssize_t len;
+ u_char *p;
+ ngx_uint_t i, n;
+ ngx_array_t *a;
+ ngx_table_elt_t **h;
+
+ a = (ngx_array_t *) ((char *) r + data);
+
+ n = a->nelts;
+
+ if (n == 0) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ h = a->elts;
+
+ if (n == 1) {
+ v->len = (*h)->value.len;
+ v->data = (*h)->value.data;
+
+ return NGX_OK;
+ }
+
+ len = - (ssize_t) (sizeof("; ") - 1);
+
+ for (i = 0; i < n; i++) {
+ len += h[i]->value.len + sizeof("; ") - 1;
+ }
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = len;
+ v->data = p;
+
+ for (i = 0; /* void */ ; i++) {
+ p = ngx_copy(p, h[i]->value.data, h[i]->value.len);
+
+ if (i == n - 1) {
+ break;
+ }
+
+ *p++ = ';'; *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;
+ }
+
+ 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;
+}
+
+
+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_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)
+{
+ size_t len;
+ ngx_str_t path;
+ ngx_http_core_loc_conf_t *clcf;
+ u_char real[NGX_MAX_PATH];
+
+ 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_realpath(path.data, 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) {
+ return NGX_ERROR;
+ }
+
+ v->len = len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ ngx_memcpy(v->data, real, len);
+
+ 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_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_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->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, *next;
+ 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;
+ }
+
+ next = cl->next->buf;
+ len = (buf->last - buf->pos) + (next->last - next->pos);
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->data = p;
+
+ p = ngx_cpymem(p, buf->pos, buf->last - buf->pos);
+ ngx_memcpy(p, next->pos, next->last - next->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_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;
+}
+
+
+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 *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 (v = ngx_http_core_variables; v->name.len; v++) {
+ 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;
+}
diff --git a/usr.sbin/nginx/src/http/ngx_http_variables.h b/usr.sbin/nginx/src/http/ngx_http_variables.h
new file mode 100644
index 00000000000..b0114e3006d
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_variables.h
@@ -0,0 +1,114 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_VARIABLES_H_INCLUDED_
+#define _NGX_HTTP_VARIABLES_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef ngx_variable_value_t ngx_http_variable_value_t;
+
+#define ngx_http_variable(v) { sizeof(v) - 1, 1, 0, 0, 0, (u_char *) v }
+
+typedef struct ngx_http_variable_s ngx_http_variable_t;
+
+typedef void (*ngx_http_set_variable_pt) (ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+typedef ngx_int_t (*ngx_http_get_variable_pt) (ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+
+
+#define NGX_HTTP_VAR_CHANGEABLE 1
+#define NGX_HTTP_VAR_NOCACHEABLE 2
+#define NGX_HTTP_VAR_INDEXED 4
+#define NGX_HTTP_VAR_NOHASH 8
+
+
+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);
+
+
+#define ngx_http_clear_variable(r, index) r->variables0[index].text.data = NULL;
+
+
+#if (NGX_PCRE)
+
+typedef struct {
+ ngx_uint_t capture;
+ ngx_int_t index;
+} ngx_http_regex_variable_t;
+
+
+typedef struct {
+ ngx_regex_t *regex;
+ ngx_uint_t ncaptures;
+ ngx_http_regex_variable_t *variables;
+ ngx_uint_t nvariables;
+ ngx_str_t name;
+} ngx_http_regex_t;
+
+
+typedef struct {
+ ngx_http_regex_t *regex;
+ void *value;
+} ngx_http_map_regex_t;
+
+
+ngx_http_regex_t *ngx_http_regex_compile(ngx_conf_t *cf,
+ ngx_regex_compile_t *rc);
+ngx_int_t ngx_http_regex_exec(ngx_http_request_t *r, ngx_http_regex_t *re,
+ ngx_str_t *s);
+
+#endif
+
+
+typedef struct {
+ ngx_hash_combined_t hash;
+#if (NGX_PCRE)
+ ngx_http_map_regex_t *regex;
+ ngx_uint_t nregex;
+#endif
+} ngx_http_map_t;
+
+
+void *ngx_http_map_find(ngx_http_request_t *r, ngx_http_map_t *map,
+ ngx_str_t *match);
+
+
+ngx_int_t ngx_http_variables_add_core_vars(ngx_conf_t *cf);
+ngx_int_t ngx_http_variables_init_vars(ngx_conf_t *cf);
+
+
+extern ngx_http_variable_value_t ngx_http_variable_null_value;
+extern ngx_http_variable_value_t ngx_http_variable_true_value;
+
+
+#endif /* _NGX_HTTP_VARIABLES_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/http/ngx_http_write_filter_module.c b/usr.sbin/nginx/src/http/ngx_http_write_filter_module.c
new file mode 100644
index 00000000000..46351349cf5
--- /dev/null
+++ b/usr.sbin/nginx/src/http/ngx_http_write_filter_module.c
@@ -0,0 +1,310 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_write_filter_init(ngx_conf_t *cf);
+
+
+static ngx_http_module_t ngx_http_write_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_write_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL, /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_write_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_write_filter_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+ngx_int_t
+ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ off_t size, sent, nsent, limit;
+ ngx_uint_t last, flush;
+ 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) {
+ r->out = NULL;
+ c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
+
+ return NGX_OK;
+ }
+
+ if (flush) {
+ do {
+ r->out = r->out->next;
+ } while (r->out);
+
+ 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 = 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;
+ }
+
+ } else if (clcf->sendfile_max_chunk) {
+ limit = clcf->sendfile_max_chunk;
+
+ } else {
+ limit = 0;
+ }
+
+ 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 + 1);
+
+ if (delay > 0) {
+ c->write->delayed = 1;
+ ngx_add_timer(c->write, delay);
+ }
+
+ } else if (c->write->ready
+ && clcf->sendfile_max_chunk
+ && (size_t) (c->sent - sent)
+ >= clcf->sendfile_max_chunk - 2 * ngx_pagesize)
+ {
+ c->write->delayed = 1;
+ ngx_add_timer(c->write, 1);
+ }
+
+ for (cl = r->out; cl && cl != chain; /* void */) {
+ ln = cl;
+ cl = cl->next;
+ ngx_free_chain(r->pool, ln);
+ }
+
+ r->out = chain;
+
+ if (chain) {
+ c->buffered |= NGX_HTTP_WRITE_BUFFERED;
+ return NGX_AGAIN;
+ }
+
+ c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
+
+ if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) {
+ return NGX_AGAIN;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_write_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_top_body_filter = ngx_http_write_filter;
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/mail/ngx_mail.c b/usr.sbin/nginx/src/mail/ngx_mail.c
new file mode 100644
index 00000000000..3eeb97fe0d2
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail.c
@@ -0,0 +1,541 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+
+
+static char *ngx_mail_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static ngx_int_t ngx_mail_add_ports(ngx_conf_t *cf, ngx_array_t *ports,
+ ngx_mail_listen_t *listen);
+static char *ngx_mail_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports);
+static ngx_int_t ngx_mail_add_addrs(ngx_conf_t *cf, ngx_mail_port_t *mport,
+ ngx_mail_conf_addr_t *addr);
+#if (NGX_HAVE_INET6)
+static ngx_int_t ngx_mail_add_addrs6(ngx_conf_t *cf, ngx_mail_port_t *mport,
+ ngx_mail_conf_addr_t *addr);
+#endif
+static ngx_int_t ngx_mail_cmp_conf_addrs(const void *one, const void *two);
+
+
+ngx_uint_t ngx_mail_max_module;
+
+
+static ngx_command_t ngx_mail_commands[] = {
+
+ { ngx_string("mail"),
+ NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+ ngx_mail_block,
+ 0,
+ 0,
+ NULL },
+
+ { ngx_string("imap"),
+ NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+ ngx_mail_block,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_core_module_t ngx_mail_module_ctx = {
+ ngx_string("mail"),
+ NULL,
+ NULL
+};
+
+
+ngx_module_t ngx_mail_module = {
+ NGX_MODULE_V1,
+ &ngx_mail_module_ctx, /* module context */
+ ngx_mail_commands, /* module directives */
+ NGX_CORE_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static char *
+ngx_mail_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *rv;
+ ngx_uint_t i, m, mi, s;
+ ngx_conf_t pcf;
+ ngx_array_t ports;
+ ngx_mail_listen_t *listen;
+ ngx_mail_module_t *module;
+ ngx_mail_conf_ctx_t *ctx;
+ ngx_mail_core_srv_conf_t **cscfp;
+ ngx_mail_core_main_conf_t *cmcf;
+
+ if (cmd->name.data[0] == 'i') {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "the \"imap\" directive is deprecated, "
+ "use the \"mail\" directive instead");
+ }
+
+ /* the main mail context */
+
+ ctx = ngx_pcalloc(cf->pool, sizeof(ngx_mail_conf_ctx_t));
+ if (ctx == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *(ngx_mail_conf_ctx_t **) conf = ctx;
+
+ /* count the number of the http modules and set up their indices */
+
+ ngx_mail_max_module = 0;
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_MAIL_MODULE) {
+ continue;
+ }
+
+ ngx_modules[m]->ctx_index = ngx_mail_max_module++;
+ }
+
+
+ /* the mail main_conf context, it is the same in the all mail contexts */
+
+ ctx->main_conf = ngx_pcalloc(cf->pool,
+ sizeof(void *) * ngx_mail_max_module);
+ if (ctx->main_conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+
+ /*
+ * the mail null srv_conf context, it is used to merge
+ * the server{}s' srv_conf's
+ */
+
+ ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_mail_max_module);
+ if (ctx->srv_conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+
+ /*
+ * create the main_conf's, the null srv_conf's, and the null loc_conf's
+ * of the all mail modules
+ */
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_MAIL_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+ mi = ngx_modules[m]->ctx_index;
+
+ if (module->create_main_conf) {
+ ctx->main_conf[mi] = module->create_main_conf(cf);
+ if (ctx->main_conf[mi] == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (module->create_srv_conf) {
+ ctx->srv_conf[mi] = module->create_srv_conf(cf);
+ if (ctx->srv_conf[mi] == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+ }
+
+
+ /* parse inside the mail{} block */
+
+ pcf = *cf;
+ cf->ctx = ctx;
+
+ cf->module_type = NGX_MAIL_MODULE;
+ cf->cmd_type = NGX_MAIL_MAIN_CONF;
+ rv = ngx_conf_parse(cf, NULL);
+
+ if (rv != NGX_CONF_OK) {
+ *cf = pcf;
+ return rv;
+ }
+
+
+ /* init mail{} main_conf's, merge the server{}s' srv_conf's */
+
+ cmcf = ctx->main_conf[ngx_mail_core_module.ctx_index];
+ cscfp = cmcf->servers.elts;
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_MAIL_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+ mi = ngx_modules[m]->ctx_index;
+
+ /* init mail{} main_conf's */
+
+ cf->ctx = ctx;
+
+ if (module->init_main_conf) {
+ rv = module->init_main_conf(cf, ctx->main_conf[mi]);
+ if (rv != NGX_CONF_OK) {
+ *cf = pcf;
+ return rv;
+ }
+ }
+
+ for (s = 0; s < cmcf->servers.nelts; s++) {
+
+ /* merge the server{}s' srv_conf's */
+
+ cf->ctx = cscfp[s]->ctx;
+
+ if (module->merge_srv_conf) {
+ rv = module->merge_srv_conf(cf,
+ ctx->srv_conf[mi],
+ cscfp[s]->ctx->srv_conf[mi]);
+ if (rv != NGX_CONF_OK) {
+ *cf = pcf;
+ return rv;
+ }
+ }
+ }
+ }
+
+ *cf = pcf;
+
+
+ if (ngx_array_init(&ports, cf->temp_pool, 4, sizeof(ngx_mail_conf_port_t))
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ listen = cmcf->listen.elts;
+
+ for (i = 0; i < cmcf->listen.nelts; i++) {
+ if (ngx_mail_add_ports(cf, &ports, &listen[i]) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ return ngx_mail_optimize_servers(cf, &ports);
+}
+
+
+static ngx_int_t
+ngx_mail_add_ports(ngx_conf_t *cf, ngx_array_t *ports,
+ ngx_mail_listen_t *listen)
+{
+ in_port_t p;
+ ngx_uint_t i;
+ struct sockaddr *sa;
+ struct sockaddr_in *sin;
+ ngx_mail_conf_port_t *port;
+ ngx_mail_conf_addr_t *addr;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+#endif
+
+ sa = (struct sockaddr *) &listen->sockaddr;
+
+ switch (sa->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) sa;
+ p = sin6->sin6_port;
+ break;
+#endif
+
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) sa;
+ p = sin->sin_port;
+ break;
+ }
+
+ port = ports->elts;
+ for (i = 0; i < ports->nelts; i++) {
+ if (p == port[i].port && sa->sa_family == port[i].family) {
+
+ /* a port is already in the port list */
+
+ port = &port[i];
+ goto found;
+ }
+ }
+
+ /* add a port to the port list */
+
+ port = ngx_array_push(ports);
+ if (port == NULL) {
+ return NGX_ERROR;
+ }
+
+ port->family = sa->sa_family;
+ port->port = p;
+
+ if (ngx_array_init(&port->addrs, cf->temp_pool, 2,
+ sizeof(ngx_mail_conf_addr_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+found:
+
+ addr = ngx_array_push(&port->addrs);
+ if (addr == NULL) {
+ return NGX_ERROR;
+ }
+
+ addr->sockaddr = (struct sockaddr *) &listen->sockaddr;
+ addr->socklen = listen->socklen;
+ addr->ctx = listen->ctx;
+ addr->bind = listen->bind;
+ addr->wildcard = listen->wildcard;
+#if (NGX_MAIL_SSL)
+ addr->ssl = listen->ssl;
+#endif
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+ addr->ipv6only = listen->ipv6only;
+#endif
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_mail_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports)
+{
+ ngx_uint_t i, p, last, bind_wildcard;
+ ngx_listening_t *ls;
+ ngx_mail_port_t *mport;
+ ngx_mail_conf_port_t *port;
+ ngx_mail_conf_addr_t *addr;
+
+ port = ports->elts;
+ for (p = 0; p < ports->nelts; p++) {
+
+ ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
+ sizeof(ngx_mail_conf_addr_t), ngx_mail_cmp_conf_addrs);
+
+ addr = port[p].addrs.elts;
+ last = port[p].addrs.nelts;
+
+ /*
+ * if there is the binding to the "*:port" then we need to bind()
+ * to the "*:port" only and ignore the other bindings
+ */
+
+ if (addr[last - 1].wildcard) {
+ addr[last - 1].bind = 1;
+ bind_wildcard = 1;
+
+ } else {
+ bind_wildcard = 0;
+ }
+
+ i = 0;
+
+ while (i < last) {
+
+ if (bind_wildcard && !addr[i].bind) {
+ i++;
+ continue;
+ }
+
+ ls = ngx_create_listening(cf, addr[i].sockaddr, addr[i].socklen);
+ if (ls == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ls->addr_ntop = 1;
+ ls->handler = ngx_mail_init_connection;
+ ls->pool_size = 256;
+
+ /* TODO: error_log directive */
+ ls->logp = &cf->cycle->new_log;
+ ls->log.data = &ls->addr_text;
+ ls->log.handler = ngx_accept_log_error;
+
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+ ls->ipv6only = addr[i].ipv6only;
+#endif
+
+ mport = ngx_palloc(cf->pool, sizeof(ngx_mail_port_t));
+ if (mport == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ls->servers = mport;
+
+ if (i == last - 1) {
+ mport->naddrs = last;
+
+ } else {
+ mport->naddrs = 1;
+ i = 0;
+ }
+
+ switch (ls->sockaddr->sa_family) {
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ if (ngx_mail_add_addrs6(cf, mport, addr) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ break;
+#endif
+ default: /* AF_INET */
+ if (ngx_mail_add_addrs(cf, mport, addr) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ break;
+ }
+
+ addr++;
+ last--;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_add_addrs(ngx_conf_t *cf, ngx_mail_port_t *mport,
+ ngx_mail_conf_addr_t *addr)
+{
+ u_char *p;
+ size_t len;
+ ngx_uint_t i;
+ ngx_mail_in_addr_t *addrs;
+ struct sockaddr_in *sin;
+ u_char buf[NGX_SOCKADDR_STRLEN];
+
+ mport->addrs = ngx_pcalloc(cf->pool,
+ mport->naddrs * sizeof(ngx_mail_in_addr_t));
+ if (mport->addrs == NULL) {
+ return NGX_ERROR;
+ }
+
+ addrs = mport->addrs;
+
+ for (i = 0; i < mport->naddrs; i++) {
+
+ sin = (struct sockaddr_in *) addr[i].sockaddr;
+ addrs[i].addr = sin->sin_addr.s_addr;
+
+ addrs[i].conf.ctx = addr[i].ctx;
+#if (NGX_MAIL_SSL)
+ addrs[i].conf.ssl = addr[i].ssl;
+#endif
+
+ len = ngx_sock_ntop(addr[i].sockaddr, buf, NGX_SOCKADDR_STRLEN, 1);
+
+ p = ngx_pnalloc(cf->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(p, buf, len);
+
+ addrs[i].conf.addr_text.len = len;
+ addrs[i].conf.addr_text.data = p;
+ }
+
+ return NGX_OK;
+}
+
+
+#if (NGX_HAVE_INET6)
+
+static ngx_int_t
+ngx_mail_add_addrs6(ngx_conf_t *cf, ngx_mail_port_t *mport,
+ ngx_mail_conf_addr_t *addr)
+{
+ u_char *p;
+ size_t len;
+ ngx_uint_t i;
+ ngx_mail_in6_addr_t *addrs6;
+ struct sockaddr_in6 *sin6;
+ u_char buf[NGX_SOCKADDR_STRLEN];
+
+ mport->addrs = ngx_pcalloc(cf->pool,
+ mport->naddrs * sizeof(ngx_mail_in6_addr_t));
+ if (mport->addrs == NULL) {
+ return NGX_ERROR;
+ }
+
+ addrs6 = mport->addrs;
+
+ for (i = 0; i < mport->naddrs; i++) {
+
+ sin6 = (struct sockaddr_in6 *) addr[i].sockaddr;
+ addrs6[i].addr6 = sin6->sin6_addr;
+
+ addrs6[i].conf.ctx = addr[i].ctx;
+#if (NGX_MAIL_SSL)
+ addrs6[i].conf.ssl = addr[i].ssl;
+#endif
+
+ len = ngx_sock_ntop(addr[i].sockaddr, buf, NGX_SOCKADDR_STRLEN, 1);
+
+ p = ngx_pnalloc(cf->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(p, buf, len);
+
+ addrs6[i].conf.addr_text.len = len;
+ addrs6[i].conf.addr_text.data = p;
+ }
+
+ return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_mail_cmp_conf_addrs(const void *one, const void *two)
+{
+ ngx_mail_conf_addr_t *first, *second;
+
+ first = (ngx_mail_conf_addr_t *) one;
+ second = (ngx_mail_conf_addr_t *) two;
+
+ if (first->wildcard) {
+ /* a wildcard must be the last resort, shift it to the end */
+ return 1;
+ }
+
+ if (first->bind && !second->bind) {
+ /* shift explicit bind()ed addresses to the start */
+ return -1;
+ }
+
+ if (!first->bind && second->bind) {
+ /* shift explicit bind()ed addresses to the start */
+ return 1;
+ }
+
+ /* do not sort by default */
+
+ return 0;
+}
diff --git a/usr.sbin/nginx/src/mail/ngx_mail.h b/usr.sbin/nginx/src/mail/ngx_mail.h
new file mode 100644
index 00000000000..fd6d318a5d4
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail.h
@@ -0,0 +1,406 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_MAIL_H_INCLUDED_
+#define _NGX_MAIL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+
+#if (NGX_MAIL_SSL)
+#include <ngx_mail_ssl_module.h>
+#endif
+
+
+
+typedef struct {
+ void **main_conf;
+ void **srv_conf;
+} ngx_mail_conf_ctx_t;
+
+
+typedef struct {
+ u_char sockaddr[NGX_SOCKADDRLEN];
+ socklen_t socklen;
+
+ /* server ctx */
+ ngx_mail_conf_ctx_t *ctx;
+
+ unsigned bind:1;
+ unsigned wildcard:1;
+#if (NGX_MAIL_SSL)
+ unsigned ssl:1;
+#endif
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+ unsigned ipv6only:2;
+#endif
+} ngx_mail_listen_t;
+
+
+typedef struct {
+ ngx_mail_conf_ctx_t *ctx;
+ ngx_str_t addr_text;
+#if (NGX_MAIL_SSL)
+ ngx_uint_t ssl; /* unsigned ssl:1; */
+#endif
+} ngx_mail_addr_conf_t;
+
+typedef struct {
+ in_addr_t addr;
+ ngx_mail_addr_conf_t conf;
+} ngx_mail_in_addr_t;
+
+
+#if (NGX_HAVE_INET6)
+
+typedef struct {
+ struct in6_addr addr6;
+ ngx_mail_addr_conf_t conf;
+} ngx_mail_in6_addr_t;
+
+#endif
+
+
+typedef struct {
+ /* ngx_mail_in_addr_t or ngx_mail_in6_addr_t */
+ void *addrs;
+ ngx_uint_t naddrs;
+} ngx_mail_port_t;
+
+
+typedef struct {
+ int family;
+ in_port_t port;
+ ngx_array_t addrs; /* array of ngx_mail_conf_addr_t */
+} ngx_mail_conf_port_t;
+
+
+typedef struct {
+ struct sockaddr *sockaddr;
+ socklen_t socklen;
+
+ ngx_mail_conf_ctx_t *ctx;
+
+ unsigned bind:1;
+ unsigned wildcard:1;
+#if (NGX_MAIL_SSL)
+ unsigned ssl:1;
+#endif
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+ unsigned ipv6only:2;
+#endif
+} ngx_mail_conf_addr_t;
+
+
+typedef struct {
+ ngx_array_t servers; /* ngx_mail_core_srv_conf_t */
+ ngx_array_t listen; /* ngx_mail_listen_t */
+} ngx_mail_core_main_conf_t;
+
+
+#define NGX_MAIL_POP3_PROTOCOL 0
+#define NGX_MAIL_IMAP_PROTOCOL 1
+#define NGX_MAIL_SMTP_PROTOCOL 2
+
+
+typedef struct ngx_mail_protocol_s ngx_mail_protocol_t;
+
+
+typedef struct {
+ ngx_mail_protocol_t *protocol;
+
+ ngx_msec_t timeout;
+ ngx_msec_t resolver_timeout;
+
+ ngx_flag_t so_keepalive;
+
+ ngx_str_t server_name;
+
+ u_char *file_name;
+ ngx_int_t line;
+
+ ngx_resolver_t *resolver;
+
+ /* server ctx */
+ ngx_mail_conf_ctx_t *ctx;
+} ngx_mail_core_srv_conf_t;
+
+
+typedef enum {
+ ngx_pop3_start = 0,
+ ngx_pop3_user,
+ ngx_pop3_passwd,
+ ngx_pop3_auth_login_username,
+ ngx_pop3_auth_login_password,
+ ngx_pop3_auth_plain,
+ ngx_pop3_auth_cram_md5
+} ngx_pop3_state_e;
+
+
+typedef enum {
+ ngx_imap_start = 0,
+ ngx_imap_auth_login_username,
+ ngx_imap_auth_login_password,
+ ngx_imap_auth_plain,
+ ngx_imap_auth_cram_md5,
+ ngx_imap_login,
+ ngx_imap_user,
+ ngx_imap_passwd
+} ngx_imap_state_e;
+
+
+typedef enum {
+ ngx_smtp_start = 0,
+ ngx_smtp_auth_login_username,
+ ngx_smtp_auth_login_password,
+ ngx_smtp_auth_plain,
+ ngx_smtp_auth_cram_md5,
+ ngx_smtp_helo,
+ ngx_smtp_helo_xclient,
+ ngx_smtp_helo_from,
+ ngx_smtp_xclient,
+ ngx_smtp_xclient_from,
+ ngx_smtp_xclient_helo,
+ ngx_smtp_from,
+ ngx_smtp_to
+} ngx_smtp_state_e;
+
+
+typedef struct {
+ ngx_peer_connection_t upstream;
+ ngx_buf_t *buffer;
+} ngx_mail_proxy_ctx_t;
+
+
+typedef struct {
+ uint32_t signature; /* "MAIL" */
+
+ ngx_connection_t *connection;
+
+ ngx_str_t out;
+ ngx_buf_t *buffer;
+
+ void **ctx;
+ void **main_conf;
+ void **srv_conf;
+
+ ngx_resolver_ctx_t *resolver_ctx;
+
+ ngx_mail_proxy_ctx_t *proxy;
+
+ ngx_uint_t mail_state;
+
+ unsigned protocol:3;
+ unsigned blocked:1;
+ unsigned quit:1;
+ unsigned quoted:1;
+ unsigned backslash:1;
+ unsigned no_sync_literal:1;
+ unsigned starttls:1;
+ unsigned esmtp:1;
+ unsigned auth_method:3;
+ unsigned auth_wait:1;
+
+ ngx_str_t login;
+ ngx_str_t passwd;
+
+ ngx_str_t salt;
+ ngx_str_t tag;
+ ngx_str_t tagged_line;
+ ngx_str_t text;
+
+ ngx_str_t *addr_text;
+ ngx_str_t host;
+ ngx_str_t smtp_helo;
+ ngx_str_t smtp_from;
+ ngx_str_t smtp_to;
+
+ ngx_uint_t command;
+ ngx_array_t args;
+
+ ngx_uint_t login_attempt;
+
+ /* used to parse POP3/IMAP/SMTP command */
+
+ ngx_uint_t state;
+ u_char *cmd_start;
+ u_char *arg_start;
+ u_char *arg_end;
+ ngx_uint_t literal_len;
+} ngx_mail_session_t;
+
+
+typedef struct {
+ ngx_str_t *client;
+ ngx_mail_session_t *session;
+} ngx_mail_log_ctx_t;
+
+
+#define NGX_POP3_USER 1
+#define NGX_POP3_PASS 2
+#define NGX_POP3_CAPA 3
+#define NGX_POP3_QUIT 4
+#define NGX_POP3_NOOP 5
+#define NGX_POP3_STLS 6
+#define NGX_POP3_APOP 7
+#define NGX_POP3_AUTH 8
+#define NGX_POP3_STAT 9
+#define NGX_POP3_LIST 10
+#define NGX_POP3_RETR 11
+#define NGX_POP3_DELE 12
+#define NGX_POP3_RSET 13
+#define NGX_POP3_TOP 14
+#define NGX_POP3_UIDL 15
+
+
+#define NGX_IMAP_LOGIN 1
+#define NGX_IMAP_LOGOUT 2
+#define NGX_IMAP_CAPABILITY 3
+#define NGX_IMAP_NOOP 4
+#define NGX_IMAP_STARTTLS 5
+
+#define NGX_IMAP_NEXT 6
+
+#define NGX_IMAP_AUTHENTICATE 7
+
+
+#define NGX_SMTP_HELO 1
+#define NGX_SMTP_EHLO 2
+#define NGX_SMTP_AUTH 3
+#define NGX_SMTP_QUIT 4
+#define NGX_SMTP_NOOP 5
+#define NGX_SMTP_MAIL 6
+#define NGX_SMTP_RSET 7
+#define NGX_SMTP_RCPT 8
+#define NGX_SMTP_DATA 9
+#define NGX_SMTP_VRFY 10
+#define NGX_SMTP_EXPN 11
+#define NGX_SMTP_HELP 12
+#define NGX_SMTP_STARTTLS 13
+
+
+#define NGX_MAIL_AUTH_PLAIN 0
+#define NGX_MAIL_AUTH_LOGIN 1
+#define NGX_MAIL_AUTH_LOGIN_USERNAME 2
+#define NGX_MAIL_AUTH_APOP 3
+#define NGX_MAIL_AUTH_CRAM_MD5 4
+#define NGX_MAIL_AUTH_NONE 5
+
+
+#define NGX_MAIL_AUTH_PLAIN_ENABLED 0x0002
+#define NGX_MAIL_AUTH_LOGIN_ENABLED 0x0004
+#define NGX_MAIL_AUTH_APOP_ENABLED 0x0008
+#define NGX_MAIL_AUTH_CRAM_MD5_ENABLED 0x0010
+#define NGX_MAIL_AUTH_NONE_ENABLED 0x0020
+
+
+#define NGX_MAIL_PARSE_INVALID_COMMAND 20
+
+
+typedef void (*ngx_mail_init_session_pt)(ngx_mail_session_t *s,
+ ngx_connection_t *c);
+typedef void (*ngx_mail_init_protocol_pt)(ngx_event_t *rev);
+typedef void (*ngx_mail_auth_state_pt)(ngx_event_t *rev);
+typedef ngx_int_t (*ngx_mail_parse_command_pt)(ngx_mail_session_t *s);
+
+
+struct ngx_mail_protocol_s {
+ ngx_str_t name;
+ in_port_t port[4];
+ ngx_uint_t type;
+
+ ngx_mail_init_session_pt init_session;
+ ngx_mail_init_protocol_pt init_protocol;
+ ngx_mail_parse_command_pt parse_command;
+ ngx_mail_auth_state_pt auth_state;
+
+ ngx_str_t internal_server_error;
+};
+
+
+typedef struct {
+ ngx_mail_protocol_t *protocol;
+
+ void *(*create_main_conf)(ngx_conf_t *cf);
+ char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
+
+ void *(*create_srv_conf)(ngx_conf_t *cf);
+ char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev,
+ void *conf);
+} ngx_mail_module_t;
+
+
+#define NGX_MAIL_MODULE 0x4C49414D /* "MAIL" */
+
+#define NGX_MAIL_MAIN_CONF 0x02000000
+#define NGX_MAIL_SRV_CONF 0x04000000
+
+
+#define NGX_MAIL_MAIN_CONF_OFFSET offsetof(ngx_mail_conf_ctx_t, main_conf)
+#define NGX_MAIL_SRV_CONF_OFFSET offsetof(ngx_mail_conf_ctx_t, srv_conf)
+
+
+#define ngx_mail_get_module_ctx(s, module) (s)->ctx[module.ctx_index]
+#define ngx_mail_set_ctx(s, c, module) s->ctx[module.ctx_index] = c;
+#define ngx_mail_delete_ctx(s, module) s->ctx[module.ctx_index] = NULL;
+
+
+#define ngx_mail_get_module_main_conf(s, module) \
+ (s)->main_conf[module.ctx_index]
+#define ngx_mail_get_module_srv_conf(s, module) (s)->srv_conf[module.ctx_index]
+
+#define ngx_mail_conf_get_module_main_conf(cf, module) \
+ ((ngx_mail_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]
+#define ngx_mail_conf_get_module_srv_conf(cf, module) \
+ ((ngx_mail_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index]
+
+
+#if (NGX_MAIL_SSL)
+void ngx_mail_starttls_handler(ngx_event_t *rev);
+ngx_int_t ngx_mail_starttls_only(ngx_mail_session_t *s, ngx_connection_t *c);
+#endif
+
+
+void ngx_mail_init_connection(ngx_connection_t *c);
+
+ngx_int_t ngx_mail_salt(ngx_mail_session_t *s, ngx_connection_t *c,
+ ngx_mail_core_srv_conf_t *cscf);
+ngx_int_t ngx_mail_auth_plain(ngx_mail_session_t *s, ngx_connection_t *c,
+ ngx_uint_t n);
+ngx_int_t ngx_mail_auth_login_username(ngx_mail_session_t *s,
+ ngx_connection_t *c, ngx_uint_t n);
+ngx_int_t ngx_mail_auth_login_password(ngx_mail_session_t *s,
+ ngx_connection_t *c);
+ngx_int_t ngx_mail_auth_cram_md5_salt(ngx_mail_session_t *s,
+ ngx_connection_t *c, char *prefix, size_t len);
+ngx_int_t ngx_mail_auth_cram_md5(ngx_mail_session_t *s, ngx_connection_t *c);
+ngx_int_t ngx_mail_auth_parse(ngx_mail_session_t *s, ngx_connection_t *c);
+
+void ngx_mail_send(ngx_event_t *wev);
+ngx_int_t ngx_mail_read_command(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_auth(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_close_connection(ngx_connection_t *c);
+void ngx_mail_session_internal_server_error(ngx_mail_session_t *s);
+u_char *ngx_mail_log_error(ngx_log_t *log, u_char *buf, size_t len);
+
+
+char *ngx_mail_capabilities(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+/* STUB */
+void ngx_mail_proxy_init(ngx_mail_session_t *s, ngx_addr_t *peer);
+void ngx_mail_auth_http_init(ngx_mail_session_t *s);
+/**/
+
+
+extern ngx_uint_t ngx_mail_max_module;
+extern ngx_module_t ngx_mail_core_module;
+
+
+#endif /* _NGX_MAIL_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/mail/ngx_mail_auth_http_module.c b/usr.sbin/nginx/src/mail/ngx_mail_auth_http_module.c
new file mode 100644
index 00000000000..d54a392ac44
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail_auth_http_module.c
@@ -0,0 +1,1451 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+#include <ngx_mail.h>
+
+
+typedef struct {
+ ngx_addr_t *peer;
+
+ ngx_msec_t timeout;
+
+ ngx_str_t host_header;
+ ngx_str_t uri;
+ ngx_str_t header;
+
+ ngx_array_t *headers;
+
+ u_char *file;
+ ngx_uint_t line;
+} ngx_mail_auth_http_conf_t;
+
+
+typedef struct ngx_mail_auth_http_ctx_s ngx_mail_auth_http_ctx_t;
+
+typedef void (*ngx_mail_auth_http_handler_pt)(ngx_mail_session_t *s,
+ ngx_mail_auth_http_ctx_t *ctx);
+
+struct ngx_mail_auth_http_ctx_s {
+ ngx_buf_t *request;
+ ngx_buf_t *response;
+ ngx_peer_connection_t peer;
+
+ ngx_mail_auth_http_handler_pt handler;
+
+ ngx_uint_t state;
+
+ u_char *header_name_start;
+ u_char *header_name_end;
+ u_char *header_start;
+ u_char *header_end;
+
+ ngx_str_t addr;
+ ngx_str_t port;
+ ngx_str_t err;
+ ngx_str_t errmsg;
+ ngx_str_t errcode;
+
+ time_t sleep;
+
+ ngx_pool_t *pool;
+};
+
+
+static void ngx_mail_auth_http_write_handler(ngx_event_t *wev);
+static void ngx_mail_auth_http_read_handler(ngx_event_t *rev);
+static void ngx_mail_auth_http_ignore_status_line(ngx_mail_session_t *s,
+ ngx_mail_auth_http_ctx_t *ctx);
+static void ngx_mail_auth_http_process_headers(ngx_mail_session_t *s,
+ ngx_mail_auth_http_ctx_t *ctx);
+static void ngx_mail_auth_sleep_handler(ngx_event_t *rev);
+static ngx_int_t ngx_mail_auth_http_parse_header_line(ngx_mail_session_t *s,
+ ngx_mail_auth_http_ctx_t *ctx);
+static void ngx_mail_auth_http_block_read(ngx_event_t *rev);
+static void ngx_mail_auth_http_dummy_handler(ngx_event_t *ev);
+static ngx_buf_t *ngx_mail_auth_http_create_request(ngx_mail_session_t *s,
+ ngx_pool_t *pool, ngx_mail_auth_http_conf_t *ahcf);
+static ngx_int_t ngx_mail_auth_http_escape(ngx_pool_t *pool, ngx_str_t *text,
+ ngx_str_t *escaped);
+
+static void *ngx_mail_auth_http_create_conf(ngx_conf_t *cf);
+static char *ngx_mail_auth_http_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_mail_auth_http(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_mail_auth_http_header(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_command_t ngx_mail_auth_http_commands[] = {
+
+ { ngx_string("auth_http"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_mail_auth_http,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("auth_http_timeout"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_auth_http_conf_t, timeout),
+ NULL },
+
+ { ngx_string("auth_http_header"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE2,
+ ngx_mail_auth_http_header,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_mail_module_t ngx_mail_auth_http_module_ctx = {
+ NULL, /* protocol */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_mail_auth_http_create_conf, /* create server configuration */
+ ngx_mail_auth_http_merge_conf /* merge server configuration */
+};
+
+
+ngx_module_t ngx_mail_auth_http_module = {
+ NGX_MODULE_V1,
+ &ngx_mail_auth_http_module_ctx, /* module context */
+ ngx_mail_auth_http_commands, /* module directives */
+ NGX_MAIL_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_mail_auth_http_method[] = {
+ ngx_string("plain"),
+ ngx_string("plain"),
+ ngx_string("plain"),
+ ngx_string("apop"),
+ ngx_string("cram-md5"),
+ ngx_string("none")
+};
+
+static ngx_str_t ngx_mail_smtp_errcode = ngx_string("535 5.7.0");
+
+
+void
+ngx_mail_auth_http_init(ngx_mail_session_t *s)
+{
+ ngx_int_t rc;
+ ngx_pool_t *pool;
+ ngx_mail_auth_http_ctx_t *ctx;
+ ngx_mail_auth_http_conf_t *ahcf;
+
+ s->connection->log->action = "in http auth state";
+
+ pool = ngx_create_pool(2048, s->connection->log);
+ if (pool == NULL) {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ctx = ngx_pcalloc(pool, sizeof(ngx_mail_auth_http_ctx_t));
+ if (ctx == NULL) {
+ ngx_destroy_pool(pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ctx->pool = pool;
+
+ ahcf = ngx_mail_get_module_srv_conf(s, ngx_mail_auth_http_module);
+
+ ctx->request = ngx_mail_auth_http_create_request(s, pool, ahcf);
+ if (ctx->request == NULL) {
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ngx_mail_set_ctx(s, ctx, ngx_mail_auth_http_module);
+
+ ctx->peer.sockaddr = ahcf->peer->sockaddr;
+ ctx->peer.socklen = ahcf->peer->socklen;
+ ctx->peer.name = &ahcf->peer->name;
+ ctx->peer.get = ngx_event_get_peer;
+ ctx->peer.log = s->connection->log;
+ ctx->peer.log_error = NGX_ERROR_ERR;
+
+ rc = ngx_event_connect_peer(&ctx->peer);
+
+ if (rc == NGX_ERROR || rc == NGX_BUSY || rc == NGX_DECLINED) {
+ if (ctx->peer.connection) {
+ ngx_close_connection(ctx->peer.connection);
+ }
+
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ctx->peer.connection->data = s;
+ ctx->peer.connection->pool = s->connection->pool;
+
+ s->connection->read->handler = ngx_mail_auth_http_block_read;
+ ctx->peer.connection->read->handler = ngx_mail_auth_http_read_handler;
+ ctx->peer.connection->write->handler = ngx_mail_auth_http_write_handler;
+
+ ctx->handler = ngx_mail_auth_http_ignore_status_line;
+
+ ngx_add_timer(ctx->peer.connection->read, ahcf->timeout);
+ ngx_add_timer(ctx->peer.connection->write, ahcf->timeout);
+
+ if (rc == NGX_OK) {
+ ngx_mail_auth_http_write_handler(ctx->peer.connection->write);
+ return;
+ }
+}
+
+
+static void
+ngx_mail_auth_http_write_handler(ngx_event_t *wev)
+{
+ ssize_t n, size;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_auth_http_ctx_t *ctx;
+ ngx_mail_auth_http_conf_t *ahcf;
+
+ c = wev->data;
+ s = c->data;
+
+ ctx = ngx_mail_get_module_ctx(s, ngx_mail_auth_http_module);
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, wev->log, 0,
+ "mail auth http write handler");
+
+ if (wev->timedout) {
+ ngx_log_error(NGX_LOG_ERR, wev->log, NGX_ETIMEDOUT,
+ "auth http server %V timed out", ctx->peer.name);
+ ngx_close_connection(c);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ size = ctx->request->last - ctx->request->pos;
+
+ n = ngx_send(c, ctx->request->pos, size);
+
+ if (n == NGX_ERROR) {
+ ngx_close_connection(c);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ if (n > 0) {
+ ctx->request->pos += n;
+
+ if (n == size) {
+ wev->handler = ngx_mail_auth_http_dummy_handler;
+
+ if (wev->timer_set) {
+ ngx_del_timer(wev);
+ }
+
+ if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+ ngx_close_connection(c);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ }
+
+ return;
+ }
+ }
+
+ if (!wev->timer_set) {
+ ahcf = ngx_mail_get_module_srv_conf(s, ngx_mail_auth_http_module);
+ ngx_add_timer(wev, ahcf->timeout);
+ }
+}
+
+
+static void
+ngx_mail_auth_http_read_handler(ngx_event_t *rev)
+{
+ ssize_t n, size;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_auth_http_ctx_t *ctx;
+
+ c = rev->data;
+ s = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail auth http read handler");
+
+ ctx = ngx_mail_get_module_ctx(s, ngx_mail_auth_http_module);
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_ERR, rev->log, NGX_ETIMEDOUT,
+ "auth http server %V timed out", ctx->peer.name);
+ ngx_close_connection(c);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ if (ctx->response == NULL) {
+ ctx->response = ngx_create_temp_buf(ctx->pool, 1024);
+ if (ctx->response == NULL) {
+ ngx_close_connection(c);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+ }
+
+ size = ctx->response->end - ctx->response->last;
+
+ n = ngx_recv(c, ctx->response->pos, size);
+
+ if (n > 0) {
+ ctx->response->last += n;
+
+ ctx->handler(s, ctx);
+ return;
+ }
+
+ if (n == NGX_AGAIN) {
+ return;
+ }
+
+ ngx_close_connection(c);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+}
+
+
+static void
+ngx_mail_auth_http_ignore_status_line(ngx_mail_session_t *s,
+ ngx_mail_auth_http_ctx_t *ctx)
+{
+ u_char *p, ch;
+ enum {
+ sw_start = 0,
+ sw_H,
+ sw_HT,
+ sw_HTT,
+ sw_HTTP,
+ sw_skip,
+ sw_almost_done
+ } state;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "mail auth http process status line");
+
+ state = ctx->state;
+
+ for (p = ctx->response->pos; p < ctx->response->last; p++) {
+ ch = *p;
+
+ switch (state) {
+
+ /* "HTTP/" */
+ case sw_start:
+ if (ch == 'H') {
+ state = sw_H;
+ break;
+ }
+ goto next;
+
+ case sw_H:
+ if (ch == 'T') {
+ state = sw_HT;
+ break;
+ }
+ goto next;
+
+ case sw_HT:
+ if (ch == 'T') {
+ state = sw_HTT;
+ break;
+ }
+ goto next;
+
+ case sw_HTT:
+ if (ch == 'P') {
+ state = sw_HTTP;
+ break;
+ }
+ goto next;
+
+ case sw_HTTP:
+ if (ch == '/') {
+ state = sw_skip;
+ break;
+ }
+ goto next;
+
+ /* any text until end of line */
+ case sw_skip:
+ switch (ch) {
+ case CR:
+ state = sw_almost_done;
+
+ break;
+ case LF:
+ goto done;
+ }
+ break;
+
+ /* end of status line */
+ case sw_almost_done:
+ if (ch == LF) {
+ goto done;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "auth http server &V sent invalid response",
+ ctx->peer.name);
+ ngx_close_connection(ctx->peer.connection);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+ }
+
+ ctx->response->pos = p;
+ ctx->state = state;
+
+ return;
+
+next:
+
+ p = ctx->response->start - 1;
+
+done:
+
+ ctx->response->pos = p + 1;
+ ctx->state = 0;
+ ctx->handler = ngx_mail_auth_http_process_headers;
+ ctx->handler(s, ctx);
+}
+
+
+static void
+ngx_mail_auth_http_process_headers(ngx_mail_session_t *s,
+ ngx_mail_auth_http_ctx_t *ctx)
+{
+ u_char *p;
+ time_t timer;
+ size_t len, size;
+ ngx_int_t rc, port, n;
+ ngx_addr_t *peer;
+ struct sockaddr_in *sin;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "mail auth http process headers");
+
+ for ( ;; ) {
+ rc = ngx_mail_auth_http_parse_header_line(s, ctx);
+
+ if (rc == NGX_OK) {
+
+#if (NGX_DEBUG)
+ {
+ ngx_str_t key, value;
+
+ key.len = ctx->header_name_end - ctx->header_name_start;
+ key.data = ctx->header_name_start;
+ value.len = ctx->header_end - ctx->header_start;
+ value.data = ctx->header_start;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "mail auth http header: \"%V: %V\"",
+ &key, &value);
+ }
+#endif
+
+ len = ctx->header_name_end - ctx->header_name_start;
+
+ if (len == sizeof("Auth-Status") - 1
+ && ngx_strncasecmp(ctx->header_name_start,
+ (u_char *) "Auth-Status",
+ sizeof("Auth-Status") - 1)
+ == 0)
+ {
+ len = ctx->header_end - ctx->header_start;
+
+ if (len == 2
+ && ctx->header_start[0] == 'O'
+ && ctx->header_start[1] == 'K')
+ {
+ continue;
+ }
+
+ if (len == 4
+ && ctx->header_start[0] == 'W'
+ && ctx->header_start[1] == 'A'
+ && ctx->header_start[2] == 'I'
+ && ctx->header_start[3] == 'T')
+ {
+ s->auth_wait = 1;
+ continue;
+ }
+
+ ctx->errmsg.len = len;
+ ctx->errmsg.data = ctx->header_start;
+
+ switch (s->protocol) {
+
+ case NGX_MAIL_POP3_PROTOCOL:
+ size = sizeof("-ERR ") - 1 + len + sizeof(CRLF) - 1;
+ break;
+
+ case NGX_MAIL_IMAP_PROTOCOL:
+ size = s->tag.len + sizeof("NO ") - 1 + len
+ + sizeof(CRLF) - 1;
+ break;
+
+ default: /* NGX_MAIL_SMTP_PROTOCOL */
+ ctx->err = ctx->errmsg;
+ continue;
+ }
+
+ p = ngx_pnalloc(s->connection->pool, size);
+ if (p == NULL) {
+ ngx_close_connection(ctx->peer.connection);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ctx->err.data = p;
+
+ switch (s->protocol) {
+
+ case NGX_MAIL_POP3_PROTOCOL:
+ *p++ = '-'; *p++ = 'E'; *p++ = 'R'; *p++ = 'R'; *p++ = ' ';
+ break;
+
+ case NGX_MAIL_IMAP_PROTOCOL:
+ p = ngx_cpymem(p, s->tag.data, s->tag.len);
+ *p++ = 'N'; *p++ = 'O'; *p++ = ' ';
+ break;
+
+ default: /* NGX_MAIL_SMTP_PROTOCOL */
+ break;
+ }
+
+ p = ngx_cpymem(p, ctx->header_start, len);
+ *p++ = CR; *p++ = LF;
+
+ ctx->err.len = p - ctx->err.data;
+
+ continue;
+ }
+
+ if (len == sizeof("Auth-Server") - 1
+ && ngx_strncasecmp(ctx->header_name_start,
+ (u_char *) "Auth-Server",
+ sizeof("Auth-Server") - 1)
+ == 0)
+ {
+ ctx->addr.len = ctx->header_end - ctx->header_start;
+ ctx->addr.data = ctx->header_start;
+
+ continue;
+ }
+
+ if (len == sizeof("Auth-Port") - 1
+ && ngx_strncasecmp(ctx->header_name_start,
+ (u_char *) "Auth-Port",
+ sizeof("Auth-Port") - 1)
+ == 0)
+ {
+ ctx->port.len = ctx->header_end - ctx->header_start;
+ ctx->port.data = ctx->header_start;
+
+ continue;
+ }
+
+ if (len == sizeof("Auth-User") - 1
+ && ngx_strncasecmp(ctx->header_name_start,
+ (u_char *) "Auth-User",
+ sizeof("Auth-User") - 1)
+ == 0)
+ {
+ s->login.len = ctx->header_end - ctx->header_start;
+
+ s->login.data = ngx_pnalloc(s->connection->pool, s->login.len);
+ if (s->login.data == NULL) {
+ ngx_close_connection(ctx->peer.connection);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ngx_memcpy(s->login.data, ctx->header_start, s->login.len);
+
+ continue;
+ }
+
+ if (len == sizeof("Auth-Pass") - 1
+ && ngx_strncasecmp(ctx->header_name_start,
+ (u_char *) "Auth-Pass",
+ sizeof("Auth-Pass") - 1)
+ == 0)
+ {
+ s->passwd.len = ctx->header_end - ctx->header_start;
+
+ s->passwd.data = ngx_pnalloc(s->connection->pool,
+ s->passwd.len);
+ if (s->passwd.data == NULL) {
+ ngx_close_connection(ctx->peer.connection);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ngx_memcpy(s->passwd.data, ctx->header_start, s->passwd.len);
+
+ continue;
+ }
+
+ if (len == sizeof("Auth-Wait") - 1
+ && ngx_strncasecmp(ctx->header_name_start,
+ (u_char *) "Auth-Wait",
+ sizeof("Auth-Wait") - 1)
+ == 0)
+ {
+ n = ngx_atoi(ctx->header_start,
+ ctx->header_end - ctx->header_start);
+
+ if (n != NGX_ERROR) {
+ ctx->sleep = n;
+ }
+
+ continue;
+ }
+
+ if (len == sizeof("Auth-Error-Code") - 1
+ && ngx_strncasecmp(ctx->header_name_start,
+ (u_char *) "Auth-Error-Code",
+ sizeof("Auth-Error-Code") - 1)
+ == 0)
+ {
+ ctx->errcode.len = ctx->header_end - ctx->header_start;
+
+ ctx->errcode.data = ngx_pnalloc(s->connection->pool,
+ ctx->errcode.len);
+ if (ctx->errcode.data == NULL) {
+ ngx_close_connection(ctx->peer.connection);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ngx_memcpy(ctx->errcode.data, ctx->header_start,
+ ctx->errcode.len);
+
+ continue;
+ }
+
+ /* ignore other headers */
+
+ continue;
+ }
+
+ if (rc == NGX_DONE) {
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "mail auth http header done");
+
+ ngx_close_connection(ctx->peer.connection);
+
+ if (ctx->err.len) {
+
+ ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
+ "client login failed: \"%V\"", &ctx->errmsg);
+
+ if (s->protocol == NGX_MAIL_SMTP_PROTOCOL) {
+
+ if (ctx->errcode.len == 0) {
+ ctx->errcode = ngx_mail_smtp_errcode;
+ }
+
+ ctx->err.len = ctx->errcode.len + ctx->errmsg.len
+ + sizeof(" " CRLF) - 1;
+
+ p = ngx_pnalloc(s->connection->pool, ctx->err.len);
+ if (p == NULL) {
+ ngx_close_connection(ctx->peer.connection);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ctx->err.data = p;
+
+ p = ngx_cpymem(p, ctx->errcode.data, ctx->errcode.len);
+ *p++ = ' ';
+ p = ngx_cpymem(p, ctx->errmsg.data, ctx->errmsg.len);
+ *p++ = CR; *p = LF;
+ }
+
+ s->out = ctx->err;
+ timer = ctx->sleep;
+
+ ngx_destroy_pool(ctx->pool);
+
+ if (timer == 0) {
+ s->quit = 1;
+ ngx_mail_send(s->connection->write);
+ return;
+ }
+
+ ngx_add_timer(s->connection->read, (ngx_msec_t) (timer * 1000));
+
+ s->connection->read->handler = ngx_mail_auth_sleep_handler;
+
+ return;
+ }
+
+ if (s->auth_wait) {
+ timer = ctx->sleep;
+
+ ngx_destroy_pool(ctx->pool);
+
+ if (timer == 0) {
+ ngx_mail_auth_http_init(s);
+ return;
+ }
+
+ ngx_add_timer(s->connection->read, (ngx_msec_t) (timer * 1000));
+
+ s->connection->read->handler = ngx_mail_auth_sleep_handler;
+
+ return;
+ }
+
+ if (ctx->addr.len == 0 || ctx->port.len == 0) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "auth http server %V did not send server or port",
+ ctx->peer.name);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ if (s->passwd.data == NULL
+ && s->protocol != NGX_MAIL_SMTP_PROTOCOL)
+ {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "auth http server %V did not send password",
+ ctx->peer.name);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ peer = ngx_pcalloc(s->connection->pool, sizeof(ngx_addr_t));
+ if (peer == NULL) {
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ /* AF_INET only */
+
+ sin = ngx_pcalloc(s->connection->pool, sizeof(struct sockaddr_in));
+ if (sin == NULL) {
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ sin->sin_family = AF_INET;
+
+ port = ngx_atoi(ctx->port.data, ctx->port.len);
+ if (port == NGX_ERROR || port < 1 || port > 65536) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "auth http server %V sent invalid server "
+ "port:\"%V\"",
+ ctx->peer.name, &ctx->port);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ sin->sin_port = htons((in_port_t) port);
+
+ sin->sin_addr.s_addr = ngx_inet_addr(ctx->addr.data, ctx->addr.len);
+ if (sin->sin_addr.s_addr == INADDR_NONE) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "auth http server %V sent invalid server "
+ "address:\"%V\"",
+ ctx->peer.name, &ctx->addr);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ peer->sockaddr = (struct sockaddr *) sin;
+ peer->socklen = sizeof(struct sockaddr_in);
+
+ len = ctx->addr.len + 1 + ctx->port.len;
+
+ peer->name.len = len;
+
+ peer->name.data = ngx_pnalloc(s->connection->pool, len);
+ if (peer->name.data == NULL) {
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ len = ctx->addr.len;
+
+ ngx_memcpy(peer->name.data, ctx->addr.data, len);
+
+ peer->name.data[len++] = ':';
+
+ ngx_memcpy(peer->name.data + len, ctx->port.data, ctx->port.len);
+
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_proxy_init(s, peer);
+
+ return;
+ }
+
+ if (rc == NGX_AGAIN ) {
+ return;
+ }
+
+ /* rc == NGX_ERROR */
+
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "auth http server %V sent invalid header in response",
+ ctx->peer.name);
+ ngx_close_connection(ctx->peer.connection);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+
+ return;
+ }
+}
+
+
+static void
+ngx_mail_auth_sleep_handler(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail auth sleep handler");
+
+ c = rev->data;
+ s = c->data;
+
+ if (rev->timedout) {
+
+ rev->timedout = 0;
+
+ if (s->auth_wait) {
+ s->auth_wait = 0;
+ ngx_mail_auth_http_init(s);
+ return;
+ }
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ rev->handler = cscf->protocol->auth_state;
+
+ s->mail_state = 0;
+ s->auth_method = NGX_MAIL_AUTH_PLAIN;
+
+ c->log->action = "in auth state";
+
+ ngx_mail_send(c->write);
+
+ if (c->destroyed) {
+ return;
+ }
+
+ ngx_add_timer(rev, cscf->timeout);
+
+ if (rev->ready) {
+ rev->handler(rev);
+ return;
+ }
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ }
+
+ return;
+ }
+
+ if (rev->active) {
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ }
+ }
+}
+
+
+static ngx_int_t
+ngx_mail_auth_http_parse_header_line(ngx_mail_session_t *s,
+ ngx_mail_auth_http_ctx_t *ctx)
+{
+ u_char c, ch, *p;
+ enum {
+ sw_start = 0,
+ sw_name,
+ sw_space_before_value,
+ sw_value,
+ sw_space_after_value,
+ sw_almost_done,
+ sw_header_almost_done
+ } state;
+
+ state = ctx->state;
+
+ for (p = ctx->response->pos; p < ctx->response->last; p++) {
+ ch = *p;
+
+ switch (state) {
+
+ /* first char */
+ case sw_start:
+
+ switch (ch) {
+ case CR:
+ ctx->header_end = p;
+ state = sw_header_almost_done;
+ break;
+ case LF:
+ ctx->header_end = p;
+ goto header_done;
+ default:
+ state = sw_name;
+ ctx->header_name_start = p;
+
+ c = (u_char) (ch | 0x20);
+ if (c >= 'a' && c <= 'z') {
+ break;
+ }
+
+ if (ch >= '0' && ch <= '9') {
+ break;
+ }
+
+ return NGX_ERROR;
+ }
+ break;
+
+ /* header name */
+ case sw_name:
+ c = (u_char) (ch | 0x20);
+ if (c >= 'a' && c <= 'z') {
+ break;
+ }
+
+ if (ch == ':') {
+ ctx->header_name_end = p;
+ state = sw_space_before_value;
+ break;
+ }
+
+ if (ch == '-') {
+ break;
+ }
+
+ if (ch >= '0' && ch <= '9') {
+ break;
+ }
+
+ if (ch == CR) {
+ ctx->header_name_end = p;
+ ctx->header_start = p;
+ ctx->header_end = p;
+ state = sw_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ ctx->header_name_end = p;
+ ctx->header_start = p;
+ ctx->header_end = p;
+ goto done;
+ }
+
+ return NGX_ERROR;
+
+ /* space* before header value */
+ case sw_space_before_value:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ ctx->header_start = p;
+ ctx->header_end = p;
+ state = sw_almost_done;
+ break;
+ case LF:
+ ctx->header_start = p;
+ ctx->header_end = p;
+ goto done;
+ default:
+ ctx->header_start = p;
+ state = sw_value;
+ break;
+ }
+ break;
+
+ /* header value */
+ case sw_value:
+ switch (ch) {
+ case ' ':
+ ctx->header_end = p;
+ state = sw_space_after_value;
+ break;
+ case CR:
+ ctx->header_end = p;
+ state = sw_almost_done;
+ break;
+ case LF:
+ ctx->header_end = p;
+ goto done;
+ }
+ break;
+
+ /* space* before end of header line */
+ case sw_space_after_value:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ default:
+ state = sw_value;
+ break;
+ }
+ break;
+
+ /* end of header line */
+ case sw_almost_done:
+ switch (ch) {
+ case LF:
+ goto done;
+ default:
+ return NGX_ERROR;
+ }
+
+ /* end of header */
+ case sw_header_almost_done:
+ switch (ch) {
+ case LF:
+ goto header_done;
+ default:
+ return NGX_ERROR;
+ }
+ }
+ }
+
+ ctx->response->pos = p;
+ ctx->state = state;
+
+ return NGX_AGAIN;
+
+done:
+
+ ctx->response->pos = p + 1;
+ ctx->state = sw_start;
+
+ return NGX_OK;
+
+header_done:
+
+ ctx->response->pos = p + 1;
+ ctx->state = sw_start;
+
+ return NGX_DONE;
+}
+
+
+static void
+ngx_mail_auth_http_block_read(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_auth_http_ctx_t *ctx;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail auth http block read");
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ c = rev->data;
+ s = c->data;
+
+ ctx = ngx_mail_get_module_ctx(s, ngx_mail_auth_http_module);
+
+ ngx_close_connection(ctx->peer.connection);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ }
+}
+
+
+static void
+ngx_mail_auth_http_dummy_handler(ngx_event_t *ev)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, ev->log, 0,
+ "mail auth http dummy handler");
+}
+
+
+static ngx_buf_t *
+ngx_mail_auth_http_create_request(ngx_mail_session_t *s, ngx_pool_t *pool,
+ ngx_mail_auth_http_conf_t *ahcf)
+{
+ size_t len;
+ ngx_buf_t *b;
+ ngx_str_t login, passwd;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ if (ngx_mail_auth_http_escape(pool, &s->login, &login) != NGX_OK) {
+ return NULL;
+ }
+
+ if (ngx_mail_auth_http_escape(pool, &s->passwd, &passwd) != NGX_OK) {
+ return NULL;
+ }
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ len = sizeof("GET ") - 1 + ahcf->uri.len + sizeof(" HTTP/1.0" CRLF) - 1
+ + sizeof("Host: ") - 1 + ahcf->host_header.len + sizeof(CRLF) - 1
+ + sizeof("Auth-Method: ") - 1
+ + ngx_mail_auth_http_method[s->auth_method].len
+ + sizeof(CRLF) - 1
+ + sizeof("Auth-User: ") - 1 + login.len + sizeof(CRLF) - 1
+ + sizeof("Auth-Pass: ") - 1 + passwd.len + sizeof(CRLF) - 1
+ + sizeof("Auth-Salt: ") - 1 + s->salt.len
+ + sizeof("Auth-Protocol: ") - 1 + cscf->protocol->name.len
+ + sizeof(CRLF) - 1
+ + sizeof("Auth-Login-Attempt: ") - 1 + NGX_INT_T_LEN
+ + sizeof(CRLF) - 1
+ + sizeof("Client-IP: ") - 1 + s->connection->addr_text.len
+ + sizeof(CRLF) - 1
+ + sizeof("Client-Host: ") - 1 + s->host.len + sizeof(CRLF) - 1
+ + sizeof("Auth-SMTP-Helo: ") - 1 + s->smtp_helo.len
+ + sizeof("Auth-SMTP-From: ") - 1 + s->smtp_from.len
+ + sizeof("Auth-SMTP-To: ") - 1 + s->smtp_to.len
+ + ahcf->header.len
+ + sizeof(CRLF) - 1;
+
+ b = ngx_create_temp_buf(pool, len);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ b->last = ngx_cpymem(b->last, "GET ", sizeof("GET ") - 1);
+ b->last = ngx_copy(b->last, ahcf->uri.data, ahcf->uri.len);
+ b->last = ngx_cpymem(b->last, " HTTP/1.0" CRLF,
+ sizeof(" HTTP/1.0" CRLF) - 1);
+
+ b->last = ngx_cpymem(b->last, "Host: ", sizeof("Host: ") - 1);
+ b->last = ngx_copy(b->last, ahcf->host_header.data,
+ ahcf->host_header.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ b->last = ngx_cpymem(b->last, "Auth-Method: ",
+ sizeof("Auth-Method: ") - 1);
+ b->last = ngx_cpymem(b->last,
+ ngx_mail_auth_http_method[s->auth_method].data,
+ ngx_mail_auth_http_method[s->auth_method].len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ b->last = ngx_cpymem(b->last, "Auth-User: ", sizeof("Auth-User: ") - 1);
+ b->last = ngx_copy(b->last, login.data, login.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ b->last = ngx_cpymem(b->last, "Auth-Pass: ", sizeof("Auth-Pass: ") - 1);
+ b->last = ngx_copy(b->last, passwd.data, passwd.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ if (s->auth_method != NGX_MAIL_AUTH_PLAIN && s->salt.len) {
+ b->last = ngx_cpymem(b->last, "Auth-Salt: ", sizeof("Auth-Salt: ") - 1);
+ b->last = ngx_copy(b->last, s->salt.data, s->salt.len);
+
+ s->passwd.data = NULL;
+ }
+
+ b->last = ngx_cpymem(b->last, "Auth-Protocol: ",
+ sizeof("Auth-Protocol: ") - 1);
+ b->last = ngx_cpymem(b->last, cscf->protocol->name.data,
+ cscf->protocol->name.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ b->last = ngx_sprintf(b->last, "Auth-Login-Attempt: %ui" CRLF,
+ s->login_attempt);
+
+ b->last = ngx_cpymem(b->last, "Client-IP: ", sizeof("Client-IP: ") - 1);
+ b->last = ngx_copy(b->last, s->connection->addr_text.data,
+ s->connection->addr_text.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ if (s->host.len) {
+ b->last = ngx_cpymem(b->last, "Client-Host: ",
+ sizeof("Client-Host: ") - 1);
+ b->last = ngx_copy(b->last, s->host.data, s->host.len);
+ *b->last++ = CR; *b->last++ = LF;
+ }
+
+ if (s->auth_method == NGX_MAIL_AUTH_NONE) {
+
+ /* HELO, MAIL FROM, and RCPT TO can't contain CRLF, no need to escape */
+
+ b->last = ngx_cpymem(b->last, "Auth-SMTP-Helo: ",
+ sizeof("Auth-SMTP-Helo: ") - 1);
+ b->last = ngx_copy(b->last, s->smtp_helo.data, s->smtp_helo.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ b->last = ngx_cpymem(b->last, "Auth-SMTP-From: ",
+ sizeof("Auth-SMTP-From: ") - 1);
+ b->last = ngx_copy(b->last, s->smtp_from.data, s->smtp_from.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ b->last = ngx_cpymem(b->last, "Auth-SMTP-To: ",
+ sizeof("Auth-SMTP-To: ") - 1);
+ b->last = ngx_copy(b->last, s->smtp_to.data, s->smtp_to.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ }
+
+ if (ahcf->header.len) {
+ b->last = ngx_copy(b->last, ahcf->header.data, ahcf->header.len);
+ }
+
+ /* add "\r\n" at the header end */
+ *b->last++ = CR; *b->last++ = LF;
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+ {
+ ngx_str_t l;
+
+ l.len = b->last - b->pos;
+ l.data = b->pos;
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "mail auth http header:\n\"%V\"", &l);
+ }
+#endif
+
+ return b;
+}
+
+
+static ngx_int_t
+ngx_mail_auth_http_escape(ngx_pool_t *pool, ngx_str_t *text, ngx_str_t *escaped)
+{
+ u_char *p;
+ uintptr_t n;
+
+ n = ngx_escape_uri(NULL, text->data, text->len, NGX_ESCAPE_MAIL_AUTH);
+
+ if (n == 0) {
+ *escaped = *text;
+ return NGX_OK;
+ }
+
+ escaped->len = text->len + n * 2;
+
+ p = ngx_pnalloc(pool, escaped->len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ (void) ngx_escape_uri(p, text->data, text->len, NGX_ESCAPE_MAIL_AUTH);
+
+ escaped->data = p;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_mail_auth_http_create_conf(ngx_conf_t *cf)
+{
+ ngx_mail_auth_http_conf_t *ahcf;
+
+ ahcf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_auth_http_conf_t));
+ if (ahcf == NULL) {
+ return NULL;
+ }
+
+ ahcf->timeout = NGX_CONF_UNSET_MSEC;
+
+ ahcf->file = cf->conf_file->file.name.data;
+ ahcf->line = cf->conf_file->line;
+
+ return ahcf;
+}
+
+
+static char *
+ngx_mail_auth_http_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_mail_auth_http_conf_t *prev = parent;
+ ngx_mail_auth_http_conf_t *conf = child;
+
+ u_char *p;
+ size_t len;
+ ngx_uint_t i;
+ ngx_table_elt_t *header;
+
+ if (conf->peer == NULL) {
+ conf->peer = prev->peer;
+ conf->host_header = prev->host_header;
+ conf->uri = prev->uri;
+
+ if (conf->peer == NULL) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"http_auth\" is defined for server in %s:%ui",
+ conf->file, conf->line);
+
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000);
+
+ if (conf->headers == NULL) {
+ conf->headers = prev->headers;
+ conf->header = prev->header;
+ }
+
+ if (conf->headers && conf->header.len == 0) {
+ len = 0;
+ header = conf->headers->elts;
+ for (i = 0; i < conf->headers->nelts; i++) {
+ len += header[i].key.len + 2 + header[i].value.len + 2;
+ }
+
+ p = ngx_pnalloc(cf->pool, len);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->header.len = len;
+ conf->header.data = p;
+
+ for (i = 0; i < conf->headers->nelts; i++) {
+ p = ngx_cpymem(p, header[i].key.data, header[i].key.len);
+ *p++ = ':'; *p++ = ' ';
+ p = ngx_cpymem(p, header[i].value.data, header[i].value.len);
+ *p++ = CR; *p++ = LF;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_auth_http(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_mail_auth_http_conf_t *ahcf = conf;
+
+ ngx_str_t *value;
+ ngx_url_t u;
+
+ value = cf->args->elts;
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.url = value[1];
+ u.default_port = 80;
+ u.uri_part = 1;
+ u.one_addr = 1;
+
+ if (ngx_strncmp(u.url.data, "http://", 7) == 0) {
+ u.url.len -= 7;
+ u.url.data += 7;
+ }
+
+ if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+ if (u.err) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "%s in auth_http \"%V\"", u.err, &u.url);
+ }
+
+ return NGX_CONF_ERROR;
+ }
+
+ ahcf->peer = u.addrs;
+
+ if (u.family != AF_UNIX) {
+ ahcf->host_header = u.host;
+
+ } else {
+ ngx_str_set(&ahcf->host_header, "localhost");
+ }
+
+ ahcf->uri = u.uri;
+
+ if (ahcf->uri.len == 0) {
+ ngx_str_set(&ahcf->uri, "/");
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_auth_http_header(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_mail_auth_http_conf_t *ahcf = conf;
+
+ ngx_str_t *value;
+ ngx_table_elt_t *header;
+
+ if (ahcf->headers == NULL) {
+ ahcf->headers = ngx_array_create(cf->pool, 1, sizeof(ngx_table_elt_t));
+ if (ahcf->headers == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ header = ngx_array_push(ahcf->headers);
+ if (header == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ header->key = value[1];
+ header->value = value[2];
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/mail/ngx_mail_core_module.c b/usr.sbin/nginx/src/mail/ngx_mail_core_module.c
new file mode 100644
index 00000000000..bd2c916d5a4
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail_core_module.c
@@ -0,0 +1,552 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+
+
+static void *ngx_mail_core_create_main_conf(ngx_conf_t *cf);
+static void *ngx_mail_core_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_mail_core_merge_srv_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_mail_core_server(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_mail_core_protocol(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_mail_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_command_t ngx_mail_core_commands[] = {
+
+ { ngx_string("server"),
+ NGX_MAIL_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+ ngx_mail_core_server,
+ 0,
+ 0,
+ NULL },
+
+ { ngx_string("listen"),
+ NGX_MAIL_SRV_CONF|NGX_CONF_TAKE12,
+ ngx_mail_core_listen,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("protocol"),
+ NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_mail_core_protocol,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("so_keepalive"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_core_srv_conf_t, so_keepalive),
+ NULL },
+
+ { ngx_string("timeout"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_core_srv_conf_t, timeout),
+ NULL },
+
+ { ngx_string("server_name"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_core_srv_conf_t, server_name),
+ NULL },
+
+ { ngx_string("resolver"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_mail_core_resolver,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("resolver_timeout"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_core_srv_conf_t, resolver_timeout),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_mail_module_t ngx_mail_core_module_ctx = {
+ NULL, /* protocol */
+
+ ngx_mail_core_create_main_conf, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_mail_core_create_srv_conf, /* create server configuration */
+ ngx_mail_core_merge_srv_conf /* merge server configuration */
+};
+
+
+ngx_module_t ngx_mail_core_module = {
+ NGX_MODULE_V1,
+ &ngx_mail_core_module_ctx, /* module context */
+ ngx_mail_core_commands, /* module directives */
+ NGX_MAIL_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static void *
+ngx_mail_core_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_mail_core_main_conf_t *cmcf;
+
+ cmcf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_core_main_conf_t));
+ if (cmcf == NULL) {
+ return NULL;
+ }
+
+ if (ngx_array_init(&cmcf->servers, cf->pool, 4,
+ sizeof(ngx_mail_core_srv_conf_t *))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ if (ngx_array_init(&cmcf->listen, cf->pool, 4, sizeof(ngx_mail_listen_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ return cmcf;
+}
+
+
+static void *
+ngx_mail_core_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_mail_core_srv_conf_t *cscf;
+
+ cscf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_core_srv_conf_t));
+ if (cscf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * cscf->protocol = NULL;
+ */
+
+ cscf->timeout = NGX_CONF_UNSET_MSEC;
+ cscf->resolver_timeout = NGX_CONF_UNSET_MSEC;
+ cscf->so_keepalive = NGX_CONF_UNSET;
+
+ cscf->resolver = NGX_CONF_UNSET_PTR;
+
+ cscf->file_name = cf->conf_file->file.name.data;
+ cscf->line = cf->conf_file->line;
+
+ return cscf;
+}
+
+
+static char *
+ngx_mail_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_mail_core_srv_conf_t *prev = parent;
+ ngx_mail_core_srv_conf_t *conf = child;
+
+ ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000);
+ ngx_conf_merge_msec_value(conf->resolver_timeout, prev->resolver_timeout,
+ 30000);
+
+ ngx_conf_merge_value(conf->so_keepalive, prev->so_keepalive, 0);
+
+
+ ngx_conf_merge_str_value(conf->server_name, prev->server_name, "");
+
+ if (conf->server_name.len == 0) {
+ conf->server_name = cf->cycle->hostname;
+ }
+
+ if (conf->protocol == NULL) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "unknown mail protocol for server in %s:%ui",
+ conf->file_name, conf->line);
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_ptr_value(conf->resolver, prev->resolver, NULL);
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *rv;
+ void *mconf;
+ ngx_uint_t m;
+ ngx_conf_t pcf;
+ ngx_mail_module_t *module;
+ ngx_mail_conf_ctx_t *ctx, *mail_ctx;
+ ngx_mail_core_srv_conf_t *cscf, **cscfp;
+ ngx_mail_core_main_conf_t *cmcf;
+
+ ctx = ngx_pcalloc(cf->pool, sizeof(ngx_mail_conf_ctx_t));
+ if (ctx == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ mail_ctx = cf->ctx;
+ ctx->main_conf = mail_ctx->main_conf;
+
+ /* the server{}'s srv_conf */
+
+ ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_mail_max_module);
+ if (ctx->srv_conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_MAIL_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;
+ }
+ }
+
+ /* the server configuration context */
+
+ cscf = ctx->srv_conf[ngx_mail_core_module.ctx_index];
+ cscf->ctx = ctx;
+
+ cmcf = ctx->main_conf[ngx_mail_core_module.ctx_index];
+
+ cscfp = ngx_array_push(&cmcf->servers);
+ if (cscfp == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *cscfp = cscf;
+
+
+ /* parse inside server{} */
+
+ pcf = *cf;
+ cf->ctx = ctx;
+ cf->cmd_type = NGX_MAIL_SRV_CONF;
+
+ rv = ngx_conf_parse(cf, NULL);
+
+ *cf = pcf;
+
+ return rv;
+}
+
+
+static char *
+ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_mail_core_srv_conf_t *cscf = conf;
+
+ size_t len, off;
+ in_port_t port;
+ ngx_str_t *value;
+ ngx_url_t u;
+ ngx_uint_t i, m;
+ struct sockaddr *sa;
+ ngx_mail_listen_t *ls;
+ ngx_mail_module_t *module;
+ struct sockaddr_in *sin;
+ ngx_mail_core_main_conf_t *cmcf;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+#endif
+
+ value = cf->args->elts;
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.url = value[1];
+ u.listen = 1;
+
+ if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+ if (u.err) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "%s in \"%V\" of the \"listen\" directive",
+ u.err, &u.url);
+ }
+
+ return NGX_CONF_ERROR;
+ }
+
+ cmcf = ngx_mail_conf_get_module_main_conf(cf, ngx_mail_core_module);
+
+ ls = cmcf->listen.elts;
+
+ for (i = 0; i < cmcf->listen.nelts; i++) {
+
+ sa = (struct sockaddr *) ls[i].sockaddr;
+
+ if (sa->sa_family != u.family) {
+ continue;
+ }
+
+ switch (sa->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ off = offsetof(struct sockaddr_in6, sin6_addr);
+ len = 16;
+ sin6 = (struct sockaddr_in6 *) sa;
+ port = sin6->sin6_port;
+ break;
+#endif
+
+ default: /* AF_INET */
+ off = offsetof(struct sockaddr_in, sin_addr);
+ len = 4;
+ sin = (struct sockaddr_in *) sa;
+ port = sin->sin_port;
+ break;
+ }
+
+ if (ngx_memcmp(ls[i].sockaddr + off, u.sockaddr + off, len) != 0) {
+ continue;
+ }
+
+ if (port != u.port) {
+ continue;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "duplicate \"%V\" address and port pair", &u.url);
+ return NGX_CONF_ERROR;
+ }
+
+ ls = ngx_array_push(&cmcf->listen);
+ if (ls == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(ls, sizeof(ngx_mail_listen_t));
+
+ ngx_memcpy(ls->sockaddr, u.sockaddr, u.socklen);
+
+ ls->socklen = u.socklen;
+ ls->wildcard = u.wildcard;
+ ls->ctx = cf->ctx;
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_MAIL_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+
+ if (module->protocol == NULL) {
+ continue;
+ }
+
+ for (i = 0; module->protocol->port[i]; i++) {
+ if (module->protocol->port[i] == u.port) {
+ cscf->protocol = module->protocol;
+ break;
+ }
+ }
+ }
+
+ for (i = 2; i < cf->args->nelts; i++) {
+
+ if (ngx_strcmp(value[i].data, "bind") == 0) {
+ ls->bind = 1;
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "ipv6only=o", 10) == 0) {
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+ struct sockaddr *sa;
+ u_char buf[NGX_SOCKADDR_STRLEN];
+
+ sa = (struct sockaddr *) ls->sockaddr;
+
+ if (sa->sa_family == AF_INET6) {
+
+ if (ngx_strcmp(&value[i].data[10], "n") == 0) {
+ ls->ipv6only = 1;
+
+ } else if (ngx_strcmp(&value[i].data[10], "ff") == 0) {
+ ls->ipv6only = 2;
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid ipv6only flags \"%s\"",
+ &value[i].data[9]);
+ return NGX_CONF_ERROR;
+ }
+
+ ls->bind = 1;
+
+ } else {
+ len = ngx_sock_ntop(sa, buf, NGX_SOCKADDR_STRLEN, 1);
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "ipv6only is not supported "
+ "on addr \"%*s\", ignored", len, buf);
+ }
+
+ continue;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "bind ipv6only is not supported "
+ "on this platform");
+ return NGX_CONF_ERROR;
+#endif
+ }
+
+ if (ngx_strcmp(value[i].data, "ssl") == 0) {
+#if (NGX_MAIL_SSL)
+ ls->ssl = 1;
+ continue;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the \"ssl\" parameter requires "
+ "ngx_mail_ssl_module");
+ return NGX_CONF_ERROR;
+#endif
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the invalid \"%V\" parameter", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_core_protocol(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_mail_core_srv_conf_t *cscf = conf;
+
+ ngx_str_t *value;
+ ngx_uint_t m;
+ ngx_mail_module_t *module;
+
+ value = cf->args->elts;
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_MAIL_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+
+ if (module->protocol
+ && ngx_strcmp(module->protocol->name.data, value[1].data) == 0)
+ {
+ cscf->protocol = module->protocol;
+
+ return NGX_CONF_OK;
+ }
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "unknown protocol \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_mail_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_mail_core_srv_conf_t *cscf = conf;
+
+ ngx_url_t u;
+ ngx_str_t *value;
+
+ value = cf->args->elts;
+
+ if (cscf->resolver != NGX_CONF_UNSET_PTR) {
+ return "is duplicate";
+ }
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ cscf->resolver = NULL;
+ return NGX_CONF_OK;
+ }
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.host = value[1];
+ u.port = 53;
+
+ if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V: %s", &u.host, u.err);
+ return NGX_CONF_ERROR;
+ }
+
+ cscf->resolver = ngx_resolver_create(cf, &u.addrs[0]);
+ if (cscf->resolver == NULL) {
+ return NGX_CONF_OK;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+char *
+ngx_mail_capabilities(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *p = conf;
+
+ ngx_str_t *c, *value;
+ ngx_uint_t i;
+ ngx_array_t *a;
+
+ a = (ngx_array_t *) (p + cmd->offset);
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+ c = ngx_array_push(a);
+ if (c == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *c = value[i];
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/mail/ngx_mail_handler.c b/usr.sbin/nginx/src/mail/ngx_mail_handler.c
new file mode 100644
index 00000000000..0e37daf4879
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail_handler.c
@@ -0,0 +1,772 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+
+
+static void ngx_mail_init_session(ngx_connection_t *c);
+
+#if (NGX_MAIL_SSL)
+static void ngx_mail_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c);
+static void ngx_mail_ssl_handshake_handler(ngx_connection_t *c);
+#endif
+
+
+void
+ngx_mail_init_connection(ngx_connection_t *c)
+{
+ ngx_uint_t i;
+ ngx_mail_port_t *port;
+ struct sockaddr *sa;
+ struct sockaddr_in *sin;
+ ngx_mail_log_ctx_t *ctx;
+ ngx_mail_in_addr_t *addr;
+ ngx_mail_session_t *s;
+ ngx_mail_addr_conf_t *addr_conf;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+ ngx_mail_in6_addr_t *addr6;
+#endif
+
+
+ /* find the server configuration for the address:port */
+
+ /* AF_INET only */
+
+ port = c->listening->servers;
+
+ if (port->naddrs > 1) {
+
+ /*
+ * There are several addresses on this port and one of them
+ * is the "*:port" wildcard so getsockname() is needed to determine
+ * the server address.
+ *
+ * AcceptEx() already gave this address.
+ */
+
+ if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ sa = c->local_sockaddr;
+
+ switch (sa->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) sa;
+
+ addr6 = port->addrs;
+
+ /* the last address is "*" */
+
+ for (i = 0; i < port->naddrs - 1; i++) {
+ if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) {
+ break;
+ }
+ }
+
+ addr_conf = &addr6[i].conf;
+
+ break;
+#endif
+
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) sa;
+
+ addr = port->addrs;
+
+ /* the last address is "*" */
+
+ for (i = 0; i < port->naddrs - 1; i++) {
+ if (addr[i].addr == sin->sin_addr.s_addr) {
+ break;
+ }
+ }
+
+ addr_conf = &addr[i].conf;
+
+ break;
+ }
+
+ } else {
+ switch (c->local_sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ addr6 = port->addrs;
+ addr_conf = &addr6[0].conf;
+ break;
+#endif
+
+ default: /* AF_INET */
+ addr = port->addrs;
+ addr_conf = &addr[0].conf;
+ break;
+ }
+ }
+
+ s = ngx_pcalloc(c->pool, sizeof(ngx_mail_session_t));
+ if (s == NULL) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ s->main_conf = addr_conf->ctx->main_conf;
+ s->srv_conf = addr_conf->ctx->srv_conf;
+
+ s->addr_text = &addr_conf->addr_text;
+
+ c->data = s;
+ s->connection = c;
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "*%ui client %V connected to %V",
+ c->number, &c->addr_text, s->addr_text);
+
+ ctx = ngx_palloc(c->pool, sizeof(ngx_mail_log_ctx_t));
+ if (ctx == NULL) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ ctx->client = &c->addr_text;
+ ctx->session = s;
+
+ c->log->connection = c->number;
+ c->log->handler = ngx_mail_log_error;
+ c->log->data = ctx;
+ c->log->action = "sending client greeting line";
+
+ c->log_error = NGX_ERROR_INFO;
+
+#if (NGX_MAIL_SSL)
+ {
+ ngx_mail_ssl_conf_t *sslcf;
+
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+ if (sslcf->enable) {
+ c->log->action = "SSL handshaking";
+
+ ngx_mail_ssl_init_connection(&sslcf->ssl, c);
+ return;
+ }
+
+ if (addr_conf->ssl) {
+
+ c->log->action = "SSL handshaking";
+
+ if (sslcf->ssl.ctx == NULL) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "no \"ssl_certificate\" is defined "
+ "in server listening on SSL port");
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ ngx_mail_ssl_init_connection(&sslcf->ssl, c);
+ return;
+ }
+
+ }
+#endif
+
+ ngx_mail_init_session(c);
+}
+
+
+#if (NGX_MAIL_SSL)
+
+void
+ngx_mail_starttls_handler(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_ssl_conf_t *sslcf;
+
+ c = rev->data;
+ s = c->data;
+ s->starttls = 1;
+
+ c->log->action = "in starttls state";
+
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+ ngx_mail_ssl_init_connection(&sslcf->ssl, c);
+}
+
+
+static void
+ngx_mail_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c)
+{
+ ngx_mail_session_t *s;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ if (ngx_ssl_create_connection(ssl, c, 0) == NGX_ERROR) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ if (ngx_ssl_handshake(c) == NGX_AGAIN) {
+
+ s = c->data;
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ ngx_add_timer(c->read, cscf->timeout);
+
+ c->ssl->handler = ngx_mail_ssl_handshake_handler;
+
+ return;
+ }
+
+ ngx_mail_ssl_handshake_handler(c);
+}
+
+
+static void
+ngx_mail_ssl_handshake_handler(ngx_connection_t *c)
+{
+ ngx_mail_session_t *s;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ if (c->ssl->handshaked) {
+
+ s = c->data;
+
+ if (s->starttls) {
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ c->read->handler = cscf->protocol->init_protocol;
+ c->write->handler = ngx_mail_send;
+
+ cscf->protocol->init_protocol(c->read);
+
+ return;
+ }
+
+ c->read->ready = 0;
+
+ ngx_mail_init_session(c);
+ return;
+ }
+
+ ngx_mail_close_connection(c);
+}
+
+#endif
+
+
+static void
+ngx_mail_init_session(ngx_connection_t *c)
+{
+ ngx_mail_session_t *s;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ s = c->data;
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ s->protocol = cscf->protocol->type;
+
+ s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_mail_max_module);
+ if (s->ctx == NULL) {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ c->write->handler = ngx_mail_send;
+
+ cscf->protocol->init_session(s, c);
+}
+
+
+ngx_int_t
+ngx_mail_salt(ngx_mail_session_t *s, ngx_connection_t *c,
+ ngx_mail_core_srv_conf_t *cscf)
+{
+ s->salt.data = ngx_pnalloc(c->pool,
+ sizeof(" <18446744073709551616.@>" CRLF) - 1
+ + NGX_TIME_T_LEN
+ + cscf->server_name.len);
+ if (s->salt.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ s->salt.len = ngx_sprintf(s->salt.data, "<%ul.%T@%V>" CRLF,
+ ngx_random(), ngx_time(), &cscf->server_name)
+ - s->salt.data;
+
+ return NGX_OK;
+}
+
+
+#if (NGX_MAIL_SSL)
+
+ngx_int_t
+ngx_mail_starttls_only(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_mail_ssl_conf_t *sslcf;
+
+ if (c->ssl) {
+ return 0;
+ }
+
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+ if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
+ return 1;
+ }
+
+ return 0;
+}
+
+#endif
+
+
+ngx_int_t
+ngx_mail_auth_plain(ngx_mail_session_t *s, ngx_connection_t *c, ngx_uint_t n)
+{
+ u_char *p, *last;
+ ngx_str_t *arg, plain;
+
+ arg = s->args.elts;
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth plain: \"%V\"", &arg[n]);
+#endif
+
+ plain.data = ngx_pnalloc(c->pool, ngx_base64_decoded_length(arg[n].len));
+ if (plain.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_decode_base64(&plain, &arg[n]) != NGX_OK) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid base64 encoding in AUTH PLAIN command");
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ p = plain.data;
+ last = p + plain.len;
+
+ while (p < last && *p++) { /* void */ }
+
+ if (p == last) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid login in AUTH PLAIN command");
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ s->login.data = p;
+
+ while (p < last && *p) { p++; }
+
+ if (p == last) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid password in AUTH PLAIN command");
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ s->login.len = p++ - s->login.data;
+
+ s->passwd.len = last - p;
+ s->passwd.data = p;
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+ ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth plain: \"%V\" \"%V\"", &s->login, &s->passwd);
+#endif
+
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_mail_auth_login_username(ngx_mail_session_t *s, ngx_connection_t *c,
+ ngx_uint_t n)
+{
+ ngx_str_t *arg;
+
+ arg = s->args.elts;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth login username: \"%V\"", &arg[n]);
+
+ s->login.data = ngx_pnalloc(c->pool, ngx_base64_decoded_length(arg[n].len));
+ if (s->login.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_decode_base64(&s->login, &arg[n]) != NGX_OK) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid base64 encoding in AUTH LOGIN command");
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth login username: \"%V\"", &s->login);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_mail_auth_login_password(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_str_t *arg;
+
+ arg = s->args.elts;
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth login password: \"%V\"", &arg[0]);
+#endif
+
+ s->passwd.data = ngx_pnalloc(c->pool,
+ ngx_base64_decoded_length(arg[0].len));
+ if (s->passwd.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_decode_base64(&s->passwd, &arg[0]) != NGX_OK) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid base64 encoding in AUTH LOGIN command");
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth login password: \"%V\"", &s->passwd);
+#endif
+
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_mail_auth_cram_md5_salt(ngx_mail_session_t *s, ngx_connection_t *c,
+ char *prefix, size_t len)
+{
+ u_char *p;
+ ngx_str_t salt;
+ ngx_uint_t n;
+
+ p = ngx_pnalloc(c->pool, len + ngx_base64_encoded_length(s->salt.len) + 2);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ salt.data = ngx_cpymem(p, prefix, len);
+ s->salt.len -= 2;
+
+ ngx_encode_base64(&salt, &s->salt);
+
+ s->salt.len += 2;
+ n = len + salt.len;
+ p[n++] = CR; p[n++] = LF;
+
+ s->out.len = n;
+ s->out.data = p;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_mail_auth_cram_md5(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ u_char *p, *last;
+ ngx_str_t *arg;
+
+ arg = s->args.elts;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth cram-md5: \"%V\"", &arg[0]);
+
+ s->login.data = ngx_pnalloc(c->pool, ngx_base64_decoded_length(arg[0].len));
+ if (s->login.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_decode_base64(&s->login, &arg[0]) != NGX_OK) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid base64 encoding in AUTH CRAM-MD5 command");
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ p = s->login.data;
+ last = p + s->login.len;
+
+ while (p < last) {
+ if (*p++ == ' ') {
+ s->login.len = p - s->login.data - 1;
+ s->passwd.len = last - p;
+ s->passwd.data = p;
+ break;
+ }
+ }
+
+ if (s->passwd.len != 32) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid CRAM-MD5 hash in AUTH CRAM-MD5 command");
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth cram-md5: \"%V\" \"%V\"", &s->login, &s->passwd);
+
+ s->auth_method = NGX_MAIL_AUTH_CRAM_MD5;
+
+ return NGX_DONE;
+}
+
+
+void
+ngx_mail_send(ngx_event_t *wev)
+{
+ ngx_int_t n;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ c = wev->data;
+ s = c->data;
+
+ if (wev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ if (s->out.len == 0) {
+ if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ }
+
+ return;
+ }
+
+ n = c->send(c, s->out.data, s->out.len);
+
+ if (n > 0) {
+ s->out.len -= n;
+
+ if (wev->timer_set) {
+ ngx_del_timer(wev);
+ }
+
+ if (s->quit) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ if (s->blocked) {
+ c->read->handler(c->read);
+ }
+
+ return;
+ }
+
+ if (n == NGX_ERROR) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ /* n == NGX_AGAIN */
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ ngx_add_timer(c->write, cscf->timeout);
+
+ if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+}
+
+
+ngx_int_t
+ngx_mail_read_command(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ssize_t n;
+ ngx_int_t rc;
+ ngx_str_t l;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ n = c->recv(c, s->buffer->last, s->buffer->end - s->buffer->last);
+
+ if (n == NGX_ERROR || n == 0) {
+ ngx_mail_close_connection(c);
+ return NGX_ERROR;
+ }
+
+ if (n > 0) {
+ s->buffer->last += n;
+ }
+
+ if (n == NGX_AGAIN) {
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ ngx_mail_session_internal_server_error(s);
+ return NGX_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ rc = cscf->protocol->parse_command(s);
+
+ if (rc == NGX_AGAIN) {
+
+ if (s->buffer->last < s->buffer->end) {
+ return rc;
+ }
+
+ l.len = s->buffer->last - s->buffer->start;
+ l.data = s->buffer->start;
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent too long command \"%V\"", &l);
+
+ s->quit = 1;
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ if (rc == NGX_IMAP_NEXT || rc == NGX_MAIL_PARSE_INVALID_COMMAND) {
+ return rc;
+ }
+
+ if (rc == NGX_ERROR) {
+ ngx_mail_close_connection(c);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+void
+ngx_mail_auth(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ s->args.nelts = 0;
+ s->buffer->pos = s->buffer->start;
+ s->buffer->last = s->buffer->start;
+ s->state = 0;
+
+ if (c->read->timer_set) {
+ ngx_del_timer(c->read);
+ }
+
+ s->login_attempt++;
+
+ ngx_mail_auth_http_init(s);
+}
+
+
+void
+ngx_mail_session_internal_server_error(ngx_mail_session_t *s)
+{
+ ngx_mail_core_srv_conf_t *cscf;
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ s->out = cscf->protocol->internal_server_error;
+ s->quit = 1;
+
+ ngx_mail_send(s->connection->write);
+}
+
+
+void
+ngx_mail_close_connection(ngx_connection_t *c)
+{
+ ngx_pool_t *pool;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "close mail connection: %d", c->fd);
+
+#if (NGX_MAIL_SSL)
+
+ if (c->ssl) {
+ if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
+ c->ssl->handler = ngx_mail_close_connection;
+ return;
+ }
+ }
+
+#endif
+
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
+#endif
+
+ c->destroyed = 1;
+
+ pool = c->pool;
+
+ ngx_close_connection(c);
+
+ ngx_destroy_pool(pool);
+}
+
+
+u_char *
+ngx_mail_log_error(ngx_log_t *log, u_char *buf, size_t len)
+{
+ u_char *p;
+ ngx_mail_session_t *s;
+ ngx_mail_log_ctx_t *ctx;
+
+ if (log->action) {
+ p = ngx_snprintf(buf, len, " while %s", log->action);
+ len -= p - buf;
+ buf = p;
+ }
+
+ ctx = log->data;
+
+ p = ngx_snprintf(buf, len, ", client: %V", ctx->client);
+ len -= p - buf;
+ buf = p;
+
+ s = ctx->session;
+
+ if (s == NULL) {
+ return p;
+ }
+
+ p = ngx_snprintf(buf, len, "%s, server: %V",
+ s->starttls ? " using starttls" : "",
+ s->addr_text);
+ len -= p - buf;
+ buf = p;
+
+ if (s->login.len == 0) {
+ return p;
+ }
+
+ p = ngx_snprintf(buf, len, ", login: \"%V\"", &s->login);
+ len -= p - buf;
+ buf = p;
+
+ if (s->proxy == NULL) {
+ return p;
+ }
+
+ p = ngx_snprintf(buf, len, ", upstream: %V", s->proxy->upstream.name);
+
+ return p;
+}
diff --git a/usr.sbin/nginx/src/mail/ngx_mail_imap_handler.c b/usr.sbin/nginx/src/mail/ngx_mail_imap_handler.c
new file mode 100644
index 00000000000..f15dbec805e
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail_imap_handler.c
@@ -0,0 +1,456 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_imap_module.h>
+
+
+static ngx_int_t ngx_mail_imap_login(ngx_mail_session_t *s,
+ ngx_connection_t *c);
+static ngx_int_t ngx_mail_imap_authenticate(ngx_mail_session_t *s,
+ ngx_connection_t *c);
+static ngx_int_t ngx_mail_imap_capability(ngx_mail_session_t *s,
+ ngx_connection_t *c);
+static ngx_int_t ngx_mail_imap_starttls(ngx_mail_session_t *s,
+ ngx_connection_t *c);
+
+
+static u_char imap_greeting[] = "* OK IMAP4 ready" CRLF;
+static u_char imap_star[] = "* ";
+static u_char imap_ok[] = "OK completed" CRLF;
+static u_char imap_next[] = "+ OK" CRLF;
+static u_char imap_plain_next[] = "+ " CRLF;
+static u_char imap_username[] = "+ VXNlcm5hbWU6" CRLF;
+static u_char imap_password[] = "+ UGFzc3dvcmQ6" CRLF;
+static u_char imap_bye[] = "* BYE" CRLF;
+static u_char imap_invalid_command[] = "BAD invalid command" CRLF;
+
+
+void
+ngx_mail_imap_init_session(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_mail_core_srv_conf_t *cscf;
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ ngx_str_set(&s->out, imap_greeting);
+
+ c->read->handler = ngx_mail_imap_init_protocol;
+
+ ngx_add_timer(c->read, cscf->timeout);
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ }
+
+ ngx_mail_send(c->write);
+}
+
+
+void
+ngx_mail_imap_init_protocol(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_imap_srv_conf_t *iscf;
+
+ c = rev->data;
+
+ c->log->action = "in auth state";
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ s = c->data;
+
+ if (s->buffer == NULL) {
+ if (ngx_array_init(&s->args, c->pool, 2, sizeof(ngx_str_t))
+ == NGX_ERROR)
+ {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ iscf = ngx_mail_get_module_srv_conf(s, ngx_mail_imap_module);
+
+ s->buffer = ngx_create_temp_buf(c->pool, iscf->client_buffer_size);
+ if (s->buffer == NULL) {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+ }
+
+ s->mail_state = ngx_imap_start;
+ c->read->handler = ngx_mail_imap_auth_state;
+
+ ngx_mail_imap_auth_state(rev);
+}
+
+
+void
+ngx_mail_imap_auth_state(ngx_event_t *rev)
+{
+ u_char *p, *dst, *src, *end;
+ ngx_str_t *arg;
+ ngx_int_t rc;
+ ngx_uint_t tag, i;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+
+ c = rev->data;
+ s = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "imap auth state");
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ if (s->out.len) {
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "imap send handler busy");
+ s->blocked = 1;
+ return;
+ }
+
+ s->blocked = 0;
+
+ rc = ngx_mail_read_command(s, c);
+
+ if (rc == NGX_AGAIN || rc == NGX_ERROR) {
+ return;
+ }
+
+ tag = 1;
+ s->text.len = 0;
+ ngx_str_set(&s->out, imap_ok);
+
+ if (rc == NGX_OK) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0, "imap auth command: %i",
+ s->command);
+
+ if (s->backslash) {
+
+ arg = s->args.elts;
+
+ for (i = 0; i < s->args.nelts; i++) {
+ dst = arg[i].data;
+ end = dst + arg[i].len;
+
+ for (src = dst; src < end; dst++) {
+ *dst = *src;
+ if (*src++ == '\\') {
+ *dst = *src++;
+ }
+ }
+
+ arg[i].len = dst - arg[i].data;
+ }
+
+ s->backslash = 0;
+ }
+
+ switch (s->mail_state) {
+
+ case ngx_imap_start:
+
+ switch (s->command) {
+
+ case NGX_IMAP_LOGIN:
+ rc = ngx_mail_imap_login(s, c);
+ break;
+
+ case NGX_IMAP_AUTHENTICATE:
+ rc = ngx_mail_imap_authenticate(s, c);
+ tag = (rc != NGX_OK);
+ break;
+
+ case NGX_IMAP_CAPABILITY:
+ rc = ngx_mail_imap_capability(s, c);
+ break;
+
+ case NGX_IMAP_LOGOUT:
+ s->quit = 1;
+ ngx_str_set(&s->text, imap_bye);
+ break;
+
+ case NGX_IMAP_NOOP:
+ break;
+
+ case NGX_IMAP_STARTTLS:
+ rc = ngx_mail_imap_starttls(s, c);
+ break;
+
+ default:
+ rc = NGX_MAIL_PARSE_INVALID_COMMAND;
+ break;
+ }
+
+ break;
+
+ case ngx_imap_auth_login_username:
+ rc = ngx_mail_auth_login_username(s, c, 0);
+
+ tag = 0;
+ ngx_str_set(&s->out, imap_password);
+ s->mail_state = ngx_imap_auth_login_password;
+
+ break;
+
+ case ngx_imap_auth_login_password:
+ rc = ngx_mail_auth_login_password(s, c);
+ break;
+
+ case ngx_imap_auth_plain:
+ rc = ngx_mail_auth_plain(s, c, 0);
+ break;
+
+ case ngx_imap_auth_cram_md5:
+ rc = ngx_mail_auth_cram_md5(s, c);
+ break;
+ }
+
+ } else if (rc == NGX_IMAP_NEXT) {
+ tag = 0;
+ ngx_str_set(&s->out, imap_next);
+ }
+
+ switch (rc) {
+
+ case NGX_DONE:
+ ngx_mail_auth(s, c);
+ return;
+
+ case NGX_ERROR:
+ ngx_mail_session_internal_server_error(s);
+ return;
+
+ case NGX_MAIL_PARSE_INVALID_COMMAND:
+ s->state = 0;
+ ngx_str_set(&s->out, imap_invalid_command);
+ s->mail_state = ngx_imap_start;
+ break;
+ }
+
+ if (tag) {
+ if (s->tag.len == 0) {
+ ngx_str_set(&s->tag, imap_star);
+ }
+
+ if (s->tagged_line.len < s->tag.len + s->text.len + s->out.len) {
+ s->tagged_line.len = s->tag.len + s->text.len + s->out.len;
+ s->tagged_line.data = ngx_pnalloc(c->pool, s->tagged_line.len);
+ if (s->tagged_line.data == NULL) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+ }
+
+ p = s->tagged_line.data;
+
+ if (s->text.len) {
+ p = ngx_cpymem(p, s->text.data, s->text.len);
+ }
+
+ p = ngx_cpymem(p, s->tag.data, s->tag.len);
+ ngx_memcpy(p, s->out.data, s->out.len);
+
+ s->out.len = s->text.len + s->tag.len + s->out.len;
+ s->out.data = s->tagged_line.data;
+ }
+
+ if (rc != NGX_IMAP_NEXT) {
+ s->args.nelts = 0;
+
+ if (s->state) {
+ /* preserve tag */
+ s->arg_start = s->buffer->start + s->tag.len;
+ s->buffer->pos = s->arg_start;
+ s->buffer->last = s->arg_start;
+
+ } else {
+ s->buffer->pos = s->buffer->start;
+ s->buffer->last = s->buffer->start;
+ s->tag.len = 0;
+ }
+ }
+
+ ngx_mail_send(c->write);
+}
+
+
+static ngx_int_t
+ngx_mail_imap_login(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_str_t *arg;
+
+#if (NGX_MAIL_SSL)
+ if (ngx_mail_starttls_only(s, c)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+#endif
+
+ arg = s->args.elts;
+
+ if (s->args.nelts != 2 || arg[0].len == 0) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ s->login.len = arg[0].len;
+ s->login.data = ngx_pnalloc(c->pool, s->login.len);
+ if (s->login.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->login.data, arg[0].data, s->login.len);
+
+ s->passwd.len = arg[1].len;
+ s->passwd.data = ngx_pnalloc(c->pool, s->passwd.len);
+ if (s->passwd.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->passwd.data, arg[1].data, s->passwd.len);
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+ ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "imap login:\"%V\" passwd:\"%V\"",
+ &s->login, &s->passwd);
+#else
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "imap login:\"%V\"", &s->login);
+#endif
+
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_mail_imap_authenticate(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_int_t rc;
+ ngx_mail_core_srv_conf_t *cscf;
+ ngx_mail_imap_srv_conf_t *iscf;
+
+#if (NGX_MAIL_SSL)
+ if (ngx_mail_starttls_only(s, c)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+#endif
+
+ rc = ngx_mail_auth_parse(s, c);
+
+ switch (rc) {
+
+ case NGX_MAIL_AUTH_LOGIN:
+
+ ngx_str_set(&s->out, imap_username);
+ s->mail_state = ngx_imap_auth_login_username;
+
+ return NGX_OK;
+
+ case NGX_MAIL_AUTH_LOGIN_USERNAME:
+
+ ngx_str_set(&s->out, imap_password);
+ s->mail_state = ngx_imap_auth_login_password;
+
+ return ngx_mail_auth_login_username(s, c, 1);
+
+ case NGX_MAIL_AUTH_PLAIN:
+
+ ngx_str_set(&s->out, imap_plain_next);
+ s->mail_state = ngx_imap_auth_plain;
+
+ return NGX_OK;
+
+ case NGX_MAIL_AUTH_CRAM_MD5:
+
+ iscf = ngx_mail_get_module_srv_conf(s, ngx_mail_imap_module);
+
+ if (!(iscf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ if (s->salt.data == NULL) {
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ if (ngx_mail_salt(s, c, cscf) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ if (ngx_mail_auth_cram_md5_salt(s, c, "+ ", 2) == NGX_OK) {
+ s->mail_state = ngx_imap_auth_cram_md5;
+ return NGX_OK;
+ }
+
+ return NGX_ERROR;
+ }
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_mail_imap_capability(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_mail_imap_srv_conf_t *iscf;
+
+ iscf = ngx_mail_get_module_srv_conf(s, ngx_mail_imap_module);
+
+#if (NGX_MAIL_SSL)
+
+ if (c->ssl == NULL) {
+ ngx_mail_ssl_conf_t *sslcf;
+
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+ if (sslcf->starttls == NGX_MAIL_STARTTLS_ON) {
+ s->text = iscf->starttls_capability;
+ return NGX_OK;
+ }
+
+ if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
+ s->text = iscf->starttls_only_capability;
+ return NGX_OK;
+ }
+ }
+#endif
+
+ s->text = iscf->capability;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_imap_starttls(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+#if (NGX_MAIL_SSL)
+ ngx_mail_ssl_conf_t *sslcf;
+
+ if (c->ssl == NULL) {
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+ if (sslcf->starttls) {
+ c->read->handler = ngx_mail_starttls_handler;
+ return NGX_OK;
+ }
+ }
+
+#endif
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
diff --git a/usr.sbin/nginx/src/mail/ngx_mail_imap_module.c b/usr.sbin/nginx/src/mail/ngx_mail_imap_module.c
new file mode 100644
index 00000000000..daf2a433ff8
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail_imap_module.c
@@ -0,0 +1,252 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_imap_module.h>
+
+
+static void *ngx_mail_imap_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_mail_imap_merge_srv_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+
+
+static ngx_str_t ngx_mail_imap_default_capabilities[] = {
+ ngx_string("IMAP4"),
+ ngx_string("IMAP4rev1"),
+ ngx_string("UIDPLUS"),
+ ngx_null_string
+};
+
+
+static ngx_conf_bitmask_t ngx_mail_imap_auth_methods[] = {
+ { ngx_string("plain"), NGX_MAIL_AUTH_PLAIN_ENABLED },
+ { ngx_string("login"), NGX_MAIL_AUTH_LOGIN_ENABLED },
+ { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_str_t ngx_mail_imap_auth_methods_names[] = {
+ ngx_string("AUTH=PLAIN"),
+ ngx_string("AUTH=LOGIN"),
+ ngx_null_string, /* APOP */
+ ngx_string("AUTH=CRAM-MD5"),
+ ngx_null_string /* NONE */
+};
+
+
+static ngx_mail_protocol_t ngx_mail_imap_protocol = {
+ ngx_string("imap"),
+ { 143, 993, 0, 0 },
+ NGX_MAIL_IMAP_PROTOCOL,
+
+ ngx_mail_imap_init_session,
+ ngx_mail_imap_init_protocol,
+ ngx_mail_imap_parse_command,
+ ngx_mail_imap_auth_state,
+
+ ngx_string("* BAD internal server error" CRLF)
+};
+
+
+static ngx_command_t ngx_mail_imap_commands[] = {
+
+ { ngx_string("imap_client_buffer"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_imap_srv_conf_t, client_buffer_size),
+ NULL },
+
+ { ngx_string("imap_capabilities"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+ ngx_mail_capabilities,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_imap_srv_conf_t, capabilities),
+ NULL },
+
+ { ngx_string("imap_auth"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_imap_srv_conf_t, auth_methods),
+ &ngx_mail_imap_auth_methods },
+
+ ngx_null_command
+};
+
+
+static ngx_mail_module_t ngx_mail_imap_module_ctx = {
+ &ngx_mail_imap_protocol, /* protocol */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_mail_imap_create_srv_conf, /* create server configuration */
+ ngx_mail_imap_merge_srv_conf /* merge server configuration */
+};
+
+
+ngx_module_t ngx_mail_imap_module = {
+ NGX_MODULE_V1,
+ &ngx_mail_imap_module_ctx, /* module context */
+ ngx_mail_imap_commands, /* module directives */
+ NGX_MAIL_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static void *
+ngx_mail_imap_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_mail_imap_srv_conf_t *iscf;
+
+ iscf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_imap_srv_conf_t));
+ if (iscf == NULL) {
+ return NULL;
+ }
+
+ iscf->client_buffer_size = NGX_CONF_UNSET_SIZE;
+
+ if (ngx_array_init(&iscf->capabilities, cf->pool, 4, sizeof(ngx_str_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ return iscf;
+}
+
+
+static char *
+ngx_mail_imap_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_mail_imap_srv_conf_t *prev = parent;
+ ngx_mail_imap_srv_conf_t *conf = child;
+
+ u_char *p, *auth;
+ size_t size;
+ ngx_str_t *c, *d;
+ ngx_uint_t i, m;
+
+ ngx_conf_merge_size_value(conf->client_buffer_size,
+ prev->client_buffer_size,
+ (size_t) ngx_pagesize);
+
+ ngx_conf_merge_bitmask_value(conf->auth_methods,
+ prev->auth_methods,
+ (NGX_CONF_BITMASK_SET
+ |NGX_MAIL_AUTH_PLAIN_ENABLED));
+
+
+ if (conf->capabilities.nelts == 0) {
+ conf->capabilities = prev->capabilities;
+ }
+
+ if (conf->capabilities.nelts == 0) {
+
+ for (d = ngx_mail_imap_default_capabilities; d->len; d++) {
+ c = ngx_array_push(&conf->capabilities);
+ if (c == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *c = *d;
+ }
+ }
+
+ size = sizeof("* CAPABILITY" CRLF) - 1;
+
+ c = conf->capabilities.elts;
+ for (i = 0; i < conf->capabilities.nelts; i++) {
+ size += 1 + c[i].len;
+ }
+
+ for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+ m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED;
+ m <<= 1, i++)
+ {
+ if (m & conf->auth_methods) {
+ size += 1 + ngx_mail_imap_auth_methods_names[i].len;
+ }
+ }
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->capability.len = size;
+ conf->capability.data = p;
+
+ p = ngx_cpymem(p, "* CAPABILITY", sizeof("* CAPABILITY") - 1);
+
+ for (i = 0; i < conf->capabilities.nelts; i++) {
+ *p++ = ' ';
+ p = ngx_cpymem(p, c[i].data, c[i].len);
+ }
+
+ auth = p;
+
+ for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+ m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED;
+ m <<= 1, i++)
+ {
+ if (m & conf->auth_methods) {
+ *p++ = ' ';
+ p = ngx_cpymem(p, ngx_mail_imap_auth_methods_names[i].data,
+ ngx_mail_imap_auth_methods_names[i].len);
+ }
+ }
+
+ *p++ = CR; *p = LF;
+
+
+ size += sizeof(" STARTTLS") - 1;
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->starttls_capability.len = size;
+ conf->starttls_capability.data = p;
+
+ p = ngx_cpymem(p, conf->capability.data,
+ conf->capability.len - (sizeof(CRLF) - 1));
+ p = ngx_cpymem(p, " STARTTLS", sizeof(" STARTTLS") - 1);
+ *p++ = CR; *p = LF;
+
+
+ size = (auth - conf->capability.data) + sizeof(CRLF) - 1
+ + sizeof(" STARTTLS LOGINDISABLED") - 1;
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->starttls_only_capability.len = size;
+ conf->starttls_only_capability.data = p;
+
+ p = ngx_cpymem(p, conf->capability.data,
+ auth - conf->capability.data);
+ p = ngx_cpymem(p, " STARTTLS LOGINDISABLED",
+ sizeof(" STARTTLS LOGINDISABLED") - 1);
+ *p++ = CR; *p = LF;
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/mail/ngx_mail_imap_module.h b/usr.sbin/nginx/src/mail/ngx_mail_imap_module.h
new file mode 100644
index 00000000000..c206ce05a5a
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail_imap_module.h
@@ -0,0 +1,38 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_MAIL_IMAP_MODULE_H_INCLUDED_
+#define _NGX_MAIL_IMAP_MODULE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_mail.h>
+
+
+typedef struct {
+ size_t client_buffer_size;
+
+ ngx_str_t capability;
+ ngx_str_t starttls_capability;
+ ngx_str_t starttls_only_capability;
+
+ ngx_uint_t auth_methods;
+
+ ngx_array_t capabilities;
+} ngx_mail_imap_srv_conf_t;
+
+
+void ngx_mail_imap_init_session(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_imap_init_protocol(ngx_event_t *rev);
+void ngx_mail_imap_auth_state(ngx_event_t *rev);
+ngx_int_t ngx_mail_imap_parse_command(ngx_mail_session_t *s);
+
+
+extern ngx_module_t ngx_mail_imap_module;
+
+
+#endif /* _NGX_MAIL_IMAP_MODULE_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/mail/ngx_mail_parse.c b/usr.sbin/nginx/src/mail/ngx_mail_parse.c
new file mode 100644
index 00000000000..67f4d5edf6d
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail_parse.c
@@ -0,0 +1,884 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+
+
+ngx_int_t
+ngx_mail_pop3_parse_command(ngx_mail_session_t *s)
+{
+ u_char ch, *p, *c, c0, c1, c2, c3;
+ ngx_str_t *arg;
+ enum {
+ sw_start = 0,
+ sw_spaces_before_argument,
+ sw_argument,
+ sw_almost_done
+ } state;
+
+ state = s->state;
+
+ for (p = s->buffer->pos; p < s->buffer->last; p++) {
+ ch = *p;
+
+ switch (state) {
+
+ /* POP3 command */
+ case sw_start:
+ if (ch == ' ' || ch == CR || ch == LF) {
+ c = s->buffer->start;
+
+ if (p - c == 4) {
+
+ c0 = ngx_toupper(c[0]);
+ c1 = ngx_toupper(c[1]);
+ c2 = ngx_toupper(c[2]);
+ c3 = ngx_toupper(c[3]);
+
+ if (c0 == 'U' && c1 == 'S' && c2 == 'E' && c3 == 'R')
+ {
+ s->command = NGX_POP3_USER;
+
+ } else if (c0 == 'P' && c1 == 'A' && c2 == 'S' && c3 == 'S')
+ {
+ s->command = NGX_POP3_PASS;
+
+ } else if (c0 == 'A' && c1 == 'P' && c2 == 'O' && c3 == 'P')
+ {
+ s->command = NGX_POP3_APOP;
+
+ } else if (c0 == 'Q' && c1 == 'U' && c2 == 'I' && c3 == 'T')
+ {
+ s->command = NGX_POP3_QUIT;
+
+ } else if (c0 == 'C' && c1 == 'A' && c2 == 'P' && c3 == 'A')
+ {
+ s->command = NGX_POP3_CAPA;
+
+ } else if (c0 == 'A' && c1 == 'U' && c2 == 'T' && c3 == 'H')
+ {
+ s->command = NGX_POP3_AUTH;
+
+ } else if (c0 == 'N' && c1 == 'O' && c2 == 'O' && c3 == 'P')
+ {
+ s->command = NGX_POP3_NOOP;
+#if (NGX_MAIL_SSL)
+ } else if (c0 == 'S' && c1 == 'T' && c2 == 'L' && c3 == 'S')
+ {
+ s->command = NGX_POP3_STLS;
+#endif
+ } else {
+ goto invalid;
+ }
+
+ } else {
+ goto invalid;
+ }
+
+ switch (ch) {
+ case ' ':
+ state = sw_spaces_before_argument;
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ }
+ break;
+ }
+
+ if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) {
+ goto invalid;
+ }
+
+ break;
+
+ case sw_spaces_before_argument:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ state = sw_almost_done;
+ s->arg_end = p;
+ break;
+ case LF:
+ s->arg_end = p;
+ goto done;
+ default:
+ if (s->args.nelts <= 2) {
+ state = sw_argument;
+ s->arg_start = p;
+ break;
+ }
+ goto invalid;
+ }
+ break;
+
+ case sw_argument:
+ switch (ch) {
+
+ case ' ':
+
+ /*
+ * the space should be considered as part of the at username
+ * or password, but not of argument in other commands
+ */
+
+ if (s->command == NGX_POP3_USER
+ || s->command == NGX_POP3_PASS)
+ {
+ break;
+ }
+
+ /* fall through */
+
+ case CR:
+ case LF:
+ arg = ngx_array_push(&s->args);
+ if (arg == NULL) {
+ return NGX_ERROR;
+ }
+ arg->len = p - s->arg_start;
+ arg->data = s->arg_start;
+ s->arg_start = NULL;
+
+ switch (ch) {
+ case ' ':
+ state = sw_spaces_before_argument;
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case sw_almost_done:
+ switch (ch) {
+ case LF:
+ goto done;
+ default:
+ goto invalid;
+ }
+ }
+ }
+
+ s->buffer->pos = p;
+ s->state = state;
+
+ return NGX_AGAIN;
+
+done:
+
+ s->buffer->pos = p + 1;
+
+ if (s->arg_start) {
+ arg = ngx_array_push(&s->args);
+ if (arg == NULL) {
+ return NGX_ERROR;
+ }
+ arg->len = s->arg_end - s->arg_start;
+ arg->data = s->arg_start;
+ s->arg_start = NULL;
+ }
+
+ s->state = (s->command != NGX_POP3_AUTH) ? sw_start : sw_argument;
+
+ return NGX_OK;
+
+invalid:
+
+ s->state = sw_start;
+ s->arg_start = NULL;
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+ngx_int_t
+ngx_mail_imap_parse_command(ngx_mail_session_t *s)
+{
+ u_char ch, *p, *c;
+ ngx_str_t *arg;
+ enum {
+ sw_start = 0,
+ sw_spaces_before_command,
+ sw_command,
+ sw_spaces_before_argument,
+ sw_argument,
+ sw_backslash,
+ sw_literal,
+ sw_no_sync_literal_argument,
+ sw_start_literal_argument,
+ sw_literal_argument,
+ sw_end_literal_argument,
+ sw_almost_done
+ } state;
+
+ state = s->state;
+
+ for (p = s->buffer->pos; p < s->buffer->last; p++) {
+ ch = *p;
+
+ switch (state) {
+
+ /* IMAP tag */
+ case sw_start:
+ switch (ch) {
+ case ' ':
+ s->tag.len = p - s->buffer->start + 1;
+ s->tag.data = s->buffer->start;
+ state = sw_spaces_before_command;
+ break;
+ case CR:
+ s->state = sw_start;
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ case LF:
+ s->state = sw_start;
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+ break;
+
+ case sw_spaces_before_command:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ s->state = sw_start;
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ case LF:
+ s->state = sw_start;
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ default:
+ s->cmd_start = p;
+ state = sw_command;
+ break;
+ }
+ break;
+
+ case sw_command:
+ if (ch == ' ' || ch == CR || ch == LF) {
+
+ c = s->cmd_start;
+
+ switch (p - c) {
+
+ case 4:
+ if ((c[0] == 'N' || c[0] == 'n')
+ && (c[1] == 'O'|| c[1] == 'o')
+ && (c[2] == 'O'|| c[2] == 'o')
+ && (c[3] == 'P'|| c[3] == 'p'))
+ {
+ s->command = NGX_IMAP_NOOP;
+
+ } else {
+ goto invalid;
+ }
+ break;
+
+ case 5:
+ if ((c[0] == 'L'|| c[0] == 'l')
+ && (c[1] == 'O'|| c[1] == 'o')
+ && (c[2] == 'G'|| c[2] == 'g')
+ && (c[3] == 'I'|| c[3] == 'i')
+ && (c[4] == 'N'|| c[4] == 'n'))
+ {
+ s->command = NGX_IMAP_LOGIN;
+
+ } else {
+ goto invalid;
+ }
+ break;
+
+ case 6:
+ if ((c[0] == 'L'|| c[0] == 'l')
+ && (c[1] == 'O'|| c[1] == 'o')
+ && (c[2] == 'G'|| c[2] == 'g')
+ && (c[3] == 'O'|| c[3] == 'o')
+ && (c[4] == 'U'|| c[4] == 'u')
+ && (c[5] == 'T'|| c[5] == 't'))
+ {
+ s->command = NGX_IMAP_LOGOUT;
+
+ } else {
+ goto invalid;
+ }
+ break;
+
+#if (NGX_MAIL_SSL)
+ case 8:
+ if ((c[0] == 'S'|| c[0] == 's')
+ && (c[1] == 'T'|| c[1] == 't')
+ && (c[2] == 'A'|| c[2] == 'a')
+ && (c[3] == 'R'|| c[3] == 'r')
+ && (c[4] == 'T'|| c[4] == 't')
+ && (c[5] == 'T'|| c[5] == 't')
+ && (c[6] == 'L'|| c[6] == 'l')
+ && (c[7] == 'S'|| c[7] == 's'))
+ {
+ s->command = NGX_IMAP_STARTTLS;
+
+ } else {
+ goto invalid;
+ }
+ break;
+#endif
+
+ case 10:
+ if ((c[0] == 'C'|| c[0] == 'c')
+ && (c[1] == 'A'|| c[1] == 'a')
+ && (c[2] == 'P'|| c[2] == 'p')
+ && (c[3] == 'A'|| c[3] == 'a')
+ && (c[4] == 'B'|| c[4] == 'b')
+ && (c[5] == 'I'|| c[5] == 'i')
+ && (c[6] == 'L'|| c[6] == 'l')
+ && (c[7] == 'I'|| c[7] == 'i')
+ && (c[8] == 'T'|| c[8] == 't')
+ && (c[9] == 'Y'|| c[9] == 'y'))
+ {
+ s->command = NGX_IMAP_CAPABILITY;
+
+ } else {
+ goto invalid;
+ }
+ break;
+
+ case 12:
+ if ((c[0] == 'A'|| c[0] == 'a')
+ && (c[1] == 'U'|| c[1] == 'u')
+ && (c[2] == 'T'|| c[2] == 't')
+ && (c[3] == 'H'|| c[3] == 'h')
+ && (c[4] == 'E'|| c[4] == 'e')
+ && (c[5] == 'N'|| c[5] == 'n')
+ && (c[6] == 'T'|| c[6] == 't')
+ && (c[7] == 'I'|| c[7] == 'i')
+ && (c[8] == 'C'|| c[8] == 'c')
+ && (c[9] == 'A'|| c[9] == 'a')
+ && (c[10] == 'T'|| c[10] == 't')
+ && (c[11] == 'E'|| c[11] == 'e'))
+ {
+ s->command = NGX_IMAP_AUTHENTICATE;
+
+ } else {
+ goto invalid;
+ }
+ break;
+
+ default:
+ goto invalid;
+ }
+
+ switch (ch) {
+ case ' ':
+ state = sw_spaces_before_argument;
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ }
+ break;
+ }
+
+ if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) {
+ goto invalid;
+ }
+
+ break;
+
+ case sw_spaces_before_argument:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ state = sw_almost_done;
+ s->arg_end = p;
+ break;
+ case LF:
+ s->arg_end = p;
+ goto done;
+ case '"':
+ if (s->args.nelts <= 2) {
+ s->quoted = 1;
+ s->arg_start = p + 1;
+ state = sw_argument;
+ break;
+ }
+ goto invalid;
+ case '{':
+ if (s->args.nelts <= 2) {
+ state = sw_literal;
+ break;
+ }
+ goto invalid;
+ default:
+ if (s->args.nelts <= 2) {
+ s->arg_start = p;
+ state = sw_argument;
+ break;
+ }
+ goto invalid;
+ }
+ break;
+
+ case sw_argument:
+ if (ch == ' ' && s->quoted) {
+ break;
+ }
+
+ switch (ch) {
+ case '"':
+ if (!s->quoted) {
+ break;
+ }
+ s->quoted = 0;
+ /* fall through */
+ case ' ':
+ case CR:
+ case LF:
+ arg = ngx_array_push(&s->args);
+ if (arg == NULL) {
+ return NGX_ERROR;
+ }
+ arg->len = p - s->arg_start;
+ arg->data = s->arg_start;
+ s->arg_start = NULL;
+
+ switch (ch) {
+ case '"':
+ case ' ':
+ state = sw_spaces_before_argument;
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ }
+ break;
+ case '\\':
+ if (s->quoted) {
+ s->backslash = 1;
+ state = sw_backslash;
+ }
+ break;
+ }
+ break;
+
+ case sw_backslash:
+ switch (ch) {
+ case CR:
+ case LF:
+ goto invalid;
+ default:
+ state = sw_argument;
+ }
+ break;
+
+ case sw_literal:
+ if (ch >= '0' && ch <= '9') {
+ s->literal_len = s->literal_len * 10 + (ch - '0');
+ break;
+ }
+ if (ch == '}') {
+ state = sw_start_literal_argument;
+ break;
+ }
+ if (ch == '+') {
+ state = sw_no_sync_literal_argument;
+ break;
+ }
+ goto invalid;
+
+ case sw_no_sync_literal_argument:
+ if (ch == '}') {
+ s->no_sync_literal = 1;
+ state = sw_start_literal_argument;
+ break;
+ }
+ goto invalid;
+
+ case sw_start_literal_argument:
+ switch (ch) {
+ case CR:
+ break;
+ case LF:
+ s->buffer->pos = p + 1;
+ s->arg_start = p + 1;
+ if (s->no_sync_literal == 0) {
+ s->state = sw_literal_argument;
+ return NGX_IMAP_NEXT;
+ }
+ state = sw_literal_argument;
+ s->no_sync_literal = 0;
+ break;
+ default:
+ goto invalid;
+ }
+ break;
+
+ case sw_literal_argument:
+ if (s->literal_len && --s->literal_len) {
+ break;
+ }
+
+ arg = ngx_array_push(&s->args);
+ if (arg == NULL) {
+ return NGX_ERROR;
+ }
+ arg->len = p + 1 - s->arg_start;
+ arg->data = s->arg_start;
+ s->arg_start = NULL;
+ state = sw_end_literal_argument;
+
+ break;
+
+ case sw_end_literal_argument:
+ switch (ch) {
+ case '{':
+ if (s->args.nelts <= 2) {
+ state = sw_literal;
+ break;
+ }
+ goto invalid;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ default:
+ state = sw_spaces_before_argument;
+ break;
+ }
+ break;
+
+ case sw_almost_done:
+ switch (ch) {
+ case LF:
+ goto done;
+ default:
+ goto invalid;
+ }
+ }
+ }
+
+ s->buffer->pos = p;
+ s->state = state;
+
+ return NGX_AGAIN;
+
+done:
+
+ s->buffer->pos = p + 1;
+
+ if (s->arg_start) {
+ arg = ngx_array_push(&s->args);
+ if (arg == NULL) {
+ return NGX_ERROR;
+ }
+ arg->len = s->arg_end - s->arg_start;
+ arg->data = s->arg_start;
+
+ s->arg_start = NULL;
+ s->cmd_start = NULL;
+ s->quoted = 0;
+ s->no_sync_literal = 0;
+ s->literal_len = 0;
+ }
+
+ s->state = (s->command != NGX_IMAP_AUTHENTICATE) ? sw_start : sw_argument;
+
+ return NGX_OK;
+
+invalid:
+
+ s->state = sw_start;
+ s->quoted = 0;
+ s->no_sync_literal = 0;
+ s->literal_len = 0;
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+ngx_int_t
+ngx_mail_smtp_parse_command(ngx_mail_session_t *s)
+{
+ u_char ch, *p, *c, c0, c1, c2, c3;
+ ngx_str_t *arg;
+ enum {
+ sw_start = 0,
+ sw_spaces_before_argument,
+ sw_argument,
+ sw_almost_done
+ } state;
+
+ state = s->state;
+
+ for (p = s->buffer->pos; p < s->buffer->last; p++) {
+ ch = *p;
+
+ switch (state) {
+
+ /* SMTP command */
+ case sw_start:
+ if (ch == ' ' || ch == CR || ch == LF) {
+ c = s->buffer->start;
+
+ if (p - c == 4) {
+
+ c0 = ngx_toupper(c[0]);
+ c1 = ngx_toupper(c[1]);
+ c2 = ngx_toupper(c[2]);
+ c3 = ngx_toupper(c[3]);
+
+ if (c0 == 'H' && c1 == 'E' && c2 == 'L' && c3 == 'O')
+ {
+ s->command = NGX_SMTP_HELO;
+
+ } else if (c0 == 'E' && c1 == 'H' && c2 == 'L' && c3 == 'O')
+ {
+ s->command = NGX_SMTP_EHLO;
+
+ } else if (c0 == 'Q' && c1 == 'U' && c2 == 'I' && c3 == 'T')
+ {
+ s->command = NGX_SMTP_QUIT;
+
+ } else if (c0 == 'A' && c1 == 'U' && c2 == 'T' && c3 == 'H')
+ {
+ s->command = NGX_SMTP_AUTH;
+
+ } else if (c0 == 'N' && c1 == 'O' && c2 == 'O' && c3 == 'P')
+ {
+ s->command = NGX_SMTP_NOOP;
+
+ } else if (c0 == 'M' && c1 == 'A' && c2 == 'I' && c3 == 'L')
+ {
+ s->command = NGX_SMTP_MAIL;
+
+ } else if (c0 == 'R' && c1 == 'S' && c2 == 'E' && c3 == 'T')
+ {
+ s->command = NGX_SMTP_RSET;
+
+ } else if (c0 == 'R' && c1 == 'C' && c2 == 'P' && c3 == 'T')
+ {
+ s->command = NGX_SMTP_RCPT;
+
+ } else if (c0 == 'V' && c1 == 'R' && c2 == 'F' && c3 == 'Y')
+ {
+ s->command = NGX_SMTP_VRFY;
+
+ } else if (c0 == 'E' && c1 == 'X' && c2 == 'P' && c3 == 'N')
+ {
+ s->command = NGX_SMTP_EXPN;
+
+ } else if (c0 == 'H' && c1 == 'E' && c2 == 'L' && c3 == 'P')
+ {
+ s->command = NGX_SMTP_HELP;
+
+ } else {
+ goto invalid;
+ }
+#if (NGX_MAIL_SSL)
+ } else if (p - c == 8) {
+
+ if ((c[0] == 'S'|| c[0] == 's')
+ && (c[1] == 'T'|| c[1] == 't')
+ && (c[2] == 'A'|| c[2] == 'a')
+ && (c[3] == 'R'|| c[3] == 'r')
+ && (c[4] == 'T'|| c[4] == 't')
+ && (c[5] == 'T'|| c[5] == 't')
+ && (c[6] == 'L'|| c[6] == 'l')
+ && (c[7] == 'S'|| c[7] == 's'))
+ {
+ s->command = NGX_SMTP_STARTTLS;
+
+ } else {
+ goto invalid;
+ }
+#endif
+ } else {
+ goto invalid;
+ }
+
+ switch (ch) {
+ case ' ':
+ state = sw_spaces_before_argument;
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ }
+ break;
+ }
+
+ if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) {
+ goto invalid;
+ }
+
+ break;
+
+ case sw_spaces_before_argument:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ state = sw_almost_done;
+ s->arg_end = p;
+ break;
+ case LF:
+ s->arg_end = p;
+ goto done;
+ default:
+ if (s->args.nelts <= 10) {
+ state = sw_argument;
+ s->arg_start = p;
+ break;
+ }
+ goto invalid;
+ }
+ break;
+
+ case sw_argument:
+ switch (ch) {
+ case ' ':
+ case CR:
+ case LF:
+ arg = ngx_array_push(&s->args);
+ if (arg == NULL) {
+ return NGX_ERROR;
+ }
+ arg->len = p - s->arg_start;
+ arg->data = s->arg_start;
+ s->arg_start = NULL;
+
+ switch (ch) {
+ case ' ':
+ state = sw_spaces_before_argument;
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case sw_almost_done:
+ switch (ch) {
+ case LF:
+ goto done;
+ default:
+ goto invalid;
+ }
+ }
+ }
+
+ s->buffer->pos = p;
+ s->state = state;
+
+ return NGX_AGAIN;
+
+done:
+
+ s->buffer->pos = p + 1;
+
+ if (s->arg_start) {
+ arg = ngx_array_push(&s->args);
+ if (arg == NULL) {
+ return NGX_ERROR;
+ }
+ arg->len = s->arg_end - s->arg_start;
+ arg->data = s->arg_start;
+ s->arg_start = NULL;
+ }
+
+ s->state = (s->command != NGX_SMTP_AUTH) ? sw_start : sw_argument;
+
+ return NGX_OK;
+
+invalid:
+
+ s->state = sw_start;
+ s->arg_start = NULL;
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+ngx_int_t
+ngx_mail_auth_parse(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_str_t *arg;
+
+#if (NGX_MAIL_SSL)
+ if (ngx_mail_starttls_only(s, c)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+#endif
+
+ arg = s->args.elts;
+
+ if (arg[0].len == 5) {
+
+ if (ngx_strncasecmp(arg[0].data, (u_char *) "LOGIN", 5) == 0) {
+
+ if (s->args.nelts == 1) {
+ return NGX_MAIL_AUTH_LOGIN;
+ }
+
+ if (s->args.nelts == 2) {
+ return NGX_MAIL_AUTH_LOGIN_USERNAME;
+ }
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ if (ngx_strncasecmp(arg[0].data, (u_char *) "PLAIN", 5) == 0) {
+
+ if (s->args.nelts == 1) {
+ return NGX_MAIL_AUTH_PLAIN;
+ }
+
+ if (s->args.nelts == 2) {
+ return ngx_mail_auth_plain(s, c, 1);
+ }
+ }
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ if (arg[0].len == 8) {
+
+ if (s->args.nelts != 1) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ if (ngx_strncasecmp(arg[0].data, (u_char *) "CRAM-MD5", 8) == 0) {
+ return NGX_MAIL_AUTH_CRAM_MD5;
+ }
+ }
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
diff --git a/usr.sbin/nginx/src/mail/ngx_mail_pop3_handler.c b/usr.sbin/nginx/src/mail/ngx_mail_pop3_handler.c
new file mode 100644
index 00000000000..56726dbcf20
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail_pop3_handler.c
@@ -0,0 +1,499 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_pop3_module.h>
+
+
+static ngx_int_t ngx_mail_pop3_user(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_pop3_pass(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_pop3_capa(ngx_mail_session_t *s, ngx_connection_t *c,
+ ngx_int_t stls);
+static ngx_int_t ngx_mail_pop3_stls(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_pop3_apop(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_pop3_auth(ngx_mail_session_t *s, ngx_connection_t *c);
+
+
+static u_char pop3_greeting[] = "+OK POP3 ready" CRLF;
+static u_char pop3_ok[] = "+OK" CRLF;
+static u_char pop3_next[] = "+ " CRLF;
+static u_char pop3_username[] = "+ VXNlcm5hbWU6" CRLF;
+static u_char pop3_password[] = "+ UGFzc3dvcmQ6" CRLF;
+static u_char pop3_invalid_command[] = "-ERR invalid command" CRLF;
+
+
+void
+ngx_mail_pop3_init_session(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ u_char *p;
+ ngx_mail_core_srv_conf_t *cscf;
+ ngx_mail_pop3_srv_conf_t *pscf;
+
+ pscf = ngx_mail_get_module_srv_conf(s, ngx_mail_pop3_module);
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ if (pscf->auth_methods
+ & (NGX_MAIL_AUTH_APOP_ENABLED|NGX_MAIL_AUTH_CRAM_MD5_ENABLED))
+ {
+ if (ngx_mail_salt(s, c, cscf) != NGX_OK) {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ s->out.data = ngx_pnalloc(c->pool, sizeof(pop3_greeting) + s->salt.len);
+ if (s->out.data == NULL) {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ p = ngx_cpymem(s->out.data, pop3_greeting, sizeof(pop3_greeting) - 3);
+ *p++ = ' ';
+ p = ngx_cpymem(p, s->salt.data, s->salt.len);
+
+ s->out.len = p - s->out.data;
+
+ } else {
+ ngx_str_set(&s->out, pop3_greeting);
+ }
+
+ c->read->handler = ngx_mail_pop3_init_protocol;
+
+ ngx_add_timer(c->read, cscf->timeout);
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ }
+
+ ngx_mail_send(c->write);
+}
+
+
+void
+ngx_mail_pop3_init_protocol(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+
+ c = rev->data;
+
+ c->log->action = "in auth state";
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ s = c->data;
+
+ if (s->buffer == NULL) {
+ if (ngx_array_init(&s->args, c->pool, 2, sizeof(ngx_str_t))
+ == NGX_ERROR)
+ {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ s->buffer = ngx_create_temp_buf(c->pool, 128);
+ if (s->buffer == NULL) {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+ }
+
+ s->mail_state = ngx_pop3_start;
+ c->read->handler = ngx_mail_pop3_auth_state;
+
+ ngx_mail_pop3_auth_state(rev);
+}
+
+
+void
+ngx_mail_pop3_auth_state(ngx_event_t *rev)
+{
+ ngx_int_t rc;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+
+ c = rev->data;
+ s = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "pop3 auth state");
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ if (s->out.len) {
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "pop3 send handler busy");
+ s->blocked = 1;
+ return;
+ }
+
+ s->blocked = 0;
+
+ rc = ngx_mail_read_command(s, c);
+
+ if (rc == NGX_AGAIN || rc == NGX_ERROR) {
+ return;
+ }
+
+ ngx_str_set(&s->out, pop3_ok);
+
+ if (rc == NGX_OK) {
+ switch (s->mail_state) {
+
+ case ngx_pop3_start:
+
+ switch (s->command) {
+
+ case NGX_POP3_USER:
+ rc = ngx_mail_pop3_user(s, c);
+ break;
+
+ case NGX_POP3_CAPA:
+ rc = ngx_mail_pop3_capa(s, c, 1);
+ break;
+
+ case NGX_POP3_APOP:
+ rc = ngx_mail_pop3_apop(s, c);
+ break;
+
+ case NGX_POP3_AUTH:
+ rc = ngx_mail_pop3_auth(s, c);
+ break;
+
+ case NGX_POP3_QUIT:
+ s->quit = 1;
+ break;
+
+ case NGX_POP3_NOOP:
+ break;
+
+ case NGX_POP3_STLS:
+ rc = ngx_mail_pop3_stls(s, c);
+ break;
+
+ default:
+ rc = NGX_MAIL_PARSE_INVALID_COMMAND;
+ break;
+ }
+
+ break;
+
+ case ngx_pop3_user:
+
+ switch (s->command) {
+
+ case NGX_POP3_PASS:
+ rc = ngx_mail_pop3_pass(s, c);
+ break;
+
+ case NGX_POP3_CAPA:
+ rc = ngx_mail_pop3_capa(s, c, 0);
+ break;
+
+ case NGX_POP3_QUIT:
+ s->quit = 1;
+ break;
+
+ case NGX_POP3_NOOP:
+ break;
+
+ default:
+ rc = NGX_MAIL_PARSE_INVALID_COMMAND;
+ break;
+ }
+
+ break;
+
+ /* suppress warinings */
+ case ngx_pop3_passwd:
+ break;
+
+ case ngx_pop3_auth_login_username:
+ rc = ngx_mail_auth_login_username(s, c, 0);
+
+ ngx_str_set(&s->out, pop3_password);
+ s->mail_state = ngx_pop3_auth_login_password;
+ break;
+
+ case ngx_pop3_auth_login_password:
+ rc = ngx_mail_auth_login_password(s, c);
+ break;
+
+ case ngx_pop3_auth_plain:
+ rc = ngx_mail_auth_plain(s, c, 0);
+ break;
+
+ case ngx_pop3_auth_cram_md5:
+ rc = ngx_mail_auth_cram_md5(s, c);
+ break;
+ }
+ }
+
+ switch (rc) {
+
+ case NGX_DONE:
+ ngx_mail_auth(s, c);
+ return;
+
+ case NGX_ERROR:
+ ngx_mail_session_internal_server_error(s);
+ return;
+
+ case NGX_MAIL_PARSE_INVALID_COMMAND:
+ s->mail_state = ngx_pop3_start;
+ s->state = 0;
+
+ ngx_str_set(&s->out, pop3_invalid_command);
+
+ /* fall through */
+
+ case NGX_OK:
+
+ s->args.nelts = 0;
+ s->buffer->pos = s->buffer->start;
+ s->buffer->last = s->buffer->start;
+
+ if (s->state) {
+ s->arg_start = s->buffer->start;
+ }
+
+ ngx_mail_send(c->write);
+ }
+}
+
+static ngx_int_t
+ngx_mail_pop3_user(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_str_t *arg;
+
+#if (NGX_MAIL_SSL)
+ if (ngx_mail_starttls_only(s, c)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+#endif
+
+ if (s->args.nelts != 1) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ arg = s->args.elts;
+ s->login.len = arg[0].len;
+ s->login.data = ngx_pnalloc(c->pool, s->login.len);
+ if (s->login.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->login.data, arg[0].data, s->login.len);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "pop3 login: \"%V\"", &s->login);
+
+ s->mail_state = ngx_pop3_user;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_pass(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_str_t *arg;
+
+ if (s->args.nelts != 1) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ arg = s->args.elts;
+ s->passwd.len = arg[0].len;
+ s->passwd.data = ngx_pnalloc(c->pool, s->passwd.len);
+ if (s->passwd.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->passwd.data, arg[0].data, s->passwd.len);
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "pop3 passwd: \"%V\"", &s->passwd);
+#endif
+
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_capa(ngx_mail_session_t *s, ngx_connection_t *c, ngx_int_t stls)
+{
+ ngx_mail_pop3_srv_conf_t *pscf;
+
+ pscf = ngx_mail_get_module_srv_conf(s, ngx_mail_pop3_module);
+
+#if (NGX_MAIL_SSL)
+
+ if (stls && c->ssl == NULL) {
+ ngx_mail_ssl_conf_t *sslcf;
+
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+ if (sslcf->starttls == NGX_MAIL_STARTTLS_ON) {
+ s->out = pscf->starttls_capability;
+ return NGX_OK;
+ }
+
+ if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
+ s->out = pscf->starttls_only_capability;
+ return NGX_OK;
+ }
+ }
+
+#endif
+
+ s->out = pscf->capability;
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_stls(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+#if (NGX_MAIL_SSL)
+ ngx_mail_ssl_conf_t *sslcf;
+
+ if (c->ssl == NULL) {
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+ if (sslcf->starttls) {
+ c->read->handler = ngx_mail_starttls_handler;
+ return NGX_OK;
+ }
+ }
+
+#endif
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_apop(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_str_t *arg;
+ ngx_mail_pop3_srv_conf_t *pscf;
+
+#if (NGX_MAIL_SSL)
+ if (ngx_mail_starttls_only(s, c)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+#endif
+
+ if (s->args.nelts != 2) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ pscf = ngx_mail_get_module_srv_conf(s, ngx_mail_pop3_module);
+
+ if (!(pscf->auth_methods & NGX_MAIL_AUTH_APOP_ENABLED)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ arg = s->args.elts;
+
+ s->login.len = arg[0].len;
+ s->login.data = ngx_pnalloc(c->pool, s->login.len);
+ if (s->login.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->login.data, arg[0].data, s->login.len);
+
+ s->passwd.len = arg[1].len;
+ s->passwd.data = ngx_pnalloc(c->pool, s->passwd.len);
+ if (s->passwd.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->passwd.data, arg[1].data, s->passwd.len);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "pop3 apop: \"%V\" \"%V\"", &s->login, &s->passwd);
+
+ s->auth_method = NGX_MAIL_AUTH_APOP;
+
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_auth(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_int_t rc;
+ ngx_mail_pop3_srv_conf_t *pscf;
+
+#if (NGX_MAIL_SSL)
+ if (ngx_mail_starttls_only(s, c)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+#endif
+
+ pscf = ngx_mail_get_module_srv_conf(s, ngx_mail_pop3_module);
+
+ if (s->args.nelts == 0) {
+ s->out = pscf->auth_capability;
+ s->state = 0;
+
+ return NGX_OK;
+ }
+
+ rc = ngx_mail_auth_parse(s, c);
+
+ switch (rc) {
+
+ case NGX_MAIL_AUTH_LOGIN:
+
+ ngx_str_set(&s->out, pop3_username);
+ s->mail_state = ngx_pop3_auth_login_username;
+
+ return NGX_OK;
+
+ case NGX_MAIL_AUTH_LOGIN_USERNAME:
+
+ ngx_str_set(&s->out, pop3_password);
+ s->mail_state = ngx_pop3_auth_login_password;
+
+ return ngx_mail_auth_login_username(s, c, 1);
+
+ case NGX_MAIL_AUTH_PLAIN:
+
+ ngx_str_set(&s->out, pop3_next);
+ s->mail_state = ngx_pop3_auth_plain;
+
+ return NGX_OK;
+
+ case NGX_MAIL_AUTH_CRAM_MD5:
+
+ if (!(pscf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ if (ngx_mail_auth_cram_md5_salt(s, c, "+ ", 2) == NGX_OK) {
+ s->mail_state = ngx_pop3_auth_cram_md5;
+ return NGX_OK;
+ }
+
+ return NGX_ERROR;
+ }
+
+ return rc;
+}
diff --git a/usr.sbin/nginx/src/mail/ngx_mail_pop3_module.c b/usr.sbin/nginx/src/mail/ngx_mail_pop3_module.c
new file mode 100644
index 00000000000..648c57ce81a
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail_pop3_module.c
@@ -0,0 +1,263 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_pop3_module.h>
+
+
+static void *ngx_mail_pop3_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_mail_pop3_merge_srv_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+
+
+static ngx_str_t ngx_mail_pop3_default_capabilities[] = {
+ ngx_string("TOP"),
+ ngx_string("USER"),
+ ngx_string("UIDL"),
+ ngx_null_string
+};
+
+
+static ngx_conf_bitmask_t ngx_mail_pop3_auth_methods[] = {
+ { ngx_string("plain"), NGX_MAIL_AUTH_PLAIN_ENABLED },
+ { ngx_string("apop"), NGX_MAIL_AUTH_APOP_ENABLED },
+ { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_str_t ngx_mail_pop3_auth_plain_capability =
+ ngx_string("+OK methods supported:" CRLF
+ "LOGIN" CRLF
+ "PLAIN" CRLF
+ "." CRLF);
+
+
+static ngx_str_t ngx_mail_pop3_auth_cram_md5_capability =
+ ngx_string("+OK methods supported:" CRLF
+ "LOGIN" CRLF
+ "PLAIN" CRLF
+ "CRAM-MD5" CRLF
+ "." CRLF);
+
+
+static ngx_mail_protocol_t ngx_mail_pop3_protocol = {
+ ngx_string("pop3"),
+ { 110, 995, 0, 0 },
+ NGX_MAIL_POP3_PROTOCOL,
+
+ ngx_mail_pop3_init_session,
+ ngx_mail_pop3_init_protocol,
+ ngx_mail_pop3_parse_command,
+ ngx_mail_pop3_auth_state,
+
+ ngx_string("-ERR internal server error" CRLF)
+};
+
+
+static ngx_command_t ngx_mail_pop3_commands[] = {
+
+ { ngx_string("pop3_capabilities"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+ ngx_mail_capabilities,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_pop3_srv_conf_t, capabilities),
+ NULL },
+
+ { ngx_string("pop3_auth"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_pop3_srv_conf_t, auth_methods),
+ &ngx_mail_pop3_auth_methods },
+
+ ngx_null_command
+};
+
+
+static ngx_mail_module_t ngx_mail_pop3_module_ctx = {
+ &ngx_mail_pop3_protocol, /* protocol */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_mail_pop3_create_srv_conf, /* create server configuration */
+ ngx_mail_pop3_merge_srv_conf /* merge server configuration */
+};
+
+
+ngx_module_t ngx_mail_pop3_module = {
+ NGX_MODULE_V1,
+ &ngx_mail_pop3_module_ctx, /* module context */
+ ngx_mail_pop3_commands, /* module directives */
+ NGX_MAIL_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static void *
+ngx_mail_pop3_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_mail_pop3_srv_conf_t *pscf;
+
+ pscf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_pop3_srv_conf_t));
+ if (pscf == NULL) {
+ return NULL;
+ }
+
+ if (ngx_array_init(&pscf->capabilities, cf->pool, 4, sizeof(ngx_str_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ return pscf;
+}
+
+
+static char *
+ngx_mail_pop3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_mail_pop3_srv_conf_t *prev = parent;
+ ngx_mail_pop3_srv_conf_t *conf = child;
+
+ u_char *p;
+ size_t size, stls_only_size;
+ ngx_str_t *c, *d;
+ ngx_uint_t i;
+
+ ngx_conf_merge_bitmask_value(conf->auth_methods,
+ prev->auth_methods,
+ (NGX_CONF_BITMASK_SET
+ |NGX_MAIL_AUTH_PLAIN_ENABLED));
+
+ if (conf->capabilities.nelts == 0) {
+ conf->capabilities = prev->capabilities;
+ }
+
+ if (conf->capabilities.nelts == 0) {
+
+ for (d = ngx_mail_pop3_default_capabilities; d->len; d++) {
+ c = ngx_array_push(&conf->capabilities);
+ if (c == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *c = *d;
+ }
+ }
+
+ size = sizeof("+OK Capability list follows" CRLF) - 1
+ + sizeof("." CRLF) - 1;
+
+ stls_only_size = size + sizeof("STLS" CRLF) - 1;
+
+ c = conf->capabilities.elts;
+ for (i = 0; i < conf->capabilities.nelts; i++) {
+ size += c[i].len + sizeof(CRLF) - 1;
+
+ if (ngx_strcasecmp(c[i].data, (u_char *) "USER") == 0) {
+ continue;
+ }
+
+ stls_only_size += c[i].len + sizeof(CRLF) - 1;
+ }
+
+ if (conf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED) {
+ size += sizeof("SASL LOGIN PLAIN CRAM-MD5" CRLF) - 1;
+
+ } else {
+ size += sizeof("SASL LOGIN PLAIN" CRLF) - 1;
+ }
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->capability.len = size;
+ conf->capability.data = p;
+
+ p = ngx_cpymem(p, "+OK Capability list follows" CRLF,
+ sizeof("+OK Capability list follows" CRLF) - 1);
+
+ for (i = 0; i < conf->capabilities.nelts; i++) {
+ p = ngx_cpymem(p, c[i].data, c[i].len);
+ *p++ = CR; *p++ = LF;
+ }
+
+ if (conf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED) {
+ p = ngx_cpymem(p, "SASL LOGIN PLAIN CRAM-MD5" CRLF,
+ sizeof("SASL LOGIN PLAIN CRAM-MD5" CRLF) - 1);
+
+ } else {
+ p = ngx_cpymem(p, "SASL LOGIN PLAIN" CRLF,
+ sizeof("SASL LOGIN PLAIN" CRLF) - 1);
+ }
+
+ *p++ = '.'; *p++ = CR; *p = LF;
+
+
+ size += sizeof("STLS" CRLF) - 1;
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->starttls_capability.len = size;
+ conf->starttls_capability.data = p;
+
+ p = ngx_cpymem(p, conf->capability.data,
+ conf->capability.len - (sizeof("." CRLF) - 1));
+
+ p = ngx_cpymem(p, "STLS" CRLF, sizeof("STLS" CRLF) - 1);
+ *p++ = '.'; *p++ = CR; *p = LF;
+
+
+ if (conf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED) {
+ conf->auth_capability = ngx_mail_pop3_auth_cram_md5_capability;
+
+ } else {
+ conf->auth_capability = ngx_mail_pop3_auth_plain_capability;
+ }
+
+
+ p = ngx_pnalloc(cf->pool, stls_only_size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->starttls_only_capability.len = stls_only_size;
+ conf->starttls_only_capability.data = p;
+
+ p = ngx_cpymem(p, "+OK Capability list follows" CRLF,
+ sizeof("+OK Capability list follows" CRLF) - 1);
+
+ for (i = 0; i < conf->capabilities.nelts; i++) {
+ if (ngx_strcasecmp(c[i].data, (u_char *) "USER") == 0) {
+ continue;
+ }
+
+ p = ngx_cpymem(p, c[i].data, c[i].len);
+ *p++ = CR; *p++ = LF;
+ }
+
+ p = ngx_cpymem(p, "STLS" CRLF, sizeof("STLS" CRLF) - 1);
+ *p++ = '.'; *p++ = CR; *p = LF;
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/mail/ngx_mail_pop3_module.h b/usr.sbin/nginx/src/mail/ngx_mail_pop3_module.h
new file mode 100644
index 00000000000..ce75c2a00e0
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail_pop3_module.h
@@ -0,0 +1,37 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_MAIL_POP3_MODULE_H_INCLUDED_
+#define _NGX_MAIL_POP3_MODULE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_mail.h>
+
+
+typedef struct {
+ ngx_str_t capability;
+ ngx_str_t starttls_capability;
+ ngx_str_t starttls_only_capability;
+ ngx_str_t auth_capability;
+
+ ngx_uint_t auth_methods;
+
+ ngx_array_t capabilities;
+} ngx_mail_pop3_srv_conf_t;
+
+
+void ngx_mail_pop3_init_session(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_pop3_init_protocol(ngx_event_t *rev);
+void ngx_mail_pop3_auth_state(ngx_event_t *rev);
+ngx_int_t ngx_mail_pop3_parse_command(ngx_mail_session_t *s);
+
+
+extern ngx_module_t ngx_mail_pop3_module;
+
+
+#endif /* _NGX_MAIL_POP3_MODULE_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/mail/ngx_mail_proxy_module.c b/usr.sbin/nginx/src/mail/ngx_mail_proxy_module.c
new file mode 100644
index 00000000000..a7c4b7e48b6
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail_proxy_module.c
@@ -0,0 +1,1088 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+#include <ngx_mail.h>
+
+
+typedef struct {
+ ngx_flag_t enable;
+ ngx_flag_t pass_error_message;
+ ngx_flag_t xclient;
+ size_t buffer_size;
+ ngx_msec_t timeout;
+} ngx_mail_proxy_conf_t;
+
+
+static void ngx_mail_proxy_block_read(ngx_event_t *rev);
+static void ngx_mail_proxy_pop3_handler(ngx_event_t *rev);
+static void ngx_mail_proxy_imap_handler(ngx_event_t *rev);
+static void ngx_mail_proxy_smtp_handler(ngx_event_t *rev);
+static void ngx_mail_proxy_dummy_handler(ngx_event_t *ev);
+static ngx_int_t ngx_mail_proxy_read_response(ngx_mail_session_t *s,
+ ngx_uint_t state);
+static void ngx_mail_proxy_handler(ngx_event_t *ev);
+static void ngx_mail_proxy_upstream_error(ngx_mail_session_t *s);
+static void ngx_mail_proxy_internal_server_error(ngx_mail_session_t *s);
+static void ngx_mail_proxy_close_session(ngx_mail_session_t *s);
+static void *ngx_mail_proxy_create_conf(ngx_conf_t *cf);
+static char *ngx_mail_proxy_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+
+
+static ngx_command_t ngx_mail_proxy_commands[] = {
+
+ { ngx_string("proxy"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_proxy_conf_t, enable),
+ NULL },
+
+ { ngx_string("proxy_buffer"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_proxy_conf_t, buffer_size),
+ NULL },
+
+ { ngx_string("proxy_timeout"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_proxy_conf_t, timeout),
+ NULL },
+
+ { ngx_string("proxy_pass_error_message"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_flag_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_proxy_conf_t, pass_error_message),
+ NULL },
+
+ { ngx_string("xclient"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_proxy_conf_t, xclient),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_mail_module_t ngx_mail_proxy_module_ctx = {
+ NULL, /* protocol */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_mail_proxy_create_conf, /* create server configuration */
+ ngx_mail_proxy_merge_conf /* merge server configuration */
+};
+
+
+ngx_module_t ngx_mail_proxy_module = {
+ NGX_MODULE_V1,
+ &ngx_mail_proxy_module_ctx, /* module context */
+ ngx_mail_proxy_commands, /* module directives */
+ NGX_MAIL_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static u_char smtp_auth_ok[] = "235 2.0.0 OK" CRLF;
+
+
+void
+ngx_mail_proxy_init(ngx_mail_session_t *s, ngx_addr_t *peer)
+{
+ int keepalive;
+ ngx_int_t rc;
+ ngx_mail_proxy_ctx_t *p;
+ ngx_mail_proxy_conf_t *pcf;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ s->connection->log->action = "connecting to upstream";
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ if (cscf->so_keepalive) {
+ keepalive = 1;
+
+ if (setsockopt(s->connection->fd, SOL_SOCKET, SO_KEEPALIVE,
+ (const void *) &keepalive, sizeof(int))
+ == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, s->connection->log, ngx_socket_errno,
+ "setsockopt(SO_KEEPALIVE) failed");
+ }
+ }
+
+ p = ngx_pcalloc(s->connection->pool, sizeof(ngx_mail_proxy_ctx_t));
+ if (p == NULL) {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ s->proxy = p;
+
+ p->upstream.sockaddr = peer->sockaddr;
+ p->upstream.socklen = peer->socklen;
+ p->upstream.name = &peer->name;
+ p->upstream.get = ngx_event_get_peer;
+ p->upstream.log = s->connection->log;
+ p->upstream.log_error = NGX_ERROR_ERR;
+
+ rc = ngx_event_connect_peer(&p->upstream);
+
+ if (rc == NGX_ERROR || rc == NGX_BUSY || rc == NGX_DECLINED) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ ngx_add_timer(p->upstream.connection->read, cscf->timeout);
+
+ p->upstream.connection->data = s;
+ p->upstream.connection->pool = s->connection->pool;
+
+ s->connection->read->handler = ngx_mail_proxy_block_read;
+ p->upstream.connection->write->handler = ngx_mail_proxy_dummy_handler;
+
+ pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+
+ s->proxy->buffer = ngx_create_temp_buf(s->connection->pool,
+ pcf->buffer_size);
+ if (s->proxy->buffer == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ s->out.len = 0;
+
+ switch (s->protocol) {
+
+ case NGX_MAIL_POP3_PROTOCOL:
+ p->upstream.connection->read->handler = ngx_mail_proxy_pop3_handler;
+ s->mail_state = ngx_pop3_start;
+ break;
+
+ case NGX_MAIL_IMAP_PROTOCOL:
+ p->upstream.connection->read->handler = ngx_mail_proxy_imap_handler;
+ s->mail_state = ngx_imap_start;
+ break;
+
+ default: /* NGX_MAIL_SMTP_PROTOCOL */
+ p->upstream.connection->read->handler = ngx_mail_proxy_smtp_handler;
+ s->mail_state = ngx_smtp_start;
+ break;
+ }
+}
+
+
+static void
+ngx_mail_proxy_block_read(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail proxy block read");
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ c = rev->data;
+ s = c->data;
+
+ ngx_mail_proxy_close_session(s);
+ }
+}
+
+
+static void
+ngx_mail_proxy_pop3_handler(ngx_event_t *rev)
+{
+ u_char *p;
+ ngx_int_t rc;
+ ngx_str_t line;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_proxy_conf_t *pcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy pop3 auth handler");
+
+ c = rev->data;
+ s = c->data;
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+ "upstream timed out");
+ c->timedout = 1;
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ rc = ngx_mail_proxy_read_response(s, 0);
+
+ if (rc == NGX_AGAIN) {
+ return;
+ }
+
+ if (rc == NGX_ERROR) {
+ ngx_mail_proxy_upstream_error(s);
+ return;
+ }
+
+ switch (s->mail_state) {
+
+ case ngx_pop3_start:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail proxy send user");
+
+ s->connection->log->action = "sending user name to upstream";
+
+ line.len = sizeof("USER ") - 1 + s->login.len + 2;
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ p = ngx_cpymem(line.data, "USER ", sizeof("USER ") - 1);
+ p = ngx_cpymem(p, s->login.data, s->login.len);
+ *p++ = CR; *p = LF;
+
+ s->mail_state = ngx_pop3_user;
+ break;
+
+ case ngx_pop3_user:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail proxy send pass");
+
+ s->connection->log->action = "sending password to upstream";
+
+ line.len = sizeof("PASS ") - 1 + s->passwd.len + 2;
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ p = ngx_cpymem(line.data, "PASS ", sizeof("PASS ") - 1);
+ p = ngx_cpymem(p, s->passwd.data, s->passwd.len);
+ *p++ = CR; *p = LF;
+
+ s->mail_state = ngx_pop3_passwd;
+ break;
+
+ case ngx_pop3_passwd:
+ s->connection->read->handler = ngx_mail_proxy_handler;
+ s->connection->write->handler = ngx_mail_proxy_handler;
+ rev->handler = ngx_mail_proxy_handler;
+ c->write->handler = ngx_mail_proxy_handler;
+
+ pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+ ngx_add_timer(s->connection->read, pcf->timeout);
+ ngx_del_timer(c->read);
+
+ c->log->action = NULL;
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in");
+
+ ngx_mail_proxy_handler(s->connection->write);
+
+ return;
+
+ default:
+#if (NGX_SUPPRESS_WARN)
+ ngx_str_null(&line);
+#endif
+ break;
+ }
+
+ if (c->send(c, line.data, line.len) < (ssize_t) line.len) {
+ /*
+ * we treat the incomplete sending as NGX_ERROR
+ * because it is very strange here
+ */
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ s->proxy->buffer->pos = s->proxy->buffer->start;
+ s->proxy->buffer->last = s->proxy->buffer->start;
+}
+
+
+static void
+ngx_mail_proxy_imap_handler(ngx_event_t *rev)
+{
+ u_char *p;
+ ngx_int_t rc;
+ ngx_str_t line;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_proxy_conf_t *pcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy imap auth handler");
+
+ c = rev->data;
+ s = c->data;
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+ "upstream timed out");
+ c->timedout = 1;
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ rc = ngx_mail_proxy_read_response(s, s->mail_state);
+
+ if (rc == NGX_AGAIN) {
+ return;
+ }
+
+ if (rc == NGX_ERROR) {
+ ngx_mail_proxy_upstream_error(s);
+ return;
+ }
+
+ switch (s->mail_state) {
+
+ case ngx_imap_start:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy send login");
+
+ s->connection->log->action = "sending LOGIN command to upstream";
+
+ line.len = s->tag.len + sizeof("LOGIN ") - 1
+ + 1 + NGX_SIZE_T_LEN + 1 + 2;
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ line.len = ngx_sprintf(line.data, "%VLOGIN {%uz}" CRLF,
+ &s->tag, s->login.len)
+ - line.data;
+
+ s->mail_state = ngx_imap_login;
+ break;
+
+ case ngx_imap_login:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail proxy send user");
+
+ s->connection->log->action = "sending user name to upstream";
+
+ line.len = s->login.len + 1 + 1 + NGX_SIZE_T_LEN + 1 + 2;
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ line.len = ngx_sprintf(line.data, "%V {%uz}" CRLF,
+ &s->login, s->passwd.len)
+ - line.data;
+
+ s->mail_state = ngx_imap_user;
+ break;
+
+ case ngx_imap_user:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy send passwd");
+
+ s->connection->log->action = "sending password to upstream";
+
+ line.len = s->passwd.len + 2;
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ p = ngx_cpymem(line.data, s->passwd.data, s->passwd.len);
+ *p++ = CR; *p = LF;
+
+ s->mail_state = ngx_imap_passwd;
+ break;
+
+ case ngx_imap_passwd:
+ s->connection->read->handler = ngx_mail_proxy_handler;
+ s->connection->write->handler = ngx_mail_proxy_handler;
+ rev->handler = ngx_mail_proxy_handler;
+ c->write->handler = ngx_mail_proxy_handler;
+
+ pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+ ngx_add_timer(s->connection->read, pcf->timeout);
+ ngx_del_timer(c->read);
+
+ c->log->action = NULL;
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in");
+
+ ngx_mail_proxy_handler(s->connection->write);
+
+ return;
+
+ default:
+#if (NGX_SUPPRESS_WARN)
+ ngx_str_null(&line);
+#endif
+ break;
+ }
+
+ if (c->send(c, line.data, line.len) < (ssize_t) line.len) {
+ /*
+ * we treat the incomplete sending as NGX_ERROR
+ * because it is very strange here
+ */
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ s->proxy->buffer->pos = s->proxy->buffer->start;
+ s->proxy->buffer->last = s->proxy->buffer->start;
+}
+
+
+static void
+ngx_mail_proxy_smtp_handler(ngx_event_t *rev)
+{
+ u_char *p;
+ ngx_int_t rc;
+ ngx_str_t line;
+ ngx_buf_t *b;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_proxy_conf_t *pcf;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy smtp auth handler");
+
+ c = rev->data;
+ s = c->data;
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+ "upstream timed out");
+ c->timedout = 1;
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ rc = ngx_mail_proxy_read_response(s, s->mail_state);
+
+ if (rc == NGX_AGAIN) {
+ return;
+ }
+
+ if (rc == NGX_ERROR) {
+ ngx_mail_proxy_upstream_error(s);
+ return;
+ }
+
+ switch (s->mail_state) {
+
+ case ngx_smtp_start:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail proxy send ehlo");
+
+ s->connection->log->action = "sending HELO/EHLO to upstream";
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ line.len = sizeof("HELO ") - 1 + cscf->server_name.len + 2;
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+
+ p = ngx_cpymem(line.data,
+ ((s->esmtp || pcf->xclient) ? "EHLO " : "HELO "),
+ sizeof("HELO ") - 1);
+
+ p = ngx_cpymem(p, cscf->server_name.data, cscf->server_name.len);
+ *p++ = CR; *p = LF;
+
+ if (pcf->xclient) {
+ s->mail_state = ngx_smtp_helo_xclient;
+
+ } else if (s->auth_method == NGX_MAIL_AUTH_NONE) {
+ s->mail_state = ngx_smtp_helo_from;
+
+ } else {
+ s->mail_state = ngx_smtp_helo;
+ }
+
+ break;
+
+ case ngx_smtp_helo_xclient:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy send xclient");
+
+ s->connection->log->action = "sending XCLIENT to upstream";
+
+ line.len = sizeof("XCLIENT ADDR= LOGIN= NAME="
+ CRLF) - 1
+ + s->connection->addr_text.len + s->login.len + s->host.len;
+
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ line.len = ngx_sprintf(line.data,
+ "XCLIENT ADDR=%V%s%V NAME=%V" CRLF,
+ &s->connection->addr_text,
+ (s->login.len ? " LOGIN=" : ""), &s->login, &s->host)
+ - line.data;
+
+ if (s->smtp_helo.len) {
+ s->mail_state = ngx_smtp_xclient_helo;
+
+ } else if (s->auth_method == NGX_MAIL_AUTH_NONE) {
+ s->mail_state = ngx_smtp_xclient_from;
+
+ } else {
+ s->mail_state = ngx_smtp_xclient;
+ }
+
+ break;
+
+ case ngx_smtp_xclient_helo:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy send client ehlo");
+
+ s->connection->log->action = "sending client HELO/EHLO to upstream";
+
+ line.len = sizeof("HELO " CRLF) - 1 + s->smtp_helo.len;
+
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ line.len = ngx_sprintf(line.data,
+ ((s->esmtp) ? "EHLO %V" CRLF : "HELO %V" CRLF),
+ &s->smtp_helo)
+ - line.data;
+
+ s->mail_state = (s->auth_method == NGX_MAIL_AUTH_NONE) ?
+ ngx_smtp_helo_from : ngx_smtp_helo;
+
+ break;
+
+ case ngx_smtp_helo_from:
+ case ngx_smtp_xclient_from:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy send mail from");
+
+ s->connection->log->action = "sending MAIL FROM to upstream";
+
+ line.len = s->smtp_from.len + sizeof(CRLF) - 1;
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ p = ngx_cpymem(line.data, s->smtp_from.data, s->smtp_from.len);
+ *p++ = CR; *p = LF;
+
+ s->mail_state = ngx_smtp_from;
+
+ break;
+
+ case ngx_smtp_from:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy send rcpt to");
+
+ s->connection->log->action = "sending RCPT TO to upstream";
+
+ line.len = s->smtp_to.len + sizeof(CRLF) - 1;
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ p = ngx_cpymem(line.data, s->smtp_to.data, s->smtp_to.len);
+ *p++ = CR; *p = LF;
+
+ s->mail_state = ngx_smtp_to;
+
+ break;
+
+ case ngx_smtp_helo:
+ case ngx_smtp_xclient:
+ case ngx_smtp_to:
+
+ b = s->proxy->buffer;
+
+ if (s->auth_method == NGX_MAIL_AUTH_NONE) {
+ b->pos = b->start;
+
+ } else {
+ ngx_memcpy(b->start, smtp_auth_ok, sizeof(smtp_auth_ok) - 1);
+ b->last = b->start + sizeof(smtp_auth_ok) - 1;
+ }
+
+ s->connection->read->handler = ngx_mail_proxy_handler;
+ s->connection->write->handler = ngx_mail_proxy_handler;
+ rev->handler = ngx_mail_proxy_handler;
+ c->write->handler = ngx_mail_proxy_handler;
+
+ pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+ ngx_add_timer(s->connection->read, pcf->timeout);
+ ngx_del_timer(c->read);
+
+ c->log->action = NULL;
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in");
+
+ ngx_mail_proxy_handler(s->connection->write);
+
+ return;
+
+ default:
+#if (NGX_SUPPRESS_WARN)
+ ngx_str_null(&line);
+#endif
+ break;
+ }
+
+ if (c->send(c, line.data, line.len) < (ssize_t) line.len) {
+ /*
+ * we treat the incomplete sending as NGX_ERROR
+ * because it is very strange here
+ */
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ s->proxy->buffer->pos = s->proxy->buffer->start;
+ s->proxy->buffer->last = s->proxy->buffer->start;
+}
+
+
+static void
+ngx_mail_proxy_dummy_handler(ngx_event_t *wev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, wev->log, 0, "mail proxy dummy handler");
+
+ if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+ c = wev->data;
+ s = c->data;
+
+ ngx_mail_proxy_close_session(s);
+ }
+}
+
+
+static ngx_int_t
+ngx_mail_proxy_read_response(ngx_mail_session_t *s, ngx_uint_t state)
+{
+ u_char *p;
+ ssize_t n;
+ ngx_buf_t *b;
+ ngx_mail_proxy_conf_t *pcf;
+
+ s->connection->log->action = "reading response from upstream";
+
+ b = s->proxy->buffer;
+
+ n = s->proxy->upstream.connection->recv(s->proxy->upstream.connection,
+ b->last, b->end - b->last);
+
+ if (n == NGX_ERROR || n == 0) {
+ return NGX_ERROR;
+ }
+
+ if (n == NGX_AGAIN) {
+ return NGX_AGAIN;
+ }
+
+ b->last += n;
+
+ if (b->last - b->pos < 4) {
+ return NGX_AGAIN;
+ }
+
+ if (*(b->last - 2) != CR || *(b->last - 1) != LF) {
+ if (b->last == b->end) {
+ *(b->last - 1) = '\0';
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "upstream sent too long response line: \"%s\"",
+ b->pos);
+ return NGX_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ p = b->pos;
+
+ switch (s->protocol) {
+
+ case NGX_MAIL_POP3_PROTOCOL:
+ if (p[0] == '+' && p[1] == 'O' && p[2] == 'K') {
+ return NGX_OK;
+ }
+ break;
+
+ case NGX_MAIL_IMAP_PROTOCOL:
+ switch (state) {
+
+ case ngx_imap_start:
+ if (p[0] == '*' && p[1] == ' ' && p[2] == 'O' && p[3] == 'K') {
+ return NGX_OK;
+ }
+ break;
+
+ case ngx_imap_login:
+ case ngx_imap_user:
+ if (p[0] == '+') {
+ return NGX_OK;
+ }
+ break;
+
+ case ngx_imap_passwd:
+ if (ngx_strncmp(p, s->tag.data, s->tag.len) == 0) {
+ p += s->tag.len;
+ if (p[0] == 'O' && p[1] == 'K') {
+ return NGX_OK;
+ }
+ }
+ break;
+ }
+
+ break;
+
+ default: /* NGX_MAIL_SMTP_PROTOCOL */
+ switch (state) {
+
+ case ngx_smtp_start:
+ if (p[0] == '2' && p[1] == '2' && p[2] == '0') {
+ return NGX_OK;
+ }
+ break;
+
+ case ngx_smtp_helo:
+ case ngx_smtp_helo_xclient:
+ case ngx_smtp_helo_from:
+ case ngx_smtp_from:
+ if (p[0] == '2' && p[1] == '5' && p[2] == '0') {
+ return NGX_OK;
+ }
+ break;
+
+ case ngx_smtp_xclient:
+ case ngx_smtp_xclient_from:
+ case ngx_smtp_xclient_helo:
+ if (p[0] == '2' && (p[1] == '2' || p[1] == '5') && p[2] == '0') {
+ return NGX_OK;
+ }
+ break;
+
+ case ngx_smtp_to:
+ return NGX_OK;
+ }
+
+ break;
+ }
+
+ pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+
+ if (pcf->pass_error_message == 0) {
+ *(b->last - 2) = '\0';
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "upstream sent invalid response: \"%s\"", p);
+ return NGX_ERROR;
+ }
+
+ s->out.len = b->last - p - 2;
+ s->out.data = p;
+
+ ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
+ "upstream sent invalid response: \"%V\"", &s->out);
+
+ s->out.len = b->last - b->pos;
+ s->out.data = b->pos;
+
+ return NGX_ERROR;
+}
+
+
+static void
+ngx_mail_proxy_handler(ngx_event_t *ev)
+{
+ char *action, *recv_action, *send_action;
+ size_t size;
+ ssize_t n;
+ ngx_buf_t *b;
+ ngx_uint_t do_write;
+ ngx_connection_t *c, *src, *dst;
+ ngx_mail_session_t *s;
+ ngx_mail_proxy_conf_t *pcf;
+
+ c = ev->data;
+ s = c->data;
+
+ if (ev->timedout) {
+ c->log->action = "proxying";
+
+ if (c == s->connection) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+ "client timed out");
+ c->timedout = 1;
+
+ } else {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+ "upstream timed out");
+ }
+
+ ngx_mail_proxy_close_session(s);
+ return;
+ }
+
+ if (c == s->connection) {
+ if (ev->write) {
+ recv_action = "proxying and reading from upstream";
+ send_action = "proxying and sending to client";
+ src = s->proxy->upstream.connection;
+ dst = c;
+ b = s->proxy->buffer;
+
+ } else {
+ recv_action = "proxying and reading from client";
+ send_action = "proxying and sending to upstream";
+ src = c;
+ dst = s->proxy->upstream.connection;
+ b = s->buffer;
+ }
+
+ } else {
+ if (ev->write) {
+ recv_action = "proxying and reading from client";
+ send_action = "proxying and sending to upstream";
+ src = s->connection;
+ dst = c;
+ b = s->buffer;
+
+ } else {
+ recv_action = "proxying and reading from upstream";
+ send_action = "proxying and sending to client";
+ src = c;
+ dst = s->connection;
+ b = s->proxy->buffer;
+ }
+ }
+
+ do_write = ev->write ? 1 : 0;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_MAIL, ev->log, 0,
+ "mail proxy handler: %d, #%d > #%d",
+ do_write, src->fd, dst->fd);
+
+ for ( ;; ) {
+
+ if (do_write) {
+
+ size = b->last - b->pos;
+
+ if (size && dst->write->ready) {
+ c->log->action = send_action;
+
+ n = dst->send(dst, b->pos, size);
+
+ if (n == NGX_ERROR) {
+ ngx_mail_proxy_close_session(s);
+ return;
+ }
+
+ if (n > 0) {
+ b->pos += n;
+
+ if (b->pos == b->last) {
+ b->pos = b->start;
+ b->last = b->start;
+ }
+ }
+ }
+ }
+
+ size = b->end - b->last;
+
+ if (size && src->read->ready) {
+ c->log->action = recv_action;
+
+ n = src->recv(src, b->last, size);
+
+ if (n == NGX_AGAIN || n == 0) {
+ break;
+ }
+
+ if (n > 0) {
+ do_write = 1;
+ b->last += n;
+
+ continue;
+ }
+
+ if (n == NGX_ERROR) {
+ src->read->eof = 1;
+ }
+ }
+
+ break;
+ }
+
+ c->log->action = "proxying";
+
+ if ((s->connection->read->eof && s->buffer->pos == s->buffer->last)
+ || (s->proxy->upstream.connection->read->eof
+ && s->proxy->buffer->pos == s->proxy->buffer->last)
+ || (s->connection->read->eof
+ && s->proxy->upstream.connection->read->eof))
+ {
+ action = c->log->action;
+ c->log->action = NULL;
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "proxied session done");
+ c->log->action = action;
+
+ ngx_mail_proxy_close_session(s);
+ return;
+ }
+
+ if (ngx_handle_write_event(dst->write, 0) != NGX_OK) {
+ ngx_mail_proxy_close_session(s);
+ return;
+ }
+
+ if (ngx_handle_read_event(dst->read, 0) != NGX_OK) {
+ ngx_mail_proxy_close_session(s);
+ return;
+ }
+
+ if (ngx_handle_write_event(src->write, 0) != NGX_OK) {
+ ngx_mail_proxy_close_session(s);
+ return;
+ }
+
+ if (ngx_handle_read_event(src->read, 0) != NGX_OK) {
+ ngx_mail_proxy_close_session(s);
+ return;
+ }
+
+ if (c == s->connection) {
+ pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+ ngx_add_timer(c->read, pcf->timeout);
+ }
+}
+
+
+static void
+ngx_mail_proxy_upstream_error(ngx_mail_session_t *s)
+{
+ if (s->proxy->upstream.connection) {
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "close mail proxy connection: %d",
+ s->proxy->upstream.connection->fd);
+
+ ngx_close_connection(s->proxy->upstream.connection);
+ }
+
+ if (s->out.len == 0) {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ s->quit = 1;
+ ngx_mail_send(s->connection->write);
+}
+
+
+static void
+ngx_mail_proxy_internal_server_error(ngx_mail_session_t *s)
+{
+ if (s->proxy->upstream.connection) {
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "close mail proxy connection: %d",
+ s->proxy->upstream.connection->fd);
+
+ ngx_close_connection(s->proxy->upstream.connection);
+ }
+
+ ngx_mail_session_internal_server_error(s);
+}
+
+
+static void
+ngx_mail_proxy_close_session(ngx_mail_session_t *s)
+{
+ if (s->proxy->upstream.connection) {
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "close mail proxy connection: %d",
+ s->proxy->upstream.connection->fd);
+
+ ngx_close_connection(s->proxy->upstream.connection);
+ }
+
+ ngx_mail_close_connection(s->connection);
+}
+
+
+static void *
+ngx_mail_proxy_create_conf(ngx_conf_t *cf)
+{
+ ngx_mail_proxy_conf_t *pcf;
+
+ pcf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_proxy_conf_t));
+ if (pcf == NULL) {
+ return NULL;
+ }
+
+ pcf->enable = NGX_CONF_UNSET;
+ pcf->pass_error_message = NGX_CONF_UNSET;
+ pcf->xclient = NGX_CONF_UNSET;
+ pcf->buffer_size = NGX_CONF_UNSET_SIZE;
+ pcf->timeout = NGX_CONF_UNSET_MSEC;
+
+ return pcf;
+}
+
+
+static char *
+ngx_mail_proxy_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_mail_proxy_conf_t *prev = parent;
+ ngx_mail_proxy_conf_t *conf = child;
+
+ ngx_conf_merge_value(conf->enable, prev->enable, 0);
+ ngx_conf_merge_value(conf->pass_error_message, prev->pass_error_message, 0);
+ ngx_conf_merge_value(conf->xclient, prev->xclient, 1);
+ ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size,
+ (size_t) ngx_pagesize);
+ ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 24 * 60 * 60000);
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/mail/ngx_mail_smtp_handler.c b/usr.sbin/nginx/src/mail/ngx_mail_smtp_handler.c
new file mode 100644
index 00000000000..0f69ce9681a
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail_smtp_handler.c
@@ -0,0 +1,871 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_smtp_module.h>
+
+
+static void ngx_mail_smtp_resolve_addr_handler(ngx_resolver_ctx_t *ctx);
+static void ngx_mail_smtp_resolve_name(ngx_event_t *rev);
+static void ngx_mail_smtp_resolve_name_handler(ngx_resolver_ctx_t *ctx);
+static void ngx_mail_smtp_greeting(ngx_mail_session_t *s, ngx_connection_t *c);
+static void ngx_mail_smtp_invalid_pipelining(ngx_event_t *rev);
+static ngx_int_t ngx_mail_smtp_create_buffer(ngx_mail_session_t *s,
+ ngx_connection_t *c);
+
+static ngx_int_t ngx_mail_smtp_helo(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_auth(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_mail(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_starttls(ngx_mail_session_t *s,
+ ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_rset(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_rcpt(ngx_mail_session_t *s, ngx_connection_t *c);
+
+static ngx_int_t ngx_mail_smtp_discard_command(ngx_mail_session_t *s,
+ ngx_connection_t *c, char *err);
+static void ngx_mail_smtp_log_rejected_command(ngx_mail_session_t *s,
+ ngx_connection_t *c, char *err);
+
+
+static u_char smtp_ok[] = "250 2.0.0 OK" CRLF;
+static u_char smtp_bye[] = "221 2.0.0 Bye" CRLF;
+static u_char smtp_starttls[] = "220 2.0.0 Start TLS" CRLF;
+static u_char smtp_next[] = "334 " CRLF;
+static u_char smtp_username[] = "334 VXNlcm5hbWU6" CRLF;
+static u_char smtp_password[] = "334 UGFzc3dvcmQ6" CRLF;
+static u_char smtp_invalid_command[] = "500 5.5.1 Invalid command" CRLF;
+static u_char smtp_invalid_pipelining[] =
+ "503 5.5.0 Improper use of SMTP command pipelining" CRLF;
+static u_char smtp_invalid_argument[] = "501 5.5.4 Invalid argument" CRLF;
+static u_char smtp_auth_required[] = "530 5.7.1 Authentication required" CRLF;
+static u_char smtp_bad_sequence[] = "503 5.5.1 Bad sequence of commands" CRLF;
+
+
+static ngx_str_t smtp_unavailable = ngx_string("[UNAVAILABLE]");
+static ngx_str_t smtp_tempunavail = ngx_string("[TEMPUNAVAIL]");
+
+
+void
+ngx_mail_smtp_init_session(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ struct sockaddr_in *sin;
+ ngx_resolver_ctx_t *ctx;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ if (cscf->resolver == NULL) {
+ s->host = smtp_unavailable;
+ ngx_mail_smtp_greeting(s, c);
+ return;
+ }
+
+ if (c->sockaddr->sa_family != AF_INET) {
+ s->host = smtp_tempunavail;
+ ngx_mail_smtp_greeting(s, c);
+ return;
+ }
+
+ c->log->action = "in resolving client address";
+
+ ctx = ngx_resolve_start(cscf->resolver, NULL);
+ if (ctx == NULL) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ /* AF_INET only */
+
+ sin = (struct sockaddr_in *) c->sockaddr;
+
+ ctx->addr = sin->sin_addr.s_addr;
+ ctx->handler = ngx_mail_smtp_resolve_addr_handler;
+ ctx->data = s;
+ ctx->timeout = cscf->resolver_timeout;
+
+ if (ngx_resolve_addr(ctx) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ }
+}
+
+
+static void
+ngx_mail_smtp_resolve_addr_handler(ngx_resolver_ctx_t *ctx)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+
+ s = ctx->data;
+ c = s->connection;
+
+ if (ctx->state) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "%V could not be resolved (%i: %s)",
+ &c->addr_text, ctx->state,
+ ngx_resolver_strerror(ctx->state));
+
+ if (ctx->state == NGX_RESOLVE_NXDOMAIN) {
+ s->host = smtp_unavailable;
+
+ } else {
+ s->host = smtp_tempunavail;
+ }
+
+ ngx_resolve_addr_done(ctx);
+
+ ngx_mail_smtp_greeting(s, s->connection);
+
+ return;
+ }
+
+ c->log->action = "in resolving client hostname";
+
+ s->host.data = ngx_pstrdup(c->pool, &ctx->name);
+ if (s->host.data == NULL) {
+ ngx_resolve_addr_done(ctx);
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ s->host.len = ctx->name.len;
+
+ ngx_resolve_addr_done(ctx);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "address resolved: %V", &s->host);
+
+ c->read->handler = ngx_mail_smtp_resolve_name;
+
+ ngx_post_event(c->read, &ngx_posted_events);
+}
+
+
+static void
+ngx_mail_smtp_resolve_name(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_resolver_ctx_t *ctx;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ c = rev->data;
+ s = c->data;
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ ctx = ngx_resolve_start(cscf->resolver, NULL);
+ if (ctx == NULL) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ ctx->name = s->host;
+ ctx->type = NGX_RESOLVE_A;
+ ctx->handler = ngx_mail_smtp_resolve_name_handler;
+ ctx->data = s;
+ ctx->timeout = cscf->resolver_timeout;
+
+ if (ngx_resolve_name(ctx) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ }
+}
+
+
+static void
+ngx_mail_smtp_resolve_name_handler(ngx_resolver_ctx_t *ctx)
+{
+ in_addr_t addr;
+ ngx_uint_t i;
+ ngx_connection_t *c;
+ struct sockaddr_in *sin;
+ ngx_mail_session_t *s;
+
+ s = ctx->data;
+ c = s->connection;
+
+ if (ctx->state) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "\"%V\" could not be resolved (%i: %s)",
+ &ctx->name, ctx->state,
+ ngx_resolver_strerror(ctx->state));
+
+ if (ctx->state == NGX_RESOLVE_NXDOMAIN) {
+ s->host = smtp_unavailable;
+
+ } else {
+ s->host = smtp_tempunavail;
+ }
+
+ } else {
+
+ /* AF_INET only */
+
+ sin = (struct sockaddr_in *) c->sockaddr;
+
+ for (i = 0; i < ctx->naddrs; i++) {
+
+ addr = ctx->addrs[i];
+
+ ngx_log_debug4(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "name was resolved to %ud.%ud.%ud.%ud",
+ (ntohl(addr) >> 24) & 0xff,
+ (ntohl(addr) >> 16) & 0xff,
+ (ntohl(addr) >> 8) & 0xff,
+ ntohl(addr) & 0xff);
+
+ if (addr == sin->sin_addr.s_addr) {
+ goto found;
+ }
+ }
+
+ s->host = smtp_unavailable;
+ }
+
+found:
+
+ ngx_resolve_name_done(ctx);
+
+ ngx_mail_smtp_greeting(s, c);
+}
+
+
+static void
+ngx_mail_smtp_greeting(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_msec_t timeout;
+ ngx_mail_core_srv_conf_t *cscf;
+ ngx_mail_smtp_srv_conf_t *sscf;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "smtp greeting for \"%V\"", &s->host);
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+ sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+ timeout = sscf->greeting_delay ? sscf->greeting_delay : cscf->timeout;
+ ngx_add_timer(c->read, timeout);
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ }
+
+ if (sscf->greeting_delay) {
+ c->read->handler = ngx_mail_smtp_invalid_pipelining;
+ return;
+ }
+
+ c->read->handler = ngx_mail_smtp_init_protocol;
+
+ s->out = sscf->greeting;
+
+ ngx_mail_send(c->write);
+}
+
+
+static void
+ngx_mail_smtp_invalid_pipelining(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_core_srv_conf_t *cscf;
+ ngx_mail_smtp_srv_conf_t *sscf;
+
+ c = rev->data;
+ s = c->data;
+
+ c->log->action = "in delay pipelining state";
+
+ if (rev->timedout) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "delay greeting");
+
+ rev->timedout = 0;
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ c->read->handler = ngx_mail_smtp_init_protocol;
+
+ ngx_add_timer(c->read, cscf->timeout);
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+ s->out = sscf->greeting;
+
+ } else {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "invalid pipelining");
+
+ if (s->buffer == NULL) {
+ if (ngx_mail_smtp_create_buffer(s, c) != NGX_OK) {
+ return;
+ }
+ }
+
+ if (ngx_mail_smtp_discard_command(s, c,
+ "client was rejected before greeting: \"%V\"")
+ != NGX_OK)
+ {
+ return;
+ }
+
+ ngx_str_set(&s->out, smtp_invalid_pipelining);
+ }
+
+ ngx_mail_send(c->write);
+}
+
+
+void
+ngx_mail_smtp_init_protocol(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+
+ c = rev->data;
+
+ c->log->action = "in auth state";
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ s = c->data;
+
+ if (s->buffer == NULL) {
+ if (ngx_mail_smtp_create_buffer(s, c) != NGX_OK) {
+ return;
+ }
+ }
+
+ s->mail_state = ngx_smtp_start;
+ c->read->handler = ngx_mail_smtp_auth_state;
+
+ ngx_mail_smtp_auth_state(rev);
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_create_buffer(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_mail_smtp_srv_conf_t *sscf;
+
+ if (ngx_array_init(&s->args, c->pool, 2, sizeof(ngx_str_t)) == NGX_ERROR) {
+ ngx_mail_session_internal_server_error(s);
+ return NGX_ERROR;
+ }
+
+ sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+ s->buffer = ngx_create_temp_buf(c->pool, sscf->client_buffer_size);
+ if (s->buffer == NULL) {
+ ngx_mail_session_internal_server_error(s);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+void
+ngx_mail_smtp_auth_state(ngx_event_t *rev)
+{
+ ngx_int_t rc;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+
+ c = rev->data;
+ s = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "smtp auth state");
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ if (s->out.len) {
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "smtp send handler busy");
+ s->blocked = 1;
+ return;
+ }
+
+ s->blocked = 0;
+
+ rc = ngx_mail_read_command(s, c);
+
+ if (rc == NGX_AGAIN || rc == NGX_ERROR) {
+ return;
+ }
+
+ ngx_str_set(&s->out, smtp_ok);
+
+ if (rc == NGX_OK) {
+ switch (s->mail_state) {
+
+ case ngx_smtp_start:
+
+ switch (s->command) {
+
+ case NGX_SMTP_HELO:
+ case NGX_SMTP_EHLO:
+ rc = ngx_mail_smtp_helo(s, c);
+ break;
+
+ case NGX_SMTP_AUTH:
+ rc = ngx_mail_smtp_auth(s, c);
+ break;
+
+ case NGX_SMTP_QUIT:
+ s->quit = 1;
+ ngx_str_set(&s->out, smtp_bye);
+ break;
+
+ case NGX_SMTP_MAIL:
+ rc = ngx_mail_smtp_mail(s, c);
+ break;
+
+ case NGX_SMTP_RCPT:
+ rc = ngx_mail_smtp_rcpt(s, c);
+ break;
+
+ case NGX_SMTP_RSET:
+ rc = ngx_mail_smtp_rset(s, c);
+ break;
+
+ case NGX_SMTP_NOOP:
+ break;
+
+ case NGX_SMTP_STARTTLS:
+ rc = ngx_mail_smtp_starttls(s, c);
+ ngx_str_set(&s->out, smtp_starttls);
+ break;
+
+ default:
+ rc = NGX_MAIL_PARSE_INVALID_COMMAND;
+ break;
+ }
+
+ break;
+
+ case ngx_smtp_auth_login_username:
+ rc = ngx_mail_auth_login_username(s, c, 0);
+
+ ngx_str_set(&s->out, smtp_password);
+ s->mail_state = ngx_smtp_auth_login_password;
+ break;
+
+ case ngx_smtp_auth_login_password:
+ rc = ngx_mail_auth_login_password(s, c);
+ break;
+
+ case ngx_smtp_auth_plain:
+ rc = ngx_mail_auth_plain(s, c, 0);
+ break;
+
+ case ngx_smtp_auth_cram_md5:
+ rc = ngx_mail_auth_cram_md5(s, c);
+ break;
+ }
+ }
+
+ switch (rc) {
+
+ case NGX_DONE:
+ ngx_mail_auth(s, c);
+ return;
+
+ case NGX_ERROR:
+ ngx_mail_session_internal_server_error(s);
+ return;
+
+ case NGX_MAIL_PARSE_INVALID_COMMAND:
+ s->mail_state = ngx_smtp_start;
+ s->state = 0;
+ ngx_str_set(&s->out, smtp_invalid_command);
+
+ /* fall through */
+
+ case NGX_OK:
+ s->args.nelts = 0;
+ s->buffer->pos = s->buffer->start;
+ s->buffer->last = s->buffer->start;
+
+ if (s->state) {
+ s->arg_start = s->buffer->start;
+ }
+
+ ngx_mail_send(c->write);
+ }
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_helo(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_str_t *arg;
+ ngx_mail_smtp_srv_conf_t *sscf;
+
+ if (s->args.nelts != 1) {
+ ngx_str_set(&s->out, smtp_invalid_argument);
+ s->state = 0;
+ return NGX_OK;
+ }
+
+ arg = s->args.elts;
+
+ s->smtp_helo.len = arg[0].len;
+
+ s->smtp_helo.data = ngx_pnalloc(c->pool, arg[0].len);
+ if (s->smtp_helo.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->smtp_helo.data, arg[0].data, arg[0].len);
+
+ ngx_str_null(&s->smtp_from);
+ ngx_str_null(&s->smtp_to);
+
+ sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+ if (s->command == NGX_SMTP_HELO) {
+ s->out = sscf->server_name;
+
+ } else {
+ s->esmtp = 1;
+
+#if (NGX_MAIL_SSL)
+
+ if (c->ssl == NULL) {
+ ngx_mail_ssl_conf_t *sslcf;
+
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+ if (sslcf->starttls == NGX_MAIL_STARTTLS_ON) {
+ s->out = sscf->starttls_capability;
+ return NGX_OK;
+ }
+
+ if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
+ s->out = sscf->starttls_only_capability;
+ return NGX_OK;
+ }
+ }
+#endif
+
+ s->out = sscf->capability;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_auth(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_int_t rc;
+ ngx_mail_core_srv_conf_t *cscf;
+ ngx_mail_smtp_srv_conf_t *sscf;
+
+#if (NGX_MAIL_SSL)
+ if (ngx_mail_starttls_only(s, c)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+#endif
+
+ if (s->args.nelts == 0) {
+ ngx_str_set(&s->out, smtp_invalid_argument);
+ s->state = 0;
+ return NGX_OK;
+ }
+
+ rc = ngx_mail_auth_parse(s, c);
+
+ switch (rc) {
+
+ case NGX_MAIL_AUTH_LOGIN:
+
+ ngx_str_set(&s->out, smtp_username);
+ s->mail_state = ngx_smtp_auth_login_username;
+
+ return NGX_OK;
+
+ case NGX_MAIL_AUTH_LOGIN_USERNAME:
+
+ ngx_str_set(&s->out, smtp_password);
+ s->mail_state = ngx_smtp_auth_login_password;
+
+ return ngx_mail_auth_login_username(s, c, 1);
+
+ case NGX_MAIL_AUTH_PLAIN:
+
+ ngx_str_set(&s->out, smtp_next);
+ s->mail_state = ngx_smtp_auth_plain;
+
+ return NGX_OK;
+
+ case NGX_MAIL_AUTH_CRAM_MD5:
+
+ sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+ if (!(sscf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ if (s->salt.data == NULL) {
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ if (ngx_mail_salt(s, c, cscf) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ if (ngx_mail_auth_cram_md5_salt(s, c, "334 ", 4) == NGX_OK) {
+ s->mail_state = ngx_smtp_auth_cram_md5;
+ return NGX_OK;
+ }
+
+ return NGX_ERROR;
+ }
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_mail(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ u_char ch;
+ ngx_str_t l;
+ ngx_uint_t i;
+ ngx_mail_smtp_srv_conf_t *sscf;
+
+ sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+ if (!(sscf->auth_methods & NGX_MAIL_AUTH_NONE_ENABLED)) {
+ ngx_mail_smtp_log_rejected_command(s, c, "client was rejected: \"%V\"");
+ ngx_str_set(&s->out, smtp_auth_required);
+ return NGX_OK;
+ }
+
+ /* auth none */
+
+ if (s->smtp_from.len) {
+ ngx_str_set(&s->out, smtp_bad_sequence);
+ return NGX_OK;
+ }
+
+ l.len = s->buffer->last - s->buffer->start;
+ l.data = s->buffer->start;
+
+ for (i = 0; i < l.len; i++) {
+ ch = l.data[i];
+
+ if (ch != CR && ch != LF) {
+ continue;
+ }
+
+ l.data[i] = ' ';
+ }
+
+ while (i) {
+ if (l.data[i - 1] != ' ') {
+ break;
+ }
+
+ i--;
+ }
+
+ l.len = i;
+
+ s->smtp_from.len = l.len;
+
+ s->smtp_from.data = ngx_pnalloc(c->pool, l.len);
+ if (s->smtp_from.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->smtp_from.data, l.data, l.len);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "smtp mail from:\"%V\"", &s->smtp_from);
+
+ ngx_str_set(&s->out, smtp_ok);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_rcpt(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ u_char ch;
+ ngx_str_t l;
+ ngx_uint_t i;
+
+ if (s->smtp_from.len == 0) {
+ ngx_str_set(&s->out, smtp_bad_sequence);
+ return NGX_OK;
+ }
+
+ l.len = s->buffer->last - s->buffer->start;
+ l.data = s->buffer->start;
+
+ for (i = 0; i < l.len; i++) {
+ ch = l.data[i];
+
+ if (ch != CR && ch != LF) {
+ continue;
+ }
+
+ l.data[i] = ' ';
+ }
+
+ while (i) {
+ if (l.data[i - 1] != ' ') {
+ break;
+ }
+
+ i--;
+ }
+
+ l.len = i;
+
+ s->smtp_to.len = l.len;
+
+ s->smtp_to.data = ngx_pnalloc(c->pool, l.len);
+ if (s->smtp_to.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->smtp_to.data, l.data, l.len);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "smtp rcpt to:\"%V\"", &s->smtp_to);
+
+ s->auth_method = NGX_MAIL_AUTH_NONE;
+
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_rset(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_str_null(&s->smtp_from);
+ ngx_str_null(&s->smtp_to);
+ ngx_str_set(&s->out, smtp_ok);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_starttls(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+#if (NGX_MAIL_SSL)
+ ngx_mail_ssl_conf_t *sslcf;
+
+ if (c->ssl == NULL) {
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+ if (sslcf->starttls) {
+
+ /*
+ * RFC3207 requires us to discard any knowledge
+ * obtained from client before STARTTLS.
+ */
+
+ ngx_str_null(&s->smtp_helo);
+ ngx_str_null(&s->smtp_from);
+ ngx_str_null(&s->smtp_to);
+
+ c->read->handler = ngx_mail_starttls_handler;
+ return NGX_OK;
+ }
+ }
+
+#endif
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_discard_command(ngx_mail_session_t *s, ngx_connection_t *c,
+ char *err)
+{
+ ssize_t n;
+
+ n = c->recv(c, s->buffer->last, s->buffer->end - s->buffer->last);
+
+ if (n == NGX_ERROR || n == 0) {
+ ngx_mail_close_connection(c);
+ return NGX_ERROR;
+ }
+
+ if (n > 0) {
+ s->buffer->last += n;
+ }
+
+ if (n == NGX_AGAIN) {
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ ngx_mail_session_internal_server_error(s);
+ return NGX_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ ngx_mail_smtp_log_rejected_command(s, c, err);
+
+ s->buffer->pos = s->buffer->start;
+ s->buffer->last = s->buffer->start;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_mail_smtp_log_rejected_command(ngx_mail_session_t *s, ngx_connection_t *c,
+ char *err)
+{
+ u_char ch;
+ ngx_str_t cmd;
+ ngx_uint_t i;
+
+ if (c->log->log_level < NGX_LOG_INFO) {
+ return;
+ }
+
+ cmd.len = s->buffer->last - s->buffer->start;
+ cmd.data = s->buffer->start;
+
+ for (i = 0; i < cmd.len; i++) {
+ ch = cmd.data[i];
+
+ if (ch != CR && ch != LF) {
+ continue;
+ }
+
+ cmd.data[i] = '_';
+ }
+
+ cmd.len = i;
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, err, &cmd);
+}
diff --git a/usr.sbin/nginx/src/mail/ngx_mail_smtp_module.c b/usr.sbin/nginx/src/mail/ngx_mail_smtp_module.c
new file mode 100644
index 00000000000..463bb6e0b6c
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail_smtp_module.c
@@ -0,0 +1,307 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_smtp_module.h>
+
+
+static void *ngx_mail_smtp_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_mail_smtp_merge_srv_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+
+
+static ngx_conf_bitmask_t ngx_mail_smtp_auth_methods[] = {
+ { ngx_string("plain"), NGX_MAIL_AUTH_PLAIN_ENABLED },
+ { ngx_string("login"), NGX_MAIL_AUTH_LOGIN_ENABLED },
+ { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
+ { ngx_string("none"), NGX_MAIL_AUTH_NONE_ENABLED },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_str_t ngx_mail_smtp_auth_methods_names[] = {
+ ngx_string("PLAIN"),
+ ngx_string("LOGIN"),
+ ngx_null_string, /* APOP */
+ ngx_string("CRAM-MD5"),
+ ngx_null_string /* NONE */
+};
+
+
+static ngx_mail_protocol_t ngx_mail_smtp_protocol = {
+ ngx_string("smtp"),
+ { 25, 465, 587, 0 },
+ NGX_MAIL_SMTP_PROTOCOL,
+
+ ngx_mail_smtp_init_session,
+ ngx_mail_smtp_init_protocol,
+ ngx_mail_smtp_parse_command,
+ ngx_mail_smtp_auth_state,
+
+ ngx_string("451 4.3.2 Internal server error" CRLF)
+};
+
+
+static ngx_command_t ngx_mail_smtp_commands[] = {
+
+ { ngx_string("smtp_client_buffer"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_smtp_srv_conf_t, client_buffer_size),
+ NULL },
+
+ { ngx_string("smtp_greeting_delay"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_smtp_srv_conf_t, greeting_delay),
+ NULL },
+
+ { ngx_string("smtp_capabilities"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+ ngx_mail_capabilities,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_smtp_srv_conf_t, capabilities),
+ NULL },
+
+ { ngx_string("smtp_auth"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_smtp_srv_conf_t, auth_methods),
+ &ngx_mail_smtp_auth_methods },
+
+ ngx_null_command
+};
+
+
+static ngx_mail_module_t ngx_mail_smtp_module_ctx = {
+ &ngx_mail_smtp_protocol, /* protocol */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_mail_smtp_create_srv_conf, /* create server configuration */
+ ngx_mail_smtp_merge_srv_conf /* merge server configuration */
+};
+
+
+ngx_module_t ngx_mail_smtp_module = {
+ NGX_MODULE_V1,
+ &ngx_mail_smtp_module_ctx, /* module context */
+ ngx_mail_smtp_commands, /* module directives */
+ NGX_MAIL_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static void *
+ngx_mail_smtp_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_mail_smtp_srv_conf_t *sscf;
+
+ sscf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_smtp_srv_conf_t));
+ if (sscf == NULL) {
+ return NULL;
+ }
+
+ sscf->client_buffer_size = NGX_CONF_UNSET_SIZE;
+ sscf->greeting_delay = NGX_CONF_UNSET_MSEC;
+
+ if (ngx_array_init(&sscf->capabilities, cf->pool, 4, sizeof(ngx_str_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ return sscf;
+}
+
+
+static char *
+ngx_mail_smtp_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_mail_smtp_srv_conf_t *prev = parent;
+ ngx_mail_smtp_srv_conf_t *conf = child;
+
+ u_char *p, *auth, *last;
+ size_t size;
+ ngx_str_t *c;
+ ngx_uint_t i, m, auth_enabled;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ ngx_conf_merge_size_value(conf->client_buffer_size,
+ prev->client_buffer_size,
+ (size_t) ngx_pagesize);
+
+ ngx_conf_merge_msec_value(conf->greeting_delay,
+ prev->greeting_delay, 0);
+
+ ngx_conf_merge_bitmask_value(conf->auth_methods,
+ prev->auth_methods,
+ (NGX_CONF_BITMASK_SET
+ |NGX_MAIL_AUTH_PLAIN_ENABLED
+ |NGX_MAIL_AUTH_LOGIN_ENABLED));
+
+
+ cscf = ngx_mail_conf_get_module_srv_conf(cf, ngx_mail_core_module);
+
+ size = sizeof("220 ESMTP ready" CRLF) - 1 + cscf->server_name.len;
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->greeting.len = size;
+ conf->greeting.data = p;
+
+ *p++ = '2'; *p++ = '2'; *p++ = '0'; *p++ = ' ';
+ p = ngx_cpymem(p, cscf->server_name.data, cscf->server_name.len);
+ ngx_memcpy(p, " ESMTP ready" CRLF, sizeof(" ESMTP ready" CRLF) - 1);
+
+
+ size = sizeof("250 " CRLF) - 1 + cscf->server_name.len;
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->server_name.len = size;
+ conf->server_name.data = p;
+
+ *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = ' ';
+ p = ngx_cpymem(p, cscf->server_name.data, cscf->server_name.len);
+ *p++ = CR; *p = LF;
+
+
+ if (conf->capabilities.nelts == 0) {
+ conf->capabilities = prev->capabilities;
+ }
+
+ size = sizeof("250-") - 1 + cscf->server_name.len + sizeof(CRLF) - 1;
+
+ c = conf->capabilities.elts;
+ for (i = 0; i < conf->capabilities.nelts; i++) {
+ size += sizeof("250 ") - 1 + c[i].len + sizeof(CRLF) - 1;
+ }
+
+ auth_enabled = 0;
+
+ for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+ m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED;
+ m <<= 1, i++)
+ {
+ if (m & conf->auth_methods) {
+ size += 1 + ngx_mail_smtp_auth_methods_names[i].len;
+ auth_enabled = 1;
+ }
+ }
+
+ if (auth_enabled) {
+ size += sizeof("250 AUTH") - 1 + sizeof(CRLF) - 1;
+ }
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->capability.len = size;
+ conf->capability.data = p;
+
+ last = p;
+
+ *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = '-';
+ p = ngx_cpymem(p, cscf->server_name.data, cscf->server_name.len);
+ *p++ = CR; *p++ = LF;
+
+ for (i = 0; i < conf->capabilities.nelts; i++) {
+ last = p;
+ *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = '-';
+ p = ngx_cpymem(p, c[i].data, c[i].len);
+ *p++ = CR; *p++ = LF;
+ }
+
+ auth = p;
+
+ if (auth_enabled) {
+ last = p;
+
+ *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = ' ';
+ *p++ = 'A'; *p++ = 'U'; *p++ = 'T'; *p++ = 'H';
+
+ for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+ m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED;
+ m <<= 1, i++)
+ {
+ if (m & conf->auth_methods) {
+ *p++ = ' ';
+ p = ngx_cpymem(p, ngx_mail_smtp_auth_methods_names[i].data,
+ ngx_mail_smtp_auth_methods_names[i].len);
+ }
+ }
+
+ *p++ = CR; *p = LF;
+
+ } else {
+ last[3] = ' ';
+ }
+
+ size += sizeof("250 STARTTLS" CRLF) - 1;
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->starttls_capability.len = size;
+ conf->starttls_capability.data = p;
+
+ p = ngx_cpymem(p, conf->capability.data, conf->capability.len);
+
+ p = ngx_cpymem(p, "250 STARTTLS" CRLF, sizeof("250 STARTTLS" CRLF) - 1);
+ *p++ = CR; *p = LF;
+
+ p = conf->starttls_capability.data
+ + (last - conf->capability.data) + 3;
+ *p = '-';
+
+ size = (auth - conf->capability.data)
+ + sizeof("250 STARTTLS" CRLF) - 1;
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->starttls_only_capability.len = size;
+ conf->starttls_only_capability.data = p;
+
+ p = ngx_cpymem(p, conf->capability.data, auth - conf->capability.data);
+
+ ngx_memcpy(p, "250 STARTTLS" CRLF, sizeof("250 STARTTLS" CRLF) - 1);
+
+ if (last < auth) {
+ p = conf->starttls_only_capability.data
+ + (last - conf->capability.data) + 3;
+ *p = '-';
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/usr.sbin/nginx/src/mail/ngx_mail_smtp_module.h b/usr.sbin/nginx/src/mail/ngx_mail_smtp_module.h
new file mode 100644
index 00000000000..21f2287be03
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail_smtp_module.h
@@ -0,0 +1,44 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_MAIL_SMTP_MODULE_H_INCLUDED_
+#define _NGX_MAIL_SMTP_MODULE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_mail.h>
+#include <ngx_mail_smtp_module.h>
+
+
+typedef struct {
+ ngx_msec_t greeting_delay;
+
+ size_t client_buffer_size;
+
+ ngx_str_t capability;
+ ngx_str_t starttls_capability;
+ ngx_str_t starttls_only_capability;
+
+ ngx_str_t server_name;
+ ngx_str_t greeting;
+
+ ngx_uint_t auth_methods;
+
+ ngx_array_t capabilities;
+} ngx_mail_smtp_srv_conf_t;
+
+
+void ngx_mail_smtp_init_session(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_smtp_init_protocol(ngx_event_t *rev);
+void ngx_mail_smtp_auth_state(ngx_event_t *rev);
+ngx_int_t ngx_mail_smtp_parse_command(ngx_mail_session_t *s);
+
+
+extern ngx_module_t ngx_mail_smtp_module;
+
+
+#endif /* _NGX_MAIL_SMTP_MODULE_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/mail/ngx_mail_ssl_module.c b/usr.sbin/nginx/src/mail/ngx_mail_ssl_module.c
new file mode 100644
index 00000000000..5767a2fd410
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail_ssl_module.c
@@ -0,0 +1,485 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_mail.h>
+
+
+#define NGX_DEFAULT_CIPHERS "HIGH:!aNULL:!MD5"
+#define NGX_DEFAULT_ECDH_CURVE "prime256v1"
+
+
+static void *ngx_mail_ssl_create_conf(ngx_conf_t *cf);
+static char *ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child);
+
+static char *ngx_mail_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_mail_ssl_starttls(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_mail_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_conf_enum_t ngx_http_starttls_state[] = {
+ { ngx_string("off"), NGX_MAIL_STARTTLS_OFF },
+ { ngx_string("on"), NGX_MAIL_STARTTLS_ON },
+ { ngx_string("only"), NGX_MAIL_STARTTLS_ONLY },
+ { ngx_null_string, 0 }
+};
+
+
+
+static ngx_conf_bitmask_t ngx_mail_ssl_protocols[] = {
+ { ngx_string("SSLv2"), NGX_SSL_SSLv2 },
+ { ngx_string("SSLv3"), NGX_SSL_SSLv3 },
+ { ngx_string("TLSv1"), NGX_SSL_TLSv1 },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t ngx_mail_ssl_commands[] = {
+
+ { ngx_string("ssl"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+ ngx_mail_ssl_enable,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, enable),
+ NULL },
+
+ { ngx_string("starttls"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_mail_ssl_starttls,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, starttls),
+ ngx_http_starttls_state },
+
+ { ngx_string("ssl_certificate"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, certificate),
+ NULL },
+
+ { ngx_string("ssl_certificate_key"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, certificate_key),
+ NULL },
+
+ { ngx_string("ssl_dhparam"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, dhparam),
+ NULL },
+
+ { ngx_string("ssl_ecdh_curve"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, ecdh_curve),
+ NULL },
+
+ { ngx_string("ssl_protocols"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, protocols),
+ &ngx_mail_ssl_protocols },
+
+ { ngx_string("ssl_ciphers"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, ciphers),
+ NULL },
+
+ { ngx_string("ssl_prefer_server_ciphers"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, prefer_server_ciphers),
+ NULL },
+
+ { ngx_string("ssl_session_cache"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE12,
+ ngx_mail_ssl_session_cache,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("ssl_session_timeout"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_sec_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, session_timeout),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_mail_module_t ngx_mail_ssl_module_ctx = {
+ NULL, /* protocol */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_mail_ssl_create_conf, /* create server configuration */
+ ngx_mail_ssl_merge_conf /* merge server configuration */
+};
+
+
+ngx_module_t ngx_mail_ssl_module = {
+ NGX_MODULE_V1,
+ &ngx_mail_ssl_module_ctx, /* module context */
+ ngx_mail_ssl_commands, /* module directives */
+ NGX_MAIL_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_mail_ssl_sess_id_ctx = ngx_string("MAIL");
+
+
+static void *
+ngx_mail_ssl_create_conf(ngx_conf_t *cf)
+{
+ ngx_mail_ssl_conf_t *scf;
+
+ scf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_ssl_conf_t));
+ if (scf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * scf->protocols = 0;
+ * scf->certificate = { 0, NULL };
+ * scf->certificate_key = { 0, NULL };
+ * scf->dhparam = { 0, NULL };
+ * scf->ecdh_curve = { 0, NULL };
+ * scf->ciphers = { 0, NULL };
+ * scf->shm_zone = NULL;
+ */
+
+ scf->enable = NGX_CONF_UNSET;
+ scf->starttls = NGX_CONF_UNSET_UINT;
+ scf->prefer_server_ciphers = NGX_CONF_UNSET;
+ scf->builtin_session_cache = NGX_CONF_UNSET;
+ scf->session_timeout = NGX_CONF_UNSET;
+
+ return scf;
+}
+
+
+static char *
+ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_mail_ssl_conf_t *prev = parent;
+ ngx_mail_ssl_conf_t *conf = child;
+
+ char *mode;
+ ngx_pool_cleanup_t *cln;
+
+ ngx_conf_merge_value(conf->enable, prev->enable, 0);
+ ngx_conf_merge_uint_value(conf->starttls, prev->starttls,
+ NGX_MAIL_STARTTLS_OFF);
+
+ ngx_conf_merge_value(conf->session_timeout,
+ prev->session_timeout, 300);
+
+ ngx_conf_merge_value(conf->prefer_server_ciphers,
+ prev->prefer_server_ciphers, 0);
+
+ ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols,
+ (NGX_CONF_BITMASK_SET|NGX_SSL_SSLv3|NGX_SSL_TLSv1));
+
+ ngx_conf_merge_str_value(conf->certificate, prev->certificate, "");
+ ngx_conf_merge_str_value(conf->certificate_key, prev->certificate_key, "");
+
+ ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, "");
+
+ ngx_conf_merge_str_value(conf->ecdh_curve, prev->ecdh_curve,
+ NGX_DEFAULT_ECDH_CURVE);
+
+ ngx_conf_merge_str_value(conf->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS);
+
+
+ conf->ssl.log = cf->log;
+
+ if (conf->enable) {
+ mode = "ssl";
+
+ } else if (conf->starttls != NGX_MAIL_STARTTLS_OFF) {
+ mode = "starttls";
+
+ } else {
+ mode = "";
+ }
+
+ if (*mode) {
+
+ if (conf->certificate.len == 0) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate\" is defined for "
+ "the \"%s\" directive in %s:%ui",
+ mode, conf->file, conf->line);
+ return NGX_CONF_ERROR;
+ }
+
+ if (conf->certificate_key.len == 0) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate_key\" is defined for "
+ "the \"%s\" directive in %s:%ui",
+ mode, conf->file, conf->line);
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+
+ if (conf->certificate.len == 0) {
+ return NGX_CONF_OK;
+ }
+
+ if (conf->certificate_key.len == 0) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate_key\" is defined "
+ "for certificate \"%V\"",
+ &conf->certificate);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (ngx_ssl_create(&conf->ssl, conf->protocols, NULL) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ cln = ngx_pool_cleanup_add(cf->pool, 0);
+ if (cln == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ cln->handler = ngx_ssl_cleanup_ctx;
+ cln->data = &conf->ssl;
+
+ if (ngx_ssl_certificate(cf, &conf->ssl, &conf->certificate,
+ &conf->certificate_key)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ if (conf->ciphers.len) {
+ if (SSL_CTX_set_cipher_list(conf->ssl.ctx,
+ (const char *) conf->ciphers.data)
+ == 0)
+ {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "SSL_CTX_set_cipher_list(\"%V\") failed",
+ &conf->ciphers);
+ }
+ }
+
+ if (conf->prefer_server_ciphers) {
+ SSL_CTX_set_options(conf->ssl.ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
+ }
+
+ SSL_CTX_set_tmp_rsa_callback(conf->ssl.ctx, ngx_ssl_rsa512_key_callback);
+
+ if (ngx_ssl_dhparam(cf, &conf->ssl, &conf->dhparam) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_value(conf->builtin_session_cache,
+ prev->builtin_session_cache, NGX_SSL_NONE_SCACHE);
+
+ if (conf->shm_zone == NULL) {
+ conf->shm_zone = prev->shm_zone;
+ }
+
+ if (ngx_ssl_session_cache(&conf->ssl, &ngx_mail_ssl_sess_id_ctx,
+ conf->builtin_session_cache,
+ conf->shm_zone, conf->session_timeout)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_mail_ssl_conf_t *scf = conf;
+
+ char *rv;
+
+ rv = ngx_conf_set_flag_slot(cf, cmd, conf);
+
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+
+ if (scf->enable && (ngx_int_t) scf->starttls > NGX_MAIL_STARTTLS_OFF) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "\"starttls\" directive conflicts with \"ssl on\"");
+ return NGX_CONF_ERROR;
+ }
+
+ scf->file = cf->conf_file->file.name.data;
+ scf->line = cf->conf_file->line;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_ssl_starttls(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_mail_ssl_conf_t *scf = conf;
+
+ char *rv;
+
+ rv = ngx_conf_set_enum_slot(cf, cmd, conf);
+
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+
+ if (scf->enable == 1 && (ngx_int_t) scf->starttls > NGX_MAIL_STARTTLS_OFF) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "\"ssl\" directive conflicts with \"starttls\"");
+ return NGX_CONF_ERROR;
+ }
+
+ scf->file = cf->conf_file->file.name.data;
+ scf->line = cf->conf_file->line;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_mail_ssl_conf_t *scf = conf;
+
+ size_t len;
+ ngx_str_t *value, name, size;
+ ngx_int_t n;
+ ngx_uint_t i, j;
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+
+ if (ngx_strcmp(value[i].data, "off") == 0) {
+ scf->builtin_session_cache = NGX_SSL_NO_SCACHE;
+ continue;
+ }
+
+ if (ngx_strcmp(value[i].data, "none") == 0) {
+ scf->builtin_session_cache = NGX_SSL_NONE_SCACHE;
+ continue;
+ }
+
+ if (ngx_strcmp(value[i].data, "builtin") == 0) {
+ scf->builtin_session_cache = NGX_SSL_DFLT_BUILTIN_SCACHE;
+ continue;
+ }
+
+ if (value[i].len > sizeof("builtin:") - 1
+ && ngx_strncmp(value[i].data, "builtin:", sizeof("builtin:") - 1)
+ == 0)
+ {
+ n = ngx_atoi(value[i].data + sizeof("builtin:") - 1,
+ value[i].len - (sizeof("builtin:") - 1));
+
+ if (n == NGX_ERROR) {
+ goto invalid;
+ }
+
+ scf->builtin_session_cache = n;
+
+ continue;
+ }
+
+ if (value[i].len > sizeof("shared:") - 1
+ && ngx_strncmp(value[i].data, "shared:", sizeof("shared:") - 1)
+ == 0)
+ {
+ len = 0;
+
+ for (j = sizeof("shared:") - 1; j < value[i].len; j++) {
+ if (value[i].data[j] == ':') {
+ break;
+ }
+
+ len++;
+ }
+
+ if (len == 0) {
+ goto invalid;
+ }
+
+ name.len = len;
+ name.data = value[i].data + sizeof("shared:") - 1;
+
+ size.len = value[i].len - j - 1;
+ size.data = name.data + len + 1;
+
+ n = ngx_parse_size(&size);
+
+ if (n == NGX_ERROR) {
+ goto invalid;
+ }
+
+ if (n < (ngx_int_t) (8 * ngx_pagesize)) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "session cache \"%V\" is too small",
+ &value[i]);
+
+ return NGX_CONF_ERROR;
+ }
+
+ scf->shm_zone = ngx_shared_memory_add(cf, &name, n,
+ &ngx_mail_ssl_module);
+ if (scf->shm_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ goto invalid;
+ }
+
+ if (scf->shm_zone && scf->builtin_session_cache == NGX_CONF_UNSET) {
+ scf->builtin_session_cache = NGX_SSL_NO_BUILTIN_SCACHE;
+ }
+
+ return NGX_CONF_OK;
+
+invalid:
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid session cache \"%V\"", &value[i]);
+
+ return NGX_CONF_ERROR;
+}
diff --git a/usr.sbin/nginx/src/mail/ngx_mail_ssl_module.h b/usr.sbin/nginx/src/mail/ngx_mail_ssl_module.h
new file mode 100644
index 00000000000..61a275b36f5
--- /dev/null
+++ b/usr.sbin/nginx/src/mail/ngx_mail_ssl_module.h
@@ -0,0 +1,51 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_MAIL_SSL_H_INCLUDED_
+#define _NGX_MAIL_SSL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_mail.h>
+
+
+#define NGX_MAIL_STARTTLS_OFF 0
+#define NGX_MAIL_STARTTLS_ON 1
+#define NGX_MAIL_STARTTLS_ONLY 2
+
+
+typedef struct {
+ ngx_flag_t enable;
+ ngx_flag_t prefer_server_ciphers;
+
+ ngx_ssl_t ssl;
+
+ ngx_uint_t starttls;
+ ngx_uint_t protocols;
+
+ ssize_t builtin_session_cache;
+
+ time_t session_timeout;
+
+ ngx_str_t certificate;
+ ngx_str_t certificate_key;
+ ngx_str_t dhparam;
+ ngx_str_t ecdh_curve;
+
+ ngx_str_t ciphers;
+
+ ngx_shm_zone_t *shm_zone;
+
+ u_char *file;
+ ngx_uint_t line;
+} ngx_mail_ssl_conf_t;
+
+
+extern ngx_module_t ngx_mail_ssl_module;
+
+
+#endif /* _NGX_MAIL_SSL_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/misc/ngx_cpp_test_module.cpp b/usr.sbin/nginx/src/misc/ngx_cpp_test_module.cpp
new file mode 100644
index 00000000000..8f87dcdad60
--- /dev/null
+++ b/usr.sbin/nginx/src/misc/ngx_cpp_test_module.cpp
@@ -0,0 +1,27 @@
+
+// stub module to test header files' C++ compatibilty
+
+extern "C" {
+ #include <ngx_config.h>
+ #include <ngx_core.h>
+ #include <ngx_event.h>
+ #include <ngx_event_connect.h>
+ #include <ngx_event_pipe.h>
+
+ #include <ngx_http.h>
+
+ #include <ngx_mail.h>
+ #include <ngx_mail_pop3_module.h>
+ #include <ngx_mail_imap_module.h>
+ #include <ngx_mail_smtp_module.h>
+}
+
+// nginx header files should go before other, because they define 64-bit off_t
+// #include <string>
+
+
+void
+ngx_cpp_test_handler(void *data)
+{
+ return;
+}
diff --git a/usr.sbin/nginx/src/misc/ngx_google_perftools_module.c b/usr.sbin/nginx/src/misc/ngx_google_perftools_module.c
new file mode 100644
index 00000000000..8be5b97ec9b
--- /dev/null
+++ b/usr.sbin/nginx/src/misc/ngx_google_perftools_module.c
@@ -0,0 +1,125 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+/*
+ * declare Profiler interface here because
+ * <google/profiler.h> is C++ header file
+ */
+
+int ProfilerStart(u_char* fname);
+void ProfilerStop(void);
+void ProfilerRegisterThread(void);
+
+
+static void *ngx_google_perftools_create_conf(ngx_cycle_t *cycle);
+static ngx_int_t ngx_google_perftools_worker(ngx_cycle_t *cycle);
+
+
+typedef struct {
+ ngx_str_t profiles;
+} ngx_google_perftools_conf_t;
+
+
+static ngx_command_t ngx_google_perftools_commands[] = {
+
+ { ngx_string("google_perftools_profiles"),
+ NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ 0,
+ offsetof(ngx_google_perftools_conf_t, profiles),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_core_module_t ngx_google_perftools_module_ctx = {
+ ngx_string("google_perftools"),
+ ngx_google_perftools_create_conf,
+ NULL
+};
+
+
+ngx_module_t ngx_google_perftools_module = {
+ NGX_MODULE_V1,
+ &ngx_google_perftools_module_ctx, /* module context */
+ ngx_google_perftools_commands, /* module directives */
+ NGX_CORE_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ ngx_google_perftools_worker, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static void *
+ngx_google_perftools_create_conf(ngx_cycle_t *cycle)
+{
+ ngx_google_perftools_conf_t *gptcf;
+
+ gptcf = ngx_pcalloc(cycle->pool, sizeof(ngx_google_perftools_conf_t));
+ if (gptcf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc()
+ *
+ * gptcf->profiles = { 0, NULL };
+ */
+
+ return gptcf;
+}
+
+
+static ngx_int_t
+ngx_google_perftools_worker(ngx_cycle_t *cycle)
+{
+ u_char *profile;
+ ngx_google_perftools_conf_t *gptcf;
+
+ gptcf = (ngx_google_perftools_conf_t *)
+ ngx_get_conf(cycle->conf_ctx, ngx_google_perftools_module);
+
+ if (gptcf->profiles.len == 0) {
+ return NGX_OK;
+ }
+
+ profile = ngx_alloc(gptcf->profiles.len + NGX_INT_T_LEN + 2, cycle->log);
+ if (profile == NULL) {
+ return NGX_OK;
+ }
+
+ if (getenv("CPUPROFILE")) {
+ /* disable inherited Profiler enabled in master process */
+ ProfilerStop();
+ }
+
+ ngx_sprintf(profile, "%V.%d%Z", &gptcf->profiles, ngx_pid);
+
+ if (ProfilerStart(profile)) {
+ /* start ITIMER_PROF timer */
+ ProfilerRegisterThread();
+
+ } else {
+ ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_errno,
+ "ProfilerStart(%s) failed", profile);
+ }
+
+ ngx_free(profile);
+
+ return NGX_OK;
+}
+
+
+/* ProfilerStop() is called on Profiler destruction */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_aio_read.c b/usr.sbin/nginx/src/os/unix/ngx_aio_read.c
new file mode 100644
index 00000000000..1e41bac5f8d
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_aio_read.c
@@ -0,0 +1,108 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+extern int ngx_kqueue;
+
+
+ssize_t
+ngx_aio_read(ngx_connection_t *c, u_char *buf, size_t size)
+{
+ int n;
+ ngx_event_t *rev;
+
+ rev = c->read;
+
+ if (!rev->ready) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0, "second aio post");
+ return NGX_AGAIN;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "rev->complete: %d", rev->complete);
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "aio size: %d", size);
+
+ if (!rev->complete) {
+ ngx_memzero(&rev->aiocb, sizeof(struct aiocb));
+
+ rev->aiocb.aio_fildes = c->fd;
+ rev->aiocb.aio_buf = buf;
+ rev->aiocb.aio_nbytes = size;
+
+#if (NGX_HAVE_KQUEUE)
+ rev->aiocb.aio_sigevent.sigev_notify_kqueue = ngx_kqueue;
+ rev->aiocb.aio_sigevent.sigev_notify = SIGEV_KEVENT;
+ rev->aiocb.aio_sigevent.sigev_value.sigval_ptr = rev;
+#endif
+
+ if (aio_read(&rev->aiocb) == -1) {
+ ngx_log_error(NGX_LOG_CRIT, rev->log, ngx_errno,
+ "aio_read() failed");
+ rev->error = 1;
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "aio_read: #%d OK", c->fd);
+
+ rev->active = 1;
+ rev->ready = 0;
+ }
+
+ rev->complete = 0;
+
+ n = aio_error(&rev->aiocb);
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, "aio_error() failed");
+ rev->error = 1;
+ return NGX_ERROR;
+ }
+
+ if (n != 0) {
+ if (n == NGX_EINPROGRESS) {
+ if (rev->ready) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, n,
+ "aio_read() still in progress");
+ rev->ready = 0;
+ }
+ return NGX_AGAIN;
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, c->log, n, "aio_read() failed");
+ rev->error = 1;
+ rev->ready = 0;
+ return NGX_ERROR;
+ }
+
+ n = aio_return(&rev->aiocb);
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+ "aio_return() failed");
+
+ rev->error = 1;
+ rev->ready = 0;
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, rev->log, 0,
+ "aio_read: #%d %d", c->fd, n);
+
+ if (n == 0) {
+ rev->eof = 1;
+ rev->ready = 0;
+ } else {
+ rev->ready = 1;
+ }
+
+ rev->active = 0;
+
+ return n;
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_aio_read_chain.c b/usr.sbin/nginx/src/os/unix/ngx_aio_read_chain.c
new file mode 100644
index 00000000000..28b9c8fa94a
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_aio_read_chain.c
@@ -0,0 +1,77 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ssize_t
+ngx_aio_read_chain(ngx_connection_t *c, ngx_chain_t *cl)
+{
+ int n;
+ u_char *buf, *prev;
+ size_t size;
+ ssize_t total;
+
+ if (c->read->pending_eof) {
+ c->read->ready = 0;
+ return 0;
+ }
+
+ total = 0;
+
+ while (cl) {
+
+ /* we can post the single aio operation only */
+
+ if (!c->read->ready) {
+ return total ? total : NGX_AGAIN;
+ }
+
+ buf = cl->buf->last;
+ prev = cl->buf->last;
+ size = 0;
+
+ /* coalesce the neighbouring bufs */
+
+ while (cl && prev == cl->buf->last) {
+ size += cl->buf->end - cl->buf->last;
+ prev = cl->buf->end;
+ cl = cl->next;
+ }
+
+ n = ngx_aio_read(c, buf, size);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "aio_read: %d", n);
+
+ if (n == NGX_AGAIN) {
+ return total ? total : NGX_AGAIN;
+ }
+
+ if (n == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (n == 0) {
+ c->read->pending_eof = 1;
+ if (total) {
+ c->read->eof = 0;
+ c->read->ready = 1;
+ }
+ return total;
+ }
+
+ if (n > 0) {
+ total += n;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "aio_read total: %d", total);
+ }
+
+ return total ? total : NGX_AGAIN;
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_aio_write.c b/usr.sbin/nginx/src/os/unix/ngx_aio_write.c
new file mode 100644
index 00000000000..9138af16aa7
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_aio_write.c
@@ -0,0 +1,108 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+extern int ngx_kqueue;
+
+
+ssize_t
+ngx_aio_write(ngx_connection_t *c, u_char *buf, size_t size)
+{
+ int n;
+ ngx_event_t *wev;
+
+ wev = c->write;
+
+ if (!wev->ready) {
+ return NGX_AGAIN;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, wev->log, 0,
+ "aio: wev->complete: %d", wev->complete);
+
+ if (!wev->complete) {
+ ngx_memzero(&wev->aiocb, sizeof(struct aiocb));
+
+ wev->aiocb.aio_fildes = c->fd;
+ wev->aiocb.aio_buf = buf;
+ wev->aiocb.aio_nbytes = size;
+
+#if (NGX_HAVE_KQUEUE)
+ wev->aiocb.aio_sigevent.sigev_notify_kqueue = ngx_kqueue;
+ wev->aiocb.aio_sigevent.sigev_notify = SIGEV_KEVENT;
+ wev->aiocb.aio_sigevent.sigev_value.sigval_ptr = wev;
+#endif
+
+ if (aio_write(&wev->aiocb) == -1) {
+ ngx_log_error(NGX_LOG_CRIT, wev->log, ngx_errno,
+ "aio_write() failed");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, wev->log, 0, "aio_write: OK");
+
+ wev->active = 1;
+ wev->ready = 0;
+ }
+
+ wev->complete = 0;
+
+ n = aio_error(&wev->aiocb);
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_CRIT, wev->log, ngx_errno, "aio_error() failed");
+ wev->error = 1;
+ return NGX_ERROR;
+ }
+
+ if (n != 0) {
+ if (n == NGX_EINPROGRESS) {
+ if (wev->ready) {
+ ngx_log_error(NGX_LOG_ALERT, wev->log, n,
+ "aio_write() still in progress");
+ wev->ready = 0;
+ }
+ return NGX_AGAIN;
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, wev->log, n, "aio_write() failed");
+ wev->error = 1;
+ wev->ready = 0;
+
+#if 1
+ n = aio_return(&wev->aiocb);
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_ALERT, wev->log, ngx_errno,
+ "aio_return() failed");
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, wev->log, n, "aio_return() %d", n);
+#endif
+
+ return NGX_ERROR;
+ }
+
+ n = aio_return(&wev->aiocb);
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_ALERT, wev->log, ngx_errno,
+ "aio_return() failed");
+
+ wev->error = 1;
+ wev->ready = 0;
+ return NGX_ERROR;
+ }
+
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, wev->log, 0, "aio_write: %d", n);
+
+ wev->active = 0;
+ wev->ready = 1;
+
+ return n;
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_aio_write_chain.c b/usr.sbin/nginx/src/os/unix/ngx_aio_write_chain.c
new file mode 100644
index 00000000000..7167896901b
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_aio_write_chain.c
@@ -0,0 +1,99 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ngx_chain_t *
+ngx_aio_write_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+ u_char *buf, *prev;
+ off_t send, sent;
+ size_t len;
+ ssize_t n, size;
+ ngx_chain_t *cl;
+
+ /* the maximum limit size is the maximum size_t value - the page size */
+
+ if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
+ limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
+ }
+
+ send = 0;
+ sent = 0;
+ cl = in;
+
+ while (cl) {
+
+ if (cl->buf->pos == cl->buf->last) {
+ cl = cl->next;
+ continue;
+ }
+
+ /* we can post the single aio operation only */
+
+ if (!c->write->ready) {
+ return cl;
+ }
+
+ buf = cl->buf->pos;
+ prev = buf;
+ len = 0;
+
+ /* coalesce the neighbouring bufs */
+
+ while (cl && prev == cl->buf->pos && send < limit) {
+ if (ngx_buf_special(cl->buf)) {
+ continue;
+ }
+
+ size = cl->buf->last - cl->buf->pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+ }
+
+ len += size;
+ prev = cl->buf->pos + size;
+ send += size;
+ cl = cl->next;
+ }
+
+ n = ngx_aio_write(c, buf, len);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "aio_write: %z", n);
+
+ if (n == NGX_ERROR) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ if (n > 0) {
+ sent += n;
+ c->sent += n;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "aio_write sent: %O", c->sent);
+
+ for (cl = in; cl; cl = cl->next) {
+
+ if (sent >= cl->buf->last - cl->buf->pos) {
+ sent -= cl->buf->last - cl->buf->pos;
+ cl->buf->pos = cl->buf->last;
+
+ continue;
+ }
+
+ cl->buf->pos += sent;
+
+ break;
+ }
+ }
+
+ return cl;
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_alloc.c b/usr.sbin/nginx/src/os/unix/ngx_alloc.c
new file mode 100644
index 00000000000..c71a10254c6
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_alloc.c
@@ -0,0 +1,89 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ngx_uint_t ngx_pagesize;
+ngx_uint_t ngx_pagesize_shift;
+ngx_uint_t ngx_cacheline_size;
+
+
+void *
+ngx_alloc(size_t size, ngx_log_t *log)
+{
+ void *p;
+
+ p = malloc(size);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+ "malloc(%uz) failed", size);
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);
+
+ return p;
+}
+
+
+void *
+ngx_calloc(size_t size, ngx_log_t *log)
+{
+ void *p;
+
+ p = ngx_alloc(size, log);
+
+ if (p) {
+ ngx_memzero(p, size);
+ }
+
+ return p;
+}
+
+
+#if (NGX_HAVE_POSIX_MEMALIGN)
+
+void *
+ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
+{
+ void *p;
+ int err;
+
+ err = posix_memalign(&p, alignment, size);
+
+ if (err) {
+ ngx_log_error(NGX_LOG_EMERG, log, err,
+ "posix_memalign(%uz, %uz) failed", alignment, size);
+ p = NULL;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
+ "posix_memalign: %p:%uz @%uz", p, size, alignment);
+
+ return p;
+}
+
+#elif (NGX_HAVE_MEMALIGN)
+
+void *
+ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
+{
+ void *p;
+
+ p = memalign(alignment, size);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+ "memalign(%uz, %uz) failed", alignment, size);
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
+ "memalign: %p:%uz @%uz", p, size, alignment);
+
+ return p;
+}
+
+#endif
diff --git a/usr.sbin/nginx/src/os/unix/ngx_alloc.h b/usr.sbin/nginx/src/os/unix/ngx_alloc.h
new file mode 100644
index 00000000000..c7a31aa54b2
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_alloc.h
@@ -0,0 +1,44 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_ALLOC_H_INCLUDED_
+#define _NGX_ALLOC_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+void *ngx_alloc(size_t size, ngx_log_t *log);
+void *ngx_calloc(size_t size, ngx_log_t *log);
+
+#define ngx_free free
+
+
+/*
+ * Linux has memalign() or posix_memalign()
+ * Solaris has memalign()
+ * FreeBSD 7.0 has posix_memalign(), besides, early version's malloc()
+ * aligns allocations bigger than page size at the page boundary
+ */
+
+#if (NGX_HAVE_POSIX_MEMALIGN || NGX_HAVE_MEMALIGN)
+
+void *ngx_memalign(size_t alignment, size_t size, ngx_log_t *log);
+
+#else
+
+#define ngx_memalign(alignment, size, log) ngx_alloc(size, log)
+
+#endif
+
+
+extern ngx_uint_t ngx_pagesize;
+extern ngx_uint_t ngx_pagesize_shift;
+extern ngx_uint_t ngx_cacheline_size;
+
+
+#endif /* _NGX_ALLOC_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_atomic.h b/usr.sbin/nginx/src/os/unix/ngx_atomic.h
new file mode 100644
index 00000000000..57826ffcfaf
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_atomic.h
@@ -0,0 +1,310 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_ATOMIC_H_INCLUDED_
+#define _NGX_ATOMIC_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if (NGX_HAVE_LIBATOMIC)
+
+#define AO_REQUIRE_CAS
+#include <atomic_ops.h>
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+typedef long ngx_atomic_int_t;
+typedef AO_t ngx_atomic_uint_t;
+typedef volatile ngx_atomic_uint_t ngx_atomic_t;
+
+#if (NGX_PTR_SIZE == 8)
+#define NGX_ATOMIC_T_LEN (sizeof("-9223372036854775808") - 1)
+#else
+#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
+#endif
+
+#define ngx_atomic_cmp_set(lock, old, new) \
+ AO_compare_and_swap(lock, old, new)
+#define ngx_atomic_fetch_add(value, add) \
+ AO_fetch_and_add(value, add)
+#define ngx_memory_barrier() AO_nop()
+#define ngx_cpu_pause()
+
+
+#elif (NGX_DARWIN_ATOMIC)
+
+/*
+ * use Darwin 8 atomic(3) and barrier(3) operations
+ * optimized at run-time for UP and SMP
+ */
+
+#include <libkern/OSAtomic.h>
+
+/* "bool" conflicts with perl's CORE/handy.h */
+#undef bool
+
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+#if (NGX_PTR_SIZE == 8)
+
+typedef int64_t ngx_atomic_int_t;
+typedef uint64_t ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-9223372036854775808") - 1)
+
+#define ngx_atomic_cmp_set(lock, old, new) \
+ OSAtomicCompareAndSwap64Barrier(old, new, (int64_t *) lock)
+
+#define ngx_atomic_fetch_add(value, add) \
+ (OSAtomicAdd64(add, (int64_t *) value) - add)
+
+#else
+
+typedef int32_t ngx_atomic_int_t;
+typedef uint32_t ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
+
+#define ngx_atomic_cmp_set(lock, old, new) \
+ OSAtomicCompareAndSwap32Barrier(old, new, (int32_t *) lock)
+
+#define ngx_atomic_fetch_add(value, add) \
+ (OSAtomicAdd32(add, (int32_t *) value) - add)
+
+#endif
+
+#define ngx_memory_barrier() OSMemoryBarrier()
+
+#define ngx_cpu_pause()
+
+typedef volatile ngx_atomic_uint_t ngx_atomic_t;
+
+
+#elif (NGX_HAVE_GCC_ATOMIC)
+
+/* GCC 4.1 builtin atomic operations */
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+typedef long ngx_atomic_int_t;
+typedef unsigned long ngx_atomic_uint_t;
+
+#if (NGX_PTR_SIZE == 8)
+#define NGX_ATOMIC_T_LEN (sizeof("-9223372036854775808") - 1)
+#else
+#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
+#endif
+
+typedef volatile ngx_atomic_uint_t ngx_atomic_t;
+
+
+#define ngx_atomic_cmp_set(lock, old, set) \
+ __sync_bool_compare_and_swap(lock, old, set)
+
+#define ngx_atomic_fetch_add(value, add) \
+ __sync_fetch_and_add(value, add)
+
+#define ngx_memory_barrier() __sync_synchronize()
+
+#if ( __i386__ || __i386 || __amd64__ || __amd64 )
+#define ngx_cpu_pause() __asm__ ("pause")
+#else
+#define ngx_cpu_pause()
+#endif
+
+
+#elif ( __i386__ || __i386 )
+
+typedef int32_t ngx_atomic_int_t;
+typedef uint32_t ngx_atomic_uint_t;
+typedef volatile ngx_atomic_uint_t ngx_atomic_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
+
+
+#if ( __SUNPRO_C )
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set);
+
+ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add);
+
+/*
+ * Sun Studio 12 exits with segmentation fault on '__asm ("pause")',
+ * so ngx_cpu_pause is declared in src/os/unix/ngx_sunpro_x86.il
+ */
+
+void
+ngx_cpu_pause(void);
+
+/* the code in src/os/unix/ngx_sunpro_x86.il */
+
+#define ngx_memory_barrier() __asm (".volatile"); __asm (".nonvolatile")
+
+
+#else /* ( __GNUC__ || __INTEL_COMPILER ) */
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+#include "ngx_gcc_atomic_x86.h"
+
+#endif
+
+
+#elif ( __amd64__ || __amd64 )
+
+typedef int64_t ngx_atomic_int_t;
+typedef uint64_t ngx_atomic_uint_t;
+typedef volatile ngx_atomic_uint_t ngx_atomic_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-9223372036854775808") - 1)
+
+
+#if ( __SUNPRO_C )
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set);
+
+ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add);
+
+/*
+ * Sun Studio 12 exits with segmentation fault on '__asm ("pause")',
+ * so ngx_cpu_pause is declared in src/os/unix/ngx_sunpro_amd64.il
+ */
+
+void
+ngx_cpu_pause(void);
+
+/* the code in src/os/unix/ngx_sunpro_amd64.il */
+
+#define ngx_memory_barrier() __asm (".volatile"); __asm (".nonvolatile")
+
+
+#else /* ( __GNUC__ || __INTEL_COMPILER ) */
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+#include "ngx_gcc_atomic_amd64.h"
+
+#endif
+
+
+#elif ( __sparc__ || __sparc || __sparcv9 )
+
+#if (NGX_PTR_SIZE == 8)
+
+typedef int64_t ngx_atomic_int_t;
+typedef uint64_t ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-9223372036854775808") - 1)
+
+#else
+
+typedef int32_t ngx_atomic_int_t;
+typedef uint32_t ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
+
+#endif
+
+typedef volatile ngx_atomic_uint_t ngx_atomic_t;
+
+
+#if ( __SUNPRO_C )
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+#include "ngx_sunpro_atomic_sparc64.h"
+
+
+#else /* ( __GNUC__ || __INTEL_COMPILER ) */
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+#include "ngx_gcc_atomic_sparc64.h"
+
+#endif
+
+
+#elif ( __powerpc__ || __POWERPC__ )
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+#if (NGX_PTR_SIZE == 8)
+
+typedef int64_t ngx_atomic_int_t;
+typedef uint64_t ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-9223372036854775808") - 1)
+
+#else
+
+typedef int32_t ngx_atomic_int_t;
+typedef uint32_t ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
+
+#endif
+
+typedef volatile ngx_atomic_uint_t ngx_atomic_t;
+
+
+#include "ngx_gcc_atomic_ppc.h"
+
+#endif
+
+
+#if !(NGX_HAVE_ATOMIC_OPS)
+
+#define NGX_HAVE_ATOMIC_OPS 0
+
+typedef int32_t ngx_atomic_int_t;
+typedef uint32_t ngx_atomic_uint_t;
+typedef volatile ngx_atomic_uint_t ngx_atomic_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
+
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set)
+{
+ if (*lock == old) {
+ *lock = set;
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+ ngx_atomic_int_t old;
+
+ old = *value;
+ *value += add;
+
+ return old;
+}
+
+#define ngx_memory_barrier()
+#define ngx_cpu_pause()
+
+#endif
+
+
+void ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin);
+
+#define ngx_trylock(lock) (*(lock) == 0 && ngx_atomic_cmp_set(lock, 0, 1))
+#define ngx_unlock(lock) *(lock) = 0
+
+
+#endif /* _NGX_ATOMIC_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_channel.c b/usr.sbin/nginx/src/os/unix/ngx_channel.c
new file mode 100644
index 00000000000..a0bdc2b8e96
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_channel.c
@@ -0,0 +1,257 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_channel.h>
+
+
+ngx_int_t
+ngx_write_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
+ ngx_log_t *log)
+{
+ ssize_t n;
+ ngx_err_t err;
+ struct iovec iov[1];
+ struct msghdr msg;
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+
+ union {
+ struct cmsghdr cm;
+ char space[CMSG_SPACE(sizeof(int))];
+ } cmsg;
+
+ if (ch->fd == -1) {
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+
+ } else {
+ msg.msg_control = (caddr_t) &cmsg;
+ msg.msg_controllen = sizeof(cmsg);
+
+ cmsg.cm.cmsg_len = CMSG_LEN(sizeof(int));
+ cmsg.cm.cmsg_level = SOL_SOCKET;
+ cmsg.cm.cmsg_type = SCM_RIGHTS;
+
+ /*
+ * We have to use ngx_memcpy() instead of simple
+ * *(int *) CMSG_DATA(&cmsg.cm) = ch->fd;
+ * because some gcc 4.4 with -O2/3/s optimization issues the warning:
+ * dereferencing type-punned pointer will break strict-aliasing rules
+ *
+ * Fortunately, gcc with -O1 compiles this ngx_memcpy()
+ * in the same simple assignment as in the code above
+ */
+
+ ngx_memcpy(CMSG_DATA(&cmsg.cm), &ch->fd, sizeof(int));
+ }
+
+ msg.msg_flags = 0;
+
+#else
+
+ if (ch->fd == -1) {
+ msg.msg_accrights = NULL;
+ msg.msg_accrightslen = 0;
+
+ } else {
+ msg.msg_accrights = (caddr_t) &ch->fd;
+ msg.msg_accrightslen = sizeof(int);
+ }
+
+#endif
+
+ iov[0].iov_base = (char *) ch;
+ iov[0].iov_len = size;
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ n = sendmsg(s, &msg, 0);
+
+ if (n == -1) {
+ err = ngx_errno;
+ if (err == NGX_EAGAIN) {
+ return NGX_AGAIN;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, log, err, "sendmsg() failed");
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size, ngx_log_t *log)
+{
+ ssize_t n;
+ ngx_err_t err;
+ struct iovec iov[1];
+ struct msghdr msg;
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+ union {
+ struct cmsghdr cm;
+ char space[CMSG_SPACE(sizeof(int))];
+ } cmsg;
+#else
+ int fd;
+#endif
+
+ iov[0].iov_base = (char *) ch;
+ iov[0].iov_len = size;
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+ msg.msg_control = (caddr_t) &cmsg;
+ msg.msg_controllen = sizeof(cmsg);
+#else
+ msg.msg_accrights = (caddr_t) &fd;
+ msg.msg_accrightslen = sizeof(int);
+#endif
+
+ n = recvmsg(s, &msg, 0);
+
+ if (n == -1) {
+ err = ngx_errno;
+ if (err == NGX_EAGAIN) {
+ return NGX_AGAIN;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, log, err, "recvmsg() failed");
+ return NGX_ERROR;
+ }
+
+ if (n == 0) {
+ ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, 0, "recvmsg() returned zero");
+ return NGX_ERROR;
+ }
+
+ if ((size_t) n < sizeof(ngx_channel_t)) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "recvmsg() returned not enough data: %uz", n);
+ return NGX_ERROR;
+ }
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+
+ if (ch->command == NGX_CMD_OPEN_CHANNEL) {
+
+ if (cmsg.cm.cmsg_len < (socklen_t) CMSG_LEN(sizeof(int))) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "recvmsg() returned too small ancillary data");
+ return NGX_ERROR;
+ }
+
+ if (cmsg.cm.cmsg_level != SOL_SOCKET || cmsg.cm.cmsg_type != SCM_RIGHTS)
+ {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "recvmsg() returned invalid ancillary data "
+ "level %d or type %d",
+ cmsg.cm.cmsg_level, cmsg.cm.cmsg_type);
+ return NGX_ERROR;
+ }
+
+ /* ch->fd = *(int *) CMSG_DATA(&cmsg.cm); */
+
+ ngx_memcpy(&ch->fd, CMSG_DATA(&cmsg.cm), sizeof(int));
+ }
+
+ if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "recvmsg() truncated data");
+ }
+
+#else
+
+ if (ch->command == NGX_CMD_OPEN_CHANNEL) {
+ if (msg.msg_accrightslen != sizeof(int)) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "recvmsg() returned no ancillary data");
+ return NGX_ERROR;
+ }
+
+ ch->fd = fd;
+ }
+
+#endif
+
+ return n;
+}
+
+
+ngx_int_t
+ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd, ngx_int_t event,
+ ngx_event_handler_pt handler)
+{
+ ngx_event_t *ev, *rev, *wev;
+ ngx_connection_t *c;
+
+ c = ngx_get_connection(fd, cycle->log);
+
+ if (c == NULL) {
+ return NGX_ERROR;
+ }
+
+ c->pool = cycle->pool;
+
+ rev = c->read;
+ wev = c->write;
+
+ rev->log = cycle->log;
+ wev->log = cycle->log;
+
+#if (NGX_THREADS)
+ rev->lock = &c->lock;
+ wev->lock = &c->lock;
+ rev->own_lock = &c->lock;
+ wev->own_lock = &c->lock;
+#endif
+
+ rev->channel = 1;
+ wev->channel = 1;
+
+ ev = (event == NGX_READ_EVENT) ? rev : wev;
+
+ ev->handler = handler;
+
+ if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
+ if (ngx_add_conn(c) == NGX_ERROR) {
+ ngx_free_connection(c);
+ return NGX_ERROR;
+ }
+
+ } else {
+ if (ngx_add_event(ev, event, 0) == NGX_ERROR) {
+ ngx_free_connection(c);
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+void
+ngx_close_channel(ngx_fd_t *fd, ngx_log_t *log)
+{
+ if (close(fd[0]) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "close() channel failed");
+ }
+
+ if (close(fd[1]) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "close() channel failed");
+ }
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_channel.h b/usr.sbin/nginx/src/os/unix/ngx_channel.h
new file mode 100644
index 00000000000..365d4394b34
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_channel.h
@@ -0,0 +1,33 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_CHANNEL_H_INCLUDED_
+#define _NGX_CHANNEL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+typedef struct {
+ ngx_uint_t command;
+ ngx_pid_t pid;
+ ngx_int_t slot;
+ ngx_fd_t fd;
+} ngx_channel_t;
+
+
+ngx_int_t ngx_write_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
+ ngx_log_t *log);
+ngx_int_t ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
+ ngx_log_t *log);
+ngx_int_t ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd,
+ ngx_int_t event, ngx_event_handler_pt handler);
+void ngx_close_channel(ngx_fd_t *fd, ngx_log_t *log);
+
+
+#endif /* _NGX_CHANNEL_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_daemon.c b/usr.sbin/nginx/src/os/unix/ngx_daemon.c
new file mode 100644
index 00000000000..7255b7a7eba
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_daemon.c
@@ -0,0 +1,68 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ngx_int_t ngx_daemon(ngx_log_t *log)
+{
+ int fd;
+
+ switch (fork()) {
+ case -1:
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "fork() failed");
+ return NGX_ERROR;
+
+ case 0:
+ break;
+
+ default:
+ exit(0);
+ }
+
+ ngx_pid = ngx_getpid();
+
+ if (setsid() == -1) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "setsid() failed");
+ return NGX_ERROR;
+ }
+
+ umask(0);
+
+ fd = open("/dev/null", O_RDWR);
+ if (fd == -1) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+ "open(\"/dev/null\") failed");
+ return NGX_ERROR;
+ }
+
+ if (dup2(fd, STDIN_FILENO) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDIN) failed");
+ return NGX_ERROR;
+ }
+
+ if (dup2(fd, STDOUT_FILENO) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDOUT) failed");
+ return NGX_ERROR;
+ }
+
+#if 0
+ if (dup2(fd, STDERR_FILENO) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDERR) failed");
+ return NGX_ERROR;
+ }
+#endif
+
+ if (fd > STDERR_FILENO) {
+ if (close(fd) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "close() failed");
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_darwin.h b/usr.sbin/nginx/src/os/unix/ngx_darwin.h
new file mode 100644
index 00000000000..7fa60ff0c9f
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_darwin.h
@@ -0,0 +1,19 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_DARWIN_H_INCLUDED_
+#define _NGX_DARWIN_H_INCLUDED_
+
+
+ngx_chain_t *ngx_darwin_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in,
+ off_t limit);
+
+extern int ngx_darwin_kern_osreldate;
+extern int ngx_darwin_hw_ncpu;
+extern u_long ngx_darwin_net_inet_tcp_sendspace;
+
+
+#endif /* _NGX_DARWIN_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_darwin_config.h b/usr.sbin/nginx/src/os/unix/ngx_darwin_config.h
new file mode 100644
index 00000000000..88aa6f502a9
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_darwin_config.h
@@ -0,0 +1,93 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_DARWIN_CONFIG_H_INCLUDED_
+#define _NGX_DARWIN_CONFIG_H_INCLUDED_
+
+
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stddef.h> /* offsetof() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+#include <glob.h>
+#include <sys/mount.h> /* statfs() */
+
+#include <sys/filio.h> /* FIONBIO */
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sched.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h> /* TCP_NODELAY */
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/un.h>
+
+#include <sys/sysctl.h>
+#include <xlocale.h>
+
+
+#ifndef IOV_MAX
+#define IOV_MAX 64
+#endif
+
+
+#include <ngx_auto_config.h>
+
+
+#if (NGX_HAVE_POSIX_SEM)
+#include <semaphore.h>
+#endif
+
+
+#if (NGX_HAVE_POLL)
+#include <poll.h>
+#endif
+
+
+#if (NGX_HAVE_KQUEUE)
+#include <sys/event.h>
+#endif
+
+
+#define NGX_LISTEN_BACKLOG -1
+
+
+#ifndef NGX_HAVE_INHERITED_NONBLOCK
+#define NGX_HAVE_INHERITED_NONBLOCK 1
+#endif
+
+
+#ifndef NGX_HAVE_CASELESS_FILESYSTEM
+#define NGX_HAVE_CASELESS_FILESYSTEM 1
+#endif
+
+
+#define NGX_HAVE_OS_SPECIFIC_INIT 1
+
+
+extern char **environ;
+
+
+#endif /* _NGX_DARWIN_CONFIG_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_darwin_init.c b/usr.sbin/nginx/src/os/unix/ngx_darwin_init.c
new file mode 100644
index 00000000000..67133198ba7
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_darwin_init.c
@@ -0,0 +1,169 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+char ngx_darwin_kern_ostype[16];
+char ngx_darwin_kern_osrelease[128];
+int ngx_darwin_hw_ncpu;
+int ngx_darwin_kern_ipc_somaxconn;
+u_long ngx_darwin_net_inet_tcp_sendspace;
+
+
+static ngx_os_io_t ngx_darwin_io = {
+ ngx_unix_recv,
+ ngx_readv_chain,
+ ngx_udp_unix_recv,
+ ngx_unix_send,
+#if (NGX_HAVE_SENDFILE)
+ ngx_darwin_sendfile_chain,
+ NGX_IO_SENDFILE
+#else
+ ngx_writev_chain,
+ 0
+#endif
+};
+
+
+typedef struct {
+ char *name;
+ void *value;
+ size_t size;
+ ngx_uint_t exists;
+} sysctl_t;
+
+
+sysctl_t sysctls[] = {
+ { "hw.ncpu",
+ &ngx_darwin_hw_ncpu,
+ sizeof(ngx_darwin_hw_ncpu), 0 },
+
+ { "net.inet.tcp.sendspace",
+ &ngx_darwin_net_inet_tcp_sendspace,
+ sizeof(ngx_darwin_net_inet_tcp_sendspace), 0 },
+
+ { "kern.ipc.somaxconn",
+ &ngx_darwin_kern_ipc_somaxconn,
+ sizeof(ngx_darwin_kern_ipc_somaxconn), 0 },
+
+ { NULL, NULL, 0, 0 }
+};
+
+
+ngx_int_t
+ngx_os_specific_init(ngx_log_t *log)
+{
+ int somaxconn;
+ size_t size;
+ ngx_err_t err;
+ ngx_uint_t i;
+
+ size = sizeof(ngx_darwin_kern_ostype);
+ if (sysctlbyname("kern.ostype", ngx_darwin_kern_ostype, &size, NULL, 0)
+ == -1)
+ {
+ err = ngx_errno;
+
+ if (err != NGX_ENOENT) {
+
+ ngx_log_error(NGX_LOG_ALERT, log, err,
+ "sysctlbyname(kern.ostype) failed");
+
+ if (err != NGX_ENOMEM) {
+ return NGX_ERROR;
+ }
+
+ ngx_darwin_kern_ostype[size - 1] = '\0';
+ }
+ }
+
+ size = sizeof(ngx_darwin_kern_osrelease);
+ if (sysctlbyname("kern.osrelease", ngx_darwin_kern_osrelease, &size,
+ NULL, 0)
+ == -1)
+ {
+ err = ngx_errno;
+
+ if (err != NGX_ENOENT) {
+
+ ngx_log_error(NGX_LOG_ALERT, log, err,
+ "sysctlbyname(kern.osrelease) failed");
+
+ if (err != NGX_ENOMEM) {
+ return NGX_ERROR;
+ }
+
+ ngx_darwin_kern_osrelease[size - 1] = '\0';
+ }
+ }
+
+ for (i = 0; sysctls[i].name; i++) {
+ size = sysctls[i].size;
+
+ if (sysctlbyname(sysctls[i].name, sysctls[i].value, &size, NULL, 0)
+ == 0)
+ {
+ sysctls[i].exists = 1;
+ continue;
+ }
+
+ err = ngx_errno;
+
+ if (err == NGX_ENOENT) {
+ continue;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, log, err,
+ "sysctlbyname(%s) failed", sysctls[i].name);
+ return NGX_ERROR;
+ }
+
+ ngx_ncpu = ngx_darwin_hw_ncpu;
+
+ somaxconn = 32676;
+
+ if (ngx_darwin_kern_ipc_somaxconn > somaxconn) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "sysctl kern.ipc.somaxconn must be no more than %d",
+ somaxconn);
+ return NGX_ERROR;
+ }
+
+ ngx_tcp_nodelay_and_tcp_nopush = 1;
+
+ ngx_os_io = ngx_darwin_io;
+
+ return NGX_OK;
+}
+
+
+void
+ngx_os_specific_status(ngx_log_t *log)
+{
+ u_long value;
+ ngx_uint_t i;
+
+ if (ngx_darwin_kern_ostype[0]) {
+ ngx_log_error(NGX_LOG_NOTICE, log, 0, "OS: %s %s",
+ ngx_darwin_kern_ostype, ngx_darwin_kern_osrelease);
+ }
+
+ for (i = 0; sysctls[i].name; i++) {
+ if (sysctls[i].exists) {
+ if (sysctls[i].size == sizeof(long)) {
+ value = *(long *) sysctls[i].value;
+
+ } else {
+ value = *(int *) sysctls[i].value;
+ }
+
+ ngx_log_error(NGX_LOG_NOTICE, log, 0, "%s: %l",
+ sysctls[i].name, value);
+ }
+ }
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_darwin_sendfile_chain.c b/usr.sbin/nginx/src/os/unix/ngx_darwin_sendfile_chain.c
new file mode 100644
index 00000000000..59d46534bdb
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_darwin_sendfile_chain.c
@@ -0,0 +1,365 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+/*
+ * It seems that Darwin 9.4 (Mac OS X 1.5) sendfile() has the same
+ * old bug as early FreeBSD sendfile() syscall:
+ * http://www.freebsd.org/cgi/query-pr.cgi?pr=33771
+ *
+ * Besides sendfile() has another bug: if one calls sendfile()
+ * with both a header and a trailer, then sendfile() ignores a file part
+ * at all and sends only the header and the trailer together.
+ * For this reason we send a trailer only if there is no a header.
+ *
+ * Although sendfile() allows to pass a header or a trailer,
+ * it may send the header or the trailer and a part of the file
+ * in different packets. And FreeBSD workaround (TCP_NOPUSH option)
+ * does not help.
+ */
+
+
+#if (IOV_MAX > 64)
+#define NGX_HEADERS 64
+#define NGX_TRAILERS 64
+#else
+#define NGX_HEADERS IOV_MAX
+#define NGX_TRAILERS IOV_MAX
+#endif
+
+
+ngx_chain_t *
+ngx_darwin_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+ int rc;
+ u_char *prev;
+ off_t size, send, prev_send, aligned, sent, fprev;
+ off_t header_size, file_size;
+ ngx_uint_t eintr, complete;
+ ngx_err_t err;
+ ngx_buf_t *file;
+ ngx_array_t header, trailer;
+ ngx_event_t *wev;
+ ngx_chain_t *cl;
+ struct sf_hdtr hdtr;
+ struct iovec *iov, headers[NGX_HEADERS], trailers[NGX_TRAILERS];
+
+ wev = c->write;
+
+ if (!wev->ready) {
+ return in;
+ }
+
+#if (NGX_HAVE_KQUEUE)
+
+ if ((ngx_event_flags & NGX_USE_KQUEUE_EVENT) && wev->pending_eof) {
+ (void) ngx_connection_error(c, wev->kq_errno,
+ "kevent() reported about an closed connection");
+ wev->error = 1;
+ return NGX_CHAIN_ERROR;
+ }
+
+#endif
+
+ /* the maximum limit size is the maximum size_t value - the page size */
+
+ if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
+ limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
+ }
+
+ send = 0;
+
+ header.elts = headers;
+ header.size = sizeof(struct iovec);
+ header.nalloc = NGX_HEADERS;
+ header.pool = c->pool;
+
+ trailer.elts = trailers;
+ trailer.size = sizeof(struct iovec);
+ trailer.nalloc = NGX_TRAILERS;
+ trailer.pool = c->pool;
+
+ for ( ;; ) {
+ file = NULL;
+ file_size = 0;
+ header_size = 0;
+ eintr = 0;
+ complete = 0;
+ prev_send = send;
+
+ header.nelts = 0;
+ trailer.nelts = 0;
+
+ /* create the header iovec and coalesce the neighbouring bufs */
+
+ prev = NULL;
+ iov = NULL;
+
+ for (cl = in;
+ cl && header.nelts < IOV_MAX && send < limit;
+ cl = cl->next)
+ {
+ if (ngx_buf_special(cl->buf)) {
+ continue;
+ }
+
+ if (!ngx_buf_in_memory_only(cl->buf)) {
+ break;
+ }
+
+ size = cl->buf->last - cl->buf->pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+ }
+
+ if (prev == cl->buf->pos) {
+ iov->iov_len += (size_t) size;
+
+ } else {
+ iov = ngx_array_push(&header);
+ if (iov == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ iov->iov_base = (void *) cl->buf->pos;
+ iov->iov_len = (size_t) size;
+ }
+
+ prev = cl->buf->pos + (size_t) size;
+ header_size += size;
+ send += size;
+ }
+
+
+ if (cl && cl->buf->in_file && send < limit) {
+ file = cl->buf;
+
+ /* coalesce the neighbouring file bufs */
+
+ do {
+ size = cl->buf->file_last - cl->buf->file_pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+
+ aligned = (cl->buf->file_pos + size + ngx_pagesize - 1)
+ & ~((off_t) ngx_pagesize - 1);
+
+ if (aligned <= cl->buf->file_last) {
+ size = aligned - cl->buf->file_pos;
+ }
+ }
+
+ file_size += size;
+ send += size;
+ fprev = cl->buf->file_pos + size;
+ cl = cl->next;
+
+ } while (cl
+ && cl->buf->in_file
+ && send < limit
+ && file->file->fd == cl->buf->file->fd
+ && fprev == cl->buf->file_pos);
+ }
+
+ if (file && header.nelts == 0) {
+
+ /* create the tailer iovec and coalesce the neighbouring bufs */
+
+ prev = NULL;
+ iov = NULL;
+
+ while (cl && header.nelts < IOV_MAX && send < limit) {
+
+ if (ngx_buf_special(cl->buf)) {
+ cl = cl->next;
+ continue;
+ }
+
+ if (!ngx_buf_in_memory_only(cl->buf)) {
+ break;
+ }
+
+ size = cl->buf->last - cl->buf->pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+ }
+
+ if (prev == cl->buf->pos) {
+ iov->iov_len += (size_t) size;
+
+ } else {
+ iov = ngx_array_push(&trailer);
+ if (iov == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ iov->iov_base = (void *) cl->buf->pos;
+ iov->iov_len = (size_t) size;
+ }
+
+ prev = cl->buf->pos + (size_t) size;
+ send += size;
+ cl = cl->next;
+ }
+ }
+
+ if (file) {
+
+ /*
+ * sendfile() returns EINVAL if sf_hdtr's count is 0,
+ * but corresponding pointer is not NULL
+ */
+
+ hdtr.headers = header.nelts ? (struct iovec *) header.elts: NULL;
+ hdtr.hdr_cnt = header.nelts;
+ hdtr.trailers = trailer.nelts ? (struct iovec *) trailer.elts: NULL;
+ hdtr.trl_cnt = trailer.nelts;
+
+ sent = header_size + file_size;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "sendfile: @%O %O h:%O",
+ file->file_pos, sent, header_size);
+
+ rc = sendfile(file->file->fd, c->fd, file->file_pos,
+ &sent, &hdtr, 0);
+
+ if (rc == -1) {
+ err = ngx_errno;
+
+ switch (err) {
+ case NGX_EAGAIN:
+ break;
+
+ case NGX_EINTR:
+ eintr = 1;
+ break;
+
+ default:
+ wev->error = 1;
+ (void) ngx_connection_error(c, err, "sendfile() failed");
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "sendfile() sent only %O bytes", sent);
+ }
+
+ if (rc == 0 && sent == 0) {
+
+ /*
+ * if rc and sent equal to zero, then someone
+ * has truncated the file, so the offset became beyond
+ * the end of the file
+ */
+
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "sendfile() reported that \"%s\" was truncated",
+ file->file->name.data);
+
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "sendfile: %d, @%O %O:%O",
+ rc, file->file_pos, sent, file_size + header_size);
+
+ } else {
+ rc = writev(c->fd, header.elts, header.nelts);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "writev: %d of %uz", rc, send);
+
+ if (rc == -1) {
+ err = ngx_errno;
+
+ switch (err) {
+ case NGX_EAGAIN:
+ break;
+
+ case NGX_EINTR:
+ eintr = 1;
+ break;
+
+ default:
+ wev->error = 1;
+ ngx_connection_error(c, err, "writev() failed");
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "writev() not ready");
+ }
+
+ sent = rc > 0 ? rc : 0;
+ }
+
+ if (send - prev_send == sent) {
+ complete = 1;
+ }
+
+ c->sent += sent;
+
+ for (cl = in; cl; cl = cl->next) {
+
+ if (ngx_buf_special(cl->buf)) {
+ continue;
+ }
+
+ if (sent == 0) {
+ break;
+ }
+
+ size = ngx_buf_size(cl->buf);
+
+ if (sent >= size) {
+ sent -= size;
+
+ if (ngx_buf_in_memory(cl->buf)) {
+ cl->buf->pos = cl->buf->last;
+ }
+
+ if (cl->buf->in_file) {
+ cl->buf->file_pos = cl->buf->file_last;
+ }
+
+ continue;
+ }
+
+ if (ngx_buf_in_memory(cl->buf)) {
+ cl->buf->pos += (size_t) sent;
+ }
+
+ if (cl->buf->in_file) {
+ cl->buf->file_pos += sent;
+ }
+
+ break;
+ }
+
+ if (eintr) {
+ continue;
+ }
+
+ if (!complete) {
+ wev->ready = 0;
+ return cl;
+ }
+
+ if (send >= limit || cl == NULL) {
+ return cl;
+ }
+
+ in = cl;
+ }
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_errno.c b/usr.sbin/nginx/src/os/unix/ngx_errno.c
new file mode 100644
index 00000000000..02994b8087f
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_errno.c
@@ -0,0 +1,86 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ * The strerror() messages are copied because:
+ *
+ * 1) strerror() and strerror_r() functions are not Async-Signal-Safe,
+ * therefore, they can not be used in signal handlers;
+ *
+ * 2) a direct sys_errlist[] array may be used instead of these functions,
+ * but Linux linker warns about its usage:
+ *
+ * warning: `sys_errlist' is deprecated; use `strerror' or `strerror_r' instead
+ * warning: `sys_nerr' is deprecated; use `strerror' or `strerror_r' instead
+ *
+ * causing false bug reports.
+ */
+
+
+static ngx_str_t *ngx_sys_errlist;
+static ngx_str_t ngx_unknown_error = ngx_string("Unknown error");
+
+
+u_char *
+ngx_strerror(ngx_err_t err, u_char *errstr, size_t size)
+{
+ ngx_str_t *msg;
+
+ msg = ((ngx_uint_t) err < NGX_SYS_NERR) ? &ngx_sys_errlist[err]:
+ &ngx_unknown_error;
+ size = ngx_min(size, msg->len);
+
+ return ngx_cpymem(errstr, msg->data, size);
+}
+
+
+ngx_uint_t
+ngx_strerror_init(void)
+{
+ char *msg;
+ u_char *p;
+ size_t len;
+ ngx_err_t err;
+
+ /*
+ * ngx_strerror() is not ready to work at this stage, therefore,
+ * malloc() is used and possible errors are logged using strerror().
+ */
+
+ len = NGX_SYS_NERR * sizeof(ngx_str_t);
+
+ ngx_sys_errlist = malloc(len);
+ if (ngx_sys_errlist == NULL) {
+ goto failed;
+ }
+
+ for (err = 0; err < NGX_SYS_NERR; err++) {
+ msg = strerror(err);
+ len = ngx_strlen(msg);
+
+ p = malloc(len);
+ if (p == NULL) {
+ goto failed;
+ }
+
+ ngx_memcpy(p, msg, len);
+ ngx_sys_errlist[err].len = len;
+ ngx_sys_errlist[err].data = p;
+ }
+
+ return NGX_OK;
+
+failed:
+
+ err = errno;
+ ngx_log_stderr(0, "malloc(%uz) failed (%d: %s)", len, err, strerror(err));
+
+ return NGX_ERROR;
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_errno.h b/usr.sbin/nginx/src/os/unix/ngx_errno.h
new file mode 100644
index 00000000000..3d51f3cfce9
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_errno.h
@@ -0,0 +1,67 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_ERRNO_H_INCLUDED_
+#define _NGX_ERRNO_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef int ngx_err_t;
+
+#define NGX_EPERM EPERM
+#define NGX_ENOENT ENOENT
+#define NGX_ENOPATH ENOENT
+#define NGX_ESRCH ESRCH
+#define NGX_EINTR EINTR
+#define NGX_ECHILD ECHILD
+#define NGX_ENOMEM ENOMEM
+#define NGX_EACCES EACCES
+#define NGX_EBUSY EBUSY
+#define NGX_EEXIST EEXIST
+#define NGX_EXDEV EXDEV
+#define NGX_ENOTDIR ENOTDIR
+#define NGX_EISDIR EISDIR
+#define NGX_EINVAL EINVAL
+#define NGX_ENOSPC ENOSPC
+#define NGX_EPIPE EPIPE
+#define NGX_EINPROGRESS EINPROGRESS
+#define NGX_EADDRINUSE EADDRINUSE
+#define NGX_ECONNABORTED ECONNABORTED
+#define NGX_ECONNRESET ECONNRESET
+#define NGX_ENOTCONN ENOTCONN
+#define NGX_ETIMEDOUT ETIMEDOUT
+#define NGX_ECONNREFUSED ECONNREFUSED
+#define NGX_ENAMETOOLONG ENAMETOOLONG
+#define NGX_ENETDOWN ENETDOWN
+#define NGX_ENETUNREACH ENETUNREACH
+#define NGX_EHOSTDOWN EHOSTDOWN
+#define NGX_EHOSTUNREACH EHOSTUNREACH
+#define NGX_ENOSYS ENOSYS
+#define NGX_ECANCELED ECANCELED
+#define NGX_EILSEQ EILSEQ
+#define NGX_ENOMOREFILES 0
+
+#if (__hpux__)
+#define NGX_EAGAIN EWOULDBLOCK
+#else
+#define NGX_EAGAIN EAGAIN
+#endif
+
+
+#define ngx_errno errno
+#define ngx_socket_errno errno
+#define ngx_set_errno(err) errno = err
+#define ngx_set_socket_errno(err) errno = err
+
+
+u_char *ngx_strerror(ngx_err_t err, u_char *errstr, size_t size);
+ngx_uint_t ngx_strerror_init(void);
+
+
+#endif /* _NGX_ERRNO_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_file_aio_read.c b/usr.sbin/nginx/src/os/unix/ngx_file_aio_read.c
new file mode 100644
index 00000000000..ef7a461079f
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_file_aio_read.c
@@ -0,0 +1,213 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+/*
+ * FreeBSD file AIO features and quirks:
+ *
+ * if an asked data are already in VM cache, then aio_error() returns 0,
+ * and the data are already copied in buffer;
+ *
+ * aio_read() preread in VM cache as minimum 16K (probably BKVASIZE);
+ * the first AIO preload may be up to 128K;
+ *
+ * aio_read/aio_error() may return EINPROGRESS for just written data;
+ *
+ * kqueue EVFILT_AIO filter is level triggered only: an event repeats
+ * until aio_return() will be called;
+ *
+ * aio_cancel() can not cancel file AIO: it returns AIO_NOTCANCELED always.
+ */
+
+
+extern int ngx_kqueue;
+
+
+static ssize_t ngx_file_aio_result(ngx_file_t *file, ngx_event_aio_t *aio,
+ ngx_event_t *ev);
+static void ngx_file_aio_event_handler(ngx_event_t *ev);
+
+
+ssize_t
+ngx_file_aio_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset,
+ ngx_pool_t *pool)
+{
+ int n;
+ ngx_event_t *ev;
+ ngx_event_aio_t *aio;
+
+ if (!ngx_file_aio) {
+ return ngx_read_file(file, buf, size, offset);
+ }
+
+ aio = file->aio;
+
+ if (aio == NULL) {
+ aio = ngx_pcalloc(pool, sizeof(ngx_event_aio_t));
+ if (aio == NULL) {
+ return NGX_ERROR;
+ }
+
+ aio->file = file;
+ aio->fd = file->fd;
+ aio->event.data = aio;
+ aio->event.ready = 1;
+ aio->event.log = file->log;
+#if (NGX_HAVE_AIO_SENDFILE)
+ aio->last_offset = -1;
+#endif
+ file->aio = aio;
+ }
+
+ ev = &aio->event;
+
+ if (!ev->ready) {
+ ngx_log_error(NGX_LOG_ALERT, file->log, 0,
+ "second aio post for \"%V\"", &file->name);
+ return NGX_AGAIN;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0,
+ "aio complete:%d @%O:%z %V",
+ ev->complete, offset, size, &file->name);
+
+ if (ev->complete) {
+ ev->complete = 0;
+ ngx_set_errno(aio->err);
+
+ if (aio->err == 0) {
+ return aio->nbytes;
+ }
+
+ return NGX_ERROR;
+ }
+
+ ngx_memzero(&aio->aiocb, sizeof(struct aiocb));
+
+ aio->aiocb.aio_fildes = file->fd;
+ aio->aiocb.aio_offset = offset;
+ aio->aiocb.aio_buf = buf;
+ aio->aiocb.aio_nbytes = size;
+#if (NGX_HAVE_KQUEUE)
+ aio->aiocb.aio_sigevent.sigev_notify_kqueue = ngx_kqueue;
+ aio->aiocb.aio_sigevent.sigev_notify = SIGEV_KEVENT;
+ aio->aiocb.aio_sigevent.sigev_value.sigval_ptr = ev;
+#endif
+ ev->handler = ngx_file_aio_event_handler;
+
+ n = aio_read(&aio->aiocb);
+
+ if (n == -1) {
+ n = ngx_errno;
+
+ if (n == NGX_EAGAIN) {
+ return ngx_read_file(file, buf, size, offset);
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, file->log, n,
+ "aio_read(\"%V\") failed", &file->name);
+
+ if (n == NGX_ENOSYS) {
+ ngx_file_aio = 0;
+ return ngx_read_file(file, buf, size, offset);
+ }
+
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, file->log, 0,
+ "aio_read: fd:%d %d", file->fd, n);
+
+ ev->active = 1;
+ ev->ready = 0;
+ ev->complete = 0;
+
+ return ngx_file_aio_result(aio->file, aio, ev);
+}
+
+
+static ssize_t
+ngx_file_aio_result(ngx_file_t *file, ngx_event_aio_t *aio, ngx_event_t *ev)
+{
+ int n;
+ ngx_err_t err;
+
+ n = aio_error(&aio->aiocb);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, file->log, 0,
+ "aio_error: fd:%d %d", file->fd, n);
+
+ if (n == -1) {
+ err = ngx_errno;
+ aio->err = err;
+
+ ngx_log_error(NGX_LOG_ALERT, file->log, err,
+ "aio_error(\"%V\") failed", &file->name);
+ return NGX_ERROR;
+ }
+
+ if (n != 0) {
+ if (n == NGX_EINPROGRESS) {
+ if (ev->ready) {
+ ev->ready = 0;
+ ngx_log_error(NGX_LOG_ALERT, file->log, n,
+ "aio_read(\"%V\") still in progress",
+ &file->name);
+ }
+
+ return NGX_AGAIN;
+ }
+
+ aio->err = n;
+ ev->ready = 0;
+
+ ngx_log_error(NGX_LOG_CRIT, file->log, n,
+ "aio_read(\"%V\") failed", &file->name);
+ return NGX_ERROR;
+ }
+
+ n = aio_return(&aio->aiocb);
+
+ if (n == -1) {
+ err = ngx_errno;
+ aio->err = err;
+ ev->ready = 0;
+
+ ngx_log_error(NGX_LOG_ALERT, file->log, err,
+ "aio_return(\"%V\") failed", &file->name);
+ return NGX_ERROR;
+ }
+
+ aio->err = 0;
+ aio->nbytes = n;
+ ev->ready = 1;
+ ev->active = 0;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, file->log, 0,
+ "aio_return: fd:%d %d", file->fd, n);
+
+ return n;
+}
+
+
+static void
+ngx_file_aio_event_handler(ngx_event_t *ev)
+{
+ ngx_event_aio_t *aio;
+
+ aio = ev->data;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, ev->log, 0,
+ "aio event handler fd:%d %V", aio->fd, &aio->file->name);
+
+ if (ngx_file_aio_result(aio->file, aio, ev) != NGX_AGAIN) {
+ aio->handler(ev);
+ }
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_files.c b/usr.sbin/nginx/src/os/unix/ngx_files.c
new file mode 100644
index 00000000000..89ab8d6bc05
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_files.c
@@ -0,0 +1,555 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+ngx_uint_t ngx_file_aio = 1;
+
+#endif
+
+
+ssize_t
+ngx_read_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset)
+{
+ ssize_t n;
+
+ ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0,
+ "read: %d, %p, %uz, %O", file->fd, buf, size, offset);
+
+#if (NGX_HAVE_PREAD)
+
+ n = pread(file->fd, buf, size, offset);
+
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "pread() \"%s\" failed", file->name.data);
+ return NGX_ERROR;
+ }
+
+#else
+
+ if (file->sys_offset != offset) {
+ if (lseek(file->fd, offset, SEEK_SET) == -1) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "lseek() \"%s\" failed", file->name.data);
+ return NGX_ERROR;
+ }
+
+ file->sys_offset = offset;
+ }
+
+ n = read(file->fd, buf, size);
+
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "read() \"%s\" failed", file->name.data);
+ return NGX_ERROR;
+ }
+
+ file->sys_offset += n;
+
+#endif
+
+ file->offset += n;
+
+ return n;
+}
+
+
+ssize_t
+ngx_write_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset)
+{
+ ssize_t n, written;
+
+ ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0,
+ "write: %d, %p, %uz, %O", file->fd, buf, size, offset);
+
+ written = 0;
+
+#if (NGX_HAVE_PWRITE)
+
+ for ( ;; ) {
+ n = pwrite(file->fd, buf + written, size, offset);
+
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "pwrite() \"%s\" failed", file->name.data);
+ return NGX_ERROR;
+ }
+
+ file->offset += n;
+ written += n;
+
+ if ((size_t) n == size) {
+ return written;
+ }
+
+ offset += n;
+ size -= n;
+ }
+
+#else
+
+ if (file->sys_offset != offset) {
+ if (lseek(file->fd, offset, SEEK_SET) == -1) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "lseek() \"%s\" failed", file->name.data);
+ return NGX_ERROR;
+ }
+
+ file->sys_offset = offset;
+ }
+
+ for ( ;; ) {
+ n = write(file->fd, buf + written, size);
+
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "write() \"%s\" failed", file->name.data);
+ return NGX_ERROR;
+ }
+
+ file->offset += n;
+ written += n;
+
+ if ((size_t) n == size) {
+ return written;
+ }
+
+ size -= n;
+ }
+#endif
+}
+
+
+ngx_fd_t
+ngx_open_tempfile(u_char *name, ngx_uint_t persistent, ngx_uint_t access)
+{
+ ngx_fd_t fd;
+
+ fd = open((const char *) name, O_CREAT|O_EXCL|O_RDWR,
+ access ? access : 0600);
+
+ if (fd != -1 && !persistent) {
+ unlink((const char *) name);
+ }
+
+ return fd;
+}
+
+
+#define NGX_IOVS 8
+
+ssize_t
+ngx_write_chain_to_file(ngx_file_t *file, ngx_chain_t *cl, off_t offset,
+ ngx_pool_t *pool)
+{
+ u_char *prev;
+ size_t size;
+ ssize_t n;
+ ngx_array_t vec;
+ struct iovec *iov, iovs[NGX_IOVS];
+
+ /* use pwrite() if there is the only buf in a chain */
+
+ if (cl->next == NULL) {
+ return ngx_write_file(file, cl->buf->pos,
+ (size_t) (cl->buf->last - cl->buf->pos),
+ offset);
+ }
+
+ vec.elts = iovs;
+ vec.size = sizeof(struct iovec);
+ vec.nalloc = NGX_IOVS;
+ vec.pool = pool;
+
+ do {
+ prev = NULL;
+ iov = NULL;
+ size = 0;
+
+ vec.nelts = 0;
+
+ /* create the iovec and coalesce the neighbouring bufs */
+
+ while (cl && vec.nelts < IOV_MAX) {
+ if (prev == cl->buf->pos) {
+ iov->iov_len += cl->buf->last - cl->buf->pos;
+
+ } else {
+ iov = ngx_array_push(&vec);
+ if (iov == NULL) {
+ return NGX_ERROR;
+ }
+
+ iov->iov_base = (void *) cl->buf->pos;
+ iov->iov_len = cl->buf->last - cl->buf->pos;
+ }
+
+ size += cl->buf->last - cl->buf->pos;
+ prev = cl->buf->last;
+ cl = cl->next;
+ }
+
+ /* use pwrite() if there is the only iovec buffer */
+
+ if (vec.nelts == 1) {
+ iov = vec.elts;
+ return ngx_write_file(file, (u_char *) iov[0].iov_base,
+ iov[0].iov_len, offset);
+ }
+
+ if (file->sys_offset != offset) {
+ if (lseek(file->fd, offset, SEEK_SET) == -1) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "lseek() \"%s\" failed", file->name.data);
+ return NGX_ERROR;
+ }
+
+ file->sys_offset = offset;
+ }
+
+ n = writev(file->fd, vec.elts, vec.nelts);
+
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "writev() \"%s\" failed", file->name.data);
+ return NGX_ERROR;
+ }
+
+ if ((size_t) n != size) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, 0,
+ "writev() \"%s\" has written only %z of %uz",
+ file->name.data, n, size);
+ return NGX_ERROR;
+ }
+
+ file->sys_offset += n;
+ file->offset += n;
+
+ } while (cl);
+
+ return n;
+}
+
+
+ngx_int_t
+ngx_set_file_time(u_char *name, ngx_fd_t fd, time_t s)
+{
+ struct timeval tv[2];
+
+ tv[0].tv_sec = ngx_time();
+ tv[0].tv_usec = 0;
+ tv[1].tv_sec = s;
+ tv[1].tv_usec = 0;
+
+ if (utimes((char *) name, tv) != -1) {
+ return NGX_OK;
+ }
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_create_file_mapping(ngx_file_mapping_t *fm)
+{
+ fm->fd = ngx_open_file(fm->name, NGX_FILE_RDWR, NGX_FILE_TRUNCATE,
+ NGX_FILE_DEFAULT_ACCESS);
+ if (fm->fd == NGX_INVALID_FILE) {
+ ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno,
+ ngx_open_file_n " \"%s\" failed", fm->name);
+ return NGX_ERROR;
+ }
+
+ if (ftruncate(fm->fd, fm->size) == -1) {
+ ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno,
+ "ftruncate() \"%s\" failed", fm->name);
+ goto failed;
+ }
+
+ fm->addr = mmap(NULL, fm->size, PROT_READ|PROT_WRITE, MAP_SHARED,
+ fm->fd, 0);
+ if (fm->addr != MAP_FAILED) {
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno,
+ "mmap(%uz) \"%s\" failed", fm->size, fm->name);
+
+failed:
+
+ if (ngx_close_file(fm->fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, fm->log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", fm->name);
+ }
+
+ return NGX_ERROR;
+}
+
+
+void
+ngx_close_file_mapping(ngx_file_mapping_t *fm)
+{
+ if (munmap(fm->addr, fm->size) == -1) {
+ ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno,
+ "munmap(%uz) \"%s\" failed", fm->size, fm->name);
+ }
+
+ if (ngx_close_file(fm->fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, fm->log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", fm->name);
+ }
+}
+
+
+ngx_int_t
+ngx_open_dir(ngx_str_t *name, ngx_dir_t *dir)
+{
+ dir->dir = opendir((const char *) name->data);
+
+ if (dir->dir == NULL) {
+ return NGX_ERROR;
+ }
+
+ dir->valid_info = 0;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_read_dir(ngx_dir_t *dir)
+{
+ dir->de = readdir(dir->dir);
+
+ if (dir->de) {
+#if (NGX_HAVE_D_TYPE)
+ dir->type = dir->de->d_type;
+#else
+ dir->type = 0;
+#endif
+ return NGX_OK;
+ }
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_open_glob(ngx_glob_t *gl)
+{
+ int n;
+
+ n = glob((char *) gl->pattern, GLOB_NOSORT, NULL, &gl->pglob);
+
+ if (n == 0) {
+ return NGX_OK;
+ }
+
+#ifdef GLOB_NOMATCH
+
+ if (n == GLOB_NOMATCH && gl->test) {
+ return NGX_OK;
+ }
+
+#endif
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_read_glob(ngx_glob_t *gl, ngx_str_t *name)
+{
+ size_t count;
+
+#ifdef GLOB_NOMATCH
+ count = (size_t) gl->pglob.gl_pathc;
+#else
+ count = (size_t) gl->pglob.gl_matchc;
+#endif
+
+ if (gl->n < count) {
+
+ name->len = (size_t) ngx_strlen(gl->pglob.gl_pathv[gl->n]);
+ name->data = (u_char *) gl->pglob.gl_pathv[gl->n];
+ gl->n++;
+
+ return NGX_OK;
+ }
+
+ return NGX_DONE;
+}
+
+
+void
+ngx_close_glob(ngx_glob_t *gl)
+{
+ globfree(&gl->pglob);
+}
+
+
+ngx_err_t
+ngx_trylock_fd(ngx_fd_t fd)
+{
+ struct flock fl;
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_pid = 0;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+
+ if (fcntl(fd, F_SETLK, &fl) == -1) {
+ return ngx_errno;
+ }
+
+ return 0;
+}
+
+
+ngx_err_t
+ngx_lock_fd(ngx_fd_t fd)
+{
+ struct flock fl;
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_pid = 0;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+
+ if (fcntl(fd, F_SETLKW, &fl) == -1) {
+ return ngx_errno;
+ }
+
+ return 0;
+}
+
+
+ngx_err_t
+ngx_unlock_fd(ngx_fd_t fd)
+{
+ struct flock fl;
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_pid = 0;
+ fl.l_type = F_UNLCK;
+ fl.l_whence = SEEK_SET;
+
+ if (fcntl(fd, F_SETLK, &fl) == -1) {
+ return ngx_errno;
+ }
+
+ return 0;
+}
+
+
+#if (NGX_HAVE_POSIX_FADVISE)
+
+ngx_int_t
+ngx_read_ahead(ngx_fd_t fd, size_t n)
+{
+ int err;
+
+ err = posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
+
+ if (err == 0) {
+ return 0;
+ }
+
+ ngx_set_errno(err);
+ return NGX_FILE_ERROR;
+}
+
+#endif
+
+
+#if (NGX_HAVE_O_DIRECT)
+
+ngx_int_t
+ngx_directio_on(ngx_fd_t fd)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFL);
+
+ if (flags == -1) {
+ return NGX_FILE_ERROR;
+ }
+
+ return fcntl(fd, F_SETFL, flags | O_DIRECT);
+}
+
+
+ngx_int_t
+ngx_directio_off(ngx_fd_t fd)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFL);
+
+ if (flags == -1) {
+ return NGX_FILE_ERROR;
+ }
+
+ return fcntl(fd, F_SETFL, flags & ~O_DIRECT);
+}
+
+#endif
+
+
+#if (NGX_HAVE_STATFS)
+
+size_t
+ngx_fs_bsize(u_char *name)
+{
+ struct statfs fs;
+
+ if (statfs((char *) name, &fs) == -1) {
+ return 512;
+ }
+
+ if ((fs.f_bsize % 512) != 0) {
+ return 512;
+ }
+
+ return (size_t) fs.f_bsize;
+}
+
+#elif (NGX_HAVE_STATVFS)
+
+size_t
+ngx_fs_bsize(u_char *name)
+{
+ struct statvfs fs;
+
+ if (statvfs((char *) name, &fs) == -1) {
+ return 512;
+ }
+
+ if ((fs.f_frsize % 512) != 0) {
+ return 512;
+ }
+
+ return (size_t) fs.f_frsize;
+}
+
+#else
+
+size_t
+ngx_fs_bsize(u_char *name)
+{
+ return 512;
+}
+
+#endif
diff --git a/usr.sbin/nginx/src/os/unix/ngx_files.h b/usr.sbin/nginx/src/os/unix/ngx_files.h
new file mode 100644
index 00000000000..af41ea38ed4
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_files.h
@@ -0,0 +1,340 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_FILES_H_INCLUDED_
+#define _NGX_FILES_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef int ngx_fd_t;
+typedef struct stat ngx_file_info_t;
+typedef ino_t ngx_file_uniq_t;
+
+
+typedef struct {
+ u_char *name;
+ size_t size;
+ void *addr;
+ ngx_fd_t fd;
+ ngx_log_t *log;
+} ngx_file_mapping_t;
+
+
+typedef struct {
+ DIR *dir;
+ struct dirent *de;
+ struct stat info;
+
+ unsigned type:8;
+ unsigned valid_info:1;
+} ngx_dir_t;
+
+
+typedef struct {
+ size_t n;
+ glob_t pglob;
+ u_char *pattern;
+ ngx_log_t *log;
+ ngx_uint_t test;
+} ngx_glob_t;
+
+
+#define NGX_INVALID_FILE -1
+#define NGX_FILE_ERROR -1
+
+
+
+#ifdef __CYGWIN__
+
+#define NGX_HAVE_CASELESS_FILESYSTEM 1
+
+#define ngx_open_file(name, mode, create, access) \
+ open((const char *) name, mode|create|O_BINARY, access)
+
+#else
+
+#define ngx_open_file(name, mode, create, access) \
+ open((const char *) name, mode|create, access)
+
+#endif
+
+#define ngx_open_file_n "open()"
+
+#define NGX_FILE_RDONLY O_RDONLY
+#define NGX_FILE_WRONLY O_WRONLY
+#define NGX_FILE_RDWR O_RDWR
+#define NGX_FILE_CREATE_OR_OPEN O_CREAT
+#define NGX_FILE_OPEN 0
+#define NGX_FILE_TRUNCATE O_CREAT|O_TRUNC
+#define NGX_FILE_APPEND O_WRONLY|O_APPEND
+#define NGX_FILE_NONBLOCK O_NONBLOCK
+
+#define NGX_FILE_DEFAULT_ACCESS 0644
+#define NGX_FILE_OWNER_ACCESS 0600
+
+
+#define ngx_close_file close
+#define ngx_close_file_n "close()"
+
+
+#define ngx_delete_file(name) unlink((const char *) name)
+#define ngx_delete_file_n "unlink()"
+
+
+ngx_fd_t ngx_open_tempfile(u_char *name, ngx_uint_t persistent,
+ ngx_uint_t access);
+#define ngx_open_tempfile_n "open()"
+
+
+ssize_t ngx_read_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset);
+#if (NGX_HAVE_PREAD)
+#define ngx_read_file_n "pread()"
+#else
+#define ngx_read_file_n "read()"
+#endif
+
+ssize_t ngx_write_file(ngx_file_t *file, u_char *buf, size_t size,
+ off_t offset);
+
+ssize_t ngx_write_chain_to_file(ngx_file_t *file, ngx_chain_t *ce,
+ off_t offset, ngx_pool_t *pool);
+
+
+#define ngx_read_fd read
+#define ngx_read_fd_n "read()"
+
+/*
+ * we use inlined function instead of simple #define
+ * because glibc 2.3 sets warn_unused_result attribute for write()
+ * and in this case gcc 4.3 ignores (void) cast
+ */
+static ngx_inline ssize_t
+ngx_write_fd(ngx_fd_t fd, void *buf, size_t n)
+{
+ return write(fd, buf, n);
+}
+
+#define ngx_write_fd_n "write()"
+
+
+#define ngx_write_console ngx_write_fd
+
+
+#define ngx_linefeed(p) *p++ = LF;
+#define NGX_LINEFEED_SIZE 1
+
+
+#define ngx_rename_file(o, n) rename((const char *) o, (const char *) n)
+#define ngx_rename_file_n "rename()"
+
+
+#define ngx_change_file_access(n, a) chmod((const char *) n, a)
+#define ngx_change_file_access_n "chmod()"
+
+
+ngx_int_t ngx_set_file_time(u_char *name, ngx_fd_t fd, time_t s);
+#define ngx_set_file_time_n "utimes()"
+
+
+#define ngx_file_info(file, sb) stat((const char *) file, sb)
+#define ngx_file_info_n "stat()"
+
+#define ngx_fd_info(fd, sb) fstat(fd, sb)
+#define ngx_fd_info_n "fstat()"
+
+#define ngx_link_info(file, sb) lstat((const char *) file, sb)
+#define ngx_link_info_n "lstat()"
+
+#define ngx_is_dir(sb) (S_ISDIR((sb)->st_mode))
+#define ngx_is_file(sb) (S_ISREG((sb)->st_mode))
+#define ngx_is_link(sb) (S_ISLNK((sb)->st_mode))
+#define ngx_is_exec(sb) (((sb)->st_mode & S_IXUSR) == S_IXUSR)
+#define ngx_file_access(sb) ((sb)->st_mode & 0777)
+#define ngx_file_size(sb) (sb)->st_size
+#define ngx_file_fs_size(sb) ((sb)->st_blocks * 512)
+#define ngx_file_mtime(sb) (sb)->st_mtime
+#define ngx_file_uniq(sb) (sb)->st_ino
+
+
+ngx_int_t ngx_create_file_mapping(ngx_file_mapping_t *fm);
+void ngx_close_file_mapping(ngx_file_mapping_t *fm);
+
+
+#if (NGX_HAVE_CASELESS_FILESYSTEM)
+
+#define ngx_filename_cmp(s1, s2, n) strncasecmp((char *) s1, (char *) s2, n)
+
+#else
+
+#define ngx_filename_cmp ngx_memcmp
+
+#endif
+
+
+#define ngx_realpath(p, r) realpath((char *) p, (char *) r)
+#define ngx_realpath_n "realpath()"
+#define ngx_getcwd(buf, size) (getcwd((char *) buf, size) != NULL)
+#define ngx_getcwd_n "getcwd()"
+#define ngx_path_separator(c) ((c) == '/')
+
+#define NGX_MAX_PATH PATH_MAX
+
+#define NGX_DIR_MASK_LEN 0
+
+
+ngx_int_t ngx_open_dir(ngx_str_t *name, ngx_dir_t *dir);
+#define ngx_open_dir_n "opendir()"
+
+
+#define ngx_close_dir(d) closedir((d)->dir)
+#define ngx_close_dir_n "closedir()"
+
+
+ngx_int_t ngx_read_dir(ngx_dir_t *dir);
+#define ngx_read_dir_n "readdir()"
+
+
+#define ngx_create_dir(name, access) mkdir((const char *) name, access)
+#define ngx_create_dir_n "mkdir()"
+
+
+#define ngx_delete_dir(name) rmdir((const char *) name)
+#define ngx_delete_dir_n "rmdir()"
+
+
+#define ngx_dir_access(a) (a | (a & 0444) >> 2)
+
+
+#define ngx_de_name(dir) ((u_char *) (dir)->de->d_name)
+#if (NGX_HAVE_D_NAMLEN)
+#define ngx_de_namelen(dir) (dir)->de->d_namlen
+#else
+#define ngx_de_namelen(dir) ngx_strlen((dir)->de->d_name)
+#endif
+
+static ngx_inline ngx_int_t
+ngx_de_info(u_char *name, ngx_dir_t *dir)
+{
+ dir->type = 0;
+ return stat((const char *) name, &dir->info);
+}
+
+#define ngx_de_info_n "stat()"
+#define ngx_de_link_info(name, dir) lstat((const char *) name, &(dir)->info)
+#define ngx_de_link_info_n "lstat()"
+
+#if (NGX_HAVE_D_TYPE)
+
+/*
+ * some file systems (e.g. XFS on Linux and CD9660 on FreeBSD)
+ * do not set dirent.d_type
+ */
+
+#define ngx_de_is_dir(dir) \
+ (((dir)->type) ? ((dir)->type == DT_DIR) : (S_ISDIR((dir)->info.st_mode)))
+#define ngx_de_is_file(dir) \
+ (((dir)->type) ? ((dir)->type == DT_REG) : (S_ISREG((dir)->info.st_mode)))
+#define ngx_de_is_link(dir) \
+ (((dir)->type) ? ((dir)->type == DT_LNK) : (S_ISLNK((dir)->info.st_mode)))
+
+#else
+
+#define ngx_de_is_dir(dir) (S_ISDIR((dir)->info.st_mode))
+#define ngx_de_is_file(dir) (S_ISREG((dir)->info.st_mode))
+#define ngx_de_is_link(dir) (S_ISLNK((dir)->info.st_mode))
+
+#endif
+
+#define ngx_de_access(dir) (((dir)->info.st_mode) & 0777)
+#define ngx_de_size(dir) (dir)->info.st_size
+#define ngx_de_fs_size(dir) ((dir)->info.st_blocks * 512)
+#define ngx_de_mtime(dir) (dir)->info.st_mtime
+
+
+ngx_int_t ngx_open_glob(ngx_glob_t *gl);
+#define ngx_open_glob_n "glob()"
+ngx_int_t ngx_read_glob(ngx_glob_t *gl, ngx_str_t *name);
+void ngx_close_glob(ngx_glob_t *gl);
+
+
+ngx_err_t ngx_trylock_fd(ngx_fd_t fd);
+ngx_err_t ngx_lock_fd(ngx_fd_t fd);
+ngx_err_t ngx_unlock_fd(ngx_fd_t fd);
+
+#define ngx_trylock_fd_n "fcntl(F_SETLK, F_WRLCK)"
+#define ngx_lock_fd_n "fcntl(F_SETLKW, F_WRLCK)"
+#define ngx_unlock_fd_n "fcntl(F_SETLK, F_UNLCK)"
+
+
+#if (NGX_HAVE_F_READAHEAD)
+
+#define NGX_HAVE_READ_AHEAD 1
+
+#define ngx_read_ahead(fd, n) fcntl(fd, F_READAHEAD, (int) n)
+#define ngx_read_ahead_n "fcntl(fd, F_READAHEAD)"
+
+#elif (NGX_HAVE_POSIX_FADVISE)
+
+#define NGX_HAVE_READ_AHEAD 1
+
+ngx_int_t ngx_read_ahead(ngx_fd_t fd, size_t n);
+#define ngx_read_ahead_n "posix_fadvise(POSIX_FADV_SEQUENTIAL)"
+
+#else
+
+#define ngx_read_ahead(fd, n) 0
+#define ngx_read_ahead_n "ngx_read_ahead_n"
+
+#endif
+
+
+#if (NGX_HAVE_O_DIRECT)
+
+ngx_int_t ngx_directio_on(ngx_fd_t fd);
+#define ngx_directio_on_n "fcntl(O_DIRECT)"
+
+ngx_int_t ngx_directio_off(ngx_fd_t fd);
+#define ngx_directio_off_n "fcntl(!O_DIRECT)"
+
+#elif (NGX_HAVE_F_NOCACHE)
+
+#define ngx_directio_on(fd) fcntl(fd, F_NOCACHE, 1)
+#define ngx_directio_on_n "fcntl(F_NOCACHE, 1)"
+
+#elif (NGX_HAVE_DIRECTIO)
+
+#define ngx_directio_on(fd) directio(fd, DIRECTIO_ON)
+#define ngx_directio_on_n "directio(DIRECTIO_ON)"
+
+#else
+
+#define ngx_directio_on(fd) 0
+#define ngx_directio_on_n "ngx_directio_on_n"
+
+#endif
+
+size_t ngx_fs_bsize(u_char *name);
+
+
+#define ngx_stderr STDERR_FILENO
+#define ngx_set_stderr(fd) dup2(fd, STDERR_FILENO)
+#define ngx_set_stderr_n "dup2(STDERR_FILENO)"
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+ssize_t ngx_file_aio_read(ngx_file_t *file, u_char *buf, size_t size,
+ off_t offset, ngx_pool_t *pool);
+
+extern ngx_uint_t ngx_file_aio;
+
+#endif
+
+
+#endif /* _NGX_FILES_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_freebsd.h b/usr.sbin/nginx/src/os/unix/ngx_freebsd.h
new file mode 100644
index 00000000000..67fce3eb7ba
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_freebsd.h
@@ -0,0 +1,23 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_FREEBSD_H_INCLUDED_
+#define _NGX_FREEBSD_H_INCLUDED_
+
+
+ngx_chain_t *ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in,
+ off_t limit);
+
+extern int ngx_freebsd_kern_osreldate;
+extern int ngx_freebsd_hw_ncpu;
+extern u_long ngx_freebsd_net_inet_tcp_sendspace;
+
+extern ngx_uint_t ngx_freebsd_sendfile_nbytes_bug;
+extern ngx_uint_t ngx_freebsd_use_tcp_nopush;
+extern ngx_uint_t ngx_freebsd_debug_malloc;
+
+
+#endif /* _NGX_FREEBSD_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_freebsd_config.h b/usr.sbin/nginx/src/os/unix/ngx_freebsd_config.h
new file mode 100644
index 00000000000..ec7a375a869
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_freebsd_config.h
@@ -0,0 +1,123 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_FREEBSD_CONFIG_H_INCLUDED_
+#define _NGX_FREEBSD_CONFIG_H_INCLUDED_
+
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stddef.h> /* offsetof() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+#include <glob.h>
+#include <sys/param.h> /* ALIGN() */
+#include <sys/mount.h> /* statfs() */
+
+#include <sys/filio.h> /* FIONBIO */
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sched.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h> /* TCP_NODELAY, TCP_NOPUSH */
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/un.h>
+
+#include <libutil.h> /* setproctitle() before 4.1 */
+#include <osreldate.h>
+#include <sys/sysctl.h>
+
+
+#if __FreeBSD_version < 400017
+
+/*
+ * FreeBSD 3.x has no CMSG_SPACE() and CMSG_LEN() and has the broken CMSG_DATA()
+ */
+
+#undef CMSG_SPACE
+#define CMSG_SPACE(l) (ALIGN(sizeof(struct cmsghdr)) + ALIGN(l))
+
+#undef CMSG_LEN
+#define CMSG_LEN(l) (ALIGN(sizeof(struct cmsghdr)) + (l))
+
+#undef CMSG_DATA
+#define CMSG_DATA(cmsg) ((u_char *)(cmsg) + ALIGN(sizeof(struct cmsghdr)))
+
+#endif
+
+
+#include <ngx_auto_config.h>
+
+
+#if (NGX_HAVE_POSIX_SEM)
+#include <semaphore.h>
+#endif
+
+
+#if (NGX_HAVE_POLL)
+#include <poll.h>
+#endif
+
+
+#if (NGX_HAVE_KQUEUE)
+#include <sys/event.h>
+#endif
+
+
+#if (NGX_HAVE_FILE_AIO || NGX_HAVE_AIO)
+#include <aio.h>
+typedef struct aiocb ngx_aiocb_t;
+#endif
+
+
+#define NGX_LISTEN_BACKLOG -1
+
+
+#if (defined SO_ACCEPTFILTER && !defined NGX_HAVE_DEFERRED_ACCEPT)
+#define NGX_HAVE_DEFERRED_ACCEPT 1
+#endif
+
+
+#if (__FreeBSD_version < 430000 || __FreeBSD_version < 500012)
+
+pid_t rfork_thread(int flags, void *stack, int (*func)(void *arg), void *arg);
+
+#endif
+
+#ifndef IOV_MAX
+#define IOV_MAX 1024
+#endif
+
+
+#ifndef NGX_HAVE_INHERITED_NONBLOCK
+#define NGX_HAVE_INHERITED_NONBLOCK 1
+#endif
+
+
+#define NGX_HAVE_OS_SPECIFIC_INIT 1
+
+
+extern char **environ;
+extern char *malloc_options;
+
+
+#endif /* _NGX_FREEBSD_CONFIG_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_freebsd_init.c b/usr.sbin/nginx/src/os/unix/ngx_freebsd_init.c
new file mode 100644
index 00000000000..1211c7ccb74
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_freebsd_init.c
@@ -0,0 +1,261 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/* FreeBSD 3.0 at least */
+char ngx_freebsd_kern_ostype[16];
+char ngx_freebsd_kern_osrelease[128];
+int ngx_freebsd_kern_osreldate;
+int ngx_freebsd_hw_ncpu;
+int ngx_freebsd_kern_ipc_somaxconn;
+u_long ngx_freebsd_net_inet_tcp_sendspace;
+
+/* FreeBSD 4.9 */
+int ngx_freebsd_machdep_hlt_logical_cpus;
+
+
+ngx_uint_t ngx_freebsd_sendfile_nbytes_bug;
+ngx_uint_t ngx_freebsd_use_tcp_nopush;
+ngx_uint_t ngx_freebsd_debug_malloc;
+
+
+static ngx_os_io_t ngx_freebsd_io = {
+ ngx_unix_recv,
+ ngx_readv_chain,
+ ngx_udp_unix_recv,
+ ngx_unix_send,
+#if (NGX_HAVE_SENDFILE)
+ ngx_freebsd_sendfile_chain,
+ NGX_IO_SENDFILE
+#else
+ ngx_writev_chain,
+ 0
+#endif
+};
+
+
+typedef struct {
+ char *name;
+ void *value;
+ size_t size;
+ ngx_uint_t exists;
+} sysctl_t;
+
+
+sysctl_t sysctls[] = {
+ { "hw.ncpu",
+ &ngx_freebsd_hw_ncpu,
+ sizeof(ngx_freebsd_hw_ncpu), 0 },
+
+ { "machdep.hlt_logical_cpus",
+ &ngx_freebsd_machdep_hlt_logical_cpus,
+ sizeof(ngx_freebsd_machdep_hlt_logical_cpus), 0 },
+
+ { "net.inet.tcp.sendspace",
+ &ngx_freebsd_net_inet_tcp_sendspace,
+ sizeof(ngx_freebsd_net_inet_tcp_sendspace), 0 },
+
+ { "kern.ipc.somaxconn",
+ &ngx_freebsd_kern_ipc_somaxconn,
+ sizeof(ngx_freebsd_kern_ipc_somaxconn), 0 },
+
+ { NULL, NULL, 0, 0 }
+};
+
+
+void
+ngx_debug_init()
+{
+#if (NGX_DEBUG_MALLOC)
+
+#if __FreeBSD_version >= 500014
+ _malloc_options = "J";
+#else
+ malloc_options = "J";
+#endif
+
+ ngx_freebsd_debug_malloc = 1;
+
+#else
+ char *mo;
+
+ mo = getenv("MALLOC_OPTIONS");
+
+ if (mo && ngx_strchr(mo, 'J')) {
+ ngx_freebsd_debug_malloc = 1;
+ }
+#endif
+}
+
+
+ngx_int_t
+ngx_os_specific_init(ngx_log_t *log)
+{
+ int version, somaxconn;
+ size_t size;
+ ngx_err_t err;
+ ngx_uint_t i;
+
+ size = sizeof(ngx_freebsd_kern_ostype);
+ if (sysctlbyname("kern.ostype",
+ ngx_freebsd_kern_ostype, &size, NULL, 0) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ "sysctlbyname(kern.ostype) failed");
+
+ if (ngx_errno != NGX_ENOMEM) {
+ return NGX_ERROR;
+ }
+
+ ngx_freebsd_kern_ostype[size - 1] = '\0';
+ }
+
+ size = sizeof(ngx_freebsd_kern_osrelease);
+ if (sysctlbyname("kern.osrelease",
+ ngx_freebsd_kern_osrelease, &size, NULL, 0) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ "sysctlbyname(kern.osrelease) failed");
+
+ if (ngx_errno != NGX_ENOMEM) {
+ return NGX_ERROR;
+ }
+
+ ngx_freebsd_kern_osrelease[size - 1] = '\0';
+ }
+
+
+ size = sizeof(int);
+ if (sysctlbyname("kern.osreldate",
+ &ngx_freebsd_kern_osreldate, &size, NULL, 0) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ "sysctlbyname(kern.osreldate) failed");
+ return NGX_ERROR;
+ }
+
+ version = ngx_freebsd_kern_osreldate;
+
+
+#if (NGX_HAVE_SENDFILE)
+
+ /*
+ * The determination of the sendfile() "nbytes bug" is complex enough.
+ * There are two sendfile() syscalls: a new #393 has no bug while
+ * an old #336 has the bug in some versions and has not in others.
+ * Besides libc_r wrapper also emulates the bug in some versions.
+ * There is no way to say exactly if syscall #336 in FreeBSD circa 4.6
+ * has the bug. We use the algorithm that is correct at least for
+ * RELEASEs and for syscalls only (not libc_r wrapper).
+ *
+ * 4.6.1-RELEASE and below have the bug
+ * 4.6.2-RELEASE and above have the new syscall
+ *
+ * We detect the new sendfile() syscall available at the compile time
+ * to allow an old binary to run correctly on an updated FreeBSD system.
+ */
+
+#if (__FreeBSD__ == 4 && __FreeBSD_version >= 460102) \
+ || __FreeBSD_version == 460002 || __FreeBSD_version >= 500039
+
+ /* a new syscall without the bug */
+
+ ngx_freebsd_sendfile_nbytes_bug = 0;
+
+#else
+
+ /* an old syscall that may have the bug */
+
+ ngx_freebsd_sendfile_nbytes_bug = 1;
+
+#endif
+
+#endif /* NGX_HAVE_SENDFILE */
+
+
+ if ((version < 500000 && version >= 440003) || version >= 500017) {
+ ngx_freebsd_use_tcp_nopush = 1;
+ }
+
+
+ for (i = 0; sysctls[i].name; i++) {
+ size = sysctls[i].size;
+
+ if (sysctlbyname(sysctls[i].name, sysctls[i].value, &size, NULL, 0)
+ == 0)
+ {
+ sysctls[i].exists = 1;
+ continue;
+ }
+
+ err = ngx_errno;
+
+ if (err == NGX_ENOENT) {
+ continue;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, log, err,
+ "sysctlbyname(%s) failed", sysctls[i].name);
+ return NGX_ERROR;
+ }
+
+ if (ngx_freebsd_machdep_hlt_logical_cpus) {
+ ngx_ncpu = ngx_freebsd_hw_ncpu / 2;
+
+ } else {
+ ngx_ncpu = ngx_freebsd_hw_ncpu;
+ }
+
+ somaxconn = version < 600008 ? 32676 : 65535;
+
+ if (ngx_freebsd_kern_ipc_somaxconn > somaxconn) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "sysctl kern.ipc.somaxconn must be no more than %d",
+ somaxconn);
+ return NGX_ERROR;
+ }
+
+ ngx_tcp_nodelay_and_tcp_nopush = 1;
+
+ ngx_os_io = ngx_freebsd_io;
+
+ return NGX_OK;
+}
+
+
+void
+ngx_os_specific_status(ngx_log_t *log)
+{
+ u_long value;
+ ngx_uint_t i;
+
+ ngx_log_error(NGX_LOG_NOTICE, log, 0, "OS: %s %s",
+ ngx_freebsd_kern_ostype, ngx_freebsd_kern_osrelease);
+
+#ifdef __DragonFly_version
+ ngx_log_error(NGX_LOG_NOTICE, log, 0,
+ "kern.osreldate: %d, built on %d",
+ ngx_freebsd_kern_osreldate, __DragonFly_version);
+#else
+ ngx_log_error(NGX_LOG_NOTICE, log, 0,
+ "kern.osreldate: %d, built on %d",
+ ngx_freebsd_kern_osreldate, __FreeBSD_version);
+#endif
+
+ for (i = 0; sysctls[i].name; i++) {
+ if (sysctls[i].exists) {
+ if (sysctls[i].size == sizeof(long)) {
+ value = *(long *) sysctls[i].value;
+
+ } else {
+ value = *(int *) sysctls[i].value;
+ }
+
+ ngx_log_error(NGX_LOG_NOTICE, log, 0, "%s: %l",
+ sysctls[i].name, value);
+ }
+ }
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_freebsd_rfork_thread.c b/usr.sbin/nginx/src/os/unix/ngx_freebsd_rfork_thread.c
new file mode 100644
index 00000000000..3c550fb8af4
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_freebsd_rfork_thread.c
@@ -0,0 +1,755 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+/*
+ * The threads implementation uses the rfork(RFPROC|RFTHREAD|RFMEM) syscall
+ * to create threads. All threads use the stacks of the same size mmap()ed
+ * below the main stack. Thus the current thread id is determinated via
+ * the stack pointer value.
+ *
+ * The mutex implementation uses the ngx_atomic_cmp_set() operation
+ * to acquire a mutex and the SysV semaphore to wait on a mutex and to wake up
+ * the waiting threads. The light mutex does not use semaphore, so after
+ * spinning in the lock the thread calls sched_yield(). However the light
+ * mutecies are intended to be used with the "trylock" operation only.
+ * The SysV semop() is a cheap syscall, particularly if it has little sembuf's
+ * and does not use SEM_UNDO.
+ *
+ * The condition variable implementation uses the signal #64.
+ * The signal handler is SIG_IGN so the kill() is a cheap syscall.
+ * The thread waits a signal in kevent(). The use of the EVFILT_SIGNAL
+ * is safe since FreeBSD 4.10-STABLE.
+ *
+ * This threads implementation currently works on i386 (486+) and amd64
+ * platforms only.
+ */
+
+
+char *ngx_freebsd_kern_usrstack;
+size_t ngx_thread_stack_size;
+
+
+static size_t rz_size;
+static size_t usable_stack_size;
+static char *last_stack;
+
+static ngx_uint_t nthreads;
+static ngx_uint_t max_threads;
+
+static ngx_uint_t nkeys;
+static ngx_tid_t *tids; /* the threads tids array */
+void **ngx_tls; /* the threads tls's array */
+
+/* the thread-safe libc errno */
+
+static int errno0; /* the main thread's errno */
+static int *errnos; /* the threads errno's array */
+
+int *
+__error()
+{
+ int tid;
+
+ tid = ngx_gettid();
+
+ return tid ? &errnos[tid - 1] : &errno0;
+}
+
+
+/*
+ * __isthreaded enables the spinlocks in some libc functions, i.e. in malloc()
+ * and some other places. Nevertheless we protect our malloc()/free() calls
+ * by own mutex that is more efficient than the spinlock.
+ *
+ * _spinlock() is a weak referenced stub in src/lib/libc/gen/_spinlock_stub.c
+ * that does nothing.
+ */
+
+extern int __isthreaded;
+
+void
+_spinlock(ngx_atomic_t *lock)
+{
+ ngx_int_t tries;
+
+ tries = 0;
+
+ for ( ;; ) {
+
+ if (*lock) {
+ if (ngx_ncpu > 1 && tries++ < 1000) {
+ continue;
+ }
+
+ sched_yield();
+ tries = 0;
+
+ } else {
+ if (ngx_atomic_cmp_set(lock, 0, 1)) {
+ return;
+ }
+ }
+ }
+}
+
+
+/*
+ * Before FreeBSD 5.1 _spinunlock() is a simple #define in
+ * src/lib/libc/include/spinlock.h that zeroes lock.
+ *
+ * Since FreeBSD 5.1 _spinunlock() is a weak referenced stub in
+ * src/lib/libc/gen/_spinlock_stub.c that does nothing.
+ */
+
+#ifndef _spinunlock
+
+void
+_spinunlock(ngx_atomic_t *lock)
+{
+ *lock = 0;
+}
+
+#endif
+
+
+ngx_err_t
+ngx_create_thread(ngx_tid_t *tid, ngx_thread_value_t (*func)(void *arg),
+ void *arg, ngx_log_t *log)
+{
+ ngx_pid_t id;
+ ngx_err_t err;
+ char *stack, *stack_top;
+
+ if (nthreads >= max_threads) {
+ ngx_log_error(NGX_LOG_CRIT, log, 0,
+ "no more than %ui threads can be created", max_threads);
+ return NGX_ERROR;
+ }
+
+ last_stack -= ngx_thread_stack_size;
+
+ stack = mmap(last_stack, usable_stack_size, PROT_READ|PROT_WRITE,
+ MAP_STACK, -1, 0);
+
+ if (stack == MAP_FAILED) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ "mmap(%p:%uz, MAP_STACK) thread stack failed",
+ last_stack, usable_stack_size);
+ return NGX_ERROR;
+ }
+
+ if (stack != last_stack) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "stack %p address was changed to %p", last_stack, stack);
+ return NGX_ERROR;
+ }
+
+ stack_top = stack + usable_stack_size;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, log, 0,
+ "thread stack: %p-%p", stack, stack_top);
+
+ ngx_set_errno(0);
+
+ id = rfork_thread(RFPROC|RFTHREAD|RFMEM, stack_top,
+ (ngx_rfork_thread_func_pt) func, arg);
+
+ err = ngx_errno;
+
+ if (id == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, err, "rfork() failed");
+
+ } else {
+ *tid = id;
+ nthreads = (ngx_freebsd_kern_usrstack - stack_top)
+ / ngx_thread_stack_size;
+ tids[nthreads] = id;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, "rfork()ed thread: %P", id);
+ }
+
+ return err;
+}
+
+
+ngx_int_t
+ngx_init_threads(int n, size_t size, ngx_cycle_t *cycle)
+{
+ char *red_zone, *zone;
+ size_t len;
+ ngx_int_t i;
+ struct sigaction sa;
+
+ max_threads = n + 1;
+
+ for (i = 0; i < n; i++) {
+ ngx_memzero(&sa, sizeof(struct sigaction));
+ sa.sa_handler = SIG_IGN;
+ sigemptyset(&sa.sa_mask);
+ if (sigaction(NGX_CV_SIGNAL, &sa, NULL) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "sigaction(%d, SIG_IGN) failed", NGX_CV_SIGNAL);
+ return NGX_ERROR;
+ }
+ }
+
+ len = sizeof(ngx_freebsd_kern_usrstack);
+ if (sysctlbyname("kern.usrstack", &ngx_freebsd_kern_usrstack, &len,
+ NULL, 0) == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "sysctlbyname(kern.usrstack) failed");
+ return NGX_ERROR;
+ }
+
+ /* the main thread stack red zone */
+ rz_size = ngx_pagesize;
+ red_zone = ngx_freebsd_kern_usrstack - (size + rz_size);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+ "usrstack: %p red zone: %p",
+ ngx_freebsd_kern_usrstack, red_zone);
+
+ zone = mmap(red_zone, rz_size, PROT_NONE, MAP_ANON, -1, 0);
+ if (zone == MAP_FAILED) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "mmap(%p:%uz, PROT_NONE, MAP_ANON) red zone failed",
+ red_zone, rz_size);
+ return NGX_ERROR;
+ }
+
+ if (zone != red_zone) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "red zone %p address was changed to %p", red_zone, zone);
+ return NGX_ERROR;
+ }
+
+ /* create the thread errno' array */
+
+ errnos = ngx_calloc(n * sizeof(int), cycle->log);
+ if (errnos == NULL) {
+ return NGX_ERROR;
+ }
+
+ /* create the thread tids array */
+
+ tids = ngx_calloc((n + 1) * sizeof(ngx_tid_t), cycle->log);
+ if (tids == NULL) {
+ return NGX_ERROR;
+ }
+
+ tids[0] = ngx_pid;
+
+ /* create the thread tls' array */
+
+ ngx_tls = ngx_calloc(NGX_THREAD_KEYS_MAX * (n + 1) * sizeof(void *),
+ cycle->log);
+ if (ngx_tls == NULL) {
+ return NGX_ERROR;
+ }
+
+ nthreads = 1;
+
+ last_stack = zone + rz_size;
+ usable_stack_size = size;
+ ngx_thread_stack_size = size + rz_size;
+
+ /* allow the spinlock in libc malloc() */
+ __isthreaded = 1;
+
+ ngx_threaded = 1;
+
+ return NGX_OK;
+}
+
+
+ngx_tid_t
+ngx_thread_self()
+{
+ ngx_int_t tid;
+
+ tid = ngx_gettid();
+
+ if (tids == NULL) {
+ return ngx_pid;
+ }
+
+ return tids[tid];
+}
+
+
+ngx_err_t
+ngx_thread_key_create(ngx_tls_key_t *key)
+{
+ if (nkeys >= NGX_THREAD_KEYS_MAX) {
+ return NGX_ENOMEM;
+ }
+
+ *key = nkeys++;
+
+ return 0;
+}
+
+
+ngx_err_t
+ngx_thread_set_tls(ngx_tls_key_t key, void *value)
+{
+ if (key >= NGX_THREAD_KEYS_MAX) {
+ return NGX_EINVAL;
+ }
+
+ ngx_tls[key * NGX_THREAD_KEYS_MAX + ngx_gettid()] = value;
+ return 0;
+}
+
+
+ngx_mutex_t *
+ngx_mutex_init(ngx_log_t *log, ngx_uint_t flags)
+{
+ ngx_mutex_t *m;
+ union semun op;
+
+ m = ngx_alloc(sizeof(ngx_mutex_t), log);
+ if (m == NULL) {
+ return NULL;
+ }
+
+ m->lock = 0;
+ m->log = log;
+
+ if (flags & NGX_MUTEX_LIGHT) {
+ m->semid = -1;
+ return m;
+ }
+
+ m->semid = semget(IPC_PRIVATE, 1, SEM_R|SEM_A);
+ if (m->semid == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "semget() failed");
+ return NULL;
+ }
+
+ op.val = 0;
+
+ if (semctl(m->semid, 0, SETVAL, op) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "semctl(SETVAL) failed");
+
+ if (semctl(m->semid, 0, IPC_RMID) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ "semctl(IPC_RMID) failed");
+ }
+
+ return NULL;
+ }
+
+ return m;
+}
+
+
+void
+ngx_mutex_destroy(ngx_mutex_t *m)
+{
+ if (semctl(m->semid, 0, IPC_RMID) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, m->log, ngx_errno,
+ "semctl(IPC_RMID) failed");
+ }
+
+ ngx_free((void *) m);
+}
+
+
+ngx_int_t
+ngx_mutex_dolock(ngx_mutex_t *m, ngx_int_t try)
+{
+ uint32_t lock, old;
+ ngx_uint_t tries;
+ struct sembuf op;
+
+ if (!ngx_threaded) {
+ return NGX_OK;
+ }
+
+#if (NGX_DEBUG)
+ if (try) {
+ ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "try lock mutex %p lock:%XD", m, m->lock);
+ } else {
+ ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "lock mutex %p lock:%XD", m, m->lock);
+ }
+#endif
+
+ old = m->lock;
+ tries = 0;
+
+ for ( ;; ) {
+ if (old & NGX_MUTEX_LOCK_BUSY) {
+
+ if (try) {
+ return NGX_AGAIN;
+ }
+
+ if (ngx_ncpu > 1 && tries++ < 1000) {
+
+ /* the spinlock is used only on the SMP system */
+
+ old = m->lock;
+ continue;
+ }
+
+ if (m->semid == -1) {
+ sched_yield();
+
+ tries = 0;
+ old = m->lock;
+ continue;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "mutex %p lock:%XD", m, m->lock);
+
+ /*
+ * The mutex is locked so we increase a number
+ * of the threads that are waiting on the mutex
+ */
+
+ lock = old + 1;
+
+ if ((lock & ~NGX_MUTEX_LOCK_BUSY) > nthreads) {
+ ngx_log_error(NGX_LOG_ALERT, m->log, ngx_errno,
+ "%D threads wait for mutex %p, "
+ "while only %ui threads are available",
+ lock & ~NGX_MUTEX_LOCK_BUSY, m, nthreads);
+ ngx_abort();
+ }
+
+ if (ngx_atomic_cmp_set(&m->lock, old, lock)) {
+
+ ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "wait mutex %p lock:%XD", m, m->lock);
+
+ /*
+ * The number of the waiting threads has been increased
+ * and we would wait on the SysV semaphore.
+ * A semaphore should wake up us more efficiently than
+ * a simple sched_yield() or usleep().
+ */
+
+ op.sem_num = 0;
+ op.sem_op = -1;
+ op.sem_flg = 0;
+
+ if (semop(m->semid, &op, 1) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, m->log, ngx_errno,
+ "semop() failed while waiting on mutex %p", m);
+ ngx_abort();
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "mutex waked up %p lock:%XD", m, m->lock);
+
+ tries = 0;
+ old = m->lock;
+ continue;
+ }
+
+ old = m->lock;
+
+ } else {
+ lock = old | NGX_MUTEX_LOCK_BUSY;
+
+ if (ngx_atomic_cmp_set(&m->lock, old, lock)) {
+
+ /* we locked the mutex */
+
+ break;
+ }
+
+ old = m->lock;
+ }
+
+ if (tries++ > 1000) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "mutex %p is contested", m);
+
+ /* the mutex is probably contested so we are giving up now */
+
+ sched_yield();
+
+ tries = 0;
+ old = m->lock;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "mutex %p is locked, lock:%XD", m, m->lock);
+
+ return NGX_OK;
+}
+
+
+void
+ngx_mutex_unlock(ngx_mutex_t *m)
+{
+ uint32_t lock, old;
+ struct sembuf op;
+
+ if (!ngx_threaded) {
+ return;
+ }
+
+ old = m->lock;
+
+ if (!(old & NGX_MUTEX_LOCK_BUSY)) {
+ ngx_log_error(NGX_LOG_ALERT, m->log, 0,
+ "trying to unlock the free mutex %p", m);
+ ngx_abort();
+ }
+
+ /* free the mutex */
+
+#if 0
+ ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "unlock mutex %p lock:%XD", m, old);
+#endif
+
+ for ( ;; ) {
+ lock = old & ~NGX_MUTEX_LOCK_BUSY;
+
+ if (ngx_atomic_cmp_set(&m->lock, old, lock)) {
+ break;
+ }
+
+ old = m->lock;
+ }
+
+ if (m->semid == -1) {
+ ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "mutex %p is unlocked", m);
+
+ return;
+ }
+
+ /* check whether we need to wake up a waiting thread */
+
+ old = m->lock;
+
+ for ( ;; ) {
+ if (old & NGX_MUTEX_LOCK_BUSY) {
+
+ /* the mutex is just locked by another thread */
+
+ break;
+ }
+
+ if (old == 0) {
+ break;
+ }
+
+ /* there are the waiting threads */
+
+ lock = old - 1;
+
+ if (ngx_atomic_cmp_set(&m->lock, old, lock)) {
+
+ /* wake up the thread that waits on semaphore */
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "wake up mutex %p", m);
+
+ op.sem_num = 0;
+ op.sem_op = 1;
+ op.sem_flg = 0;
+
+ if (semop(m->semid, &op, 1) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, m->log, ngx_errno,
+ "semop() failed while waking up on mutex %p", m);
+ ngx_abort();
+ }
+
+ break;
+ }
+
+ old = m->lock;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "mutex %p is unlocked", m);
+
+ return;
+}
+
+
+ngx_cond_t *
+ngx_cond_init(ngx_log_t *log)
+{
+ ngx_cond_t *cv;
+
+ cv = ngx_alloc(sizeof(ngx_cond_t), log);
+ if (cv == NULL) {
+ return NULL;
+ }
+
+ cv->signo = NGX_CV_SIGNAL;
+ cv->tid = -1;
+ cv->log = log;
+ cv->kq = -1;
+
+ return cv;
+}
+
+
+void
+ngx_cond_destroy(ngx_cond_t *cv)
+{
+ if (close(cv->kq) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cv->log, ngx_errno,
+ "kqueue close() failed");
+ }
+
+ ngx_free(cv);
+}
+
+
+ngx_int_t
+ngx_cond_wait(ngx_cond_t *cv, ngx_mutex_t *m)
+{
+ int n;
+ ngx_err_t err;
+ struct kevent kev;
+ struct timespec ts;
+
+ if (cv->kq == -1) {
+
+ /*
+ * We have to add the EVFILT_SIGNAL filter in the rfork()ed thread.
+ * Otherwise the thread would not get a signal event.
+ *
+ * However, we have not to open the kqueue in the thread,
+ * it is simply handy do it together.
+ */
+
+ cv->kq = kqueue();
+ if (cv->kq == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cv->log, ngx_errno, "kqueue() failed");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, cv->log, 0,
+ "cv kq:%d signo:%d", cv->kq, cv->signo);
+
+ kev.ident = cv->signo;
+ kev.filter = EVFILT_SIGNAL;
+ kev.flags = EV_ADD;
+ kev.fflags = 0;
+ kev.data = 0;
+ kev.udata = NULL;
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+
+ if (kevent(cv->kq, &kev, 1, NULL, 0, &ts) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cv->log, ngx_errno, "kevent() failed");
+ return NGX_ERROR;
+ }
+
+ cv->tid = ngx_thread_self();
+ }
+
+ ngx_mutex_unlock(m);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_CORE, cv->log, 0,
+ "cv %p wait, kq:%d, signo:%d", cv, cv->kq, cv->signo);
+
+ for ( ;; ) {
+ n = kevent(cv->kq, NULL, 0, &kev, 1, NULL);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, cv->log, 0,
+ "cv %p kevent: %d", cv, n);
+
+ if (n == -1) {
+ err = ngx_errno;
+ ngx_log_error((err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT,
+ cv->log, ngx_errno,
+ "kevent() failed while waiting condition variable %p",
+ cv);
+
+ if (err == NGX_EINTR) {
+ break;
+ }
+
+ return NGX_ERROR;
+ }
+
+ if (n == 0) {
+ ngx_log_error(NGX_LOG_ALERT, cv->log, 0,
+ "kevent() returned no events "
+ "while waiting condition variable %p",
+ cv);
+ continue;
+ }
+
+ if (kev.filter != EVFILT_SIGNAL) {
+ ngx_log_error(NGX_LOG_ALERT, cv->log, 0,
+ "kevent() returned unexpected events: %d "
+ "while waiting condition variable %p",
+ kev.filter, cv);
+ continue;
+ }
+
+ if (kev.ident != (uintptr_t) cv->signo) {
+ ngx_log_error(NGX_LOG_ALERT, cv->log, 0,
+ "kevent() returned unexpected signal: %d ",
+ "while waiting condition variable %p",
+ kev.ident, cv);
+ continue;
+ }
+
+ break;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, cv->log, 0, "cv %p is waked up", cv);
+
+ ngx_mutex_lock(m);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_cond_signal(ngx_cond_t *cv)
+{
+ ngx_err_t err;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_CORE, cv->log, 0,
+ "cv %p to signal %P %d",
+ cv, cv->tid, cv->signo);
+
+ if (cv->tid == -1) {
+ return NGX_OK;
+ }
+
+ if (kill(cv->tid, cv->signo) == -1) {
+
+ err = ngx_errno;
+
+ ngx_log_error(NGX_LOG_ALERT, cv->log, err,
+ "kill() failed while signaling condition variable %p", cv);
+
+ if (err == NGX_ESRCH) {
+ cv->tid = -1;
+ }
+
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, cv->log, 0, "cv %p is signaled", cv);
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_freebsd_rfork_thread.h b/usr.sbin/nginx/src/os/unix/ngx_freebsd_rfork_thread.h
new file mode 100644
index 00000000000..9826822b245
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_freebsd_rfork_thread.h
@@ -0,0 +1,121 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_FREEBSD_RFORK_THREAD_H_INCLUDED_
+#define _NGX_FREEBSD_RFORK_THREAD_H_INCLUDED_
+
+
+#include <sys/ipc.h>
+#include <sys/sem.h>
+#include <sched.h>
+
+typedef pid_t ngx_tid_t;
+
+#define ngx_log_pid ngx_thread_self()
+#define ngx_log_tid 0
+
+#define NGX_TID_T_FMT "%P"
+
+
+#define NGX_MUTEX_LIGHT 1
+
+#define NGX_MUTEX_LOCK_BUSY 0x80000000
+
+typedef volatile struct {
+ ngx_atomic_t lock;
+ ngx_log_t *log;
+ int semid;
+} ngx_mutex_t;
+
+
+#define NGX_CV_SIGNAL 64
+
+typedef struct {
+ int signo;
+ int kq;
+ ngx_tid_t tid;
+ ngx_log_t *log;
+} ngx_cond_t;
+
+
+#define ngx_thread_sigmask(how, set, oset) \
+ (sigprocmask(how, set, oset) == -1) ? ngx_errno : 0
+
+#define ngx_thread_sigmask_n "sigprocmask()"
+
+#define ngx_thread_join(t, p)
+
+#define ngx_setthrtitle(n) setproctitle(n)
+
+
+extern char *ngx_freebsd_kern_usrstack;
+extern size_t ngx_thread_stack_size;
+
+
+static ngx_inline ngx_int_t
+ngx_gettid()
+{
+ char *sp;
+
+ if (ngx_thread_stack_size == 0) {
+ return 0;
+ }
+
+#if ( __i386__ )
+
+ __asm__ volatile ("mov %%esp, %0" : "=q" (sp));
+
+#elif ( __amd64__ )
+
+ __asm__ volatile ("mov %%rsp, %0" : "=q" (sp));
+
+#else
+
+#error "rfork()ed threads are not supported on this platform"
+
+#endif
+
+ return (ngx_freebsd_kern_usrstack - sp) / ngx_thread_stack_size;
+}
+
+
+ngx_tid_t ngx_thread_self();
+
+
+typedef ngx_uint_t ngx_tls_key_t;
+
+#define NGX_THREAD_KEYS_MAX 16
+
+extern void **ngx_tls;
+
+ngx_err_t ngx_thread_key_create(ngx_tls_key_t *key);
+#define ngx_thread_key_create_n "the tls key creation"
+
+ngx_err_t ngx_thread_set_tls(ngx_tls_key_t key, void *value);
+#define ngx_thread_set_tls_n "the tls key setting"
+
+
+static void *
+ngx_thread_get_tls(ngx_tls_key_t key)
+{
+ if (key >= NGX_THREAD_KEYS_MAX) {
+ return NULL;
+ }
+
+ return ngx_tls[key * NGX_THREAD_KEYS_MAX + ngx_gettid()];
+}
+
+
+#define ngx_mutex_trylock(m) ngx_mutex_dolock(m, 1)
+#define ngx_mutex_lock(m) (void) ngx_mutex_dolock(m, 0)
+ngx_int_t ngx_mutex_dolock(ngx_mutex_t *m, ngx_int_t try);
+void ngx_mutex_unlock(ngx_mutex_t *m);
+
+
+typedef int (*ngx_rfork_thread_func_pt)(void *arg);
+
+
+#endif /* _NGX_FREEBSD_RFORK_THREAD_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_freebsd_sendfile_chain.c b/usr.sbin/nginx/src/os/unix/ngx_freebsd_sendfile_chain.c
new file mode 100644
index 00000000000..70cdb74950f
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_freebsd_sendfile_chain.c
@@ -0,0 +1,430 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+/*
+ * Although FreeBSD sendfile() allows to pass a header and a trailer,
+ * it can not send a header with a part of the file in one packet until
+ * FreeBSD 5.3. Besides, over the fast ethernet connection sendfile()
+ * may send the partially filled packets, i.e. the 8 file pages may be sent
+ * as the 11 full 1460-bytes packets, then one incomplete 324-bytes packet,
+ * and then again the 11 full 1460-bytes packets.
+ *
+ * Threfore we use the TCP_NOPUSH option (similar to Linux's TCP_CORK)
+ * to postpone the sending - it not only sends a header and the first part of
+ * the file in one packet, but also sends the file pages in the full packets.
+ *
+ * But until FreeBSD 4.5 turning TCP_NOPUSH off does not flush a pending
+ * data that less than MSS, so that data may be sent with 5 second delay.
+ * So we do not use TCP_NOPUSH on FreeBSD prior to 4.5, although it can be used
+ * for non-keepalive HTTP connections.
+ */
+
+
+#if (IOV_MAX > 64)
+#define NGX_HEADERS 64
+#define NGX_TRAILERS 64
+#else
+#define NGX_HEADERS IOV_MAX
+#define NGX_TRAILERS IOV_MAX
+#endif
+
+
+ngx_chain_t *
+ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+ int rc, flags;
+ u_char *prev;
+ off_t size, send, prev_send, aligned, sent, fprev;
+ size_t header_size, file_size;
+ ngx_uint_t eintr, eagain, complete;
+ ngx_err_t err;
+ ngx_buf_t *file;
+ ngx_array_t header, trailer;
+ ngx_event_t *wev;
+ ngx_chain_t *cl;
+ struct sf_hdtr hdtr;
+ struct iovec *iov, headers[NGX_HEADERS], trailers[NGX_TRAILERS];
+
+ wev = c->write;
+
+ if (!wev->ready) {
+ return in;
+ }
+
+#if (NGX_HAVE_KQUEUE)
+
+ if ((ngx_event_flags & NGX_USE_KQUEUE_EVENT) && wev->pending_eof) {
+ (void) ngx_connection_error(c, wev->kq_errno,
+ "kevent() reported about an closed connection");
+ wev->error = 1;
+ return NGX_CHAIN_ERROR;
+ }
+
+#endif
+
+ /* the maximum limit size is the maximum size_t value - the page size */
+
+ if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
+ limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
+ }
+
+ send = 0;
+ eagain = 0;
+ flags = 0;
+
+ header.elts = headers;
+ header.size = sizeof(struct iovec);
+ header.nalloc = NGX_HEADERS;
+ header.pool = c->pool;
+
+ trailer.elts = trailers;
+ trailer.size = sizeof(struct iovec);
+ trailer.nalloc = NGX_TRAILERS;
+ trailer.pool = c->pool;
+
+ for ( ;; ) {
+ file = NULL;
+ file_size = 0;
+ header_size = 0;
+ eintr = 0;
+ complete = 0;
+ prev_send = send;
+
+ header.nelts = 0;
+ trailer.nelts = 0;
+
+ /* create the header iovec and coalesce the neighbouring bufs */
+
+ prev = NULL;
+ iov = NULL;
+
+ for (cl = in;
+ cl && header.nelts < IOV_MAX && send < limit;
+ cl = cl->next)
+ {
+ if (ngx_buf_special(cl->buf)) {
+ continue;
+ }
+
+ if (!ngx_buf_in_memory_only(cl->buf)) {
+ break;
+ }
+
+ size = cl->buf->last - cl->buf->pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+ }
+
+ if (prev == cl->buf->pos) {
+ iov->iov_len += (size_t) size;
+
+ } else {
+ iov = ngx_array_push(&header);
+ if (iov == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ iov->iov_base = (void *) cl->buf->pos;
+ iov->iov_len = (size_t) size;
+ }
+
+ prev = cl->buf->pos + (size_t) size;
+ header_size += (size_t) size;
+ send += size;
+ }
+
+
+ if (cl && cl->buf->in_file && send < limit) {
+ file = cl->buf;
+
+ /* coalesce the neighbouring file bufs */
+
+ do {
+ size = cl->buf->file_last - cl->buf->file_pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+
+ aligned = (cl->buf->file_pos + size + ngx_pagesize - 1)
+ & ~((off_t) ngx_pagesize - 1);
+
+ if (aligned <= cl->buf->file_last) {
+ size = aligned - cl->buf->file_pos;
+ }
+ }
+
+ file_size += (size_t) size;
+ send += size;
+ fprev = cl->buf->file_pos + size;
+ cl = cl->next;
+
+ } while (cl
+ && cl->buf->in_file
+ && send < limit
+ && file->file->fd == cl->buf->file->fd
+ && fprev == cl->buf->file_pos);
+ }
+
+
+ if (file) {
+
+ /* create the tailer iovec and coalesce the neighbouring bufs */
+
+ prev = NULL;
+ iov = NULL;
+
+ while (cl && header.nelts < IOV_MAX && send < limit) {
+
+ if (ngx_buf_special(cl->buf)) {
+ cl = cl->next;
+ continue;
+ }
+
+ if (!ngx_buf_in_memory_only(cl->buf)) {
+ break;
+ }
+
+ size = cl->buf->last - cl->buf->pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+ }
+
+ if (prev == cl->buf->pos) {
+ iov->iov_len += (size_t) size;
+
+ } else {
+ iov = ngx_array_push(&trailer);
+ if (iov == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ iov->iov_base = (void *) cl->buf->pos;
+ iov->iov_len = (size_t) size;
+ }
+
+ prev = cl->buf->pos + (size_t) size;
+ send += size;
+ cl = cl->next;
+ }
+ }
+
+ if (file) {
+
+ if (ngx_freebsd_use_tcp_nopush
+ && c->tcp_nopush == NGX_TCP_NOPUSH_UNSET)
+ {
+ if (ngx_tcp_nopush(c->fd) == NGX_ERROR) {
+ err = ngx_errno;
+
+ /*
+ * there is a tiny chance to be interrupted, however,
+ * we continue a processing without the TCP_NOPUSH
+ */
+
+ if (err != NGX_EINTR) {
+ wev->error = 1;
+ (void) ngx_connection_error(c, err,
+ ngx_tcp_nopush_n " failed");
+ return NGX_CHAIN_ERROR;
+ }
+
+ } else {
+ c->tcp_nopush = NGX_TCP_NOPUSH_SET;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "tcp_nopush");
+ }
+ }
+
+ hdtr.headers = (struct iovec *) header.elts;
+ hdtr.hdr_cnt = header.nelts;
+ hdtr.trailers = (struct iovec *) trailer.elts;
+ hdtr.trl_cnt = trailer.nelts;
+
+ /*
+ * the "nbytes bug" of the old sendfile() syscall:
+ * http://www.freebsd.org/cgi/query-pr.cgi?pr=33771
+ */
+
+ if (!ngx_freebsd_sendfile_nbytes_bug) {
+ header_size = 0;
+ }
+
+ sent = 0;
+
+#if (NGX_HAVE_AIO_SENDFILE)
+ flags = c->aio_sendfile ? SF_NODISKIO : 0;
+#endif
+
+ rc = sendfile(file->file->fd, c->fd, file->file_pos,
+ file_size + header_size, &hdtr, &sent, flags);
+
+ if (rc == -1) {
+ err = ngx_errno;
+
+ switch (err) {
+ case NGX_EAGAIN:
+ eagain = 1;
+ break;
+
+ case NGX_EINTR:
+ eintr = 1;
+ break;
+
+#if (NGX_HAVE_AIO_SENDFILE)
+ case NGX_EBUSY:
+ c->busy_sendfile = file;
+ break;
+#endif
+
+ default:
+ wev->error = 1;
+ (void) ngx_connection_error(c, err, "sendfile() failed");
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "sendfile() sent only %O bytes", sent);
+
+ /*
+ * sendfile() in FreeBSD 3.x-4.x may return value >= 0
+ * on success, although only 0 is documented
+ */
+
+ } else if (rc >= 0 && sent == 0) {
+
+ /*
+ * if rc is OK and sent equal to zero, then someone
+ * has truncated the file, so the offset became beyond
+ * the end of the file
+ */
+
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "sendfile() reported that \"%s\" was truncated at %O",
+ file->file->name.data, file->file_pos);
+
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "sendfile: %d, @%O %O:%uz",
+ rc, file->file_pos, sent, file_size + header_size);
+
+ } else {
+ rc = writev(c->fd, header.elts, header.nelts);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "writev: %d of %uz", rc, header_size);
+
+ if (rc == -1) {
+ err = ngx_errno;
+
+ switch (err) {
+ case NGX_EAGAIN:
+ break;
+
+ case NGX_EINTR:
+ eintr = 1;
+ break;
+
+ default:
+ wev->error = 1;
+ ngx_connection_error(c, err, "writev() failed");
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "writev() not ready");
+ }
+
+ sent = rc > 0 ? rc : 0;
+ }
+
+ if (send - prev_send == sent) {
+ complete = 1;
+ }
+
+ c->sent += sent;
+
+ for (cl = in; cl; cl = cl->next) {
+
+ if (ngx_buf_special(cl->buf)) {
+ continue;
+ }
+
+ if (sent == 0) {
+ break;
+ }
+
+ size = ngx_buf_size(cl->buf);
+
+ if (sent >= size) {
+ sent -= size;
+
+ if (ngx_buf_in_memory(cl->buf)) {
+ cl->buf->pos = cl->buf->last;
+ }
+
+ if (cl->buf->in_file) {
+ cl->buf->file_pos = cl->buf->file_last;
+ }
+
+ continue;
+ }
+
+ if (ngx_buf_in_memory(cl->buf)) {
+ cl->buf->pos += (size_t) sent;
+ }
+
+ if (cl->buf->in_file) {
+ cl->buf->file_pos += sent;
+ }
+
+ break;
+ }
+
+#if (NGX_HAVE_AIO_SENDFILE)
+ if (c->busy_sendfile) {
+ return cl;
+ }
+#endif
+
+ if (eagain) {
+
+ /*
+ * sendfile() may return EAGAIN, even if it has sent a whole file
+ * part, it indicates that the successive sendfile() call would
+ * return EAGAIN right away and would not send anything.
+ * We use it as a hint.
+ */
+
+ wev->ready = 0;
+ return cl;
+ }
+
+ if (eintr) {
+ continue;
+ }
+
+ if (!complete) {
+ wev->ready = 0;
+ return cl;
+ }
+
+ if (send >= limit || cl == NULL) {
+ return cl;
+ }
+
+ in = cl;
+ }
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_gcc_atomic_amd64.h b/usr.sbin/nginx/src/os/unix/ngx_gcc_atomic_amd64.h
new file mode 100644
index 00000000000..1008a60177a
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_gcc_atomic_amd64.h
@@ -0,0 +1,81 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#if (NGX_SMP)
+#define NGX_SMP_LOCK "lock;"
+#else
+#define NGX_SMP_LOCK
+#endif
+
+
+/*
+ * "cmpxchgq r, [m]":
+ *
+ * if (rax == [m]) {
+ * zf = 1;
+ * [m] = r;
+ * } else {
+ * zf = 0;
+ * rax = [m];
+ * }
+ *
+ *
+ * The "r" is any register, %rax (%r0) - %r16.
+ * The "=a" and "a" are the %rax register.
+ * Although we can return result in any register, we use "a" because it is
+ * used in cmpxchgq anyway. The result is actually in %al but not in $rax,
+ * however as the code is inlined gcc can test %al as well as %rax.
+ *
+ * The "cc" means that flags were changed.
+ */
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set)
+{
+ u_char res;
+
+ __asm__ volatile (
+
+ NGX_SMP_LOCK
+ " cmpxchgq %3, %1; "
+ " sete %0; "
+
+ : "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "cc", "memory");
+
+ return res;
+}
+
+
+/*
+ * "xaddq r, [m]":
+ *
+ * temp = [m];
+ * [m] += r;
+ * r = temp;
+ *
+ *
+ * The "+r" is any register, %rax (%r0) - %r16.
+ * The "cc" means that flags were changed.
+ */
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+ __asm__ volatile (
+
+ NGX_SMP_LOCK
+ " xaddq %0, %1; "
+
+ : "+r" (add) : "m" (*value) : "cc", "memory");
+
+ return add;
+}
+
+
+#define ngx_memory_barrier() __asm__ volatile ("" ::: "memory")
+
+#define ngx_cpu_pause() __asm__ ("pause")
diff --git a/usr.sbin/nginx/src/os/unix/ngx_gcc_atomic_ppc.h b/usr.sbin/nginx/src/os/unix/ngx_gcc_atomic_ppc.h
new file mode 100644
index 00000000000..e82e75574b3
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_gcc_atomic_ppc.h
@@ -0,0 +1,154 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+/*
+ * The ppc assembler treats ";" as comment, so we have to use "\n".
+ * The minus in "bne-" is a hint for the branch prediction unit that
+ * this branch is unlikely to be taken.
+ * The "1b" means the nearest backward label "1" and the "1f" means
+ * the nearest forward label "1".
+ *
+ * The "b" means that the base registers can be used only, i.e.
+ * any register except r0. The r0 register always has a zero value and
+ * could not be used in "addi r0, r0, 1".
+ * The "=&b" means that no input registers can be used.
+ *
+ * "sync" read and write barriers
+ * "isync" read barrier, is faster than "sync"
+ * "eieio" write barrier, is faster than "sync"
+ * "lwsync" write barrier, is faster than "eieio" on ppc64
+ */
+
+#if (NGX_PTR_SIZE == 8)
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set)
+{
+ ngx_atomic_uint_t res, temp;
+
+ __asm__ volatile (
+
+ " li %0, 0 \n" /* preset "0" to "res" */
+ " lwsync \n" /* write barrier */
+ "1: \n"
+ " ldarx %1, 0, %2 \n" /* load from [lock] into "temp" */
+ /* and store reservation */
+ " cmpd %1, %3 \n" /* compare "temp" and "old" */
+ " bne- 2f \n" /* not equal */
+ " stdcx. %4, 0, %2 \n" /* store "set" into [lock] if reservation */
+ /* is not cleared */
+ " bne- 1b \n" /* the reservation was cleared */
+ " isync \n" /* read barrier */
+ " li %0, 1 \n" /* set "1" to "res" */
+ "2: \n"
+
+ : "=&b" (res), "=&b" (temp)
+ : "b" (lock), "b" (old), "b" (set)
+ : "cc", "memory");
+
+ return res;
+}
+
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+ ngx_atomic_uint_t res, temp;
+
+ __asm__ volatile (
+
+ " lwsync \n" /* write barrier */
+ "1: ldarx %0, 0, %2 \n" /* load from [value] into "res" */
+ /* and store reservation */
+ " add %1, %0, %3 \n" /* "res" + "add" store in "temp" */
+ " stdcx. %1, 0, %2 \n" /* store "temp" into [value] if reservation */
+ /* is not cleared */
+ " bne- 1b \n" /* try again if reservation was cleared */
+ " isync \n" /* read barrier */
+
+ : "=&b" (res), "=&b" (temp)
+ : "b" (value), "b" (add)
+ : "cc", "memory");
+
+ return res;
+}
+
+
+#if (NGX_SMP)
+#define ngx_memory_barrier() \
+ __asm__ volatile ("isync \n lwsync \n" ::: "memory")
+#else
+#define ngx_memory_barrier() __asm__ volatile ("" ::: "memory")
+#endif
+
+#else
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set)
+{
+ ngx_atomic_uint_t res, temp;
+
+ __asm__ volatile (
+
+ " li %0, 0 \n" /* preset "0" to "res" */
+ " eieio \n" /* write barrier */
+ "1: \n"
+ " lwarx %1, 0, %2 \n" /* load from [lock] into "temp" */
+ /* and store reservation */
+ " cmpw %1, %3 \n" /* compare "temp" and "old" */
+ " bne- 2f \n" /* not equal */
+ " stwcx. %4, 0, %2 \n" /* store "set" into [lock] if reservation */
+ /* is not cleared */
+ " bne- 1b \n" /* the reservation was cleared */
+ " isync \n" /* read barrier */
+ " li %0, 1 \n" /* set "1" to "res" */
+ "2: \n"
+
+ : "=&b" (res), "=&b" (temp)
+ : "b" (lock), "b" (old), "b" (set)
+ : "cc", "memory");
+
+ return res;
+}
+
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+ ngx_atomic_uint_t res, temp;
+
+ __asm__ volatile (
+
+ " eieio \n" /* write barrier */
+ "1: lwarx %0, 0, %2 \n" /* load from [value] into "res" */
+ /* and store reservation */
+ " add %1, %0, %3 \n" /* "res" + "add" store in "temp" */
+ " stwcx. %1, 0, %2 \n" /* store "temp" into [value] if reservation */
+ /* is not cleared */
+ " bne- 1b \n" /* try again if reservation was cleared */
+ " isync \n" /* read barrier */
+
+ : "=&b" (res), "=&b" (temp)
+ : "b" (value), "b" (add)
+ : "cc", "memory");
+
+ return res;
+}
+
+
+#if (NGX_SMP)
+#define ngx_memory_barrier() \
+ __asm__ volatile ("isync \n eieio \n" ::: "memory")
+#else
+#define ngx_memory_barrier() __asm__ volatile ("" ::: "memory")
+#endif
+
+#endif
+
+
+#define ngx_cpu_pause()
diff --git a/usr.sbin/nginx/src/os/unix/ngx_gcc_atomic_sparc64.h b/usr.sbin/nginx/src/os/unix/ngx_gcc_atomic_sparc64.h
new file mode 100644
index 00000000000..e5a6254ec74
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_gcc_atomic_sparc64.h
@@ -0,0 +1,81 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+/*
+ * "casa [r1] 0x80, r2, r0" and
+ * "casxa [r1] 0x80, r2, r0" do the following:
+ *
+ * if ([r1] == r2) {
+ * swap(r0, [r1]);
+ * } else {
+ * r0 = [r1];
+ * }
+ *
+ * so "r0 == r2" means that the operation was successfull.
+ *
+ *
+ * The "r" means the general register.
+ * The "+r" means the general register used for both input and output.
+ */
+
+
+#if (NGX_PTR_SIZE == 4)
+#define NGX_CASA "casa"
+#else
+#define NGX_CASA "casxa"
+#endif
+
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set)
+{
+ __asm__ volatile (
+
+ NGX_CASA " [%1] 0x80, %2, %0"
+
+ : "+r" (set) : "r" (lock), "r" (old) : "memory");
+
+ return (set == old);
+}
+
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+ ngx_atomic_uint_t old, res;
+
+ old = *value;
+
+ for ( ;; ) {
+
+ res = old + add;
+
+ __asm__ volatile (
+
+ NGX_CASA " [%1] 0x80, %2, %0"
+
+ : "+r" (res) : "r" (value), "r" (old) : "memory");
+
+ if (res == old) {
+ return res;
+ }
+
+ old = res;
+ }
+}
+
+
+#if (NGX_SMP)
+#define ngx_memory_barrier() \
+ __asm__ volatile ( \
+ "membar #LoadLoad | #LoadStore | #StoreStore | #StoreLoad" \
+ ::: "memory")
+#else
+#define ngx_memory_barrier() __asm__ volatile ("" ::: "memory")
+#endif
+
+#define ngx_cpu_pause()
diff --git a/usr.sbin/nginx/src/os/unix/ngx_gcc_atomic_x86.h b/usr.sbin/nginx/src/os/unix/ngx_gcc_atomic_x86.h
new file mode 100644
index 00000000000..1951d00e246
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_gcc_atomic_x86.h
@@ -0,0 +1,126 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#if (NGX_SMP)
+#define NGX_SMP_LOCK "lock;"
+#else
+#define NGX_SMP_LOCK
+#endif
+
+
+/*
+ * "cmpxchgl r, [m]":
+ *
+ * if (eax == [m]) {
+ * zf = 1;
+ * [m] = r;
+ * } else {
+ * zf = 0;
+ * eax = [m];
+ * }
+ *
+ *
+ * The "r" means the general register.
+ * The "=a" and "a" are the %eax register.
+ * Although we can return result in any register, we use "a" because it is
+ * used in cmpxchgl anyway. The result is actually in %al but not in %eax,
+ * however, as the code is inlined gcc can test %al as well as %eax,
+ * and icc adds "movzbl %al, %eax" by itself.
+ *
+ * The "cc" means that flags were changed.
+ */
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set)
+{
+ u_char res;
+
+ __asm__ volatile (
+
+ NGX_SMP_LOCK
+ " cmpxchgl %3, %1; "
+ " sete %0; "
+
+ : "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "cc", "memory");
+
+ return res;
+}
+
+
+/*
+ * "xaddl r, [m]":
+ *
+ * temp = [m];
+ * [m] += r;
+ * r = temp;
+ *
+ *
+ * The "+r" means the general register.
+ * The "cc" means that flags were changed.
+ */
+
+
+#if !(( __GNUC__ == 2 && __GNUC_MINOR__ <= 7 ) || ( __INTEL_COMPILER >= 800 ))
+
+/*
+ * icc 8.1 and 9.0 compile broken code with -march=pentium4 option:
+ * ngx_atomic_fetch_add() always return the input "add" value,
+ * so we use the gcc 2.7 version.
+ *
+ * icc 8.1 and 9.0 with -march=pentiumpro option or icc 7.1 compile
+ * correct code.
+ */
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+ __asm__ volatile (
+
+ NGX_SMP_LOCK
+ " xaddl %0, %1; "
+
+ : "+r" (add) : "m" (*value) : "cc", "memory");
+
+ return add;
+}
+
+
+#else
+
+/*
+ * gcc 2.7 does not support "+r", so we have to use the fixed
+ * %eax ("=a" and "a") and this adds two superfluous instructions in the end
+ * of code, something like this: "mov %eax, %edx / mov %edx, %eax".
+ */
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+ ngx_atomic_uint_t old;
+
+ __asm__ volatile (
+
+ NGX_SMP_LOCK
+ " xaddl %2, %1; "
+
+ : "=a" (old) : "m" (*value), "a" (add) : "cc", "memory");
+
+ return old;
+}
+
+#endif
+
+
+/*
+ * on x86 the write operations go in a program order, so we need only
+ * to disable the gcc reorder optimizations
+ */
+
+#define ngx_memory_barrier() __asm__ volatile ("" ::: "memory")
+
+/* old "as" does not support "pause" opcode */
+#define ngx_cpu_pause() __asm__ (".byte 0xf3, 0x90")
diff --git a/usr.sbin/nginx/src/os/unix/ngx_linux.h b/usr.sbin/nginx/src/os/unix/ngx_linux.h
new file mode 100644
index 00000000000..e871ba9b0e6
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_linux.h
@@ -0,0 +1,17 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_LINUX_H_INCLUDED_
+#define _NGX_LINUX_H_INCLUDED_
+
+
+ngx_chain_t *ngx_linux_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in,
+ off_t limit);
+
+extern int ngx_linux_rtsig_max;
+
+
+#endif /* _NGX_LINUX_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_linux_aio_read.c b/usr.sbin/nginx/src/os/unix/ngx_linux_aio_read.c
new file mode 100644
index 00000000000..72875ca0101
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_linux_aio_read.c
@@ -0,0 +1,134 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+extern int ngx_eventfd;
+extern aio_context_t ngx_aio_ctx;
+
+
+static void ngx_file_aio_event_handler(ngx_event_t *ev);
+
+
+static long
+io_submit(aio_context_t ctx, long n, struct iocb **paiocb)
+{
+ return syscall(SYS_io_submit, ctx, n, paiocb);
+}
+
+
+ssize_t
+ngx_file_aio_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset,
+ ngx_pool_t *pool)
+{
+ long n;
+ struct iocb *piocb[1];
+ ngx_event_t *ev;
+ ngx_event_aio_t *aio;
+
+ if (!ngx_file_aio) {
+ return ngx_read_file(file, buf, size, offset);
+ }
+
+ aio = file->aio;
+
+ if (aio == NULL) {
+ aio = ngx_pcalloc(pool, sizeof(ngx_event_aio_t));
+ if (aio == NULL) {
+ return NGX_ERROR;
+ }
+
+ aio->file = file;
+ aio->fd = file->fd;
+ aio->event.data = aio;
+ aio->event.ready = 1;
+ aio->event.log = file->log;
+ file->aio = aio;
+ }
+
+ ev = &aio->event;
+
+ if (!ev->ready) {
+ ngx_log_error(NGX_LOG_ALERT, file->log, 0,
+ "second aio post for \"%V\"", &file->name);
+ return NGX_AGAIN;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0,
+ "aio complete:%d @%O:%z %V",
+ ev->complete, offset, size, &file->name);
+
+ if (ev->complete) {
+ ev->active = 0;
+ ev->complete = 0;
+
+ if (aio->res >= 0) {
+ ngx_set_errno(0);
+ return aio->res;
+ }
+
+ ngx_set_errno(-aio->res);
+ return NGX_ERROR;
+ }
+
+ ngx_memzero(&aio->aiocb, sizeof(struct iocb));
+
+ aio->aiocb.aio_data = (uint64_t) (uintptr_t) ev;
+ aio->aiocb.aio_lio_opcode = IOCB_CMD_PREAD;
+ aio->aiocb.aio_fildes = file->fd;
+ aio->aiocb.aio_buf = (uint64_t) (uintptr_t) buf;
+ aio->aiocb.aio_nbytes = size;
+ aio->aiocb.aio_offset = offset;
+ aio->aiocb.aio_flags = IOCB_FLAG_RESFD;
+ aio->aiocb.aio_resfd = ngx_eventfd;
+
+ ev->handler = ngx_file_aio_event_handler;
+
+ piocb[0] = &aio->aiocb;
+
+ n = io_submit(ngx_aio_ctx, 1, piocb);
+
+ if (n == 1) {
+ ev->active = 1;
+ ev->ready = 0;
+ ev->complete = 0;
+
+ return NGX_AGAIN;
+ }
+
+ n = -n;
+
+ if (n == NGX_EAGAIN) {
+ return ngx_read_file(file, buf, size, offset);
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, file->log, n,
+ "io_submit(\"%V\") failed", &file->name);
+
+ if (n == NGX_ENOSYS) {
+ ngx_file_aio = 0;
+ return ngx_read_file(file, buf, size, offset);
+ }
+
+ return NGX_ERROR;
+}
+
+
+static void
+ngx_file_aio_event_handler(ngx_event_t *ev)
+{
+ ngx_event_aio_t *aio;
+
+ aio = ev->data;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, ev->log, 0,
+ "aio event handler fd:%d %V", aio->fd, &aio->file->name);
+
+ aio->handler(ev);
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_linux_config.h b/usr.sbin/nginx/src/os/unix/ngx_linux_config.h
new file mode 100644
index 00000000000..046095d1d62
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_linux_config.h
@@ -0,0 +1,121 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_LINUX_CONFIG_H_INCLUDED_
+#define _NGX_LINUX_CONFIG_H_INCLUDED_
+
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE /* pread(), pwrite(), gethostname() */
+#endif
+
+#define _FILE_OFFSET_BITS 64
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stddef.h> /* offsetof() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+#include <glob.h>
+#include <sys/vfs.h> /* statfs() */
+
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sched.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h> /* TCP_NODELAY, TCP_CORK */
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/un.h>
+
+#include <time.h> /* tzset() */
+#include <malloc.h> /* memalign() */
+#include <limits.h> /* IOV_MAX */
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+#include <crypt.h>
+#include <sys/utsname.h> /* uname() */
+
+
+#include <ngx_auto_config.h>
+
+
+#if (NGX_HAVE_POSIX_SEM)
+#include <semaphore.h>
+#endif
+
+
+#if (NGX_HAVE_SYS_PRCTL_H)
+#include <sys/prctl.h>
+#endif
+
+
+#if (NGX_HAVE_SENDFILE64)
+#include <sys/sendfile.h>
+#else
+extern ssize_t sendfile(int s, int fd, int32_t *offset, size_t size);
+#define NGX_SENDFILE_LIMIT 0x80000000
+#endif
+
+
+#if (NGX_HAVE_POLL || NGX_HAVE_RTSIG)
+#include <poll.h>
+#endif
+
+
+#if (NGX_HAVE_EPOLL)
+#include <sys/epoll.h>
+#endif
+
+
+#if (NGX_HAVE_FILE_AIO)
+#include <sys/syscall.h>
+#include <linux/aio_abi.h>
+typedef struct iocb ngx_aiocb_t;
+#endif
+
+
+#define NGX_LISTEN_BACKLOG 511
+
+
+#if defined TCP_DEFER_ACCEPT && !defined NGX_HAVE_DEFERRED_ACCEPT
+#define NGX_HAVE_DEFERRED_ACCEPT 1
+#endif
+
+
+#ifndef NGX_HAVE_SO_SNDLOWAT
+/* setsockopt(SO_SNDLOWAT) returns ENOPROTOOPT */
+#define NGX_HAVE_SO_SNDLOWAT 0
+#endif
+
+
+#ifndef NGX_HAVE_INHERITED_NONBLOCK
+#define NGX_HAVE_INHERITED_NONBLOCK 0
+#endif
+
+
+#define NGX_HAVE_OS_SPECIFIC_INIT 1
+
+
+extern char **environ;
+
+
+#endif /* _NGX_LINUX_CONFIG_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_linux_init.c b/usr.sbin/nginx/src/os/unix/ngx_linux_init.c
new file mode 100644
index 00000000000..277be95371b
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_linux_init.c
@@ -0,0 +1,90 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+u_char ngx_linux_kern_ostype[50];
+u_char ngx_linux_kern_osrelease[50];
+
+int ngx_linux_rtsig_max;
+
+
+static ngx_os_io_t ngx_linux_io = {
+ ngx_unix_recv,
+ ngx_readv_chain,
+ ngx_udp_unix_recv,
+ ngx_unix_send,
+#if (NGX_HAVE_SENDFILE)
+ ngx_linux_sendfile_chain,
+ NGX_IO_SENDFILE
+#else
+ ngx_writev_chain,
+ 0
+#endif
+};
+
+
+ngx_int_t
+ngx_os_specific_init(ngx_log_t *log)
+{
+ struct utsname u;
+
+ if (uname(&u) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "uname() failed");
+ return NGX_ERROR;
+ }
+
+ (void) ngx_cpystrn(ngx_linux_kern_ostype, (u_char *) u.sysname,
+ sizeof(ngx_linux_kern_ostype));
+
+ (void) ngx_cpystrn(ngx_linux_kern_osrelease, (u_char *) u.release,
+ sizeof(ngx_linux_kern_osrelease));
+
+#if (NGX_HAVE_RTSIG)
+ {
+ int name[2];
+ size_t len;
+ ngx_err_t err;
+
+ name[0] = CTL_KERN;
+ name[1] = KERN_RTSIGMAX;
+ len = sizeof(ngx_linux_rtsig_max);
+
+ if (sysctl(name, 2, &ngx_linux_rtsig_max, &len, NULL, 0) == -1) {
+ err = ngx_errno;
+
+ if (err != NGX_ENOTDIR && err != NGX_ENOSYS) {
+ ngx_log_error(NGX_LOG_ALERT, log, err,
+ "sysctl(KERN_RTSIGMAX) failed");
+
+ return NGX_ERROR;
+ }
+
+ ngx_linux_rtsig_max = 0;
+ }
+
+ }
+#endif
+
+ ngx_os_io = ngx_linux_io;
+
+ return NGX_OK;
+}
+
+
+void
+ngx_os_specific_status(ngx_log_t *log)
+{
+ ngx_log_error(NGX_LOG_NOTICE, log, 0, "OS: %s %s",
+ ngx_linux_kern_ostype, ngx_linux_kern_osrelease);
+
+#if (NGX_HAVE_RTSIG)
+ ngx_log_error(NGX_LOG_NOTICE, log, 0, "sysctl(KERN_RTSIGMAX): %d",
+ ngx_linux_rtsig_max);
+#endif
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_linux_sendfile_chain.c b/usr.sbin/nginx/src/os/unix/ngx_linux_sendfile_chain.c
new file mode 100644
index 00000000000..a2225d9c6d5
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_linux_sendfile_chain.c
@@ -0,0 +1,377 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+/*
+ * On Linux up to 2.4.21 sendfile() (syscall #187) works with 32-bit
+ * offsets only, and the including <sys/sendfile.h> breaks the compiling,
+ * if off_t is 64 bit wide. So we use own sendfile() definition, where offset
+ * parameter is int32_t, and use sendfile() for the file parts below 2G only,
+ * see src/os/unix/ngx_linux_config.h
+ *
+ * Linux 2.4.21 has the new sendfile64() syscall #239.
+ *
+ * On Linux up to 2.6.16 sendfile() does not allow to pass the count parameter
+ * more than 2G-1 bytes even on 64-bit platforms: it returns EINVAL,
+ * so we limit it to 2G-1 bytes.
+ */
+
+#define NGX_SENDFILE_LIMIT 2147483647L
+
+
+#if (IOV_MAX > 64)
+#define NGX_HEADERS 64
+#else
+#define NGX_HEADERS IOV_MAX
+#endif
+
+
+ngx_chain_t *
+ngx_linux_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+ int rc, tcp_nodelay;
+ off_t size, send, prev_send, aligned, sent, fprev;
+ u_char *prev;
+ size_t file_size;
+ ngx_err_t err;
+ ngx_buf_t *file;
+ ngx_uint_t eintr, complete;
+ ngx_array_t header;
+ ngx_event_t *wev;
+ ngx_chain_t *cl;
+ struct iovec *iov, headers[NGX_HEADERS];
+#if (NGX_HAVE_SENDFILE64)
+ off_t offset;
+#else
+ int32_t offset;
+#endif
+
+ wev = c->write;
+
+ if (!wev->ready) {
+ return in;
+ }
+
+
+ /* the maximum limit size is 2G-1 - the page size */
+
+ if (limit == 0 || limit > (off_t) (NGX_SENDFILE_LIMIT - ngx_pagesize)) {
+ limit = NGX_SENDFILE_LIMIT - ngx_pagesize;
+ }
+
+
+ send = 0;
+
+ header.elts = headers;
+ header.size = sizeof(struct iovec);
+ header.nalloc = NGX_HEADERS;
+ header.pool = c->pool;
+
+ for ( ;; ) {
+ file = NULL;
+ file_size = 0;
+ eintr = 0;
+ complete = 0;
+ prev_send = send;
+
+ header.nelts = 0;
+
+ prev = NULL;
+ iov = NULL;
+
+ /* create the iovec and coalesce the neighbouring bufs */
+
+ for (cl = in;
+ cl && header.nelts < IOV_MAX && send < limit;
+ cl = cl->next)
+ {
+ if (ngx_buf_special(cl->buf)) {
+ continue;
+ }
+
+#if 1
+ if (!ngx_buf_in_memory(cl->buf) && !cl->buf->in_file) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "zero size buf in sendfile "
+ "t:%d r:%d f:%d %p %p-%p %p %O-%O",
+ cl->buf->temporary,
+ cl->buf->recycled,
+ cl->buf->in_file,
+ cl->buf->start,
+ cl->buf->pos,
+ cl->buf->last,
+ cl->buf->file,
+ cl->buf->file_pos,
+ cl->buf->file_last);
+
+ ngx_debug_point();
+
+ return NGX_CHAIN_ERROR;
+ }
+#endif
+
+ if (!ngx_buf_in_memory_only(cl->buf)) {
+ break;
+ }
+
+ size = cl->buf->last - cl->buf->pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+ }
+
+ if (prev == cl->buf->pos) {
+ iov->iov_len += (size_t) size;
+
+ } else {
+ iov = ngx_array_push(&header);
+ if (iov == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ iov->iov_base = (void *) cl->buf->pos;
+ iov->iov_len = (size_t) size;
+ }
+
+ prev = cl->buf->pos + (size_t) size;
+ send += size;
+ }
+
+ /* set TCP_CORK if there is a header before a file */
+
+ if (c->tcp_nopush == NGX_TCP_NOPUSH_UNSET
+ && header.nelts != 0
+ && cl
+ && cl->buf->in_file)
+ {
+ /* the TCP_CORK and TCP_NODELAY are mutually exclusive */
+
+ if (c->tcp_nodelay == NGX_TCP_NODELAY_SET) {
+
+ tcp_nodelay = 0;
+
+ if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,
+ (const void *) &tcp_nodelay, sizeof(int)) == -1)
+ {
+ err = ngx_errno;
+
+ /*
+ * there is a tiny chance to be interrupted, however,
+ * we continue a processing with the TCP_NODELAY
+ * and without the TCP_CORK
+ */
+
+ if (err != NGX_EINTR) {
+ wev->error = 1;
+ ngx_connection_error(c, err,
+ "setsockopt(TCP_NODELAY) failed");
+ return NGX_CHAIN_ERROR;
+ }
+
+ } else {
+ c->tcp_nodelay = NGX_TCP_NODELAY_UNSET;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "no tcp_nodelay");
+ }
+ }
+
+ if (c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) {
+
+ if (ngx_tcp_nopush(c->fd) == NGX_ERROR) {
+ err = ngx_errno;
+
+ /*
+ * there is a tiny chance to be interrupted, however,
+ * we continue a processing without the TCP_CORK
+ */
+
+ if (err != NGX_EINTR) {
+ wev->error = 1;
+ ngx_connection_error(c, err,
+ ngx_tcp_nopush_n " failed");
+ return NGX_CHAIN_ERROR;
+ }
+
+ } else {
+ c->tcp_nopush = NGX_TCP_NOPUSH_SET;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "tcp_nopush");
+ }
+ }
+ }
+
+ /* get the file buf */
+
+ if (header.nelts == 0 && cl && cl->buf->in_file && send < limit) {
+ file = cl->buf;
+
+ /* coalesce the neighbouring file bufs */
+
+ do {
+ size = cl->buf->file_last - cl->buf->file_pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+
+ aligned = (cl->buf->file_pos + size + ngx_pagesize - 1)
+ & ~((off_t) ngx_pagesize - 1);
+
+ if (aligned <= cl->buf->file_last) {
+ size = aligned - cl->buf->file_pos;
+ }
+ }
+
+ file_size += (size_t) size;
+ send += size;
+ fprev = cl->buf->file_pos + size;
+ cl = cl->next;
+
+ } while (cl
+ && cl->buf->in_file
+ && send < limit
+ && file->file->fd == cl->buf->file->fd
+ && fprev == cl->buf->file_pos);
+ }
+
+ if (file) {
+#if 1
+ if (file_size == 0) {
+ ngx_debug_point();
+ return NGX_CHAIN_ERROR;
+ }
+#endif
+#if (NGX_HAVE_SENDFILE64)
+ offset = file->file_pos;
+#else
+ offset = (int32_t) file->file_pos;
+#endif
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "sendfile: @%O %uz", file->file_pos, file_size);
+
+ rc = sendfile(c->fd, file->file->fd, &offset, file_size);
+
+ if (rc == -1) {
+ err = ngx_errno;
+
+ switch (err) {
+ case NGX_EAGAIN:
+ break;
+
+ case NGX_EINTR:
+ eintr = 1;
+ break;
+
+ default:
+ wev->error = 1;
+ ngx_connection_error(c, err, "sendfile() failed");
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "sendfile() is not ready");
+ }
+
+ sent = rc > 0 ? rc : 0;
+
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "sendfile: %d, @%O %O:%uz",
+ rc, file->file_pos, sent, file_size);
+
+ } else {
+ rc = writev(c->fd, header.elts, header.nelts);
+
+ if (rc == -1) {
+ err = ngx_errno;
+
+ switch (err) {
+ case NGX_EAGAIN:
+ break;
+
+ case NGX_EINTR:
+ eintr = 1;
+ break;
+
+ default:
+ wev->error = 1;
+ ngx_connection_error(c, err, "writev() failed");
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "writev() not ready");
+ }
+
+ sent = rc > 0 ? rc : 0;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "writev: %O", sent);
+ }
+
+ if (send - prev_send == sent) {
+ complete = 1;
+ }
+
+ c->sent += sent;
+
+ for (cl = in; cl; cl = cl->next) {
+
+ if (ngx_buf_special(cl->buf)) {
+ continue;
+ }
+
+ if (sent == 0) {
+ break;
+ }
+
+ size = ngx_buf_size(cl->buf);
+
+ if (sent >= size) {
+ sent -= size;
+
+ if (ngx_buf_in_memory(cl->buf)) {
+ cl->buf->pos = cl->buf->last;
+ }
+
+ if (cl->buf->in_file) {
+ cl->buf->file_pos = cl->buf->file_last;
+ }
+
+ continue;
+ }
+
+ if (ngx_buf_in_memory(cl->buf)) {
+ cl->buf->pos += (size_t) sent;
+ }
+
+ if (cl->buf->in_file) {
+ cl->buf->file_pos += sent;
+ }
+
+ break;
+ }
+
+ if (eintr) {
+ continue;
+ }
+
+ if (!complete) {
+ wev->ready = 0;
+ return cl;
+ }
+
+ if (send >= limit || cl == NULL) {
+ return cl;
+ }
+
+ in = cl;
+ }
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_os.h b/usr.sbin/nginx/src/os/unix/ngx_os.h
new file mode 100644
index 00000000000..f1d8e68248c
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_os.h
@@ -0,0 +1,83 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_OS_H_INCLUDED_
+#define _NGX_OS_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_IO_SENDFILE 1
+
+
+typedef ssize_t (*ngx_recv_pt)(ngx_connection_t *c, u_char *buf, size_t size);
+typedef ssize_t (*ngx_recv_chain_pt)(ngx_connection_t *c, ngx_chain_t *in);
+typedef ssize_t (*ngx_send_pt)(ngx_connection_t *c, u_char *buf, size_t size);
+typedef ngx_chain_t *(*ngx_send_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
+ off_t limit);
+
+typedef struct {
+ ngx_recv_pt recv;
+ ngx_recv_chain_pt recv_chain;
+ ngx_recv_pt udp_recv;
+ ngx_send_pt send;
+ ngx_send_chain_pt send_chain;
+ ngx_uint_t flags;
+} ngx_os_io_t;
+
+
+void ngx_debug_init(void);
+ngx_int_t ngx_os_init(ngx_log_t *log);
+void ngx_os_status(ngx_log_t *log);
+ngx_int_t ngx_os_specific_init(ngx_log_t *log);
+void ngx_os_specific_status(ngx_log_t *log);
+ngx_int_t ngx_daemon(ngx_log_t *log);
+ngx_int_t ngx_os_signal_process(ngx_cycle_t *cycle, char *sig, ngx_int_t pid);
+
+
+ssize_t ngx_unix_recv(ngx_connection_t *c, u_char *buf, size_t size);
+ssize_t ngx_readv_chain(ngx_connection_t *c, ngx_chain_t *entry);
+ssize_t ngx_udp_unix_recv(ngx_connection_t *c, u_char *buf, size_t size);
+ssize_t ngx_unix_send(ngx_connection_t *c, u_char *buf, size_t size);
+ngx_chain_t *ngx_writev_chain(ngx_connection_t *c, ngx_chain_t *in,
+ off_t limit);
+
+#if (NGX_HAVE_AIO)
+ssize_t ngx_aio_read(ngx_connection_t *c, u_char *buf, size_t size);
+ssize_t ngx_aio_read_chain(ngx_connection_t *c, ngx_chain_t *cl);
+ssize_t ngx_aio_write(ngx_connection_t *c, u_char *buf, size_t size);
+ngx_chain_t *ngx_aio_write_chain(ngx_connection_t *c, ngx_chain_t *in,
+ off_t limit);
+#endif
+
+
+extern ngx_os_io_t ngx_os_io;
+extern ngx_int_t ngx_ncpu;
+extern ngx_int_t ngx_max_sockets;
+extern ngx_uint_t ngx_inherited_nonblocking;
+extern ngx_uint_t ngx_tcp_nodelay_and_tcp_nopush;
+
+
+#if (NGX_FREEBSD)
+#include <ngx_freebsd.h>
+
+
+#elif (NGX_LINUX)
+#include <ngx_linux.h>
+
+
+#elif (NGX_SOLARIS)
+#include <ngx_solaris.h>
+
+
+#elif (NGX_DARWIN)
+#include <ngx_darwin.h>
+#endif
+
+
+#endif /* _NGX_OS_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_posix_config.h b/usr.sbin/nginx/src/os/unix/ngx_posix_config.h
new file mode 100644
index 00000000000..aec8a0a3550
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_posix_config.h
@@ -0,0 +1,152 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_POSIX_CONFIG_H_INCLUDED_
+#define _NGX_POSIX_CONFIG_H_INCLUDED_
+
+
+#if (NGX_HPUX)
+#define _XOPEN_SOURCE
+#define _XOPEN_SOURCE_EXTENDED 1
+#endif
+
+
+#if (NGX_TRU64)
+#define _REENTRANT
+#endif
+
+
+#ifdef __CYGWIN__
+#define timezonevar /* timezone is variable */
+#define NGX_BROKEN_SCM_RIGHTS 1
+#endif
+
+
+#include <sys/types.h>
+#include <sys/time.h>
+#if (NGX_HAVE_UNISTD_H)
+#include <unistd.h>
+#endif
+#if (NGX_HAVE_INTTYPES_H)
+#include <inttypes.h>
+#endif
+#include <stdarg.h>
+#include <stddef.h> /* offsetof() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+#include <glob.h>
+#if (NGX_HAVE_SYS_PARAM_H)
+#include <sys/param.h> /* statfs() */
+#endif
+#if (NGX_HAVE_SYS_MOUNT_H)
+#include <sys/mount.h> /* statfs() */
+#endif
+#if (NGX_HAVE_SYS_STATVFS_H)
+#include <sys/statvfs.h> /* statvfs() */
+#endif
+
+#if (NGX_HAVE_SYS_FILIO_H)
+#include <sys/filio.h> /* FIONBIO */
+#endif
+#include <sys/ioctl.h> /* FIONBIO */
+
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sched.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h> /* TCP_NODELAY */
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/un.h>
+
+#if (NGX_HAVE_LIMITS_H)
+#include <limits.h> /* IOV_MAX */
+#endif
+
+#ifdef __CYGWIN__
+#include <malloc.h> /* memalign() */
+#endif
+
+#if (NGX_HAVE_CRYPT_H)
+#include <crypt.h>
+#endif
+
+
+#ifndef IOV_MAX
+#define IOV_MAX 16
+#endif
+
+
+#include <ngx_auto_config.h>
+
+
+#if (NGX_HAVE_POSIX_SEM)
+#include <semaphore.h>
+#endif
+
+
+#if (NGX_HAVE_POLL)
+#include <poll.h>
+#endif
+
+
+#if (NGX_HAVE_KQUEUE)
+#include <sys/event.h>
+#endif
+
+
+#if (NGX_HAVE_DEVPOLL)
+#include <sys/ioctl.h>
+#include <sys/devpoll.h>
+#endif
+
+
+#if (NGX_HAVE_FILE_AIO)
+#include <aio.h>
+typedef struct aiocb ngx_aiocb_t;
+#endif
+
+
+#define NGX_LISTEN_BACKLOG 511
+
+
+#if (__FreeBSD__) && (__FreeBSD_version < 400017)
+
+#include <sys/param.h> /* ALIGN() */
+
+/*
+ * FreeBSD 3.x has no CMSG_SPACE() and CMSG_LEN() and has the broken CMSG_DATA()
+ */
+
+#undef CMSG_SPACE
+#define CMSG_SPACE(l) (ALIGN(sizeof(struct cmsghdr)) + ALIGN(l))
+
+#undef CMSG_LEN
+#define CMSG_LEN(l) (ALIGN(sizeof(struct cmsghdr)) + (l))
+
+#undef CMSG_DATA
+#define CMSG_DATA(cmsg) ((u_char *)(cmsg) + ALIGN(sizeof(struct cmsghdr)))
+
+#endif
+
+
+extern char **environ;
+
+
+#endif /* _NGX_POSIX_CONFIG_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_posix_init.c b/usr.sbin/nginx/src/os/unix/ngx_posix_init.c
new file mode 100644
index 00000000000..2357ab31f46
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_posix_init.c
@@ -0,0 +1,117 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <nginx.h>
+
+
+ngx_int_t ngx_ncpu;
+ngx_int_t ngx_max_sockets;
+ngx_uint_t ngx_inherited_nonblocking;
+ngx_uint_t ngx_tcp_nodelay_and_tcp_nopush;
+
+
+struct rlimit rlmt;
+
+
+ngx_os_io_t ngx_os_io = {
+ ngx_unix_recv,
+ ngx_readv_chain,
+ ngx_udp_unix_recv,
+ ngx_unix_send,
+ ngx_writev_chain,
+ 0
+};
+
+
+ngx_int_t
+ngx_os_init(ngx_log_t *log)
+{
+ ngx_uint_t n;
+
+#if (NGX_HAVE_OS_SPECIFIC_INIT)
+ if (ngx_os_specific_init(log) != NGX_OK) {
+ return NGX_ERROR;
+ }
+#endif
+
+ ngx_init_setproctitle(log);
+
+ ngx_pagesize = getpagesize();
+ ngx_cacheline_size = NGX_CPU_CACHE_LINE;
+
+ for (n = ngx_pagesize; n >>= 1; ngx_pagesize_shift++) { /* void */ }
+
+ if (ngx_ncpu == 0) {
+ ngx_ncpu = 1;
+ }
+
+ ngx_cpuinfo();
+
+ if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, errno,
+ "getrlimit(RLIMIT_NOFILE) failed)");
+ return NGX_ERROR;
+ }
+
+ ngx_max_sockets = (ngx_int_t) rlmt.rlim_cur;
+
+#if (NGX_HAVE_INHERITED_NONBLOCK || NGX_HAVE_ACCEPT4)
+ ngx_inherited_nonblocking = 1;
+#else
+ ngx_inherited_nonblocking = 0;
+#endif
+
+ srandom(ngx_time());
+
+ return NGX_OK;
+}
+
+
+void
+ngx_os_status(ngx_log_t *log)
+{
+ ngx_log_error(NGX_LOG_NOTICE, log, 0, NGINX_VER);
+
+#ifdef NGX_COMPILER
+ ngx_log_error(NGX_LOG_NOTICE, log, 0, "built by " NGX_COMPILER);
+#endif
+
+#if (NGX_HAVE_OS_SPECIFIC_INIT)
+ ngx_os_specific_status(log);
+#endif
+
+ ngx_log_error(NGX_LOG_NOTICE, log, 0,
+ "getrlimit(RLIMIT_NOFILE): %r:%r",
+ rlmt.rlim_cur, rlmt.rlim_max);
+}
+
+
+ngx_int_t
+ngx_posix_post_conf_init(ngx_log_t *log)
+{
+ ngx_fd_t pp[2];
+
+ if (pipe(pp) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "pipe() failed");
+ return NGX_ERROR;
+ }
+
+ if (dup2(pp[1], STDERR_FILENO) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, log, errno, "dup2(STDERR) failed");
+ return NGX_ERROR;
+ }
+
+ if (pp[1] > STDERR_FILENO) {
+ if (close(pp[1]) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, log, errno, "close() failed");
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_process.c b/usr.sbin/nginx/src/os/unix/ngx_process.c
new file mode 100644
index 00000000000..605558786f2
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_process.c
@@ -0,0 +1,584 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_channel.h>
+
+
+typedef struct {
+ int signo;
+ char *signame;
+ char *name;
+ void (*handler)(int signo);
+} ngx_signal_t;
+
+
+
+static void ngx_execute_proc(ngx_cycle_t *cycle, void *data);
+static void ngx_signal_handler(int signo);
+static void ngx_process_get_status(void);
+
+
+int ngx_argc;
+char **ngx_argv;
+char **ngx_os_argv;
+
+ngx_int_t ngx_process_slot;
+ngx_socket_t ngx_channel;
+ngx_int_t ngx_last_process;
+ngx_process_t ngx_processes[NGX_MAX_PROCESSES];
+
+
+ngx_signal_t signals[] = {
+ { ngx_signal_value(NGX_RECONFIGURE_SIGNAL),
+ "SIG" ngx_value(NGX_RECONFIGURE_SIGNAL),
+ "reload",
+ ngx_signal_handler },
+
+ { ngx_signal_value(NGX_REOPEN_SIGNAL),
+ "SIG" ngx_value(NGX_REOPEN_SIGNAL),
+ "reopen",
+ ngx_signal_handler },
+
+ { ngx_signal_value(NGX_NOACCEPT_SIGNAL),
+ "SIG" ngx_value(NGX_NOACCEPT_SIGNAL),
+ "",
+ ngx_signal_handler },
+
+ { ngx_signal_value(NGX_TERMINATE_SIGNAL),
+ "SIG" ngx_value(NGX_TERMINATE_SIGNAL),
+ "stop",
+ ngx_signal_handler },
+
+ { ngx_signal_value(NGX_SHUTDOWN_SIGNAL),
+ "SIG" ngx_value(NGX_SHUTDOWN_SIGNAL),
+ "quit",
+ ngx_signal_handler },
+
+ { ngx_signal_value(NGX_CHANGEBIN_SIGNAL),
+ "SIG" ngx_value(NGX_CHANGEBIN_SIGNAL),
+ "",
+ ngx_signal_handler },
+
+ { SIGALRM, "SIGALRM", "", ngx_signal_handler },
+
+ { SIGINT, "SIGINT", "", ngx_signal_handler },
+
+ { SIGIO, "SIGIO", "", ngx_signal_handler },
+
+ { SIGCHLD, "SIGCHLD", "", ngx_signal_handler },
+
+ { SIGSYS, "SIGSYS, SIG_IGN", "", SIG_IGN },
+
+ { SIGPIPE, "SIGPIPE, SIG_IGN", "", SIG_IGN },
+
+ { 0, NULL, "", NULL }
+};
+
+
+ngx_pid_t
+ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
+ char *name, ngx_int_t respawn)
+{
+ u_long on;
+ ngx_pid_t pid;
+ ngx_int_t s;
+
+ if (respawn >= 0) {
+ s = respawn;
+
+ } else {
+ for (s = 0; s < ngx_last_process; s++) {
+ if (ngx_processes[s].pid == -1) {
+ break;
+ }
+ }
+
+ if (s == NGX_MAX_PROCESSES) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "no more than %d processes can be spawned",
+ NGX_MAX_PROCESSES);
+ return NGX_INVALID_PID;
+ }
+ }
+
+
+ if (respawn != NGX_PROCESS_DETACHED) {
+
+ /* Solaris 9 still has no AF_LOCAL */
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "socketpair() failed while spawning \"%s\"", name);
+ return NGX_INVALID_PID;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+ "channel %d:%d",
+ ngx_processes[s].channel[0],
+ ngx_processes[s].channel[1]);
+
+ if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ ngx_nonblocking_n " failed while spawning \"%s\"",
+ name);
+ ngx_close_channel(ngx_processes[s].channel, cycle->log);
+ return NGX_INVALID_PID;
+ }
+
+ if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ ngx_nonblocking_n " failed while spawning \"%s\"",
+ name);
+ ngx_close_channel(ngx_processes[s].channel, cycle->log);
+ return NGX_INVALID_PID;
+ }
+
+ on = 1;
+ if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "ioctl(FIOASYNC) failed while spawning \"%s\"", name);
+ ngx_close_channel(ngx_processes[s].channel, cycle->log);
+ return NGX_INVALID_PID;
+ }
+
+ if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "fcntl(F_SETOWN) failed while spawning \"%s\"", name);
+ ngx_close_channel(ngx_processes[s].channel, cycle->log);
+ return NGX_INVALID_PID;
+ }
+
+ if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
+ name);
+ ngx_close_channel(ngx_processes[s].channel, cycle->log);
+ return NGX_INVALID_PID;
+ }
+
+ if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
+ name);
+ ngx_close_channel(ngx_processes[s].channel, cycle->log);
+ return NGX_INVALID_PID;
+ }
+
+ ngx_channel = ngx_processes[s].channel[1];
+
+ } else {
+ ngx_processes[s].channel[0] = -1;
+ ngx_processes[s].channel[1] = -1;
+ }
+
+ ngx_process_slot = s;
+
+
+ pid = fork();
+
+ switch (pid) {
+
+ case -1:
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "fork() failed while spawning \"%s\"", name);
+ ngx_close_channel(ngx_processes[s].channel, cycle->log);
+ return NGX_INVALID_PID;
+
+ case 0:
+ ngx_pid = ngx_getpid();
+ proc(cycle, data);
+ break;
+
+ default:
+ break;
+ }
+
+ ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start %s %P", name, pid);
+
+ ngx_processes[s].pid = pid;
+ ngx_processes[s].exited = 0;
+
+ if (respawn >= 0) {
+ return pid;
+ }
+
+ ngx_processes[s].proc = proc;
+ ngx_processes[s].data = data;
+ ngx_processes[s].name = name;
+ ngx_processes[s].exiting = 0;
+
+ switch (respawn) {
+
+ case NGX_PROCESS_NORESPAWN:
+ ngx_processes[s].respawn = 0;
+ ngx_processes[s].just_spawn = 0;
+ ngx_processes[s].detached = 0;
+ break;
+
+ case NGX_PROCESS_JUST_SPAWN:
+ ngx_processes[s].respawn = 0;
+ ngx_processes[s].just_spawn = 1;
+ ngx_processes[s].detached = 0;
+ break;
+
+ case NGX_PROCESS_RESPAWN:
+ ngx_processes[s].respawn = 1;
+ ngx_processes[s].just_spawn = 0;
+ ngx_processes[s].detached = 0;
+ break;
+
+ case NGX_PROCESS_JUST_RESPAWN:
+ ngx_processes[s].respawn = 1;
+ ngx_processes[s].just_spawn = 1;
+ ngx_processes[s].detached = 0;
+ break;
+
+ case NGX_PROCESS_DETACHED:
+ ngx_processes[s].respawn = 0;
+ ngx_processes[s].just_spawn = 0;
+ ngx_processes[s].detached = 1;
+ break;
+ }
+
+ if (s == ngx_last_process) {
+ ngx_last_process++;
+ }
+
+ return pid;
+}
+
+
+ngx_pid_t
+ngx_execute(ngx_cycle_t *cycle, ngx_exec_ctx_t *ctx)
+{
+ return ngx_spawn_process(cycle, ngx_execute_proc, ctx, ctx->name,
+ NGX_PROCESS_DETACHED);
+}
+
+
+static void
+ngx_execute_proc(ngx_cycle_t *cycle, void *data)
+{
+ ngx_exec_ctx_t *ctx = data;
+
+ if (execve(ctx->path, ctx->argv, ctx->envp) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "execve() failed while executing %s \"%s\"",
+ ctx->name, ctx->path);
+ }
+
+ exit(1);
+}
+
+
+ngx_int_t
+ngx_init_signals(ngx_log_t *log)
+{
+ ngx_signal_t *sig;
+ struct sigaction sa;
+
+ for (sig = signals; sig->signo != 0; sig++) {
+ ngx_memzero(&sa, sizeof(struct sigaction));
+ sa.sa_handler = sig->handler;
+ sigemptyset(&sa.sa_mask);
+ if (sigaction(sig->signo, &sa, NULL) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+ "sigaction(%s) failed", sig->signame);
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+void
+ngx_signal_handler(int signo)
+{
+ char *action;
+ ngx_int_t ignore;
+ ngx_err_t err;
+ ngx_signal_t *sig;
+
+ ignore = 0;
+
+ err = ngx_errno;
+
+ for (sig = signals; sig->signo != 0; sig++) {
+ if (sig->signo == signo) {
+ break;
+ }
+ }
+
+ ngx_time_sigsafe_update();
+
+ action = "";
+
+ switch (ngx_process) {
+
+ case NGX_PROCESS_MASTER:
+ case NGX_PROCESS_SINGLE:
+ switch (signo) {
+
+ case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
+ ngx_quit = 1;
+ action = ", shutting down";
+ break;
+
+ case ngx_signal_value(NGX_TERMINATE_SIGNAL):
+ case SIGINT:
+ ngx_terminate = 1;
+ action = ", exiting";
+ break;
+
+ case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
+ ngx_noaccept = 1;
+ action = ", stop accepting connections";
+ break;
+
+ case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
+ ngx_reconfigure = 1;
+ action = ", reconfiguring";
+ break;
+
+ case ngx_signal_value(NGX_REOPEN_SIGNAL):
+ ngx_reopen = 1;
+ action = ", reopening logs";
+ break;
+
+ case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
+ if (getppid() > 1 || ngx_new_binary > 0) {
+
+ /*
+ * Ignore the signal in the new binary if its parent is
+ * not the init process, i.e. the old binary's process
+ * is still running. Or ignore the signal in the old binary's
+ * process if the new binary's process is already running.
+ */
+
+ action = ", ignoring";
+ ignore = 1;
+ break;
+ }
+
+ ngx_change_binary = 1;
+ action = ", changing binary";
+ break;
+
+ case SIGALRM:
+ ngx_sigalrm = 1;
+ break;
+
+ case SIGIO:
+ ngx_sigio = 1;
+ break;
+
+ case SIGCHLD:
+ ngx_reap = 1;
+ break;
+ }
+
+ break;
+
+ case NGX_PROCESS_WORKER:
+ case NGX_PROCESS_HELPER:
+ switch (signo) {
+
+ case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
+ ngx_debug_quit = 1;
+ case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
+ ngx_quit = 1;
+ action = ", shutting down";
+ break;
+
+ case ngx_signal_value(NGX_TERMINATE_SIGNAL):
+ case SIGINT:
+ ngx_terminate = 1;
+ action = ", exiting";
+ break;
+
+ case ngx_signal_value(NGX_REOPEN_SIGNAL):
+ ngx_reopen = 1;
+ action = ", reopening logs";
+ break;
+
+ case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
+ case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
+ case SIGIO:
+ action = ", ignoring";
+ break;
+ }
+
+ break;
+ }
+
+ ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
+ "signal %d (%s) received%s", signo, sig->signame, action);
+
+ if (ignore) {
+ ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, 0,
+ "the changing binary signal is ignored: "
+ "you should shutdown or terminate "
+ "before either old or new binary's process");
+ }
+
+ if (signo == SIGCHLD) {
+ ngx_process_get_status();
+ }
+
+ ngx_set_errno(err);
+}
+
+
+static void
+ngx_process_get_status(void)
+{
+ int status;
+ char *process;
+ ngx_pid_t pid;
+ ngx_err_t err;
+ ngx_int_t i;
+ ngx_uint_t one;
+
+ one = 0;
+
+ for ( ;; ) {
+ pid = waitpid(-1, &status, WNOHANG);
+
+ if (pid == 0) {
+ return;
+ }
+
+ if (pid == -1) {
+ err = ngx_errno;
+
+ if (err == NGX_EINTR) {
+ continue;
+ }
+
+ if (err == NGX_ECHILD && one) {
+ return;
+ }
+
+#if (NGX_SOLARIS || NGX_FREEBSD)
+
+ /*
+ * Solaris always calls the signal handler for each exited process
+ * despite waitpid() may be already called for this process.
+ *
+ * When several processes exit at the same time FreeBSD may
+ * erroneously call the signal handler for exited process
+ * despite waitpid() may be already called for this process.
+ */
+
+ if (err == NGX_ECHILD) {
+ ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, err,
+ "waitpid() failed");
+ return;
+ }
+
+#endif
+
+ ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err,
+ "waitpid() failed");
+ return;
+ }
+
+
+ if (ngx_accept_mutex_ptr) {
+
+ /*
+ * unlock the accept mutex if the abnormally exited process
+ * held it
+ */
+
+ ngx_atomic_cmp_set(ngx_accept_mutex_ptr, pid, 0);
+ }
+
+
+ one = 1;
+ process = "unknown process";
+
+ for (i = 0; i < ngx_last_process; i++) {
+ if (ngx_processes[i].pid == pid) {
+ ngx_processes[i].status = status;
+ ngx_processes[i].exited = 1;
+ process = ngx_processes[i].name;
+ break;
+ }
+ }
+
+ if (WTERMSIG(status)) {
+#ifdef WCOREDUMP
+ ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+ "%s %P exited on signal %d%s",
+ process, pid, WTERMSIG(status),
+ WCOREDUMP(status) ? " (core dumped)" : "");
+#else
+ ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+ "%s %P exited on signal %d",
+ process, pid, WTERMSIG(status));
+#endif
+
+ } else {
+ ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
+ "%s %P exited with code %d",
+ process, pid, WEXITSTATUS(status));
+ }
+
+ if (WEXITSTATUS(status) == 2 && ngx_processes[i].respawn) {
+ ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+ "%s %P exited with fatal code %d "
+ "and can not be respawn",
+ process, pid, WEXITSTATUS(status));
+ ngx_processes[i].respawn = 0;
+ }
+ }
+}
+
+
+void
+ngx_debug_point(void)
+{
+ ngx_core_conf_t *ccf;
+
+ ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
+ ngx_core_module);
+
+ switch (ccf->debug_points) {
+
+ case NGX_DEBUG_POINTS_STOP:
+ raise(SIGSTOP);
+ break;
+
+ case NGX_DEBUG_POINTS_ABORT:
+ ngx_abort();
+ }
+}
+
+
+ngx_int_t
+ngx_os_signal_process(ngx_cycle_t *cycle, char *name, ngx_int_t pid)
+{
+ ngx_signal_t *sig;
+
+ for (sig = signals; sig->signo != 0; sig++) {
+ if (ngx_strcmp(name, sig->name) == 0) {
+ if (kill(pid, sig->signo) != -1) {
+ return 0;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "kill(%P, %d) failed", pid, sig->signo);
+ }
+ }
+
+ return 1;
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_process.h b/usr.sbin/nginx/src/os/unix/ngx_process.h
new file mode 100644
index 00000000000..aba0b516952
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_process.h
@@ -0,0 +1,86 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_PROCESS_H_INCLUDED_
+#define _NGX_PROCESS_H_INCLUDED_
+
+
+#include <ngx_setproctitle.h>
+
+
+typedef pid_t ngx_pid_t;
+
+#define NGX_INVALID_PID -1
+
+typedef void (*ngx_spawn_proc_pt) (ngx_cycle_t *cycle, void *data);
+
+typedef struct {
+ ngx_pid_t pid;
+ int status;
+ ngx_socket_t channel[2];
+
+ ngx_spawn_proc_pt proc;
+ void *data;
+ char *name;
+
+ unsigned respawn:1;
+ unsigned just_spawn:1;
+ unsigned detached:1;
+ unsigned exiting:1;
+ unsigned exited:1;
+} ngx_process_t;
+
+
+typedef struct {
+ char *path;
+ char *name;
+ char *const *argv;
+ char *const *envp;
+} ngx_exec_ctx_t;
+
+
+#define NGX_MAX_PROCESSES 1024
+
+#define NGX_PROCESS_NORESPAWN -1
+#define NGX_PROCESS_JUST_SPAWN -2
+#define NGX_PROCESS_RESPAWN -3
+#define NGX_PROCESS_JUST_RESPAWN -4
+#define NGX_PROCESS_DETACHED -5
+
+
+#define ngx_getpid getpid
+
+#ifndef ngx_log_pid
+#define ngx_log_pid ngx_pid
+#endif
+
+
+ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle,
+ ngx_spawn_proc_pt proc, void *data, char *name, ngx_int_t respawn);
+ngx_pid_t ngx_execute(ngx_cycle_t *cycle, ngx_exec_ctx_t *ctx);
+ngx_int_t ngx_init_signals(ngx_log_t *log);
+void ngx_debug_point(void);
+
+
+#if (NGX_HAVE_SCHED_YIELD)
+#define ngx_sched_yield() sched_yield()
+#else
+#define ngx_sched_yield() usleep(1)
+#endif
+
+
+extern int ngx_argc;
+extern char **ngx_argv;
+extern char **ngx_os_argv;
+
+extern ngx_pid_t ngx_pid;
+extern ngx_socket_t ngx_channel;
+extern ngx_int_t ngx_process_slot;
+extern ngx_int_t ngx_last_process;
+extern ngx_process_t ngx_processes[NGX_MAX_PROCESSES];
+
+
+#endif /* _NGX_PROCESS_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_process_cycle.c b/usr.sbin/nginx/src/os/unix/ngx_process_cycle.c
new file mode 100644
index 00000000000..3ff0f75c6a2
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_process_cycle.c
@@ -0,0 +1,1385 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_channel.h>
+
+
+static void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n,
+ ngx_int_t type);
+static void ngx_start_cache_manager_processes(ngx_cycle_t *cycle,
+ ngx_uint_t respawn);
+static void ngx_pass_open_channel(ngx_cycle_t *cycle, ngx_channel_t *ch);
+static void ngx_signal_worker_processes(ngx_cycle_t *cycle, int signo);
+static ngx_uint_t ngx_reap_children(ngx_cycle_t *cycle);
+static void ngx_master_process_exit(ngx_cycle_t *cycle);
+static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data);
+static void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_uint_t priority);
+static void ngx_worker_process_exit(ngx_cycle_t *cycle);
+static void ngx_channel_handler(ngx_event_t *ev);
+#if (NGX_THREADS)
+static void ngx_wakeup_worker_threads(ngx_cycle_t *cycle);
+static ngx_thread_value_t ngx_worker_thread_cycle(void *data);
+#endif
+static void ngx_cache_manager_process_cycle(ngx_cycle_t *cycle, void *data);
+static void ngx_cache_manager_process_handler(ngx_event_t *ev);
+static void ngx_cache_loader_process_handler(ngx_event_t *ev);
+
+
+ngx_uint_t ngx_process;
+ngx_pid_t ngx_pid;
+ngx_uint_t ngx_threaded;
+
+sig_atomic_t ngx_reap;
+sig_atomic_t ngx_sigio;
+sig_atomic_t ngx_sigalrm;
+sig_atomic_t ngx_terminate;
+sig_atomic_t ngx_quit;
+sig_atomic_t ngx_debug_quit;
+ngx_uint_t ngx_exiting;
+sig_atomic_t ngx_reconfigure;
+sig_atomic_t ngx_reopen;
+
+sig_atomic_t ngx_change_binary;
+ngx_pid_t ngx_new_binary;
+ngx_uint_t ngx_inherited;
+ngx_uint_t ngx_daemonized;
+
+sig_atomic_t ngx_noaccept;
+ngx_uint_t ngx_noaccepting;
+ngx_uint_t ngx_restart;
+
+
+#if (NGX_THREADS)
+volatile ngx_thread_t ngx_threads[NGX_MAX_THREADS];
+ngx_int_t ngx_threads_n;
+#endif
+
+
+u_long cpu_affinity;
+static u_char master_process[] = "master process";
+
+
+static ngx_cache_manager_ctx_t ngx_cache_manager_ctx = {
+ ngx_cache_manager_process_handler, "cache manager process", 0
+};
+
+static ngx_cache_manager_ctx_t ngx_cache_loader_ctx = {
+ ngx_cache_loader_process_handler, "cache loader process", 60000
+};
+
+
+static ngx_cycle_t ngx_exit_cycle;
+static ngx_log_t ngx_exit_log;
+static ngx_open_file_t ngx_exit_log_file;
+
+
+void
+ngx_master_process_cycle(ngx_cycle_t *cycle)
+{
+ char *title;
+ u_char *p;
+ size_t size;
+ ngx_int_t i;
+ ngx_uint_t n, sigio;
+ sigset_t set;
+ struct itimerval itv;
+ ngx_uint_t live;
+ ngx_msec_t delay;
+ ngx_listening_t *ls;
+ ngx_core_conf_t *ccf;
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGCHLD);
+ sigaddset(&set, SIGALRM);
+ sigaddset(&set, SIGIO);
+ sigaddset(&set, SIGINT);
+ sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
+ sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
+ sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
+ sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
+ sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
+ sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));
+
+ if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "sigprocmask() failed");
+ }
+
+ sigemptyset(&set);
+
+
+ size = sizeof(master_process);
+
+ for (i = 0; i < ngx_argc; i++) {
+ size += ngx_strlen(ngx_argv[i]) + 1;
+ }
+
+ title = ngx_pnalloc(cycle->pool, size);
+
+ p = ngx_cpymem(title, master_process, sizeof(master_process) - 1);
+ for (i = 0; i < ngx_argc; i++) {
+ *p++ = ' ';
+ p = ngx_cpystrn(p, (u_char *) ngx_argv[i], size);
+ }
+
+ ngx_setproctitle(title);
+
+
+ ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+ ngx_start_worker_processes(cycle, ccf->worker_processes,
+ NGX_PROCESS_RESPAWN);
+ ngx_start_cache_manager_processes(cycle, 0);
+
+ ngx_new_binary = 0;
+ delay = 0;
+ sigio = 0;
+ live = 1;
+
+ for ( ;; ) {
+ if (delay) {
+ if (ngx_sigalrm) {
+ sigio = 0;
+ delay *= 2;
+ ngx_sigalrm = 0;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "termination cycle: %d", delay);
+
+ itv.it_interval.tv_sec = 0;
+ itv.it_interval.tv_usec = 0;
+ itv.it_value.tv_sec = delay / 1000;
+ itv.it_value.tv_usec = (delay % 1000 ) * 1000;
+
+ if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "setitimer() failed");
+ }
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "sigsuspend");
+
+ sigsuspend(&set);
+
+ ngx_time_update();
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "wake up, sigio %i", sigio);
+
+ if (ngx_reap) {
+ ngx_reap = 0;
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");
+
+ live = ngx_reap_children(cycle);
+ }
+
+ if (!live && (ngx_terminate || ngx_quit)) {
+ ngx_master_process_exit(cycle);
+ }
+
+ if (ngx_terminate) {
+ if (delay == 0) {
+ delay = 50;
+ }
+
+ if (sigio) {
+ sigio--;
+ continue;
+ }
+
+ sigio = ccf->worker_processes + 2 /* cache processes */;
+
+ if (delay > 1000) {
+ ngx_signal_worker_processes(cycle, SIGKILL);
+ } else {
+ ngx_signal_worker_processes(cycle,
+ ngx_signal_value(NGX_TERMINATE_SIGNAL));
+ }
+
+ continue;
+ }
+
+ if (ngx_quit) {
+ ngx_signal_worker_processes(cycle,
+ ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
+
+ ls = cycle->listening.elts;
+ for (n = 0; n < cycle->listening.nelts; n++) {
+ if (ngx_close_socket(ls[n].fd) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
+ ngx_close_socket_n " %V failed",
+ &ls[n].addr_text);
+ }
+ }
+ cycle->listening.nelts = 0;
+
+ continue;
+ }
+
+ if (ngx_reconfigure) {
+ ngx_reconfigure = 0;
+
+ if (ngx_new_binary) {
+ ngx_start_worker_processes(cycle, ccf->worker_processes,
+ NGX_PROCESS_RESPAWN);
+ ngx_start_cache_manager_processes(cycle, 0);
+ ngx_noaccepting = 0;
+
+ continue;
+ }
+
+ ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");
+
+ cycle = ngx_init_cycle(cycle);
+ if (cycle == NULL) {
+ cycle = (ngx_cycle_t *) ngx_cycle;
+ continue;
+ }
+
+ ngx_cycle = cycle;
+ ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
+ ngx_core_module);
+ ngx_start_worker_processes(cycle, ccf->worker_processes,
+ NGX_PROCESS_JUST_RESPAWN);
+ ngx_start_cache_manager_processes(cycle, 1);
+ live = 1;
+ ngx_signal_worker_processes(cycle,
+ ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
+ }
+
+ if (ngx_restart) {
+ ngx_restart = 0;
+ ngx_start_worker_processes(cycle, ccf->worker_processes,
+ NGX_PROCESS_RESPAWN);
+ ngx_start_cache_manager_processes(cycle, 0);
+ live = 1;
+ }
+
+ if (ngx_reopen) {
+ ngx_reopen = 0;
+ ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
+ ngx_reopen_files(cycle, ccf->user);
+ ngx_signal_worker_processes(cycle,
+ ngx_signal_value(NGX_REOPEN_SIGNAL));
+ }
+
+ if (ngx_change_binary) {
+ ngx_change_binary = 0;
+ ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "changing binary");
+ ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
+ }
+
+ if (ngx_noaccept) {
+ ngx_noaccept = 0;
+ ngx_noaccepting = 1;
+ ngx_signal_worker_processes(cycle,
+ ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
+ }
+ }
+}
+
+
+void
+ngx_single_process_cycle(ngx_cycle_t *cycle)
+{
+ ngx_uint_t i;
+
+ if (ngx_set_environment(cycle, NULL) == NULL) {
+ /* fatal */
+ exit(2);
+ }
+
+ for (i = 0; ngx_modules[i]; i++) {
+ if (ngx_modules[i]->init_process) {
+ if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) {
+ /* fatal */
+ exit(2);
+ }
+ }
+ }
+
+ for ( ;; ) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
+
+ ngx_process_events_and_timers(cycle);
+
+ if (ngx_terminate || ngx_quit) {
+
+ for (i = 0; ngx_modules[i]; i++) {
+ if (ngx_modules[i]->exit_process) {
+ ngx_modules[i]->exit_process(cycle);
+ }
+ }
+
+ ngx_master_process_exit(cycle);
+ }
+
+ if (ngx_reconfigure) {
+ ngx_reconfigure = 0;
+ ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");
+
+ cycle = ngx_init_cycle(cycle);
+ if (cycle == NULL) {
+ cycle = (ngx_cycle_t *) ngx_cycle;
+ continue;
+ }
+
+ ngx_cycle = cycle;
+ }
+
+ if (ngx_reopen) {
+ ngx_reopen = 0;
+ ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
+ ngx_reopen_files(cycle, (ngx_uid_t) -1);
+ }
+ }
+}
+
+
+static void
+ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
+{
+ ngx_int_t i;
+ ngx_channel_t ch;
+
+ ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes");
+
+ ch.command = NGX_CMD_OPEN_CHANNEL;
+
+ for (i = 0; i < n; i++) {
+
+ cpu_affinity = ngx_get_cpu_affinity(i);
+
+ ngx_spawn_process(cycle, ngx_worker_process_cycle, NULL,
+ "worker process", type);
+
+ ch.pid = ngx_processes[ngx_process_slot].pid;
+ ch.slot = ngx_process_slot;
+ ch.fd = ngx_processes[ngx_process_slot].channel[0];
+
+ ngx_pass_open_channel(cycle, &ch);
+ }
+}
+
+
+static void
+ngx_start_cache_manager_processes(ngx_cycle_t *cycle, ngx_uint_t respawn)
+{
+ ngx_uint_t i, manager, loader;
+ ngx_path_t **path;
+ ngx_channel_t ch;
+
+ manager = 0;
+ loader = 0;
+
+ path = ngx_cycle->pathes.elts;
+ for (i = 0; i < ngx_cycle->pathes.nelts; i++) {
+
+ if (path[i]->manager) {
+ manager = 1;
+ }
+
+ if (path[i]->loader) {
+ loader = 1;
+ }
+ }
+
+ if (manager == 0) {
+ return;
+ }
+
+ ngx_spawn_process(cycle, ngx_cache_manager_process_cycle,
+ &ngx_cache_manager_ctx, "cache manager process",
+ respawn ? NGX_PROCESS_JUST_RESPAWN : NGX_PROCESS_RESPAWN);
+
+ ch.command = NGX_CMD_OPEN_CHANNEL;
+ ch.pid = ngx_processes[ngx_process_slot].pid;
+ ch.slot = ngx_process_slot;
+ ch.fd = ngx_processes[ngx_process_slot].channel[0];
+
+ ngx_pass_open_channel(cycle, &ch);
+
+ if (loader == 0) {
+ return;
+ }
+
+ ngx_spawn_process(cycle, ngx_cache_manager_process_cycle,
+ &ngx_cache_loader_ctx, "cache loader process",
+ respawn ? NGX_PROCESS_JUST_SPAWN : NGX_PROCESS_NORESPAWN);
+
+ ch.command = NGX_CMD_OPEN_CHANNEL;
+ ch.pid = ngx_processes[ngx_process_slot].pid;
+ ch.slot = ngx_process_slot;
+ ch.fd = ngx_processes[ngx_process_slot].channel[0];
+
+ ngx_pass_open_channel(cycle, &ch);
+}
+
+
+static void
+ngx_pass_open_channel(ngx_cycle_t *cycle, ngx_channel_t *ch)
+{
+ ngx_int_t i;
+
+ for (i = 0; i < ngx_last_process; i++) {
+
+ if (i == ngx_process_slot
+ || ngx_processes[i].pid == -1
+ || ngx_processes[i].channel[0] == -1)
+ {
+ continue;
+ }
+
+ ngx_log_debug6(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+ "pass channel s:%d pid:%P fd:%d to s:%i pid:%P fd:%d",
+ ch->slot, ch->pid, ch->fd,
+ i, ngx_processes[i].pid,
+ ngx_processes[i].channel[0]);
+
+ /* TODO: NGX_AGAIN */
+
+ ngx_write_channel(ngx_processes[i].channel[0],
+ ch, sizeof(ngx_channel_t), cycle->log);
+ }
+}
+
+
+static void
+ngx_signal_worker_processes(ngx_cycle_t *cycle, int signo)
+{
+ ngx_int_t i;
+ ngx_err_t err;
+ ngx_channel_t ch;
+
+#if (NGX_BROKEN_SCM_RIGHTS)
+
+ ch.command = 0;
+
+#else
+
+ switch (signo) {
+
+ case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
+ ch.command = NGX_CMD_QUIT;
+ break;
+
+ case ngx_signal_value(NGX_TERMINATE_SIGNAL):
+ ch.command = NGX_CMD_TERMINATE;
+ break;
+
+ case ngx_signal_value(NGX_REOPEN_SIGNAL):
+ ch.command = NGX_CMD_REOPEN;
+ break;
+
+ default:
+ ch.command = 0;
+ }
+
+#endif
+
+ ch.fd = -1;
+
+
+ for (i = 0; i < ngx_last_process; i++) {
+
+ ngx_log_debug7(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "child: %d %P e:%d t:%d d:%d r:%d j:%d",
+ i,
+ ngx_processes[i].pid,
+ ngx_processes[i].exiting,
+ ngx_processes[i].exited,
+ ngx_processes[i].detached,
+ ngx_processes[i].respawn,
+ ngx_processes[i].just_spawn);
+
+ if (ngx_processes[i].detached || ngx_processes[i].pid == -1) {
+ continue;
+ }
+
+ if (ngx_processes[i].just_spawn) {
+ ngx_processes[i].just_spawn = 0;
+ continue;
+ }
+
+ if (ngx_processes[i].exiting
+ && signo == ngx_signal_value(NGX_SHUTDOWN_SIGNAL))
+ {
+ continue;
+ }
+
+ if (ch.command) {
+ if (ngx_write_channel(ngx_processes[i].channel[0],
+ &ch, sizeof(ngx_channel_t), cycle->log)
+ == NGX_OK)
+ {
+ if (signo != ngx_signal_value(NGX_REOPEN_SIGNAL)) {
+ ngx_processes[i].exiting = 1;
+ }
+
+ continue;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+ "kill (%P, %d)" , ngx_processes[i].pid, signo);
+
+ if (kill(ngx_processes[i].pid, signo) == -1) {
+ err = ngx_errno;
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+ "kill(%P, %d) failed", ngx_processes[i].pid, signo);
+
+ if (err == NGX_ESRCH) {
+ ngx_processes[i].exited = 1;
+ ngx_processes[i].exiting = 0;
+ ngx_reap = 1;
+ }
+
+ continue;
+ }
+
+ if (signo != ngx_signal_value(NGX_REOPEN_SIGNAL)) {
+ ngx_processes[i].exiting = 1;
+ }
+ }
+}
+
+
+static ngx_uint_t
+ngx_reap_children(ngx_cycle_t *cycle)
+{
+ ngx_int_t i, n;
+ ngx_uint_t live;
+ ngx_channel_t ch;
+ ngx_core_conf_t *ccf;
+
+ ch.command = NGX_CMD_CLOSE_CHANNEL;
+ ch.fd = -1;
+
+ live = 0;
+ for (i = 0; i < ngx_last_process; i++) {
+
+ ngx_log_debug7(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+ "child: %d %P e:%d t:%d d:%d r:%d j:%d",
+ i,
+ ngx_processes[i].pid,
+ ngx_processes[i].exiting,
+ ngx_processes[i].exited,
+ ngx_processes[i].detached,
+ ngx_processes[i].respawn,
+ ngx_processes[i].just_spawn);
+
+ if (ngx_processes[i].pid == -1) {
+ continue;
+ }
+
+ if (ngx_processes[i].exited) {
+
+ if (!ngx_processes[i].detached) {
+ ngx_close_channel(ngx_processes[i].channel, cycle->log);
+
+ ngx_processes[i].channel[0] = -1;
+ ngx_processes[i].channel[1] = -1;
+
+ ch.pid = ngx_processes[i].pid;
+ ch.slot = i;
+
+ for (n = 0; n < ngx_last_process; n++) {
+ if (ngx_processes[n].exited
+ || ngx_processes[n].pid == -1
+ || ngx_processes[n].channel[0] == -1)
+ {
+ continue;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+ "pass close channel s:%i pid:%P to:%P",
+ ch.slot, ch.pid, ngx_processes[n].pid);
+
+ /* TODO: NGX_AGAIN */
+
+ ngx_write_channel(ngx_processes[n].channel[0],
+ &ch, sizeof(ngx_channel_t), cycle->log);
+ }
+ }
+
+ if (ngx_processes[i].respawn
+ && !ngx_processes[i].exiting
+ && !ngx_terminate
+ && !ngx_quit)
+ {
+ if (ngx_spawn_process(cycle, ngx_processes[i].proc,
+ ngx_processes[i].data,
+ ngx_processes[i].name, i)
+ == NGX_INVALID_PID)
+ {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "can not respawn %s", ngx_processes[i].name);
+ continue;
+ }
+
+
+ ch.command = NGX_CMD_OPEN_CHANNEL;
+ ch.pid = ngx_processes[ngx_process_slot].pid;
+ ch.slot = ngx_process_slot;
+ ch.fd = ngx_processes[ngx_process_slot].channel[0];
+
+ ngx_pass_open_channel(cycle, &ch);
+
+ live = 1;
+
+ continue;
+ }
+
+ if (ngx_processes[i].pid == ngx_new_binary) {
+
+ ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
+ ngx_core_module);
+
+ if (ngx_rename_file((char *) ccf->oldpid.data,
+ (char *) ccf->pid.data)
+ != NGX_OK)
+ {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ ngx_rename_file_n " %s back to %s failed "
+ "after the new binary process \"%s\" exited",
+ ccf->oldpid.data, ccf->pid.data, ngx_argv[0]);
+ }
+
+ ngx_new_binary = 0;
+ if (ngx_noaccepting) {
+ ngx_restart = 1;
+ ngx_noaccepting = 0;
+ }
+ }
+
+ if (i == ngx_last_process - 1) {
+ ngx_last_process--;
+
+ } else {
+ ngx_processes[i].pid = -1;
+ }
+
+ } else if (ngx_processes[i].exiting || !ngx_processes[i].detached) {
+ live = 1;
+ }
+ }
+
+ return live;
+}
+
+
+static void
+ngx_master_process_exit(ngx_cycle_t *cycle)
+{
+ ngx_uint_t i;
+
+ ngx_delete_pidfile(cycle);
+
+ ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exit");
+
+ for (i = 0; ngx_modules[i]; i++) {
+ if (ngx_modules[i]->exit_master) {
+ ngx_modules[i]->exit_master(cycle);
+ }
+ }
+
+ ngx_close_listening_sockets(cycle);
+
+ /*
+ * Copy ngx_cycle->log related data to the special static exit cycle,
+ * log, and log file structures enough to allow a signal handler to log.
+ * The handler may be called when standard ngx_cycle->log allocated from
+ * ngx_cycle->pool is already destroyed.
+ */
+
+ ngx_exit_log_file.fd = ngx_cycle->log->file->fd;
+
+ ngx_exit_log = *ngx_cycle->log;
+ ngx_exit_log.file = &ngx_exit_log_file;
+
+ ngx_exit_cycle.log = &ngx_exit_log;
+ ngx_cycle = &ngx_exit_cycle;
+
+ ngx_destroy_pool(cycle->pool);
+
+ exit(0);
+}
+
+
+static void
+ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
+{
+ ngx_uint_t i;
+ ngx_connection_t *c;
+
+ ngx_process = NGX_PROCESS_WORKER;
+
+ ngx_worker_process_init(cycle, 1);
+
+ ngx_setproctitle("worker process");
+
+#if (NGX_THREADS)
+ {
+ ngx_int_t n;
+ ngx_err_t err;
+ ngx_core_conf_t *ccf;
+
+ ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+ if (ngx_threads_n) {
+ if (ngx_init_threads(ngx_threads_n, ccf->thread_stack_size, cycle)
+ == NGX_ERROR)
+ {
+ /* fatal */
+ exit(2);
+ }
+
+ err = ngx_thread_key_create(&ngx_core_tls_key);
+ if (err != 0) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+ ngx_thread_key_create_n " failed");
+ /* fatal */
+ exit(2);
+ }
+
+ for (n = 0; n < ngx_threads_n; n++) {
+
+ ngx_threads[n].cv = ngx_cond_init(cycle->log);
+
+ if (ngx_threads[n].cv == NULL) {
+ /* fatal */
+ exit(2);
+ }
+
+ if (ngx_create_thread((ngx_tid_t *) &ngx_threads[n].tid,
+ ngx_worker_thread_cycle,
+ (void *) &ngx_threads[n], cycle->log)
+ != 0)
+ {
+ /* fatal */
+ exit(2);
+ }
+ }
+ }
+ }
+#endif
+
+ for ( ;; ) {
+
+ if (ngx_exiting) {
+
+ c = cycle->connections;
+
+ for (i = 0; i < cycle->connection_n; i++) {
+
+ /* THREAD: lock */
+
+ if (c[i].fd != -1 && c[i].idle) {
+ c[i].close = 1;
+ c[i].read->handler(c[i].read);
+ }
+ }
+
+ if (ngx_event_timer_rbtree.root == ngx_event_timer_rbtree.sentinel)
+ {
+ ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
+
+ ngx_worker_process_exit(cycle);
+ }
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
+
+ ngx_process_events_and_timers(cycle);
+
+ if (ngx_terminate) {
+ ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
+
+ ngx_worker_process_exit(cycle);
+ }
+
+ if (ngx_quit) {
+ ngx_quit = 0;
+ ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
+ "gracefully shutting down");
+ ngx_setproctitle("worker process is shutting down");
+
+ if (!ngx_exiting) {
+ ngx_close_listening_sockets(cycle);
+ ngx_exiting = 1;
+ }
+ }
+
+ if (ngx_reopen) {
+ ngx_reopen = 0;
+ ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
+ ngx_reopen_files(cycle, -1);
+ }
+ }
+}
+
+
+static void
+ngx_worker_process_init(ngx_cycle_t *cycle, ngx_uint_t priority)
+{
+ sigset_t set;
+ ngx_int_t n;
+ ngx_uint_t i;
+ struct rlimit rlmt;
+ ngx_core_conf_t *ccf;
+ ngx_listening_t *ls;
+
+ if (ngx_set_environment(cycle, NULL) == NULL) {
+ /* fatal */
+ exit(2);
+ }
+
+ ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+ if (priority && ccf->priority != 0) {
+ if (setpriority(PRIO_PROCESS, 0, ccf->priority) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "setpriority(%d) failed", ccf->priority);
+ }
+ }
+
+ if (ccf->rlimit_nofile != NGX_CONF_UNSET) {
+ rlmt.rlim_cur = (rlim_t) ccf->rlimit_nofile;
+ rlmt.rlim_max = (rlim_t) ccf->rlimit_nofile;
+
+ if (setrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "setrlimit(RLIMIT_NOFILE, %i) failed",
+ ccf->rlimit_nofile);
+ }
+ }
+
+ if (ccf->rlimit_core != NGX_CONF_UNSET) {
+ rlmt.rlim_cur = (rlim_t) ccf->rlimit_core;
+ rlmt.rlim_max = (rlim_t) ccf->rlimit_core;
+
+ if (setrlimit(RLIMIT_CORE, &rlmt) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "setrlimit(RLIMIT_CORE, %O) failed",
+ ccf->rlimit_core);
+ }
+ }
+
+#ifdef RLIMIT_SIGPENDING
+ if (ccf->rlimit_sigpending != NGX_CONF_UNSET) {
+ rlmt.rlim_cur = (rlim_t) ccf->rlimit_sigpending;
+ rlmt.rlim_max = (rlim_t) ccf->rlimit_sigpending;
+
+ if (setrlimit(RLIMIT_SIGPENDING, &rlmt) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "setrlimit(RLIMIT_SIGPENDING, %i) failed",
+ ccf->rlimit_sigpending);
+ }
+ }
+#endif
+
+ if (geteuid() == 0) {
+ if (setgid(ccf->group) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+ "setgid(%d) failed", ccf->group);
+ /* fatal */
+ exit(2);
+ }
+
+ if (initgroups(ccf->username, ccf->group) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+ "initgroups(%s, %d) failed",
+ ccf->username, ccf->group);
+ }
+
+ if (setuid(ccf->user) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+ "setuid(%d) failed", ccf->user);
+ /* fatal */
+ exit(2);
+ }
+ }
+
+#if (NGX_HAVE_SCHED_SETAFFINITY)
+
+ if (cpu_affinity) {
+ ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
+ "sched_setaffinity(0x%08Xl)", cpu_affinity);
+
+ if (sched_setaffinity(0, 32, (cpu_set_t *) &cpu_affinity) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "sched_setaffinity(0x%08Xl) failed", cpu_affinity);
+ }
+ }
+
+#endif
+
+#if (NGX_HAVE_PR_SET_DUMPABLE)
+
+ /* allow coredump after setuid() in Linux 2.4.x */
+
+ if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "prctl(PR_SET_DUMPABLE) failed");
+ }
+
+#endif
+
+ if (ccf->working_directory.len) {
+ if (chdir((char *) ccf->working_directory.data) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "chdir(\"%s\") failed", ccf->working_directory.data);
+ /* fatal */
+ exit(2);
+ }
+ }
+
+ sigemptyset(&set);
+
+ if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "sigprocmask() failed");
+ }
+
+ /*
+ * disable deleting previous events for the listening sockets because
+ * in the worker processes there are no events at all at this point
+ */
+ ls = cycle->listening.elts;
+ for (i = 0; i < cycle->listening.nelts; i++) {
+ ls[i].previous = NULL;
+ }
+
+ for (i = 0; ngx_modules[i]; i++) {
+ if (ngx_modules[i]->init_process) {
+ if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) {
+ /* fatal */
+ exit(2);
+ }
+ }
+ }
+
+ for (n = 0; n < ngx_last_process; n++) {
+
+ if (ngx_processes[n].pid == -1) {
+ continue;
+ }
+
+ if (n == ngx_process_slot) {
+ continue;
+ }
+
+ if (ngx_processes[n].channel[1] == -1) {
+ continue;
+ }
+
+ if (close(ngx_processes[n].channel[1]) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "close() channel failed");
+ }
+ }
+
+ if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "close() channel failed");
+ }
+
+#if 0
+ ngx_last_process = 0;
+#endif
+
+ if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
+ ngx_channel_handler)
+ == NGX_ERROR)
+ {
+ /* fatal */
+ exit(2);
+ }
+}
+
+
+static void
+ngx_worker_process_exit(ngx_cycle_t *cycle)
+{
+ ngx_uint_t i;
+ ngx_connection_t *c;
+
+#if (NGX_THREADS)
+ ngx_terminate = 1;
+
+ ngx_wakeup_worker_threads(cycle);
+#endif
+
+ for (i = 0; ngx_modules[i]; i++) {
+ if (ngx_modules[i]->exit_process) {
+ ngx_modules[i]->exit_process(cycle);
+ }
+ }
+
+ if (ngx_exiting) {
+ c = cycle->connections;
+ for (i = 0; i < cycle->connection_n; i++) {
+ if (c[i].fd != -1
+ && c[i].read
+ && !c[i].read->accept
+ && !c[i].read->channel
+ && !c[i].read->resolver)
+ {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "open socket #%d left in connection %ui",
+ c[i].fd, i);
+ ngx_debug_quit = 1;
+ }
+ }
+
+ if (ngx_debug_quit) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "aborting");
+ ngx_debug_point();
+ }
+ }
+
+ /*
+ * Copy ngx_cycle->log related data to the special static exit cycle,
+ * log, and log file structures enough to allow a signal handler to log.
+ * The handler may be called when standard ngx_cycle->log allocated from
+ * ngx_cycle->pool is already destroyed.
+ */
+
+ ngx_exit_log_file.fd = ngx_cycle->log->file->fd;
+
+ ngx_exit_log = *ngx_cycle->log;
+ ngx_exit_log.file = &ngx_exit_log_file;
+
+ ngx_exit_cycle.log = &ngx_exit_log;
+ ngx_cycle = &ngx_exit_cycle;
+
+ ngx_destroy_pool(cycle->pool);
+
+ ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0, "exit");
+
+ exit(0);
+}
+
+
+static void
+ngx_channel_handler(ngx_event_t *ev)
+{
+ ngx_int_t n;
+ ngx_channel_t ch;
+ ngx_connection_t *c;
+
+ if (ev->timedout) {
+ ev->timedout = 0;
+ return;
+ }
+
+ c = ev->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_CORE, ev->log, 0, "channel handler");
+
+ for ( ;; ) {
+
+ n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0, "channel: %i", n);
+
+ if (n == NGX_ERROR) {
+
+ if (ngx_event_flags & NGX_USE_EPOLL_EVENT) {
+ ngx_del_conn(c, 0);
+ }
+
+ ngx_close_connection(c);
+ return;
+ }
+
+ if (ngx_event_flags & NGX_USE_EVENTPORT_EVENT) {
+ if (ngx_add_event(ev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+ return;
+ }
+ }
+
+ if (n == NGX_AGAIN) {
+ return;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0,
+ "channel command: %d", ch.command);
+
+ switch (ch.command) {
+
+ case NGX_CMD_QUIT:
+ ngx_quit = 1;
+ break;
+
+ case NGX_CMD_TERMINATE:
+ ngx_terminate = 1;
+ break;
+
+ case NGX_CMD_REOPEN:
+ ngx_reopen = 1;
+ break;
+
+ case NGX_CMD_OPEN_CHANNEL:
+
+ ngx_log_debug3(NGX_LOG_DEBUG_CORE, ev->log, 0,
+ "get channel s:%i pid:%P fd:%d",
+ ch.slot, ch.pid, ch.fd);
+
+ ngx_processes[ch.slot].pid = ch.pid;
+ ngx_processes[ch.slot].channel[0] = ch.fd;
+ break;
+
+ case NGX_CMD_CLOSE_CHANNEL:
+
+ ngx_log_debug4(NGX_LOG_DEBUG_CORE, ev->log, 0,
+ "close channel s:%i pid:%P our:%P fd:%d",
+ ch.slot, ch.pid, ngx_processes[ch.slot].pid,
+ ngx_processes[ch.slot].channel[0]);
+
+ if (close(ngx_processes[ch.slot].channel[0]) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+ "close() channel failed");
+ }
+
+ ngx_processes[ch.slot].channel[0] = -1;
+ break;
+ }
+ }
+}
+
+
+#if (NGX_THREADS)
+
+static void
+ngx_wakeup_worker_threads(ngx_cycle_t *cycle)
+{
+ ngx_int_t i;
+ ngx_uint_t live;
+
+ for ( ;; ) {
+
+ live = 0;
+
+ for (i = 0; i < ngx_threads_n; i++) {
+ if (ngx_threads[i].state < NGX_THREAD_EXIT) {
+ if (ngx_cond_signal(ngx_threads[i].cv) == NGX_ERROR) {
+ ngx_threads[i].state = NGX_THREAD_DONE;
+
+ } else {
+ live = 1;
+ }
+ }
+
+ if (ngx_threads[i].state == NGX_THREAD_EXIT) {
+ ngx_thread_join(ngx_threads[i].tid, NULL);
+ ngx_threads[i].state = NGX_THREAD_DONE;
+ }
+ }
+
+ if (live == 0) {
+ ngx_log_debug0(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+ "all worker threads are joined");
+
+ /* STUB */
+ ngx_done_events(cycle);
+ ngx_mutex_destroy(ngx_event_timer_mutex);
+ ngx_mutex_destroy(ngx_posted_events_mutex);
+
+ return;
+ }
+
+ ngx_sched_yield();
+ }
+}
+
+
+static ngx_thread_value_t
+ngx_worker_thread_cycle(void *data)
+{
+ ngx_thread_t *thr = data;
+
+ sigset_t set;
+ ngx_err_t err;
+ ngx_core_tls_t *tls;
+ ngx_cycle_t *cycle;
+
+ cycle = (ngx_cycle_t *) ngx_cycle;
+
+ sigemptyset(&set);
+ sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
+ sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
+ sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));
+
+ err = ngx_thread_sigmask(SIG_BLOCK, &set, NULL);
+ if (err) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+ ngx_thread_sigmask_n " failed");
+ return (ngx_thread_value_t) 1;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+ "thread " NGX_TID_T_FMT " started", ngx_thread_self());
+
+ ngx_setthrtitle("worker thread");
+
+ tls = ngx_calloc(sizeof(ngx_core_tls_t), cycle->log);
+ if (tls == NULL) {
+ return (ngx_thread_value_t) 1;
+ }
+
+ err = ngx_thread_set_tls(ngx_core_tls_key, tls);
+ if (err != 0) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+ ngx_thread_set_tls_n " failed");
+ return (ngx_thread_value_t) 1;
+ }
+
+ ngx_mutex_lock(ngx_posted_events_mutex);
+
+ for ( ;; ) {
+ thr->state = NGX_THREAD_FREE;
+
+ if (ngx_cond_wait(thr->cv, ngx_posted_events_mutex) == NGX_ERROR) {
+ return (ngx_thread_value_t) 1;
+ }
+
+ if (ngx_terminate) {
+ thr->state = NGX_THREAD_EXIT;
+
+ ngx_mutex_unlock(ngx_posted_events_mutex);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+ "thread " NGX_TID_T_FMT " is done",
+ ngx_thread_self());
+
+ return (ngx_thread_value_t) 0;
+ }
+
+ thr->state = NGX_THREAD_BUSY;
+
+ if (ngx_event_thread_process_posted(cycle) == NGX_ERROR) {
+ return (ngx_thread_value_t) 1;
+ }
+
+ if (ngx_event_thread_process_posted(cycle) == NGX_ERROR) {
+ return (ngx_thread_value_t) 1;
+ }
+
+ if (ngx_process_changes) {
+ if (ngx_process_changes(cycle, 1) == NGX_ERROR) {
+ return (ngx_thread_value_t) 1;
+ }
+ }
+ }
+}
+
+#endif
+
+
+static void
+ngx_cache_manager_process_cycle(ngx_cycle_t *cycle, void *data)
+{
+ ngx_cache_manager_ctx_t *ctx = data;
+
+ void *ident[4];
+ ngx_event_t ev;
+
+ cycle->connection_n = 512;
+
+ ngx_process = NGX_PROCESS_HELPER;
+
+ ngx_worker_process_init(cycle, 0);
+
+ ngx_close_listening_sockets(cycle);
+
+ ngx_memzero(&ev, sizeof(ngx_event_t));
+ ev.handler = ctx->handler;
+ ev.data = ident;
+ ev.log = cycle->log;
+ ident[3] = (void *) -1;
+
+ ngx_use_accept_mutex = 0;
+
+ ngx_setproctitle(ctx->name);
+
+ ngx_add_timer(&ev, ctx->delay);
+
+ for ( ;; ) {
+
+ if (ngx_terminate || ngx_quit) {
+ ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
+ exit(0);
+ }
+
+ if (ngx_reopen) {
+ ngx_reopen = 0;
+ ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
+ ngx_reopen_files(cycle, -1);
+ }
+
+ ngx_process_events_and_timers(cycle);
+ }
+}
+
+
+static void
+ngx_cache_manager_process_handler(ngx_event_t *ev)
+{
+ time_t next, n;
+ ngx_uint_t i;
+ ngx_path_t **path;
+
+ next = 60 * 60;
+
+ path = ngx_cycle->pathes.elts;
+ for (i = 0; i < ngx_cycle->pathes.nelts; i++) {
+
+ if (path[i]->manager) {
+ n = path[i]->manager(path[i]->data);
+
+ next = (n <= next) ? n : next;
+
+ ngx_time_update();
+ }
+ }
+
+ if (next == 0) {
+ next = 1;
+ }
+
+ ngx_add_timer(ev, next * 1000);
+}
+
+
+static void
+ngx_cache_loader_process_handler(ngx_event_t *ev)
+{
+ ngx_uint_t i;
+ ngx_path_t **path;
+ ngx_cycle_t *cycle;
+
+ cycle = (ngx_cycle_t *) ngx_cycle;
+
+ path = cycle->pathes.elts;
+ for (i = 0; i < cycle->pathes.nelts; i++) {
+
+ if (ngx_terminate || ngx_quit) {
+ break;
+ }
+
+ if (path[i]->loader) {
+ path[i]->loader(path[i]->data);
+ ngx_time_update();
+ }
+ }
+
+ exit(0);
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_process_cycle.h b/usr.sbin/nginx/src/os/unix/ngx_process_cycle.h
new file mode 100644
index 00000000000..e6cef6b3f96
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_process_cycle.h
@@ -0,0 +1,60 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_PROCESS_CYCLE_H_INCLUDED_
+#define _NGX_PROCESS_CYCLE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_CMD_OPEN_CHANNEL 1
+#define NGX_CMD_CLOSE_CHANNEL 2
+#define NGX_CMD_QUIT 3
+#define NGX_CMD_TERMINATE 4
+#define NGX_CMD_REOPEN 5
+
+
+#define NGX_PROCESS_SINGLE 0
+#define NGX_PROCESS_MASTER 1
+#define NGX_PROCESS_SIGNALLER 2
+#define NGX_PROCESS_WORKER 3
+#define NGX_PROCESS_HELPER 4
+
+
+typedef struct {
+ ngx_event_handler_pt handler;
+ char *name;
+ ngx_msec_t delay;
+} ngx_cache_manager_ctx_t;
+
+
+void ngx_master_process_cycle(ngx_cycle_t *cycle);
+void ngx_single_process_cycle(ngx_cycle_t *cycle);
+
+
+extern ngx_uint_t ngx_process;
+extern ngx_pid_t ngx_pid;
+extern ngx_pid_t ngx_new_binary;
+extern ngx_uint_t ngx_inherited;
+extern ngx_uint_t ngx_daemonized;
+extern ngx_uint_t ngx_threaded;
+extern ngx_uint_t ngx_exiting;
+
+extern sig_atomic_t ngx_reap;
+extern sig_atomic_t ngx_sigio;
+extern sig_atomic_t ngx_sigalrm;
+extern sig_atomic_t ngx_quit;
+extern sig_atomic_t ngx_debug_quit;
+extern sig_atomic_t ngx_terminate;
+extern sig_atomic_t ngx_noaccept;
+extern sig_atomic_t ngx_reconfigure;
+extern sig_atomic_t ngx_reopen;
+extern sig_atomic_t ngx_change_binary;
+
+
+#endif /* _NGX_PROCESS_CYCLE_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_pthread_thread.c b/usr.sbin/nginx/src/os/unix/ngx_pthread_thread.c
new file mode 100644
index 00000000000..676c7609830
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_pthread_thread.c
@@ -0,0 +1,277 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+static ngx_uint_t nthreads;
+static ngx_uint_t max_threads;
+
+
+static pthread_attr_t thr_attr;
+
+
+ngx_err_t
+ngx_create_thread(ngx_tid_t *tid, ngx_thread_value_t (*func)(void *arg),
+ void *arg, ngx_log_t *log)
+{
+ int err;
+
+ if (nthreads >= max_threads) {
+ ngx_log_error(NGX_LOG_CRIT, log, 0,
+ "no more than %ui threads can be created", max_threads);
+ return NGX_ERROR;
+ }
+
+ err = pthread_create(tid, &thr_attr, func, arg);
+
+ if (err != 0) {
+ ngx_log_error(NGX_LOG_ALERT, log, err, "pthread_create() failed");
+ return err;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0,
+ "thread is created: " NGX_TID_T_FMT, *tid);
+
+ nthreads++;
+
+ return err;
+}
+
+
+ngx_int_t
+ngx_init_threads(int n, size_t size, ngx_cycle_t *cycle)
+{
+ int err;
+
+ max_threads = n;
+
+ err = pthread_attr_init(&thr_attr);
+
+ if (err != 0) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+ "pthread_attr_init() failed");
+ return NGX_ERROR;
+ }
+
+ err = pthread_attr_setstacksize(&thr_attr, size);
+
+ if (err != 0) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+ "pthread_attr_setstacksize() failed");
+ return NGX_ERROR;
+ }
+
+ ngx_threaded = 1;
+
+ return NGX_OK;
+}
+
+
+ngx_mutex_t *
+ngx_mutex_init(ngx_log_t *log, ngx_uint_t flags)
+{
+ int err;
+ ngx_mutex_t *m;
+
+ m = ngx_alloc(sizeof(ngx_mutex_t), log);
+ if (m == NULL) {
+ return NULL;
+ }
+
+ m->log = log;
+
+ err = pthread_mutex_init(&m->mutex, NULL);
+
+ if (err != 0) {
+ ngx_log_error(NGX_LOG_ALERT, m->log, err,
+ "pthread_mutex_init() failed");
+ return NULL;
+ }
+
+ return m;
+}
+
+
+void
+ngx_mutex_destroy(ngx_mutex_t *m)
+{
+ int err;
+
+ err = pthread_mutex_destroy(&m->mutex);
+
+ if (err != 0) {
+ ngx_log_error(NGX_LOG_ALERT, m->log, err,
+ "pthread_mutex_destroy(%p) failed", m);
+ }
+
+ ngx_free(m);
+}
+
+
+void
+ngx_mutex_lock(ngx_mutex_t *m)
+{
+ int err;
+
+ if (!ngx_threaded) {
+ return;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0, "lock mutex %p", m);
+
+ err = pthread_mutex_lock(&m->mutex);
+
+ if (err != 0) {
+ ngx_log_error(NGX_LOG_ALERT, m->log, err,
+ "pthread_mutex_lock(%p) failed", m);
+ ngx_abort();
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0, "mutex %p is locked", m);
+
+ return;
+}
+
+
+ngx_int_t
+ngx_mutex_trylock(ngx_mutex_t *m)
+{
+ int err;
+
+ if (!ngx_threaded) {
+ return NGX_OK;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0, "try lock mutex %p", m);
+
+ err = pthread_mutex_trylock(&m->mutex);
+
+ if (err == NGX_EBUSY) {
+ return NGX_AGAIN;
+ }
+
+ if (err != 0) {
+ ngx_log_error(NGX_LOG_ALERT, m->log, err,
+ "pthread_mutex_trylock(%p) failed", m);
+ ngx_abort();
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0, "mutex %p is locked", m);
+
+ return NGX_OK;
+}
+
+
+void
+ngx_mutex_unlock(ngx_mutex_t *m)
+{
+ int err;
+
+ if (!ngx_threaded) {
+ return;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0, "unlock mutex %p", m);
+
+ err = pthread_mutex_unlock(&m->mutex);
+
+ if (err != 0) {
+ ngx_log_error(NGX_LOG_ALERT, m->log, err,
+ "pthread_mutex_unlock(%p) failed", m);
+ ngx_abort();
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0, "mutex %p is unlocked", m);
+
+ return;
+}
+
+
+ngx_cond_t *
+ngx_cond_init(ngx_log_t *log)
+{
+ int err;
+ ngx_cond_t *cv;
+
+ cv = ngx_alloc(sizeof(ngx_cond_t), log);
+ if (cv == NULL) {
+ return NULL;
+ }
+
+ cv->log = log;
+
+ err = pthread_cond_init(&cv->cond, NULL);
+
+ if (err != 0) {
+ ngx_log_error(NGX_LOG_ALERT, cv->log, err,
+ "pthread_cond_init() failed");
+ return NULL;
+ }
+
+ return cv;
+}
+
+
+void
+ngx_cond_destroy(ngx_cond_t *cv)
+{
+ int err;
+
+ err = pthread_cond_destroy(&cv->cond);
+
+ if (err != 0) {
+ ngx_log_error(NGX_LOG_ALERT, cv->log, err,
+ "pthread_cond_destroy(%p) failed", cv);
+ }
+
+ ngx_free(cv);
+}
+
+
+ngx_int_t
+ngx_cond_wait(ngx_cond_t *cv, ngx_mutex_t *m)
+{
+ int err;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, cv->log, 0, "cv %p wait", cv);
+
+ err = pthread_cond_wait(&cv->cond, &m->mutex);
+
+ if (err != 0) {
+ ngx_log_error(NGX_LOG_ALERT, cv->log, err,
+ "pthread_cond_wait(%p) failed", cv);
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, cv->log, 0, "cv %p is waked up", cv);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0, "mutex %p is locked", m);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_cond_signal(ngx_cond_t *cv)
+{
+ int err;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, cv->log, 0, "cv %p to signal", cv);
+
+ err = pthread_cond_signal(&cv->cond);
+
+ if (err != 0) {
+ ngx_log_error(NGX_LOG_ALERT, cv->log, err,
+ "pthread_cond_signal(%p) failed", cv);
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, cv->log, 0, "cv %p is signaled", cv);
+
+ return NGX_OK;
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_readv_chain.c b/usr.sbin/nginx/src/os/unix/ngx_readv_chain.c
new file mode 100644
index 00000000000..b65a0d7b85e
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_readv_chain.c
@@ -0,0 +1,257 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define NGX_IOVS 16
+
+
+#if (NGX_HAVE_KQUEUE)
+
+ssize_t
+ngx_readv_chain(ngx_connection_t *c, ngx_chain_t *chain)
+{
+ u_char *prev;
+ ssize_t n, size;
+ ngx_err_t err;
+ ngx_array_t vec;
+ ngx_event_t *rev;
+ struct iovec *iov, iovs[NGX_IOVS];
+
+ rev = c->read;
+
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "readv: eof:%d, avail:%d, err:%d",
+ rev->pending_eof, rev->available, rev->kq_errno);
+
+ if (rev->available == 0) {
+ if (rev->pending_eof) {
+ rev->ready = 0;
+ rev->eof = 1;
+
+ ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno,
+ "kevent() reported about an closed connection");
+
+ if (rev->kq_errno) {
+ rev->error = 1;
+ ngx_set_socket_errno(rev->kq_errno);
+ return NGX_ERROR;
+ }
+
+ return 0;
+
+ } else {
+ return NGX_AGAIN;
+ }
+ }
+ }
+
+ prev = NULL;
+ iov = NULL;
+ size = 0;
+
+ vec.elts = iovs;
+ vec.nelts = 0;
+ vec.size = sizeof(struct iovec);
+ vec.nalloc = NGX_IOVS;
+ vec.pool = c->pool;
+
+ /* coalesce the neighbouring bufs */
+
+ while (chain) {
+ if (prev == chain->buf->last) {
+ iov->iov_len += chain->buf->end - chain->buf->last;
+
+ } else {
+ iov = ngx_array_push(&vec);
+ if (iov == NULL) {
+ return NGX_ERROR;
+ }
+
+ iov->iov_base = (void *) chain->buf->last;
+ iov->iov_len = chain->buf->end - chain->buf->last;
+ }
+
+ size += chain->buf->end - chain->buf->last;
+ prev = chain->buf->end;
+ chain = chain->next;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "readv: %d, last:%d", vec.nelts, iov->iov_len);
+
+ rev = c->read;
+
+ do {
+ n = readv(c->fd, (struct iovec *) vec.elts, vec.nelts);
+
+ if (n >= 0) {
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+ rev->available -= n;
+
+ /*
+ * rev->available may be negative here because some additional
+ * bytes may be received between kevent() and recv()
+ */
+
+ if (rev->available <= 0) {
+ if (!rev->pending_eof) {
+ rev->ready = 0;
+ }
+
+ if (rev->available < 0) {
+ rev->available = 0;
+ }
+ }
+
+ if (n == 0) {
+
+ /*
+ * on FreeBSD recv() may return 0 on closed socket
+ * even if kqueue reported about available data
+ */
+
+#if 0
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "readv() returned 0 while kevent() reported "
+ "%d available bytes", rev->available);
+#endif
+
+ rev->eof = 1;
+ rev->available = 0;
+ }
+
+ return n;
+ }
+
+ if (n < size) {
+ rev->ready = 0;
+ }
+
+ if (n == 0) {
+ rev->eof = 1;
+ }
+
+ return n;
+ }
+
+ err = ngx_socket_errno;
+
+ if (err == NGX_EAGAIN || err == NGX_EINTR) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "readv() not ready");
+ n = NGX_AGAIN;
+
+ } else {
+ n = ngx_connection_error(c, err, "readv() failed");
+ break;
+ }
+
+ } while (err == NGX_EINTR);
+
+ rev->ready = 0;
+
+ if (n == NGX_ERROR) {
+ c->read->error = 1;
+ }
+
+ return n;
+}
+
+#else /* ! NGX_HAVE_KQUEUE */
+
+ssize_t
+ngx_readv_chain(ngx_connection_t *c, ngx_chain_t *chain)
+{
+ u_char *prev;
+ ssize_t n, size;
+ ngx_err_t err;
+ ngx_array_t vec;
+ ngx_event_t *rev;
+ struct iovec *iov, iovs[NGX_IOVS];
+
+ prev = NULL;
+ iov = NULL;
+ size = 0;
+
+ vec.elts = iovs;
+ vec.nelts = 0;
+ vec.size = sizeof(struct iovec);
+ vec.nalloc = NGX_IOVS;
+ vec.pool = c->pool;
+
+ /* coalesce the neighbouring bufs */
+
+ while (chain) {
+ if (prev == chain->buf->last) {
+ iov->iov_len += chain->buf->end - chain->buf->last;
+
+ } else {
+ iov = ngx_array_push(&vec);
+ if (iov == NULL) {
+ return NGX_ERROR;
+ }
+
+ iov->iov_base = (void *) chain->buf->last;
+ iov->iov_len = chain->buf->end - chain->buf->last;
+ }
+
+ size += chain->buf->end - chain->buf->last;
+ prev = chain->buf->end;
+ chain = chain->next;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "readv: %d:%d", vec.nelts, iov->iov_len);
+
+ rev = c->read;
+
+ do {
+ n = readv(c->fd, (struct iovec *) vec.elts, vec.nelts);
+
+ if (n == 0) {
+ rev->ready = 0;
+ rev->eof = 1;
+
+ return n;
+
+ } else if (n > 0) {
+
+ if (n < size && !(ngx_event_flags & NGX_USE_GREEDY_EVENT)) {
+ rev->ready = 0;
+ }
+
+ return n;
+ }
+
+ err = ngx_socket_errno;
+
+ if (err == NGX_EAGAIN || err == NGX_EINTR) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "readv() not ready");
+ n = NGX_AGAIN;
+
+ } else {
+ n = ngx_connection_error(c, err, "readv() failed");
+ break;
+ }
+
+ } while (err == NGX_EINTR);
+
+ rev->ready = 0;
+
+ if (n == NGX_ERROR) {
+ c->read->error = 1;
+ }
+
+ return n;
+}
+
+#endif /* NGX_HAVE_KQUEUE */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_recv.c b/usr.sbin/nginx/src/os/unix/ngx_recv.c
new file mode 100644
index 00000000000..316e0516d14
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_recv.c
@@ -0,0 +1,179 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (NGX_HAVE_KQUEUE)
+
+ssize_t
+ngx_unix_recv(ngx_connection_t *c, u_char *buf, size_t size)
+{
+ ssize_t n;
+ ngx_err_t err;
+ ngx_event_t *rev;
+
+ rev = c->read;
+
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "recv: eof:%d, avail:%d, err:%d",
+ rev->pending_eof, rev->available, rev->kq_errno);
+
+ if (rev->available == 0) {
+ if (rev->pending_eof) {
+ rev->ready = 0;
+ rev->eof = 1;
+
+ if (rev->kq_errno) {
+ rev->error = 1;
+ ngx_set_socket_errno(rev->kq_errno);
+
+ return ngx_connection_error(c, rev->kq_errno,
+ "kevent() reported about an closed connection");
+ }
+
+ return 0;
+
+ } else {
+ rev->ready = 0;
+ return NGX_AGAIN;
+ }
+ }
+ }
+
+ do {
+ n = recv(c->fd, buf, size, 0);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "recv: fd:%d %d of %d", c->fd, n, size);
+
+ if (n >= 0) {
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+ rev->available -= n;
+
+ /*
+ * rev->available may be negative here because some additional
+ * bytes may be received between kevent() and recv()
+ */
+
+ if (rev->available <= 0) {
+ if (!rev->pending_eof) {
+ rev->ready = 0;
+ }
+
+ if (rev->available < 0) {
+ rev->available = 0;
+ }
+ }
+
+ if (n == 0) {
+
+ /*
+ * on FreeBSD recv() may return 0 on closed socket
+ * even if kqueue reported about available data
+ */
+
+ rev->eof = 1;
+ rev->available = 0;
+ }
+
+ return n;
+ }
+
+ if ((size_t) n < size) {
+ rev->ready = 0;
+ }
+
+ if (n == 0) {
+ rev->eof = 1;
+ }
+
+ return n;
+ }
+
+ err = ngx_socket_errno;
+
+ if (err == NGX_EAGAIN || err == NGX_EINTR) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "recv() not ready");
+ n = NGX_AGAIN;
+
+ } else {
+ n = ngx_connection_error(c, err, "recv() failed");
+ break;
+ }
+
+ } while (err == NGX_EINTR);
+
+ rev->ready = 0;
+
+ if (n == NGX_ERROR) {
+ rev->error = 1;
+ }
+
+ return n;
+}
+
+#else /* ! NGX_HAVE_KQUEUE */
+
+ssize_t
+ngx_unix_recv(ngx_connection_t *c, u_char *buf, size_t size)
+{
+ ssize_t n;
+ ngx_err_t err;
+ ngx_event_t *rev;
+
+ rev = c->read;
+
+ do {
+ n = recv(c->fd, buf, size, 0);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "recv: fd:%d %d of %d", c->fd, n, size);
+
+ if (n == 0) {
+ rev->ready = 0;
+ rev->eof = 1;
+ return n;
+
+ } else if (n > 0) {
+
+ if ((size_t) n < size
+ && !(ngx_event_flags & NGX_USE_GREEDY_EVENT))
+ {
+ rev->ready = 0;
+ }
+
+ return n;
+ }
+
+ err = ngx_socket_errno;
+
+ if (err == NGX_EAGAIN || err == NGX_EINTR) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "recv() not ready");
+ n = NGX_AGAIN;
+
+ } else {
+ n = ngx_connection_error(c, err, "recv() failed");
+ break;
+ }
+
+ } while (err == NGX_EINTR);
+
+ rev->ready = 0;
+
+ if (n == NGX_ERROR) {
+ rev->error = 1;
+ }
+
+ return n;
+}
+
+#endif /* NGX_HAVE_KQUEUE */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_send.c b/usr.sbin/nginx/src/os/unix/ngx_send.c
new file mode 100644
index 00000000000..e0d69007738
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_send.c
@@ -0,0 +1,72 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ssize_t
+ngx_unix_send(ngx_connection_t *c, u_char *buf, size_t size)
+{
+ ssize_t n;
+ ngx_err_t err;
+ ngx_event_t *wev;
+
+ wev = c->write;
+
+#if (NGX_HAVE_KQUEUE)
+
+ if ((ngx_event_flags & NGX_USE_KQUEUE_EVENT) && wev->pending_eof) {
+ (void) ngx_connection_error(c, wev->kq_errno,
+ "kevent() reported about an closed connection");
+ wev->error = 1;
+ return NGX_ERROR;
+ }
+
+#endif
+
+ for ( ;; ) {
+ n = send(c->fd, buf, size, 0);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "send: fd:%d %d of %d", c->fd, n, size);
+
+ if (n > 0) {
+ if (n < (ssize_t) size) {
+ wev->ready = 0;
+ }
+
+ c->sent += n;
+
+ return n;
+ }
+
+ err = ngx_socket_errno;
+
+ if (n == 0) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, err, "send() returned zero");
+ wev->ready = 0;
+ return n;
+ }
+
+ if (err == NGX_EAGAIN || err == NGX_EINTR) {
+ wev->ready = 0;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "send() not ready");
+
+ if (err == NGX_EAGAIN) {
+ return NGX_AGAIN;
+ }
+
+ } else {
+ wev->error = 1;
+ (void) ngx_connection_error(c, err, "send() failed");
+ return NGX_ERROR;
+ }
+ }
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_setproctitle.c b/usr.sbin/nginx/src/os/unix/ngx_setproctitle.c
new file mode 100644
index 00000000000..b814610b763
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_setproctitle.c
@@ -0,0 +1,134 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if (NGX_SETPROCTITLE_USES_ENV)
+
+/*
+ * To change the process title in Linux and Solaris we have to set argv[1]
+ * to NULL and to copy the title to the same place where the argv[0] points to.
+ * However, argv[0] may be too small to hold a new title. Fortunately, Linux
+ * and Solaris store argv[] and environ[] one after another. So we should
+ * ensure that is the continuous memory and then we allocate the new memory
+ * for environ[] and copy it. After this we could use the memory starting
+ * from argv[0] for our process title.
+ *
+ * The Solaris's standard /bin/ps does not show the changed process title.
+ * You have to use "/usr/ucb/ps -w" instead. Besides, the UCB ps dos not
+ * show a new title if its length less than the origin command line length.
+ * To avoid it we append to a new title the origin command line in the
+ * parenthesis.
+ */
+
+extern char **environ;
+
+static char *ngx_os_argv_last;
+
+ngx_int_t
+ngx_init_setproctitle(ngx_log_t *log)
+{
+ u_char *p;
+ size_t size;
+ ngx_uint_t i;
+
+ size = 0;
+
+ for (i = 0; environ[i]; i++) {
+ size += ngx_strlen(environ[i]) + 1;
+ }
+
+ p = ngx_alloc(size, log);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_os_argv_last = ngx_os_argv[0];
+
+ for (i = 0; ngx_os_argv[i]; i++) {
+ if (ngx_os_argv_last == ngx_os_argv[i]) {
+ ngx_os_argv_last = ngx_os_argv[i] + ngx_strlen(ngx_os_argv[i]) + 1;
+ }
+ }
+
+ for (i = 0; environ[i]; i++) {
+ if (ngx_os_argv_last == environ[i]) {
+
+ size = ngx_strlen(environ[i]) + 1;
+ ngx_os_argv_last = environ[i] + size;
+
+ ngx_cpystrn(p, (u_char *) environ[i], size);
+ environ[i] = (char *) p;
+ p += size;
+ }
+ }
+
+ ngx_os_argv_last--;
+
+ return NGX_OK;
+}
+
+
+void
+ngx_setproctitle(char *title)
+{
+ u_char *p;
+
+#if (NGX_SOLARIS)
+
+ ngx_int_t i;
+ size_t size;
+
+#endif
+
+ ngx_os_argv[1] = NULL;
+
+ p = ngx_cpystrn((u_char *) ngx_os_argv[0], (u_char *) "nginx: ",
+ ngx_os_argv_last - ngx_os_argv[0]);
+
+ p = ngx_cpystrn(p, (u_char *) title, ngx_os_argv_last - (char *) p);
+
+#if (NGX_SOLARIS)
+
+ size = 0;
+
+ for (i = 0; i < ngx_argc; i++) {
+ size += ngx_strlen(ngx_argv[i]) + 1;
+ }
+
+ if (size > (size_t) ((char *) p - ngx_os_argv[0])) {
+
+ /*
+ * ngx_setproctitle() is too rare operation so we use
+ * the non-optimized copies
+ */
+
+ p = ngx_cpystrn(p, (u_char *) " (", ngx_os_argv_last - (char *) p);
+
+ for (i = 0; i < ngx_argc; i++) {
+ p = ngx_cpystrn(p, (u_char *) ngx_argv[i],
+ ngx_os_argv_last - (char *) p);
+ p = ngx_cpystrn(p, (u_char *) " ", ngx_os_argv_last - (char *) p);
+ }
+
+ if (*(p - 1) == ' ') {
+ *(p - 1) = ')';
+ }
+ }
+
+#endif
+
+ if (ngx_os_argv_last - (char *) p) {
+ ngx_memset(p, NGX_SETPROCTITLE_PAD, ngx_os_argv_last - (char *) p);
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
+ "setproctitle: \"%s\"", ngx_os_argv[0]);
+}
+
+#endif /* NGX_SETPROCTITLE_USES_ENV */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_setproctitle.h b/usr.sbin/nginx/src/os/unix/ngx_setproctitle.h
new file mode 100644
index 00000000000..09973e990b8
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_setproctitle.h
@@ -0,0 +1,51 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_SETPROCTITLE_H_INCLUDED_
+#define _NGX_SETPROCTITLE_H_INCLUDED_
+
+
+#if (NGX_HAVE_SETPROCTITLE)
+
+/* FreeBSD, NetBSD, OpenBSD */
+
+#define ngx_init_setproctitle(log)
+#define ngx_setproctitle(title) setproctitle("%s", title)
+
+
+#else /* !NGX_HAVE_SETPROCTITLE */
+
+#if !defined NGX_SETPROCTITLE_USES_ENV
+
+#if (NGX_SOLARIS)
+
+#define NGX_SETPROCTITLE_USES_ENV 1
+#define NGX_SETPROCTITLE_PAD ' '
+
+ngx_int_t ngx_init_setproctitle(ngx_log_t *log);
+void ngx_setproctitle(char *title);
+
+#elif (NGX_LINUX) || (NGX_DARWIN)
+
+#define NGX_SETPROCTITLE_USES_ENV 1
+#define NGX_SETPROCTITLE_PAD '\0'
+
+ngx_int_t ngx_init_setproctitle(ngx_log_t *log);
+void ngx_setproctitle(char *title);
+
+#else
+
+#define ngx_init_setproctitle(log)
+#define ngx_setproctitle(title)
+
+#endif /* OSes */
+
+#endif /* NGX_SETPROCTITLE_USES_ENV */
+
+#endif /* NGX_HAVE_SETPROCTITLE */
+
+
+#endif /* _NGX_SETPROCTITLE_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_shmem.c b/usr.sbin/nginx/src/os/unix/ngx_shmem.c
new file mode 100644
index 00000000000..f7f831fdb96
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_shmem.c
@@ -0,0 +1,125 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if (NGX_HAVE_MAP_ANON)
+
+ngx_int_t
+ngx_shm_alloc(ngx_shm_t *shm)
+{
+ shm->addr = (u_char *) mmap(NULL, shm->size,
+ PROT_READ|PROT_WRITE,
+ MAP_ANON|MAP_SHARED, -1, 0);
+
+ if (shm->addr == MAP_FAILED) {
+ ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+ "mmap(MAP_ANON|MAP_SHARED, %uz) failed", shm->size);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+void
+ngx_shm_free(ngx_shm_t *shm)
+{
+ if (munmap((void *) shm->addr, shm->size) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+ "munmap(%p, %uz) failed", shm->addr, shm->size);
+ }
+}
+
+#elif (NGX_HAVE_MAP_DEVZERO)
+
+ngx_int_t
+ngx_shm_alloc(ngx_shm_t *shm)
+{
+ ngx_fd_t fd;
+
+ fd = open("/dev/zero", O_RDWR);
+
+ if (fd == -1) {
+ ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+ "open(\"/dev/zero\") failed");
+ return NGX_ERROR;
+ }
+
+ shm->addr = (u_char *) mmap(NULL, shm->size, PROT_READ|PROT_WRITE,
+ MAP_SHARED, fd, 0);
+
+ if (shm->addr == MAP_FAILED) {
+ ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+ "mmap(/dev/zero, MAP_SHARED, %uz) failed", shm->size);
+ }
+
+ if (close(fd) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+ "close(\"/dev/zero\") failed");
+ }
+
+ return (shm->addr == MAP_FAILED) ? NGX_ERROR : NGX_OK;
+}
+
+
+void
+ngx_shm_free(ngx_shm_t *shm)
+{
+ if (munmap((void *) shm->addr, shm->size) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+ "munmap(%p, %uz) failed", shm->addr, shm->size);
+ }
+}
+
+#elif (NGX_HAVE_SYSVSHM)
+
+#include <sys/ipc.h>
+#include <sys/shm.h>
+
+
+ngx_int_t
+ngx_shm_alloc(ngx_shm_t *shm)
+{
+ int id;
+
+ id = shmget(IPC_PRIVATE, shm->size, (SHM_R|SHM_W|IPC_CREAT));
+
+ if (id == -1) {
+ ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+ "shmget(%uz) failed", shm->size);
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, shm->log, 0, "shmget id: %d", id);
+
+ shm->addr = shmat(id, NULL, 0);
+
+ if (shm->addr == (void *) -1) {
+ ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "shmat() failed");
+ }
+
+ if (shmctl(id, IPC_RMID, NULL) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+ "shmctl(IPC_RMID) failed");
+ }
+
+ return (shm->addr == (void *) -1) ? NGX_ERROR : NGX_OK;
+}
+
+
+void
+ngx_shm_free(ngx_shm_t *shm)
+{
+ if (shmdt(shm->addr) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
+ "shmdt(%p) failed", shm->addr);
+ }
+}
+
+#endif
diff --git a/usr.sbin/nginx/src/os/unix/ngx_shmem.h b/usr.sbin/nginx/src/os/unix/ngx_shmem.h
new file mode 100644
index 00000000000..b5f6697010d
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_shmem.h
@@ -0,0 +1,28 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_SHMEM_H_INCLUDED_
+#define _NGX_SHMEM_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct {
+ u_char *addr;
+ size_t size;
+ ngx_str_t name;
+ ngx_log_t *log;
+ ngx_uint_t exists; /* unsigned exists:1; */
+} ngx_shm_t;
+
+
+ngx_int_t ngx_shm_alloc(ngx_shm_t *shm);
+void ngx_shm_free(ngx_shm_t *shm);
+
+
+#endif /* _NGX_SHMEM_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_socket.c b/usr.sbin/nginx/src/os/unix/ngx_socket.c
new file mode 100644
index 00000000000..27ae48dc509
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_socket.c
@@ -0,0 +1,115 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ * ioctl(FIONBIO) sets a non-blocking mode with the single syscall
+ * while fcntl(F_SETFL, O_NONBLOCK) needs to learn the current state
+ * using fcntl(F_GETFL).
+ *
+ * ioctl() and fcntl() are syscalls at least in FreeBSD 2.x, Linux 2.2
+ * and Solaris 7.
+ *
+ * ioctl() in Linux 2.4 and 2.6 uses BKL, however, fcntl(F_SETFL) uses it too.
+ */
+
+
+#if (NGX_HAVE_FIONBIO)
+
+int
+ngx_nonblocking(ngx_socket_t s)
+{
+ int nb;
+
+ nb = 1;
+
+ return ioctl(s, FIONBIO, &nb);
+}
+
+
+int
+ngx_blocking(ngx_socket_t s)
+{
+ int nb;
+
+ nb = 0;
+
+ return ioctl(s, FIONBIO, &nb);
+}
+
+#endif
+
+
+#if (NGX_FREEBSD)
+
+int
+ngx_tcp_nopush(ngx_socket_t s)
+{
+ int tcp_nopush;
+
+ tcp_nopush = 1;
+
+ return setsockopt(s, IPPROTO_TCP, TCP_NOPUSH,
+ (const void *) &tcp_nopush, sizeof(int));
+}
+
+
+int
+ngx_tcp_push(ngx_socket_t s)
+{
+ int tcp_nopush;
+
+ tcp_nopush = 0;
+
+ return setsockopt(s, IPPROTO_TCP, TCP_NOPUSH,
+ (const void *) &tcp_nopush, sizeof(int));
+}
+
+#elif (NGX_LINUX)
+
+
+int
+ngx_tcp_nopush(ngx_socket_t s)
+{
+ int cork;
+
+ cork = 1;
+
+ return setsockopt(s, IPPROTO_TCP, TCP_CORK,
+ (const void *) &cork, sizeof(int));
+}
+
+
+int
+ngx_tcp_push(ngx_socket_t s)
+{
+ int cork;
+
+ cork = 0;
+
+ return setsockopt(s, IPPROTO_TCP, TCP_CORK,
+ (const void *) &cork, sizeof(int));
+}
+
+#else
+
+int
+ngx_tcp_nopush(ngx_socket_t s)
+{
+ return 0;
+}
+
+
+int
+ngx_tcp_push(ngx_socket_t s)
+{
+ return 0;
+}
+
+#endif
diff --git a/usr.sbin/nginx/src/os/unix/ngx_socket.h b/usr.sbin/nginx/src/os/unix/ngx_socket.h
new file mode 100644
index 00000000000..9e6a859bb20
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_socket.h
@@ -0,0 +1,63 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_SOCKET_H_INCLUDED_
+#define _NGX_SOCKET_H_INCLUDED_
+
+
+#include <ngx_config.h>
+
+
+#define NGX_WRITE_SHUTDOWN SHUT_WR
+
+typedef int ngx_socket_t;
+
+#define ngx_socket socket
+#define ngx_socket_n "socket()"
+
+
+#if (NGX_HAVE_FIONBIO)
+
+int ngx_nonblocking(ngx_socket_t s);
+int ngx_blocking(ngx_socket_t s);
+
+#define ngx_nonblocking_n "ioctl(FIONBIO)"
+#define ngx_blocking_n "ioctl(!FIONBIO)"
+
+#else
+
+#define ngx_nonblocking(s) fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK)
+#define ngx_nonblocking_n "fcntl(O_NONBLOCK)"
+
+#define ngx_blocking(s) fcntl(s, F_SETFL, fcntl(s, F_GETFL) & ~O_NONBLOCK)
+#define ngx_blocking_n "fcntl(!O_NONBLOCK)"
+
+#endif
+
+int ngx_tcp_nopush(ngx_socket_t s);
+int ngx_tcp_push(ngx_socket_t s);
+
+#if (NGX_LINUX)
+
+#define ngx_tcp_nopush_n "setsockopt(TCP_CORK)"
+#define ngx_tcp_push_n "setsockopt(!TCP_CORK)"
+
+#else
+
+#define ngx_tcp_nopush_n "setsockopt(TCP_NOPUSH)"
+#define ngx_tcp_push_n "setsockopt(!TCP_NOPUSH)"
+
+#endif
+
+
+#define ngx_shutdown_socket shutdown
+#define ngx_shutdown_socket_n "shutdown()"
+
+#define ngx_close_socket close
+#define ngx_close_socket_n "close() socket"
+
+
+#endif /* _NGX_SOCKET_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_solaris.h b/usr.sbin/nginx/src/os/unix/ngx_solaris.h
new file mode 100644
index 00000000000..44ce7967983
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_solaris.h
@@ -0,0 +1,15 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_SOLARIS_H_INCLUDED_
+#define _NGX_SOLARIS_H_INCLUDED_
+
+
+ngx_chain_t *ngx_solaris_sendfilev_chain(ngx_connection_t *c, ngx_chain_t *in,
+ off_t limit);
+
+
+#endif /* _NGX_SOLARIS_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_solaris_config.h b/usr.sbin/nginx/src/os/unix/ngx_solaris_config.h
new file mode 100644
index 00000000000..6b3d42eaae2
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_solaris_config.h
@@ -0,0 +1,106 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_SOLARIS_CONFIG_H_INCLUDED_
+#define _NGX_SOLARIS_CONFIG_H_INCLUDED_
+
+
+#ifndef _REENTRANT
+#define _REENTRANT
+#endif
+
+#define _FILE_OFFSET_BITS 64 /* must be before <sys/types.h> */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stddef.h> /* offsetof() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+#include <glob.h>
+#include <sys/statvfs.h> /* statvfs() */
+
+#include <sys/filio.h> /* FIONBIO */
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sched.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h> /* TCP_NODELAY */
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/un.h>
+
+#include <sys/systeminfo.h>
+#include <limits.h> /* IOV_MAX */
+#include <inttypes.h>
+#include <crypt.h>
+
+#define NGX_ALIGNMENT _MAX_ALIGNMENT
+
+#include <ngx_auto_config.h>
+
+
+#if (NGX_HAVE_POSIX_SEM)
+#include <semaphore.h>
+#endif
+
+
+#if (NGX_HAVE_POLL)
+#include <poll.h>
+#endif
+
+
+#if (NGX_HAVE_DEVPOLL)
+#include <sys/ioctl.h>
+#include <sys/devpoll.h>
+#endif
+
+
+#if (NGX_HAVE_EVENTPORT)
+#include <port.h>
+#endif
+
+
+#if (NGX_HAVE_SENDFILE)
+#include <sys/sendfile.h>
+#endif
+
+
+#define NGX_LISTEN_BACKLOG 511
+
+
+#ifndef NGX_HAVE_INHERITED_NONBLOCK
+#define NGX_HAVE_INHERITED_NONBLOCK 1
+#endif
+
+
+#ifndef NGX_HAVE_SO_SNDLOWAT
+/* setsockopt(SO_SNDLOWAT) returns ENOPROTOOPT */
+#define NGX_HAVE_SO_SNDLOWAT 0
+#endif
+
+
+#define NGX_HAVE_OS_SPECIFIC_INIT 1
+
+
+extern char **environ;
+
+
+#endif /* _NGX_SOLARIS_CONFIG_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_solaris_init.c b/usr.sbin/nginx/src/os/unix/ngx_solaris_init.c
new file mode 100644
index 00000000000..57a859e6806
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_solaris_init.c
@@ -0,0 +1,74 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+char ngx_solaris_sysname[20];
+char ngx_solaris_release[10];
+char ngx_solaris_version[50];
+
+
+static ngx_os_io_t ngx_solaris_io = {
+ ngx_unix_recv,
+ ngx_readv_chain,
+ ngx_udp_unix_recv,
+ ngx_unix_send,
+#if (NGX_HAVE_SENDFILE)
+ ngx_solaris_sendfilev_chain,
+ NGX_IO_SENDFILE
+#else
+ ngx_writev_chain,
+ 0
+#endif
+};
+
+
+ngx_int_t
+ngx_os_specific_init(ngx_log_t *log)
+{
+ if (sysinfo(SI_SYSNAME, ngx_solaris_sysname, sizeof(ngx_solaris_sysname))
+ == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ "sysinfo(SI_SYSNAME) failed");
+ return NGX_ERROR;
+ }
+
+ if (sysinfo(SI_RELEASE, ngx_solaris_release, sizeof(ngx_solaris_release))
+ == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ "sysinfo(SI_RELEASE) failed");
+ return NGX_ERROR;
+ }
+
+ if (sysinfo(SI_VERSION, ngx_solaris_version, sizeof(ngx_solaris_version))
+ == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ "sysinfo(SI_SYSNAME) failed");
+ return NGX_ERROR;
+ }
+
+
+ ngx_os_io = ngx_solaris_io;
+
+ return NGX_OK;
+}
+
+
+void
+ngx_os_specific_status(ngx_log_t *log)
+{
+
+ ngx_log_error(NGX_LOG_NOTICE, log, 0, "OS: %s %s",
+ ngx_solaris_sysname, ngx_solaris_release);
+
+ ngx_log_error(NGX_LOG_NOTICE, log, 0, "version: %s",
+ ngx_solaris_version);
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_solaris_sendfilev_chain.c b/usr.sbin/nginx/src/os/unix/ngx_solaris_sendfilev_chain.c
new file mode 100644
index 00000000000..3a9356cdbba
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_solaris_sendfilev_chain.c
@@ -0,0 +1,250 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (NGX_TEST_BUILD_SOLARIS_SENDFILEV)
+
+/* Solaris declarations */
+
+typedef struct sendfilevec {
+ int sfv_fd;
+ u_int sfv_flag;
+ off_t sfv_off;
+ size_t sfv_len;
+} sendfilevec_t;
+
+#define SFV_FD_SELF -2
+
+static ssize_t sendfilev(int fd, const struct sendfilevec *vec,
+ int sfvcnt, size_t *xferred)
+{
+ return -1;
+}
+
+#endif
+
+
+#if (IOV_MAX > 64)
+#define NGX_SENDFILEVECS 64
+#else
+#define NGX_SENDFILEVECS IOV_MAX
+#endif
+
+
+
+ngx_chain_t *
+ngx_solaris_sendfilev_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+ int fd;
+ u_char *prev;
+ off_t size, send, prev_send, aligned, fprev;
+ size_t sent;
+ ssize_t n;
+ ngx_int_t eintr, complete;
+ ngx_err_t err;
+ sendfilevec_t *sfv, sfvs[NGX_SENDFILEVECS];
+ ngx_array_t vec;
+ ngx_event_t *wev;
+ ngx_chain_t *cl;
+
+ wev = c->write;
+
+ if (!wev->ready) {
+ return in;
+ }
+
+ if (!c->sendfile) {
+ return ngx_writev_chain(c, in, limit);
+ }
+
+
+ /* the maximum limit size is the maximum size_t value - the page size */
+
+ if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
+ limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
+ }
+
+
+ send = 0;
+ complete = 0;
+
+ vec.elts = sfvs;
+ vec.size = sizeof(sendfilevec_t);
+ vec.nalloc = NGX_SENDFILEVECS;
+ vec.pool = c->pool;
+
+ for ( ;; ) {
+ fd = SFV_FD_SELF;
+ prev = NULL;
+ fprev = 0;
+ sfv = NULL;
+ eintr = 0;
+ sent = 0;
+ prev_send = send;
+
+ vec.nelts = 0;
+
+ /* create the sendfilevec and coalesce the neighbouring bufs */
+
+ for (cl = in; cl && vec.nelts < IOV_MAX && send < limit; cl = cl->next)
+ {
+ if (ngx_buf_special(cl->buf)) {
+ continue;
+ }
+
+ if (ngx_buf_in_memory_only(cl->buf)) {
+ fd = SFV_FD_SELF;
+
+ size = cl->buf->last - cl->buf->pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+ }
+
+ if (prev == cl->buf->pos) {
+ sfv->sfv_len += (size_t) size;
+
+ } else {
+ sfv = ngx_array_push(&vec);
+ if (sfv == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ sfv->sfv_fd = SFV_FD_SELF;
+ sfv->sfv_flag = 0;
+ sfv->sfv_off = (off_t) (uintptr_t) cl->buf->pos;
+ sfv->sfv_len = (size_t) size;
+ }
+
+ prev = cl->buf->pos + (size_t) size;
+ send += size;
+
+ } else {
+ prev = NULL;
+
+ size = cl->buf->file_last - cl->buf->file_pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+
+ aligned = (cl->buf->file_pos + size + ngx_pagesize - 1)
+ & ~((off_t) ngx_pagesize - 1);
+
+ if (aligned <= cl->buf->file_last) {
+ size = aligned - cl->buf->file_pos;
+ }
+ }
+
+ if (fd == cl->buf->file->fd && fprev == cl->buf->file_pos) {
+ sfv->sfv_len += (size_t) size;
+
+ } else {
+ sfv = ngx_array_push(&vec);
+ if (sfv == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ fd = cl->buf->file->fd;
+ sfv->sfv_fd = fd;
+ sfv->sfv_flag = 0;
+ sfv->sfv_off = cl->buf->file_pos;
+ sfv->sfv_len = (size_t) size;
+ }
+
+ fprev = cl->buf->file_pos + size;
+ send += size;
+ }
+ }
+
+ n = sendfilev(c->fd, vec.elts, vec.nelts, &sent);
+
+ if (n == -1) {
+ err = ngx_errno;
+
+ switch (err) {
+ case NGX_EAGAIN:
+ break;
+
+ case NGX_EINTR:
+ eintr = 1;
+ break;
+
+ default:
+ wev->error = 1;
+ ngx_connection_error(c, err, "sendfilev() failed");
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "sendfilev() sent only %uz bytes", sent);
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "sendfilev: %z %z", n, sent);
+
+ if (send - prev_send == (off_t) sent) {
+ complete = 1;
+ }
+
+ c->sent += sent;
+
+ for (cl = in; cl; cl = cl->next) {
+
+ if (ngx_buf_special(cl->buf)) {
+ continue;
+ }
+
+ if (sent == 0) {
+ break;
+ }
+
+ size = ngx_buf_size(cl->buf);
+
+ if ((off_t) sent >= size) {
+ sent = (size_t) ((off_t) sent - size);
+
+ if (ngx_buf_in_memory(cl->buf)) {
+ cl->buf->pos = cl->buf->last;
+ }
+
+ if (cl->buf->in_file) {
+ cl->buf->file_pos = cl->buf->file_last;
+ }
+
+ continue;
+ }
+
+ if (ngx_buf_in_memory(cl->buf)) {
+ cl->buf->pos += sent;
+ }
+
+ if (cl->buf->in_file) {
+ cl->buf->file_pos += sent;
+ }
+
+ break;
+ }
+
+ if (eintr) {
+ continue;
+ }
+
+ if (!complete) {
+ wev->ready = 0;
+ return cl;
+ }
+
+ if (send >= limit || cl == NULL) {
+ return cl;
+ }
+
+ in = cl;
+ }
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_sunpro_amd64.il b/usr.sbin/nginx/src/os/unix/ngx_sunpro_amd64.il
new file mode 100644
index 00000000000..c6519574d70
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_sunpro_amd64.il
@@ -0,0 +1,42 @@
+/
+/ Copyright (C) Igor Sysoev
+/
+
+/ ngx_atomic_uint_t ngx_atomic_cmp_set(ngx_atomic_t *lock,
+/ ngx_atomic_uint_t old, ngx_atomic_uint_t set);
+/
+/ the arguments are passed in %rdi, %rsi, %rdx
+/ the result is returned in the %rax
+
+ .inline ngx_atomic_cmp_set,0
+ movq %rsi, %rax
+ lock
+ cmpxchgq %rdx, (%rdi)
+ setz %al
+ movzbq %al, %rax
+ .end
+
+
+/ ngx_atomic_int_t ngx_atomic_fetch_add(ngx_atomic_t *value,
+/ ngx_atomic_int_t add);
+/
+/ the arguments are passed in %rdi, %rsi
+/ the result is returned in the %rax
+
+ .inline ngx_atomic_fetch_add,0
+ movq %rsi, %rax
+ lock
+ xaddq %rax, (%rdi)
+ .end
+
+
+/ ngx_cpu_pause()
+/
+/ the "rep; nop" is used instead of "pause" to avoid the "[ PAUSE ]" hardware
+/ capability added by linker because Solaris/amd64 does not know about it:
+/
+/ ld.so.1: nginx: fatal: hardware capability unsupported: 0x2000 [ PAUSE ]
+
+ .inline ngx_cpu_pause,0
+ rep; nop
+ .end
diff --git a/usr.sbin/nginx/src/os/unix/ngx_sunpro_atomic_sparc64.h b/usr.sbin/nginx/src/os/unix/ngx_sunpro_atomic_sparc64.h
new file mode 100644
index 00000000000..b12603009ee
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_sunpro_atomic_sparc64.h
@@ -0,0 +1,60 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#if (NGX_PTR_SIZE == 4)
+#define NGX_CASA ngx_casa
+#else
+#define NGX_CASA ngx_casxa
+#endif
+
+
+ngx_atomic_uint_t
+ngx_casa(ngx_atomic_uint_t set, ngx_atomic_uint_t old, ngx_atomic_t *lock);
+
+ngx_atomic_uint_t
+ngx_casxa(ngx_atomic_uint_t set, ngx_atomic_uint_t old, ngx_atomic_t *lock);
+
+/* the code in src/os/unix/ngx_sunpro_sparc64.il */
+
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set)
+{
+ set = NGX_CASA(set, old, lock);
+
+ return (set == old);
+}
+
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+ ngx_atomic_uint_t old, res;
+
+ old = *value;
+
+ for ( ;; ) {
+
+ res = old + add;
+
+ res = NGX_CASA(res, old, value);
+
+ if (res == old) {
+ return res;
+ }
+
+ old = res;
+ }
+}
+
+
+#define ngx_memory_barrier() \
+ __asm (".volatile"); \
+ __asm ("membar #LoadLoad | #LoadStore | #StoreStore | #StoreLoad"); \
+ __asm (".nonvolatile")
+
+#define ngx_cpu_pause()
diff --git a/usr.sbin/nginx/src/os/unix/ngx_sunpro_sparc64.il b/usr.sbin/nginx/src/os/unix/ngx_sunpro_sparc64.il
new file mode 100644
index 00000000000..2dd83204a26
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_sunpro_sparc64.il
@@ -0,0 +1,35 @@
+/
+/ Copyright (C) Igor Sysoev
+/
+
+
+/ "casa [%o2] 0x80, %o1, %o0" and
+/ "casxa [%o2] 0x80, %o1, %o0" do the following:
+/
+/ if ([%o2] == %o1) {
+/ swap(%o0, [%o2]);
+/ } else {
+/ %o0 = [%o2];
+/ }
+
+
+/ ngx_atomic_uint_t ngx_casa(ngx_atomic_uint_t set, ngx_atomic_uint_t old,
+/ ngx_atomic_t *lock);
+/
+/ the arguments are passed in the %o0, %o1, %o2
+/ the result is returned in the %o0
+
+ .inline ngx_casa,0
+ casa [%o2] 0x80, %o1, %o0
+ .end
+
+
+/ ngx_atomic_uint_t ngx_casxa(ngx_atomic_uint_t set, ngx_atomic_uint_t old,
+/ ngx_atomic_t *lock);
+/
+/ the arguments are passed in the %o0, %o1, %o2
+/ the result is returned in the %o0
+
+ .inline ngx_casxa,0
+ casxa [%o2] 0x80, %o1, %o0
+ .end
diff --git a/usr.sbin/nginx/src/os/unix/ngx_sunpro_x86.il b/usr.sbin/nginx/src/os/unix/ngx_sunpro_x86.il
new file mode 100644
index 00000000000..e32ea8cc119
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_sunpro_x86.il
@@ -0,0 +1,43 @@
+/
+/ Copyright (C) Igor Sysoev
+/
+
+/ ngx_atomic_uint_t ngx_atomic_cmp_set(ngx_atomic_t *lock,
+/ ngx_atomic_uint_t old, ngx_atomic_uint_t set);
+/
+/ the arguments are passed on stack (%esp), 4(%esp), 8(%esp)
+
+ .inline ngx_atomic_cmp_set,0
+ movl (%esp), %ecx
+ movl 4(%esp), %eax
+ movl 8(%esp), %edx
+ lock
+ cmpxchgl %edx, (%ecx)
+ setz %al
+ movzbl %al, %eax
+ .end
+
+
+/ ngx_atomic_int_t ngx_atomic_fetch_add(ngx_atomic_t *value,
+/ ngx_atomic_int_t add);
+/
+/ the arguments are passed on stack (%esp), 4(%esp)
+
+ .inline ngx_atomic_fetch_add,0
+ movl (%esp), %ecx
+ movl 4(%esp), %eax
+ lock
+ xaddl %eax, (%ecx)
+ .end
+
+
+/ ngx_cpu_pause()
+/
+/ the "rep; nop" is used instead of "pause" to avoid the "[ PAUSE ]" hardware
+/ capability added by linker because Solaris/i386 does not know about it:
+/
+/ ld.so.1: nginx: fatal: hardware capability unsupported: 0x2000 [ PAUSE ]
+
+ .inline ngx_cpu_pause,0
+ rep; nop
+ .end
diff --git a/usr.sbin/nginx/src/os/unix/ngx_thread.h b/usr.sbin/nginx/src/os/unix/ngx_thread.h
new file mode 100644
index 00000000000..eec297a8cdc
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_thread.h
@@ -0,0 +1,127 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_THREAD_H_INCLUDED_
+#define _NGX_THREAD_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+#if (NGX_THREADS)
+
+#define NGX_MAX_THREADS 128
+
+#if (NGX_USE_RFORK)
+#include <ngx_freebsd_rfork_thread.h>
+
+
+#else /* use pthreads */
+
+#include <pthread.h>
+
+typedef pthread_t ngx_tid_t;
+
+#define ngx_thread_self() pthread_self()
+#define ngx_log_tid (int) ngx_thread_self()
+
+#if (NGX_FREEBSD) && !(NGX_LINUXTHREADS)
+#define NGX_TID_T_FMT "%p"
+#else
+#define NGX_TID_T_FMT "%d"
+#endif
+
+
+typedef pthread_key_t ngx_tls_key_t;
+
+#define ngx_thread_key_create(key) pthread_key_create(key, NULL)
+#define ngx_thread_key_create_n "pthread_key_create()"
+#define ngx_thread_set_tls pthread_setspecific
+#define ngx_thread_set_tls_n "pthread_setspecific()"
+#define ngx_thread_get_tls pthread_getspecific
+
+
+#define NGX_MUTEX_LIGHT 0
+
+typedef struct {
+ pthread_mutex_t mutex;
+ ngx_log_t *log;
+} ngx_mutex_t;
+
+typedef struct {
+ pthread_cond_t cond;
+ ngx_log_t *log;
+} ngx_cond_t;
+
+#define ngx_thread_sigmask pthread_sigmask
+#define ngx_thread_sigmask_n "pthread_sigmask()"
+
+#define ngx_thread_join(t, p) pthread_join(t, p)
+
+#define ngx_setthrtitle(n)
+
+
+
+ngx_int_t ngx_mutex_trylock(ngx_mutex_t *m);
+void ngx_mutex_lock(ngx_mutex_t *m);
+void ngx_mutex_unlock(ngx_mutex_t *m);
+
+#endif
+
+
+#define ngx_thread_volatile volatile
+
+
+typedef struct {
+ ngx_tid_t tid;
+ ngx_cond_t *cv;
+ ngx_uint_t state;
+} ngx_thread_t;
+
+#define NGX_THREAD_FREE 1
+#define NGX_THREAD_BUSY 2
+#define NGX_THREAD_EXIT 3
+#define NGX_THREAD_DONE 4
+
+extern ngx_int_t ngx_threads_n;
+extern volatile ngx_thread_t ngx_threads[NGX_MAX_THREADS];
+
+
+typedef void * ngx_thread_value_t;
+
+ngx_int_t ngx_init_threads(int n, size_t size, ngx_cycle_t *cycle);
+ngx_err_t ngx_create_thread(ngx_tid_t *tid,
+ ngx_thread_value_t (*func)(void *arg), void *arg, ngx_log_t *log);
+
+ngx_mutex_t *ngx_mutex_init(ngx_log_t *log, ngx_uint_t flags);
+void ngx_mutex_destroy(ngx_mutex_t *m);
+
+
+ngx_cond_t *ngx_cond_init(ngx_log_t *log);
+void ngx_cond_destroy(ngx_cond_t *cv);
+ngx_int_t ngx_cond_wait(ngx_cond_t *cv, ngx_mutex_t *m);
+ngx_int_t ngx_cond_signal(ngx_cond_t *cv);
+
+
+#else /* !NGX_THREADS */
+
+#define ngx_thread_volatile
+
+#define ngx_log_tid 0
+#define NGX_TID_T_FMT "%d"
+
+#define ngx_mutex_trylock(m) NGX_OK
+#define ngx_mutex_lock(m)
+#define ngx_mutex_unlock(m)
+
+#define ngx_cond_signal(cv)
+
+#define ngx_thread_main() 1
+
+#endif
+
+
+#endif /* _NGX_THREAD_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_time.c b/usr.sbin/nginx/src/os/unix/ngx_time.c
new file mode 100644
index 00000000000..4ca8be67249
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_time.c
@@ -0,0 +1,103 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ * FreeBSD does not test /etc/localtime change, however, we can workaround it
+ * by calling tzset() with TZ and then without TZ to update timezone.
+ * The trick should work since FreeBSD 2.1.0.
+ *
+ * Linux does not test /etc/localtime change in localtime(),
+ * but may stat("/etc/localtime") several times in every strftime(),
+ * therefore we use it to update timezone.
+ *
+ * Solaris does not test /etc/TIMEZONE change too and no workaround available.
+ */
+
+void
+ngx_timezone_update(void)
+{
+#if (NGX_FREEBSD)
+
+ if (getenv("TZ")) {
+ return;
+ }
+
+ putenv("TZ=UTC");
+
+ tzset();
+
+ unsetenv("TZ");
+
+ tzset();
+
+#elif (NGX_LINUX)
+ time_t s;
+ struct tm *t;
+ char buf[4];
+
+ s = time(0);
+
+ t = localtime(&s);
+
+ strftime(buf, 4, "%H", t);
+
+#endif
+}
+
+
+void
+ngx_localtime(time_t s, ngx_tm_t *tm)
+{
+#if (NGX_HAVE_LOCALTIME_R)
+ (void) localtime_r(&s, tm);
+
+#else
+ ngx_tm_t *t;
+
+ t = localtime(&s);
+ *tm = *t;
+
+#endif
+
+ tm->ngx_tm_mon++;
+ tm->ngx_tm_year += 1900;
+}
+
+
+void
+ngx_libc_localtime(time_t s, struct tm *tm)
+{
+#if (NGX_HAVE_LOCALTIME_R)
+ (void) localtime_r(&s, tm);
+
+#else
+ struct tm *t;
+
+ t = localtime(&s);
+ *tm = *t;
+
+#endif
+}
+
+
+void
+ngx_libc_gmtime(time_t s, struct tm *tm)
+{
+#if (NGX_HAVE_LOCALTIME_R)
+ (void) gmtime_r(&s, tm);
+
+#else
+ struct tm *t;
+
+ t = gmtime(&s);
+ *tm = *t;
+
+#endif
+}
diff --git a/usr.sbin/nginx/src/os/unix/ngx_time.h b/usr.sbin/nginx/src/os/unix/ngx_time.h
new file mode 100644
index 00000000000..5d9406cdebc
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_time.h
@@ -0,0 +1,65 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_TIME_H_INCLUDED_
+#define _NGX_TIME_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef ngx_rbtree_key_t ngx_msec_t;
+typedef ngx_rbtree_key_int_t ngx_msec_int_t;
+
+typedef struct tm ngx_tm_t;
+
+#define ngx_tm_sec tm_sec
+#define ngx_tm_min tm_min
+#define ngx_tm_hour tm_hour
+#define ngx_tm_mday tm_mday
+#define ngx_tm_mon tm_mon
+#define ngx_tm_year tm_year
+#define ngx_tm_wday tm_wday
+#define ngx_tm_isdst tm_isdst
+
+#define ngx_tm_sec_t int
+#define ngx_tm_min_t int
+#define ngx_tm_hour_t int
+#define ngx_tm_mday_t int
+#define ngx_tm_mon_t int
+#define ngx_tm_year_t int
+#define ngx_tm_wday_t int
+
+
+#if (NGX_HAVE_GMTOFF)
+#define ngx_tm_gmtoff tm_gmtoff
+#define ngx_tm_zone tm_zone
+#endif
+
+
+#if (NGX_SOLARIS)
+
+#define ngx_timezone(isdst) (- (isdst ? altzone : timezone) / 60)
+
+#else
+
+#define ngx_timezone(isdst) (- (isdst ? timezone + 3600 : timezone) / 60)
+
+#endif
+
+
+void ngx_timezone_update(void);
+void ngx_localtime(time_t s, ngx_tm_t *tm);
+void ngx_libc_localtime(time_t s, struct tm *tm);
+void ngx_libc_gmtime(time_t s, struct tm *tm);
+
+#define ngx_gettimeofday(tp) (void) gettimeofday(tp, NULL);
+#define ngx_msleep(ms) (void) usleep(ms * 1000)
+#define ngx_sleep(s) (void) sleep(s)
+
+
+#endif /* _NGX_TIME_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_udp_recv.c b/usr.sbin/nginx/src/os/unix/ngx_udp_recv.c
new file mode 100644
index 00000000000..fdcd7fa79ca
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_udp_recv.c
@@ -0,0 +1,114 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (NGX_HAVE_KQUEUE)
+
+ssize_t
+ngx_udp_unix_recv(ngx_connection_t *c, u_char *buf, size_t size)
+{
+ ssize_t n;
+ ngx_err_t err;
+ ngx_event_t *rev;
+
+ rev = c->read;
+
+ do {
+ n = recv(c->fd, buf, size, 0);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "recv: fd:%d %d of %d", c->fd, n, size);
+
+ if (n >= 0) {
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+ rev->available -= n;
+
+ /*
+ * rev->available may be negative here because some additional
+ * bytes may be received between kevent() and recv()
+ */
+
+ if (rev->available <= 0) {
+ rev->ready = 0;
+ rev->available = 0;
+ }
+ }
+
+ return n;
+ }
+
+ err = ngx_socket_errno;
+
+ if (err == NGX_EAGAIN || err == NGX_EINTR) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "recv() not ready");
+ n = NGX_AGAIN;
+
+ } else {
+ n = ngx_connection_error(c, err, "recv() failed");
+ break;
+ }
+
+ } while (err == NGX_EINTR);
+
+ rev->ready = 0;
+
+ if (n == NGX_ERROR) {
+ rev->error = 1;
+ }
+
+ return n;
+}
+
+#else /* ! NGX_HAVE_KQUEUE */
+
+ssize_t
+ngx_udp_unix_recv(ngx_connection_t *c, u_char *buf, size_t size)
+{
+ ssize_t n;
+ ngx_err_t err;
+ ngx_event_t *rev;
+
+ rev = c->read;
+
+ do {
+ n = recv(c->fd, buf, size, 0);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "recv: fd:%d %d of %d", c->fd, n, size);
+
+ if (n >= 0) {
+ return n;
+ }
+
+ err = ngx_socket_errno;
+
+ if (err == NGX_EAGAIN || err == NGX_EINTR) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "recv() not ready");
+ n = NGX_AGAIN;
+
+ } else {
+ n = ngx_connection_error(c, err, "recv() failed");
+ break;
+ }
+
+ } while (err == NGX_EINTR);
+
+ rev->ready = 0;
+
+ if (n == NGX_ERROR) {
+ rev->error = 1;
+ }
+
+ return n;
+}
+
+#endif /* NGX_HAVE_KQUEUE */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_user.c b/usr.sbin/nginx/src/os/unix/ngx_user.c
new file mode 100644
index 00000000000..c46fb2bfd8d
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_user.c
@@ -0,0 +1,108 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ * Solaris has thread-safe crypt()
+ * Linux has crypt_r(); "struct crypt_data" is more than 128K
+ * FreeBSD needs the mutex to protect crypt()
+ *
+ * TODO:
+ * ngx_crypt_init() to init mutex
+ */
+
+
+#if (NGX_CRYPT)
+
+#if (NGX_HAVE_GNU_CRYPT_R)
+
+ngx_int_t
+ngx_libc_crypt(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted)
+{
+ char *value;
+ size_t len;
+ ngx_err_t err;
+ struct crypt_data cd;
+
+ ngx_set_errno(0);
+
+ cd.initialized = 0;
+ /* work around the glibc bug */
+ cd.current_salt[0] = ~salt[0];
+
+ value = crypt_r((char *) key, (char *) salt, &cd);
+
+ err = ngx_errno;
+
+ if (err == 0) {
+ len = ngx_strlen(value) + 1;
+
+ *encrypted = ngx_pnalloc(pool, len);
+ if (*encrypted) {
+ ngx_memcpy(*encrypted, value, len);
+ return NGX_OK;
+ }
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, pool->log, err, "crypt_r() failed");
+
+ return NGX_ERROR;
+}
+
+#else
+
+ngx_int_t
+ngx_libc_crypt(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted)
+{
+ char *value;
+ size_t len;
+ ngx_err_t err;
+
+#if (NGX_THREADS && NGX_NONREENTRANT_CRYPT)
+
+ /* crypt() is a time consuming funtion, so we only try to lock */
+
+ if (ngx_mutex_trylock(ngx_crypt_mutex) != NGX_OK) {
+ return NGX_AGAIN;
+ }
+
+#endif
+
+ ngx_set_errno(0);
+
+ value = crypt((char *) key, (char *) salt);
+
+ if (value) {
+ len = ngx_strlen(value) + 1;
+
+ *encrypted = ngx_pnalloc(pool, len);
+ if (*encrypted) {
+ ngx_memcpy(*encrypted, value, len);
+ }
+
+#if (NGX_THREADS && NGX_NONREENTRANT_CRYPT)
+ ngx_mutex_unlock(ngx_crypt_mutex);
+#endif
+ return NGX_OK;
+ }
+
+ err = ngx_errno;
+
+#if (NGX_THREADS && NGX_NONREENTRANT_CRYPT)
+ ngx_mutex_unlock(ngx_crypt_mutex);
+#endif
+
+ ngx_log_error(NGX_LOG_CRIT, pool->log, err, "crypt() failed");
+
+ return NGX_ERROR;
+}
+
+#endif
+
+#endif /* NGX_CRYPT */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_user.h b/usr.sbin/nginx/src/os/unix/ngx_user.h
new file mode 100644
index 00000000000..68d1c6e5f05
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_user.h
@@ -0,0 +1,23 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_USER_H_INCLUDED_
+#define _NGX_USER_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef uid_t ngx_uid_t;
+typedef gid_t ngx_gid_t;
+
+
+ngx_int_t ngx_libc_crypt(ngx_pool_t *pool, u_char *key, u_char *salt,
+ u_char **encrypted);
+
+
+#endif /* _NGX_USER_H_INCLUDED_ */
diff --git a/usr.sbin/nginx/src/os/unix/ngx_writev_chain.c b/usr.sbin/nginx/src/os/unix/ngx_writev_chain.c
new file mode 100644
index 00000000000..695cb49789a
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/ngx_writev_chain.c
@@ -0,0 +1,180 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (IOV_MAX > 64)
+#define NGX_IOVS 64
+#else
+#define NGX_IOVS IOV_MAX
+#endif
+
+
+ngx_chain_t *
+ngx_writev_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+ u_char *prev;
+ ssize_t n, size, sent;
+ off_t send, prev_send;
+ ngx_uint_t eintr, complete;
+ ngx_err_t err;
+ ngx_array_t vec;
+ ngx_chain_t *cl;
+ ngx_event_t *wev;
+ struct iovec *iov, iovs[NGX_IOVS];
+
+ wev = c->write;
+
+ if (!wev->ready) {
+ return in;
+ }
+
+#if (NGX_HAVE_KQUEUE)
+
+ if ((ngx_event_flags & NGX_USE_KQUEUE_EVENT) && wev->pending_eof) {
+ (void) ngx_connection_error(c, wev->kq_errno,
+ "kevent() reported about an closed connection");
+ wev->error = 1;
+ return NGX_CHAIN_ERROR;
+ }
+
+#endif
+
+ /* the maximum limit size is the maximum size_t value - the page size */
+
+ if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
+ limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
+ }
+
+ send = 0;
+ complete = 0;
+
+ vec.elts = iovs;
+ vec.size = sizeof(struct iovec);
+ vec.nalloc = NGX_IOVS;
+ vec.pool = c->pool;
+
+ for ( ;; ) {
+ prev = NULL;
+ iov = NULL;
+ eintr = 0;
+ prev_send = send;
+
+ vec.nelts = 0;
+
+ /* create the iovec and coalesce the neighbouring bufs */
+
+ for (cl = in; cl && vec.nelts < IOV_MAX && send < limit; cl = cl->next)
+ {
+ if (ngx_buf_special(cl->buf)) {
+ continue;
+ }
+
+#if 1
+ if (!ngx_buf_in_memory(cl->buf)) {
+ ngx_debug_point();
+ }
+#endif
+
+ size = cl->buf->last - cl->buf->pos;
+
+ if (send + size > limit) {
+ size = (ssize_t) (limit - send);
+ }
+
+ if (prev == cl->buf->pos) {
+ iov->iov_len += size;
+
+ } else {
+ iov = ngx_array_push(&vec);
+ if (iov == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ iov->iov_base = (void *) cl->buf->pos;
+ iov->iov_len = size;
+ }
+
+ prev = cl->buf->pos + size;
+ send += size;
+ }
+
+ n = writev(c->fd, vec.elts, vec.nelts);
+
+ if (n == -1) {
+ err = ngx_errno;
+
+ switch (err) {
+ case NGX_EAGAIN:
+ break;
+
+ case NGX_EINTR:
+ eintr = 1;
+ break;
+
+ default:
+ wev->error = 1;
+ (void) ngx_connection_error(c, err, "writev() failed");
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "writev() not ready");
+ }
+
+ sent = n > 0 ? n : 0;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "writev: %z", sent);
+
+ if (send - prev_send == sent) {
+ complete = 1;
+ }
+
+ c->sent += sent;
+
+ for (cl = in; cl; cl = cl->next) {
+
+ if (ngx_buf_special(cl->buf)) {
+ continue;
+ }
+
+ if (sent == 0) {
+ break;
+ }
+
+ size = cl->buf->last - cl->buf->pos;
+
+ if (sent >= size) {
+ sent -= size;
+ cl->buf->pos = cl->buf->last;
+
+ continue;
+ }
+
+ cl->buf->pos += sent;
+
+ break;
+ }
+
+ if (eintr) {
+ continue;
+ }
+
+ if (!complete) {
+ wev->ready = 0;
+ return cl;
+ }
+
+ if (send >= limit || cl == NULL) {
+ return cl;
+ }
+
+ in = cl;
+ }
+}
diff --git a/usr.sbin/nginx/src/os/unix/rfork_thread.S b/usr.sbin/nginx/src/os/unix/rfork_thread.S
new file mode 100644
index 00000000000..161007d6be3
--- /dev/null
+++ b/usr.sbin/nginx/src/os/unix/rfork_thread.S
@@ -0,0 +1,72 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <sys/syscall.h>
+#include <machine/asm.h>
+
+/*
+ * rfork_thread(3) - rfork_thread(flags, stack, func, arg);
+ */
+
+#define KERNCALL int $0x80
+
+ENTRY(rfork_thread)
+ push %ebp
+ mov %esp, %ebp
+ push %esi
+
+ mov 12(%ebp), %esi # the thread stack address
+
+ sub $4, %esi
+ mov 20(%ebp), %eax # the thread argument
+ mov %eax, (%esi)
+
+ sub $4, %esi
+ mov 16(%ebp), %eax # the thread start address
+ mov %eax, (%esi)
+
+ push 8(%ebp) # rfork(2) flags
+ push $0
+ mov $SYS_rfork, %eax
+ KERNCALL
+ jc error
+
+ cmp $0, %edx
+ jne child
+
+parent:
+ add $8, %esp
+ pop %esi
+ leave
+ ret
+
+child:
+ mov %esi, %esp
+ pop %eax
+ call *%eax # call a thread start address ...
+ add $4, %esp
+
+ push %eax
+ push $0
+ mov $SYS_exit, %eax # ... and exit(2) after a thread would return
+ KERNCALL
+
+error:
+ add $8, %esp
+ pop %esi
+ leave
+ PIC_PROLOGUE
+
+ /* libc's cerror: jmp PIC_PLT(HIDENAME(cerror)) */
+
+ push %eax
+ call PIC_PLT(CNAME(__error))
+ pop %ecx
+ PIC_EPILOGUE
+ mov %ecx, (%eax)
+ mov $-1, %eax
+ mov $-1, %edx
+ ret
diff --git a/usr.sbin/nginx/src/pcre/LICENCE b/usr.sbin/nginx/src/pcre/LICENCE
new file mode 100644
index 00000000000..65a7ec75343
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/LICENCE
@@ -0,0 +1,68 @@
+PCRE LICENCE
+------------
+
+PCRE is a library of functions to support regular expressions whose syntax
+and semantics are as close as possible to those of the Perl 5 language.
+
+Release 8 of PCRE is distributed under the terms of the "BSD" licence, as
+specified below. The documentation for PCRE, supplied in the "doc"
+directory, is distributed under the same terms as the software itself.
+
+The basic library functions are written in C and are freestanding. Also
+included in the distribution is a set of C++ wrapper functions.
+
+
+THE BASIC LIBRARY FUNCTIONS
+---------------------------
+
+Written by: Philip Hazel
+Email local part: ph10
+Email domain: cam.ac.uk
+
+University of Cambridge Computing Service,
+Cambridge, England.
+
+Copyright (c) 1997-2011 University of Cambridge
+All rights reserved.
+
+
+THE C++ WRAPPER FUNCTIONS
+-------------------------
+
+Contributed by: Google Inc.
+
+Copyright (c) 2007-2011, Google Inc.
+All rights reserved.
+
+
+THE "BSD" LICENCE
+-----------------
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the University of Cambridge nor the name of Google
+ Inc. nor the names of their contributors may be used to endorse or
+ promote products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+End
diff --git a/usr.sbin/nginx/src/pcre/config.h b/usr.sbin/nginx/src/pcre/config.h
new file mode 100644
index 00000000000..25bd8064d84
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/config.h
@@ -0,0 +1,262 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+
+/* On Unix-like systems config.h.in is converted by "configure" into config.h.
+Some other environments also support the use of "configure". PCRE is written in
+Standard C, but there are a few non-standard things it can cope with, allowing
+it to run on SunOS4 and other "close to standard" systems.
+
+If you are going to build PCRE "by hand" on a system without "configure" you
+should copy the distributed config.h.generic to config.h, and then set up the
+macro definitions the way you need them. You must then add -DHAVE_CONFIG_H to
+all of your compile commands, so that config.h is included at the start of
+every source.
+
+Alternatively, you can avoid editing by using -D on the compiler command line
+to set the macro values. In this case, you do not have to set -DHAVE_CONFIG_H.
+
+PCRE uses memmove() if HAVE_MEMMOVE is set to 1; otherwise it uses bcopy() if
+HAVE_BCOPY is set to 1. If your system has neither bcopy() nor memmove(), set
+them both to 0; an emulation function will be used. */
+
+/* By default, the \R escape sequence matches any Unicode line ending
+ character or sequence of characters. If BSR_ANYCRLF is defined, this is
+ changed so that backslash-R matches only CR, LF, or CRLF. The build- time
+ default can be overridden by the user of PCRE at runtime. On systems that
+ support it, "configure" can be used to override the default. */
+/* #undef BSR_ANYCRLF */
+
+/* If you are compiling for a system that uses EBCDIC instead of ASCII
+ character codes, define this macro as 1. On systems that can use
+ "configure", this can be done via --enable-ebcdic. PCRE will then assume
+ that all input strings are in EBCDIC. If you do not define this macro, PCRE
+ will assume input strings are ASCII or UTF-8 Unicode. It is not possible to
+ build a version of PCRE that supports both EBCDIC and UTF-8. */
+/* #undef EBCDIC */
+
+/* Define to 1 if you have the `bcopy' function. */
+#define HAVE_BCOPY 1
+
+/* Define to 1 if you have the <bits/type_traits.h> header file. */
+/* #undef HAVE_BITS_TYPE_TRAITS_H */
+
+/* Define to 1 if you have the <bzlib.h> header file. */
+/* #undef HAVE_BZLIB_H */
+
+/* Define to 1 if you have the <dirent.h> header file. */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if the system has the type `long long'. */
+#define HAVE_LONG_LONG 1
+
+/* Define to 1 if you have the `memmove' function. */
+#define HAVE_MEMMOVE 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the <readline/history.h> header file. */
+#define HAVE_READLINE_HISTORY_H 1
+
+/* Define to 1 if you have the <readline/readline.h> header file. */
+#define HAVE_READLINE_READLINE_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the <string> header file. */
+/* #undef HAVE_STRING */
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have `strtoimax'. */
+/* #undef HAVE_STRTOIMAX */
+
+/* Define to 1 if you have `strtoll'. */
+/* #undef HAVE_STRTOLL */
+
+/* Define to 1 if you have `strtoq'. */
+/* #undef HAVE_STRTOQ */
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <type_traits.h> header file. */
+/* #undef HAVE_TYPE_TRAITS_H */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if the system has the type `unsigned long long'. */
+#define HAVE_UNSIGNED_LONG_LONG 1
+
+/* Define to 1 if you have the <windows.h> header file. */
+/* #undef HAVE_WINDOWS_H */
+
+/* Define to 1 if you have the <zlib.h> header file. */
+#define HAVE_ZLIB_H 1
+
+/* Define to 1 if you have `_strtoi64'. */
+/* #undef HAVE__STRTOI64 */
+
+/* The value of LINK_SIZE determines the number of bytes used to store links
+ as offsets within the compiled regex. The default is 2, which allows for
+ compiled patterns up to 64K long. This covers the vast majority of cases.
+ However, PCRE can also be compiled to use 3 or 4 bytes instead. This allows
+ for longer patterns in extreme cases. On systems that support it,
+ "configure" can be used to override this default. */
+#define LINK_SIZE 2
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+ */
+#define LT_OBJDIR ".libs/"
+
+/* The value of MATCH_LIMIT determines the default number of times the
+ internal match() function can be called during a single execution of
+ pcre_exec(). There is a runtime interface for setting a different limit.
+ The limit exists in order to catch runaway regular expressions that take
+ for ever to determine that they do not match. The default is set very large
+ so that it does not accidentally catch legitimate cases. On systems that
+ support it, "configure" can be used to override this default default. */
+#define MATCH_LIMIT 10000000
+
+/* The above limit applies to all calls of match(), whether or not they
+ increase the recursion depth. In some environments it is desirable to limit
+ the depth of recursive calls of match() more strictly, in order to restrict
+ the maximum amount of stack (or heap, if NO_RECURSE is defined) that is
+ used. The value of MATCH_LIMIT_RECURSION applies only to recursive calls of
+ match(). To have any useful effect, it must be less than the value of
+ MATCH_LIMIT. The default is to use the same value as MATCH_LIMIT. There is
+ a runtime method for setting a different limit. On systems that support it,
+ "configure" can be used to override the default. */
+#define MATCH_LIMIT_RECURSION MATCH_LIMIT
+
+/* This limit is parameterized just in case anybody ever wants to change it.
+ Care must be taken if it is increased, because it guards against integer
+ overflow caused by enormously large patterns. */
+#define MAX_NAME_COUNT 10000
+
+/* This limit is parameterized just in case anybody ever wants to change it.
+ Care must be taken if it is increased, because it guards against integer
+ overflow caused by enormously large patterns. */
+#define MAX_NAME_SIZE 32
+
+/* The value of NEWLINE determines the newline character sequence. On systems
+ that support it, "configure" can be used to override the default, which is
+ 10. The possible values are 10 (LF), 13 (CR), 3338 (CRLF), -1 (ANY), or -2
+ (ANYCRLF). */
+#define NEWLINE 10
+
+/* PCRE uses recursive function calls to handle backtracking while matching.
+ This can sometimes be a problem on systems that have stacks of limited
+ size. Define NO_RECURSE to get a version that doesn't use recursion in the
+ match() function; instead it creates its own stack by steam using
+ pcre_recurse_malloc() to obtain memory from the heap. For more detail, see
+ the comments and other stuff just above the match() function. On systems
+ that support it, "configure" can be used to set this in the Makefile (use
+ --disable-stack-for-recursion). */
+/* #undef NO_RECURSE */
+
+/* Name of package */
+#define PACKAGE "pcre"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "PCRE"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "PCRE 8.13"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "pcre"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "8.13"
+
+/* If you are compiling for a system other than a Unix-like system or
+ Win32, and it needs some magic to be inserted before the definition
+ of a function that is exported by the library, define this macro to
+ contain the relevant magic. If you do not define this macro, it
+ defaults to "extern" for a C compiler and "extern C" for a C++
+ compiler on non-Win32 systems. This macro apears at the start of
+ every exported function that is part of the external API. It does
+ not appear on functions that are "external" in the C sense, but
+ which are internal to the library. */
+/* #undef PCRE_EXP_DEFN */
+
+/* Define if linking statically (TODO: make nice with Libtool) */
+/* #undef PCRE_STATIC */
+
+/* When calling PCRE via the POSIX interface, additional working storage is
+ required for holding the pointers to capturing substrings because PCRE
+ requires three integers per substring, whereas the POSIX interface provides
+ only two. If the number of expected substrings is small, the wrapper
+ function uses space on the stack, because this is faster than using
+ malloc() for each call. The threshold above which the stack is no longer
+ used is defined by POSIX_MALLOC_THRESHOLD. On systems that support it,
+ "configure" can be used to override this default. */
+#define POSIX_MALLOC_THRESHOLD 10
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define to allow pcregrep to be linked with libbz2, so that it is able to
+ handle .bz2 files. */
+/* #undef SUPPORT_LIBBZ2 */
+
+/* Define to allow pcretest to be linked with libreadline. */
+/* #undef SUPPORT_LIBREADLINE */
+
+/* Define to allow pcregrep to be linked with libz, so that it is able to
+ handle .gz files. */
+/* #undef SUPPORT_LIBZ */
+
+/* Define to enable support for Unicode properties */
+#define SUPPORT_UCP /**/
+
+/* Define to enable support for the UTF-8 Unicode encoding. This will work
+ even in an EBCDIC environment, but it is incompatible with the EBCDIC
+ macro. That is, PCRE can support *either* EBCDIC code *or* ASCII/UTF-8, but
+ not both at once. */
+#define SUPPORT_UTF8 /**/
+
+/* Version number of package */
+#define VERSION "8.13"
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to the type of a signed integer type of width exactly 64 bits if
+ such a type exists and the standard includes do not define it. */
+/* #undef int64_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */
diff --git a/usr.sbin/nginx/src/pcre/pcre.h b/usr.sbin/nginx/src/pcre/pcre.h
new file mode 100644
index 00000000000..20d6c0b914f
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/pcre.h
@@ -0,0 +1,346 @@
+/*************************************************
+* Perl-Compatible Regular Expressions *
+*************************************************/
+
+/* This is the public header file for the PCRE library, to be #included by
+applications that call the PCRE functions.
+
+ Copyright (c) 1997-2011 University of Cambridge
+
+-----------------------------------------------------------------------------
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the University of Cambridge nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+-----------------------------------------------------------------------------
+*/
+
+#ifndef _PCRE_H
+#define _PCRE_H
+
+/* The current PCRE version information. */
+
+#define PCRE_MAJOR 8
+#define PCRE_MINOR 13
+#define PCRE_PRERELEASE
+#define PCRE_DATE 2011-08-16
+
+/* When an application links to a PCRE DLL in Windows, the symbols that are
+imported have to be identified as such. When building PCRE, the appropriate
+export setting is defined in pcre_internal.h, which includes this file. So we
+don't change existing definitions of PCRE_EXP_DECL and PCRECPP_EXP_DECL. */
+
+#if defined(_WIN32) && !defined(PCRE_STATIC)
+# ifndef PCRE_EXP_DECL
+# define PCRE_EXP_DECL extern __declspec(dllimport)
+# endif
+# ifdef __cplusplus
+# ifndef PCRECPP_EXP_DECL
+# define PCRECPP_EXP_DECL extern __declspec(dllimport)
+# endif
+# ifndef PCRECPP_EXP_DEFN
+# define PCRECPP_EXP_DEFN __declspec(dllimport)
+# endif
+# endif
+#endif
+
+/* By default, we use the standard "extern" declarations. */
+
+#ifndef PCRE_EXP_DECL
+# ifdef __cplusplus
+# define PCRE_EXP_DECL extern "C"
+# else
+# define PCRE_EXP_DECL extern
+# endif
+#endif
+
+#ifdef __cplusplus
+# ifndef PCRECPP_EXP_DECL
+# define PCRECPP_EXP_DECL extern
+# endif
+# ifndef PCRECPP_EXP_DEFN
+# define PCRECPP_EXP_DEFN
+# endif
+#endif
+
+/* Have to include stdlib.h in order to ensure that size_t is defined;
+it is needed here for malloc. */
+
+#include <stdlib.h>
+
+/* Allow for C++ users */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Options. Some are compile-time only, some are run-time only, and some are
+both, so we keep them all distinct. However, almost all the bits in the options
+word are now used. In the long run, we may have to re-use some of the
+compile-time only bits for runtime options, or vice versa. */
+
+#define PCRE_CASELESS 0x00000001 /* Compile */
+#define PCRE_MULTILINE 0x00000002 /* Compile */
+#define PCRE_DOTALL 0x00000004 /* Compile */
+#define PCRE_EXTENDED 0x00000008 /* Compile */
+#define PCRE_ANCHORED 0x00000010 /* Compile, exec, DFA exec */
+#define PCRE_DOLLAR_ENDONLY 0x00000020 /* Compile */
+#define PCRE_EXTRA 0x00000040 /* Compile */
+#define PCRE_NOTBOL 0x00000080 /* Exec, DFA exec */
+#define PCRE_NOTEOL 0x00000100 /* Exec, DFA exec */
+#define PCRE_UNGREEDY 0x00000200 /* Compile */
+#define PCRE_NOTEMPTY 0x00000400 /* Exec, DFA exec */
+#define PCRE_UTF8 0x00000800 /* Compile */
+#define PCRE_NO_AUTO_CAPTURE 0x00001000 /* Compile */
+#define PCRE_NO_UTF8_CHECK 0x00002000 /* Compile, exec, DFA exec */
+#define PCRE_AUTO_CALLOUT 0x00004000 /* Compile */
+#define PCRE_PARTIAL_SOFT 0x00008000 /* Exec, DFA exec */
+#define PCRE_PARTIAL 0x00008000 /* Backwards compatible synonym */
+#define PCRE_DFA_SHORTEST 0x00010000 /* DFA exec */
+#define PCRE_DFA_RESTART 0x00020000 /* DFA exec */
+#define PCRE_FIRSTLINE 0x00040000 /* Compile */
+#define PCRE_DUPNAMES 0x00080000 /* Compile */
+#define PCRE_NEWLINE_CR 0x00100000 /* Compile, exec, DFA exec */
+#define PCRE_NEWLINE_LF 0x00200000 /* Compile, exec, DFA exec */
+#define PCRE_NEWLINE_CRLF 0x00300000 /* Compile, exec, DFA exec */
+#define PCRE_NEWLINE_ANY 0x00400000 /* Compile, exec, DFA exec */
+#define PCRE_NEWLINE_ANYCRLF 0x00500000 /* Compile, exec, DFA exec */
+#define PCRE_BSR_ANYCRLF 0x00800000 /* Compile, exec, DFA exec */
+#define PCRE_BSR_UNICODE 0x01000000 /* Compile, exec, DFA exec */
+#define PCRE_JAVASCRIPT_COMPAT 0x02000000 /* Compile */
+#define PCRE_NO_START_OPTIMIZE 0x04000000 /* Compile, exec, DFA exec */
+#define PCRE_NO_START_OPTIMISE 0x04000000 /* Synonym */
+#define PCRE_PARTIAL_HARD 0x08000000 /* Exec, DFA exec */
+#define PCRE_NOTEMPTY_ATSTART 0x10000000 /* Exec, DFA exec */
+#define PCRE_UCP 0x20000000 /* Compile */
+
+/* Exec-time and get/set-time error codes */
+
+#define PCRE_ERROR_NOMATCH (-1)
+#define PCRE_ERROR_NULL (-2)
+#define PCRE_ERROR_BADOPTION (-3)
+#define PCRE_ERROR_BADMAGIC (-4)
+#define PCRE_ERROR_UNKNOWN_OPCODE (-5)
+#define PCRE_ERROR_UNKNOWN_NODE (-5) /* For backward compatibility */
+#define PCRE_ERROR_NOMEMORY (-6)
+#define PCRE_ERROR_NOSUBSTRING (-7)
+#define PCRE_ERROR_MATCHLIMIT (-8)
+#define PCRE_ERROR_CALLOUT (-9) /* Never used by PCRE itself */
+#define PCRE_ERROR_BADUTF8 (-10)
+#define PCRE_ERROR_BADUTF8_OFFSET (-11)
+#define PCRE_ERROR_PARTIAL (-12)
+#define PCRE_ERROR_BADPARTIAL (-13)
+#define PCRE_ERROR_INTERNAL (-14)
+#define PCRE_ERROR_BADCOUNT (-15)
+#define PCRE_ERROR_DFA_UITEM (-16)
+#define PCRE_ERROR_DFA_UCOND (-17)
+#define PCRE_ERROR_DFA_UMLIMIT (-18)
+#define PCRE_ERROR_DFA_WSSIZE (-19)
+#define PCRE_ERROR_DFA_RECURSE (-20)
+#define PCRE_ERROR_RECURSIONLIMIT (-21)
+#define PCRE_ERROR_NULLWSLIMIT (-22) /* No longer actually used */
+#define PCRE_ERROR_BADNEWLINE (-23)
+#define PCRE_ERROR_BADOFFSET (-24)
+#define PCRE_ERROR_SHORTUTF8 (-25)
+#define PCRE_ERROR_RECURSELOOP (-26)
+
+/* Specific error codes for UTF-8 validity checks */
+
+#define PCRE_UTF8_ERR0 0
+#define PCRE_UTF8_ERR1 1
+#define PCRE_UTF8_ERR2 2
+#define PCRE_UTF8_ERR3 3
+#define PCRE_UTF8_ERR4 4
+#define PCRE_UTF8_ERR5 5
+#define PCRE_UTF8_ERR6 6
+#define PCRE_UTF8_ERR7 7
+#define PCRE_UTF8_ERR8 8
+#define PCRE_UTF8_ERR9 9
+#define PCRE_UTF8_ERR10 10
+#define PCRE_UTF8_ERR11 11
+#define PCRE_UTF8_ERR12 12
+#define PCRE_UTF8_ERR13 13
+#define PCRE_UTF8_ERR14 14
+#define PCRE_UTF8_ERR15 15
+#define PCRE_UTF8_ERR16 16
+#define PCRE_UTF8_ERR17 17
+#define PCRE_UTF8_ERR18 18
+#define PCRE_UTF8_ERR19 19
+#define PCRE_UTF8_ERR20 20
+#define PCRE_UTF8_ERR21 21
+
+/* Request types for pcre_fullinfo() */
+
+#define PCRE_INFO_OPTIONS 0
+#define PCRE_INFO_SIZE 1
+#define PCRE_INFO_CAPTURECOUNT 2
+#define PCRE_INFO_BACKREFMAX 3
+#define PCRE_INFO_FIRSTBYTE 4
+#define PCRE_INFO_FIRSTCHAR 4 /* For backwards compatibility */
+#define PCRE_INFO_FIRSTTABLE 5
+#define PCRE_INFO_LASTLITERAL 6
+#define PCRE_INFO_NAMEENTRYSIZE 7
+#define PCRE_INFO_NAMECOUNT 8
+#define PCRE_INFO_NAMETABLE 9
+#define PCRE_INFO_STUDYSIZE 10
+#define PCRE_INFO_DEFAULT_TABLES 11
+#define PCRE_INFO_OKPARTIAL 12
+#define PCRE_INFO_JCHANGED 13
+#define PCRE_INFO_HASCRORLF 14
+#define PCRE_INFO_MINLENGTH 15
+
+/* Request types for pcre_config(). Do not re-arrange, in order to remain
+compatible. */
+
+#define PCRE_CONFIG_UTF8 0
+#define PCRE_CONFIG_NEWLINE 1
+#define PCRE_CONFIG_LINK_SIZE 2
+#define PCRE_CONFIG_POSIX_MALLOC_THRESHOLD 3
+#define PCRE_CONFIG_MATCH_LIMIT 4
+#define PCRE_CONFIG_STACKRECURSE 5
+#define PCRE_CONFIG_UNICODE_PROPERTIES 6
+#define PCRE_CONFIG_MATCH_LIMIT_RECURSION 7
+#define PCRE_CONFIG_BSR 8
+
+/* Bit flags for the pcre_extra structure. Do not re-arrange or redefine
+these bits, just add new ones on the end, in order to remain compatible. */
+
+#define PCRE_EXTRA_STUDY_DATA 0x0001
+#define PCRE_EXTRA_MATCH_LIMIT 0x0002
+#define PCRE_EXTRA_CALLOUT_DATA 0x0004
+#define PCRE_EXTRA_TABLES 0x0008
+#define PCRE_EXTRA_MATCH_LIMIT_RECURSION 0x0010
+#define PCRE_EXTRA_MARK 0x0020
+
+/* Types */
+
+struct real_pcre; /* declaration; the definition is private */
+typedef struct real_pcre pcre;
+
+/* When PCRE is compiled as a C++ library, the subject pointer type can be
+replaced with a custom type. For conventional use, the public interface is a
+const char *. */
+
+#ifndef PCRE_SPTR
+#define PCRE_SPTR const char *
+#endif
+
+/* The structure for passing additional data to pcre_exec(). This is defined in
+such as way as to be extensible. Always add new fields at the end, in order to
+remain compatible. */
+
+typedef struct pcre_extra {
+ unsigned long int flags; /* Bits for which fields are set */
+ void *study_data; /* Opaque data from pcre_study() */
+ unsigned long int match_limit; /* Maximum number of calls to match() */
+ void *callout_data; /* Data passed back in callouts */
+ const unsigned char *tables; /* Pointer to character tables */
+ unsigned long int match_limit_recursion; /* Max recursive calls to match() */
+ unsigned char **mark; /* For passing back a mark pointer */
+} pcre_extra;
+
+/* The structure for passing out data via the pcre_callout_function. We use a
+structure so that new fields can be added on the end in future versions,
+without changing the API of the function, thereby allowing old clients to work
+without modification. */
+
+typedef struct pcre_callout_block {
+ int version; /* Identifies version of block */
+ /* ------------------------ Version 0 ------------------------------- */
+ int callout_number; /* Number compiled into pattern */
+ int *offset_vector; /* The offset vector */
+ PCRE_SPTR subject; /* The subject being matched */
+ int subject_length; /* The length of the subject */
+ int start_match; /* Offset to start of this match attempt */
+ int current_position; /* Where we currently are in the subject */
+ int capture_top; /* Max current capture */
+ int capture_last; /* Most recently closed capture */
+ void *callout_data; /* Data passed in with the call */
+ /* ------------------- Added for Version 1 -------------------------- */
+ int pattern_position; /* Offset to next item in the pattern */
+ int next_item_length; /* Length of next item in the pattern */
+ /* ------------------- Added for Version 2 -------------------------- */
+ const unsigned char *mark; /* Pointer to current mark or NULL */
+ /* ------------------------------------------------------------------ */
+} pcre_callout_block;
+
+/* Indirection for store get and free functions. These can be set to
+alternative malloc/free functions if required. Special ones are used in the
+non-recursive case for "frames". There is also an optional callout function
+that is triggered by the (?) regex item. For Virtual Pascal, these definitions
+have to take another form. */
+
+#ifndef VPCOMPAT
+PCRE_EXP_DECL void *(*pcre_malloc)(size_t);
+PCRE_EXP_DECL void (*pcre_free)(void *);
+PCRE_EXP_DECL void *(*pcre_stack_malloc)(size_t);
+PCRE_EXP_DECL void (*pcre_stack_free)(void *);
+PCRE_EXP_DECL int (*pcre_callout)(pcre_callout_block *);
+#else /* VPCOMPAT */
+PCRE_EXP_DECL void *pcre_malloc(size_t);
+PCRE_EXP_DECL void pcre_free(void *);
+PCRE_EXP_DECL void *pcre_stack_malloc(size_t);
+PCRE_EXP_DECL void pcre_stack_free(void *);
+PCRE_EXP_DECL int pcre_callout(pcre_callout_block *);
+#endif /* VPCOMPAT */
+
+/* Exported PCRE functions */
+
+PCRE_EXP_DECL pcre *pcre_compile(const char *, int, const char **, int *,
+ const unsigned char *);
+PCRE_EXP_DECL pcre *pcre_compile2(const char *, int, int *, const char **,
+ int *, const unsigned char *);
+PCRE_EXP_DECL int pcre_config(int, void *);
+PCRE_EXP_DECL int pcre_copy_named_substring(const pcre *, const char *,
+ int *, int, const char *, char *, int);
+PCRE_EXP_DECL int pcre_copy_substring(const char *, int *, int, int, char *,
+ int);
+PCRE_EXP_DECL int pcre_dfa_exec(const pcre *, const pcre_extra *,
+ const char *, int, int, int, int *, int , int *, int);
+PCRE_EXP_DECL int pcre_exec(const pcre *, const pcre_extra *, PCRE_SPTR,
+ int, int, int, int *, int);
+PCRE_EXP_DECL void pcre_free_substring(const char *);
+PCRE_EXP_DECL void pcre_free_substring_list(const char **);
+PCRE_EXP_DECL int pcre_fullinfo(const pcre *, const pcre_extra *, int,
+ void *);
+PCRE_EXP_DECL int pcre_get_named_substring(const pcre *, const char *,
+ int *, int, const char *, const char **);
+PCRE_EXP_DECL int pcre_get_stringnumber(const pcre *, const char *);
+PCRE_EXP_DECL int pcre_get_stringtable_entries(const pcre *, const char *,
+ char **, char **);
+PCRE_EXP_DECL int pcre_get_substring(const char *, int *, int, int,
+ const char **);
+PCRE_EXP_DECL int pcre_get_substring_list(const char *, int *, int,
+ const char ***);
+PCRE_EXP_DECL int pcre_info(const pcre *, int *, int *);
+PCRE_EXP_DECL const unsigned char *pcre_maketables(void);
+PCRE_EXP_DECL int pcre_refcount(pcre *, int);
+PCRE_EXP_DECL pcre_extra *pcre_study(const pcre *, int, const char **);
+PCRE_EXP_DECL const char *pcre_version(void);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* End of pcre.h */
diff --git a/usr.sbin/nginx/src/pcre/pcre_chartables.c b/usr.sbin/nginx/src/pcre/pcre_chartables.c
new file mode 100644
index 00000000000..9117ae3c7fa
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/pcre_chartables.c
@@ -0,0 +1,198 @@
+/*************************************************
+* Perl-Compatible Regular Expressions *
+*************************************************/
+
+/* This file contains character tables that are used when no external tables
+are passed to PCRE by the application that calls it. The tables are used only
+for characters whose code values are less than 256.
+
+This is a default version of the tables that assumes ASCII encoding. A program
+called dftables (which is distributed with PCRE) can be used to build
+alternative versions of this file. This is necessary if you are running in an
+EBCDIC environment, or if you want to default to a different encoding, for
+example ISO-8859-1. When dftables is run, it creates these tables in the
+current locale. If PCRE is configured with --enable-rebuild-chartables, this
+happens automatically.
+
+The following #includes are present because without them gcc 4.x may remove the
+array definition from the final binary if PCRE is built into a static library
+and dead code stripping is activated. This leads to link errors. Pulling in the
+header ensures that the array gets flagged as "someone outside this compilation
+unit might reference this" and so it will always be supplied to the linker. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pcre_internal.h"
+
+const unsigned char _pcre_default_tables[] = {
+
+/* This table is a lower casing table. */
+
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39,
+ 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55,
+ 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 97, 98, 99,100,101,102,103,
+ 104,105,106,107,108,109,110,111,
+ 112,113,114,115,116,117,118,119,
+ 120,121,122, 91, 92, 93, 94, 95,
+ 96, 97, 98, 99,100,101,102,103,
+ 104,105,106,107,108,109,110,111,
+ 112,113,114,115,116,117,118,119,
+ 120,121,122,123,124,125,126,127,
+ 128,129,130,131,132,133,134,135,
+ 136,137,138,139,140,141,142,143,
+ 144,145,146,147,148,149,150,151,
+ 152,153,154,155,156,157,158,159,
+ 160,161,162,163,164,165,166,167,
+ 168,169,170,171,172,173,174,175,
+ 176,177,178,179,180,181,182,183,
+ 184,185,186,187,188,189,190,191,
+ 192,193,194,195,196,197,198,199,
+ 200,201,202,203,204,205,206,207,
+ 208,209,210,211,212,213,214,215,
+ 216,217,218,219,220,221,222,223,
+ 224,225,226,227,228,229,230,231,
+ 232,233,234,235,236,237,238,239,
+ 240,241,242,243,244,245,246,247,
+ 248,249,250,251,252,253,254,255,
+
+/* This table is a case flipping table. */
+
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39,
+ 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55,
+ 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 97, 98, 99,100,101,102,103,
+ 104,105,106,107,108,109,110,111,
+ 112,113,114,115,116,117,118,119,
+ 120,121,122, 91, 92, 93, 94, 95,
+ 96, 65, 66, 67, 68, 69, 70, 71,
+ 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87,
+ 88, 89, 90,123,124,125,126,127,
+ 128,129,130,131,132,133,134,135,
+ 136,137,138,139,140,141,142,143,
+ 144,145,146,147,148,149,150,151,
+ 152,153,154,155,156,157,158,159,
+ 160,161,162,163,164,165,166,167,
+ 168,169,170,171,172,173,174,175,
+ 176,177,178,179,180,181,182,183,
+ 184,185,186,187,188,189,190,191,
+ 192,193,194,195,196,197,198,199,
+ 200,201,202,203,204,205,206,207,
+ 208,209,210,211,212,213,214,215,
+ 216,217,218,219,220,221,222,223,
+ 224,225,226,227,228,229,230,231,
+ 232,233,234,235,236,237,238,239,
+ 240,241,242,243,244,245,246,247,
+ 248,249,250,251,252,253,254,255,
+
+/* This table contains bit maps for various character classes. Each map is 32
+bytes long and the bits run from the least significant end of each byte. The
+classes that have their own maps are: space, xdigit, digit, upper, lower, word,
+graph, print, punct, and cntrl. Other classes are built from combinations. */
+
+ 0x00,0x3e,0x00,0x00,0x01,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+ 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03,
+ 0x7e,0x00,0x00,0x00,0x7e,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+ 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0xfe,0xff,0xff,0x07,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x07,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+ 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03,
+ 0xfe,0xff,0xff,0x87,0xfe,0xff,0xff,0x07,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+ 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+ 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+ 0x00,0x00,0x00,0x00,0xfe,0xff,0x00,0xfc,
+ 0x01,0x00,0x00,0xf8,0x01,0x00,0x00,0x78,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+ 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+/* This table identifies various classes of character by individual bits:
+ 0x01 white space character
+ 0x02 letter
+ 0x04 decimal digit
+ 0x08 hexadecimal digit
+ 0x10 alphanumeric or '_'
+ 0x80 regular expression metacharacter or binary zero
+*/
+
+ 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 0- 7 */
+ 0x00,0x01,0x01,0x00,0x01,0x01,0x00,0x00, /* 8- 15 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 16- 23 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */
+ 0x01,0x00,0x00,0x00,0x80,0x00,0x00,0x00, /* - ' */
+ 0x80,0x80,0x80,0x80,0x00,0x00,0x80,0x00, /* ( - / */
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c, /* 0 - 7 */
+ 0x1c,0x1c,0x00,0x00,0x00,0x00,0x00,0x80, /* 8 - ? */
+ 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* @ - G */
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* H - O */
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* P - W */
+ 0x12,0x12,0x12,0x80,0x80,0x00,0x80,0x10, /* X - _ */
+ 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* ` - g */
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* h - o */
+ 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* p - w */
+ 0x12,0x12,0x12,0x80,0x80,0x00,0x00,0x00, /* x -127 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 128-135 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 136-143 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 144-151 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 152-159 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 160-167 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 168-175 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 176-183 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 184-191 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 192-199 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 200-207 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 208-215 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 216-223 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 224-231 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 232-239 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 240-247 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};/* 248-255 */
+
+/* End of pcre_chartables.c */
diff --git a/usr.sbin/nginx/src/pcre/pcre_compile.c b/usr.sbin/nginx/src/pcre/pcre_compile.c
new file mode 100644
index 00000000000..e72869696f6
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/pcre_compile.c
@@ -0,0 +1,7462 @@
+/*************************************************
+* Perl-Compatible Regular Expressions *
+*************************************************/
+
+/* PCRE is a library of functions to support regular expressions whose syntax
+and semantics are as close as possible to those of the Perl 5 language.
+
+ Written by Philip Hazel
+ Copyright (c) 1997-2011 University of Cambridge
+
+-----------------------------------------------------------------------------
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the University of Cambridge nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+-----------------------------------------------------------------------------
+*/
+
+
+/* This module contains the external function pcre_compile(), along with
+supporting internal functions that are not used by other modules. */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define NLBLOCK cd /* Block containing newline information */
+#define PSSTART start_pattern /* Field containing processed string start */
+#define PSEND end_pattern /* Field containing processed string end */
+
+#include "pcre_internal.h"
+
+
+/* When PCRE_DEBUG is defined, we need the pcre_printint() function, which is
+also used by pcretest. PCRE_DEBUG is not defined when building a production
+library. */
+
+#ifdef PCRE_DEBUG
+#include "pcre_printint.src"
+#endif
+
+
+/* Macro for setting individual bits in class bitmaps. */
+
+#define SETBIT(a,b) a[b/8] |= (1 << (b%8))
+
+/* Maximum length value to check against when making sure that the integer that
+holds the compiled pattern length does not overflow. We make it a bit less than
+INT_MAX to allow for adding in group terminating bytes, so that we don't have
+to check them every time. */
+
+#define OFLOW_MAX (INT_MAX - 20)
+
+
+/*************************************************
+* Code parameters and static tables *
+*************************************************/
+
+/* This value specifies the size of stack workspace that is used during the
+first pre-compile phase that determines how much memory is required. The regex
+is partly compiled into this space, but the compiled parts are discarded as
+soon as they can be, so that hopefully there will never be an overrun. The code
+does, however, check for an overrun. The largest amount I've seen used is 218,
+so this number is very generous.
+
+The same workspace is used during the second, actual compile phase for
+remembering forward references to groups so that they can be filled in at the
+end. Each entry in this list occupies LINK_SIZE bytes, so even when LINK_SIZE
+is 4 there is plenty of room. */
+
+#define COMPILE_WORK_SIZE (4096)
+
+/* The overrun tests check for a slightly smaller size so that they detect the
+overrun before it actually does run off the end of the data block. */
+
+#define WORK_SIZE_CHECK (COMPILE_WORK_SIZE - 100)
+
+
+/* Table for handling escaped characters in the range '0'-'z'. Positive returns
+are simple data values; negative values are for special things like \d and so
+on. Zero means further processing is needed (for things like \x), or the escape
+is invalid. */
+
+#ifndef EBCDIC
+
+/* This is the "normal" table for ASCII systems or for EBCDIC systems running
+in UTF-8 mode. */
+
+static const short int escapes[] = {
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ CHAR_COLON, CHAR_SEMICOLON,
+ CHAR_LESS_THAN_SIGN, CHAR_EQUALS_SIGN,
+ CHAR_GREATER_THAN_SIGN, CHAR_QUESTION_MARK,
+ CHAR_COMMERCIAL_AT, -ESC_A,
+ -ESC_B, -ESC_C,
+ -ESC_D, -ESC_E,
+ 0, -ESC_G,
+ -ESC_H, 0,
+ 0, -ESC_K,
+ 0, 0,
+ -ESC_N, 0,
+ -ESC_P, -ESC_Q,
+ -ESC_R, -ESC_S,
+ 0, 0,
+ -ESC_V, -ESC_W,
+ -ESC_X, 0,
+ -ESC_Z, CHAR_LEFT_SQUARE_BRACKET,
+ CHAR_BACKSLASH, CHAR_RIGHT_SQUARE_BRACKET,
+ CHAR_CIRCUMFLEX_ACCENT, CHAR_UNDERSCORE,
+ CHAR_GRAVE_ACCENT, 7,
+ -ESC_b, 0,
+ -ESC_d, ESC_e,
+ ESC_f, 0,
+ -ESC_h, 0,
+ 0, -ESC_k,
+ 0, 0,
+ ESC_n, 0,
+ -ESC_p, 0,
+ ESC_r, -ESC_s,
+ ESC_tee, 0,
+ -ESC_v, -ESC_w,
+ 0, 0,
+ -ESC_z
+};
+
+#else
+
+/* This is the "abnormal" table for EBCDIC systems without UTF-8 support. */
+
+static const short int escapes[] = {
+/* 48 */ 0, 0, 0, '.', '<', '(', '+', '|',
+/* 50 */ '&', 0, 0, 0, 0, 0, 0, 0,
+/* 58 */ 0, 0, '!', '$', '*', ')', ';', '~',
+/* 60 */ '-', '/', 0, 0, 0, 0, 0, 0,
+/* 68 */ 0, 0, '|', ',', '%', '_', '>', '?',
+/* 70 */ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 78 */ 0, '`', ':', '#', '@', '\'', '=', '"',
+/* 80 */ 0, 7, -ESC_b, 0, -ESC_d, ESC_e, ESC_f, 0,
+/* 88 */-ESC_h, 0, 0, '{', 0, 0, 0, 0,
+/* 90 */ 0, 0, -ESC_k, 'l', 0, ESC_n, 0, -ESC_p,
+/* 98 */ 0, ESC_r, 0, '}', 0, 0, 0, 0,
+/* A0 */ 0, '~', -ESC_s, ESC_tee, 0,-ESC_v, -ESC_w, 0,
+/* A8 */ 0,-ESC_z, 0, 0, 0, '[', 0, 0,
+/* B0 */ 0, 0, 0, 0, 0, 0, 0, 0,
+/* B8 */ 0, 0, 0, 0, 0, ']', '=', '-',
+/* C0 */ '{',-ESC_A, -ESC_B, -ESC_C, -ESC_D,-ESC_E, 0, -ESC_G,
+/* C8 */-ESC_H, 0, 0, 0, 0, 0, 0, 0,
+/* D0 */ '}', 0, -ESC_K, 0, 0,-ESC_N, 0, -ESC_P,
+/* D8 */-ESC_Q,-ESC_R, 0, 0, 0, 0, 0, 0,
+/* E0 */ '\\', 0, -ESC_S, 0, 0,-ESC_V, -ESC_W, -ESC_X,
+/* E8 */ 0,-ESC_Z, 0, 0, 0, 0, 0, 0,
+/* F0 */ 0, 0, 0, 0, 0, 0, 0, 0,
+/* F8 */ 0, 0, 0, 0, 0, 0, 0, 0
+};
+#endif
+
+
+/* Table of special "verbs" like (*PRUNE). This is a short table, so it is
+searched linearly. Put all the names into a single string, in order to reduce
+the number of relocations when a shared library is dynamically linked. The
+string is built from string macros so that it works in UTF-8 mode on EBCDIC
+platforms. */
+
+typedef struct verbitem {
+ int len; /* Length of verb name */
+ int op; /* Op when no arg, or -1 if arg mandatory */
+ int op_arg; /* Op when arg present, or -1 if not allowed */
+} verbitem;
+
+static const char verbnames[] =
+ "\0" /* Empty name is a shorthand for MARK */
+ STRING_MARK0
+ STRING_ACCEPT0
+ STRING_COMMIT0
+ STRING_F0
+ STRING_FAIL0
+ STRING_PRUNE0
+ STRING_SKIP0
+ STRING_THEN;
+
+static const verbitem verbs[] = {
+ { 0, -1, OP_MARK },
+ { 4, -1, OP_MARK },
+ { 6, OP_ACCEPT, -1 },
+ { 6, OP_COMMIT, -1 },
+ { 1, OP_FAIL, -1 },
+ { 4, OP_FAIL, -1 },
+ { 5, OP_PRUNE, OP_PRUNE_ARG },
+ { 4, OP_SKIP, OP_SKIP_ARG },
+ { 4, OP_THEN, OP_THEN_ARG }
+};
+
+static const int verbcount = sizeof(verbs)/sizeof(verbitem);
+
+
+/* Tables of names of POSIX character classes and their lengths. The names are
+now all in a single string, to reduce the number of relocations when a shared
+library is dynamically loaded. The list of lengths is terminated by a zero
+length entry. The first three must be alpha, lower, upper, as this is assumed
+for handling case independence. */
+
+static const char posix_names[] =
+ STRING_alpha0 STRING_lower0 STRING_upper0 STRING_alnum0
+ STRING_ascii0 STRING_blank0 STRING_cntrl0 STRING_digit0
+ STRING_graph0 STRING_print0 STRING_punct0 STRING_space0
+ STRING_word0 STRING_xdigit;
+
+static const uschar posix_name_lengths[] = {
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 6, 0 };
+
+/* Table of class bit maps for each POSIX class. Each class is formed from a
+base map, with an optional addition or removal of another map. Then, for some
+classes, there is some additional tweaking: for [:blank:] the vertical space
+characters are removed, and for [:alpha:] and [:alnum:] the underscore
+character is removed. The triples in the table consist of the base map offset,
+second map offset or -1 if no second map, and a non-negative value for map
+addition or a negative value for map subtraction (if there are two maps). The
+absolute value of the third field has these meanings: 0 => no tweaking, 1 =>
+remove vertical space characters, 2 => remove underscore. */
+
+static const int posix_class_maps[] = {
+ cbit_word, cbit_digit, -2, /* alpha */
+ cbit_lower, -1, 0, /* lower */
+ cbit_upper, -1, 0, /* upper */
+ cbit_word, -1, 2, /* alnum - word without underscore */
+ cbit_print, cbit_cntrl, 0, /* ascii */
+ cbit_space, -1, 1, /* blank - a GNU extension */
+ cbit_cntrl, -1, 0, /* cntrl */
+ cbit_digit, -1, 0, /* digit */
+ cbit_graph, -1, 0, /* graph */
+ cbit_print, -1, 0, /* print */
+ cbit_punct, -1, 0, /* punct */
+ cbit_space, -1, 0, /* space */
+ cbit_word, -1, 0, /* word - a Perl extension */
+ cbit_xdigit,-1, 0 /* xdigit */
+};
+
+/* Table of substitutes for \d etc when PCRE_UCP is set. The POSIX class
+substitutes must be in the order of the names, defined above, and there are
+both positive and negative cases. NULL means no substitute. */
+
+#ifdef SUPPORT_UCP
+static const uschar *substitutes[] = {
+ (uschar *)"\\P{Nd}", /* \D */
+ (uschar *)"\\p{Nd}", /* \d */
+ (uschar *)"\\P{Xsp}", /* \S */ /* NOTE: Xsp is Perl space */
+ (uschar *)"\\p{Xsp}", /* \s */
+ (uschar *)"\\P{Xwd}", /* \W */
+ (uschar *)"\\p{Xwd}" /* \w */
+};
+
+static const uschar *posix_substitutes[] = {
+ (uschar *)"\\p{L}", /* alpha */
+ (uschar *)"\\p{Ll}", /* lower */
+ (uschar *)"\\p{Lu}", /* upper */
+ (uschar *)"\\p{Xan}", /* alnum */
+ NULL, /* ascii */
+ (uschar *)"\\h", /* blank */
+ NULL, /* cntrl */
+ (uschar *)"\\p{Nd}", /* digit */
+ NULL, /* graph */
+ NULL, /* print */
+ NULL, /* punct */
+ (uschar *)"\\p{Xps}", /* space */ /* NOTE: Xps is POSIX space */
+ (uschar *)"\\p{Xwd}", /* word */
+ NULL, /* xdigit */
+ /* Negated cases */
+ (uschar *)"\\P{L}", /* ^alpha */
+ (uschar *)"\\P{Ll}", /* ^lower */
+ (uschar *)"\\P{Lu}", /* ^upper */
+ (uschar *)"\\P{Xan}", /* ^alnum */
+ NULL, /* ^ascii */
+ (uschar *)"\\H", /* ^blank */
+ NULL, /* ^cntrl */
+ (uschar *)"\\P{Nd}", /* ^digit */
+ NULL, /* ^graph */
+ NULL, /* ^print */
+ NULL, /* ^punct */
+ (uschar *)"\\P{Xps}", /* ^space */ /* NOTE: Xps is POSIX space */
+ (uschar *)"\\P{Xwd}", /* ^word */
+ NULL /* ^xdigit */
+};
+#define POSIX_SUBSIZE (sizeof(posix_substitutes)/sizeof(uschar *))
+#endif
+
+#define STRING(a) # a
+#define XSTRING(s) STRING(s)
+
+/* The texts of compile-time error messages. These are "char *" because they
+are passed to the outside world. Do not ever re-use any error number, because
+they are documented. Always add a new error instead. Messages marked DEAD below
+are no longer used. This used to be a table of strings, but in order to reduce
+the number of relocations needed when a shared library is loaded dynamically,
+it is now one long string. We cannot use a table of offsets, because the
+lengths of inserts such as XSTRING(MAX_NAME_SIZE) are not known. Instead, we
+simply count through to the one we want - this isn't a performance issue
+because these strings are used only when there is a compilation error.
+
+Each substring ends with \0 to insert a null character. This includes the final
+substring, so that the whole string ends with \0\0, which can be detected when
+counting through. */
+
+static const char error_texts[] =
+ "no error\0"
+ "\\ at end of pattern\0"
+ "\\c at end of pattern\0"
+ "unrecognized character follows \\\0"
+ "numbers out of order in {} quantifier\0"
+ /* 5 */
+ "number too big in {} quantifier\0"
+ "missing terminating ] for character class\0"
+ "invalid escape sequence in character class\0"
+ "range out of order in character class\0"
+ "nothing to repeat\0"
+ /* 10 */
+ "operand of unlimited repeat could match the empty string\0" /** DEAD **/
+ "internal error: unexpected repeat\0"
+ "unrecognized character after (? or (?-\0"
+ "POSIX named classes are supported only within a class\0"
+ "missing )\0"
+ /* 15 */
+ "reference to non-existent subpattern\0"
+ "erroffset passed as NULL\0"
+ "unknown option bit(s) set\0"
+ "missing ) after comment\0"
+ "parentheses nested too deeply\0" /** DEAD **/
+ /* 20 */
+ "regular expression is too large\0"
+ "failed to get memory\0"
+ "unmatched parentheses\0"
+ "internal error: code overflow\0"
+ "unrecognized character after (?<\0"
+ /* 25 */
+ "lookbehind assertion is not fixed length\0"
+ "malformed number or name after (?(\0"
+ "conditional group contains more than two branches\0"
+ "assertion expected after (?(\0"
+ "(?R or (?[+-]digits must be followed by )\0"
+ /* 30 */
+ "unknown POSIX class name\0"
+ "POSIX collating elements are not supported\0"
+ "this version of PCRE is not compiled with PCRE_UTF8 support\0"
+ "spare error\0" /** DEAD **/
+ "character value in \\x{...} sequence is too large\0"
+ /* 35 */
+ "invalid condition (?(0)\0"
+ "\\C not allowed in lookbehind assertion\0"
+ "PCRE does not support \\L, \\l, \\N{name}, \\U, or \\u\0"
+ "number after (?C is > 255\0"
+ "closing ) for (?C expected\0"
+ /* 40 */
+ "recursive call could loop indefinitely\0"
+ "unrecognized character after (?P\0"
+ "syntax error in subpattern name (missing terminator)\0"
+ "two named subpatterns have the same name\0"
+ "invalid UTF-8 string\0"
+ /* 45 */
+ "support for \\P, \\p, and \\X has not been compiled\0"
+ "malformed \\P or \\p sequence\0"
+ "unknown property name after \\P or \\p\0"
+ "subpattern name is too long (maximum " XSTRING(MAX_NAME_SIZE) " characters)\0"
+ "too many named subpatterns (maximum " XSTRING(MAX_NAME_COUNT) ")\0"
+ /* 50 */
+ "repeated subpattern is too long\0" /** DEAD **/
+ "octal value is greater than \\377 (not in UTF-8 mode)\0"
+ "internal error: overran compiling workspace\0"
+ "internal error: previously-checked referenced subpattern not found\0"
+ "DEFINE group contains more than one branch\0"
+ /* 55 */
+ "repeating a DEFINE group is not allowed\0" /** DEAD **/
+ "inconsistent NEWLINE options\0"
+ "\\g is not followed by a braced, angle-bracketed, or quoted name/number or by a plain number\0"
+ "a numbered reference must not be zero\0"
+ "an argument is not allowed for (*ACCEPT), (*FAIL), or (*COMMIT)\0"
+ /* 60 */
+ "(*VERB) not recognized\0"
+ "number is too big\0"
+ "subpattern name expected\0"
+ "digit expected after (?+\0"
+ "] is an invalid data character in JavaScript compatibility mode\0"
+ /* 65 */
+ "different names for subpatterns of the same number are not allowed\0"
+ "(*MARK) must have an argument\0"
+ "this version of PCRE is not compiled with PCRE_UCP support\0"
+ "\\c must be followed by an ASCII character\0"
+ "\\k is not followed by a braced, angle-bracketed, or quoted name\0"
+ ;
+
+/* Table to identify digits and hex digits. This is used when compiling
+patterns. Note that the tables in chartables are dependent on the locale, and
+may mark arbitrary characters as digits - but the PCRE compiling code expects
+to handle only 0-9, a-z, and A-Z as digits when compiling. That is why we have
+a private table here. It costs 256 bytes, but it is a lot faster than doing
+character value tests (at least in some simple cases I timed), and in some
+applications one wants PCRE to compile efficiently as well as match
+efficiently.
+
+For convenience, we use the same bit definitions as in chartables:
+
+ 0x04 decimal digit
+ 0x08 hexadecimal digit
+
+Then we can use ctype_digit and ctype_xdigit in the code. */
+
+#ifndef EBCDIC
+
+/* This is the "normal" case, for ASCII systems, and EBCDIC systems running in
+UTF-8 mode. */
+
+static const unsigned char digitab[] =
+ {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 0- 7 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 8- 15 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 16- 23 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* - ' */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ( - / */
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c, /* 0 - 7 */
+ 0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00, /* 8 - ? */
+ 0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x00, /* @ - G */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* H - O */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* P - W */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* X - _ */
+ 0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x00, /* ` - g */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* h - o */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* p - w */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* x -127 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 128-135 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 136-143 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 144-151 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 152-159 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 160-167 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 168-175 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 176-183 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 184-191 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 192-199 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 200-207 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 208-215 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 216-223 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 224-231 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 232-239 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 240-247 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};/* 248-255 */
+
+#else
+
+/* This is the "abnormal" case, for EBCDIC systems not running in UTF-8 mode. */
+
+static const unsigned char digitab[] =
+ {
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 0- 7 0 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 8- 15 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 16- 23 10 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 32- 39 20 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 40- 47 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 48- 55 30 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 56- 63 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* - 71 40 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 72- | */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* & - 87 50 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 88- 95 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* - -103 60 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 104- ? */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 112-119 70 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 120- " */
+ 0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x00, /* 128- g 80 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* h -143 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 144- p 90 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* q -159 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 160- x A0 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* y -175 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ^ -183 B0 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 184-191 */
+ 0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x00, /* { - G C0 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* H -207 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* } - P D0 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Q -223 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* \ - X E0 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Y -239 */
+ 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c, /* 0 - 7 F0 */
+ 0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00};/* 8 -255 */
+
+static const unsigned char ebcdic_chartab[] = { /* chartable partial dup */
+ 0x80,0x00,0x00,0x00,0x00,0x01,0x00,0x00, /* 0- 7 */
+ 0x00,0x00,0x00,0x00,0x01,0x01,0x00,0x00, /* 8- 15 */
+ 0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00, /* 16- 23 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */
+ 0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00, /* 32- 39 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 40- 47 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 48- 55 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 56- 63 */
+ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* - 71 */
+ 0x00,0x00,0x00,0x80,0x00,0x80,0x80,0x80, /* 72- | */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* & - 87 */
+ 0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00, /* 88- 95 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* - -103 */
+ 0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x80, /* 104- ? */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 112-119 */
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 120- " */
+ 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* 128- g */
+ 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* h -143 */
+ 0x00,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* 144- p */
+ 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* q -159 */
+ 0x00,0x00,0x12,0x12,0x12,0x12,0x12,0x12, /* 160- x */
+ 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* y -175 */
+ 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ^ -183 */
+ 0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00, /* 184-191 */
+ 0x80,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* { - G */
+ 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* H -207 */
+ 0x00,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* } - P */
+ 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* Q -223 */
+ 0x00,0x00,0x12,0x12,0x12,0x12,0x12,0x12, /* \ - X */
+ 0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, /* Y -239 */
+ 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c, /* 0 - 7 */
+ 0x1c,0x1c,0x00,0x00,0x00,0x00,0x00,0x00};/* 8 -255 */
+#endif
+
+
+/* Definition to allow mutual recursion */
+
+static BOOL
+ compile_regex(int, uschar **, const uschar **, int *, BOOL, BOOL, int, int,
+ int *, int *, branch_chain *, compile_data *, int *);
+
+
+
+/*************************************************
+* Find an error text *
+*************************************************/
+
+/* The error texts are now all in one long string, to save on relocations. As
+some of the text is of unknown length, we can't use a table of offsets.
+Instead, just count through the strings. This is not a performance issue
+because it happens only when there has been a compilation error.
+
+Argument: the error number
+Returns: pointer to the error string
+*/
+
+static const char *
+find_error_text(int n)
+{
+const char *s = error_texts;
+for (; n > 0; n--)
+ {
+ while (*s++ != 0) {};
+ if (*s == 0) return "Error text not found (please report)";
+ }
+return s;
+}
+
+
+/*************************************************
+* Check for counted repeat *
+*************************************************/
+
+/* This function is called when a '{' is encountered in a place where it might
+start a quantifier. It looks ahead to see if it really is a quantifier or not.
+It is only a quantifier if it is one of the forms {ddd} {ddd,} or {ddd,ddd}
+where the ddds are digits.
+
+Arguments:
+ p pointer to the first char after '{'
+
+Returns: TRUE or FALSE
+*/
+
+static BOOL
+is_counted_repeat(const uschar *p)
+{
+if ((digitab[*p++] & ctype_digit) == 0) return FALSE;
+while ((digitab[*p] & ctype_digit) != 0) p++;
+if (*p == CHAR_RIGHT_CURLY_BRACKET) return TRUE;
+
+if (*p++ != CHAR_COMMA) return FALSE;
+if (*p == CHAR_RIGHT_CURLY_BRACKET) return TRUE;
+
+if ((digitab[*p++] & ctype_digit) == 0) return FALSE;
+while ((digitab[*p] & ctype_digit) != 0) p++;
+
+return (*p == CHAR_RIGHT_CURLY_BRACKET);
+}
+
+
+
+/*************************************************
+* Handle escapes *
+*************************************************/
+
+/* This function is called when a \ has been encountered. It either returns a
+positive value for a simple escape such as \n, or a negative value which
+encodes one of the more complicated things such as \d. A backreference to group
+n is returned as -(ESC_REF + n); ESC_REF is the highest ESC_xxx macro. When
+UTF-8 is enabled, a positive value greater than 255 may be returned. On entry,
+ptr is pointing at the \. On exit, it is on the final character of the escape
+sequence.
+
+Arguments:
+ ptrptr points to the pattern position pointer
+ errorcodeptr points to the errorcode variable
+ bracount number of previous extracting brackets
+ options the options bits
+ isclass TRUE if inside a character class
+
+Returns: zero or positive => a data character
+ negative => a special escape sequence
+ on error, errorcodeptr is set
+*/
+
+static int
+check_escape(const uschar **ptrptr, int *errorcodeptr, int bracount,
+ int options, BOOL isclass)
+{
+BOOL utf8 = (options & PCRE_UTF8) != 0;
+const uschar *ptr = *ptrptr + 1;
+int c, i;
+
+GETCHARINCTEST(c, ptr); /* Get character value, increment pointer */
+ptr--; /* Set pointer back to the last byte */
+
+/* If backslash is at the end of the pattern, it's an error. */
+
+if (c == 0) *errorcodeptr = ERR1;
+
+/* Non-alphanumerics are literals. For digits or letters, do an initial lookup
+in a table. A non-zero result is something that can be returned immediately.
+Otherwise further processing may be required. */
+
+#ifndef EBCDIC /* ASCII/UTF-8 coding */
+else if (c < CHAR_0 || c > CHAR_z) {} /* Not alphanumeric */
+else if ((i = escapes[c - CHAR_0]) != 0) c = i;
+
+#else /* EBCDIC coding */
+else if (c < 'a' || (ebcdic_chartab[c] & 0x0E) == 0) {} /* Not alphanumeric */
+else if ((i = escapes[c - 0x48]) != 0) c = i;
+#endif
+
+/* Escapes that need further processing, or are illegal. */
+
+else
+ {
+ const uschar *oldptr;
+ BOOL braced, negated;
+
+ switch (c)
+ {
+ /* A number of Perl escapes are not handled by PCRE. We give an explicit
+ error. */
+
+ case CHAR_l:
+ case CHAR_L:
+ case CHAR_u:
+ case CHAR_U:
+ *errorcodeptr = ERR37;
+ break;
+
+ /* In a character class, \g is just a literal "g". Outside a character
+ class, \g must be followed by one of a number of specific things:
+
+ (1) A number, either plain or braced. If positive, it is an absolute
+ backreference. If negative, it is a relative backreference. This is a Perl
+ 5.10 feature.
+
+ (2) Perl 5.10 also supports \g{name} as a reference to a named group. This
+ is part of Perl's movement towards a unified syntax for back references. As
+ this is synonymous with \k{name}, we fudge it up by pretending it really
+ was \k.
+
+ (3) For Oniguruma compatibility we also support \g followed by a name or a
+ number either in angle brackets or in single quotes. However, these are
+ (possibly recursive) subroutine calls, _not_ backreferences. Just return
+ the -ESC_g code (cf \k). */
+
+ case CHAR_g:
+ if (isclass) break;
+ if (ptr[1] == CHAR_LESS_THAN_SIGN || ptr[1] == CHAR_APOSTROPHE)
+ {
+ c = -ESC_g;
+ break;
+ }
+
+ /* Handle the Perl-compatible cases */
+
+ if (ptr[1] == CHAR_LEFT_CURLY_BRACKET)
+ {
+ const uschar *p;
+ for (p = ptr+2; *p != 0 && *p != CHAR_RIGHT_CURLY_BRACKET; p++)
+ if (*p != CHAR_MINUS && (digitab[*p] & ctype_digit) == 0) break;
+ if (*p != 0 && *p != CHAR_RIGHT_CURLY_BRACKET)
+ {
+ c = -ESC_k;
+ break;
+ }
+ braced = TRUE;
+ ptr++;
+ }
+ else braced = FALSE;
+
+ if (ptr[1] == CHAR_MINUS)
+ {
+ negated = TRUE;
+ ptr++;
+ }
+ else negated = FALSE;
+
+ c = 0;
+ while ((digitab[ptr[1]] & ctype_digit) != 0)
+ c = c * 10 + *(++ptr) - CHAR_0;
+
+ if (c < 0) /* Integer overflow */
+ {
+ *errorcodeptr = ERR61;
+ break;
+ }
+
+ if (braced && *(++ptr) != CHAR_RIGHT_CURLY_BRACKET)
+ {
+ *errorcodeptr = ERR57;
+ break;
+ }
+
+ if (c == 0)
+ {
+ *errorcodeptr = ERR58;
+ break;
+ }
+
+ if (negated)
+ {
+ if (c > bracount)
+ {
+ *errorcodeptr = ERR15;
+ break;
+ }
+ c = bracount - (c - 1);
+ }
+
+ c = -(ESC_REF + c);
+ break;
+
+ /* The handling of escape sequences consisting of a string of digits
+ starting with one that is not zero is not straightforward. By experiment,
+ the way Perl works seems to be as follows:
+
+ Outside a character class, the digits are read as a decimal number. If the
+ number is less than 10, or if there are that many previous extracting
+ left brackets, then it is a back reference. Otherwise, up to three octal
+ digits are read to form an escaped byte. Thus \123 is likely to be octal
+ 123 (cf \0123, which is octal 012 followed by the literal 3). If the octal
+ value is greater than 377, the least significant 8 bits are taken. Inside a
+ character class, \ followed by a digit is always an octal number. */
+
+ case CHAR_1: case CHAR_2: case CHAR_3: case CHAR_4: case CHAR_5:
+ case CHAR_6: case CHAR_7: case CHAR_8: case CHAR_9:
+
+ if (!isclass)
+ {
+ oldptr = ptr;
+ c -= CHAR_0;
+ while ((digitab[ptr[1]] & ctype_digit) != 0)
+ c = c * 10 + *(++ptr) - CHAR_0;
+ if (c < 0) /* Integer overflow */
+ {
+ *errorcodeptr = ERR61;
+ break;
+ }
+ if (c < 10 || c <= bracount)
+ {
+ c = -(ESC_REF + c);
+ break;
+ }
+ ptr = oldptr; /* Put the pointer back and fall through */
+ }
+
+ /* Handle an octal number following \. If the first digit is 8 or 9, Perl
+ generates a binary zero byte and treats the digit as a following literal.
+ Thus we have to pull back the pointer by one. */
+
+ if ((c = *ptr) >= CHAR_8)
+ {
+ ptr--;
+ c = 0;
+ break;
+ }
+
+ /* \0 always starts an octal number, but we may drop through to here with a
+ larger first octal digit. The original code used just to take the least
+ significant 8 bits of octal numbers (I think this is what early Perls used
+ to do). Nowadays we allow for larger numbers in UTF-8 mode, but no more
+ than 3 octal digits. */
+
+ case CHAR_0:
+ c -= CHAR_0;
+ while(i++ < 2 && ptr[1] >= CHAR_0 && ptr[1] <= CHAR_7)
+ c = c * 8 + *(++ptr) - CHAR_0;
+ if (!utf8 && c > 255) *errorcodeptr = ERR51;
+ break;
+
+ /* \x is complicated. \x{ddd} is a character number which can be greater
+ than 0xff in utf8 mode, but only if the ddd are hex digits. If not, { is
+ treated as a data character. */
+
+ case CHAR_x:
+ if (ptr[1] == CHAR_LEFT_CURLY_BRACKET)
+ {
+ const uschar *pt = ptr + 2;
+ int count = 0;
+
+ c = 0;
+ while ((digitab[*pt] & ctype_xdigit) != 0)
+ {
+ register int cc = *pt++;
+ if (c == 0 && cc == CHAR_0) continue; /* Leading zeroes */
+ count++;
+
+#ifndef EBCDIC /* ASCII/UTF-8 coding */
+ if (cc >= CHAR_a) cc -= 32; /* Convert to upper case */
+ c = (c << 4) + cc - ((cc < CHAR_A)? CHAR_0 : (CHAR_A - 10));
+#else /* EBCDIC coding */
+ if (cc >= CHAR_a && cc <= CHAR_z) cc += 64; /* Convert to upper case */
+ c = (c << 4) + cc - ((cc >= CHAR_0)? CHAR_0 : (CHAR_A - 10));
+#endif
+ }
+
+ if (*pt == CHAR_RIGHT_CURLY_BRACKET)
+ {
+ if (c < 0 || count > (utf8? 8 : 2)) *errorcodeptr = ERR34;
+ ptr = pt;
+ break;
+ }
+
+ /* If the sequence of hex digits does not end with '}', then we don't
+ recognize this construct; fall through to the normal \x handling. */
+ }
+
+ /* Read just a single-byte hex-defined char */
+
+ c = 0;
+ while (i++ < 2 && (digitab[ptr[1]] & ctype_xdigit) != 0)
+ {
+ int cc; /* Some compilers don't like */
+ cc = *(++ptr); /* ++ in initializers */
+#ifndef EBCDIC /* ASCII/UTF-8 coding */
+ if (cc >= CHAR_a) cc -= 32; /* Convert to upper case */
+ c = c * 16 + cc - ((cc < CHAR_A)? CHAR_0 : (CHAR_A - 10));
+#else /* EBCDIC coding */
+ if (cc <= CHAR_z) cc += 64; /* Convert to upper case */
+ c = c * 16 + cc - ((cc >= CHAR_0)? CHAR_0 : (CHAR_A - 10));
+#endif
+ }
+ break;
+
+ /* For \c, a following letter is upper-cased; then the 0x40 bit is flipped.
+ An error is given if the byte following \c is not an ASCII character. This
+ coding is ASCII-specific, but then the whole concept of \cx is
+ ASCII-specific. (However, an EBCDIC equivalent has now been added.) */
+
+ case CHAR_c:
+ c = *(++ptr);
+ if (c == 0)
+ {
+ *errorcodeptr = ERR2;
+ break;
+ }
+#ifndef EBCDIC /* ASCII/UTF-8 coding */
+ if (c > 127) /* Excludes all non-ASCII in either mode */
+ {
+ *errorcodeptr = ERR68;
+ break;
+ }
+ if (c >= CHAR_a && c <= CHAR_z) c -= 32;
+ c ^= 0x40;
+#else /* EBCDIC coding */
+ if (c >= CHAR_a && c <= CHAR_z) c += 64;
+ c ^= 0xC0;
+#endif
+ break;
+
+ /* PCRE_EXTRA enables extensions to Perl in the matter of escapes. Any
+ other alphanumeric following \ is an error if PCRE_EXTRA was set;
+ otherwise, for Perl compatibility, it is a literal. This code looks a bit
+ odd, but there used to be some cases other than the default, and there may
+ be again in future, so I haven't "optimized" it. */
+
+ default:
+ if ((options & PCRE_EXTRA) != 0) switch(c)
+ {
+ default:
+ *errorcodeptr = ERR3;
+ break;
+ }
+ break;
+ }
+ }
+
+/* Perl supports \N{name} for character names, as well as plain \N for "not
+newline". PCRE does not support \N{name}. However, it does support
+quantification such as \N{2,3}. */
+
+if (c == -ESC_N && ptr[1] == CHAR_LEFT_CURLY_BRACKET &&
+ !is_counted_repeat(ptr+2))
+ *errorcodeptr = ERR37;
+
+/* If PCRE_UCP is set, we change the values for \d etc. */
+
+if ((options & PCRE_UCP) != 0 && c <= -ESC_D && c >= -ESC_w)
+ c -= (ESC_DU - ESC_D);
+
+/* Set the pointer to the final character before returning. */
+
+*ptrptr = ptr;
+return c;
+}
+
+
+
+#ifdef SUPPORT_UCP
+/*************************************************
+* Handle \P and \p *
+*************************************************/
+
+/* This function is called after \P or \p has been encountered, provided that
+PCRE is compiled with support for Unicode properties. On entry, ptrptr is
+pointing at the P or p. On exit, it is pointing at the final character of the
+escape sequence.
+
+Argument:
+ ptrptr points to the pattern position pointer
+ negptr points to a boolean that is set TRUE for negation else FALSE
+ dptr points to an int that is set to the detailed property value
+ errorcodeptr points to the error code variable
+
+Returns: type value from ucp_type_table, or -1 for an invalid type
+*/
+
+static int
+get_ucp(const uschar **ptrptr, BOOL *negptr, int *dptr, int *errorcodeptr)
+{
+int c, i, bot, top;
+const uschar *ptr = *ptrptr;
+char name[32];
+
+c = *(++ptr);
+if (c == 0) goto ERROR_RETURN;
+
+*negptr = FALSE;
+
+/* \P or \p can be followed by a name in {}, optionally preceded by ^ for
+negation. */
+
+if (c == CHAR_LEFT_CURLY_BRACKET)
+ {
+ if (ptr[1] == CHAR_CIRCUMFLEX_ACCENT)
+ {
+ *negptr = TRUE;
+ ptr++;
+ }
+ for (i = 0; i < (int)sizeof(name) - 1; i++)
+ {
+ c = *(++ptr);
+ if (c == 0) goto ERROR_RETURN;
+ if (c == CHAR_RIGHT_CURLY_BRACKET) break;
+ name[i] = c;
+ }
+ if (c != CHAR_RIGHT_CURLY_BRACKET) goto ERROR_RETURN;
+ name[i] = 0;
+ }
+
+/* Otherwise there is just one following character */
+
+else
+ {
+ name[0] = c;
+ name[1] = 0;
+ }
+
+*ptrptr = ptr;
+
+/* Search for a recognized property name using binary chop */
+
+bot = 0;
+top = _pcre_utt_size;
+
+while (bot < top)
+ {
+ i = (bot + top) >> 1;
+ c = strcmp(name, _pcre_utt_names + _pcre_utt[i].name_offset);
+ if (c == 0)
+ {
+ *dptr = _pcre_utt[i].value;
+ return _pcre_utt[i].type;
+ }
+ if (c > 0) bot = i + 1; else top = i;
+ }
+
+*errorcodeptr = ERR47;
+*ptrptr = ptr;
+return -1;
+
+ERROR_RETURN:
+*errorcodeptr = ERR46;
+*ptrptr = ptr;
+return -1;
+}
+#endif
+
+
+
+
+/*************************************************
+* Read repeat counts *
+*************************************************/
+
+/* Read an item of the form {n,m} and return the values. This is called only
+after is_counted_repeat() has confirmed that a repeat-count quantifier exists,
+so the syntax is guaranteed to be correct, but we need to check the values.
+
+Arguments:
+ p pointer to first char after '{'
+ minp pointer to int for min
+ maxp pointer to int for max
+ returned as -1 if no max
+ errorcodeptr points to error code variable
+
+Returns: pointer to '}' on success;
+ current ptr on error, with errorcodeptr set non-zero
+*/
+
+static const uschar *
+read_repeat_counts(const uschar *p, int *minp, int *maxp, int *errorcodeptr)
+{
+int min = 0;
+int max = -1;
+
+/* Read the minimum value and do a paranoid check: a negative value indicates
+an integer overflow. */
+
+while ((digitab[*p] & ctype_digit) != 0) min = min * 10 + *p++ - CHAR_0;
+if (min < 0 || min > 65535)
+ {
+ *errorcodeptr = ERR5;
+ return p;
+ }
+
+/* Read the maximum value if there is one, and again do a paranoid on its size.
+Also, max must not be less than min. */
+
+if (*p == CHAR_RIGHT_CURLY_BRACKET) max = min; else
+ {
+ if (*(++p) != CHAR_RIGHT_CURLY_BRACKET)
+ {
+ max = 0;
+ while((digitab[*p] & ctype_digit) != 0) max = max * 10 + *p++ - CHAR_0;
+ if (max < 0 || max > 65535)
+ {
+ *errorcodeptr = ERR5;
+ return p;
+ }
+ if (max < min)
+ {
+ *errorcodeptr = ERR4;
+ return p;
+ }
+ }
+ }
+
+/* Fill in the required variables, and pass back the pointer to the terminating
+'}'. */
+
+*minp = min;
+*maxp = max;
+return p;
+}
+
+
+
+/*************************************************
+* Subroutine for finding forward reference *
+*************************************************/
+
+/* This recursive function is called only from find_parens() below. The
+top-level call starts at the beginning of the pattern. All other calls must
+start at a parenthesis. It scans along a pattern's text looking for capturing
+subpatterns, and counting them. If it finds a named pattern that matches the
+name it is given, it returns its number. Alternatively, if the name is NULL, it
+returns when it reaches a given numbered subpattern. Recursion is used to keep
+track of subpatterns that reset the capturing group numbers - the (?| feature.
+
+This function was originally called only from the second pass, in which we know
+that if (?< or (?' or (?P< is encountered, the name will be correctly
+terminated because that is checked in the first pass. There is now one call to
+this function in the first pass, to check for a recursive back reference by
+name (so that we can make the whole group atomic). In this case, we need check
+only up to the current position in the pattern, and that is still OK because
+and previous occurrences will have been checked. To make this work, the test
+for "end of pattern" is a check against cd->end_pattern in the main loop,
+instead of looking for a binary zero. This means that the special first-pass
+call can adjust cd->end_pattern temporarily. (Checks for binary zero while
+processing items within the loop are OK, because afterwards the main loop will
+terminate.)
+
+Arguments:
+ ptrptr address of the current character pointer (updated)
+ cd compile background data
+ name name to seek, or NULL if seeking a numbered subpattern
+ lorn name length, or subpattern number if name is NULL
+ xmode TRUE if we are in /x mode
+ utf8 TRUE if we are in UTF-8 mode
+ count pointer to the current capturing subpattern number (updated)
+
+Returns: the number of the named subpattern, or -1 if not found
+*/
+
+static int
+find_parens_sub(uschar **ptrptr, compile_data *cd, const uschar *name, int lorn,
+ BOOL xmode, BOOL utf8, int *count)
+{
+uschar *ptr = *ptrptr;
+int start_count = *count;
+int hwm_count = start_count;
+BOOL dup_parens = FALSE;
+
+/* If the first character is a parenthesis, check on the type of group we are
+dealing with. The very first call may not start with a parenthesis. */
+
+if (ptr[0] == CHAR_LEFT_PARENTHESIS)
+ {
+ /* Handle specials such as (*SKIP) or (*UTF8) etc. */
+
+ if (ptr[1] == CHAR_ASTERISK) ptr += 2;
+
+ /* Handle a normal, unnamed capturing parenthesis. */
+
+ else if (ptr[1] != CHAR_QUESTION_MARK)
+ {
+ *count += 1;
+ if (name == NULL && *count == lorn) return *count;
+ ptr++;
+ }
+
+ /* All cases now have (? at the start. Remember when we are in a group
+ where the parenthesis numbers are duplicated. */
+
+ else if (ptr[2] == CHAR_VERTICAL_LINE)
+ {
+ ptr += 3;
+ dup_parens = TRUE;
+ }
+
+ /* Handle comments; all characters are allowed until a ket is reached. */
+
+ else if (ptr[2] == CHAR_NUMBER_SIGN)
+ {
+ for (ptr += 3; *ptr != 0; ptr++) if (*ptr == CHAR_RIGHT_PARENTHESIS) break;
+ goto FAIL_EXIT;
+ }
+
+ /* Handle a condition. If it is an assertion, just carry on so that it
+ is processed as normal. If not, skip to the closing parenthesis of the
+ condition (there can't be any nested parens). */
+
+ else if (ptr[2] == CHAR_LEFT_PARENTHESIS)
+ {
+ ptr += 2;
+ if (ptr[1] != CHAR_QUESTION_MARK)
+ {
+ while (*ptr != 0 && *ptr != CHAR_RIGHT_PARENTHESIS) ptr++;
+ if (*ptr != 0) ptr++;
+ }
+ }
+
+ /* Start with (? but not a condition. */
+
+ else
+ {
+ ptr += 2;
+ if (*ptr == CHAR_P) ptr++; /* Allow optional P */
+
+ /* We have to disambiguate (?<! and (?<= from (?<name> for named groups */
+
+ if ((*ptr == CHAR_LESS_THAN_SIGN && ptr[1] != CHAR_EXCLAMATION_MARK &&
+ ptr[1] != CHAR_EQUALS_SIGN) || *ptr == CHAR_APOSTROPHE)
+ {
+ int term;
+ const uschar *thisname;
+ *count += 1;
+ if (name == NULL && *count == lorn) return *count;
+ term = *ptr++;
+ if (term == CHAR_LESS_THAN_SIGN) term = CHAR_GREATER_THAN_SIGN;
+ thisname = ptr;
+ while (*ptr != term) ptr++;
+ if (name != NULL && lorn == ptr - thisname &&
+ strncmp((const char *)name, (const char *)thisname, lorn) == 0)
+ return *count;
+ term++;
+ }
+ }
+ }
+
+/* Past any initial parenthesis handling, scan for parentheses or vertical
+bars. Stop if we get to cd->end_pattern. Note that this is important for the
+first-pass call when this value is temporarily adjusted to stop at the current
+position. So DO NOT change this to a test for binary zero. */
+
+for (; ptr < cd->end_pattern; ptr++)
+ {
+ /* Skip over backslashed characters and also entire \Q...\E */
+
+ if (*ptr == CHAR_BACKSLASH)
+ {
+ if (*(++ptr) == 0) goto FAIL_EXIT;
+ if (*ptr == CHAR_Q) for (;;)
+ {
+ while (*(++ptr) != 0 && *ptr != CHAR_BACKSLASH) {};
+ if (*ptr == 0) goto FAIL_EXIT;
+ if (*(++ptr) == CHAR_E) break;
+ }
+ continue;
+ }
+
+ /* Skip over character classes; this logic must be similar to the way they
+ are handled for real. If the first character is '^', skip it. Also, if the
+ first few characters (either before or after ^) are \Q\E or \E we skip them
+ too. This makes for compatibility with Perl. Note the use of STR macros to
+ encode "Q\\E" so that it works in UTF-8 on EBCDIC platforms. */
+
+ if (*ptr == CHAR_LEFT_SQUARE_BRACKET)
+ {
+ BOOL negate_class = FALSE;
+ for (;;)
+ {
+ if (ptr[1] == CHAR_BACKSLASH)
+ {
+ if (ptr[2] == CHAR_E)
+ ptr+= 2;
+ else if (strncmp((const char *)ptr+2,
+ STR_Q STR_BACKSLASH STR_E, 3) == 0)
+ ptr += 4;
+ else
+ break;
+ }
+ else if (!negate_class && ptr[1] == CHAR_CIRCUMFLEX_ACCENT)
+ {
+ negate_class = TRUE;
+ ptr++;
+ }
+ else break;
+ }
+
+ /* If the next character is ']', it is a data character that must be
+ skipped, except in JavaScript compatibility mode. */
+
+ if (ptr[1] == CHAR_RIGHT_SQUARE_BRACKET &&
+ (cd->external_options & PCRE_JAVASCRIPT_COMPAT) == 0)
+ ptr++;
+
+ while (*(++ptr) != CHAR_RIGHT_SQUARE_BRACKET)
+ {
+ if (*ptr == 0) return -1;
+ if (*ptr == CHAR_BACKSLASH)
+ {
+ if (*(++ptr) == 0) goto FAIL_EXIT;
+ if (*ptr == CHAR_Q) for (;;)
+ {
+ while (*(++ptr) != 0 && *ptr != CHAR_BACKSLASH) {};
+ if (*ptr == 0) goto FAIL_EXIT;
+ if (*(++ptr) == CHAR_E) break;
+ }
+ continue;
+ }
+ }
+ continue;
+ }
+
+ /* Skip comments in /x mode */
+
+ if (xmode && *ptr == CHAR_NUMBER_SIGN)
+ {
+ ptr++;
+ while (*ptr != 0)
+ {
+ if (IS_NEWLINE(ptr)) { ptr += cd->nllen - 1; break; }
+ ptr++;
+#ifdef SUPPORT_UTF8
+ if (utf8) while ((*ptr & 0xc0) == 0x80) ptr++;
+#endif
+ }
+ if (*ptr == 0) goto FAIL_EXIT;
+ continue;
+ }
+
+ /* Check for the special metacharacters */
+
+ if (*ptr == CHAR_LEFT_PARENTHESIS)
+ {
+ int rc = find_parens_sub(&ptr, cd, name, lorn, xmode, utf8, count);
+ if (rc > 0) return rc;
+ if (*ptr == 0) goto FAIL_EXIT;
+ }
+
+ else if (*ptr == CHAR_RIGHT_PARENTHESIS)
+ {
+ if (dup_parens && *count < hwm_count) *count = hwm_count;
+ goto FAIL_EXIT;
+ }
+
+ else if (*ptr == CHAR_VERTICAL_LINE && dup_parens)
+ {
+ if (*count > hwm_count) hwm_count = *count;
+ *count = start_count;
+ }
+ }
+
+FAIL_EXIT:
+*ptrptr = ptr;
+return -1;
+}
+
+
+
+
+/*************************************************
+* Find forward referenced subpattern *
+*************************************************/
+
+/* This function scans along a pattern's text looking for capturing
+subpatterns, and counting them. If it finds a named pattern that matches the
+name it is given, it returns its number. Alternatively, if the name is NULL, it
+returns when it reaches a given numbered subpattern. This is used for forward
+references to subpatterns. We used to be able to start this scan from the
+current compiling point, using the current count value from cd->bracount, and
+do it all in a single loop, but the addition of the possibility of duplicate
+subpattern numbers means that we have to scan from the very start, in order to
+take account of such duplicates, and to use a recursive function to keep track
+of the different types of group.
+
+Arguments:
+ cd compile background data
+ name name to seek, or NULL if seeking a numbered subpattern
+ lorn name length, or subpattern number if name is NULL
+ xmode TRUE if we are in /x mode
+ utf8 TRUE if we are in UTF-8 mode
+
+Returns: the number of the found subpattern, or -1 if not found
+*/
+
+static int
+find_parens(compile_data *cd, const uschar *name, int lorn, BOOL xmode,
+ BOOL utf8)
+{
+uschar *ptr = (uschar *)cd->start_pattern;
+int count = 0;
+int rc;
+
+/* If the pattern does not start with an opening parenthesis, the first call
+to find_parens_sub() will scan right to the end (if necessary). However, if it
+does start with a parenthesis, find_parens_sub() will return when it hits the
+matching closing parens. That is why we have to have a loop. */
+
+for (;;)
+ {
+ rc = find_parens_sub(&ptr, cd, name, lorn, xmode, utf8, &count);
+ if (rc > 0 || *ptr++ == 0) break;
+ }
+
+return rc;
+}
+
+
+
+
+/*************************************************
+* Find first significant op code *
+*************************************************/
+
+/* This is called by several functions that scan a compiled expression looking
+for a fixed first character, or an anchoring op code etc. It skips over things
+that do not influence this. For some calls, it makes sense to skip negative
+forward and all backward assertions, and also the \b assertion; for others it
+does not.
+
+Arguments:
+ code pointer to the start of the group
+ skipassert TRUE if certain assertions are to be skipped
+
+Returns: pointer to the first significant opcode
+*/
+
+static const uschar*
+first_significant_code(const uschar *code, BOOL skipassert)
+{
+for (;;)
+ {
+ switch ((int)*code)
+ {
+ case OP_ASSERT_NOT:
+ case OP_ASSERTBACK:
+ case OP_ASSERTBACK_NOT:
+ if (!skipassert) return code;
+ do code += GET(code, 1); while (*code == OP_ALT);
+ code += _pcre_OP_lengths[*code];
+ break;
+
+ case OP_WORD_BOUNDARY:
+ case OP_NOT_WORD_BOUNDARY:
+ if (!skipassert) return code;
+ /* Fall through */
+
+ case OP_CALLOUT:
+ case OP_CREF:
+ case OP_NCREF:
+ case OP_RREF:
+ case OP_NRREF:
+ case OP_DEF:
+ code += _pcre_OP_lengths[*code];
+ break;
+
+ default:
+ return code;
+ }
+ }
+/* Control never reaches here */
+}
+
+
+
+
+/*************************************************
+* Find the fixed length of a branch *
+*************************************************/
+
+/* Scan a branch and compute the fixed length of subject that will match it,
+if the length is fixed. This is needed for dealing with backward assertions.
+In UTF8 mode, the result is in characters rather than bytes. The branch is
+temporarily terminated with OP_END when this function is called.
+
+This function is called when a backward assertion is encountered, so that if it
+fails, the error message can point to the correct place in the pattern.
+However, we cannot do this when the assertion contains subroutine calls,
+because they can be forward references. We solve this by remembering this case
+and doing the check at the end; a flag specifies which mode we are running in.
+
+Arguments:
+ code points to the start of the pattern (the bracket)
+ utf8 TRUE in UTF-8 mode
+ atend TRUE if called when the pattern is complete
+ cd the "compile data" structure
+
+Returns: the fixed length,
+ or -1 if there is no fixed length,
+ or -2 if \C was encountered
+ or -3 if an OP_RECURSE item was encountered and atend is FALSE
+*/
+
+static int
+find_fixedlength(uschar *code, BOOL utf8, BOOL atend, compile_data *cd)
+{
+int length = -1;
+
+register int branchlength = 0;
+register uschar *cc = code + 1 + LINK_SIZE;
+
+/* Scan along the opcodes for this branch. If we get to the end of the
+branch, check the length against that of the other branches. */
+
+for (;;)
+ {
+ int d;
+ uschar *ce, *cs;
+ register int op = *cc;
+ switch (op)
+ {
+ /* We only need to continue for OP_CBRA (normal capturing bracket) and
+ OP_BRA (normal non-capturing bracket) because the other variants of these
+ opcodes are all concerned with unlimited repeated groups, which of course
+ are not of fixed length. They will cause a -1 response from the default
+ case of this switch. */
+
+ case OP_CBRA:
+ case OP_BRA:
+ case OP_ONCE:
+ case OP_COND:
+ d = find_fixedlength(cc + ((op == OP_CBRA)? 2:0), utf8, atend, cd);
+ if (d < 0) return d;
+ branchlength += d;
+ do cc += GET(cc, 1); while (*cc == OP_ALT);
+ cc += 1 + LINK_SIZE;
+ break;
+
+ /* Reached end of a branch; if it's a ket it is the end of a nested
+ call. If it's ALT it is an alternation in a nested call. If it is
+ END it's the end of the outer call. All can be handled by the same code.
+ Note that we must not include the OP_KETRxxx opcodes here, because they
+ all imply an unlimited repeat. */
+
+ case OP_ALT:
+ case OP_KET:
+ case OP_END:
+ if (length < 0) length = branchlength;
+ else if (length != branchlength) return -1;
+ if (*cc != OP_ALT) return length;
+ cc += 1 + LINK_SIZE;
+ branchlength = 0;
+ break;
+
+ /* A true recursion implies not fixed length, but a subroutine call may
+ be OK. If the subroutine is a forward reference, we can't deal with
+ it until the end of the pattern, so return -3. */
+
+ case OP_RECURSE:
+ if (!atend) return -3;
+ cs = ce = (uschar *)cd->start_code + GET(cc, 1); /* Start subpattern */
+ do ce += GET(ce, 1); while (*ce == OP_ALT); /* End subpattern */
+ if (cc > cs && cc < ce) return -1; /* Recursion */
+ d = find_fixedlength(cs + 2, utf8, atend, cd);
+ if (d < 0) return d;
+ branchlength += d;
+ cc += 1 + LINK_SIZE;
+ break;
+
+ /* Skip over assertive subpatterns */
+
+ case OP_ASSERT:
+ case OP_ASSERT_NOT:
+ case OP_ASSERTBACK:
+ case OP_ASSERTBACK_NOT:
+ do cc += GET(cc, 1); while (*cc == OP_ALT);
+ /* Fall through */
+
+ /* Skip over things that don't match chars */
+
+ case OP_REVERSE:
+ case OP_CREF:
+ case OP_NCREF:
+ case OP_RREF:
+ case OP_NRREF:
+ case OP_DEF:
+ case OP_CALLOUT:
+ case OP_SOD:
+ case OP_SOM:
+ case OP_SET_SOM:
+ case OP_EOD:
+ case OP_EODN:
+ case OP_CIRC:
+ case OP_CIRCM:
+ case OP_DOLL:
+ case OP_DOLLM:
+ case OP_NOT_WORD_BOUNDARY:
+ case OP_WORD_BOUNDARY:
+ cc += _pcre_OP_lengths[*cc];
+ break;
+
+ /* Handle literal characters */
+
+ case OP_CHAR:
+ case OP_CHARI:
+ case OP_NOT:
+ case OP_NOTI:
+ branchlength++;
+ cc += 2;
+#ifdef SUPPORT_UTF8
+ if (utf8 && cc[-1] >= 0xc0) cc += _pcre_utf8_table4[cc[-1] & 0x3f];
+#endif
+ break;
+
+ /* Handle exact repetitions. The count is already in characters, but we
+ need to skip over a multibyte character in UTF8 mode. */
+
+ case OP_EXACT:
+ branchlength += GET2(cc,1);
+ cc += 4;
+#ifdef SUPPORT_UTF8
+ if (utf8 && cc[-1] >= 0xc0) cc += _pcre_utf8_table4[cc[-1] & 0x3f];
+#endif
+ break;
+
+ case OP_TYPEEXACT:
+ branchlength += GET2(cc,1);
+ if (cc[3] == OP_PROP || cc[3] == OP_NOTPROP) cc += 2;
+ cc += 4;
+ break;
+
+ /* Handle single-char matchers */
+
+ case OP_PROP:
+ case OP_NOTPROP:
+ cc += 2;
+ /* Fall through */
+
+ case OP_NOT_DIGIT:
+ case OP_DIGIT:
+ case OP_NOT_WHITESPACE:
+ case OP_WHITESPACE:
+ case OP_NOT_WORDCHAR:
+ case OP_WORDCHAR:
+ case OP_ANY:
+ case OP_ALLANY:
+ branchlength++;
+ cc++;
+ break;
+
+ /* The single-byte matcher isn't allowed */
+
+ case OP_ANYBYTE:
+ return -2;
+
+ /* Check a class for variable quantification */
+
+#ifdef SUPPORT_UTF8
+ case OP_XCLASS:
+ cc += GET(cc, 1) - 33;
+ /* Fall through */
+#endif
+
+ case OP_CLASS:
+ case OP_NCLASS:
+ cc += 33;
+
+ switch (*cc)
+ {
+ case OP_CRSTAR:
+ case OP_CRMINSTAR:
+ case OP_CRQUERY:
+ case OP_CRMINQUERY:
+ return -1;
+
+ case OP_CRRANGE:
+ case OP_CRMINRANGE:
+ if (GET2(cc,1) != GET2(cc,3)) return -1;
+ branchlength += GET2(cc,1);
+ cc += 5;
+ break;
+
+ default:
+ branchlength++;
+ }
+ break;
+
+ /* Anything else is variable length */
+
+ default:
+ return -1;
+ }
+ }
+/* Control never gets here */
+}
+
+
+
+
+/*************************************************
+* Scan compiled regex for specific bracket *
+*************************************************/
+
+/* This little function scans through a compiled pattern until it finds a
+capturing bracket with the given number, or, if the number is negative, an
+instance of OP_REVERSE for a lookbehind. The function is global in the C sense
+so that it can be called from pcre_study() when finding the minimum matching
+length.
+
+Arguments:
+ code points to start of expression
+ utf8 TRUE in UTF-8 mode
+ number the required bracket number or negative to find a lookbehind
+
+Returns: pointer to the opcode for the bracket, or NULL if not found
+*/
+
+const uschar *
+_pcre_find_bracket(const uschar *code, BOOL utf8, int number)
+{
+for (;;)
+ {
+ register int c = *code;
+
+ if (c == OP_END) return NULL;
+
+ /* XCLASS is used for classes that cannot be represented just by a bit
+ map. This includes negated single high-valued characters. The length in
+ the table is zero; the actual length is stored in the compiled code. */
+
+ if (c == OP_XCLASS) code += GET(code, 1);
+
+ /* Handle recursion */
+
+ else if (c == OP_REVERSE)
+ {
+ if (number < 0) return (uschar *)code;
+ code += _pcre_OP_lengths[c];
+ }
+
+ /* Handle capturing bracket */
+
+ else if (c == OP_CBRA || c == OP_SCBRA ||
+ c == OP_CBRAPOS || c == OP_SCBRAPOS)
+ {
+ int n = GET2(code, 1+LINK_SIZE);
+ if (n == number) return (uschar *)code;
+ code += _pcre_OP_lengths[c];
+ }
+
+ /* Otherwise, we can get the item's length from the table, except that for
+ repeated character types, we have to test for \p and \P, which have an extra
+ two bytes of parameters, and for MARK/PRUNE/SKIP/THEN with an argument, we
+ must add in its length. */
+
+ else
+ {
+ switch(c)
+ {
+ case OP_TYPESTAR:
+ case OP_TYPEMINSTAR:
+ case OP_TYPEPLUS:
+ case OP_TYPEMINPLUS:
+ case OP_TYPEQUERY:
+ case OP_TYPEMINQUERY:
+ case OP_TYPEPOSSTAR:
+ case OP_TYPEPOSPLUS:
+ case OP_TYPEPOSQUERY:
+ if (code[1] == OP_PROP || code[1] == OP_NOTPROP) code += 2;
+ break;
+
+ case OP_TYPEUPTO:
+ case OP_TYPEMINUPTO:
+ case OP_TYPEEXACT:
+ case OP_TYPEPOSUPTO:
+ if (code[3] == OP_PROP || code[3] == OP_NOTPROP) code += 2;
+ break;
+
+ case OP_MARK:
+ case OP_PRUNE_ARG:
+ case OP_SKIP_ARG:
+ code += code[1];
+ break;
+
+ case OP_THEN_ARG:
+ code += code[1+LINK_SIZE];
+ break;
+ }
+
+ /* Add in the fixed length from the table */
+
+ code += _pcre_OP_lengths[c];
+
+ /* In UTF-8 mode, opcodes that are followed by a character may be followed by
+ a multi-byte character. The length in the table is a minimum, so we have to
+ arrange to skip the extra bytes. */
+
+#ifdef SUPPORT_UTF8
+ if (utf8) switch(c)
+ {
+ case OP_CHAR:
+ case OP_CHARI:
+ case OP_EXACT:
+ case OP_EXACTI:
+ case OP_UPTO:
+ case OP_UPTOI:
+ case OP_MINUPTO:
+ case OP_MINUPTOI:
+ case OP_POSUPTO:
+ case OP_POSUPTOI:
+ case OP_STAR:
+ case OP_STARI:
+ case OP_MINSTAR:
+ case OP_MINSTARI:
+ case OP_POSSTAR:
+ case OP_POSSTARI:
+ case OP_PLUS:
+ case OP_PLUSI:
+ case OP_MINPLUS:
+ case OP_MINPLUSI:
+ case OP_POSPLUS:
+ case OP_POSPLUSI:
+ case OP_QUERY:
+ case OP_QUERYI:
+ case OP_MINQUERY:
+ case OP_MINQUERYI:
+ case OP_POSQUERY:
+ case OP_POSQUERYI:
+ if (code[-1] >= 0xc0) code += _pcre_utf8_table4[code[-1] & 0x3f];
+ break;
+ }
+#else
+ (void)(utf8); /* Keep compiler happy by referencing function argument */
+#endif
+ }
+ }
+}
+
+
+
+/*************************************************
+* Scan compiled regex for recursion reference *
+*************************************************/
+
+/* This little function scans through a compiled pattern until it finds an
+instance of OP_RECURSE.
+
+Arguments:
+ code points to start of expression
+ utf8 TRUE in UTF-8 mode
+
+Returns: pointer to the opcode for OP_RECURSE, or NULL if not found
+*/
+
+static const uschar *
+find_recurse(const uschar *code, BOOL utf8)
+{
+for (;;)
+ {
+ register int c = *code;
+ if (c == OP_END) return NULL;
+ if (c == OP_RECURSE) return code;
+
+ /* XCLASS is used for classes that cannot be represented just by a bit
+ map. This includes negated single high-valued characters. The length in
+ the table is zero; the actual length is stored in the compiled code. */
+
+ if (c == OP_XCLASS) code += GET(code, 1);
+
+ /* Otherwise, we can get the item's length from the table, except that for
+ repeated character types, we have to test for \p and \P, which have an extra
+ two bytes of parameters, and for MARK/PRUNE/SKIP/THEN with an argument, we
+ must add in its length. */
+
+ else
+ {
+ switch(c)
+ {
+ case OP_TYPESTAR:
+ case OP_TYPEMINSTAR:
+ case OP_TYPEPLUS:
+ case OP_TYPEMINPLUS:
+ case OP_TYPEQUERY:
+ case OP_TYPEMINQUERY:
+ case OP_TYPEPOSSTAR:
+ case OP_TYPEPOSPLUS:
+ case OP_TYPEPOSQUERY:
+ if (code[1] == OP_PROP || code[1] == OP_NOTPROP) code += 2;
+ break;
+
+ case OP_TYPEPOSUPTO:
+ case OP_TYPEUPTO:
+ case OP_TYPEMINUPTO:
+ case OP_TYPEEXACT:
+ if (code[3] == OP_PROP || code[3] == OP_NOTPROP) code += 2;
+ break;
+
+ case OP_MARK:
+ case OP_PRUNE_ARG:
+ case OP_SKIP_ARG:
+ code += code[1];
+ break;
+
+ case OP_THEN_ARG:
+ code += code[1+LINK_SIZE];
+ break;
+ }
+
+ /* Add in the fixed length from the table */
+
+ code += _pcre_OP_lengths[c];
+
+ /* In UTF-8 mode, opcodes that are followed by a character may be followed
+ by a multi-byte character. The length in the table is a minimum, so we have
+ to arrange to skip the extra bytes. */
+
+#ifdef SUPPORT_UTF8
+ if (utf8) switch(c)
+ {
+ case OP_CHAR:
+ case OP_CHARI:
+ case OP_EXACT:
+ case OP_EXACTI:
+ case OP_UPTO:
+ case OP_UPTOI:
+ case OP_MINUPTO:
+ case OP_MINUPTOI:
+ case OP_POSUPTO:
+ case OP_POSUPTOI:
+ case OP_STAR:
+ case OP_STARI:
+ case OP_MINSTAR:
+ case OP_MINSTARI:
+ case OP_POSSTAR:
+ case OP_POSSTARI:
+ case OP_PLUS:
+ case OP_PLUSI:
+ case OP_MINPLUS:
+ case OP_MINPLUSI:
+ case OP_POSPLUS:
+ case OP_POSPLUSI:
+ case OP_QUERY:
+ case OP_QUERYI:
+ case OP_MINQUERY:
+ case OP_MINQUERYI:
+ case OP_POSQUERY:
+ case OP_POSQUERYI:
+ if (code[-1] >= 0xc0) code += _pcre_utf8_table4[code[-1] & 0x3f];
+ break;
+ }
+#else
+ (void)(utf8); /* Keep compiler happy by referencing function argument */
+#endif
+ }
+ }
+}
+
+
+
+/*************************************************
+* Scan compiled branch for non-emptiness *
+*************************************************/
+
+/* This function scans through a branch of a compiled pattern to see whether it
+can match the empty string or not. It is called from could_be_empty()
+below and from compile_branch() when checking for an unlimited repeat of a
+group that can match nothing. Note that first_significant_code() skips over
+backward and negative forward assertions when its final argument is TRUE. If we
+hit an unclosed bracket, we return "empty" - this means we've struck an inner
+bracket whose current branch will already have been scanned.
+
+Arguments:
+ code points to start of search
+ endcode points to where to stop
+ utf8 TRUE if in UTF8 mode
+ cd contains pointers to tables etc.
+
+Returns: TRUE if what is matched could be empty
+*/
+
+static BOOL
+could_be_empty_branch(const uschar *code, const uschar *endcode, BOOL utf8,
+ compile_data *cd)
+{
+register int c;
+for (code = first_significant_code(code + _pcre_OP_lengths[*code], TRUE);
+ code < endcode;
+ code = first_significant_code(code + _pcre_OP_lengths[c], TRUE))
+ {
+ const uschar *ccode;
+
+ c = *code;
+
+ /* Skip over forward assertions; the other assertions are skipped by
+ first_significant_code() with a TRUE final argument. */
+
+ if (c == OP_ASSERT)
+ {
+ do code += GET(code, 1); while (*code == OP_ALT);
+ c = *code;
+ continue;
+ }
+
+ /* For a recursion/subroutine call, if its end has been reached, which
+ implies a backward reference subroutine call, we can scan it. If it's a
+ forward reference subroutine call, we can't. To detect forward reference
+ we have to scan up the list that is kept in the workspace. This function is
+ called only when doing the real compile, not during the pre-compile that
+ measures the size of the compiled pattern. */
+
+ if (c == OP_RECURSE)
+ {
+ const uschar *scode;
+ BOOL empty_branch;
+
+ /* Test for forward reference */
+
+ for (scode = cd->start_workspace; scode < cd->hwm; scode += LINK_SIZE)
+ if (GET(scode, 0) == code + 1 - cd->start_code) return TRUE;
+
+ /* Not a forward reference, test for completed backward reference */
+
+ empty_branch = FALSE;
+ scode = cd->start_code + GET(code, 1);
+ if (GET(scode, 1) == 0) return TRUE; /* Unclosed */
+
+ /* Completed backwards reference */
+
+ do
+ {
+ if (could_be_empty_branch(scode, endcode, utf8, cd))
+ {
+ empty_branch = TRUE;
+ break;
+ }
+ scode += GET(scode, 1);
+ }
+ while (*scode == OP_ALT);
+
+ if (!empty_branch) return FALSE; /* All branches are non-empty */
+ continue;
+ }
+
+ /* Groups with zero repeats can of course be empty; skip them. */
+
+ if (c == OP_BRAZERO || c == OP_BRAMINZERO || c == OP_SKIPZERO ||
+ c == OP_BRAPOSZERO)
+ {
+ code += _pcre_OP_lengths[c];
+ do code += GET(code, 1); while (*code == OP_ALT);
+ c = *code;
+ continue;
+ }
+
+ /* A nested group that is already marked as "could be empty" can just be
+ skipped. */
+
+ if (c == OP_SBRA || c == OP_SBRAPOS ||
+ c == OP_SCBRA || c == OP_SCBRAPOS)
+ {
+ do code += GET(code, 1); while (*code == OP_ALT);
+ c = *code;
+ continue;
+ }
+
+ /* For other groups, scan the branches. */
+
+ if (c == OP_BRA || c == OP_BRAPOS ||
+ c == OP_CBRA || c == OP_CBRAPOS ||
+ c == OP_ONCE || c == OP_COND)
+ {
+ BOOL empty_branch;
+ if (GET(code, 1) == 0) return TRUE; /* Hit unclosed bracket */
+
+ /* If a conditional group has only one branch, there is a second, implied,
+ empty branch, so just skip over the conditional, because it could be empty.
+ Otherwise, scan the individual branches of the group. */
+
+ if (c == OP_COND && code[GET(code, 1)] != OP_ALT)
+ code += GET(code, 1);
+ else
+ {
+ empty_branch = FALSE;
+ do
+ {
+ if (!empty_branch && could_be_empty_branch(code, endcode, utf8, cd))
+ empty_branch = TRUE;
+ code += GET(code, 1);
+ }
+ while (*code == OP_ALT);
+ if (!empty_branch) return FALSE; /* All branches are non-empty */
+ }
+
+ c = *code;
+ continue;
+ }
+
+ /* Handle the other opcodes */
+
+ switch (c)
+ {
+ /* Check for quantifiers after a class. XCLASS is used for classes that
+ cannot be represented just by a bit map. This includes negated single
+ high-valued characters. The length in _pcre_OP_lengths[] is zero; the
+ actual length is stored in the compiled code, so we must update "code"
+ here. */
+
+#ifdef SUPPORT_UTF8
+ case OP_XCLASS:
+ ccode = code += GET(code, 1);
+ goto CHECK_CLASS_REPEAT;
+#endif
+
+ case OP_CLASS:
+ case OP_NCLASS:
+ ccode = code + 33;
+
+#ifdef SUPPORT_UTF8
+ CHECK_CLASS_REPEAT:
+#endif
+
+ switch (*ccode)
+ {
+ case OP_CRSTAR: /* These could be empty; continue */
+ case OP_CRMINSTAR:
+ case OP_CRQUERY:
+ case OP_CRMINQUERY:
+ break;
+
+ default: /* Non-repeat => class must match */
+ case OP_CRPLUS: /* These repeats aren't empty */
+ case OP_CRMINPLUS:
+ return FALSE;
+
+ case OP_CRRANGE:
+ case OP_CRMINRANGE:
+ if (GET2(ccode, 1) > 0) return FALSE; /* Minimum > 0 */
+ break;
+ }
+ break;
+
+ /* Opcodes that must match a character */
+
+ case OP_PROP:
+ case OP_NOTPROP:
+ case OP_EXTUNI:
+ case OP_NOT_DIGIT:
+ case OP_DIGIT:
+ case OP_NOT_WHITESPACE:
+ case OP_WHITESPACE:
+ case OP_NOT_WORDCHAR:
+ case OP_WORDCHAR:
+ case OP_ANY:
+ case OP_ALLANY:
+ case OP_ANYBYTE:
+ case OP_CHAR:
+ case OP_CHARI:
+ case OP_NOT:
+ case OP_NOTI:
+ case OP_PLUS:
+ case OP_MINPLUS:
+ case OP_POSPLUS:
+ case OP_EXACT:
+ case OP_NOTPLUS:
+ case OP_NOTMINPLUS:
+ case OP_NOTPOSPLUS:
+ case OP_NOTEXACT:
+ case OP_TYPEPLUS:
+ case OP_TYPEMINPLUS:
+ case OP_TYPEPOSPLUS:
+ case OP_TYPEEXACT:
+ return FALSE;
+
+ /* These are going to continue, as they may be empty, but we have to
+ fudge the length for the \p and \P cases. */
+
+ case OP_TYPESTAR:
+ case OP_TYPEMINSTAR:
+ case OP_TYPEPOSSTAR:
+ case OP_TYPEQUERY:
+ case OP_TYPEMINQUERY:
+ case OP_TYPEPOSQUERY:
+ if (code[1] == OP_PROP || code[1] == OP_NOTPROP) code += 2;
+ break;
+
+ /* Same for these */
+
+ case OP_TYPEUPTO:
+ case OP_TYPEMINUPTO:
+ case OP_TYPEPOSUPTO:
+ if (code[3] == OP_PROP || code[3] == OP_NOTPROP) code += 2;
+ break;
+
+ /* End of branch */
+
+ case OP_KET:
+ case OP_KETRMAX:
+ case OP_KETRMIN:
+ case OP_KETRPOS:
+ case OP_ALT:
+ return TRUE;
+
+ /* In UTF-8 mode, STAR, MINSTAR, POSSTAR, QUERY, MINQUERY, POSQUERY, UPTO,
+ MINUPTO, and POSUPTO may be followed by a multibyte character */
+
+#ifdef SUPPORT_UTF8
+ case OP_STAR:
+ case OP_STARI:
+ case OP_MINSTAR:
+ case OP_MINSTARI:
+ case OP_POSSTAR:
+ case OP_POSSTARI:
+ case OP_QUERY:
+ case OP_QUERYI:
+ case OP_MINQUERY:
+ case OP_MINQUERYI:
+ case OP_POSQUERY:
+ case OP_POSQUERYI:
+ if (utf8 && code[1] >= 0xc0) code += _pcre_utf8_table4[code[1] & 0x3f];
+ break;
+
+ case OP_UPTO:
+ case OP_UPTOI:
+ case OP_MINUPTO:
+ case OP_MINUPTOI:
+ case OP_POSUPTO:
+ case OP_POSUPTOI:
+ if (utf8 && code[3] >= 0xc0) code += _pcre_utf8_table4[code[3] & 0x3f];
+ break;
+#endif
+
+ /* MARK, and PRUNE/SKIP/THEN with an argument must skip over the argument
+ string. */
+
+ case OP_MARK:
+ case OP_PRUNE_ARG:
+ case OP_SKIP_ARG:
+ code += code[1];
+ break;
+
+ case OP_THEN_ARG:
+ code += code[1+LINK_SIZE];
+ break;
+
+ /* None of the remaining opcodes are required to match a character. */
+
+ default:
+ break;
+ }
+ }
+
+return TRUE;
+}
+
+
+
+/*************************************************
+* Scan compiled regex for non-emptiness *
+*************************************************/
+
+/* This function is called to check for left recursive calls. We want to check
+the current branch of the current pattern to see if it could match the empty
+string. If it could, we must look outwards for branches at other levels,
+stopping when we pass beyond the bracket which is the subject of the recursion.
+This function is called only during the real compile, not during the
+pre-compile.
+
+Arguments:
+ code points to start of the recursion
+ endcode points to where to stop (current RECURSE item)
+ bcptr points to the chain of current (unclosed) branch starts
+ utf8 TRUE if in UTF-8 mode
+ cd pointers to tables etc
+
+Returns: TRUE if what is matched could be empty
+*/
+
+static BOOL
+could_be_empty(const uschar *code, const uschar *endcode, branch_chain *bcptr,
+ BOOL utf8, compile_data *cd)
+{
+while (bcptr != NULL && bcptr->current_branch >= code)
+ {
+ if (!could_be_empty_branch(bcptr->current_branch, endcode, utf8, cd))
+ return FALSE;
+ bcptr = bcptr->outer;
+ }
+return TRUE;
+}
+
+
+
+/*************************************************
+* Check for POSIX class syntax *
+*************************************************/
+
+/* This function is called when the sequence "[:" or "[." or "[=" is
+encountered in a character class. It checks whether this is followed by a
+sequence of characters terminated by a matching ":]" or ".]" or "=]". If we
+reach an unescaped ']' without the special preceding character, return FALSE.
+
+Originally, this function only recognized a sequence of letters between the
+terminators, but it seems that Perl recognizes any sequence of characters,
+though of course unknown POSIX names are subsequently rejected. Perl gives an
+"Unknown POSIX class" error for [:f\oo:] for example, where previously PCRE
+didn't consider this to be a POSIX class. Likewise for [:1234:].
+
+The problem in trying to be exactly like Perl is in the handling of escapes. We
+have to be sure that [abc[:x\]pqr] is *not* treated as containing a POSIX
+class, but [abc[:x\]pqr:]] is (so that an error can be generated). The code
+below handles the special case of \], but does not try to do any other escape
+processing. This makes it different from Perl for cases such as [:l\ower:]
+where Perl recognizes it as the POSIX class "lower" but PCRE does not recognize
+"l\ower". This is a lesser evil that not diagnosing bad classes when Perl does,
+I think.
+
+A user pointed out that PCRE was rejecting [:a[:digit:]] whereas Perl was not.
+It seems that the appearance of a nested POSIX class supersedes an apparent
+external class. For example, [:a[:digit:]b:] matches "a", "b", ":", or
+a digit. Also, unescaped square brackets may also appear as part of class
+names. For example, [:a[:abc]b:] gives unknown class "[:abc]b:]"in Perl.
+
+Arguments:
+ ptr pointer to the initial [
+ endptr where to return the end pointer
+
+Returns: TRUE or FALSE
+*/
+
+static BOOL
+check_posix_syntax(const uschar *ptr, const uschar **endptr)
+{
+int terminator; /* Don't combine these lines; the Solaris cc */
+terminator = *(++ptr); /* compiler warns about "non-constant" initializer. */
+for (++ptr; *ptr != 0; ptr++)
+ {
+ if (*ptr == CHAR_BACKSLASH && ptr[1] == CHAR_RIGHT_SQUARE_BRACKET)
+ ptr++;
+ else
+ {
+ if (*ptr == terminator && ptr[1] == CHAR_RIGHT_SQUARE_BRACKET)
+ {
+ *endptr = ptr;
+ return TRUE;
+ }
+ if (*ptr == CHAR_LEFT_SQUARE_BRACKET &&
+ (ptr[1] == CHAR_COLON || ptr[1] == CHAR_DOT ||
+ ptr[1] == CHAR_EQUALS_SIGN) &&
+ check_posix_syntax(ptr, endptr))
+ return FALSE;
+ }
+ }
+return FALSE;
+}
+
+
+
+
+/*************************************************
+* Check POSIX class name *
+*************************************************/
+
+/* This function is called to check the name given in a POSIX-style class entry
+such as [:alnum:].
+
+Arguments:
+ ptr points to the first letter
+ len the length of the name
+
+Returns: a value representing the name, or -1 if unknown
+*/
+
+static int
+check_posix_name(const uschar *ptr, int len)
+{
+const char *pn = posix_names;
+register int yield = 0;
+while (posix_name_lengths[yield] != 0)
+ {
+ if (len == posix_name_lengths[yield] &&
+ strncmp((const char *)ptr, pn, len) == 0) return yield;
+ pn += posix_name_lengths[yield] + 1;
+ yield++;
+ }
+return -1;
+}
+
+
+/*************************************************
+* Adjust OP_RECURSE items in repeated group *
+*************************************************/
+
+/* OP_RECURSE items contain an offset from the start of the regex to the group
+that is referenced. This means that groups can be replicated for fixed
+repetition simply by copying (because the recursion is allowed to refer to
+earlier groups that are outside the current group). However, when a group is
+optional (i.e. the minimum quantifier is zero), OP_BRAZERO or OP_SKIPZERO is
+inserted before it, after it has been compiled. This means that any OP_RECURSE
+items within it that refer to the group itself or any contained groups have to
+have their offsets adjusted. That one of the jobs of this function. Before it
+is called, the partially compiled regex must be temporarily terminated with
+OP_END.
+
+This function has been extended with the possibility of forward references for
+recursions and subroutine calls. It must also check the list of such references
+for the group we are dealing with. If it finds that one of the recursions in
+the current group is on this list, it adjusts the offset in the list, not the
+value in the reference (which is a group number).
+
+Arguments:
+ group points to the start of the group
+ adjust the amount by which the group is to be moved
+ utf8 TRUE in UTF-8 mode
+ cd contains pointers to tables etc.
+ save_hwm the hwm forward reference pointer at the start of the group
+
+Returns: nothing
+*/
+
+static void
+adjust_recurse(uschar *group, int adjust, BOOL utf8, compile_data *cd,
+ uschar *save_hwm)
+{
+uschar *ptr = group;
+
+while ((ptr = (uschar *)find_recurse(ptr, utf8)) != NULL)
+ {
+ int offset;
+ uschar *hc;
+
+ /* See if this recursion is on the forward reference list. If so, adjust the
+ reference. */
+
+ for (hc = save_hwm; hc < cd->hwm; hc += LINK_SIZE)
+ {
+ offset = GET(hc, 0);
+ if (cd->start_code + offset == ptr + 1)
+ {
+ PUT(hc, 0, offset + adjust);
+ break;
+ }
+ }
+
+ /* Otherwise, adjust the recursion offset if it's after the start of this
+ group. */
+
+ if (hc >= cd->hwm)
+ {
+ offset = GET(ptr, 1);
+ if (cd->start_code + offset >= group) PUT(ptr, 1, offset + adjust);
+ }
+
+ ptr += 1 + LINK_SIZE;
+ }
+}
+
+
+
+/*************************************************
+* Insert an automatic callout point *
+*************************************************/
+
+/* This function is called when the PCRE_AUTO_CALLOUT option is set, to insert
+callout points before each pattern item.
+
+Arguments:
+ code current code pointer
+ ptr current pattern pointer
+ cd pointers to tables etc
+
+Returns: new code pointer
+*/
+
+static uschar *
+auto_callout(uschar *code, const uschar *ptr, compile_data *cd)
+{
+*code++ = OP_CALLOUT;
+*code++ = 255;
+PUT(code, 0, (int)(ptr - cd->start_pattern)); /* Pattern offset */
+PUT(code, LINK_SIZE, 0); /* Default length */
+return code + 2*LINK_SIZE;
+}
+
+
+
+/*************************************************
+* Complete a callout item *
+*************************************************/
+
+/* A callout item contains the length of the next item in the pattern, which
+we can't fill in till after we have reached the relevant point. This is used
+for both automatic and manual callouts.
+
+Arguments:
+ previous_callout points to previous callout item
+ ptr current pattern pointer
+ cd pointers to tables etc
+
+Returns: nothing
+*/
+
+static void
+complete_callout(uschar *previous_callout, const uschar *ptr, compile_data *cd)
+{
+int length = (int)(ptr - cd->start_pattern - GET(previous_callout, 2));
+PUT(previous_callout, 2 + LINK_SIZE, length);
+}
+
+
+
+#ifdef SUPPORT_UCP
+/*************************************************
+* Get othercase range *
+*************************************************/
+
+/* This function is passed the start and end of a class range, in UTF-8 mode
+with UCP support. It searches up the characters, looking for internal ranges of
+characters in the "other" case. Each call returns the next one, updating the
+start address.
+
+Arguments:
+ cptr points to starting character value; updated
+ d end value
+ ocptr where to put start of othercase range
+ odptr where to put end of othercase range
+
+Yield: TRUE when range returned; FALSE when no more
+*/
+
+static BOOL
+get_othercase_range(unsigned int *cptr, unsigned int d, unsigned int *ocptr,
+ unsigned int *odptr)
+{
+unsigned int c, othercase, next;
+
+for (c = *cptr; c <= d; c++)
+ { if ((othercase = UCD_OTHERCASE(c)) != c) break; }
+
+if (c > d) return FALSE;
+
+*ocptr = othercase;
+next = othercase + 1;
+
+for (++c; c <= d; c++)
+ {
+ if (UCD_OTHERCASE(c) != next) break;
+ next++;
+ }
+
+*odptr = next - 1;
+*cptr = c;
+
+return TRUE;
+}
+
+
+
+/*************************************************
+* Check a character and a property *
+*************************************************/
+
+/* This function is called by check_auto_possessive() when a property item
+is adjacent to a fixed character.
+
+Arguments:
+ c the character
+ ptype the property type
+ pdata the data for the type
+ negated TRUE if it's a negated property (\P or \p{^)
+
+Returns: TRUE if auto-possessifying is OK
+*/
+
+static BOOL
+check_char_prop(int c, int ptype, int pdata, BOOL negated)
+{
+const ucd_record *prop = GET_UCD(c);
+switch(ptype)
+ {
+ case PT_LAMP:
+ return (prop->chartype == ucp_Lu ||
+ prop->chartype == ucp_Ll ||
+ prop->chartype == ucp_Lt) == negated;
+
+ case PT_GC:
+ return (pdata == _pcre_ucp_gentype[prop->chartype]) == negated;
+
+ case PT_PC:
+ return (pdata == prop->chartype) == negated;
+
+ case PT_SC:
+ return (pdata == prop->script) == negated;
+
+ /* These are specials */
+
+ case PT_ALNUM:
+ return (_pcre_ucp_gentype[prop->chartype] == ucp_L ||
+ _pcre_ucp_gentype[prop->chartype] == ucp_N) == negated;
+
+ case PT_SPACE: /* Perl space */
+ return (_pcre_ucp_gentype[prop->chartype] == ucp_Z ||
+ c == CHAR_HT || c == CHAR_NL || c == CHAR_FF || c == CHAR_CR)
+ == negated;
+
+ case PT_PXSPACE: /* POSIX space */
+ return (_pcre_ucp_gentype[prop->chartype] == ucp_Z ||
+ c == CHAR_HT || c == CHAR_NL || c == CHAR_VT ||
+ c == CHAR_FF || c == CHAR_CR)
+ == negated;
+
+ case PT_WORD:
+ return (_pcre_ucp_gentype[prop->chartype] == ucp_L ||
+ _pcre_ucp_gentype[prop->chartype] == ucp_N ||
+ c == CHAR_UNDERSCORE) == negated;
+ }
+return FALSE;
+}
+#endif /* SUPPORT_UCP */
+
+
+
+/*************************************************
+* Check if auto-possessifying is possible *
+*************************************************/
+
+/* This function is called for unlimited repeats of certain items, to see
+whether the next thing could possibly match the repeated item. If not, it makes
+sense to automatically possessify the repeated item.
+
+Arguments:
+ previous pointer to the repeated opcode
+ utf8 TRUE in UTF-8 mode
+ ptr next character in pattern
+ options options bits
+ cd contains pointers to tables etc.
+
+Returns: TRUE if possessifying is wanted
+*/
+
+static BOOL
+check_auto_possessive(const uschar *previous, BOOL utf8, const uschar *ptr,
+ int options, compile_data *cd)
+{
+int c, next;
+int op_code = *previous++;
+
+/* Skip whitespace and comments in extended mode */
+
+if ((options & PCRE_EXTENDED) != 0)
+ {
+ for (;;)
+ {
+ while ((cd->ctypes[*ptr] & ctype_space) != 0) ptr++;
+ if (*ptr == CHAR_NUMBER_SIGN)
+ {
+ ptr++;
+ while (*ptr != 0)
+ {
+ if (IS_NEWLINE(ptr)) { ptr += cd->nllen; break; }
+ ptr++;
+#ifdef SUPPORT_UTF8
+ if (utf8) while ((*ptr & 0xc0) == 0x80) ptr++;
+#endif
+ }
+ }
+ else break;
+ }
+ }
+
+/* If the next item is one that we can handle, get its value. A non-negative
+value is a character, a negative value is an escape value. */
+
+if (*ptr == CHAR_BACKSLASH)
+ {
+ int temperrorcode = 0;
+ next = check_escape(&ptr, &temperrorcode, cd->bracount, options, FALSE);
+ if (temperrorcode != 0) return FALSE;
+ ptr++; /* Point after the escape sequence */
+ }
+
+else if ((cd->ctypes[*ptr] & ctype_meta) == 0)
+ {
+#ifdef SUPPORT_UTF8
+ if (utf8) { GETCHARINC(next, ptr); } else
+#endif
+ next = *ptr++;
+ }
+
+else return FALSE;
+
+/* Skip whitespace and comments in extended mode */
+
+if ((options & PCRE_EXTENDED) != 0)
+ {
+ for (;;)
+ {
+ while ((cd->ctypes[*ptr] & ctype_space) != 0) ptr++;
+ if (*ptr == CHAR_NUMBER_SIGN)
+ {
+ ptr++;
+ while (*ptr != 0)
+ {
+ if (IS_NEWLINE(ptr)) { ptr += cd->nllen; break; }
+ ptr++;
+#ifdef SUPPORT_UTF8
+ if (utf8) while ((*ptr & 0xc0) == 0x80) ptr++;
+#endif
+ }
+ }
+ else break;
+ }
+ }
+
+/* If the next thing is itself optional, we have to give up. */
+
+if (*ptr == CHAR_ASTERISK || *ptr == CHAR_QUESTION_MARK ||
+ strncmp((char *)ptr, STR_LEFT_CURLY_BRACKET STR_0 STR_COMMA, 3) == 0)
+ return FALSE;
+
+/* Now compare the next item with the previous opcode. First, handle cases when
+the next item is a character. */
+
+if (next >= 0) switch(op_code)
+ {
+ case OP_CHAR:
+#ifdef SUPPORT_UTF8
+ GETCHARTEST(c, previous);
+#else
+ c = *previous;
+#endif
+ return c != next;
+
+ /* For CHARI (caseless character) we must check the other case. If we have
+ Unicode property support, we can use it to test the other case of
+ high-valued characters. */
+
+ case OP_CHARI:
+#ifdef SUPPORT_UTF8
+ GETCHARTEST(c, previous);
+#else
+ c = *previous;
+#endif
+ if (c == next) return FALSE;
+#ifdef SUPPORT_UTF8
+ if (utf8)
+ {
+ unsigned int othercase;
+ if (next < 128) othercase = cd->fcc[next]; else
+#ifdef SUPPORT_UCP
+ othercase = UCD_OTHERCASE((unsigned int)next);
+#else
+ othercase = NOTACHAR;
+#endif
+ return (unsigned int)c != othercase;
+ }
+ else
+#endif /* SUPPORT_UTF8 */
+ return (c != cd->fcc[next]); /* Non-UTF-8 mode */
+
+ /* For OP_NOT and OP_NOTI, the data is always a single-byte character. These
+ opcodes are not used for multi-byte characters, because they are coded using
+ an XCLASS instead. */
+
+ case OP_NOT:
+ return (c = *previous) == next;
+
+ case OP_NOTI:
+ if ((c = *previous) == next) return TRUE;
+#ifdef SUPPORT_UTF8
+ if (utf8)
+ {
+ unsigned int othercase;
+ if (next < 128) othercase = cd->fcc[next]; else
+#ifdef SUPPORT_UCP
+ othercase = UCD_OTHERCASE(next);
+#else
+ othercase = NOTACHAR;
+#endif
+ return (unsigned int)c == othercase;
+ }
+ else
+#endif /* SUPPORT_UTF8 */
+ return (c == cd->fcc[next]); /* Non-UTF-8 mode */
+
+ /* Note that OP_DIGIT etc. are generated only when PCRE_UCP is *not* set.
+ When it is set, \d etc. are converted into OP_(NOT_)PROP codes. */
+
+ case OP_DIGIT:
+ return next > 127 || (cd->ctypes[next] & ctype_digit) == 0;
+
+ case OP_NOT_DIGIT:
+ return next <= 127 && (cd->ctypes[next] & ctype_digit) != 0;
+
+ case OP_WHITESPACE:
+ return next > 127 || (cd->ctypes[next] & ctype_space) == 0;
+
+ case OP_NOT_WHITESPACE:
+ return next <= 127 && (cd->ctypes[next] & ctype_space) != 0;
+
+ case OP_WORDCHAR:
+ return next > 127 || (cd->ctypes[next] & ctype_word) == 0;
+
+ case OP_NOT_WORDCHAR:
+ return next <= 127 && (cd->ctypes[next] & ctype_word) != 0;
+
+ case OP_HSPACE:
+ case OP_NOT_HSPACE:
+ switch(next)
+ {
+ case 0x09:
+ case 0x20:
+ case 0xa0:
+ case 0x1680:
+ case 0x180e:
+ case 0x2000:
+ case 0x2001:
+ case 0x2002:
+ case 0x2003:
+ case 0x2004:
+ case 0x2005:
+ case 0x2006:
+ case 0x2007:
+ case 0x2008:
+ case 0x2009:
+ case 0x200A:
+ case 0x202f:
+ case 0x205f:
+ case 0x3000:
+ return op_code == OP_NOT_HSPACE;
+ default:
+ return op_code != OP_NOT_HSPACE;
+ }
+
+ case OP_ANYNL:
+ case OP_VSPACE:
+ case OP_NOT_VSPACE:
+ switch(next)
+ {
+ case 0x0a:
+ case 0x0b:
+ case 0x0c:
+ case 0x0d:
+ case 0x85:
+ case 0x2028:
+ case 0x2029:
+ return op_code == OP_NOT_VSPACE;
+ default:
+ return op_code != OP_NOT_VSPACE;
+ }
+
+#ifdef SUPPORT_UCP
+ case OP_PROP:
+ return check_char_prop(next, previous[0], previous[1], FALSE);
+
+ case OP_NOTPROP:
+ return check_char_prop(next, previous[0], previous[1], TRUE);
+#endif
+
+ default:
+ return FALSE;
+ }
+
+
+/* Handle the case when the next item is \d, \s, etc. Note that when PCRE_UCP
+is set, \d turns into ESC_du rather than ESC_d, etc., so ESC_d etc. are
+generated only when PCRE_UCP is *not* set, that is, when only ASCII
+characteristics are recognized. Similarly, the opcodes OP_DIGIT etc. are
+replaced by OP_PROP codes when PCRE_UCP is set. */
+
+switch(op_code)
+ {
+ case OP_CHAR:
+ case OP_CHARI:
+#ifdef SUPPORT_UTF8
+ GETCHARTEST(c, previous);
+#else
+ c = *previous;
+#endif
+ switch(-next)
+ {
+ case ESC_d:
+ return c > 127 || (cd->ctypes[c] & ctype_digit) == 0;
+
+ case ESC_D:
+ return c <= 127 && (cd->ctypes[c] & ctype_digit) != 0;
+
+ case ESC_s:
+ return c > 127 || (cd->ctypes[c] & ctype_space) == 0;
+
+ case ESC_S:
+ return c <= 127 && (cd->ctypes[c] & ctype_space) != 0;
+
+ case ESC_w:
+ return c > 127 || (cd->ctypes[c] & ctype_word) == 0;
+
+ case ESC_W:
+ return c <= 127 && (cd->ctypes[c] & ctype_word) != 0;
+
+ case ESC_h:
+ case ESC_H:
+ switch(c)
+ {
+ case 0x09:
+ case 0x20:
+ case 0xa0:
+ case 0x1680:
+ case 0x180e:
+ case 0x2000:
+ case 0x2001:
+ case 0x2002:
+ case 0x2003:
+ case 0x2004:
+ case 0x2005:
+ case 0x2006:
+ case 0x2007:
+ case 0x2008:
+ case 0x2009:
+ case 0x200A:
+ case 0x202f:
+ case 0x205f:
+ case 0x3000:
+ return -next != ESC_h;
+ default:
+ return -next == ESC_h;
+ }
+
+ case ESC_v:
+ case ESC_V:
+ switch(c)
+ {
+ case 0x0a:
+ case 0x0b:
+ case 0x0c:
+ case 0x0d:
+ case 0x85:
+ case 0x2028:
+ case 0x2029:
+ return -next != ESC_v;
+ default:
+ return -next == ESC_v;
+ }
+
+ /* When PCRE_UCP is set, these values get generated for \d etc. Find
+ their substitutions and process them. The result will always be either
+ -ESC_p or -ESC_P. Then fall through to process those values. */
+
+#ifdef SUPPORT_UCP
+ case ESC_du:
+ case ESC_DU:
+ case ESC_wu:
+ case ESC_WU:
+ case ESC_su:
+ case ESC_SU:
+ {
+ int temperrorcode = 0;
+ ptr = substitutes[-next - ESC_DU];
+ next = check_escape(&ptr, &temperrorcode, 0, options, FALSE);
+ if (temperrorcode != 0) return FALSE;
+ ptr++; /* For compatibility */
+ }
+ /* Fall through */
+
+ case ESC_p:
+ case ESC_P:
+ {
+ int ptype, pdata, errorcodeptr;
+ BOOL negated;
+
+ ptr--; /* Make ptr point at the p or P */
+ ptype = get_ucp(&ptr, &negated, &pdata, &errorcodeptr);
+ if (ptype < 0) return FALSE;
+ ptr++; /* Point past the final curly ket */
+
+ /* If the property item is optional, we have to give up. (When generated
+ from \d etc by PCRE_UCP, this test will have been applied much earlier,
+ to the original \d etc. At this point, ptr will point to a zero byte. */
+
+ if (*ptr == CHAR_ASTERISK || *ptr == CHAR_QUESTION_MARK ||
+ strncmp((char *)ptr, STR_LEFT_CURLY_BRACKET STR_0 STR_COMMA, 3) == 0)
+ return FALSE;
+
+ /* Do the property check. */
+
+ return check_char_prop(c, ptype, pdata, (next == -ESC_P) != negated);
+ }
+#endif
+
+ default:
+ return FALSE;
+ }
+
+ /* In principle, support for Unicode properties should be integrated here as
+ well. It means re-organizing the above code so as to get hold of the property
+ values before switching on the op-code. However, I wonder how many patterns
+ combine ASCII \d etc with Unicode properties? (Note that if PCRE_UCP is set,
+ these op-codes are never generated.) */
+
+ case OP_DIGIT:
+ return next == -ESC_D || next == -ESC_s || next == -ESC_W ||
+ next == -ESC_h || next == -ESC_v || next == -ESC_R;
+
+ case OP_NOT_DIGIT:
+ return next == -ESC_d;
+
+ case OP_WHITESPACE:
+ return next == -ESC_S || next == -ESC_d || next == -ESC_w || next == -ESC_R;
+
+ case OP_NOT_WHITESPACE:
+ return next == -ESC_s || next == -ESC_h || next == -ESC_v;
+
+ case OP_HSPACE:
+ return next == -ESC_S || next == -ESC_H || next == -ESC_d ||
+ next == -ESC_w || next == -ESC_v || next == -ESC_R;
+
+ case OP_NOT_HSPACE:
+ return next == -ESC_h;
+
+ /* Can't have \S in here because VT matches \S (Perl anomaly) */
+ case OP_ANYNL:
+ case OP_VSPACE:
+ return next == -ESC_V || next == -ESC_d || next == -ESC_w;
+
+ case OP_NOT_VSPACE:
+ return next == -ESC_v || next == -ESC_R;
+
+ case OP_WORDCHAR:
+ return next == -ESC_W || next == -ESC_s || next == -ESC_h ||
+ next == -ESC_v || next == -ESC_R;
+
+ case OP_NOT_WORDCHAR:
+ return next == -ESC_w || next == -ESC_d;
+
+ default:
+ return FALSE;
+ }
+
+/* Control does not reach here */
+}
+
+
+
+/*************************************************
+* Compile one branch *
+*************************************************/
+
+/* Scan the pattern, compiling it into the a vector. If the options are
+changed during the branch, the pointer is used to change the external options
+bits. This function is used during the pre-compile phase when we are trying
+to find out the amount of memory needed, as well as during the real compile
+phase. The value of lengthptr distinguishes the two phases.
+
+Arguments:
+ optionsptr pointer to the option bits
+ codeptr points to the pointer to the current code point
+ ptrptr points to the current pattern pointer
+ errorcodeptr points to error code variable
+ firstbyteptr set to initial literal character, or < 0 (REQ_UNSET, REQ_NONE)
+ reqbyteptr set to the last literal character required, else < 0
+ bcptr points to current branch chain
+ cond_depth conditional nesting depth
+ cd contains pointers to tables etc.
+ lengthptr NULL during the real compile phase
+ points to length accumulator during pre-compile phase
+
+Returns: TRUE on success
+ FALSE, with *errorcodeptr set non-zero on error
+*/
+
+static BOOL
+compile_branch(int *optionsptr, uschar **codeptr, const uschar **ptrptr,
+ int *errorcodeptr, int *firstbyteptr, int *reqbyteptr, branch_chain *bcptr,
+ int cond_depth, compile_data *cd, int *lengthptr)
+{
+int repeat_type, op_type;
+int repeat_min = 0, repeat_max = 0; /* To please picky compilers */
+int bravalue = 0;
+int greedy_default, greedy_non_default;
+int firstbyte, reqbyte;
+int zeroreqbyte, zerofirstbyte;
+int req_caseopt, reqvary, tempreqvary;
+int options = *optionsptr; /* May change dynamically */
+int after_manual_callout = 0;
+int length_prevgroup = 0;
+register int c;
+register uschar *code = *codeptr;
+uschar *last_code = code;
+uschar *orig_code = code;
+uschar *tempcode;
+BOOL inescq = FALSE;
+BOOL groupsetfirstbyte = FALSE;
+const uschar *ptr = *ptrptr;
+const uschar *tempptr;
+const uschar *nestptr = NULL;
+uschar *previous = NULL;
+uschar *previous_callout = NULL;
+uschar *save_hwm = NULL;
+uschar classbits[32];
+
+/* We can fish out the UTF-8 setting once and for all into a BOOL, but we
+must not do this for other options (e.g. PCRE_EXTENDED) because they may change
+dynamically as we process the pattern. */
+
+#ifdef SUPPORT_UTF8
+BOOL class_utf8;
+BOOL utf8 = (options & PCRE_UTF8) != 0;
+uschar *class_utf8data;
+uschar *class_utf8data_base;
+uschar utf8_char[6];
+#else
+BOOL utf8 = FALSE;
+uschar *utf8_char = NULL;
+#endif
+
+#ifdef PCRE_DEBUG
+if (lengthptr != NULL) DPRINTF((">> start branch\n"));
+#endif
+
+/* Set up the default and non-default settings for greediness */
+
+greedy_default = ((options & PCRE_UNGREEDY) != 0);
+greedy_non_default = greedy_default ^ 1;
+
+/* Initialize no first byte, no required byte. REQ_UNSET means "no char
+matching encountered yet". It gets changed to REQ_NONE if we hit something that
+matches a non-fixed char first char; reqbyte just remains unset if we never
+find one.
+
+When we hit a repeat whose minimum is zero, we may have to adjust these values
+to take the zero repeat into account. This is implemented by setting them to
+zerofirstbyte and zeroreqbyte when such a repeat is encountered. The individual
+item types that can be repeated set these backoff variables appropriately. */
+
+firstbyte = reqbyte = zerofirstbyte = zeroreqbyte = REQ_UNSET;
+
+/* The variable req_caseopt contains either the REQ_CASELESS value or zero,
+according to the current setting of the caseless flag. REQ_CASELESS is a bit
+value > 255. It is added into the firstbyte or reqbyte variables to record the
+case status of the value. This is used only for ASCII characters. */
+
+req_caseopt = ((options & PCRE_CASELESS) != 0)? REQ_CASELESS : 0;
+
+/* Switch on next character until the end of the branch */
+
+for (;; ptr++)
+ {
+ BOOL negate_class;
+ BOOL should_flip_negation;
+ BOOL possessive_quantifier;
+ BOOL is_quantifier;
+ BOOL is_recurse;
+ BOOL reset_bracount;
+ int class_charcount;
+ int class_lastchar;
+ int newoptions;
+ int recno;
+ int refsign;
+ int skipbytes;
+ int subreqbyte;
+ int subfirstbyte;
+ int terminator;
+ int mclength;
+ uschar mcbuffer[8];
+
+ /* Get next byte in the pattern */
+
+ c = *ptr;
+
+ /* If we are at the end of a nested substitution, revert to the outer level
+ string. Nesting only happens one level deep. */
+
+ if (c == 0 && nestptr != NULL)
+ {
+ ptr = nestptr;
+ nestptr = NULL;
+ c = *ptr;
+ }
+
+ /* If we are in the pre-compile phase, accumulate the length used for the
+ previous cycle of this loop. */
+
+ if (lengthptr != NULL)
+ {
+#ifdef PCRE_DEBUG
+ if (code > cd->hwm) cd->hwm = code; /* High water info */
+#endif
+ if (code > cd->start_workspace + WORK_SIZE_CHECK) /* Check for overrun */
+ {
+ *errorcodeptr = ERR52;
+ goto FAILED;
+ }
+
+ /* There is at least one situation where code goes backwards: this is the
+ case of a zero quantifier after a class (e.g. [ab]{0}). At compile time,
+ the class is simply eliminated. However, it is created first, so we have to
+ allow memory for it. Therefore, don't ever reduce the length at this point.
+ */
+
+ if (code < last_code) code = last_code;
+
+ /* Paranoid check for integer overflow */
+
+ if (OFLOW_MAX - *lengthptr < code - last_code)
+ {
+ *errorcodeptr = ERR20;
+ goto FAILED;
+ }
+
+ *lengthptr += (int)(code - last_code);
+ DPRINTF(("length=%d added %d c=%c\n", *lengthptr, code - last_code, c));
+
+ /* If "previous" is set and it is not at the start of the work space, move
+ it back to there, in order to avoid filling up the work space. Otherwise,
+ if "previous" is NULL, reset the current code pointer to the start. */
+
+ if (previous != NULL)
+ {
+ if (previous > orig_code)
+ {
+ memmove(orig_code, previous, code - previous);
+ code -= previous - orig_code;
+ previous = orig_code;
+ }
+ }
+ else code = orig_code;
+
+ /* Remember where this code item starts so we can pick up the length
+ next time round. */
+
+ last_code = code;
+ }
+
+ /* In the real compile phase, just check the workspace used by the forward
+ reference list. */
+
+ else if (cd->hwm > cd->start_workspace + WORK_SIZE_CHECK)
+ {
+ *errorcodeptr = ERR52;
+ goto FAILED;
+ }
+
+ /* If in \Q...\E, check for the end; if not, we have a literal */
+
+ if (inescq && c != 0)
+ {
+ if (c == CHAR_BACKSLASH && ptr[1] == CHAR_E)
+ {
+ inescq = FALSE;
+ ptr++;
+ continue;
+ }
+ else
+ {
+ if (previous_callout != NULL)
+ {
+ if (lengthptr == NULL) /* Don't attempt in pre-compile phase */
+ complete_callout(previous_callout, ptr, cd);
+ previous_callout = NULL;
+ }
+ if ((options & PCRE_AUTO_CALLOUT) != 0)
+ {
+ previous_callout = code;
+ code = auto_callout(code, ptr, cd);
+ }
+ goto NORMAL_CHAR;
+ }
+ }
+
+ /* Fill in length of a previous callout, except when the next thing is
+ a quantifier. */
+
+ is_quantifier =
+ c == CHAR_ASTERISK || c == CHAR_PLUS || c == CHAR_QUESTION_MARK ||
+ (c == CHAR_LEFT_CURLY_BRACKET && is_counted_repeat(ptr+1));
+
+ if (!is_quantifier && previous_callout != NULL &&
+ after_manual_callout-- <= 0)
+ {
+ if (lengthptr == NULL) /* Don't attempt in pre-compile phase */
+ complete_callout(previous_callout, ptr, cd);
+ previous_callout = NULL;
+ }
+
+ /* In extended mode, skip white space and comments. */
+
+ if ((options & PCRE_EXTENDED) != 0)
+ {
+ if ((cd->ctypes[c] & ctype_space) != 0) continue;
+ if (c == CHAR_NUMBER_SIGN)
+ {
+ ptr++;
+ while (*ptr != 0)
+ {
+ if (IS_NEWLINE(ptr)) { ptr += cd->nllen - 1; break; }
+ ptr++;
+#ifdef SUPPORT_UTF8
+ if (utf8) while ((*ptr & 0xc0) == 0x80) ptr++;
+#endif
+ }
+ if (*ptr != 0) continue;
+
+ /* Else fall through to handle end of string */
+ c = 0;
+ }
+ }
+
+ /* No auto callout for quantifiers. */
+
+ if ((options & PCRE_AUTO_CALLOUT) != 0 && !is_quantifier)
+ {
+ previous_callout = code;
+ code = auto_callout(code, ptr, cd);
+ }
+
+ switch(c)
+ {
+ /* ===================================================================*/
+ case 0: /* The branch terminates at string end */
+ case CHAR_VERTICAL_LINE: /* or | or ) */
+ case CHAR_RIGHT_PARENTHESIS:
+ *firstbyteptr = firstbyte;
+ *reqbyteptr = reqbyte;
+ *codeptr = code;
+ *ptrptr = ptr;
+ if (lengthptr != NULL)
+ {
+ if (OFLOW_MAX - *lengthptr < code - last_code)
+ {
+ *errorcodeptr = ERR20;
+ goto FAILED;
+ }
+ *lengthptr += (int)(code - last_code); /* To include callout length */
+ DPRINTF((">> end branch\n"));
+ }
+ return TRUE;
+
+
+ /* ===================================================================*/
+ /* Handle single-character metacharacters. In multiline mode, ^ disables
+ the setting of any following char as a first character. */
+
+ case CHAR_CIRCUMFLEX_ACCENT:
+ previous = NULL;
+ if ((options & PCRE_MULTILINE) != 0)
+ {
+ if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE;
+ *code++ = OP_CIRCM;
+ }
+ else *code++ = OP_CIRC;
+ break;
+
+ case CHAR_DOLLAR_SIGN:
+ previous = NULL;
+ *code++ = ((options & PCRE_MULTILINE) != 0)? OP_DOLLM : OP_DOLL;
+ break;
+
+ /* There can never be a first char if '.' is first, whatever happens about
+ repeats. The value of reqbyte doesn't change either. */
+
+ case CHAR_DOT:
+ if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE;
+ zerofirstbyte = firstbyte;
+ zeroreqbyte = reqbyte;
+ previous = code;
+ *code++ = ((options & PCRE_DOTALL) != 0)? OP_ALLANY: OP_ANY;
+ break;
+
+
+ /* ===================================================================*/
+ /* Character classes. If the included characters are all < 256, we build a
+ 32-byte bitmap of the permitted characters, except in the special case
+ where there is only one such character. For negated classes, we build the
+ map as usual, then invert it at the end. However, we use a different opcode
+ so that data characters > 255 can be handled correctly.
+
+ If the class contains characters outside the 0-255 range, a different
+ opcode is compiled. It may optionally have a bit map for characters < 256,
+ but those above are are explicitly listed afterwards. A flag byte tells
+ whether the bitmap is present, and whether this is a negated class or not.
+
+ In JavaScript compatibility mode, an isolated ']' causes an error. In
+ default (Perl) mode, it is treated as a data character. */
+
+ case CHAR_RIGHT_SQUARE_BRACKET:
+ if ((cd->external_options & PCRE_JAVASCRIPT_COMPAT) != 0)
+ {
+ *errorcodeptr = ERR64;
+ goto FAILED;
+ }
+ goto NORMAL_CHAR;
+
+ case CHAR_LEFT_SQUARE_BRACKET:
+ previous = code;
+
+ /* PCRE supports POSIX class stuff inside a class. Perl gives an error if
+ they are encountered at the top level, so we'll do that too. */
+
+ if ((ptr[1] == CHAR_COLON || ptr[1] == CHAR_DOT ||
+ ptr[1] == CHAR_EQUALS_SIGN) &&
+ check_posix_syntax(ptr, &tempptr))
+ {
+ *errorcodeptr = (ptr[1] == CHAR_COLON)? ERR13 : ERR31;
+ goto FAILED;
+ }
+
+ /* If the first character is '^', set the negation flag and skip it. Also,
+ if the first few characters (either before or after ^) are \Q\E or \E we
+ skip them too. This makes for compatibility with Perl. */
+
+ negate_class = FALSE;
+ for (;;)
+ {
+ c = *(++ptr);
+ if (c == CHAR_BACKSLASH)
+ {
+ if (ptr[1] == CHAR_E)
+ ptr++;
+ else if (strncmp((const char *)ptr+1,
+ STR_Q STR_BACKSLASH STR_E, 3) == 0)
+ ptr += 3;
+ else
+ break;
+ }
+ else if (!negate_class && c == CHAR_CIRCUMFLEX_ACCENT)
+ negate_class = TRUE;
+ else break;
+ }
+
+ /* Empty classes are allowed in JavaScript compatibility mode. Otherwise,
+ an initial ']' is taken as a data character -- the code below handles
+ that. In JS mode, [] must always fail, so generate OP_FAIL, whereas
+ [^] must match any character, so generate OP_ALLANY. */
+
+ if (c == CHAR_RIGHT_SQUARE_BRACKET &&
+ (cd->external_options & PCRE_JAVASCRIPT_COMPAT) != 0)
+ {
+ *code++ = negate_class? OP_ALLANY : OP_FAIL;
+ if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE;
+ zerofirstbyte = firstbyte;
+ break;
+ }
+
+ /* If a class contains a negative special such as \S, we need to flip the
+ negation flag at the end, so that support for characters > 255 works
+ correctly (they are all included in the class). */
+
+ should_flip_negation = FALSE;
+
+ /* Keep a count of chars with values < 256 so that we can optimize the case
+ of just a single character (as long as it's < 256). However, For higher
+ valued UTF-8 characters, we don't yet do any optimization. */
+
+ class_charcount = 0;
+ class_lastchar = -1;
+
+ /* Initialize the 32-char bit map to all zeros. We build the map in a
+ temporary bit of memory, in case the class contains only 1 character (less
+ than 256), because in that case the compiled code doesn't use the bit map.
+ */
+
+ memset(classbits, 0, 32 * sizeof(uschar));
+
+#ifdef SUPPORT_UTF8
+ class_utf8 = FALSE; /* No chars >= 256 */
+ class_utf8data = code + LINK_SIZE + 2; /* For UTF-8 items */
+ class_utf8data_base = class_utf8data; /* For resetting in pass 1 */
+#endif
+
+ /* Process characters until ] is reached. By writing this as a "do" it
+ means that an initial ] is taken as a data character. At the start of the
+ loop, c contains the first byte of the character. */
+
+ if (c != 0) do
+ {
+ const uschar *oldptr;
+
+#ifdef SUPPORT_UTF8
+ if (utf8 && c > 127)
+ { /* Braces are required because the */
+ GETCHARLEN(c, ptr, ptr); /* macro generates multiple statements */
+ }
+
+ /* In the pre-compile phase, accumulate the length of any UTF-8 extra
+ data and reset the pointer. This is so that very large classes that
+ contain a zillion UTF-8 characters no longer overwrite the work space
+ (which is on the stack). */
+
+ if (lengthptr != NULL)
+ {
+ *lengthptr += class_utf8data - class_utf8data_base;
+ class_utf8data = class_utf8data_base;
+ }
+
+#endif
+
+ /* Inside \Q...\E everything is literal except \E */
+
+ if (inescq)
+ {
+ if (c == CHAR_BACKSLASH && ptr[1] == CHAR_E) /* If we are at \E */
+ {
+ inescq = FALSE; /* Reset literal state */
+ ptr++; /* Skip the 'E' */
+ continue; /* Carry on with next */
+ }
+ goto CHECK_RANGE; /* Could be range if \E follows */
+ }
+
+ /* Handle POSIX class names. Perl allows a negation extension of the
+ form [:^name:]. A square bracket that doesn't match the syntax is
+ treated as a literal. We also recognize the POSIX constructions
+ [.ch.] and [=ch=] ("collating elements") and fault them, as Perl
+ 5.6 and 5.8 do. */
+
+ if (c == CHAR_LEFT_SQUARE_BRACKET &&
+ (ptr[1] == CHAR_COLON || ptr[1] == CHAR_DOT ||
+ ptr[1] == CHAR_EQUALS_SIGN) && check_posix_syntax(ptr, &tempptr))
+ {
+ BOOL local_negate = FALSE;
+ int posix_class, taboffset, tabopt;
+ register const uschar *cbits = cd->cbits;
+ uschar pbits[32];
+
+ if (ptr[1] != CHAR_COLON)
+ {
+ *errorcodeptr = ERR31;
+ goto FAILED;
+ }
+
+ ptr += 2;
+ if (*ptr == CHAR_CIRCUMFLEX_ACCENT)
+ {
+ local_negate = TRUE;
+ should_flip_negation = TRUE; /* Note negative special */
+ ptr++;
+ }
+
+ posix_class = check_posix_name(ptr, (int)(tempptr - ptr));
+ if (posix_class < 0)
+ {
+ *errorcodeptr = ERR30;
+ goto FAILED;
+ }
+
+ /* If matching is caseless, upper and lower are converted to
+ alpha. This relies on the fact that the class table starts with
+ alpha, lower, upper as the first 3 entries. */
+
+ if ((options & PCRE_CASELESS) != 0 && posix_class <= 2)
+ posix_class = 0;
+
+ /* When PCRE_UCP is set, some of the POSIX classes are converted to
+ different escape sequences that use Unicode properties. */
+
+#ifdef SUPPORT_UCP
+ if ((options & PCRE_UCP) != 0)
+ {
+ int pc = posix_class + ((local_negate)? POSIX_SUBSIZE/2 : 0);
+ if (posix_substitutes[pc] != NULL)
+ {
+ nestptr = tempptr + 1;
+ ptr = posix_substitutes[pc] - 1;
+ continue;
+ }
+ }
+#endif
+ /* In the non-UCP case, we build the bit map for the POSIX class in a
+ chunk of local store because we may be adding and subtracting from it,
+ and we don't want to subtract bits that may be in the main map already.
+ At the end we or the result into the bit map that is being built. */
+
+ posix_class *= 3;
+
+ /* Copy in the first table (always present) */
+
+ memcpy(pbits, cbits + posix_class_maps[posix_class],
+ 32 * sizeof(uschar));
+
+ /* If there is a second table, add or remove it as required. */
+
+ taboffset = posix_class_maps[posix_class + 1];
+ tabopt = posix_class_maps[posix_class + 2];
+
+ if (taboffset >= 0)
+ {
+ if (tabopt >= 0)
+ for (c = 0; c < 32; c++) pbits[c] |= cbits[c + taboffset];
+ else
+ for (c = 0; c < 32; c++) pbits[c] &= ~cbits[c + taboffset];
+ }
+
+ /* Not see if we need to remove any special characters. An option
+ value of 1 removes vertical space and 2 removes underscore. */
+
+ if (tabopt < 0) tabopt = -tabopt;
+ if (tabopt == 1) pbits[1] &= ~0x3c;
+ else if (tabopt == 2) pbits[11] &= 0x7f;
+
+ /* Add the POSIX table or its complement into the main table that is
+ being built and we are done. */
+
+ if (local_negate)
+ for (c = 0; c < 32; c++) classbits[c] |= ~pbits[c];
+ else
+ for (c = 0; c < 32; c++) classbits[c] |= pbits[c];
+
+ ptr = tempptr + 1;
+ class_charcount = 10; /* Set > 1; assumes more than 1 per class */
+ continue; /* End of POSIX syntax handling */
+ }
+
+ /* Backslash may introduce a single character, or it may introduce one
+ of the specials, which just set a flag. The sequence \b is a special
+ case. Inside a class (and only there) it is treated as backspace. We
+ assume that other escapes have more than one character in them, so set
+ class_charcount bigger than one. Unrecognized escapes fall through and
+ are either treated as literal characters (by default), or are faulted if
+ PCRE_EXTRA is set. */
+
+ if (c == CHAR_BACKSLASH)
+ {
+ c = check_escape(&ptr, errorcodeptr, cd->bracount, options, TRUE);
+ if (*errorcodeptr != 0) goto FAILED;
+
+ if (-c == ESC_b) c = CHAR_BS; /* \b is backspace in a class */
+ else if (-c == ESC_Q) /* Handle start of quoted string */
+ {
+ if (ptr[1] == CHAR_BACKSLASH && ptr[2] == CHAR_E)
+ {
+ ptr += 2; /* avoid empty string */
+ }
+ else inescq = TRUE;
+ continue;
+ }
+ else if (-c == ESC_E) continue; /* Ignore orphan \E */
+
+ if (c < 0)
+ {
+ register const uschar *cbits = cd->cbits;
+ class_charcount += 2; /* Greater than 1 is what matters */
+
+ switch (-c)
+ {
+#ifdef SUPPORT_UCP
+ case ESC_du: /* These are the values given for \d etc */
+ case ESC_DU: /* when PCRE_UCP is set. We replace the */
+ case ESC_wu: /* escape sequence with an appropriate \p */
+ case ESC_WU: /* or \P to test Unicode properties instead */
+ case ESC_su: /* of the default ASCII testing. */
+ case ESC_SU:
+ nestptr = ptr;
+ ptr = substitutes[-c - ESC_DU] - 1; /* Just before substitute */
+ class_charcount -= 2; /* Undo! */
+ continue;
+#endif
+ case ESC_d:
+ for (c = 0; c < 32; c++) classbits[c] |= cbits[c+cbit_digit];
+ continue;
+
+ case ESC_D:
+ should_flip_negation = TRUE;
+ for (c = 0; c < 32; c++) classbits[c] |= ~cbits[c+cbit_digit];
+ continue;
+
+ case ESC_w:
+ for (c = 0; c < 32; c++) classbits[c] |= cbits[c+cbit_word];
+ continue;
+
+ case ESC_W:
+ should_flip_negation = TRUE;
+ for (c = 0; c < 32; c++) classbits[c] |= ~cbits[c+cbit_word];
+ continue;
+
+ /* Perl 5.004 onwards omits VT from \s, but we must preserve it
+ if it was previously set by something earlier in the character
+ class. */
+
+ case ESC_s:
+ classbits[0] |= cbits[cbit_space];
+ classbits[1] |= cbits[cbit_space+1] & ~0x08;
+ for (c = 2; c < 32; c++) classbits[c] |= cbits[c+cbit_space];
+ continue;
+
+ case ESC_S:
+ should_flip_negation = TRUE;
+ for (c = 0; c < 32; c++) classbits[c] |= ~cbits[c+cbit_space];
+ classbits[1] |= 0x08; /* Perl 5.004 onwards omits VT from \s */
+ continue;
+
+ case ESC_h:
+ SETBIT(classbits, 0x09); /* VT */
+ SETBIT(classbits, 0x20); /* SPACE */
+ SETBIT(classbits, 0xa0); /* NSBP */
+#ifdef SUPPORT_UTF8
+ if (utf8)
+ {
+ class_utf8 = TRUE;
+ *class_utf8data++ = XCL_SINGLE;
+ class_utf8data += _pcre_ord2utf8(0x1680, class_utf8data);
+ *class_utf8data++ = XCL_SINGLE;
+ class_utf8data += _pcre_ord2utf8(0x180e, class_utf8data);
+ *class_utf8data++ = XCL_RANGE;
+ class_utf8data += _pcre_ord2utf8(0x2000, class_utf8data);
+ class_utf8data += _pcre_ord2utf8(0x200A, class_utf8data);
+ *class_utf8data++ = XCL_SINGLE;
+ class_utf8data += _pcre_ord2utf8(0x202f, class_utf8data);
+ *class_utf8data++ = XCL_SINGLE;
+ class_utf8data += _pcre_ord2utf8(0x205f, class_utf8data);
+ *class_utf8data++ = XCL_SINGLE;
+ class_utf8data += _pcre_ord2utf8(0x3000, class_utf8data);
+ }
+#endif
+ continue;
+
+ case ESC_H:
+ for (c = 0; c < 32; c++)
+ {
+ int x = 0xff;
+ switch (c)
+ {
+ case 0x09/8: x ^= 1 << (0x09%8); break;
+ case 0x20/8: x ^= 1 << (0x20%8); break;
+ case 0xa0/8: x ^= 1 << (0xa0%8); break;
+ default: break;
+ }
+ classbits[c] |= x;
+ }
+
+#ifdef SUPPORT_UTF8
+ if (utf8)
+ {
+ class_utf8 = TRUE;
+ *class_utf8data++ = XCL_RANGE;
+ class_utf8data += _pcre_ord2utf8(0x0100, class_utf8data);
+ class_utf8data += _pcre_ord2utf8(0x167f, class_utf8data);
+ *class_utf8data++ = XCL_RANGE;
+ class_utf8data += _pcre_ord2utf8(0x1681, class_utf8data);
+ class_utf8data += _pcre_ord2utf8(0x180d, class_utf8data);
+ *class_utf8data++ = XCL_RANGE;
+ class_utf8data += _pcre_ord2utf8(0x180f, class_utf8data);
+ class_utf8data += _pcre_ord2utf8(0x1fff, class_utf8data);
+ *class_utf8data++ = XCL_RANGE;
+ class_utf8data += _pcre_ord2utf8(0x200B, class_utf8data);
+ class_utf8data += _pcre_ord2utf8(0x202e, class_utf8data);
+ *class_utf8data++ = XCL_RANGE;
+ class_utf8data += _pcre_ord2utf8(0x2030, class_utf8data);
+ class_utf8data += _pcre_ord2utf8(0x205e, class_utf8data);
+ *class_utf8data++ = XCL_RANGE;
+ class_utf8data += _pcre_ord2utf8(0x2060, class_utf8data);
+ class_utf8data += _pcre_ord2utf8(0x2fff, class_utf8data);
+ *class_utf8data++ = XCL_RANGE;
+ class_utf8data += _pcre_ord2utf8(0x3001, class_utf8data);
+ class_utf8data += _pcre_ord2utf8(0x7fffffff, class_utf8data);
+ }
+#endif
+ continue;
+
+ case ESC_v:
+ SETBIT(classbits, 0x0a); /* LF */
+ SETBIT(classbits, 0x0b); /* VT */
+ SETBIT(classbits, 0x0c); /* FF */
+ SETBIT(classbits, 0x0d); /* CR */
+ SETBIT(classbits, 0x85); /* NEL */
+#ifdef SUPPORT_UTF8
+ if (utf8)
+ {
+ class_utf8 = TRUE;
+ *class_utf8data++ = XCL_RANGE;
+ class_utf8data += _pcre_ord2utf8(0x2028, class_utf8data);
+ class_utf8data += _pcre_ord2utf8(0x2029, class_utf8data);
+ }
+#endif
+ continue;
+
+ case ESC_V:
+ for (c = 0; c < 32; c++)
+ {
+ int x = 0xff;
+ switch (c)
+ {
+ case 0x0a/8: x ^= 1 << (0x0a%8);
+ x ^= 1 << (0x0b%8);
+ x ^= 1 << (0x0c%8);
+ x ^= 1 << (0x0d%8);
+ break;
+ case 0x85/8: x ^= 1 << (0x85%8); break;
+ default: break;
+ }
+ classbits[c] |= x;
+ }
+
+#ifdef SUPPORT_UTF8
+ if (utf8)
+ {
+ class_utf8 = TRUE;
+ *class_utf8data++ = XCL_RANGE;
+ class_utf8data += _pcre_ord2utf8(0x0100, class_utf8data);
+ class_utf8data += _pcre_ord2utf8(0x2027, class_utf8data);
+ *class_utf8data++ = XCL_RANGE;
+ class_utf8data += _pcre_ord2utf8(0x2029, class_utf8data);
+ class_utf8data += _pcre_ord2utf8(0x7fffffff, class_utf8data);
+ }
+#endif
+ continue;
+
+#ifdef SUPPORT_UCP
+ case ESC_p:
+ case ESC_P:
+ {
+ BOOL negated;
+ int pdata;
+ int ptype = get_ucp(&ptr, &negated, &pdata, errorcodeptr);
+ if (ptype < 0) goto FAILED;
+ class_utf8 = TRUE;
+ *class_utf8data++ = ((-c == ESC_p) != negated)?
+ XCL_PROP : XCL_NOTPROP;
+ *class_utf8data++ = ptype;
+ *class_utf8data++ = pdata;
+ class_charcount -= 2; /* Not a < 256 character */
+ continue;
+ }
+#endif
+ /* Unrecognized escapes are faulted if PCRE is running in its
+ strict mode. By default, for compatibility with Perl, they are
+ treated as literals. */
+
+ default:
+ if ((options & PCRE_EXTRA) != 0)
+ {
+ *errorcodeptr = ERR7;
+ goto FAILED;
+ }
+ class_charcount -= 2; /* Undo the default count from above */
+ c = *ptr; /* Get the final character and fall through */
+ break;
+ }
+ }
+
+ /* Fall through if we have a single character (c >= 0). This may be
+ greater than 256 in UTF-8 mode. */
+
+ } /* End of backslash handling */
+
+ /* A single character may be followed by '-' to form a range. However,
+ Perl does not permit ']' to be the end of the range. A '-' character
+ at the end is treated as a literal. Perl ignores orphaned \E sequences
+ entirely. The code for handling \Q and \E is messy. */
+
+ CHECK_RANGE:
+ while (ptr[1] == CHAR_BACKSLASH && ptr[2] == CHAR_E)
+ {
+ inescq = FALSE;
+ ptr += 2;
+ }
+
+ oldptr = ptr;
+
+ /* Remember \r or \n */
+
+ if (c == CHAR_CR || c == CHAR_NL) cd->external_flags |= PCRE_HASCRORLF;
+
+ /* Check for range */
+
+ if (!inescq && ptr[1] == CHAR_MINUS)
+ {
+ int d;
+ ptr += 2;
+ while (*ptr == CHAR_BACKSLASH && ptr[1] == CHAR_E) ptr += 2;
+
+ /* If we hit \Q (not followed by \E) at this point, go into escaped
+ mode. */
+
+ while (*ptr == CHAR_BACKSLASH && ptr[1] == CHAR_Q)
+ {
+ ptr += 2;
+ if (*ptr == CHAR_BACKSLASH && ptr[1] == CHAR_E)
+ { ptr += 2; continue; }
+ inescq = TRUE;
+ break;
+ }
+
+ if (*ptr == 0 || (!inescq && *ptr == CHAR_RIGHT_SQUARE_BRACKET))
+ {
+ ptr = oldptr;
+ goto LONE_SINGLE_CHARACTER;
+ }
+
+#ifdef SUPPORT_UTF8
+ if (utf8)
+ { /* Braces are required because the */
+ GETCHARLEN(d, ptr, ptr); /* macro generates multiple statements */
+ }
+ else
+#endif
+ d = *ptr; /* Not UTF-8 mode */
+
+ /* The second part of a range can be a single-character escape, but
+ not any of the other escapes. Perl 5.6 treats a hyphen as a literal
+ in such circumstances. */
+
+ if (!inescq && d == CHAR_BACKSLASH)
+ {
+ d = check_escape(&ptr, errorcodeptr, cd->bracount, options, TRUE);
+ if (*errorcodeptr != 0) goto FAILED;
+
+ /* \b is backspace; any other special means the '-' was literal */
+
+ if (d < 0)
+ {
+ if (d == -ESC_b) d = CHAR_BS; else
+ {
+ ptr = oldptr;
+ goto LONE_SINGLE_CHARACTER; /* A few lines below */
+ }
+ }
+ }
+
+ /* Check that the two values are in the correct order. Optimize
+ one-character ranges */
+
+ if (d < c)
+ {
+ *errorcodeptr = ERR8;
+ goto FAILED;
+ }
+
+ if (d == c) goto LONE_SINGLE_CHARACTER; /* A few lines below */
+
+ /* Remember \r or \n */
+
+ if (d == CHAR_CR || d == CHAR_NL) cd->external_flags |= PCRE_HASCRORLF;
+
+ /* In UTF-8 mode, if the upper limit is > 255, or > 127 for caseless
+ matching, we have to use an XCLASS with extra data items. Caseless
+ matching for characters > 127 is available only if UCP support is
+ available. */
+
+#ifdef SUPPORT_UTF8
+ if (utf8 && (d > 255 || ((options & PCRE_CASELESS) != 0 && d > 127)))
+ {
+ class_utf8 = TRUE;
+
+ /* With UCP support, we can find the other case equivalents of
+ the relevant characters. There may be several ranges. Optimize how
+ they fit with the basic range. */
+
+#ifdef SUPPORT_UCP
+ if ((options & PCRE_CASELESS) != 0)
+ {
+ unsigned int occ, ocd;
+ unsigned int cc = c;
+ unsigned int origd = d;
+ while (get_othercase_range(&cc, origd, &occ, &ocd))
+ {
+ if (occ >= (unsigned int)c &&
+ ocd <= (unsigned int)d)
+ continue; /* Skip embedded ranges */
+
+ if (occ < (unsigned int)c &&
+ ocd >= (unsigned int)c - 1) /* Extend the basic range */
+ { /* if there is overlap, */
+ c = occ; /* noting that if occ < c */
+ continue; /* we can't have ocd > d */
+ } /* because a subrange is */
+ if (ocd > (unsigned int)d &&
+ occ <= (unsigned int)d + 1) /* always shorter than */
+ { /* the basic range. */
+ d = ocd;
+ continue;
+ }
+
+ if (occ == ocd)
+ {
+ *class_utf8data++ = XCL_SINGLE;
+ }
+ else
+ {
+ *class_utf8data++ = XCL_RANGE;
+ class_utf8data += _pcre_ord2utf8(occ, class_utf8data);
+ }
+ class_utf8data += _pcre_ord2utf8(ocd, class_utf8data);
+ }
+ }
+#endif /* SUPPORT_UCP */
+
+ /* Now record the original range, possibly modified for UCP caseless
+ overlapping ranges. */
+
+ *class_utf8data++ = XCL_RANGE;
+ class_utf8data += _pcre_ord2utf8(c, class_utf8data);
+ class_utf8data += _pcre_ord2utf8(d, class_utf8data);
+
+ /* With UCP support, we are done. Without UCP support, there is no
+ caseless matching for UTF-8 characters > 127; we can use the bit map
+ for the smaller ones. */
+
+#ifdef SUPPORT_UCP
+ continue; /* With next character in the class */
+#else
+ if ((options & PCRE_CASELESS) == 0 || c > 127) continue;
+
+ /* Adjust upper limit and fall through to set up the map */
+
+ d = 127;
+
+#endif /* SUPPORT_UCP */
+ }
+#endif /* SUPPORT_UTF8 */
+
+ /* We use the bit map for all cases when not in UTF-8 mode; else
+ ranges that lie entirely within 0-127 when there is UCP support; else
+ for partial ranges without UCP support. */
+
+ class_charcount += d - c + 1;
+ class_lastchar = d;
+
+ /* We can save a bit of time by skipping this in the pre-compile. */
+
+ if (lengthptr == NULL) for (; c <= d; c++)
+ {
+ classbits[c/8] |= (1 << (c&7));
+ if ((options & PCRE_CASELESS) != 0)
+ {
+ int uc = cd->fcc[c]; /* flip case */
+ classbits[uc/8] |= (1 << (uc&7));
+ }
+ }
+
+ continue; /* Go get the next char in the class */
+ }
+
+ /* Handle a lone single character - we can get here for a normal
+ non-escape char, or after \ that introduces a single character or for an
+ apparent range that isn't. */
+
+ LONE_SINGLE_CHARACTER:
+
+ /* Handle a character that cannot go in the bit map */
+
+#ifdef SUPPORT_UTF8
+ if (utf8 && (c > 255 || ((options & PCRE_CASELESS) != 0 && c > 127)))
+ {
+ class_utf8 = TRUE;
+ *class_utf8data++ = XCL_SINGLE;
+ class_utf8data += _pcre_ord2utf8(c, class_utf8data);
+
+#ifdef SUPPORT_UCP
+ if ((options & PCRE_CASELESS) != 0)
+ {
+ unsigned int othercase;
+ if ((othercase = UCD_OTHERCASE(c)) != c)
+ {
+ *class_utf8data++ = XCL_SINGLE;
+ class_utf8data += _pcre_ord2utf8(othercase, class_utf8data);
+ }
+ }
+#endif /* SUPPORT_UCP */
+
+ }
+ else
+#endif /* SUPPORT_UTF8 */
+
+ /* Handle a single-byte character */
+ {
+ classbits[c/8] |= (1 << (c&7));
+ if ((options & PCRE_CASELESS) != 0)
+ {
+ c = cd->fcc[c]; /* flip case */
+ classbits[c/8] |= (1 << (c&7));
+ }
+ class_charcount++;
+ class_lastchar = c;
+ }
+ }
+
+ /* Loop until ']' reached. This "while" is the end of the "do" far above.
+ If we are at the end of an internal nested string, revert to the outer
+ string. */
+
+ while (((c = *(++ptr)) != 0 ||
+ (nestptr != NULL &&
+ (ptr = nestptr, nestptr = NULL, c = *(++ptr)) != 0)) &&
+ (c != CHAR_RIGHT_SQUARE_BRACKET || inescq));
+
+ /* Check for missing terminating ']' */
+
+ if (c == 0)
+ {
+ *errorcodeptr = ERR6;
+ goto FAILED;
+ }
+
+ /* If class_charcount is 1, we saw precisely one character whose value is
+ less than 256. As long as there were no characters >= 128 and there was no
+ use of \p or \P, in other words, no use of any XCLASS features, we can
+ optimize.
+
+ In UTF-8 mode, we can optimize the negative case only if there were no
+ characters >= 128 because OP_NOT and the related opcodes like OP_NOTSTAR
+ operate on single-bytes characters only. This is an historical hangover.
+ Maybe one day we can tidy these opcodes to handle multi-byte characters.
+
+ The optimization throws away the bit map. We turn the item into a
+ 1-character OP_CHAR[I] if it's positive, or OP_NOT[I] if it's negative.
+ Note that OP_NOT[I] does not support multibyte characters. In the positive
+ case, it can cause firstbyte to be set. Otherwise, there can be no first
+ char if this item is first, whatever repeat count may follow. In the case
+ of reqbyte, save the previous value for reinstating. */
+
+#ifdef SUPPORT_UTF8
+ if (class_charcount == 1 && !class_utf8 &&
+ (!utf8 || !negate_class || class_lastchar < 128))
+#else
+ if (class_charcount == 1)
+#endif
+ {
+ zeroreqbyte = reqbyte;
+
+ /* The OP_NOT[I] opcodes work on one-byte characters only. */
+
+ if (negate_class)
+ {
+ if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE;
+ zerofirstbyte = firstbyte;
+ *code++ = ((options & PCRE_CASELESS) != 0)? OP_NOTI: OP_NOT;
+ *code++ = class_lastchar;
+ break;
+ }
+
+ /* For a single, positive character, get the value into mcbuffer, and
+ then we can handle this with the normal one-character code. */
+
+#ifdef SUPPORT_UTF8
+ if (utf8 && class_lastchar > 127)
+ mclength = _pcre_ord2utf8(class_lastchar, mcbuffer);
+ else
+#endif
+ {
+ mcbuffer[0] = class_lastchar;
+ mclength = 1;
+ }
+ goto ONE_CHAR;
+ } /* End of 1-char optimization */
+
+ /* The general case - not the one-char optimization. If this is the first
+ thing in the branch, there can be no first char setting, whatever the
+ repeat count. Any reqbyte setting must remain unchanged after any kind of
+ repeat. */
+
+ if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE;
+ zerofirstbyte = firstbyte;
+ zeroreqbyte = reqbyte;
+
+ /* If there are characters with values > 255, we have to compile an
+ extended class, with its own opcode, unless there was a negated special
+ such as \S in the class, and PCRE_UCP is not set, because in that case all
+ characters > 255 are in the class, so any that were explicitly given as
+ well can be ignored. If (when there are explicit characters > 255 that must
+ be listed) there are no characters < 256, we can omit the bitmap in the
+ actual compiled code. */
+
+#ifdef SUPPORT_UTF8
+ if (class_utf8 && (!should_flip_negation || (options & PCRE_UCP) != 0))
+ {
+ *class_utf8data++ = XCL_END; /* Marks the end of extra data */
+ *code++ = OP_XCLASS;
+ code += LINK_SIZE;
+ *code = negate_class? XCL_NOT : 0;
+
+ /* If the map is required, move up the extra data to make room for it;
+ otherwise just move the code pointer to the end of the extra data. */
+
+ if (class_charcount > 0)
+ {
+ *code++ |= XCL_MAP;
+ memmove(code + 32, code, class_utf8data - code);
+ memcpy(code, classbits, 32);
+ code = class_utf8data + 32;
+ }
+ else code = class_utf8data;
+
+ /* Now fill in the complete length of the item */
+
+ PUT(previous, 1, code - previous);
+ break; /* End of class handling */
+ }
+#endif
+
+ /* If there are no characters > 255, or they are all to be included or
+ excluded, set the opcode to OP_CLASS or OP_NCLASS, depending on whether the
+ whole class was negated and whether there were negative specials such as \S
+ (non-UCP) in the class. Then copy the 32-byte map into the code vector,
+ negating it if necessary. */
+
+ *code++ = (negate_class == should_flip_negation) ? OP_CLASS : OP_NCLASS;
+ if (negate_class)
+ {
+ if (lengthptr == NULL) /* Save time in the pre-compile phase */
+ for (c = 0; c < 32; c++) code[c] = ~classbits[c];
+ }
+ else
+ {
+ memcpy(code, classbits, 32);
+ }
+ code += 32;
+ break;
+
+
+ /* ===================================================================*/
+ /* Various kinds of repeat; '{' is not necessarily a quantifier, but this
+ has been tested above. */
+
+ case CHAR_LEFT_CURLY_BRACKET:
+ if (!is_quantifier) goto NORMAL_CHAR;
+ ptr = read_repeat_counts(ptr+1, &repeat_min, &repeat_max, errorcodeptr);
+ if (*errorcodeptr != 0) goto FAILED;
+ goto REPEAT;
+
+ case CHAR_ASTERISK:
+ repeat_min = 0;
+ repeat_max = -1;
+ goto REPEAT;
+
+ case CHAR_PLUS:
+ repeat_min = 1;
+ repeat_max = -1;
+ goto REPEAT;
+
+ case CHAR_QUESTION_MARK:
+ repeat_min = 0;
+ repeat_max = 1;
+
+ REPEAT:
+ if (previous == NULL)
+ {
+ *errorcodeptr = ERR9;
+ goto FAILED;
+ }
+
+ if (repeat_min == 0)
+ {
+ firstbyte = zerofirstbyte; /* Adjust for zero repeat */
+ reqbyte = zeroreqbyte; /* Ditto */
+ }
+
+ /* Remember whether this is a variable length repeat */
+
+ reqvary = (repeat_min == repeat_max)? 0 : REQ_VARY;
+
+ op_type = 0; /* Default single-char op codes */
+ possessive_quantifier = FALSE; /* Default not possessive quantifier */
+
+ /* Save start of previous item, in case we have to move it up in order to
+ insert something before it. */
+
+ tempcode = previous;
+
+ /* If the next character is '+', we have a possessive quantifier. This
+ implies greediness, whatever the setting of the PCRE_UNGREEDY option.
+ If the next character is '?' this is a minimizing repeat, by default,
+ but if PCRE_UNGREEDY is set, it works the other way round. We change the
+ repeat type to the non-default. */
+
+ if (ptr[1] == CHAR_PLUS)
+ {
+ repeat_type = 0; /* Force greedy */
+ possessive_quantifier = TRUE;
+ ptr++;
+ }
+ else if (ptr[1] == CHAR_QUESTION_MARK)
+ {
+ repeat_type = greedy_non_default;
+ ptr++;
+ }
+ else repeat_type = greedy_default;
+
+ /* If previous was a recursion call, wrap it in atomic brackets so that
+ previous becomes the atomic group. All recursions were so wrapped in the
+ past, but it no longer happens for non-repeated recursions. In fact, the
+ repeated ones could be re-implemented independently so as not to need this,
+ but for the moment we rely on the code for repeating groups. */
+
+ if (*previous == OP_RECURSE)
+ {
+ memmove(previous + 1 + LINK_SIZE, previous, 1 + LINK_SIZE);
+ *previous = OP_ONCE;
+ PUT(previous, 1, 2 + 2*LINK_SIZE);
+ previous[2 + 2*LINK_SIZE] = OP_KET;
+ PUT(previous, 3 + 2*LINK_SIZE, 2 + 2*LINK_SIZE);
+ code += 2 + 2 * LINK_SIZE;
+ length_prevgroup = 3 + 3*LINK_SIZE;
+
+ /* When actually compiling, we need to check whether this was a forward
+ reference, and if so, adjust the offset. */
+
+ if (lengthptr == NULL && cd->hwm >= cd->start_workspace + LINK_SIZE)
+ {
+ int offset = GET(cd->hwm, -LINK_SIZE);
+ if (offset == previous + 1 - cd->start_code)
+ PUT(cd->hwm, -LINK_SIZE, offset + 1 + LINK_SIZE);
+ }
+ }
+
+ /* Now handle repetition for the different types of item. */
+
+ /* If previous was a character match, abolish the item and generate a
+ repeat item instead. If a char item has a minumum of more than one, ensure
+ that it is set in reqbyte - it might not be if a sequence such as x{3} is
+ the first thing in a branch because the x will have gone into firstbyte
+ instead. */
+
+ if (*previous == OP_CHAR || *previous == OP_CHARI)
+ {
+ op_type = (*previous == OP_CHAR)? 0 : OP_STARI - OP_STAR;
+
+ /* Deal with UTF-8 characters that take up more than one byte. It's
+ easier to write this out separately than try to macrify it. Use c to
+ hold the length of the character in bytes, plus 0x80 to flag that it's a
+ length rather than a small character. */
+
+#ifdef SUPPORT_UTF8
+ if (utf8 && (code[-1] & 0x80) != 0)
+ {
+ uschar *lastchar = code - 1;
+ while((*lastchar & 0xc0) == 0x80) lastchar--;
+ c = code - lastchar; /* Length of UTF-8 character */
+ memcpy(utf8_char, lastchar, c); /* Save the char */
+ c |= 0x80; /* Flag c as a length */
+ }
+ else
+#endif
+
+ /* Handle the case of a single byte - either with no UTF8 support, or
+ with UTF-8 disabled, or for a UTF-8 character < 128. */
+
+ {
+ c = code[-1];
+ if (repeat_min > 1) reqbyte = c | req_caseopt | cd->req_varyopt;
+ }
+
+ /* If the repetition is unlimited, it pays to see if the next thing on
+ the line is something that cannot possibly match this character. If so,
+ automatically possessifying this item gains some performance in the case
+ where the match fails. */
+
+ if (!possessive_quantifier &&
+ repeat_max < 0 &&
+ check_auto_possessive(previous, utf8, ptr + 1, options, cd))
+ {
+ repeat_type = 0; /* Force greedy */
+ possessive_quantifier = TRUE;
+ }
+
+ goto OUTPUT_SINGLE_REPEAT; /* Code shared with single character types */
+ }
+
+ /* If previous was a single negated character ([^a] or similar), we use
+ one of the special opcodes, replacing it. The code is shared with single-
+ character repeats by setting opt_type to add a suitable offset into
+ repeat_type. We can also test for auto-possessification. OP_NOT and OP_NOTI
+ are currently used only for single-byte chars. */
+
+ else if (*previous == OP_NOT || *previous == OP_NOTI)
+ {
+ op_type = ((*previous == OP_NOT)? OP_NOTSTAR : OP_NOTSTARI) - OP_STAR;
+ c = previous[1];
+ if (!possessive_quantifier &&
+ repeat_max < 0 &&
+ check_auto_possessive(previous, utf8, ptr + 1, options, cd))
+ {
+ repeat_type = 0; /* Force greedy */
+ possessive_quantifier = TRUE;
+ }
+ goto OUTPUT_SINGLE_REPEAT;
+ }
+
+ /* If previous was a character type match (\d or similar), abolish it and
+ create a suitable repeat item. The code is shared with single-character
+ repeats by setting op_type to add a suitable offset into repeat_type. Note
+ the the Unicode property types will be present only when SUPPORT_UCP is
+ defined, but we don't wrap the little bits of code here because it just
+ makes it horribly messy. */
+
+ else if (*previous < OP_EODN)
+ {
+ uschar *oldcode;
+ int prop_type, prop_value;
+ op_type = OP_TYPESTAR - OP_STAR; /* Use type opcodes */
+ c = *previous;
+
+ if (!possessive_quantifier &&
+ repeat_max < 0 &&
+ check_auto_possessive(previous, utf8, ptr + 1, options, cd))
+ {
+ repeat_type = 0; /* Force greedy */
+ possessive_quantifier = TRUE;
+ }
+
+ OUTPUT_SINGLE_REPEAT:
+ if (*previous == OP_PROP || *previous == OP_NOTPROP)
+ {
+ prop_type = previous[1];
+ prop_value = previous[2];
+ }
+ else prop_type = prop_value = -1;
+
+ oldcode = code;
+ code = previous; /* Usually overwrite previous item */
+
+ /* If the maximum is zero then the minimum must also be zero; Perl allows
+ this case, so we do too - by simply omitting the item altogether. */
+
+ if (repeat_max == 0) goto END_REPEAT;
+
+ /*--------------------------------------------------------------------*/
+ /* This code is obsolete from release 8.00; the restriction was finally
+ removed: */
+
+ /* All real repeats make it impossible to handle partial matching (maybe
+ one day we will be able to remove this restriction). */
+
+ /* if (repeat_max != 1) cd->external_flags |= PCRE_NOPARTIAL; */
+ /*--------------------------------------------------------------------*/
+
+ /* Combine the op_type with the repeat_type */
+
+ repeat_type += op_type;
+
+ /* A minimum of zero is handled either as the special case * or ?, or as
+ an UPTO, with the maximum given. */
+
+ if (repeat_min == 0)
+ {
+ if (repeat_max == -1) *code++ = OP_STAR + repeat_type;
+ else if (repeat_max == 1) *code++ = OP_QUERY + repeat_type;
+ else
+ {
+ *code++ = OP_UPTO + repeat_type;
+ PUT2INC(code, 0, repeat_max);
+ }
+ }
+
+ /* A repeat minimum of 1 is optimized into some special cases. If the
+ maximum is unlimited, we use OP_PLUS. Otherwise, the original item is
+ left in place and, if the maximum is greater than 1, we use OP_UPTO with
+ one less than the maximum. */
+
+ else if (repeat_min == 1)
+ {
+ if (repeat_max == -1)
+ *code++ = OP_PLUS + repeat_type;
+ else
+ {
+ code = oldcode; /* leave previous item in place */
+ if (repeat_max == 1) goto END_REPEAT;
+ *code++ = OP_UPTO + repeat_type;
+ PUT2INC(code, 0, repeat_max - 1);
+ }
+ }
+
+ /* The case {n,n} is just an EXACT, while the general case {n,m} is
+ handled as an EXACT followed by an UPTO. */
+
+ else
+ {
+ *code++ = OP_EXACT + op_type; /* NB EXACT doesn't have repeat_type */
+ PUT2INC(code, 0, repeat_min);
+
+ /* If the maximum is unlimited, insert an OP_STAR. Before doing so,
+ we have to insert the character for the previous code. For a repeated
+ Unicode property match, there are two extra bytes that define the
+ required property. In UTF-8 mode, long characters have their length in
+ c, with the 0x80 bit as a flag. */
+
+ if (repeat_max < 0)
+ {
+#ifdef SUPPORT_UTF8
+ if (utf8 && c >= 128)
+ {
+ memcpy(code, utf8_char, c & 7);
+ code += c & 7;
+ }
+ else
+#endif
+ {
+ *code++ = c;
+ if (prop_type >= 0)
+ {
+ *code++ = prop_type;
+ *code++ = prop_value;
+ }
+ }
+ *code++ = OP_STAR + repeat_type;
+ }
+
+ /* Else insert an UPTO if the max is greater than the min, again
+ preceded by the character, for the previously inserted code. If the
+ UPTO is just for 1 instance, we can use QUERY instead. */
+
+ else if (repeat_max != repeat_min)
+ {
+#ifdef SUPPORT_UTF8
+ if (utf8 && c >= 128)
+ {
+ memcpy(code, utf8_char, c & 7);
+ code += c & 7;
+ }
+ else
+#endif
+ *code++ = c;
+ if (prop_type >= 0)
+ {
+ *code++ = prop_type;
+ *code++ = prop_value;
+ }
+ repeat_max -= repeat_min;
+
+ if (repeat_max == 1)
+ {
+ *code++ = OP_QUERY + repeat_type;
+ }
+ else
+ {
+ *code++ = OP_UPTO + repeat_type;
+ PUT2INC(code, 0, repeat_max);
+ }
+ }
+ }
+
+ /* The character or character type itself comes last in all cases. */
+
+#ifdef SUPPORT_UTF8
+ if (utf8 && c >= 128)
+ {
+ memcpy(code, utf8_char, c & 7);
+ code += c & 7;
+ }
+ else
+#endif
+ *code++ = c;
+
+ /* For a repeated Unicode property match, there are two extra bytes that
+ define the required property. */
+
+#ifdef SUPPORT_UCP
+ if (prop_type >= 0)
+ {
+ *code++ = prop_type;
+ *code++ = prop_value;
+ }
+#endif
+ }
+
+ /* If previous was a character class or a back reference, we put the repeat
+ stuff after it, but just skip the item if the repeat was {0,0}. */
+
+ else if (*previous == OP_CLASS ||
+ *previous == OP_NCLASS ||
+#ifdef SUPPORT_UTF8
+ *previous == OP_XCLASS ||
+#endif
+ *previous == OP_REF ||
+ *previous == OP_REFI)
+ {
+ if (repeat_max == 0)
+ {
+ code = previous;
+ goto END_REPEAT;
+ }
+
+ /*--------------------------------------------------------------------*/
+ /* This code is obsolete from release 8.00; the restriction was finally
+ removed: */
+
+ /* All real repeats make it impossible to handle partial matching (maybe
+ one day we will be able to remove this restriction). */
+
+ /* if (repeat_max != 1) cd->external_flags |= PCRE_NOPARTIAL; */
+ /*--------------------------------------------------------------------*/
+
+ if (repeat_min == 0 && repeat_max == -1)
+ *code++ = OP_CRSTAR + repeat_type;
+ else if (repeat_min == 1 && repeat_max == -1)
+ *code++ = OP_CRPLUS + repeat_type;
+ else if (repeat_min == 0 && repeat_max == 1)
+ *code++ = OP_CRQUERY + repeat_type;
+ else
+ {
+ *code++ = OP_CRRANGE + repeat_type;
+ PUT2INC(code, 0, repeat_min);
+ if (repeat_max == -1) repeat_max = 0; /* 2-byte encoding for max */
+ PUT2INC(code, 0, repeat_max);
+ }
+ }
+
+ /* If previous was a bracket group, we may have to replicate it in certain
+ cases. Note that at this point we can encounter only the "basic" bracket
+ opcodes such as BRA and CBRA, as this is the place where they get converted
+ into the more special varieties such as BRAPOS and SBRA. A test for >=
+ OP_ASSERT and <= OP_COND includes ASSERT, ASSERT_NOT, ASSERTBACK,
+ ASSERTBACK_NOT, ONCE, BRA, CBRA, and COND. Originally, PCRE did not allow
+ repetition of assertions, but now it does, for Perl compatibility. */
+
+ else if (*previous >= OP_ASSERT && *previous <= OP_COND)
+ {
+ register int i;
+ int len = (int)(code - previous);
+ uschar *bralink = NULL;
+ uschar *brazeroptr = NULL;
+
+ /* Repeating a DEFINE group is pointless, but Perl allows the syntax, so
+ we just ignore the repeat. */
+
+ if (*previous == OP_COND && previous[LINK_SIZE+1] == OP_DEF)
+ goto END_REPEAT;
+
+ /* There is no sense in actually repeating assertions. The only potential
+ use of repetition is in cases when the assertion is optional. Therefore,
+ if the minimum is greater than zero, just ignore the repeat. If the
+ maximum is not not zero or one, set it to 1. */
+
+ if (*previous < OP_ONCE) /* Assertion */
+ {
+ if (repeat_min > 0) goto END_REPEAT;
+ if (repeat_max < 0 || repeat_max > 1) repeat_max = 1;
+ }
+
+ /* The case of a zero minimum is special because of the need to stick
+ OP_BRAZERO in front of it, and because the group appears once in the
+ data, whereas in other cases it appears the minimum number of times. For
+ this reason, it is simplest to treat this case separately, as otherwise
+ the code gets far too messy. There are several special subcases when the
+ minimum is zero. */
+
+ if (repeat_min == 0)
+ {
+ /* If the maximum is also zero, we used to just omit the group from the
+ output altogether, like this:
+
+ ** if (repeat_max == 0)
+ ** {
+ ** code = previous;
+ ** goto END_REPEAT;
+ ** }
+
+ However, that fails when a group or a subgroup within it is referenced
+ as a subroutine from elsewhere in the pattern, so now we stick in
+ OP_SKIPZERO in front of it so that it is skipped on execution. As we
+ don't have a list of which groups are referenced, we cannot do this
+ selectively.
+
+ If the maximum is 1 or unlimited, we just have to stick in the BRAZERO
+ and do no more at this point. However, we do need to adjust any
+ OP_RECURSE calls inside the group that refer to the group itself or any
+ internal or forward referenced group, because the offset is from the
+ start of the whole regex. Temporarily terminate the pattern while doing
+ this. */
+
+ if (repeat_max <= 1) /* Covers 0, 1, and unlimited */
+ {
+ *code = OP_END;
+ adjust_recurse(previous, 1, utf8, cd, save_hwm);
+ memmove(previous+1, previous, len);
+ code++;
+ if (repeat_max == 0)
+ {
+ *previous++ = OP_SKIPZERO;
+ goto END_REPEAT;
+ }
+ brazeroptr = previous; /* Save for possessive optimizing */
+ *previous++ = OP_BRAZERO + repeat_type;
+ }
+
+ /* If the maximum is greater than 1 and limited, we have to replicate
+ in a nested fashion, sticking OP_BRAZERO before each set of brackets.
+ The first one has to be handled carefully because it's the original
+ copy, which has to be moved up. The remainder can be handled by code
+ that is common with the non-zero minimum case below. We have to
+ adjust the value or repeat_max, since one less copy is required. Once
+ again, we may have to adjust any OP_RECURSE calls inside the group. */
+
+ else
+ {
+ int offset;
+ *code = OP_END;
+ adjust_recurse(previous, 2 + LINK_SIZE, utf8, cd, save_hwm);
+ memmove(previous + 2 + LINK_SIZE, previous, len);
+ code += 2 + LINK_SIZE;
+ *previous++ = OP_BRAZERO + repeat_type;
+ *previous++ = OP_BRA;
+
+ /* We chain together the bracket offset fields that have to be
+ filled in later when the ends of the brackets are reached. */
+
+ offset = (bralink == NULL)? 0 : (int)(previous - bralink);
+ bralink = previous;
+ PUTINC(previous, 0, offset);
+ }
+
+ repeat_max--;
+ }
+
+ /* If the minimum is greater than zero, replicate the group as many
+ times as necessary, and adjust the maximum to the number of subsequent
+ copies that we need. If we set a first char from the group, and didn't
+ set a required char, copy the latter from the former. If there are any
+ forward reference subroutine calls in the group, there will be entries on
+ the workspace list; replicate these with an appropriate increment. */
+
+ else
+ {
+ if (repeat_min > 1)
+ {
+ /* In the pre-compile phase, we don't actually do the replication. We
+ just adjust the length as if we had. Do some paranoid checks for
+ potential integer overflow. The INT64_OR_DOUBLE type is a 64-bit
+ integer type when available, otherwise double. */
+
+ if (lengthptr != NULL)
+ {
+ int delta = (repeat_min - 1)*length_prevgroup;
+ if ((INT64_OR_DOUBLE)(repeat_min - 1)*
+ (INT64_OR_DOUBLE)length_prevgroup >
+ (INT64_OR_DOUBLE)INT_MAX ||
+ OFLOW_MAX - *lengthptr < delta)
+ {
+ *errorcodeptr = ERR20;
+ goto FAILED;
+ }
+ *lengthptr += delta;
+ }
+
+ /* This is compiling for real */
+
+ else
+ {
+ if (groupsetfirstbyte && reqbyte < 0) reqbyte = firstbyte;
+ for (i = 1; i < repeat_min; i++)
+ {
+ uschar *hc;
+ uschar *this_hwm = cd->hwm;
+ memcpy(code, previous, len);
+ for (hc = save_hwm; hc < this_hwm; hc += LINK_SIZE)
+ {
+ PUT(cd->hwm, 0, GET(hc, 0) + len);
+ cd->hwm += LINK_SIZE;
+ }
+ save_hwm = this_hwm;
+ code += len;
+ }
+ }
+ }
+
+ if (repeat_max > 0) repeat_max -= repeat_min;
+ }
+
+ /* This code is common to both the zero and non-zero minimum cases. If
+ the maximum is limited, it replicates the group in a nested fashion,
+ remembering the bracket starts on a stack. In the case of a zero minimum,
+ the first one was set up above. In all cases the repeat_max now specifies
+ the number of additional copies needed. Again, we must remember to
+ replicate entries on the forward reference list. */
+
+ if (repeat_max >= 0)
+ {
+ /* In the pre-compile phase, we don't actually do the replication. We
+ just adjust the length as if we had. For each repetition we must add 1
+ to the length for BRAZERO and for all but the last repetition we must
+ add 2 + 2*LINKSIZE to allow for the nesting that occurs. Do some
+ paranoid checks to avoid integer overflow. The INT64_OR_DOUBLE type is
+ a 64-bit integer type when available, otherwise double. */
+
+ if (lengthptr != NULL && repeat_max > 0)
+ {
+ int delta = repeat_max * (length_prevgroup + 1 + 2 + 2*LINK_SIZE) -
+ 2 - 2*LINK_SIZE; /* Last one doesn't nest */
+ if ((INT64_OR_DOUBLE)repeat_max *
+ (INT64_OR_DOUBLE)(length_prevgroup + 1 + 2 + 2*LINK_SIZE)
+ > (INT64_OR_DOUBLE)INT_MAX ||
+ OFLOW_MAX - *lengthptr < delta)
+ {
+ *errorcodeptr = ERR20;
+ goto FAILED;
+ }
+ *lengthptr += delta;
+ }
+
+ /* This is compiling for real */
+
+ else for (i = repeat_max - 1; i >= 0; i--)
+ {
+ uschar *hc;
+ uschar *this_hwm = cd->hwm;
+
+ *code++ = OP_BRAZERO + repeat_type;
+
+ /* All but the final copy start a new nesting, maintaining the
+ chain of brackets outstanding. */
+
+ if (i != 0)
+ {
+ int offset;
+ *code++ = OP_BRA;
+ offset = (bralink == NULL)? 0 : (int)(code - bralink);
+ bralink = code;
+ PUTINC(code, 0, offset);
+ }
+
+ memcpy(code, previous, len);
+ for (hc = save_hwm; hc < this_hwm; hc += LINK_SIZE)
+ {
+ PUT(cd->hwm, 0, GET(hc, 0) + len + ((i != 0)? 2+LINK_SIZE : 1));
+ cd->hwm += LINK_SIZE;
+ }
+ save_hwm = this_hwm;
+ code += len;
+ }
+
+ /* Now chain through the pending brackets, and fill in their length
+ fields (which are holding the chain links pro tem). */
+
+ while (bralink != NULL)
+ {
+ int oldlinkoffset;
+ int offset = (int)(code - bralink + 1);
+ uschar *bra = code - offset;
+ oldlinkoffset = GET(bra, 1);
+ bralink = (oldlinkoffset == 0)? NULL : bralink - oldlinkoffset;
+ *code++ = OP_KET;
+ PUTINC(code, 0, offset);
+ PUT(bra, 1, offset);
+ }
+ }
+
+ /* If the maximum is unlimited, set a repeater in the final copy. For
+ ONCE brackets, that's all we need to do. However, possessively repeated
+ ONCE brackets can be converted into non-capturing brackets, as the
+ behaviour of (?:xx)++ is the same as (?>xx)++ and this saves having to
+ deal with possessive ONCEs specially.
+
+ Otherwise, if the quantifier was possessive, we convert the BRA code to
+ the POS form, and the KET code to KETRPOS. (It turns out to be convenient
+ at runtime to detect this kind of subpattern at both the start and at the
+ end.) The use of special opcodes makes it possible to reduce greatly the
+ stack usage in pcre_exec(). If the group is preceded by OP_BRAZERO,
+ convert this to OP_BRAPOSZERO. Then cancel the possessive flag so that
+ the default action below, of wrapping everything inside atomic brackets,
+ does not happen.
+
+ Then, when we are doing the actual compile phase, check to see whether
+ this group is one that could match an empty string. If so, convert the
+ initial operator to the S form (e.g. OP_BRA -> OP_SBRA) so that runtime
+ checking can be done. [This check is also applied to ONCE groups at
+ runtime, but in a different way.] */
+
+ else
+ {
+ uschar *ketcode = code - 1 - LINK_SIZE;
+ uschar *bracode = ketcode - GET(ketcode, 1);
+
+ if (*bracode == OP_ONCE && possessive_quantifier) *bracode = OP_BRA;
+ if (*bracode == OP_ONCE)
+ *ketcode = OP_KETRMAX + repeat_type;
+ else
+ {
+ if (possessive_quantifier)
+ {
+ *bracode += 1; /* Switch to xxxPOS opcodes */
+ *ketcode = OP_KETRPOS;
+ if (brazeroptr != NULL) *brazeroptr = OP_BRAPOSZERO;
+ possessive_quantifier = FALSE;
+ }
+ else *ketcode = OP_KETRMAX + repeat_type;
+
+ if (lengthptr == NULL)
+ {
+ uschar *scode = bracode;
+ do
+ {
+ if (could_be_empty_branch(scode, ketcode, utf8, cd))
+ {
+ *bracode += OP_SBRA - OP_BRA;
+ break;
+ }
+ scode += GET(scode, 1);
+ }
+ while (*scode == OP_ALT);
+ }
+ }
+ }
+ }
+
+ /* If previous is OP_FAIL, it was generated by an empty class [] in
+ JavaScript mode. The other ways in which OP_FAIL can be generated, that is
+ by (*FAIL) or (?!) set previous to NULL, which gives a "nothing to repeat"
+ error above. We can just ignore the repeat in JS case. */
+
+ else if (*previous == OP_FAIL) goto END_REPEAT;
+
+ /* Else there's some kind of shambles */
+
+ else
+ {
+ *errorcodeptr = ERR11;
+ goto FAILED;
+ }
+
+ /* If the character following a repeat is '+', or if certain optimization
+ tests above succeeded, possessive_quantifier is TRUE. For some opcodes,
+ there are special alternative opcodes for this case. For anything else, we
+ wrap the entire repeated item inside OP_ONCE brackets. Logically, the '+'
+ notation is just syntactic sugar, taken from Sun's Java package, but the
+ special opcodes can optimize it.
+
+ Possessively repeated subpatterns have already been handled in the code
+ just above, so possessive_quantifier is always FALSE for them at this
+ stage.
+
+ Note that the repeated item starts at tempcode, not at previous, which
+ might be the first part of a string whose (former) last char we repeated.
+
+ Possessifying an 'exact' quantifier has no effect, so we can ignore it. But
+ an 'upto' may follow. We skip over an 'exact' item, and then test the
+ length of what remains before proceeding. */
+
+ if (possessive_quantifier)
+ {
+ int len;
+
+ if (*tempcode == OP_TYPEEXACT)
+ tempcode += _pcre_OP_lengths[*tempcode] +
+ ((tempcode[3] == OP_PROP || tempcode[3] == OP_NOTPROP)? 2 : 0);
+
+ else if (*tempcode == OP_EXACT || *tempcode == OP_NOTEXACT)
+ {
+ tempcode += _pcre_OP_lengths[*tempcode];
+#ifdef SUPPORT_UTF8
+ if (utf8 && tempcode[-1] >= 0xc0)
+ tempcode += _pcre_utf8_table4[tempcode[-1] & 0x3f];
+#endif
+ }
+
+ len = (int)(code - tempcode);
+ if (len > 0) switch (*tempcode)
+ {
+ case OP_STAR: *tempcode = OP_POSSTAR; break;
+ case OP_PLUS: *tempcode = OP_POSPLUS; break;
+ case OP_QUERY: *tempcode = OP_POSQUERY; break;
+ case OP_UPTO: *tempcode = OP_POSUPTO; break;
+
+ case OP_STARI: *tempcode = OP_POSSTARI; break;
+ case OP_PLUSI: *tempcode = OP_POSPLUSI; break;
+ case OP_QUERYI: *tempcode = OP_POSQUERYI; break;
+ case OP_UPTOI: *tempcode = OP_POSUPTOI; break;
+
+ case OP_NOTSTAR: *tempcode = OP_NOTPOSSTAR; break;
+ case OP_NOTPLUS: *tempcode = OP_NOTPOSPLUS; break;
+ case OP_NOTQUERY: *tempcode = OP_NOTPOSQUERY; break;
+ case OP_NOTUPTO: *tempcode = OP_NOTPOSUPTO; break;
+
+ case OP_NOTSTARI: *tempcode = OP_NOTPOSSTARI; break;
+ case OP_NOTPLUSI: *tempcode = OP_NOTPOSPLUSI; break;
+ case OP_NOTQUERYI: *tempcode = OP_NOTPOSQUERYI; break;
+ case OP_NOTUPTOI: *tempcode = OP_NOTPOSUPTOI; break;
+
+ case OP_TYPESTAR: *tempcode = OP_TYPEPOSSTAR; break;
+ case OP_TYPEPLUS: *tempcode = OP_TYPEPOSPLUS; break;
+ case OP_TYPEQUERY: *tempcode = OP_TYPEPOSQUERY; break;
+ case OP_TYPEUPTO: *tempcode = OP_TYPEPOSUPTO; break;
+
+ /* Because we are moving code along, we must ensure that any
+ pending recursive references are updated. */
+
+ default:
+ *code = OP_END;
+ adjust_recurse(tempcode, 1 + LINK_SIZE, utf8, cd, save_hwm);
+ memmove(tempcode + 1+LINK_SIZE, tempcode, len);
+ code += 1 + LINK_SIZE;
+ len += 1 + LINK_SIZE;
+ tempcode[0] = OP_ONCE;
+ *code++ = OP_KET;
+ PUTINC(code, 0, len);
+ PUT(tempcode, 1, len);
+ break;
+ }
+ }
+
+ /* In all case we no longer have a previous item. We also set the
+ "follows varying string" flag for subsequently encountered reqbytes if
+ it isn't already set and we have just passed a varying length item. */
+
+ END_REPEAT:
+ previous = NULL;
+ cd->req_varyopt |= reqvary;
+ break;
+
+
+ /* ===================================================================*/
+ /* Start of nested parenthesized sub-expression, or comment or lookahead or
+ lookbehind or option setting or condition or all the other extended
+ parenthesis forms. */
+
+ case CHAR_LEFT_PARENTHESIS:
+ newoptions = options;
+ skipbytes = 0;
+ bravalue = OP_CBRA;
+ save_hwm = cd->hwm;
+ reset_bracount = FALSE;
+
+ /* First deal with various "verbs" that can be introduced by '*'. */
+
+ if (*(++ptr) == CHAR_ASTERISK &&
+ ((cd->ctypes[ptr[1]] & ctype_letter) != 0 || ptr[1] == ':'))
+ {
+ int i, namelen;
+ int arglen = 0;
+ const char *vn = verbnames;
+ const uschar *name = ptr + 1;
+ const uschar *arg = NULL;
+ previous = NULL;
+ while ((cd->ctypes[*++ptr] & ctype_letter) != 0) {};
+ namelen = (int)(ptr - name);
+
+ /* It appears that Perl allows any characters whatsoever, other than
+ a closing parenthesis, to appear in arguments, so we no longer insist on
+ letters, digits, and underscores. */
+
+ if (*ptr == CHAR_COLON)
+ {
+ arg = ++ptr;
+ while (*ptr != 0 && *ptr != CHAR_RIGHT_PARENTHESIS) ptr++;
+ arglen = (int)(ptr - arg);
+ }
+
+ if (*ptr != CHAR_RIGHT_PARENTHESIS)
+ {
+ *errorcodeptr = ERR60;
+ goto FAILED;
+ }
+
+ /* Scan the table of verb names */
+
+ for (i = 0; i < verbcount; i++)
+ {
+ if (namelen == verbs[i].len &&
+ strncmp((char *)name, vn, namelen) == 0)
+ {
+ /* Check for open captures before ACCEPT and convert it to
+ ASSERT_ACCEPT if in an assertion. */
+
+ if (verbs[i].op == OP_ACCEPT)
+ {
+ open_capitem *oc;
+ if (arglen != 0)
+ {
+ *errorcodeptr = ERR59;
+ goto FAILED;
+ }
+ cd->had_accept = TRUE;
+ for (oc = cd->open_caps; oc != NULL; oc = oc->next)
+ {
+ *code++ = OP_CLOSE;
+ PUT2INC(code, 0, oc->number);
+ }
+ *code++ = (cd->assert_depth > 0)? OP_ASSERT_ACCEPT : OP_ACCEPT;
+ }
+
+ /* Handle other cases with/without an argument */
+
+ else if (arglen == 0)
+ {
+ if (verbs[i].op < 0) /* Argument is mandatory */
+ {
+ *errorcodeptr = ERR66;
+ goto FAILED;
+ }
+ *code = verbs[i].op;
+ if (*code++ == OP_THEN)
+ {
+ PUT(code, 0, code - bcptr->current_branch - 1);
+ code += LINK_SIZE;
+ }
+ }
+
+ else
+ {
+ if (verbs[i].op_arg < 0) /* Argument is forbidden */
+ {
+ *errorcodeptr = ERR59;
+ goto FAILED;
+ }
+ *code = verbs[i].op_arg;
+ if (*code++ == OP_THEN_ARG)
+ {
+ PUT(code, 0, code - bcptr->current_branch - 1);
+ code += LINK_SIZE;
+ }
+ *code++ = arglen;
+ memcpy(code, arg, arglen);
+ code += arglen;
+ *code++ = 0;
+ }
+
+ break; /* Found verb, exit loop */
+ }
+
+ vn += verbs[i].len + 1;
+ }
+
+ if (i < verbcount) continue; /* Successfully handled a verb */
+ *errorcodeptr = ERR60; /* Verb not recognized */
+ goto FAILED;
+ }
+
+ /* Deal with the extended parentheses; all are introduced by '?', and the
+ appearance of any of them means that this is not a capturing group. */
+
+ else if (*ptr == CHAR_QUESTION_MARK)
+ {
+ int i, set, unset, namelen;
+ int *optset;
+ const uschar *name;
+ uschar *slot;
+
+ switch (*(++ptr))
+ {
+ case CHAR_NUMBER_SIGN: /* Comment; skip to ket */
+ ptr++;
+ while (*ptr != 0 && *ptr != CHAR_RIGHT_PARENTHESIS) ptr++;
+ if (*ptr == 0)
+ {
+ *errorcodeptr = ERR18;
+ goto FAILED;
+ }
+ continue;
+
+
+ /* ------------------------------------------------------------ */
+ case CHAR_VERTICAL_LINE: /* Reset capture count for each branch */
+ reset_bracount = TRUE;
+ /* Fall through */
+
+ /* ------------------------------------------------------------ */
+ case CHAR_COLON: /* Non-capturing bracket */
+ bravalue = OP_BRA;
+ ptr++;
+ break;
+
+
+ /* ------------------------------------------------------------ */
+ case CHAR_LEFT_PARENTHESIS:
+ bravalue = OP_COND; /* Conditional group */
+
+ /* A condition can be an assertion, a number (referring to a numbered
+ group), a name (referring to a named group), or 'R', referring to
+ recursion. R<digits> and R&name are also permitted for recursion tests.
+
+ There are several syntaxes for testing a named group: (?(name)) is used
+ by Python; Perl 5.10 onwards uses (?(<name>) or (?('name')).
+
+ There are two unfortunate ambiguities, caused by history. (a) 'R' can
+ be the recursive thing or the name 'R' (and similarly for 'R' followed
+ by digits), and (b) a number could be a name that consists of digits.
+ In both cases, we look for a name first; if not found, we try the other
+ cases. */
+
+ /* For conditions that are assertions, check the syntax, and then exit
+ the switch. This will take control down to where bracketed groups,
+ including assertions, are processed. */
+
+ if (ptr[1] == CHAR_QUESTION_MARK && (ptr[2] == CHAR_EQUALS_SIGN ||
+ ptr[2] == CHAR_EXCLAMATION_MARK || ptr[2] == CHAR_LESS_THAN_SIGN))
+ break;
+
+ /* Most other conditions use OP_CREF (a couple change to OP_RREF
+ below), and all need to skip 3 bytes at the start of the group. */
+
+ code[1+LINK_SIZE] = OP_CREF;
+ skipbytes = 3;
+ refsign = -1;
+
+ /* Check for a test for recursion in a named group. */
+
+ if (ptr[1] == CHAR_R && ptr[2] == CHAR_AMPERSAND)
+ {
+ terminator = -1;
+ ptr += 2;
+ code[1+LINK_SIZE] = OP_RREF; /* Change the type of test */
+ }
+
+ /* Check for a test for a named group's having been set, using the Perl
+ syntax (?(<name>) or (?('name') */
+
+ else if (ptr[1] == CHAR_LESS_THAN_SIGN)
+ {
+ terminator = CHAR_GREATER_THAN_SIGN;
+ ptr++;
+ }
+ else if (ptr[1] == CHAR_APOSTROPHE)
+ {
+ terminator = CHAR_APOSTROPHE;
+ ptr++;
+ }
+ else
+ {
+ terminator = 0;
+ if (ptr[1] == CHAR_MINUS || ptr[1] == CHAR_PLUS) refsign = *(++ptr);
+ }
+
+ /* We now expect to read a name; any thing else is an error */
+
+ if ((cd->ctypes[ptr[1]] & ctype_word) == 0)
+ {
+ ptr += 1; /* To get the right offset */
+ *errorcodeptr = ERR28;
+ goto FAILED;
+ }
+
+ /* Read the name, but also get it as a number if it's all digits */
+
+ recno = 0;
+ name = ++ptr;
+ while ((cd->ctypes[*ptr] & ctype_word) != 0)
+ {
+ if (recno >= 0)
+ recno = ((digitab[*ptr] & ctype_digit) != 0)?
+ recno * 10 + *ptr - CHAR_0 : -1;
+ ptr++;
+ }
+ namelen = (int)(ptr - name);
+
+ if ((terminator > 0 && *ptr++ != terminator) ||
+ *ptr++ != CHAR_RIGHT_PARENTHESIS)
+ {
+ ptr--; /* Error offset */
+ *errorcodeptr = ERR26;
+ goto FAILED;
+ }
+
+ /* Do no further checking in the pre-compile phase. */
+
+ if (lengthptr != NULL) break;
+
+ /* In the real compile we do the work of looking for the actual
+ reference. If the string started with "+" or "-" we require the rest to
+ be digits, in which case recno will be set. */
+
+ if (refsign > 0)
+ {
+ if (recno <= 0)
+ {
+ *errorcodeptr = ERR58;
+ goto FAILED;
+ }
+ recno = (refsign == CHAR_MINUS)?
+ cd->bracount - recno + 1 : recno +cd->bracount;
+ if (recno <= 0 || recno > cd->final_bracount)
+ {
+ *errorcodeptr = ERR15;
+ goto FAILED;
+ }
+ PUT2(code, 2+LINK_SIZE, recno);
+ break;
+ }
+
+ /* Otherwise (did not start with "+" or "-"), start by looking for the
+ name. If we find a name, add one to the opcode to change OP_CREF or
+ OP_RREF into OP_NCREF or OP_NRREF. These behave exactly the same,
+ except they record that the reference was originally to a name. The
+ information is used to check duplicate names. */
+
+ slot = cd->name_table;
+ for (i = 0; i < cd->names_found; i++)
+ {
+ if (strncmp((char *)name, (char *)slot+2, namelen) == 0) break;
+ slot += cd->name_entry_size;
+ }
+
+ /* Found a previous named subpattern */
+
+ if (i < cd->names_found)
+ {
+ recno = GET2(slot, 0);
+ PUT2(code, 2+LINK_SIZE, recno);
+ code[1+LINK_SIZE]++;
+ }
+
+ /* Search the pattern for a forward reference */
+
+ else if ((i = find_parens(cd, name, namelen,
+ (options & PCRE_EXTENDED) != 0, utf8)) > 0)
+ {
+ PUT2(code, 2+LINK_SIZE, i);
+ code[1+LINK_SIZE]++;
+ }
+
+ /* If terminator == 0 it means that the name followed directly after
+ the opening parenthesis [e.g. (?(abc)...] and in this case there are
+ some further alternatives to try. For the cases where terminator != 0
+ [things like (?(<name>... or (?('name')... or (?(R&name)... ] we have
+ now checked all the possibilities, so give an error. */
+
+ else if (terminator != 0)
+ {
+ *errorcodeptr = ERR15;
+ goto FAILED;
+ }
+
+ /* Check for (?(R) for recursion. Allow digits after R to specify a
+ specific group number. */
+
+ else if (*name == CHAR_R)
+ {
+ recno = 0;
+ for (i = 1; i < namelen; i++)
+ {
+ if ((digitab[name[i]] & ctype_digit) == 0)
+ {
+ *errorcodeptr = ERR15;
+ goto FAILED;
+ }
+ recno = recno * 10 + name[i] - CHAR_0;
+ }
+ if (recno == 0) recno = RREF_ANY;
+ code[1+LINK_SIZE] = OP_RREF; /* Change test type */
+ PUT2(code, 2+LINK_SIZE, recno);
+ }
+
+ /* Similarly, check for the (?(DEFINE) "condition", which is always
+ false. */
+
+ else if (namelen == 6 && strncmp((char *)name, STRING_DEFINE, 6) == 0)
+ {
+ code[1+LINK_SIZE] = OP_DEF;
+ skipbytes = 1;
+ }
+
+ /* Check for the "name" actually being a subpattern number. We are
+ in the second pass here, so final_bracount is set. */
+
+ else if (recno > 0 && recno <= cd->final_bracount)
+ {
+ PUT2(code, 2+LINK_SIZE, recno);
+ }
+
+ /* Either an unidentified subpattern, or a reference to (?(0) */
+
+ else
+ {
+ *errorcodeptr = (recno == 0)? ERR35: ERR15;
+ goto FAILED;
+ }
+ break;
+
+
+ /* ------------------------------------------------------------ */
+ case CHAR_EQUALS_SIGN: /* Positive lookahead */
+ bravalue = OP_ASSERT;
+ cd->assert_depth += 1;
+ ptr++;
+ break;
+
+
+ /* ------------------------------------------------------------ */
+ case CHAR_EXCLAMATION_MARK: /* Negative lookahead */
+ ptr++;
+ if (*ptr == CHAR_RIGHT_PARENTHESIS) /* Optimize (?!) */
+ {
+ *code++ = OP_FAIL;
+ previous = NULL;
+ continue;
+ }
+ bravalue = OP_ASSERT_NOT;
+ cd->assert_depth += 1;
+ break;
+
+
+ /* ------------------------------------------------------------ */
+ case CHAR_LESS_THAN_SIGN: /* Lookbehind or named define */
+ switch (ptr[1])
+ {
+ case CHAR_EQUALS_SIGN: /* Positive lookbehind */
+ bravalue = OP_ASSERTBACK;
+ cd->assert_depth += 1;
+ ptr += 2;
+ break;
+
+ case CHAR_EXCLAMATION_MARK: /* Negative lookbehind */
+ bravalue = OP_ASSERTBACK_NOT;
+ cd->assert_depth += 1;
+ ptr += 2;
+ break;
+
+ default: /* Could be name define, else bad */
+ if ((cd->ctypes[ptr[1]] & ctype_word) != 0) goto DEFINE_NAME;
+ ptr++; /* Correct offset for error */
+ *errorcodeptr = ERR24;
+ goto FAILED;
+ }
+ break;
+
+
+ /* ------------------------------------------------------------ */
+ case CHAR_GREATER_THAN_SIGN: /* One-time brackets */
+ bravalue = OP_ONCE;
+ ptr++;
+ break;
+
+
+ /* ------------------------------------------------------------ */
+ case CHAR_C: /* Callout - may be followed by digits; */
+ previous_callout = code; /* Save for later completion */
+ after_manual_callout = 1; /* Skip one item before completing */
+ *code++ = OP_CALLOUT;
+ {
+ int n = 0;
+ while ((digitab[*(++ptr)] & ctype_digit) != 0)
+ n = n * 10 + *ptr - CHAR_0;
+ if (*ptr != CHAR_RIGHT_PARENTHESIS)
+ {
+ *errorcodeptr = ERR39;
+ goto FAILED;
+ }
+ if (n > 255)
+ {
+ *errorcodeptr = ERR38;
+ goto FAILED;
+ }
+ *code++ = n;
+ PUT(code, 0, (int)(ptr - cd->start_pattern + 1)); /* Pattern offset */
+ PUT(code, LINK_SIZE, 0); /* Default length */
+ code += 2 * LINK_SIZE;
+ }
+ previous = NULL;
+ continue;
+
+
+ /* ------------------------------------------------------------ */
+ case CHAR_P: /* Python-style named subpattern handling */
+ if (*(++ptr) == CHAR_EQUALS_SIGN ||
+ *ptr == CHAR_GREATER_THAN_SIGN) /* Reference or recursion */
+ {
+ is_recurse = *ptr == CHAR_GREATER_THAN_SIGN;
+ terminator = CHAR_RIGHT_PARENTHESIS;
+ goto NAMED_REF_OR_RECURSE;
+ }
+ else if (*ptr != CHAR_LESS_THAN_SIGN) /* Test for Python-style defn */
+ {
+ *errorcodeptr = ERR41;
+ goto FAILED;
+ }
+ /* Fall through to handle (?P< as (?< is handled */
+
+
+ /* ------------------------------------------------------------ */
+ DEFINE_NAME: /* Come here from (?< handling */
+ case CHAR_APOSTROPHE:
+ {
+ terminator = (*ptr == CHAR_LESS_THAN_SIGN)?
+ CHAR_GREATER_THAN_SIGN : CHAR_APOSTROPHE;
+ name = ++ptr;
+
+ while ((cd->ctypes[*ptr] & ctype_word) != 0) ptr++;
+ namelen = (int)(ptr - name);
+
+ /* In the pre-compile phase, just do a syntax check. */
+
+ if (lengthptr != NULL)
+ {
+ if (*ptr != terminator)
+ {
+ *errorcodeptr = ERR42;
+ goto FAILED;
+ }
+ if (cd->names_found >= MAX_NAME_COUNT)
+ {
+ *errorcodeptr = ERR49;
+ goto FAILED;
+ }
+ if (namelen + 3 > cd->name_entry_size)
+ {
+ cd->name_entry_size = namelen + 3;
+ if (namelen > MAX_NAME_SIZE)
+ {
+ *errorcodeptr = ERR48;
+ goto FAILED;
+ }
+ }
+ }
+
+ /* In the real compile, create the entry in the table, maintaining
+ alphabetical order. Duplicate names for different numbers are
+ permitted only if PCRE_DUPNAMES is set. Duplicate names for the same
+ number are always OK. (An existing number can be re-used if (?|
+ appears in the pattern.) In either event, a duplicate name results in
+ a duplicate entry in the table, even if the number is the same. This
+ is because the number of names, and hence the table size, is computed
+ in the pre-compile, and it affects various numbers and pointers which
+ would all have to be modified, and the compiled code moved down, if
+ duplicates with the same number were omitted from the table. This
+ doesn't seem worth the hassle. However, *different* names for the
+ same number are not permitted. */
+
+ else
+ {
+ BOOL dupname = FALSE;
+ slot = cd->name_table;
+
+ for (i = 0; i < cd->names_found; i++)
+ {
+ int crc = memcmp(name, slot+2, namelen);
+ if (crc == 0)
+ {
+ if (slot[2+namelen] == 0)
+ {
+ if (GET2(slot, 0) != cd->bracount + 1 &&
+ (options & PCRE_DUPNAMES) == 0)
+ {
+ *errorcodeptr = ERR43;
+ goto FAILED;
+ }
+ else dupname = TRUE;
+ }
+ else crc = -1; /* Current name is a substring */
+ }
+
+ /* Make space in the table and break the loop for an earlier
+ name. For a duplicate or later name, carry on. We do this for
+ duplicates so that in the simple case (when ?(| is not used) they
+ are in order of their numbers. */
+
+ if (crc < 0)
+ {
+ memmove(slot + cd->name_entry_size, slot,
+ (cd->names_found - i) * cd->name_entry_size);
+ break;
+ }
+
+ /* Continue the loop for a later or duplicate name */
+
+ slot += cd->name_entry_size;
+ }
+
+ /* For non-duplicate names, check for a duplicate number before
+ adding the new name. */
+
+ if (!dupname)
+ {
+ uschar *cslot = cd->name_table;
+ for (i = 0; i < cd->names_found; i++)
+ {
+ if (cslot != slot)
+ {
+ if (GET2(cslot, 0) == cd->bracount + 1)
+ {
+ *errorcodeptr = ERR65;
+ goto FAILED;
+ }
+ }
+ else i--;
+ cslot += cd->name_entry_size;
+ }
+ }
+
+ PUT2(slot, 0, cd->bracount + 1);
+ memcpy(slot + 2, name, namelen);
+ slot[2+namelen] = 0;
+ }
+ }
+
+ /* In both pre-compile and compile, count the number of names we've
+ encountered. */
+
+ cd->names_found++;
+ ptr++; /* Move past > or ' */
+ goto NUMBERED_GROUP;
+
+
+ /* ------------------------------------------------------------ */
+ case CHAR_AMPERSAND: /* Perl recursion/subroutine syntax */
+ terminator = CHAR_RIGHT_PARENTHESIS;
+ is_recurse = TRUE;
+ /* Fall through */
+
+ /* We come here from the Python syntax above that handles both
+ references (?P=name) and recursion (?P>name), as well as falling
+ through from the Perl recursion syntax (?&name). We also come here from
+ the Perl \k<name> or \k'name' back reference syntax and the \k{name}
+ .NET syntax, and the Oniguruma \g<...> and \g'...' subroutine syntax. */
+
+ NAMED_REF_OR_RECURSE:
+ name = ++ptr;
+ while ((cd->ctypes[*ptr] & ctype_word) != 0) ptr++;
+ namelen = (int)(ptr - name);
+
+ /* In the pre-compile phase, do a syntax check. We used to just set
+ a dummy reference number, because it was not used in the first pass.
+ However, with the change of recursive back references to be atomic,
+ we have to look for the number so that this state can be identified, as
+ otherwise the incorrect length is computed. If it's not a backwards
+ reference, the dummy number will do. */
+
+ if (lengthptr != NULL)
+ {
+ const uschar *temp;
+
+ if (namelen == 0)
+ {
+ *errorcodeptr = ERR62;
+ goto FAILED;
+ }
+ if (*ptr != terminator)
+ {
+ *errorcodeptr = ERR42;
+ goto FAILED;
+ }
+ if (namelen > MAX_NAME_SIZE)
+ {
+ *errorcodeptr = ERR48;
+ goto FAILED;
+ }
+
+ /* The name table does not exist in the first pass, so we cannot
+ do a simple search as in the code below. Instead, we have to scan the
+ pattern to find the number. It is important that we scan it only as
+ far as we have got because the syntax of named subpatterns has not
+ been checked for the rest of the pattern, and find_parens() assumes
+ correct syntax. In any case, it's a waste of resources to scan
+ further. We stop the scan at the current point by temporarily
+ adjusting the value of cd->endpattern. */
+
+ temp = cd->end_pattern;
+ cd->end_pattern = ptr;
+ recno = find_parens(cd, name, namelen,
+ (options & PCRE_EXTENDED) != 0, utf8);
+ cd->end_pattern = temp;
+ if (recno < 0) recno = 0; /* Forward ref; set dummy number */
+ }
+
+ /* In the real compile, seek the name in the table. We check the name
+ first, and then check that we have reached the end of the name in the
+ table. That way, if the name that is longer than any in the table,
+ the comparison will fail without reading beyond the table entry. */
+
+ else
+ {
+ slot = cd->name_table;
+ for (i = 0; i < cd->names_found; i++)
+ {
+ if (strncmp((char *)name, (char *)slot+2, namelen) == 0 &&
+ slot[2+namelen] == 0)
+ break;
+ slot += cd->name_entry_size;
+ }
+
+ if (i < cd->names_found) /* Back reference */
+ {
+ recno = GET2(slot, 0);
+ }
+ else if ((recno = /* Forward back reference */
+ find_parens(cd, name, namelen,
+ (options & PCRE_EXTENDED) != 0, utf8)) <= 0)
+ {
+ *errorcodeptr = ERR15;
+ goto FAILED;
+ }
+ }
+
+ /* In both phases, we can now go to the code than handles numerical
+ recursion or backreferences. */
+
+ if (is_recurse) goto HANDLE_RECURSION;
+ else goto HANDLE_REFERENCE;
+
+
+ /* ------------------------------------------------------------ */
+ case CHAR_R: /* Recursion */
+ ptr++; /* Same as (?0) */
+ /* Fall through */
+
+
+ /* ------------------------------------------------------------ */
+ case CHAR_MINUS: case CHAR_PLUS: /* Recursion or subroutine */
+ case CHAR_0: case CHAR_1: case CHAR_2: case CHAR_3: case CHAR_4:
+ case CHAR_5: case CHAR_6: case CHAR_7: case CHAR_8: case CHAR_9:
+ {
+ const uschar *called;
+ terminator = CHAR_RIGHT_PARENTHESIS;
+
+ /* Come here from the \g<...> and \g'...' code (Oniguruma
+ compatibility). However, the syntax has been checked to ensure that
+ the ... are a (signed) number, so that neither ERR63 nor ERR29 will
+ be called on this path, nor with the jump to OTHER_CHAR_AFTER_QUERY
+ ever be taken. */
+
+ HANDLE_NUMERICAL_RECURSION:
+
+ if ((refsign = *ptr) == CHAR_PLUS)
+ {
+ ptr++;
+ if ((digitab[*ptr] & ctype_digit) == 0)
+ {
+ *errorcodeptr = ERR63;
+ goto FAILED;
+ }
+ }
+ else if (refsign == CHAR_MINUS)
+ {
+ if ((digitab[ptr[1]] & ctype_digit) == 0)
+ goto OTHER_CHAR_AFTER_QUERY;
+ ptr++;
+ }
+
+ recno = 0;
+ while((digitab[*ptr] & ctype_digit) != 0)
+ recno = recno * 10 + *ptr++ - CHAR_0;
+
+ if (*ptr != terminator)
+ {
+ *errorcodeptr = ERR29;
+ goto FAILED;
+ }
+
+ if (refsign == CHAR_MINUS)
+ {
+ if (recno == 0)
+ {
+ *errorcodeptr = ERR58;
+ goto FAILED;
+ }
+ recno = cd->bracount - recno + 1;
+ if (recno <= 0)
+ {
+ *errorcodeptr = ERR15;
+ goto FAILED;
+ }
+ }
+ else if (refsign == CHAR_PLUS)
+ {
+ if (recno == 0)
+ {
+ *errorcodeptr = ERR58;
+ goto FAILED;
+ }
+ recno += cd->bracount;
+ }
+
+ /* Come here from code above that handles a named recursion */
+
+ HANDLE_RECURSION:
+
+ previous = code;
+ called = cd->start_code;
+
+ /* When we are actually compiling, find the bracket that is being
+ referenced. Temporarily end the regex in case it doesn't exist before
+ this point. If we end up with a forward reference, first check that
+ the bracket does occur later so we can give the error (and position)
+ now. Then remember this forward reference in the workspace so it can
+ be filled in at the end. */
+
+ if (lengthptr == NULL)
+ {
+ *code = OP_END;
+ if (recno != 0)
+ called = _pcre_find_bracket(cd->start_code, utf8, recno);
+
+ /* Forward reference */
+
+ if (called == NULL)
+ {
+ if (find_parens(cd, NULL, recno,
+ (options & PCRE_EXTENDED) != 0, utf8) < 0)
+ {
+ *errorcodeptr = ERR15;
+ goto FAILED;
+ }
+
+ /* Fudge the value of "called" so that when it is inserted as an
+ offset below, what it actually inserted is the reference number
+ of the group. Then remember the forward reference. */
+
+ called = cd->start_code + recno;
+ PUTINC(cd->hwm, 0, (int)(code + 1 - cd->start_code));
+ }
+
+ /* If not a forward reference, and the subpattern is still open,
+ this is a recursive call. We check to see if this is a left
+ recursion that could loop for ever, and diagnose that case. We
+ must not, however, do this check if we are in a conditional
+ subpattern because the condition might be testing for recursion in
+ a pattern such as /(?(R)a+|(?R)b)/, which is perfectly valid.
+ Forever loops are also detected at runtime, so those that occur in
+ conditional subpatterns will be picked up then. */
+
+ else if (GET(called, 1) == 0 && cond_depth <= 0 &&
+ could_be_empty(called, code, bcptr, utf8, cd))
+ {
+ *errorcodeptr = ERR40;
+ goto FAILED;
+ }
+ }
+
+ /* Insert the recursion/subroutine item. */
+
+ *code = OP_RECURSE;
+ PUT(code, 1, (int)(called - cd->start_code));
+ code += 1 + LINK_SIZE;
+ }
+
+ /* Can't determine a first byte now */
+
+ if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE;
+ continue;
+
+
+ /* ------------------------------------------------------------ */
+ default: /* Other characters: check option setting */
+ OTHER_CHAR_AFTER_QUERY:
+ set = unset = 0;
+ optset = &set;
+
+ while (*ptr != CHAR_RIGHT_PARENTHESIS && *ptr != CHAR_COLON)
+ {
+ switch (*ptr++)
+ {
+ case CHAR_MINUS: optset = &unset; break;
+
+ case CHAR_J: /* Record that it changed in the external options */
+ *optset |= PCRE_DUPNAMES;
+ cd->external_flags |= PCRE_JCHANGED;
+ break;
+
+ case CHAR_i: *optset |= PCRE_CASELESS; break;
+ case CHAR_m: *optset |= PCRE_MULTILINE; break;
+ case CHAR_s: *optset |= PCRE_DOTALL; break;
+ case CHAR_x: *optset |= PCRE_EXTENDED; break;
+ case CHAR_U: *optset |= PCRE_UNGREEDY; break;
+ case CHAR_X: *optset |= PCRE_EXTRA; break;
+
+ default: *errorcodeptr = ERR12;
+ ptr--; /* Correct the offset */
+ goto FAILED;
+ }
+ }
+
+ /* Set up the changed option bits, but don't change anything yet. */
+
+ newoptions = (options | set) & (~unset);
+
+ /* If the options ended with ')' this is not the start of a nested
+ group with option changes, so the options change at this level. If this
+ item is right at the start of the pattern, the options can be
+ abstracted and made external in the pre-compile phase, and ignored in
+ the compile phase. This can be helpful when matching -- for instance in
+ caseless checking of required bytes.
+
+ If the code pointer is not (cd->start_code + 1 + LINK_SIZE), we are
+ definitely *not* at the start of the pattern because something has been
+ compiled. In the pre-compile phase, however, the code pointer can have
+ that value after the start, because it gets reset as code is discarded
+ during the pre-compile. However, this can happen only at top level - if
+ we are within parentheses, the starting BRA will still be present. At
+ any parenthesis level, the length value can be used to test if anything
+ has been compiled at that level. Thus, a test for both these conditions
+ is necessary to ensure we correctly detect the start of the pattern in
+ both phases.
+
+ If we are not at the pattern start, reset the greedy defaults and the
+ case value for firstbyte and reqbyte. */
+
+ if (*ptr == CHAR_RIGHT_PARENTHESIS)
+ {
+ if (code == cd->start_code + 1 + LINK_SIZE &&
+ (lengthptr == NULL || *lengthptr == 2 + 2*LINK_SIZE))
+ {
+ cd->external_options = newoptions;
+ }
+ else
+ {
+ greedy_default = ((newoptions & PCRE_UNGREEDY) != 0);
+ greedy_non_default = greedy_default ^ 1;
+ req_caseopt = ((newoptions & PCRE_CASELESS) != 0)? REQ_CASELESS : 0;
+ }
+
+ /* Change options at this level, and pass them back for use
+ in subsequent branches. */
+
+ *optionsptr = options = newoptions;
+ previous = NULL; /* This item can't be repeated */
+ continue; /* It is complete */
+ }
+
+ /* If the options ended with ':' we are heading into a nested group
+ with possible change of options. Such groups are non-capturing and are
+ not assertions of any kind. All we need to do is skip over the ':';
+ the newoptions value is handled below. */
+
+ bravalue = OP_BRA;
+ ptr++;
+ } /* End of switch for character following (? */
+ } /* End of (? handling */
+
+ /* Opening parenthesis not followed by '*' or '?'. If PCRE_NO_AUTO_CAPTURE
+ is set, all unadorned brackets become non-capturing and behave like (?:...)
+ brackets. */
+
+ else if ((options & PCRE_NO_AUTO_CAPTURE) != 0)
+ {
+ bravalue = OP_BRA;
+ }
+
+ /* Else we have a capturing group. */
+
+ else
+ {
+ NUMBERED_GROUP:
+ cd->bracount += 1;
+ PUT2(code, 1+LINK_SIZE, cd->bracount);
+ skipbytes = 2;
+ }
+
+ /* Process nested bracketed regex. Assertions used not to be repeatable,
+ but this was changed for Perl compatibility, so all kinds can now be
+ repeated. We copy code into a non-register variable (tempcode) in order to
+ be able to pass its address because some compilers complain otherwise. */
+
+ previous = code; /* For handling repetition */
+ *code = bravalue;
+ tempcode = code;
+ tempreqvary = cd->req_varyopt; /* Save value before bracket */
+ length_prevgroup = 0; /* Initialize for pre-compile phase */
+
+ if (!compile_regex(
+ newoptions, /* The complete new option state */
+ &tempcode, /* Where to put code (updated) */
+ &ptr, /* Input pointer (updated) */
+ errorcodeptr, /* Where to put an error message */
+ (bravalue == OP_ASSERTBACK ||
+ bravalue == OP_ASSERTBACK_NOT), /* TRUE if back assert */
+ reset_bracount, /* True if (?| group */
+ skipbytes, /* Skip over bracket number */
+ cond_depth +
+ ((bravalue == OP_COND)?1:0), /* Depth of condition subpatterns */
+ &subfirstbyte, /* For possible first char */
+ &subreqbyte, /* For possible last char */
+ bcptr, /* Current branch chain */
+ cd, /* Tables block */
+ (lengthptr == NULL)? NULL : /* Actual compile phase */
+ &length_prevgroup /* Pre-compile phase */
+ ))
+ goto FAILED;
+
+ if (bravalue >= OP_ASSERT && bravalue <= OP_ASSERTBACK_NOT)
+ cd->assert_depth -= 1;
+
+ /* At the end of compiling, code is still pointing to the start of the
+ group, while tempcode has been updated to point past the end of the group
+ and any option resetting that may follow it. The pattern pointer (ptr)
+ is on the bracket. */
+
+ /* If this is a conditional bracket, check that there are no more than
+ two branches in the group, or just one if it's a DEFINE group. We do this
+ in the real compile phase, not in the pre-pass, where the whole group may
+ not be available. */
+
+ if (bravalue == OP_COND && lengthptr == NULL)
+ {
+ uschar *tc = code;
+ int condcount = 0;
+
+ do {
+ condcount++;
+ tc += GET(tc,1);
+ }
+ while (*tc != OP_KET);
+
+ /* A DEFINE group is never obeyed inline (the "condition" is always
+ false). It must have only one branch. */
+
+ if (code[LINK_SIZE+1] == OP_DEF)
+ {
+ if (condcount > 1)
+ {
+ *errorcodeptr = ERR54;
+ goto FAILED;
+ }
+ bravalue = OP_DEF; /* Just a flag to suppress char handling below */
+ }
+
+ /* A "normal" conditional group. If there is just one branch, we must not
+ make use of its firstbyte or reqbyte, because this is equivalent to an
+ empty second branch. */
+
+ else
+ {
+ if (condcount > 2)
+ {
+ *errorcodeptr = ERR27;
+ goto FAILED;
+ }
+ if (condcount == 1) subfirstbyte = subreqbyte = REQ_NONE;
+ }
+ }
+
+ /* Error if hit end of pattern */
+
+ if (*ptr != CHAR_RIGHT_PARENTHESIS)
+ {
+ *errorcodeptr = ERR14;
+ goto FAILED;
+ }
+
+ /* In the pre-compile phase, update the length by the length of the group,
+ less the brackets at either end. Then reduce the compiled code to just a
+ set of non-capturing brackets so that it doesn't use much memory if it is
+ duplicated by a quantifier.*/
+
+ if (lengthptr != NULL)
+ {
+ if (OFLOW_MAX - *lengthptr < length_prevgroup - 2 - 2*LINK_SIZE)
+ {
+ *errorcodeptr = ERR20;
+ goto FAILED;
+ }
+ *lengthptr += length_prevgroup - 2 - 2*LINK_SIZE;
+ code++; /* This already contains bravalue */
+ PUTINC(code, 0, 1 + LINK_SIZE);
+ *code++ = OP_KET;
+ PUTINC(code, 0, 1 + LINK_SIZE);
+ break; /* No need to waste time with special character handling */
+ }
+
+ /* Otherwise update the main code pointer to the end of the group. */
+
+ code = tempcode;
+
+ /* For a DEFINE group, required and first character settings are not
+ relevant. */
+
+ if (bravalue == OP_DEF) break;
+
+ /* Handle updating of the required and first characters for other types of
+ group. Update for normal brackets of all kinds, and conditions with two
+ branches (see code above). If the bracket is followed by a quantifier with
+ zero repeat, we have to back off. Hence the definition of zeroreqbyte and
+ zerofirstbyte outside the main loop so that they can be accessed for the
+ back off. */
+
+ zeroreqbyte = reqbyte;
+ zerofirstbyte = firstbyte;
+ groupsetfirstbyte = FALSE;
+
+ if (bravalue >= OP_ONCE)
+ {
+ /* If we have not yet set a firstbyte in this branch, take it from the
+ subpattern, remembering that it was set here so that a repeat of more
+ than one can replicate it as reqbyte if necessary. If the subpattern has
+ no firstbyte, set "none" for the whole branch. In both cases, a zero
+ repeat forces firstbyte to "none". */
+
+ if (firstbyte == REQ_UNSET)
+ {
+ if (subfirstbyte >= 0)
+ {
+ firstbyte = subfirstbyte;
+ groupsetfirstbyte = TRUE;
+ }
+ else firstbyte = REQ_NONE;
+ zerofirstbyte = REQ_NONE;
+ }
+
+ /* If firstbyte was previously set, convert the subpattern's firstbyte
+ into reqbyte if there wasn't one, using the vary flag that was in
+ existence beforehand. */
+
+ else if (subfirstbyte >= 0 && subreqbyte < 0)
+ subreqbyte = subfirstbyte | tempreqvary;
+
+ /* If the subpattern set a required byte (or set a first byte that isn't
+ really the first byte - see above), set it. */
+
+ if (subreqbyte >= 0) reqbyte = subreqbyte;
+ }
+
+ /* For a forward assertion, we take the reqbyte, if set. This can be
+ helpful if the pattern that follows the assertion doesn't set a different
+ char. For example, it's useful for /(?=abcde).+/. We can't set firstbyte
+ for an assertion, however because it leads to incorrect effect for patterns
+ such as /(?=a)a.+/ when the "real" "a" would then become a reqbyte instead
+ of a firstbyte. This is overcome by a scan at the end if there's no
+ firstbyte, looking for an asserted first char. */
+
+ else if (bravalue == OP_ASSERT && subreqbyte >= 0) reqbyte = subreqbyte;
+ break; /* End of processing '(' */
+
+
+ /* ===================================================================*/
+ /* Handle metasequences introduced by \. For ones like \d, the ESC_ values
+ are arranged to be the negation of the corresponding OP_values in the
+ default case when PCRE_UCP is not set. For the back references, the values
+ are ESC_REF plus the reference number. Only back references and those types
+ that consume a character may be repeated. We can test for values between
+ ESC_b and ESC_Z for the latter; this may have to change if any new ones are
+ ever created. */
+
+ case CHAR_BACKSLASH:
+ tempptr = ptr;
+ c = check_escape(&ptr, errorcodeptr, cd->bracount, options, FALSE);
+ if (*errorcodeptr != 0) goto FAILED;
+
+ if (c < 0)
+ {
+ if (-c == ESC_Q) /* Handle start of quoted string */
+ {
+ if (ptr[1] == CHAR_BACKSLASH && ptr[2] == CHAR_E)
+ ptr += 2; /* avoid empty string */
+ else inescq = TRUE;
+ continue;
+ }
+
+ if (-c == ESC_E) continue; /* Perl ignores an orphan \E */
+
+ /* For metasequences that actually match a character, we disable the
+ setting of a first character if it hasn't already been set. */
+
+ if (firstbyte == REQ_UNSET && -c > ESC_b && -c < ESC_Z)
+ firstbyte = REQ_NONE;
+
+ /* Set values to reset to if this is followed by a zero repeat. */
+
+ zerofirstbyte = firstbyte;
+ zeroreqbyte = reqbyte;
+
+ /* \g<name> or \g'name' is a subroutine call by name and \g<n> or \g'n'
+ is a subroutine call by number (Oniguruma syntax). In fact, the value
+ -ESC_g is returned only for these cases. So we don't need to check for <
+ or ' if the value is -ESC_g. For the Perl syntax \g{n} the value is
+ -ESC_REF+n, and for the Perl syntax \g{name} the result is -ESC_k (as
+ that is a synonym for a named back reference). */
+
+ if (-c == ESC_g)
+ {
+ const uschar *p;
+ save_hwm = cd->hwm; /* Normally this is set when '(' is read */
+ terminator = (*(++ptr) == CHAR_LESS_THAN_SIGN)?
+ CHAR_GREATER_THAN_SIGN : CHAR_APOSTROPHE;
+
+ /* These two statements stop the compiler for warning about possibly
+ unset variables caused by the jump to HANDLE_NUMERICAL_RECURSION. In
+ fact, because we actually check for a number below, the paths that
+ would actually be in error are never taken. */
+
+ skipbytes = 0;
+ reset_bracount = FALSE;
+
+ /* Test for a name */
+
+ if (ptr[1] != CHAR_PLUS && ptr[1] != CHAR_MINUS)
+ {
+ BOOL isnumber = TRUE;
+ for (p = ptr + 1; *p != 0 && *p != terminator; p++)
+ {
+ if ((cd->ctypes[*p] & ctype_digit) == 0) isnumber = FALSE;
+ if ((cd->ctypes[*p] & ctype_word) == 0) break;
+ }
+ if (*p != terminator)
+ {
+ *errorcodeptr = ERR57;
+ break;
+ }
+ if (isnumber)
+ {
+ ptr++;
+ goto HANDLE_NUMERICAL_RECURSION;
+ }
+ is_recurse = TRUE;
+ goto NAMED_REF_OR_RECURSE;
+ }
+
+ /* Test a signed number in angle brackets or quotes. */
+
+ p = ptr + 2;
+ while ((digitab[*p] & ctype_digit) != 0) p++;
+ if (*p != terminator)
+ {
+ *errorcodeptr = ERR57;
+ break;
+ }
+ ptr++;
+ goto HANDLE_NUMERICAL_RECURSION;
+ }
+
+ /* \k<name> or \k'name' is a back reference by name (Perl syntax).
+ We also support \k{name} (.NET syntax). */
+
+ if (-c == ESC_k)
+ {
+ if ((ptr[1] != CHAR_LESS_THAN_SIGN &&
+ ptr[1] != CHAR_APOSTROPHE && ptr[1] != CHAR_LEFT_CURLY_BRACKET))
+ {
+ *errorcodeptr = ERR69;
+ break;
+ }
+ is_recurse = FALSE;
+ terminator = (*(++ptr) == CHAR_LESS_THAN_SIGN)?
+ CHAR_GREATER_THAN_SIGN : (*ptr == CHAR_APOSTROPHE)?
+ CHAR_APOSTROPHE : CHAR_RIGHT_CURLY_BRACKET;
+ goto NAMED_REF_OR_RECURSE;
+ }
+
+ /* Back references are handled specially; must disable firstbyte if
+ not set to cope with cases like (?=(\w+))\1: which would otherwise set
+ ':' later. */
+
+ if (-c >= ESC_REF)
+ {
+ open_capitem *oc;
+ recno = -c - ESC_REF;
+
+ HANDLE_REFERENCE: /* Come here from named backref handling */
+ if (firstbyte == REQ_UNSET) firstbyte = REQ_NONE;
+ previous = code;
+ *code++ = ((options & PCRE_CASELESS) != 0)? OP_REFI : OP_REF;
+ PUT2INC(code, 0, recno);
+ cd->backref_map |= (recno < 32)? (1 << recno) : 1;
+ if (recno > cd->top_backref) cd->top_backref = recno;
+
+ /* Check to see if this back reference is recursive, that it, it
+ is inside the group that it references. A flag is set so that the
+ group can be made atomic. */
+
+ for (oc = cd->open_caps; oc != NULL; oc = oc->next)
+ {
+ if (oc->number == recno)
+ {
+ oc->flag = TRUE;
+ break;
+ }
+ }
+ }
+
+ /* So are Unicode property matches, if supported. */
+
+#ifdef SUPPORT_UCP
+ else if (-c == ESC_P || -c == ESC_p)
+ {
+ BOOL negated;
+ int pdata;
+ int ptype = get_ucp(&ptr, &negated, &pdata, errorcodeptr);
+ if (ptype < 0) goto FAILED;
+ previous = code;
+ *code++ = ((-c == ESC_p) != negated)? OP_PROP : OP_NOTPROP;
+ *code++ = ptype;
+ *code++ = pdata;
+ }
+#else
+
+ /* If Unicode properties are not supported, \X, \P, and \p are not
+ allowed. */
+
+ else if (-c == ESC_X || -c == ESC_P || -c == ESC_p)
+ {
+ *errorcodeptr = ERR45;
+ goto FAILED;
+ }
+#endif
+
+ /* For the rest (including \X when Unicode properties are supported), we
+ can obtain the OP value by negating the escape value in the default
+ situation when PCRE_UCP is not set. When it *is* set, we substitute
+ Unicode property tests. */
+
+ else
+ {
+#ifdef SUPPORT_UCP
+ if (-c >= ESC_DU && -c <= ESC_wu)
+ {
+ nestptr = ptr + 1; /* Where to resume */
+ ptr = substitutes[-c - ESC_DU] - 1; /* Just before substitute */
+ }
+ else
+#endif
+ {
+ previous = (-c > ESC_b && -c < ESC_Z)? code : NULL;
+ *code++ = -c;
+ }
+ }
+ continue;
+ }
+
+ /* We have a data character whose value is in c. In UTF-8 mode it may have
+ a value > 127. We set its representation in the length/buffer, and then
+ handle it as a data character. */
+
+#ifdef SUPPORT_UTF8
+ if (utf8 && c > 127)
+ mclength = _pcre_ord2utf8(c, mcbuffer);
+ else
+#endif
+
+ {
+ mcbuffer[0] = c;
+ mclength = 1;
+ }
+ goto ONE_CHAR;
+
+
+ /* ===================================================================*/
+ /* Handle a literal character. It is guaranteed not to be whitespace or #
+ when the extended flag is set. If we are in UTF-8 mode, it may be a
+ multi-byte literal character. */
+
+ default:
+ NORMAL_CHAR:
+ mclength = 1;
+ mcbuffer[0] = c;
+
+#ifdef SUPPORT_UTF8
+ if (utf8 && c >= 0xc0)
+ {
+ while ((ptr[1] & 0xc0) == 0x80)
+ mcbuffer[mclength++] = *(++ptr);
+ }
+#endif
+
+ /* At this point we have the character's bytes in mcbuffer, and the length
+ in mclength. When not in UTF-8 mode, the length is always 1. */
+
+ ONE_CHAR:
+ previous = code;
+ *code++ = ((options & PCRE_CASELESS) != 0)? OP_CHARI : OP_CHAR;
+ for (c = 0; c < mclength; c++) *code++ = mcbuffer[c];
+
+ /* Remember if \r or \n were seen */
+
+ if (mcbuffer[0] == CHAR_CR || mcbuffer[0] == CHAR_NL)
+ cd->external_flags |= PCRE_HASCRORLF;
+
+ /* Set the first and required bytes appropriately. If no previous first
+ byte, set it from this character, but revert to none on a zero repeat.
+ Otherwise, leave the firstbyte value alone, and don't change it on a zero
+ repeat. */
+
+ if (firstbyte == REQ_UNSET)
+ {
+ zerofirstbyte = REQ_NONE;
+ zeroreqbyte = reqbyte;
+
+ /* If the character is more than one byte long, we can set firstbyte
+ only if it is not to be matched caselessly. */
+
+ if (mclength == 1 || req_caseopt == 0)
+ {
+ firstbyte = mcbuffer[0] | req_caseopt;
+ if (mclength != 1) reqbyte = code[-1] | cd->req_varyopt;
+ }
+ else firstbyte = reqbyte = REQ_NONE;
+ }
+
+ /* firstbyte was previously set; we can set reqbyte only the length is
+ 1 or the matching is caseful. */
+
+ else
+ {
+ zerofirstbyte = firstbyte;
+ zeroreqbyte = reqbyte;
+ if (mclength == 1 || req_caseopt == 0)
+ reqbyte = code[-1] | req_caseopt | cd->req_varyopt;
+ }
+
+ break; /* End of literal character handling */
+ }
+ } /* end of big loop */
+
+
+/* Control never reaches here by falling through, only by a goto for all the
+error states. Pass back the position in the pattern so that it can be displayed
+to the user for diagnosing the error. */
+
+FAILED:
+*ptrptr = ptr;
+return FALSE;
+}
+
+
+
+
+/*************************************************
+* Compile sequence of alternatives *
+*************************************************/
+
+/* On entry, ptr is pointing past the bracket character, but on return it
+points to the closing bracket, or vertical bar, or end of string. The code
+variable is pointing at the byte into which the BRA operator has been stored.
+This function is used during the pre-compile phase when we are trying to find
+out the amount of memory needed, as well as during the real compile phase. The
+value of lengthptr distinguishes the two phases.
+
+Arguments:
+ options option bits, including any changes for this subpattern
+ codeptr -> the address of the current code pointer
+ ptrptr -> the address of the current pattern pointer
+ errorcodeptr -> pointer to error code variable
+ lookbehind TRUE if this is a lookbehind assertion
+ reset_bracount TRUE to reset the count for each branch
+ skipbytes skip this many bytes at start (for brackets and OP_COND)
+ cond_depth depth of nesting for conditional subpatterns
+ firstbyteptr place to put the first required character, or a negative number
+ reqbyteptr place to put the last required character, or a negative number
+ bcptr pointer to the chain of currently open branches
+ cd points to the data block with tables pointers etc.
+ lengthptr NULL during the real compile phase
+ points to length accumulator during pre-compile phase
+
+Returns: TRUE on success
+*/
+
+static BOOL
+compile_regex(int options, uschar **codeptr, const uschar **ptrptr,
+ int *errorcodeptr, BOOL lookbehind, BOOL reset_bracount, int skipbytes,
+ int cond_depth, int *firstbyteptr, int *reqbyteptr, branch_chain *bcptr,
+ compile_data *cd, int *lengthptr)
+{
+const uschar *ptr = *ptrptr;
+uschar *code = *codeptr;
+uschar *last_branch = code;
+uschar *start_bracket = code;
+uschar *reverse_count = NULL;
+open_capitem capitem;
+int capnumber = 0;
+int firstbyte, reqbyte;
+int branchfirstbyte, branchreqbyte;
+int length;
+int orig_bracount;
+int max_bracount;
+branch_chain bc;
+
+bc.outer = bcptr;
+bc.current_branch = code;
+
+firstbyte = reqbyte = REQ_UNSET;
+
+/* Accumulate the length for use in the pre-compile phase. Start with the
+length of the BRA and KET and any extra bytes that are required at the
+beginning. We accumulate in a local variable to save frequent testing of
+lenthptr for NULL. We cannot do this by looking at the value of code at the
+start and end of each alternative, because compiled items are discarded during
+the pre-compile phase so that the work space is not exceeded. */
+
+length = 2 + 2*LINK_SIZE + skipbytes;
+
+/* WARNING: If the above line is changed for any reason, you must also change
+the code that abstracts option settings at the start of the pattern and makes
+them global. It tests the value of length for (2 + 2*LINK_SIZE) in the
+pre-compile phase to find out whether anything has yet been compiled or not. */
+
+/* If this is a capturing subpattern, add to the chain of open capturing items
+so that we can detect them if (*ACCEPT) is encountered. This is also used to
+detect groups that contain recursive back references to themselves. Note that
+only OP_CBRA need be tested here; changing this opcode to one of its variants,
+e.g. OP_SCBRAPOS, happens later, after the group has been compiled. */
+
+if (*code == OP_CBRA)
+ {
+ capnumber = GET2(code, 1 + LINK_SIZE);
+ capitem.number = capnumber;
+ capitem.next = cd->open_caps;
+ capitem.flag = FALSE;
+ cd->open_caps = &capitem;
+ }
+
+/* Offset is set zero to mark that this bracket is still open */
+
+PUT(code, 1, 0);
+code += 1 + LINK_SIZE + skipbytes;
+
+/* Loop for each alternative branch */
+
+orig_bracount = max_bracount = cd->bracount;
+for (;;)
+ {
+ /* For a (?| group, reset the capturing bracket count so that each branch
+ uses the same numbers. */
+
+ if (reset_bracount) cd->bracount = orig_bracount;
+
+ /* Set up dummy OP_REVERSE if lookbehind assertion */
+
+ if (lookbehind)
+ {
+ *code++ = OP_REVERSE;
+ reverse_count = code;
+ PUTINC(code, 0, 0);
+ length += 1 + LINK_SIZE;
+ }
+
+ /* Now compile the branch; in the pre-compile phase its length gets added
+ into the length. */
+
+ if (!compile_branch(&options, &code, &ptr, errorcodeptr, &branchfirstbyte,
+ &branchreqbyte, &bc, cond_depth, cd,
+ (lengthptr == NULL)? NULL : &length))
+ {
+ *ptrptr = ptr;
+ return FALSE;
+ }
+
+ /* Keep the highest bracket count in case (?| was used and some branch
+ has fewer than the rest. */
+
+ if (cd->bracount > max_bracount) max_bracount = cd->bracount;
+
+ /* In the real compile phase, there is some post-processing to be done. */
+
+ if (lengthptr == NULL)
+ {
+ /* If this is the first branch, the firstbyte and reqbyte values for the
+ branch become the values for the regex. */
+
+ if (*last_branch != OP_ALT)
+ {
+ firstbyte = branchfirstbyte;
+ reqbyte = branchreqbyte;
+ }
+
+ /* If this is not the first branch, the first char and reqbyte have to
+ match the values from all the previous branches, except that if the
+ previous value for reqbyte didn't have REQ_VARY set, it can still match,
+ and we set REQ_VARY for the regex. */
+
+ else
+ {
+ /* If we previously had a firstbyte, but it doesn't match the new branch,
+ we have to abandon the firstbyte for the regex, but if there was
+ previously no reqbyte, it takes on the value of the old firstbyte. */
+
+ if (firstbyte >= 0 && firstbyte != branchfirstbyte)
+ {
+ if (reqbyte < 0) reqbyte = firstbyte;
+ firstbyte = REQ_NONE;
+ }
+
+ /* If we (now or from before) have no firstbyte, a firstbyte from the
+ branch becomes a reqbyte if there isn't a branch reqbyte. */
+
+ if (firstbyte < 0 && branchfirstbyte >= 0 && branchreqbyte < 0)
+ branchreqbyte = branchfirstbyte;
+
+ /* Now ensure that the reqbytes match */
+
+ if ((reqbyte & ~REQ_VARY) != (branchreqbyte & ~REQ_VARY))
+ reqbyte = REQ_NONE;
+ else reqbyte |= branchreqbyte; /* To "or" REQ_VARY */
+ }
+
+ /* If lookbehind, check that this branch matches a fixed-length string, and
+ put the length into the OP_REVERSE item. Temporarily mark the end of the
+ branch with OP_END. If the branch contains OP_RECURSE, the result is -3
+ because there may be forward references that we can't check here. Set a
+ flag to cause another lookbehind check at the end. Why not do it all at the
+ end? Because common, erroneous checks are picked up here and the offset of
+ the problem can be shown. */
+
+ if (lookbehind)
+ {
+ int fixed_length;
+ *code = OP_END;
+ fixed_length = find_fixedlength(last_branch, (options & PCRE_UTF8) != 0,
+ FALSE, cd);
+ DPRINTF(("fixed length = %d\n", fixed_length));
+ if (fixed_length == -3)
+ {
+ cd->check_lookbehind = TRUE;
+ }
+ else if (fixed_length < 0)
+ {
+ *errorcodeptr = (fixed_length == -2)? ERR36 : ERR25;
+ *ptrptr = ptr;
+ return FALSE;
+ }
+ else { PUT(reverse_count, 0, fixed_length); }
+ }
+ }
+
+ /* Reached end of expression, either ')' or end of pattern. In the real
+ compile phase, go back through the alternative branches and reverse the chain
+ of offsets, with the field in the BRA item now becoming an offset to the
+ first alternative. If there are no alternatives, it points to the end of the
+ group. The length in the terminating ket is always the length of the whole
+ bracketed item. Return leaving the pointer at the terminating char. */
+
+ if (*ptr != CHAR_VERTICAL_LINE)
+ {
+ if (lengthptr == NULL)
+ {
+ int branch_length = (int)(code - last_branch);
+ do
+ {
+ int prev_length = GET(last_branch, 1);
+ PUT(last_branch, 1, branch_length);
+ branch_length = prev_length;
+ last_branch -= branch_length;
+ }
+ while (branch_length > 0);
+ }
+
+ /* Fill in the ket */
+
+ *code = OP_KET;
+ PUT(code, 1, (int)(code - start_bracket));
+ code += 1 + LINK_SIZE;
+
+ /* If it was a capturing subpattern, check to see if it contained any
+ recursive back references. If so, we must wrap it in atomic brackets.
+ In any event, remove the block from the chain. */
+
+ if (capnumber > 0)
+ {
+ if (cd->open_caps->flag)
+ {
+ memmove(start_bracket + 1 + LINK_SIZE, start_bracket,
+ code - start_bracket);
+ *start_bracket = OP_ONCE;
+ code += 1 + LINK_SIZE;
+ PUT(start_bracket, 1, (int)(code - start_bracket));
+ *code = OP_KET;
+ PUT(code, 1, (int)(code - start_bracket));
+ code += 1 + LINK_SIZE;
+ length += 2 + 2*LINK_SIZE;
+ }
+ cd->open_caps = cd->open_caps->next;
+ }
+
+ /* Retain the highest bracket number, in case resetting was used. */
+
+ cd->bracount = max_bracount;
+
+ /* Set values to pass back */
+
+ *codeptr = code;
+ *ptrptr = ptr;
+ *firstbyteptr = firstbyte;
+ *reqbyteptr = reqbyte;
+ if (lengthptr != NULL)
+ {
+ if (OFLOW_MAX - *lengthptr < length)
+ {
+ *errorcodeptr = ERR20;
+ return FALSE;
+ }
+ *lengthptr += length;
+ }
+ return TRUE;
+ }
+
+ /* Another branch follows. In the pre-compile phase, we can move the code
+ pointer back to where it was for the start of the first branch. (That is,
+ pretend that each branch is the only one.)
+
+ In the real compile phase, insert an ALT node. Its length field points back
+ to the previous branch while the bracket remains open. At the end the chain
+ is reversed. It's done like this so that the start of the bracket has a
+ zero offset until it is closed, making it possible to detect recursion. */
+
+ if (lengthptr != NULL)
+ {
+ code = *codeptr + 1 + LINK_SIZE + skipbytes;
+ length += 1 + LINK_SIZE;
+ }
+ else
+ {
+ *code = OP_ALT;
+ PUT(code, 1, (int)(code - last_branch));
+ bc.current_branch = last_branch = code;
+ code += 1 + LINK_SIZE;
+ }
+
+ ptr++;
+ }
+/* Control never reaches here */
+}
+
+
+
+
+/*************************************************
+* Check for anchored expression *
+*************************************************/
+
+/* Try to find out if this is an anchored regular expression. Consider each
+alternative branch. If they all start with OP_SOD or OP_CIRC, or with a bracket
+all of whose alternatives start with OP_SOD or OP_CIRC (recurse ad lib), then
+it's anchored. However, if this is a multiline pattern, then only OP_SOD will
+be found, because ^ generates OP_CIRCM in that mode.
+
+We can also consider a regex to be anchored if OP_SOM starts all its branches.
+This is the code for \G, which means "match at start of match position, taking
+into account the match offset".
+
+A branch is also implicitly anchored if it starts with .* and DOTALL is set,
+because that will try the rest of the pattern at all possible matching points,
+so there is no point trying again.... er ....
+
+.... except when the .* appears inside capturing parentheses, and there is a
+subsequent back reference to those parentheses. We haven't enough information
+to catch that case precisely.
+
+At first, the best we could do was to detect when .* was in capturing brackets
+and the highest back reference was greater than or equal to that level.
+However, by keeping a bitmap of the first 31 back references, we can catch some
+of the more common cases more precisely.
+
+Arguments:
+ code points to start of expression (the bracket)
+ bracket_map a bitmap of which brackets we are inside while testing; this
+ handles up to substring 31; after that we just have to take
+ the less precise approach
+ backref_map the back reference bitmap
+
+Returns: TRUE or FALSE
+*/
+
+static BOOL
+is_anchored(register const uschar *code, unsigned int bracket_map,
+ unsigned int backref_map)
+{
+do {
+ const uschar *scode = first_significant_code(code + _pcre_OP_lengths[*code],
+ FALSE);
+ register int op = *scode;
+
+ /* Non-capturing brackets */
+
+ if (op == OP_BRA || op == OP_BRAPOS ||
+ op == OP_SBRA || op == OP_SBRAPOS)
+ {
+ if (!is_anchored(scode, bracket_map, backref_map)) return FALSE;
+ }
+
+ /* Capturing brackets */
+
+ else if (op == OP_CBRA || op == OP_CBRAPOS ||
+ op == OP_SCBRA || op == OP_SCBRAPOS)
+ {
+ int n = GET2(scode, 1+LINK_SIZE);
+ int new_map = bracket_map | ((n < 32)? (1 << n) : 1);
+ if (!is_anchored(scode, new_map, backref_map)) return FALSE;
+ }
+
+ /* Other brackets */
+
+ else if (op == OP_ASSERT || op == OP_ONCE || op == OP_COND)
+ {
+ if (!is_anchored(scode, bracket_map, backref_map)) return FALSE;
+ }
+
+ /* .* is not anchored unless DOTALL is set (which generates OP_ALLANY) and
+ it isn't in brackets that are or may be referenced. */
+
+ else if ((op == OP_TYPESTAR || op == OP_TYPEMINSTAR ||
+ op == OP_TYPEPOSSTAR))
+ {
+ if (scode[1] != OP_ALLANY || (bracket_map & backref_map) != 0)
+ return FALSE;
+ }
+
+ /* Check for explicit anchoring */
+
+ else if (op != OP_SOD && op != OP_SOM && op != OP_CIRC) return FALSE;
+ code += GET(code, 1);
+ }
+while (*code == OP_ALT); /* Loop for each alternative */
+return TRUE;
+}
+
+
+
+/*************************************************
+* Check for starting with ^ or .* *
+*************************************************/
+
+/* This is called to find out if every branch starts with ^ or .* so that
+"first char" processing can be done to speed things up in multiline
+matching and for non-DOTALL patterns that start with .* (which must start at
+the beginning or after \n). As in the case of is_anchored() (see above), we
+have to take account of back references to capturing brackets that contain .*
+because in that case we can't make the assumption.
+
+Arguments:
+ code points to start of expression (the bracket)
+ bracket_map a bitmap of which brackets we are inside while testing; this
+ handles up to substring 31; after that we just have to take
+ the less precise approach
+ backref_map the back reference bitmap
+
+Returns: TRUE or FALSE
+*/
+
+static BOOL
+is_startline(const uschar *code, unsigned int bracket_map,
+ unsigned int backref_map)
+{
+do {
+ const uschar *scode = first_significant_code(code + _pcre_OP_lengths[*code],
+ FALSE);
+ register int op = *scode;
+
+ /* If we are at the start of a conditional assertion group, *both* the
+ conditional assertion *and* what follows the condition must satisfy the test
+ for start of line. Other kinds of condition fail. Note that there may be an
+ auto-callout at the start of a condition. */
+
+ if (op == OP_COND)
+ {
+ scode += 1 + LINK_SIZE;
+ if (*scode == OP_CALLOUT) scode += _pcre_OP_lengths[OP_CALLOUT];
+ switch (*scode)
+ {
+ case OP_CREF:
+ case OP_NCREF:
+ case OP_RREF:
+ case OP_NRREF:
+ case OP_DEF:
+ return FALSE;
+
+ default: /* Assertion */
+ if (!is_startline(scode, bracket_map, backref_map)) return FALSE;
+ do scode += GET(scode, 1); while (*scode == OP_ALT);
+ scode += 1 + LINK_SIZE;
+ break;
+ }
+ scode = first_significant_code(scode, FALSE);
+ op = *scode;
+ }
+
+ /* Non-capturing brackets */
+
+ if (op == OP_BRA || op == OP_BRAPOS ||
+ op == OP_SBRA || op == OP_SBRAPOS)
+ {
+ if (!is_startline(scode, bracket_map, backref_map)) return FALSE;
+ }
+
+ /* Capturing brackets */
+
+ else if (op == OP_CBRA || op == OP_CBRAPOS ||
+ op == OP_SCBRA || op == OP_SCBRAPOS)
+ {
+ int n = GET2(scode, 1+LINK_SIZE);
+ int new_map = bracket_map | ((n < 32)? (1 << n) : 1);
+ if (!is_startline(scode, new_map, backref_map)) return FALSE;
+ }
+
+ /* Other brackets */
+
+ else if (op == OP_ASSERT || op == OP_ONCE)
+ {
+ if (!is_startline(scode, bracket_map, backref_map)) return FALSE;
+ }
+
+ /* .* means "start at start or after \n" if it isn't in brackets that
+ may be referenced. */
+
+ else if (op == OP_TYPESTAR || op == OP_TYPEMINSTAR || op == OP_TYPEPOSSTAR)
+ {
+ if (scode[1] != OP_ANY || (bracket_map & backref_map) != 0) return FALSE;
+ }
+
+ /* Check for explicit circumflex */
+
+ else if (op != OP_CIRC && op != OP_CIRCM) return FALSE;
+
+ /* Move on to the next alternative */
+
+ code += GET(code, 1);
+ }
+while (*code == OP_ALT); /* Loop for each alternative */
+return TRUE;
+}
+
+
+
+/*************************************************
+* Check for asserted fixed first char *
+*************************************************/
+
+/* During compilation, the "first char" settings from forward assertions are
+discarded, because they can cause conflicts with actual literals that follow.
+However, if we end up without a first char setting for an unanchored pattern,
+it is worth scanning the regex to see if there is an initial asserted first
+char. If all branches start with the same asserted char, or with a bracket all
+of whose alternatives start with the same asserted char (recurse ad lib), then
+we return that char, otherwise -1.
+
+Arguments:
+ code points to start of expression (the bracket)
+ inassert TRUE if in an assertion
+
+Returns: -1 or the fixed first char
+*/
+
+static int
+find_firstassertedchar(const uschar *code, BOOL inassert)
+{
+register int c = -1;
+do {
+ int d;
+ int xl = (*code == OP_CBRA || *code == OP_SCBRA ||
+ *code == OP_CBRAPOS || *code == OP_SCBRAPOS)? 2:0;
+ const uschar *scode = first_significant_code(code + 1+LINK_SIZE + xl, TRUE);
+ register int op = *scode;
+
+ switch(op)
+ {
+ default:
+ return -1;
+
+ case OP_BRA:
+ case OP_BRAPOS:
+ case OP_CBRA:
+ case OP_SCBRA:
+ case OP_CBRAPOS:
+ case OP_SCBRAPOS:
+ case OP_ASSERT:
+ case OP_ONCE:
+ case OP_COND:
+ if ((d = find_firstassertedchar(scode, op == OP_ASSERT)) < 0)
+ return -1;
+ if (c < 0) c = d; else if (c != d) return -1;
+ break;
+
+ case OP_EXACT:
+ scode += 2;
+ /* Fall through */
+
+ case OP_CHAR:
+ case OP_PLUS:
+ case OP_MINPLUS:
+ case OP_POSPLUS:
+ if (!inassert) return -1;
+ if (c < 0) c = scode[1];
+ else if (c != scode[1]) return -1;
+ break;
+
+ case OP_EXACTI:
+ scode += 2;
+ /* Fall through */
+
+ case OP_CHARI:
+ case OP_PLUSI:
+ case OP_MINPLUSI:
+ case OP_POSPLUSI:
+ if (!inassert) return -1;
+ if (c < 0) c = scode[1] | REQ_CASELESS;
+ else if (c != scode[1]) return -1;
+ break;
+ }
+
+ code += GET(code, 1);
+ }
+while (*code == OP_ALT);
+return c;
+}
+
+
+
+/*************************************************
+* Compile a Regular Expression *
+*************************************************/
+
+/* This function takes a string and returns a pointer to a block of store
+holding a compiled version of the expression. The original API for this
+function had no error code return variable; it is retained for backwards
+compatibility. The new function is given a new name.
+
+Arguments:
+ pattern the regular expression
+ options various option bits
+ errorcodeptr pointer to error code variable (pcre_compile2() only)
+ can be NULL if you don't want a code value
+ errorptr pointer to pointer to error text
+ erroroffset ptr offset in pattern where error was detected
+ tables pointer to character tables or NULL
+
+Returns: pointer to compiled data block, or NULL on error,
+ with errorptr and erroroffset set
+*/
+
+PCRE_EXP_DEFN pcre * PCRE_CALL_CONVENTION
+pcre_compile(const char *pattern, int options, const char **errorptr,
+ int *erroroffset, const unsigned char *tables)
+{
+return pcre_compile2(pattern, options, NULL, errorptr, erroroffset, tables);
+}
+
+
+PCRE_EXP_DEFN pcre * PCRE_CALL_CONVENTION
+pcre_compile2(const char *pattern, int options, int *errorcodeptr,
+ const char **errorptr, int *erroroffset, const unsigned char *tables)
+{
+real_pcre *re;
+int length = 1; /* For final END opcode */
+int firstbyte, reqbyte, newline;
+int errorcode = 0;
+int skipatstart = 0;
+BOOL utf8;
+size_t size;
+uschar *code;
+const uschar *codestart;
+const uschar *ptr;
+compile_data compile_block;
+compile_data *cd = &compile_block;
+
+/* This space is used for "compiling" into during the first phase, when we are
+computing the amount of memory that is needed. Compiled items are thrown away
+as soon as possible, so that a fairly large buffer should be sufficient for
+this purpose. The same space is used in the second phase for remembering where
+to fill in forward references to subpatterns. */
+
+uschar cworkspace[COMPILE_WORK_SIZE];
+
+/* Set this early so that early errors get offset 0. */
+
+ptr = (const uschar *)pattern;
+
+/* We can't pass back an error message if errorptr is NULL; I guess the best we
+can do is just return NULL, but we can set a code value if there is a code
+pointer. */
+
+if (errorptr == NULL)
+ {
+ if (errorcodeptr != NULL) *errorcodeptr = 99;
+ return NULL;
+ }
+
+*errorptr = NULL;
+if (errorcodeptr != NULL) *errorcodeptr = ERR0;
+
+/* However, we can give a message for this error */
+
+if (erroroffset == NULL)
+ {
+ errorcode = ERR16;
+ goto PCRE_EARLY_ERROR_RETURN2;
+ }
+
+*erroroffset = 0;
+
+/* Set up pointers to the individual character tables */
+
+if (tables == NULL) tables = _pcre_default_tables;
+cd->lcc = tables + lcc_offset;
+cd->fcc = tables + fcc_offset;
+cd->cbits = tables + cbits_offset;
+cd->ctypes = tables + ctypes_offset;
+
+/* Check that all undefined public option bits are zero */
+
+if ((options & ~PUBLIC_COMPILE_OPTIONS) != 0)
+ {
+ errorcode = ERR17;
+ goto PCRE_EARLY_ERROR_RETURN;
+ }
+
+/* Check for global one-time settings at the start of the pattern, and remember
+the offset for later. */
+
+while (ptr[skipatstart] == CHAR_LEFT_PARENTHESIS &&
+ ptr[skipatstart+1] == CHAR_ASTERISK)
+ {
+ int newnl = 0;
+ int newbsr = 0;
+
+ if (strncmp((char *)(ptr+skipatstart+2), STRING_UTF8_RIGHTPAR, 5) == 0)
+ { skipatstart += 7; options |= PCRE_UTF8; continue; }
+ else if (strncmp((char *)(ptr+skipatstart+2), STRING_UCP_RIGHTPAR, 4) == 0)
+ { skipatstart += 6; options |= PCRE_UCP; continue; }
+ else if (strncmp((char *)(ptr+skipatstart+2), STRING_NO_START_OPT_RIGHTPAR, 13) == 0)
+ { skipatstart += 15; options |= PCRE_NO_START_OPTIMIZE; continue; }
+
+ if (strncmp((char *)(ptr+skipatstart+2), STRING_CR_RIGHTPAR, 3) == 0)
+ { skipatstart += 5; newnl = PCRE_NEWLINE_CR; }
+ else if (strncmp((char *)(ptr+skipatstart+2), STRING_LF_RIGHTPAR, 3) == 0)
+ { skipatstart += 5; newnl = PCRE_NEWLINE_LF; }
+ else if (strncmp((char *)(ptr+skipatstart+2), STRING_CRLF_RIGHTPAR, 5) == 0)
+ { skipatstart += 7; newnl = PCRE_NEWLINE_CR + PCRE_NEWLINE_LF; }
+ else if (strncmp((char *)(ptr+skipatstart+2), STRING_ANY_RIGHTPAR, 4) == 0)
+ { skipatstart += 6; newnl = PCRE_NEWLINE_ANY; }
+ else if (strncmp((char *)(ptr+skipatstart+2), STRING_ANYCRLF_RIGHTPAR, 8) == 0)
+ { skipatstart += 10; newnl = PCRE_NEWLINE_ANYCRLF; }
+
+ else if (strncmp((char *)(ptr+skipatstart+2), STRING_BSR_ANYCRLF_RIGHTPAR, 12) == 0)
+ { skipatstart += 14; newbsr = PCRE_BSR_ANYCRLF; }
+ else if (strncmp((char *)(ptr+skipatstart+2), STRING_BSR_UNICODE_RIGHTPAR, 12) == 0)
+ { skipatstart += 14; newbsr = PCRE_BSR_UNICODE; }
+
+ if (newnl != 0)
+ options = (options & ~PCRE_NEWLINE_BITS) | newnl;
+ else if (newbsr != 0)
+ options = (options & ~(PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE)) | newbsr;
+ else break;
+ }
+
+utf8 = (options & PCRE_UTF8) != 0;
+
+/* Can't support UTF8 unless PCRE has been compiled to include the code. The
+return of an error code from _pcre_valid_utf8() is a new feature, introduced in
+release 8.13. It is passed back from pcre_[dfa_]exec(), but at the moment is
+not used here. */
+
+#ifdef SUPPORT_UTF8
+if (utf8 && (options & PCRE_NO_UTF8_CHECK) == 0 &&
+ (errorcode = _pcre_valid_utf8((USPTR)pattern, -1, erroroffset)) != 0)
+ {
+ errorcode = ERR44;
+ goto PCRE_EARLY_ERROR_RETURN2;
+ }
+#else
+if (utf8)
+ {
+ errorcode = ERR32;
+ goto PCRE_EARLY_ERROR_RETURN;
+ }
+#endif
+
+/* Can't support UCP unless PCRE has been compiled to include the code. */
+
+#ifndef SUPPORT_UCP
+if ((options & PCRE_UCP) != 0)
+ {
+ errorcode = ERR67;
+ goto PCRE_EARLY_ERROR_RETURN;
+ }
+#endif
+
+/* Check validity of \R options. */
+
+if ((options & (PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE)) ==
+ (PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE))
+ {
+ errorcode = ERR56;
+ goto PCRE_EARLY_ERROR_RETURN;
+ }
+
+/* Handle different types of newline. The three bits give seven cases. The
+current code allows for fixed one- or two-byte sequences, plus "any" and
+"anycrlf". */
+
+switch (options & PCRE_NEWLINE_BITS)
+ {
+ case 0: newline = NEWLINE; break; /* Build-time default */
+ case PCRE_NEWLINE_CR: newline = CHAR_CR; break;
+ case PCRE_NEWLINE_LF: newline = CHAR_NL; break;
+ case PCRE_NEWLINE_CR+
+ PCRE_NEWLINE_LF: newline = (CHAR_CR << 8) | CHAR_NL; break;
+ case PCRE_NEWLINE_ANY: newline = -1; break;
+ case PCRE_NEWLINE_ANYCRLF: newline = -2; break;
+ default: errorcode = ERR56; goto PCRE_EARLY_ERROR_RETURN;
+ }
+
+if (newline == -2)
+ {
+ cd->nltype = NLTYPE_ANYCRLF;
+ }
+else if (newline < 0)
+ {
+ cd->nltype = NLTYPE_ANY;
+ }
+else
+ {
+ cd->nltype = NLTYPE_FIXED;
+ if (newline > 255)
+ {
+ cd->nllen = 2;
+ cd->nl[0] = (newline >> 8) & 255;
+ cd->nl[1] = newline & 255;
+ }
+ else
+ {
+ cd->nllen = 1;
+ cd->nl[0] = newline;
+ }
+ }
+
+/* Maximum back reference and backref bitmap. The bitmap records up to 31 back
+references to help in deciding whether (.*) can be treated as anchored or not.
+*/
+
+cd->top_backref = 0;
+cd->backref_map = 0;
+
+/* Reflect pattern for debugging output */
+
+DPRINTF(("------------------------------------------------------------------\n"));
+DPRINTF(("%s\n", pattern));
+
+/* Pretend to compile the pattern while actually just accumulating the length
+of memory required. This behaviour is triggered by passing a non-NULL final
+argument to compile_regex(). We pass a block of workspace (cworkspace) for it
+to compile parts of the pattern into; the compiled code is discarded when it is
+no longer needed, so hopefully this workspace will never overflow, though there
+is a test for its doing so. */
+
+cd->bracount = cd->final_bracount = 0;
+cd->names_found = 0;
+cd->name_entry_size = 0;
+cd->name_table = NULL;
+cd->start_workspace = cworkspace;
+cd->start_code = cworkspace;
+cd->hwm = cworkspace;
+cd->start_pattern = (const uschar *)pattern;
+cd->end_pattern = (const uschar *)(pattern + strlen(pattern));
+cd->req_varyopt = 0;
+cd->external_options = options;
+cd->external_flags = 0;
+cd->open_caps = NULL;
+
+/* Now do the pre-compile. On error, errorcode will be set non-zero, so we
+don't need to look at the result of the function here. The initial options have
+been put into the cd block so that they can be changed if an option setting is
+found within the regex right at the beginning. Bringing initial option settings
+outside can help speed up starting point checks. */
+
+ptr += skipatstart;
+code = cworkspace;
+*code = OP_BRA;
+(void)compile_regex(cd->external_options, &code, &ptr, &errorcode, FALSE,
+ FALSE, 0, 0, &firstbyte, &reqbyte, NULL, cd, &length);
+if (errorcode != 0) goto PCRE_EARLY_ERROR_RETURN;
+
+DPRINTF(("end pre-compile: length=%d workspace=%d\n", length,
+ cd->hwm - cworkspace));
+
+if (length > MAX_PATTERN_SIZE)
+ {
+ errorcode = ERR20;
+ goto PCRE_EARLY_ERROR_RETURN;
+ }
+
+/* Compute the size of data block needed and get it, either from malloc or
+externally provided function. Integer overflow should no longer be possible
+because nowadays we limit the maximum value of cd->names_found and
+cd->name_entry_size. */
+
+size = length + sizeof(real_pcre) + cd->names_found * (cd->name_entry_size + 3);
+re = (real_pcre *)(pcre_malloc)(size);
+
+if (re == NULL)
+ {
+ errorcode = ERR21;
+ goto PCRE_EARLY_ERROR_RETURN;
+ }
+
+/* Put in the magic number, and save the sizes, initial options, internal
+flags, and character table pointer. NULL is used for the default character
+tables. The nullpad field is at the end; it's there to help in the case when a
+regex compiled on a system with 4-byte pointers is run on another with 8-byte
+pointers. */
+
+re->magic_number = MAGIC_NUMBER;
+re->size = (int)size;
+re->options = cd->external_options;
+re->flags = cd->external_flags;
+re->dummy1 = 0;
+re->first_byte = 0;
+re->req_byte = 0;
+re->name_table_offset = sizeof(real_pcre);
+re->name_entry_size = cd->name_entry_size;
+re->name_count = cd->names_found;
+re->ref_count = 0;
+re->tables = (tables == _pcre_default_tables)? NULL : tables;
+re->nullpad = NULL;
+
+/* The starting points of the name/number translation table and of the code are
+passed around in the compile data block. The start/end pattern and initial
+options are already set from the pre-compile phase, as is the name_entry_size
+field. Reset the bracket count and the names_found field. Also reset the hwm
+field; this time it's used for remembering forward references to subpatterns.
+*/
+
+cd->final_bracount = cd->bracount; /* Save for checking forward references */
+cd->assert_depth = 0;
+cd->bracount = 0;
+cd->names_found = 0;
+cd->name_table = (uschar *)re + re->name_table_offset;
+codestart = cd->name_table + re->name_entry_size * re->name_count;
+cd->start_code = codestart;
+cd->hwm = cworkspace;
+cd->req_varyopt = 0;
+cd->had_accept = FALSE;
+cd->check_lookbehind = FALSE;
+cd->open_caps = NULL;
+
+/* Set up a starting, non-extracting bracket, then compile the expression. On
+error, errorcode will be set non-zero, so we don't need to look at the result
+of the function here. */
+
+ptr = (const uschar *)pattern + skipatstart;
+code = (uschar *)codestart;
+*code = OP_BRA;
+(void)compile_regex(re->options, &code, &ptr, &errorcode, FALSE, FALSE, 0, 0,
+ &firstbyte, &reqbyte, NULL, cd, NULL);
+re->top_bracket = cd->bracount;
+re->top_backref = cd->top_backref;
+re->flags = cd->external_flags;
+
+if (cd->had_accept) reqbyte = -1; /* Must disable after (*ACCEPT) */
+
+/* If not reached end of pattern on success, there's an excess bracket. */
+
+if (errorcode == 0 && *ptr != 0) errorcode = ERR22;
+
+/* Fill in the terminating state and check for disastrous overflow, but
+if debugging, leave the test till after things are printed out. */
+
+*code++ = OP_END;
+
+#ifndef PCRE_DEBUG
+if (code - codestart > length) errorcode = ERR23;
+#endif
+
+/* Fill in any forward references that are required. */
+
+while (errorcode == 0 && cd->hwm > cworkspace)
+ {
+ int offset, recno;
+ const uschar *groupptr;
+ cd->hwm -= LINK_SIZE;
+ offset = GET(cd->hwm, 0);
+ recno = GET(codestart, offset);
+ groupptr = _pcre_find_bracket(codestart, utf8, recno);
+ if (groupptr == NULL) errorcode = ERR53;
+ else PUT(((uschar *)codestart), offset, (int)(groupptr - codestart));
+ }
+
+/* Give an error if there's back reference to a non-existent capturing
+subpattern. */
+
+if (errorcode == 0 && re->top_backref > re->top_bracket) errorcode = ERR15;
+
+/* If there were any lookbehind assertions that contained OP_RECURSE
+(recursions or subroutine calls), a flag is set for them to be checked here,
+because they may contain forward references. Actual recursions can't be fixed
+length, but subroutine calls can. It is done like this so that those without
+OP_RECURSE that are not fixed length get a diagnosic with a useful offset. The
+exceptional ones forgo this. We scan the pattern to check that they are fixed
+length, and set their lengths. */
+
+if (cd->check_lookbehind)
+ {
+ uschar *cc = (uschar *)codestart;
+
+ /* Loop, searching for OP_REVERSE items, and process those that do not have
+ their length set. (Actually, it will also re-process any that have a length
+ of zero, but that is a pathological case, and it does no harm.) When we find
+ one, we temporarily terminate the branch it is in while we scan it. */
+
+ for (cc = (uschar *)_pcre_find_bracket(codestart, utf8, -1);
+ cc != NULL;
+ cc = (uschar *)_pcre_find_bracket(cc, utf8, -1))
+ {
+ if (GET(cc, 1) == 0)
+ {
+ int fixed_length;
+ uschar *be = cc - 1 - LINK_SIZE + GET(cc, -LINK_SIZE);
+ int end_op = *be;
+ *be = OP_END;
+ fixed_length = find_fixedlength(cc, (re->options & PCRE_UTF8) != 0, TRUE,
+ cd);
+ *be = end_op;
+ DPRINTF(("fixed length = %d\n", fixed_length));
+ if (fixed_length < 0)
+ {
+ errorcode = (fixed_length == -2)? ERR36 : ERR25;
+ break;
+ }
+ PUT(cc, 1, fixed_length);
+ }
+ cc += 1 + LINK_SIZE;
+ }
+ }
+
+/* Failed to compile, or error while post-processing */
+
+if (errorcode != 0)
+ {
+ (pcre_free)(re);
+ PCRE_EARLY_ERROR_RETURN:
+ *erroroffset = (int)(ptr - (const uschar *)pattern);
+ PCRE_EARLY_ERROR_RETURN2:
+ *errorptr = find_error_text(errorcode);
+ if (errorcodeptr != NULL) *errorcodeptr = errorcode;
+ return NULL;
+ }
+
+/* If the anchored option was not passed, set the flag if we can determine that
+the pattern is anchored by virtue of ^ characters or \A or anything else (such
+as starting with .* when DOTALL is set).
+
+Otherwise, if we know what the first byte has to be, save it, because that
+speeds up unanchored matches no end. If not, see if we can set the
+PCRE_STARTLINE flag. This is helpful for multiline matches when all branches
+start with ^. and also when all branches start with .* for non-DOTALL matches.
+*/
+
+if ((re->options & PCRE_ANCHORED) == 0)
+ {
+ if (is_anchored(codestart, 0, cd->backref_map))
+ re->options |= PCRE_ANCHORED;
+ else
+ {
+ if (firstbyte < 0)
+ firstbyte = find_firstassertedchar(codestart, FALSE);
+ if (firstbyte >= 0) /* Remove caseless flag for non-caseable chars */
+ {
+ int ch = firstbyte & 255;
+ re->first_byte = ((firstbyte & REQ_CASELESS) != 0 &&
+ cd->fcc[ch] == ch)? ch : firstbyte;
+ re->flags |= PCRE_FIRSTSET;
+ }
+ else if (is_startline(codestart, 0, cd->backref_map))
+ re->flags |= PCRE_STARTLINE;
+ }
+ }
+
+/* For an anchored pattern, we use the "required byte" only if it follows a
+variable length item in the regex. Remove the caseless flag for non-caseable
+bytes. */
+
+if (reqbyte >= 0 &&
+ ((re->options & PCRE_ANCHORED) == 0 || (reqbyte & REQ_VARY) != 0))
+ {
+ int ch = reqbyte & 255;
+ re->req_byte = ((reqbyte & REQ_CASELESS) != 0 &&
+ cd->fcc[ch] == ch)? (reqbyte & ~REQ_CASELESS) : reqbyte;
+ re->flags |= PCRE_REQCHSET;
+ }
+
+/* Print out the compiled data if debugging is enabled. This is never the
+case when building a production library. */
+
+#ifdef PCRE_DEBUG
+printf("Length = %d top_bracket = %d top_backref = %d\n",
+ length, re->top_bracket, re->top_backref);
+
+printf("Options=%08x\n", re->options);
+
+if ((re->flags & PCRE_FIRSTSET) != 0)
+ {
+ int ch = re->first_byte & 255;
+ const char *caseless = ((re->first_byte & REQ_CASELESS) == 0)?
+ "" : " (caseless)";
+ if (isprint(ch)) printf("First char = %c%s\n", ch, caseless);
+ else printf("First char = \\x%02x%s\n", ch, caseless);
+ }
+
+if ((re->flags & PCRE_REQCHSET) != 0)
+ {
+ int ch = re->req_byte & 255;
+ const char *caseless = ((re->req_byte & REQ_CASELESS) == 0)?
+ "" : " (caseless)";
+ if (isprint(ch)) printf("Req char = %c%s\n", ch, caseless);
+ else printf("Req char = \\x%02x%s\n", ch, caseless);
+ }
+
+pcre_printint(re, stdout, TRUE);
+
+/* This check is done here in the debugging case so that the code that
+was compiled can be seen. */
+
+if (code - codestart > length)
+ {
+ (pcre_free)(re);
+ *errorptr = find_error_text(ERR23);
+ *erroroffset = ptr - (uschar *)pattern;
+ if (errorcodeptr != NULL) *errorcodeptr = ERR23;
+ return NULL;
+ }
+#endif /* PCRE_DEBUG */
+
+return (pcre *)re;
+}
+
+/* End of pcre_compile.c */
diff --git a/usr.sbin/nginx/src/pcre/pcre_exec.c b/usr.sbin/nginx/src/pcre/pcre_exec.c
new file mode 100644
index 00000000000..b1ab3875bba
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/pcre_exec.c
@@ -0,0 +1,6468 @@
+/*************************************************
+* Perl-Compatible Regular Expressions *
+*************************************************/
+
+/* PCRE is a library of functions to support regular expressions whose syntax
+and semantics are as close as possible to those of the Perl 5 language.
+
+ Written by Philip Hazel
+ Copyright (c) 1997-2011 University of Cambridge
+
+-----------------------------------------------------------------------------
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the University of Cambridge nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+-----------------------------------------------------------------------------
+*/
+
+
+/* This module contains pcre_exec(), the externally visible function that does
+pattern matching using an NFA algorithm, trying to mimic Perl as closely as
+possible. There are also some static supporting functions. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define NLBLOCK md /* Block containing newline information */
+#define PSSTART start_subject /* Field containing processed string start */
+#define PSEND end_subject /* Field containing processed string end */
+
+#include "pcre_internal.h"
+
+/* Undefine some potentially clashing cpp symbols */
+
+#undef min
+#undef max
+
+/* Values for setting in md->match_function_type to indicate two special types
+of call to match(). We do it this way to save on using another stack variable,
+as stack usage is to be discouraged. */
+
+#define MATCH_CONDASSERT 1 /* Called to check a condition assertion */
+#define MATCH_CBEGROUP 2 /* Could-be-empty unlimited repeat group */
+
+/* Non-error returns from the match() function. Error returns are externally
+defined PCRE_ERROR_xxx codes, which are all negative. */
+
+#define MATCH_MATCH 1
+#define MATCH_NOMATCH 0
+
+/* Special internal returns from the match() function. Make them sufficiently
+negative to avoid the external error codes. */
+
+#define MATCH_ACCEPT (-999)
+#define MATCH_COMMIT (-998)
+#define MATCH_KETRPOS (-997)
+#define MATCH_ONCE (-996)
+#define MATCH_PRUNE (-995)
+#define MATCH_SKIP (-994)
+#define MATCH_SKIP_ARG (-993)
+#define MATCH_THEN (-992)
+
+/* This is a convenience macro for code that occurs many times. */
+
+#define MRRETURN(ra) \
+ { \
+ md->mark = markptr; \
+ RRETURN(ra); \
+ }
+
+/* Maximum number of ints of offset to save on the stack for recursive calls.
+If the offset vector is bigger, malloc is used. This should be a multiple of 3,
+because the offset vector is always a multiple of 3 long. */
+
+#define REC_STACK_SAVE_MAX 30
+
+/* Min and max values for the common repeats; for the maxima, 0 => infinity */
+
+static const char rep_min[] = { 0, 0, 1, 1, 0, 0 };
+static const char rep_max[] = { 0, 0, 0, 0, 1, 1 };
+
+
+
+#ifdef PCRE_DEBUG
+/*************************************************
+* Debugging function to print chars *
+*************************************************/
+
+/* Print a sequence of chars in printable format, stopping at the end of the
+subject if the requested.
+
+Arguments:
+ p points to characters
+ length number to print
+ is_subject TRUE if printing from within md->start_subject
+ md pointer to matching data block, if is_subject is TRUE
+
+Returns: nothing
+*/
+
+static void
+pchars(const uschar *p, int length, BOOL is_subject, match_data *md)
+{
+unsigned int c;
+if (is_subject && length > md->end_subject - p) length = md->end_subject - p;
+while (length-- > 0)
+ if (isprint(c = *(p++))) printf("%c", c); else printf("\\x%02x", c);
+}
+#endif
+
+
+
+/*************************************************
+* Match a back-reference *
+*************************************************/
+
+/* Normally, if a back reference hasn't been set, the length that is passed is
+negative, so the match always fails. However, in JavaScript compatibility mode,
+the length passed is zero. Note that in caseless UTF-8 mode, the number of
+subject bytes matched may be different to the number of reference bytes.
+
+Arguments:
+ offset index into the offset vector
+ eptr pointer into the subject
+ length length of reference to be matched (number of bytes)
+ md points to match data block
+ caseless TRUE if caseless
+
+Returns: < 0 if not matched, otherwise the number of subject bytes matched
+*/
+
+static int
+match_ref(int offset, register USPTR eptr, int length, match_data *md,
+ BOOL caseless)
+{
+USPTR eptr_start = eptr;
+register USPTR p = md->start_subject + md->offset_vector[offset];
+
+#ifdef PCRE_DEBUG
+if (eptr >= md->end_subject)
+ printf("matching subject <null>");
+else
+ {
+ printf("matching subject ");
+ pchars(eptr, length, TRUE, md);
+ }
+printf(" against backref ");
+pchars(p, length, FALSE, md);
+printf("\n");
+#endif
+
+/* Always fail if reference not set (and not JavaScript compatible). */
+
+if (length < 0) return -1;
+
+/* Separate the caseless case for speed. In UTF-8 mode we can only do this
+properly if Unicode properties are supported. Otherwise, we can check only
+ASCII characters. */
+
+if (caseless)
+ {
+#ifdef SUPPORT_UTF8
+#ifdef SUPPORT_UCP
+ if (md->utf8)
+ {
+ /* Match characters up to the end of the reference. NOTE: the number of
+ bytes matched may differ, because there are some characters whose upper and
+ lower case versions code as different numbers of bytes. For example, U+023A
+ (2 bytes in UTF-8) is the upper case version of U+2C65 (3 bytes in UTF-8);
+ a sequence of 3 of the former uses 6 bytes, as does a sequence of two of
+ the latter. It is important, therefore, to check the length along the
+ reference, not along the subject (earlier code did this wrong). */
+
+ USPTR endptr = p + length;
+ while (p < endptr)
+ {
+ int c, d;
+ if (eptr >= md->end_subject) return -1;
+ GETCHARINC(c, eptr);
+ GETCHARINC(d, p);
+ if (c != d && c != UCD_OTHERCASE(d)) return -1;
+ }
+ }
+ else
+#endif
+#endif
+
+ /* The same code works when not in UTF-8 mode and in UTF-8 mode when there
+ is no UCP support. */
+ {
+ if (eptr + length > md->end_subject) return -1;
+ while (length-- > 0)
+ { if (md->lcc[*p++] != md->lcc[*eptr++]) return -1; }
+ }
+ }
+
+/* In the caseful case, we can just compare the bytes, whether or not we
+are in UTF-8 mode. */
+
+else
+ {
+ if (eptr + length > md->end_subject) return -1;
+ while (length-- > 0) if (*p++ != *eptr++) return -1;
+ }
+
+return eptr - eptr_start;
+}
+
+
+
+/***************************************************************************
+****************************************************************************
+ RECURSION IN THE match() FUNCTION
+
+The match() function is highly recursive, though not every recursive call
+increases the recursive depth. Nevertheless, some regular expressions can cause
+it to recurse to a great depth. I was writing for Unix, so I just let it call
+itself recursively. This uses the stack for saving everything that has to be
+saved for a recursive call. On Unix, the stack can be large, and this works
+fine.
+
+It turns out that on some non-Unix-like systems there are problems with
+programs that use a lot of stack. (This despite the fact that every last chip
+has oodles of memory these days, and techniques for extending the stack have
+been known for decades.) So....
+
+There is a fudge, triggered by defining NO_RECURSE, which avoids recursive
+calls by keeping local variables that need to be preserved in blocks of memory
+obtained from malloc() instead instead of on the stack. Macros are used to
+achieve this so that the actual code doesn't look very different to what it
+always used to.
+
+The original heap-recursive code used longjmp(). However, it seems that this
+can be very slow on some operating systems. Following a suggestion from Stan
+Switzer, the use of longjmp() has been abolished, at the cost of having to
+provide a unique number for each call to RMATCH. There is no way of generating
+a sequence of numbers at compile time in C. I have given them names, to make
+them stand out more clearly.
+
+Crude tests on x86 Linux show a small speedup of around 5-8%. However, on
+FreeBSD, avoiding longjmp() more than halves the time taken to run the standard
+tests. Furthermore, not using longjmp() means that local dynamic variables
+don't have indeterminate values; this has meant that the frame size can be
+reduced because the result can be "passed back" by straight setting of the
+variable instead of being passed in the frame.
+****************************************************************************
+***************************************************************************/
+
+/* Numbers for RMATCH calls. When this list is changed, the code at HEAP_RETURN
+below must be updated in sync. */
+
+enum { RM1=1, RM2, RM3, RM4, RM5, RM6, RM7, RM8, RM9, RM10,
+ RM11, RM12, RM13, RM14, RM15, RM16, RM17, RM18, RM19, RM20,
+ RM21, RM22, RM23, RM24, RM25, RM26, RM27, RM28, RM29, RM30,
+ RM31, RM32, RM33, RM34, RM35, RM36, RM37, RM38, RM39, RM40,
+ RM41, RM42, RM43, RM44, RM45, RM46, RM47, RM48, RM49, RM50,
+ RM51, RM52, RM53, RM54, RM55, RM56, RM57, RM58, RM59, RM60,
+ RM61, RM62, RM63 };
+
+/* These versions of the macros use the stack, as normal. There are debugging
+versions and production versions. Note that the "rw" argument of RMATCH isn't
+actually used in this definition. */
+
+#ifndef NO_RECURSE
+#define REGISTER register
+
+#ifdef PCRE_DEBUG
+#define RMATCH(ra,rb,rc,rd,re,rw) \
+ { \
+ printf("match() called in line %d\n", __LINE__); \
+ rrc = match(ra,rb,mstart,markptr,rc,rd,re,rdepth+1); \
+ printf("to line %d\n", __LINE__); \
+ }
+#define RRETURN(ra) \
+ { \
+ printf("match() returned %d from line %d ", ra, __LINE__); \
+ return ra; \
+ }
+#else
+#define RMATCH(ra,rb,rc,rd,re,rw) \
+ rrc = match(ra,rb,mstart,markptr,rc,rd,re,rdepth+1)
+#define RRETURN(ra) return ra
+#endif
+
+#else
+
+
+/* These versions of the macros manage a private stack on the heap. Note that
+the "rd" argument of RMATCH isn't actually used in this definition. It's the md
+argument of match(), which never changes. */
+
+#define REGISTER
+
+#define RMATCH(ra,rb,rc,rd,re,rw)\
+ {\
+ heapframe *newframe = (heapframe *)(pcre_stack_malloc)(sizeof(heapframe));\
+ if (newframe == NULL) RRETURN(PCRE_ERROR_NOMEMORY);\
+ frame->Xwhere = rw; \
+ newframe->Xeptr = ra;\
+ newframe->Xecode = rb;\
+ newframe->Xmstart = mstart;\
+ newframe->Xmarkptr = markptr;\
+ newframe->Xoffset_top = rc;\
+ newframe->Xeptrb = re;\
+ newframe->Xrdepth = frame->Xrdepth + 1;\
+ newframe->Xprevframe = frame;\
+ frame = newframe;\
+ DPRINTF(("restarting from line %d\n", __LINE__));\
+ goto HEAP_RECURSE;\
+ L_##rw:\
+ DPRINTF(("jumped back to line %d\n", __LINE__));\
+ }
+
+#define RRETURN(ra)\
+ {\
+ heapframe *oldframe = frame;\
+ frame = oldframe->Xprevframe;\
+ (pcre_stack_free)(oldframe);\
+ if (frame != NULL)\
+ {\
+ rrc = ra;\
+ goto HEAP_RETURN;\
+ }\
+ return ra;\
+ }
+
+
+/* Structure for remembering the local variables in a private frame */
+
+typedef struct heapframe {
+ struct heapframe *Xprevframe;
+
+ /* Function arguments that may change */
+
+ USPTR Xeptr;
+ const uschar *Xecode;
+ USPTR Xmstart;
+ USPTR Xmarkptr;
+ int Xoffset_top;
+ eptrblock *Xeptrb;
+ unsigned int Xrdepth;
+
+ /* Function local variables */
+
+ USPTR Xcallpat;
+#ifdef SUPPORT_UTF8
+ USPTR Xcharptr;
+#endif
+ USPTR Xdata;
+ USPTR Xnext;
+ USPTR Xpp;
+ USPTR Xprev;
+ USPTR Xsaved_eptr;
+
+ recursion_info Xnew_recursive;
+
+ BOOL Xcur_is_word;
+ BOOL Xcondition;
+ BOOL Xprev_is_word;
+
+#ifdef SUPPORT_UCP
+ int Xprop_type;
+ int Xprop_value;
+ int Xprop_fail_result;
+ int Xoclength;
+ uschar Xocchars[8];
+#endif
+
+ int Xcodelink;
+ int Xctype;
+ unsigned int Xfc;
+ int Xfi;
+ int Xlength;
+ int Xmax;
+ int Xmin;
+ int Xnumber;
+ int Xoffset;
+ int Xop;
+ int Xsave_capture_last;
+ int Xsave_offset1, Xsave_offset2, Xsave_offset3;
+ int Xstacksave[REC_STACK_SAVE_MAX];
+
+ eptrblock Xnewptrb;
+
+ /* Where to jump back to */
+
+ int Xwhere;
+
+} heapframe;
+
+#endif
+
+
+/***************************************************************************
+***************************************************************************/
+
+
+
+/*************************************************
+* Match from current position *
+*************************************************/
+
+/* This function is called recursively in many circumstances. Whenever it
+returns a negative (error) response, the outer incarnation must also return the
+same response. */
+
+/* These macros pack up tests that are used for partial matching, and which
+appears several times in the code. We set the "hit end" flag if the pointer is
+at the end of the subject and also past the start of the subject (i.e.
+something has been matched). For hard partial matching, we then return
+immediately. The second one is used when we already know we are past the end of
+the subject. */
+
+#define CHECK_PARTIAL()\
+ if (md->partial != 0 && eptr >= md->end_subject && \
+ eptr > md->start_used_ptr) \
+ { \
+ md->hitend = TRUE; \
+ if (md->partial > 1) MRRETURN(PCRE_ERROR_PARTIAL); \
+ }
+
+#define SCHECK_PARTIAL()\
+ if (md->partial != 0 && eptr > md->start_used_ptr) \
+ { \
+ md->hitend = TRUE; \
+ if (md->partial > 1) MRRETURN(PCRE_ERROR_PARTIAL); \
+ }
+
+
+/* Performance note: It might be tempting to extract commonly used fields from
+the md structure (e.g. utf8, end_subject) into individual variables to improve
+performance. Tests using gcc on a SPARC disproved this; in the first case, it
+made performance worse.
+
+Arguments:
+ eptr pointer to current character in subject
+ ecode pointer to current position in compiled code
+ mstart pointer to the current match start position (can be modified
+ by encountering \K)
+ markptr pointer to the most recent MARK name, or NULL
+ offset_top current top pointer
+ md pointer to "static" info for the match
+ eptrb pointer to chain of blocks containing eptr at start of
+ brackets - for testing for empty matches
+ rdepth the recursion depth
+
+Returns: MATCH_MATCH if matched ) these values are >= 0
+ MATCH_NOMATCH if failed to match )
+ a negative MATCH_xxx value for PRUNE, SKIP, etc
+ a negative PCRE_ERROR_xxx value if aborted by an error condition
+ (e.g. stopped by repeated call or recursion limit)
+*/
+
+static int
+match(REGISTER USPTR eptr, REGISTER const uschar *ecode, USPTR mstart,
+ const uschar *markptr, int offset_top, match_data *md, eptrblock *eptrb,
+ unsigned int rdepth)
+{
+/* These variables do not need to be preserved over recursion in this function,
+so they can be ordinary variables in all cases. Mark some of them with
+"register" because they are used a lot in loops. */
+
+register int rrc; /* Returns from recursive calls */
+register int i; /* Used for loops not involving calls to RMATCH() */
+register unsigned int c; /* Character values not kept over RMATCH() calls */
+register BOOL utf8; /* Local copy of UTF-8 flag for speed */
+
+BOOL minimize, possessive; /* Quantifier options */
+BOOL caseless;
+int condcode;
+
+/* When recursion is not being used, all "local" variables that have to be
+preserved over calls to RMATCH() are part of a "frame" which is obtained from
+heap storage. Set up the top-level frame here; others are obtained from the
+heap whenever RMATCH() does a "recursion". See the macro definitions above. */
+
+#ifdef NO_RECURSE
+heapframe *frame = (heapframe *)(pcre_stack_malloc)(sizeof(heapframe));
+if (frame == NULL) RRETURN(PCRE_ERROR_NOMEMORY);
+frame->Xprevframe = NULL; /* Marks the top level */
+
+/* Copy in the original argument variables */
+
+frame->Xeptr = eptr;
+frame->Xecode = ecode;
+frame->Xmstart = mstart;
+frame->Xmarkptr = markptr;
+frame->Xoffset_top = offset_top;
+frame->Xeptrb = eptrb;
+frame->Xrdepth = rdepth;
+
+/* This is where control jumps back to to effect "recursion" */
+
+HEAP_RECURSE:
+
+/* Macros make the argument variables come from the current frame */
+
+#define eptr frame->Xeptr
+#define ecode frame->Xecode
+#define mstart frame->Xmstart
+#define markptr frame->Xmarkptr
+#define offset_top frame->Xoffset_top
+#define eptrb frame->Xeptrb
+#define rdepth frame->Xrdepth
+
+/* Ditto for the local variables */
+
+#ifdef SUPPORT_UTF8
+#define charptr frame->Xcharptr
+#endif
+#define callpat frame->Xcallpat
+#define codelink frame->Xcodelink
+#define data frame->Xdata
+#define next frame->Xnext
+#define pp frame->Xpp
+#define prev frame->Xprev
+#define saved_eptr frame->Xsaved_eptr
+
+#define new_recursive frame->Xnew_recursive
+
+#define cur_is_word frame->Xcur_is_word
+#define condition frame->Xcondition
+#define prev_is_word frame->Xprev_is_word
+
+#ifdef SUPPORT_UCP
+#define prop_type frame->Xprop_type
+#define prop_value frame->Xprop_value
+#define prop_fail_result frame->Xprop_fail_result
+#define oclength frame->Xoclength
+#define occhars frame->Xocchars
+#endif
+
+#define ctype frame->Xctype
+#define fc frame->Xfc
+#define fi frame->Xfi
+#define length frame->Xlength
+#define max frame->Xmax
+#define min frame->Xmin
+#define number frame->Xnumber
+#define offset frame->Xoffset
+#define op frame->Xop
+#define save_capture_last frame->Xsave_capture_last
+#define save_offset1 frame->Xsave_offset1
+#define save_offset2 frame->Xsave_offset2
+#define save_offset3 frame->Xsave_offset3
+#define stacksave frame->Xstacksave
+
+#define newptrb frame->Xnewptrb
+
+/* When recursion is being used, local variables are allocated on the stack and
+get preserved during recursion in the normal way. In this environment, fi and
+i, and fc and c, can be the same variables. */
+
+#else /* NO_RECURSE not defined */
+#define fi i
+#define fc c
+
+/* Many of the following variables are used only in small blocks of the code.
+My normal style of coding would have declared them within each of those blocks.
+However, in order to accommodate the version of this code that uses an external
+"stack" implemented on the heap, it is easier to declare them all here, so the
+declarations can be cut out in a block. The only declarations within blocks
+below are for variables that do not have to be preserved over a recursive call
+to RMATCH(). */
+
+#ifdef SUPPORT_UTF8
+const uschar *charptr;
+#endif
+const uschar *callpat;
+const uschar *data;
+const uschar *next;
+USPTR pp;
+const uschar *prev;
+USPTR saved_eptr;
+
+recursion_info new_recursive;
+
+BOOL cur_is_word;
+BOOL condition;
+BOOL prev_is_word;
+
+#ifdef SUPPORT_UCP
+int prop_type;
+int prop_value;
+int prop_fail_result;
+int oclength;
+uschar occhars[8];
+#endif
+
+int codelink;
+int ctype;
+int length;
+int max;
+int min;
+int number;
+int offset;
+int op;
+int save_capture_last;
+int save_offset1, save_offset2, save_offset3;
+int stacksave[REC_STACK_SAVE_MAX];
+
+eptrblock newptrb;
+#endif /* NO_RECURSE */
+
+/* To save space on the stack and in the heap frame, I have doubled up on some
+of the local variables that are used only in localised parts of the code, but
+still need to be preserved over recursive calls of match(). These macros define
+the alternative names that are used. */
+
+#define allow_zero cur_is_word
+#define cbegroup condition
+#define code_offset codelink
+#define condassert condition
+#define matched_once prev_is_word
+
+/* These statements are here to stop the compiler complaining about unitialized
+variables. */
+
+#ifdef SUPPORT_UCP
+prop_value = 0;
+prop_fail_result = 0;
+#endif
+
+
+/* This label is used for tail recursion, which is used in a few cases even
+when NO_RECURSE is not defined, in order to reduce the amount of stack that is
+used. Thanks to Ian Taylor for noticing this possibility and sending the
+original patch. */
+
+TAIL_RECURSE:
+
+/* OK, now we can get on with the real code of the function. Recursive calls
+are specified by the macro RMATCH and RRETURN is used to return. When
+NO_RECURSE is *not* defined, these just turn into a recursive call to match()
+and a "return", respectively (possibly with some debugging if PCRE_DEBUG is
+defined). However, RMATCH isn't like a function call because it's quite a
+complicated macro. It has to be used in one particular way. This shouldn't,
+however, impact performance when true recursion is being used. */
+
+#ifdef SUPPORT_UTF8
+utf8 = md->utf8; /* Local copy of the flag */
+#else
+utf8 = FALSE;
+#endif
+
+/* First check that we haven't called match() too many times, or that we
+haven't exceeded the recursive call limit. */
+
+if (md->match_call_count++ >= md->match_limit) RRETURN(PCRE_ERROR_MATCHLIMIT);
+if (rdepth >= md->match_limit_recursion) RRETURN(PCRE_ERROR_RECURSIONLIMIT);
+
+/* At the start of a group with an unlimited repeat that may match an empty
+string, the variable md->match_function_type is set to MATCH_CBEGROUP. It is
+done this way to save having to use another function argument, which would take
+up space on the stack. See also MATCH_CONDASSERT below.
+
+When MATCH_CBEGROUP is set, add the current subject pointer to the chain of
+such remembered pointers, to be checked when we hit the closing ket, in order
+to break infinite loops that match no characters. When match() is called in
+other circumstances, don't add to the chain. The MATCH_CBEGROUP feature must
+NOT be used with tail recursion, because the memory block that is used is on
+the stack, so a new one may be required for each match(). */
+
+if (md->match_function_type == MATCH_CBEGROUP)
+ {
+ newptrb.epb_saved_eptr = eptr;
+ newptrb.epb_prev = eptrb;
+ eptrb = &newptrb;
+ md->match_function_type = 0;
+ }
+
+/* Now start processing the opcodes. */
+
+for (;;)
+ {
+ minimize = possessive = FALSE;
+ op = *ecode;
+
+ switch(op)
+ {
+ case OP_MARK:
+ markptr = ecode + 2;
+ RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode] + ecode[1], offset_top, md,
+ eptrb, RM55);
+
+ /* A return of MATCH_SKIP_ARG means that matching failed at SKIP with an
+ argument, and we must check whether that argument matches this MARK's
+ argument. It is passed back in md->start_match_ptr (an overloading of that
+ variable). If it does match, we reset that variable to the current subject
+ position and return MATCH_SKIP. Otherwise, pass back the return code
+ unaltered. */
+
+ if (rrc == MATCH_SKIP_ARG &&
+ strcmp((char *)markptr, (char *)(md->start_match_ptr)) == 0)
+ {
+ md->start_match_ptr = eptr;
+ RRETURN(MATCH_SKIP);
+ }
+
+ if (md->mark == NULL) md->mark = markptr;
+ RRETURN(rrc);
+
+ case OP_FAIL:
+ MRRETURN(MATCH_NOMATCH);
+
+ /* COMMIT overrides PRUNE, SKIP, and THEN */
+
+ case OP_COMMIT:
+ RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode], offset_top, md,
+ eptrb, RM52);
+ if (rrc != MATCH_NOMATCH && rrc != MATCH_PRUNE &&
+ rrc != MATCH_SKIP && rrc != MATCH_SKIP_ARG &&
+ rrc != MATCH_THEN)
+ RRETURN(rrc);
+ MRRETURN(MATCH_COMMIT);
+
+ /* PRUNE overrides THEN */
+
+ case OP_PRUNE:
+ RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode], offset_top, md,
+ eptrb, RM51);
+ if (rrc != MATCH_NOMATCH && rrc != MATCH_THEN) RRETURN(rrc);
+ MRRETURN(MATCH_PRUNE);
+
+ case OP_PRUNE_ARG:
+ RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode] + ecode[1], offset_top, md,
+ eptrb, RM56);
+ if (rrc != MATCH_NOMATCH && rrc != MATCH_THEN) RRETURN(rrc);
+ md->mark = ecode + 2;
+ RRETURN(MATCH_PRUNE);
+
+ /* SKIP overrides PRUNE and THEN */
+
+ case OP_SKIP:
+ RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode], offset_top, md,
+ eptrb, RM53);
+ if (rrc != MATCH_NOMATCH && rrc != MATCH_PRUNE && rrc != MATCH_THEN)
+ RRETURN(rrc);
+ md->start_match_ptr = eptr; /* Pass back current position */
+ MRRETURN(MATCH_SKIP);
+
+ case OP_SKIP_ARG:
+ RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode] + ecode[1], offset_top, md,
+ eptrb, RM57);
+ if (rrc != MATCH_NOMATCH && rrc != MATCH_PRUNE && rrc != MATCH_THEN)
+ RRETURN(rrc);
+
+ /* Pass back the current skip name by overloading md->start_match_ptr and
+ returning the special MATCH_SKIP_ARG return code. This will either be
+ caught by a matching MARK, or get to the top, where it is treated the same
+ as PRUNE. */
+
+ md->start_match_ptr = ecode + 2;
+ RRETURN(MATCH_SKIP_ARG);
+
+ /* For THEN (and THEN_ARG) we pass back the address of the bracket or
+ the alt that is at the start of the current branch. This makes it possible
+ to skip back past alternatives that precede the THEN within the current
+ branch. */
+
+ case OP_THEN:
+ RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode], offset_top, md,
+ eptrb, RM54);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ md->start_match_ptr = ecode - GET(ecode, 1);
+ MRRETURN(MATCH_THEN);
+
+ case OP_THEN_ARG:
+ RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode] + ecode[1+LINK_SIZE],
+ offset_top, md, eptrb, RM58);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ md->start_match_ptr = ecode - GET(ecode, 1);
+ md->mark = ecode + LINK_SIZE + 2;
+ RRETURN(MATCH_THEN);
+
+ /* Handle a capturing bracket, other than those that are possessive with an
+ unlimited repeat. If there is space in the offset vector, save the current
+ subject position in the working slot at the top of the vector. We mustn't
+ change the current values of the data slot, because they may be set from a
+ previous iteration of this group, and be referred to by a reference inside
+ the group. A failure to match might occur after the group has succeeded,
+ if something later on doesn't match. For this reason, we need to restore
+ the working value and also the values of the final offsets, in case they
+ were set by a previous iteration of the same bracket.
+
+ If there isn't enough space in the offset vector, treat this as if it were
+ a non-capturing bracket. Don't worry about setting the flag for the error
+ case here; that is handled in the code for KET. */
+
+ case OP_CBRA:
+ case OP_SCBRA:
+ number = GET2(ecode, 1+LINK_SIZE);
+ offset = number << 1;
+
+#ifdef PCRE_DEBUG
+ printf("start bracket %d\n", number);
+ printf("subject=");
+ pchars(eptr, 16, TRUE, md);
+ printf("\n");
+#endif
+
+ if (offset < md->offset_max)
+ {
+ save_offset1 = md->offset_vector[offset];
+ save_offset2 = md->offset_vector[offset+1];
+ save_offset3 = md->offset_vector[md->offset_end - number];
+ save_capture_last = md->capture_last;
+
+ DPRINTF(("saving %d %d %d\n", save_offset1, save_offset2, save_offset3));
+ md->offset_vector[md->offset_end - number] =
+ (int)(eptr - md->start_subject);
+
+ for (;;)
+ {
+ if (op >= OP_SBRA) md->match_function_type = MATCH_CBEGROUP;
+ RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode], offset_top, md,
+ eptrb, RM1);
+ if (rrc == MATCH_ONCE) break; /* Backing up through an atomic group */
+ if (rrc != MATCH_NOMATCH &&
+ (rrc != MATCH_THEN || md->start_match_ptr != ecode))
+ RRETURN(rrc);
+ md->capture_last = save_capture_last;
+ ecode += GET(ecode, 1);
+ if (*ecode != OP_ALT) break;
+ }
+
+ DPRINTF(("bracket %d failed\n", number));
+ md->offset_vector[offset] = save_offset1;
+ md->offset_vector[offset+1] = save_offset2;
+ md->offset_vector[md->offset_end - number] = save_offset3;
+
+ /* At this point, rrc will be one of MATCH_ONCE, MATCH_NOMATCH, or
+ MATCH_THEN. */
+
+ if (rrc != MATCH_THEN && md->mark == NULL) md->mark = markptr;
+ RRETURN(((rrc == MATCH_ONCE)? MATCH_ONCE:MATCH_NOMATCH));
+ }
+
+ /* FALL THROUGH ... Insufficient room for saving captured contents. Treat
+ as a non-capturing bracket. */
+
+ /* VVVVVVVVVVVVVVVVVVVVVVVVV */
+ /* VVVVVVVVVVVVVVVVVVVVVVVVV */
+
+ DPRINTF(("insufficient capture room: treat as non-capturing\n"));
+
+ /* VVVVVVVVVVVVVVVVVVVVVVVVV */
+ /* VVVVVVVVVVVVVVVVVVVVVVVVV */
+
+ /* Non-capturing or atomic group, except for possessive with unlimited
+ repeat. Loop for all the alternatives. When we get to the final alternative
+ within the brackets, we used to return the result of a recursive call to
+ match() whatever happened so it was possible to reduce stack usage by
+ turning this into a tail recursion, except in the case of a possibly empty
+ group. However, now that there is the possiblity of (*THEN) occurring in
+ the final alternative, this optimization is no longer possible.
+
+ MATCH_ONCE is returned when the end of an atomic group is successfully
+ reached, but subsequent matching fails. It passes back up the tree (causing
+ captured values to be reset) until the original atomic group level is
+ reached. This is tested by comparing md->once_target with the start of the
+ group. At this point, the return is converted into MATCH_NOMATCH so that
+ previous backup points can be taken. */
+
+ case OP_ONCE:
+ case OP_BRA:
+ case OP_SBRA:
+ DPRINTF(("start non-capturing bracket\n"));
+
+ for (;;)
+ {
+ if (op >= OP_SBRA || op == OP_ONCE) md->match_function_type = MATCH_CBEGROUP;
+ RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode], offset_top, md, eptrb,
+ RM2);
+ if (rrc != MATCH_NOMATCH &&
+ (rrc != MATCH_THEN || md->start_match_ptr != ecode))
+ {
+ if (rrc == MATCH_ONCE)
+ {
+ const uschar *scode = ecode;
+ if (*scode != OP_ONCE) /* If not at start, find it */
+ {
+ while (*scode == OP_ALT) scode += GET(scode, 1);
+ scode -= GET(scode, 1);
+ }
+ if (md->once_target == scode) rrc = MATCH_NOMATCH;
+ }
+ RRETURN(rrc);
+ }
+ ecode += GET(ecode, 1);
+ if (*ecode != OP_ALT) break;
+ }
+ if (rrc != MATCH_THEN && md->mark == NULL) md->mark = markptr;
+ RRETURN(MATCH_NOMATCH);
+
+ /* Handle possessive capturing brackets with an unlimited repeat. We come
+ here from BRAZERO with allow_zero set TRUE. The offset_vector values are
+ handled similarly to the normal case above. However, the matching is
+ different. The end of these brackets will always be OP_KETRPOS, which
+ returns MATCH_KETRPOS without going further in the pattern. By this means
+ we can handle the group by iteration rather than recursion, thereby
+ reducing the amount of stack needed. */
+
+ case OP_CBRAPOS:
+ case OP_SCBRAPOS:
+ allow_zero = FALSE;
+
+ POSSESSIVE_CAPTURE:
+ number = GET2(ecode, 1+LINK_SIZE);
+ offset = number << 1;
+
+#ifdef PCRE_DEBUG
+ printf("start possessive bracket %d\n", number);
+ printf("subject=");
+ pchars(eptr, 16, TRUE, md);
+ printf("\n");
+#endif
+
+ if (offset < md->offset_max)
+ {
+ matched_once = FALSE;
+ code_offset = ecode - md->start_code;
+
+ save_offset1 = md->offset_vector[offset];
+ save_offset2 = md->offset_vector[offset+1];
+ save_offset3 = md->offset_vector[md->offset_end - number];
+ save_capture_last = md->capture_last;
+
+ DPRINTF(("saving %d %d %d\n", save_offset1, save_offset2, save_offset3));
+
+ /* Each time round the loop, save the current subject position for use
+ when the group matches. For MATCH_MATCH, the group has matched, so we
+ restart it with a new subject starting position, remembering that we had
+ at least one match. For MATCH_NOMATCH, carry on with the alternatives, as
+ usual. If we haven't matched any alternatives in any iteration, check to
+ see if a previous iteration matched. If so, the group has matched;
+ continue from afterwards. Otherwise it has failed; restore the previous
+ capture values before returning NOMATCH. */
+
+ for (;;)
+ {
+ md->offset_vector[md->offset_end - number] =
+ (int)(eptr - md->start_subject);
+ if (op >= OP_SBRA) md->match_function_type = MATCH_CBEGROUP;
+ RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode], offset_top, md,
+ eptrb, RM63);
+ if (rrc == MATCH_KETRPOS)
+ {
+ offset_top = md->end_offset_top;
+ eptr = md->end_match_ptr;
+ ecode = md->start_code + code_offset;
+ save_capture_last = md->capture_last;
+ matched_once = TRUE;
+ continue;
+ }
+ if (rrc != MATCH_NOMATCH &&
+ (rrc != MATCH_THEN || md->start_match_ptr != ecode))
+ RRETURN(rrc);
+ md->capture_last = save_capture_last;
+ ecode += GET(ecode, 1);
+ if (*ecode != OP_ALT) break;
+ }
+
+ if (!matched_once)
+ {
+ md->offset_vector[offset] = save_offset1;
+ md->offset_vector[offset+1] = save_offset2;
+ md->offset_vector[md->offset_end - number] = save_offset3;
+ }
+
+ if (rrc != MATCH_THEN && md->mark == NULL) md->mark = markptr;
+ if (allow_zero || matched_once)
+ {
+ ecode += 1 + LINK_SIZE;
+ break;
+ }
+
+ RRETURN(MATCH_NOMATCH);
+ }
+
+ /* FALL THROUGH ... Insufficient room for saving captured contents. Treat
+ as a non-capturing bracket. */
+
+ /* VVVVVVVVVVVVVVVVVVVVVVVVV */
+ /* VVVVVVVVVVVVVVVVVVVVVVVVV */
+
+ DPRINTF(("insufficient capture room: treat as non-capturing\n"));
+
+ /* VVVVVVVVVVVVVVVVVVVVVVVVV */
+ /* VVVVVVVVVVVVVVVVVVVVVVVVV */
+
+ /* Non-capturing possessive bracket with unlimited repeat. We come here
+ from BRAZERO with allow_zero = TRUE. The code is similar to the above,
+ without the capturing complication. It is written out separately for speed
+ and cleanliness. */
+
+ case OP_BRAPOS:
+ case OP_SBRAPOS:
+ allow_zero = FALSE;
+
+ POSSESSIVE_NON_CAPTURE:
+ matched_once = FALSE;
+ code_offset = ecode - md->start_code;
+
+ for (;;)
+ {
+ if (op >= OP_SBRA) md->match_function_type = MATCH_CBEGROUP;
+ RMATCH(eptr, ecode + _pcre_OP_lengths[*ecode], offset_top, md,
+ eptrb, RM48);
+ if (rrc == MATCH_KETRPOS)
+ {
+ offset_top = md->end_offset_top;
+ eptr = md->end_match_ptr;
+ ecode = md->start_code + code_offset;
+ matched_once = TRUE;
+ continue;
+ }
+ if (rrc != MATCH_NOMATCH &&
+ (rrc != MATCH_THEN || md->start_match_ptr != ecode))
+ RRETURN(rrc);
+ ecode += GET(ecode, 1);
+ if (*ecode != OP_ALT) break;
+ }
+
+ if (matched_once || allow_zero)
+ {
+ ecode += 1 + LINK_SIZE;
+ break;
+ }
+ RRETURN(MATCH_NOMATCH);
+
+ /* Control never reaches here. */
+
+ /* Conditional group: compilation checked that there are no more than
+ two branches. If the condition is false, skipping the first branch takes us
+ past the end if there is only one branch, but that's OK because that is
+ exactly what going to the ket would do. */
+
+ case OP_COND:
+ case OP_SCOND:
+ codelink = GET(ecode, 1);
+
+ /* Because of the way auto-callout works during compile, a callout item is
+ inserted between OP_COND and an assertion condition. */
+
+ if (ecode[LINK_SIZE+1] == OP_CALLOUT)
+ {
+ if (pcre_callout != NULL)
+ {
+ pcre_callout_block cb;
+ cb.version = 2; /* Version 1 of the callout block */
+ cb.callout_number = ecode[LINK_SIZE+2];
+ cb.offset_vector = md->offset_vector;
+ cb.subject = (PCRE_SPTR)md->start_subject;
+ cb.subject_length = (int)(md->end_subject - md->start_subject);
+ cb.start_match = (int)(mstart - md->start_subject);
+ cb.current_position = (int)(eptr - md->start_subject);
+ cb.pattern_position = GET(ecode, LINK_SIZE + 3);
+ cb.next_item_length = GET(ecode, 3 + 2*LINK_SIZE);
+ cb.capture_top = offset_top/2;
+ cb.capture_last = md->capture_last;
+ cb.callout_data = md->callout_data;
+ cb.mark = markptr;
+ if ((rrc = (*pcre_callout)(&cb)) > 0) MRRETURN(MATCH_NOMATCH);
+ if (rrc < 0) RRETURN(rrc);
+ }
+ ecode += _pcre_OP_lengths[OP_CALLOUT];
+ }
+
+ condcode = ecode[LINK_SIZE+1];
+
+ /* Now see what the actual condition is */
+
+ if (condcode == OP_RREF || condcode == OP_NRREF) /* Recursion test */
+ {
+ if (md->recursive == NULL) /* Not recursing => FALSE */
+ {
+ condition = FALSE;
+ ecode += GET(ecode, 1);
+ }
+ else
+ {
+ int recno = GET2(ecode, LINK_SIZE + 2); /* Recursion group number*/
+ condition = (recno == RREF_ANY || recno == md->recursive->group_num);
+
+ /* If the test is for recursion into a specific subpattern, and it is
+ false, but the test was set up by name, scan the table to see if the
+ name refers to any other numbers, and test them. The condition is true
+ if any one is set. */
+
+ if (!condition && condcode == OP_NRREF && recno != RREF_ANY)
+ {
+ uschar *slotA = md->name_table;
+ for (i = 0; i < md->name_count; i++)
+ {
+ if (GET2(slotA, 0) == recno) break;
+ slotA += md->name_entry_size;
+ }
+
+ /* Found a name for the number - there can be only one; duplicate
+ names for different numbers are allowed, but not vice versa. First
+ scan down for duplicates. */
+
+ if (i < md->name_count)
+ {
+ uschar *slotB = slotA;
+ while (slotB > md->name_table)
+ {
+ slotB -= md->name_entry_size;
+ if (strcmp((char *)slotA + 2, (char *)slotB + 2) == 0)
+ {
+ condition = GET2(slotB, 0) == md->recursive->group_num;
+ if (condition) break;
+ }
+ else break;
+ }
+
+ /* Scan up for duplicates */
+
+ if (!condition)
+ {
+ slotB = slotA;
+ for (i++; i < md->name_count; i++)
+ {
+ slotB += md->name_entry_size;
+ if (strcmp((char *)slotA + 2, (char *)slotB + 2) == 0)
+ {
+ condition = GET2(slotB, 0) == md->recursive->group_num;
+ if (condition) break;
+ }
+ else break;
+ }
+ }
+ }
+ }
+
+ /* Chose branch according to the condition */
+
+ ecode += condition? 3 : GET(ecode, 1);
+ }
+ }
+
+ else if (condcode == OP_CREF || condcode == OP_NCREF) /* Group used test */
+ {
+ offset = GET2(ecode, LINK_SIZE+2) << 1; /* Doubled ref number */
+ condition = offset < offset_top && md->offset_vector[offset] >= 0;
+
+ /* If the numbered capture is unset, but the reference was by name,
+ scan the table to see if the name refers to any other numbers, and test
+ them. The condition is true if any one is set. This is tediously similar
+ to the code above, but not close enough to try to amalgamate. */
+
+ if (!condition && condcode == OP_NCREF)
+ {
+ int refno = offset >> 1;
+ uschar *slotA = md->name_table;
+
+ for (i = 0; i < md->name_count; i++)
+ {
+ if (GET2(slotA, 0) == refno) break;
+ slotA += md->name_entry_size;
+ }
+
+ /* Found a name for the number - there can be only one; duplicate names
+ for different numbers are allowed, but not vice versa. First scan down
+ for duplicates. */
+
+ if (i < md->name_count)
+ {
+ uschar *slotB = slotA;
+ while (slotB > md->name_table)
+ {
+ slotB -= md->name_entry_size;
+ if (strcmp((char *)slotA + 2, (char *)slotB + 2) == 0)
+ {
+ offset = GET2(slotB, 0) << 1;
+ condition = offset < offset_top &&
+ md->offset_vector[offset] >= 0;
+ if (condition) break;
+ }
+ else break;
+ }
+
+ /* Scan up for duplicates */
+
+ if (!condition)
+ {
+ slotB = slotA;
+ for (i++; i < md->name_count; i++)
+ {
+ slotB += md->name_entry_size;
+ if (strcmp((char *)slotA + 2, (char *)slotB + 2) == 0)
+ {
+ offset = GET2(slotB, 0) << 1;
+ condition = offset < offset_top &&
+ md->offset_vector[offset] >= 0;
+ if (condition) break;
+ }
+ else break;
+ }
+ }
+ }
+ }
+
+ /* Chose branch according to the condition */
+
+ ecode += condition? 3 : GET(ecode, 1);
+ }
+
+ else if (condcode == OP_DEF) /* DEFINE - always false */
+ {
+ condition = FALSE;
+ ecode += GET(ecode, 1);
+ }
+
+ /* The condition is an assertion. Call match() to evaluate it - setting
+ md->match_function_type to MATCH_CONDASSERT causes it to stop at the end of
+ an assertion. */
+
+ else
+ {
+ md->match_function_type = MATCH_CONDASSERT;
+ RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, md, NULL, RM3);
+ if (rrc == MATCH_MATCH)
+ {
+ if (md->end_offset_top > offset_top)
+ offset_top = md->end_offset_top; /* Captures may have happened */
+ condition = TRUE;
+ ecode += 1 + LINK_SIZE + GET(ecode, LINK_SIZE + 2);
+ while (*ecode == OP_ALT) ecode += GET(ecode, 1);
+ }
+ else if (rrc != MATCH_NOMATCH &&
+ (rrc != MATCH_THEN || md->start_match_ptr != ecode))
+ {
+ RRETURN(rrc); /* Need braces because of following else */
+ }
+ else
+ {
+ condition = FALSE;
+ ecode += codelink;
+ }
+ }
+
+ /* We are now at the branch that is to be obeyed. As there is only one,
+ we used to use tail recursion to avoid using another stack frame, except
+ when there was unlimited repeat of a possibly empty group. However, that
+ strategy no longer works because of the possibilty of (*THEN) being
+ encountered in the branch. A recursive call to match() is always required,
+ unless the second alternative doesn't exist, in which case we can just
+ plough on. */
+
+ if (condition || *ecode == OP_ALT)
+ {
+ if (op == OP_SCOND) md->match_function_type = MATCH_CBEGROUP;
+ RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, md, eptrb, RM49);
+ if (rrc == MATCH_THEN && md->start_match_ptr == ecode)
+ rrc = MATCH_NOMATCH;
+ RRETURN(rrc);
+ }
+ else /* Condition false & no alternative */
+ {
+ ecode += 1 + LINK_SIZE;
+ }
+ break;
+
+
+ /* Before OP_ACCEPT there may be any number of OP_CLOSE opcodes,
+ to close any currently open capturing brackets. */
+
+ case OP_CLOSE:
+ number = GET2(ecode, 1);
+ offset = number << 1;
+
+#ifdef PCRE_DEBUG
+ printf("end bracket %d at *ACCEPT", number);
+ printf("\n");
+#endif
+
+ md->capture_last = number;
+ if (offset >= md->offset_max) md->offset_overflow = TRUE; else
+ {
+ md->offset_vector[offset] =
+ md->offset_vector[md->offset_end - number];
+ md->offset_vector[offset+1] = (int)(eptr - md->start_subject);
+ if (offset_top <= offset) offset_top = offset + 2;
+ }
+ ecode += 3;
+ break;
+
+
+ /* End of the pattern, either real or forced. */
+
+ case OP_END:
+ case OP_ACCEPT:
+ case OP_ASSERT_ACCEPT:
+
+ /* If we have matched an empty string, fail if not in an assertion and not
+ in a recursion if either PCRE_NOTEMPTY is set, or if PCRE_NOTEMPTY_ATSTART
+ is set and we have matched at the start of the subject. In both cases,
+ backtracking will then try other alternatives, if any. */
+
+ if (eptr == mstart && op != OP_ASSERT_ACCEPT &&
+ md->recursive == NULL &&
+ (md->notempty ||
+ (md->notempty_atstart &&
+ mstart == md->start_subject + md->start_offset)))
+ MRRETURN(MATCH_NOMATCH);
+
+ /* Otherwise, we have a match. */
+
+ md->end_match_ptr = eptr; /* Record where we ended */
+ md->end_offset_top = offset_top; /* and how many extracts were taken */
+ md->start_match_ptr = mstart; /* and the start (\K can modify) */
+
+ /* For some reason, the macros don't work properly if an expression is
+ given as the argument to MRRETURN when the heap is in use. */
+
+ rrc = (op == OP_END)? MATCH_MATCH : MATCH_ACCEPT;
+ MRRETURN(rrc);
+
+ /* Assertion brackets. Check the alternative branches in turn - the
+ matching won't pass the KET for an assertion. If any one branch matches,
+ the assertion is true. Lookbehind assertions have an OP_REVERSE item at the
+ start of each branch to move the current point backwards, so the code at
+ this level is identical to the lookahead case. When the assertion is part
+ of a condition, we want to return immediately afterwards. The caller of
+ this incarnation of the match() function will have set MATCH_CONDASSERT in
+ md->match_function type, and one of these opcodes will be the first opcode
+ that is processed. We use a local variable that is preserved over calls to
+ match() to remember this case. */
+
+ case OP_ASSERT:
+ case OP_ASSERTBACK:
+ if (md->match_function_type == MATCH_CONDASSERT)
+ {
+ condassert = TRUE;
+ md->match_function_type = 0;
+ }
+ else condassert = FALSE;
+
+ do
+ {
+ RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, md, NULL, RM4);
+ if (rrc == MATCH_MATCH || rrc == MATCH_ACCEPT)
+ {
+ mstart = md->start_match_ptr; /* In case \K reset it */
+ markptr = md->mark;
+ break;
+ }
+ if (rrc != MATCH_NOMATCH &&
+ (rrc != MATCH_THEN || md->start_match_ptr != ecode))
+ RRETURN(rrc);
+ ecode += GET(ecode, 1);
+ }
+ while (*ecode == OP_ALT);
+
+ if (*ecode == OP_KET) MRRETURN(MATCH_NOMATCH);
+
+ /* If checking an assertion for a condition, return MATCH_MATCH. */
+
+ if (condassert) RRETURN(MATCH_MATCH);
+
+ /* Continue from after the assertion, updating the offsets high water
+ mark, since extracts may have been taken during the assertion. */
+
+ do ecode += GET(ecode,1); while (*ecode == OP_ALT);
+ ecode += 1 + LINK_SIZE;
+ offset_top = md->end_offset_top;
+ continue;
+
+ /* Negative assertion: all branches must fail to match. Encountering SKIP,
+ PRUNE, or COMMIT means we must assume failure without checking subsequent
+ branches. */
+
+ case OP_ASSERT_NOT:
+ case OP_ASSERTBACK_NOT:
+ if (md->match_function_type == MATCH_CONDASSERT)
+ {
+ condassert = TRUE;
+ md->match_function_type = 0;
+ }
+ else condassert = FALSE;
+
+ do
+ {
+ RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, md, NULL, RM5);
+ if (rrc == MATCH_MATCH || rrc == MATCH_ACCEPT) MRRETURN(MATCH_NOMATCH);
+ if (rrc == MATCH_SKIP || rrc == MATCH_PRUNE || rrc == MATCH_COMMIT)
+ {
+ do ecode += GET(ecode,1); while (*ecode == OP_ALT);
+ break;
+ }
+ if (rrc != MATCH_NOMATCH &&
+ (rrc != MATCH_THEN || md->start_match_ptr != ecode))
+ RRETURN(rrc);
+ ecode += GET(ecode,1);
+ }
+ while (*ecode == OP_ALT);
+
+ if (condassert) RRETURN(MATCH_MATCH); /* Condition assertion */
+
+ ecode += 1 + LINK_SIZE;
+ continue;
+
+ /* Move the subject pointer back. This occurs only at the start of
+ each branch of a lookbehind assertion. If we are too close to the start to
+ move back, this match function fails. When working with UTF-8 we move
+ back a number of characters, not bytes. */
+
+ case OP_REVERSE:
+#ifdef SUPPORT_UTF8
+ if (utf8)
+ {
+ i = GET(ecode, 1);
+ while (i-- > 0)
+ {
+ eptr--;
+ if (eptr < md->start_subject) MRRETURN(MATCH_NOMATCH);
+ BACKCHAR(eptr);
+ }
+ }
+ else
+#endif
+
+ /* No UTF-8 support, or not in UTF-8 mode: count is byte count */
+
+ {
+ eptr -= GET(ecode, 1);
+ if (eptr < md->start_subject) MRRETURN(MATCH_NOMATCH);
+ }
+
+ /* Save the earliest consulted character, then skip to next op code */
+
+ if (eptr < md->start_used_ptr) md->start_used_ptr = eptr;
+ ecode += 1 + LINK_SIZE;
+ break;
+
+ /* The callout item calls an external function, if one is provided, passing
+ details of the match so far. This is mainly for debugging, though the
+ function is able to force a failure. */
+
+ case OP_CALLOUT:
+ if (pcre_callout != NULL)
+ {
+ pcre_callout_block cb;
+ cb.version = 2; /* Version 1 of the callout block */
+ cb.callout_number = ecode[1];
+ cb.offset_vector = md->offset_vector;
+ cb.subject = (PCRE_SPTR)md->start_subject;
+ cb.subject_length = (int)(md->end_subject - md->start_subject);
+ cb.start_match = (int)(mstart - md->start_subject);
+ cb.current_position = (int)(eptr - md->start_subject);
+ cb.pattern_position = GET(ecode, 2);
+ cb.next_item_length = GET(ecode, 2 + LINK_SIZE);
+ cb.capture_top = offset_top/2;
+ cb.capture_last = md->capture_last;
+ cb.callout_data = md->callout_data;
+ cb.mark = markptr;
+ if ((rrc = (*pcre_callout)(&cb)) > 0) MRRETURN(MATCH_NOMATCH);
+ if (rrc < 0) RRETURN(rrc);
+ }
+ ecode += 2 + 2*LINK_SIZE;
+ break;
+
+ /* Recursion either matches the current regex, or some subexpression. The
+ offset data is the offset to the starting bracket from the start of the
+ whole pattern. (This is so that it works from duplicated subpatterns.)
+
+ The state of the capturing groups is preserved over recursion, and
+ re-instated afterwards. We don't know how many are started and not yet
+ finished (offset_top records the completed total) so we just have to save
+ all the potential data. There may be up to 65535 such values, which is too
+ large to put on the stack, but using malloc for small numbers seems
+ expensive. As a compromise, the stack is used when there are no more than
+ REC_STACK_SAVE_MAX values to store; otherwise malloc is used.
+
+ There are also other values that have to be saved. We use a chained
+ sequence of blocks that actually live on the stack. Thanks to Robin Houston
+ for the original version of this logic. It has, however, been hacked around
+ a lot, so he is not to blame for the current way it works. */
+
+ case OP_RECURSE:
+ {
+ recursion_info *ri;
+ int recno;
+
+ callpat = md->start_code + GET(ecode, 1);
+ recno = (callpat == md->start_code)? 0 :
+ GET2(callpat, 1 + LINK_SIZE);
+
+ /* Check for repeating a recursion without advancing the subject pointer.
+ This should catch convoluted mutual recursions. (Some simple cases are
+ caught at compile time.) */
+
+ for (ri = md->recursive; ri != NULL; ri = ri->prevrec)
+ if (recno == ri->group_num && eptr == ri->subject_position)
+ RRETURN(PCRE_ERROR_RECURSELOOP);
+
+ /* Add to "recursing stack" */
+
+ new_recursive.group_num = recno;
+ new_recursive.subject_position = eptr;
+ new_recursive.prevrec = md->recursive;
+ md->recursive = &new_recursive;
+
+ /* Where to continue from afterwards */
+
+ ecode += 1 + LINK_SIZE;
+
+ /* Now save the offset data */
+
+ new_recursive.saved_max = md->offset_end;
+ if (new_recursive.saved_max <= REC_STACK_SAVE_MAX)
+ new_recursive.offset_save = stacksave;
+ else
+ {
+ new_recursive.offset_save =
+ (int *)(pcre_malloc)(new_recursive.saved_max * sizeof(int));
+ if (new_recursive.offset_save == NULL) RRETURN(PCRE_ERROR_NOMEMORY);
+ }
+ memcpy(new_recursive.offset_save, md->offset_vector,
+ new_recursive.saved_max * sizeof(int));
+
+ /* OK, now we can do the recursion. After processing each alternative,
+ restore the offset data. If there were nested recursions, md->recursive
+ might be changed, so reset it before looping. */
+
+ DPRINTF(("Recursing into group %d\n", new_recursive.group_num));
+ cbegroup = (*callpat >= OP_SBRA);
+ do
+ {
+ if (cbegroup) md->match_function_type = MATCH_CBEGROUP;
+ RMATCH(eptr, callpat + _pcre_OP_lengths[*callpat], offset_top,
+ md, eptrb, RM6);
+ memcpy(md->offset_vector, new_recursive.offset_save,
+ new_recursive.saved_max * sizeof(int));
+ if (rrc == MATCH_MATCH || rrc == MATCH_ACCEPT)
+ {
+ DPRINTF(("Recursion matched\n"));
+ md->recursive = new_recursive.prevrec;
+ if (new_recursive.offset_save != stacksave)
+ (pcre_free)(new_recursive.offset_save);
+
+ /* Set where we got to in the subject, and reset the start in case
+ it was changed by \K. This *is* propagated back out of a recursion,
+ for Perl compatibility. */
+
+ eptr = md->end_match_ptr;
+ mstart = md->start_match_ptr;
+ goto RECURSION_MATCHED; /* Exit loop; end processing */
+ }
+ else if (rrc != MATCH_NOMATCH &&
+ (rrc != MATCH_THEN || md->start_match_ptr != ecode))
+ {
+ DPRINTF(("Recursion gave error %d\n", rrc));
+ if (new_recursive.offset_save != stacksave)
+ (pcre_free)(new_recursive.offset_save);
+ RRETURN(rrc);
+ }
+
+ md->recursive = &new_recursive;
+ callpat += GET(callpat, 1);
+ }
+ while (*callpat == OP_ALT);
+
+ DPRINTF(("Recursion didn't match\n"));
+ md->recursive = new_recursive.prevrec;
+ if (new_recursive.offset_save != stacksave)
+ (pcre_free)(new_recursive.offset_save);
+ MRRETURN(MATCH_NOMATCH);
+ }
+
+ RECURSION_MATCHED:
+ break;
+
+ /* An alternation is the end of a branch; scan along to find the end of the
+ bracketed group and go to there. */
+
+ case OP_ALT:
+ do ecode += GET(ecode,1); while (*ecode == OP_ALT);
+ break;
+
+ /* BRAZERO, BRAMINZERO and SKIPZERO occur just before a bracket group,
+ indicating that it may occur zero times. It may repeat infinitely, or not
+ at all - i.e. it could be ()* or ()? or even (){0} in the pattern. Brackets
+ with fixed upper repeat limits are compiled as a number of copies, with the
+ optional ones preceded by BRAZERO or BRAMINZERO. */
+
+ case OP_BRAZERO:
+ next = ecode + 1;
+ RMATCH(eptr, next, offset_top, md, eptrb, RM10);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ do next += GET(next, 1); while (*next == OP_ALT);
+ ecode = next + 1 + LINK_SIZE;
+ break;
+
+ case OP_BRAMINZERO:
+ next = ecode + 1;
+ do next += GET(next, 1); while (*next == OP_ALT);
+ RMATCH(eptr, next + 1+LINK_SIZE, offset_top, md, eptrb, RM11);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ ecode++;
+ break;
+
+ case OP_SKIPZERO:
+ next = ecode+1;
+ do next += GET(next,1); while (*next == OP_ALT);
+ ecode = next + 1 + LINK_SIZE;
+ break;
+
+ /* BRAPOSZERO occurs before a possessive bracket group. Don't do anything
+ here; just jump to the group, with allow_zero set TRUE. */
+
+ case OP_BRAPOSZERO:
+ op = *(++ecode);
+ allow_zero = TRUE;
+ if (op == OP_CBRAPOS || op == OP_SCBRAPOS) goto POSSESSIVE_CAPTURE;
+ goto POSSESSIVE_NON_CAPTURE;
+
+ /* End of a group, repeated or non-repeating. */
+
+ case OP_KET:
+ case OP_KETRMIN:
+ case OP_KETRMAX:
+ case OP_KETRPOS:
+ prev = ecode - GET(ecode, 1);
+
+ /* If this was a group that remembered the subject start, in order to break
+ infinite repeats of empty string matches, retrieve the subject start from
+ the chain. Otherwise, set it NULL. */
+
+ if (*prev >= OP_SBRA || *prev == OP_ONCE)
+ {
+ saved_eptr = eptrb->epb_saved_eptr; /* Value at start of group */
+ eptrb = eptrb->epb_prev; /* Backup to previous group */
+ }
+ else saved_eptr = NULL;
+
+ /* If we are at the end of an assertion group, stop matching and return
+ MATCH_MATCH, but record the current high water mark for use by positive
+ assertions. We also need to record the match start in case it was changed
+ by \K. */
+
+ if (*prev == OP_ASSERT || *prev == OP_ASSERT_NOT ||
+ *prev == OP_ASSERTBACK || *prev == OP_ASSERTBACK_NOT)
+ {
+ md->end_match_ptr = eptr; /* For ONCE */
+ md->end_offset_top = offset_top;
+ md->start_match_ptr = mstart;
+ MRRETURN(MATCH_MATCH); /* Sets md->mark */
+ }
+
+ /* For capturing groups we have to check the group number back at the start
+ and if necessary complete handling an extraction by setting the offsets and
+ bumping the high water mark. Whole-pattern recursion is coded as a recurse
+ into group 0, so it won't be picked up here. Instead, we catch it when the
+ OP_END is reached. Other recursion is handled here. We just have to record
+ the current subject position and start match pointer and give a MATCH
+ return. */
+
+ if (*prev == OP_CBRA || *prev == OP_SCBRA ||
+ *prev == OP_CBRAPOS || *prev == OP_SCBRAPOS)
+ {
+ number = GET2(prev, 1+LINK_SIZE);
+ offset = number << 1;
+
+#ifdef PCRE_DEBUG
+ printf("end bracket %d", number);
+ printf("\n");
+#endif
+
+ /* Handle a recursively called group. */
+
+ if (md->recursive != NULL && md->recursive->group_num == number)
+ {
+ md->end_match_ptr = eptr;
+ md->start_match_ptr = mstart;
+ RRETURN(MATCH_MATCH);
+ }
+
+ /* Deal with capturing */
+
+ md->capture_last = number;
+ if (offset >= md->offset_max) md->offset_overflow = TRUE; else
+ {
+ /* If offset is greater than offset_top, it means that we are
+ "skipping" a capturing group, and that group's offsets must be marked
+ unset. In earlier versions of PCRE, all the offsets were unset at the
+ start of matching, but this doesn't work because atomic groups and
+ assertions can cause a value to be set that should later be unset.
+ Example: matching /(?>(a))b|(a)c/ against "ac". This sets group 1 as
+ part of the atomic group, but this is not on the final matching path,
+ so must be unset when 2 is set. (If there is no group 2, there is no
+ problem, because offset_top will then be 2, indicating no capture.) */
+
+ if (offset > offset_top)
+ {
+ register int *iptr = md->offset_vector + offset_top;
+ register int *iend = md->offset_vector + offset;
+ while (iptr < iend) *iptr++ = -1;
+ }
+
+ /* Now make the extraction */
+
+ md->offset_vector[offset] =
+ md->offset_vector[md->offset_end - number];
+ md->offset_vector[offset+1] = (int)(eptr - md->start_subject);
+ if (offset_top <= offset) offset_top = offset + 2;
+ }
+ }
+
+ /* For an ordinary non-repeating ket, just continue at this level. This
+ also happens for a repeating ket if no characters were matched in the
+ group. This is the forcible breaking of infinite loops as implemented in
+ Perl 5.005. For a non-repeating atomic group, establish a backup point by
+ processing the rest of the pattern at a lower level. If this results in a
+ NOMATCH return, pass MATCH_ONCE back to the original OP_ONCE level, thereby
+ bypassing intermediate backup points, but resetting any captures that
+ happened along the way. */
+
+ if (*ecode == OP_KET || eptr == saved_eptr)
+ {
+ if (*prev == OP_ONCE)
+ {
+ RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, md, eptrb, RM12);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ md->once_target = prev; /* Level at which to change to MATCH_NOMATCH */
+ RRETURN(MATCH_ONCE);
+ }
+ ecode += 1 + LINK_SIZE; /* Carry on at this level */
+ break;
+ }
+
+ /* OP_KETRPOS is a possessive repeating ket. Remember the current position,
+ and return the MATCH_KETRPOS. This makes it possible to do the repeats one
+ at a time from the outer level, thus saving stack. */
+
+ if (*ecode == OP_KETRPOS)
+ {
+ md->end_match_ptr = eptr;
+ md->end_offset_top = offset_top;
+ RRETURN(MATCH_KETRPOS);
+ }
+
+ /* The normal repeating kets try the rest of the pattern or restart from
+ the preceding bracket, in the appropriate order. In the second case, we can
+ use tail recursion to avoid using another stack frame, unless we have an
+ an atomic group or an unlimited repeat of a group that can match an empty
+ string. */
+
+ if (*ecode == OP_KETRMIN)
+ {
+ RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, md, eptrb, RM7);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (*prev == OP_ONCE)
+ {
+ RMATCH(eptr, prev, offset_top, md, eptrb, RM8);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ md->once_target = prev; /* Level at which to change to MATCH_NOMATCH */
+ RRETURN(MATCH_ONCE);
+ }
+ if (*prev >= OP_SBRA) /* Could match an empty string */
+ {
+ md->match_function_type = MATCH_CBEGROUP;
+ RMATCH(eptr, prev, offset_top, md, eptrb, RM50);
+ RRETURN(rrc);
+ }
+ ecode = prev;
+ goto TAIL_RECURSE;
+ }
+ else /* OP_KETRMAX */
+ {
+ if (*prev >= OP_SBRA) md->match_function_type = MATCH_CBEGROUP;
+ RMATCH(eptr, prev, offset_top, md, eptrb, RM13);
+ if (rrc == MATCH_ONCE && md->once_target == prev) rrc = MATCH_NOMATCH;
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (*prev == OP_ONCE)
+ {
+ RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, md, eptrb, RM9);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ md->once_target = prev;
+ RRETURN(MATCH_ONCE);
+ }
+ ecode += 1 + LINK_SIZE;
+ goto TAIL_RECURSE;
+ }
+ /* Control never gets here */
+
+ /* Not multiline mode: start of subject assertion, unless notbol. */
+
+ case OP_CIRC:
+ if (md->notbol && eptr == md->start_subject) MRRETURN(MATCH_NOMATCH);
+
+ /* Start of subject assertion */
+
+ case OP_SOD:
+ if (eptr != md->start_subject) MRRETURN(MATCH_NOMATCH);
+ ecode++;
+ break;
+
+ /* Multiline mode: start of subject unless notbol, or after any newline. */
+
+ case OP_CIRCM:
+ if (md->notbol && eptr == md->start_subject) MRRETURN(MATCH_NOMATCH);
+ if (eptr != md->start_subject &&
+ (eptr == md->end_subject || !WAS_NEWLINE(eptr)))
+ MRRETURN(MATCH_NOMATCH);
+ ecode++;
+ break;
+
+ /* Start of match assertion */
+
+ case OP_SOM:
+ if (eptr != md->start_subject + md->start_offset) MRRETURN(MATCH_NOMATCH);
+ ecode++;
+ break;
+
+ /* Reset the start of match point */
+
+ case OP_SET_SOM:
+ mstart = eptr;
+ ecode++;
+ break;
+
+ /* Multiline mode: assert before any newline, or before end of subject
+ unless noteol is set. */
+
+ case OP_DOLLM:
+ if (eptr < md->end_subject)
+ { if (!IS_NEWLINE(eptr)) MRRETURN(MATCH_NOMATCH); }
+ else
+ {
+ if (md->noteol) MRRETURN(MATCH_NOMATCH);
+ SCHECK_PARTIAL();
+ }
+ ecode++;
+ break;
+
+ /* Not multiline mode: assert before a terminating newline or before end of
+ subject unless noteol is set. */
+
+ case OP_DOLL:
+ if (md->noteol) MRRETURN(MATCH_NOMATCH);
+ if (!md->endonly) goto ASSERT_NL_OR_EOS;
+
+ /* ... else fall through for endonly */
+
+ /* End of subject assertion (\z) */
+
+ case OP_EOD:
+ if (eptr < md->end_subject) MRRETURN(MATCH_NOMATCH);
+ SCHECK_PARTIAL();
+ ecode++;
+ break;
+
+ /* End of subject or ending \n assertion (\Z) */
+
+ case OP_EODN:
+ ASSERT_NL_OR_EOS:
+ if (eptr < md->end_subject &&
+ (!IS_NEWLINE(eptr) || eptr != md->end_subject - md->nllen))
+ MRRETURN(MATCH_NOMATCH);
+
+ /* Either at end of string or \n before end. */
+
+ SCHECK_PARTIAL();
+ ecode++;
+ break;
+
+ /* Word boundary assertions */
+
+ case OP_NOT_WORD_BOUNDARY:
+ case OP_WORD_BOUNDARY:
+ {
+
+ /* Find out if the previous and current characters are "word" characters.
+ It takes a bit more work in UTF-8 mode. Characters > 255 are assumed to
+ be "non-word" characters. Remember the earliest consulted character for
+ partial matching. */
+
+#ifdef SUPPORT_UTF8
+ if (utf8)
+ {
+ /* Get status of previous character */
+
+ if (eptr == md->start_subject) prev_is_word = FALSE; else
+ {
+ USPTR lastptr = eptr - 1;
+ while((*lastptr & 0xc0) == 0x80) lastptr--;
+ if (lastptr < md->start_used_ptr) md->start_used_ptr = lastptr;
+ GETCHAR(c, lastptr);
+#ifdef SUPPORT_UCP
+ if (md->use_ucp)
+ {
+ if (c == '_') prev_is_word = TRUE; else
+ {
+ int cat = UCD_CATEGORY(c);
+ prev_is_word = (cat == ucp_L || cat == ucp_N);
+ }
+ }
+ else
+#endif
+ prev_is_word = c < 256 && (md->ctypes[c] & ctype_word) != 0;
+ }
+
+ /* Get status of next character */
+
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ cur_is_word = FALSE;
+ }
+ else
+ {
+ GETCHAR(c, eptr);
+#ifdef SUPPORT_UCP
+ if (md->use_ucp)
+ {
+ if (c == '_') cur_is_word = TRUE; else
+ {
+ int cat = UCD_CATEGORY(c);
+ cur_is_word = (cat == ucp_L || cat == ucp_N);
+ }
+ }
+ else
+#endif
+ cur_is_word = c < 256 && (md->ctypes[c] & ctype_word) != 0;
+ }
+ }
+ else
+#endif
+
+ /* Not in UTF-8 mode, but we may still have PCRE_UCP set, and for
+ consistency with the behaviour of \w we do use it in this case. */
+
+ {
+ /* Get status of previous character */
+
+ if (eptr == md->start_subject) prev_is_word = FALSE; else
+ {
+ if (eptr <= md->start_used_ptr) md->start_used_ptr = eptr - 1;
+#ifdef SUPPORT_UCP
+ if (md->use_ucp)
+ {
+ c = eptr[-1];
+ if (c == '_') prev_is_word = TRUE; else
+ {
+ int cat = UCD_CATEGORY(c);
+ prev_is_word = (cat == ucp_L || cat == ucp_N);
+ }
+ }
+ else
+#endif
+ prev_is_word = ((md->ctypes[eptr[-1]] & ctype_word) != 0);
+ }
+
+ /* Get status of next character */
+
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ cur_is_word = FALSE;
+ }
+ else
+#ifdef SUPPORT_UCP
+ if (md->use_ucp)
+ {
+ c = *eptr;
+ if (c == '_') cur_is_word = TRUE; else
+ {
+ int cat = UCD_CATEGORY(c);
+ cur_is_word = (cat == ucp_L || cat == ucp_N);
+ }
+ }
+ else
+#endif
+ cur_is_word = ((md->ctypes[*eptr] & ctype_word) != 0);
+ }
+
+ /* Now see if the situation is what we want */
+
+ if ((*ecode++ == OP_WORD_BOUNDARY)?
+ cur_is_word == prev_is_word : cur_is_word != prev_is_word)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ /* Match a single character type; inline for speed */
+
+ case OP_ANY:
+ if (IS_NEWLINE(eptr)) MRRETURN(MATCH_NOMATCH);
+ /* Fall through */
+
+ case OP_ALLANY:
+ if (eptr >= md->end_subject) /* DO NOT merge the eptr++ here; it must */
+ { /* not be updated before SCHECK_PARTIAL. */
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ eptr++;
+ if (utf8) while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++;
+ ecode++;
+ break;
+
+ /* Match a single byte, even in UTF-8 mode. This opcode really does match
+ any byte, even newline, independent of the setting of PCRE_DOTALL. */
+
+ case OP_ANYBYTE:
+ if (eptr >= md->end_subject) /* DO NOT merge the eptr++ here; it must */
+ { /* not be updated before SCHECK_PARTIAL. */
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ eptr++;
+ ecode++;
+ break;
+
+ case OP_NOT_DIGIT:
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if (
+#ifdef SUPPORT_UTF8
+ c < 256 &&
+#endif
+ (md->ctypes[c] & ctype_digit) != 0
+ )
+ MRRETURN(MATCH_NOMATCH);
+ ecode++;
+ break;
+
+ case OP_DIGIT:
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if (
+#ifdef SUPPORT_UTF8
+ c >= 256 ||
+#endif
+ (md->ctypes[c] & ctype_digit) == 0
+ )
+ MRRETURN(MATCH_NOMATCH);
+ ecode++;
+ break;
+
+ case OP_NOT_WHITESPACE:
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if (
+#ifdef SUPPORT_UTF8
+ c < 256 &&
+#endif
+ (md->ctypes[c] & ctype_space) != 0
+ )
+ MRRETURN(MATCH_NOMATCH);
+ ecode++;
+ break;
+
+ case OP_WHITESPACE:
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if (
+#ifdef SUPPORT_UTF8
+ c >= 256 ||
+#endif
+ (md->ctypes[c] & ctype_space) == 0
+ )
+ MRRETURN(MATCH_NOMATCH);
+ ecode++;
+ break;
+
+ case OP_NOT_WORDCHAR:
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if (
+#ifdef SUPPORT_UTF8
+ c < 256 &&
+#endif
+ (md->ctypes[c] & ctype_word) != 0
+ )
+ MRRETURN(MATCH_NOMATCH);
+ ecode++;
+ break;
+
+ case OP_WORDCHAR:
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if (
+#ifdef SUPPORT_UTF8
+ c >= 256 ||
+#endif
+ (md->ctypes[c] & ctype_word) == 0
+ )
+ MRRETURN(MATCH_NOMATCH);
+ ecode++;
+ break;
+
+ case OP_ANYNL:
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ switch(c)
+ {
+ default: MRRETURN(MATCH_NOMATCH);
+
+ case 0x000d:
+ if (eptr < md->end_subject && *eptr == 0x0a) eptr++;
+ break;
+
+ case 0x000a:
+ break;
+
+ case 0x000b:
+ case 0x000c:
+ case 0x0085:
+ case 0x2028:
+ case 0x2029:
+ if (md->bsr_anycrlf) MRRETURN(MATCH_NOMATCH);
+ break;
+ }
+ ecode++;
+ break;
+
+ case OP_NOT_HSPACE:
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ switch(c)
+ {
+ default: break;
+ case 0x09: /* HT */
+ case 0x20: /* SPACE */
+ case 0xa0: /* NBSP */
+ case 0x1680: /* OGHAM SPACE MARK */
+ case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */
+ case 0x2000: /* EN QUAD */
+ case 0x2001: /* EM QUAD */
+ case 0x2002: /* EN SPACE */
+ case 0x2003: /* EM SPACE */
+ case 0x2004: /* THREE-PER-EM SPACE */
+ case 0x2005: /* FOUR-PER-EM SPACE */
+ case 0x2006: /* SIX-PER-EM SPACE */
+ case 0x2007: /* FIGURE SPACE */
+ case 0x2008: /* PUNCTUATION SPACE */
+ case 0x2009: /* THIN SPACE */
+ case 0x200A: /* HAIR SPACE */
+ case 0x202f: /* NARROW NO-BREAK SPACE */
+ case 0x205f: /* MEDIUM MATHEMATICAL SPACE */
+ case 0x3000: /* IDEOGRAPHIC SPACE */
+ MRRETURN(MATCH_NOMATCH);
+ }
+ ecode++;
+ break;
+
+ case OP_HSPACE:
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ switch(c)
+ {
+ default: MRRETURN(MATCH_NOMATCH);
+ case 0x09: /* HT */
+ case 0x20: /* SPACE */
+ case 0xa0: /* NBSP */
+ case 0x1680: /* OGHAM SPACE MARK */
+ case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */
+ case 0x2000: /* EN QUAD */
+ case 0x2001: /* EM QUAD */
+ case 0x2002: /* EN SPACE */
+ case 0x2003: /* EM SPACE */
+ case 0x2004: /* THREE-PER-EM SPACE */
+ case 0x2005: /* FOUR-PER-EM SPACE */
+ case 0x2006: /* SIX-PER-EM SPACE */
+ case 0x2007: /* FIGURE SPACE */
+ case 0x2008: /* PUNCTUATION SPACE */
+ case 0x2009: /* THIN SPACE */
+ case 0x200A: /* HAIR SPACE */
+ case 0x202f: /* NARROW NO-BREAK SPACE */
+ case 0x205f: /* MEDIUM MATHEMATICAL SPACE */
+ case 0x3000: /* IDEOGRAPHIC SPACE */
+ break;
+ }
+ ecode++;
+ break;
+
+ case OP_NOT_VSPACE:
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ switch(c)
+ {
+ default: break;
+ case 0x0a: /* LF */
+ case 0x0b: /* VT */
+ case 0x0c: /* FF */
+ case 0x0d: /* CR */
+ case 0x85: /* NEL */
+ case 0x2028: /* LINE SEPARATOR */
+ case 0x2029: /* PARAGRAPH SEPARATOR */
+ MRRETURN(MATCH_NOMATCH);
+ }
+ ecode++;
+ break;
+
+ case OP_VSPACE:
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ switch(c)
+ {
+ default: MRRETURN(MATCH_NOMATCH);
+ case 0x0a: /* LF */
+ case 0x0b: /* VT */
+ case 0x0c: /* FF */
+ case 0x0d: /* CR */
+ case 0x85: /* NEL */
+ case 0x2028: /* LINE SEPARATOR */
+ case 0x2029: /* PARAGRAPH SEPARATOR */
+ break;
+ }
+ ecode++;
+ break;
+
+#ifdef SUPPORT_UCP
+ /* Check the next character by Unicode property. We will get here only
+ if the support is in the binary; otherwise a compile-time error occurs. */
+
+ case OP_PROP:
+ case OP_NOTPROP:
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ {
+ const ucd_record *prop = GET_UCD(c);
+
+ switch(ecode[1])
+ {
+ case PT_ANY:
+ if (op == OP_NOTPROP) MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case PT_LAMP:
+ if ((prop->chartype == ucp_Lu ||
+ prop->chartype == ucp_Ll ||
+ prop->chartype == ucp_Lt) == (op == OP_NOTPROP))
+ MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case PT_GC:
+ if ((ecode[2] != _pcre_ucp_gentype[prop->chartype]) == (op == OP_PROP))
+ MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case PT_PC:
+ if ((ecode[2] != prop->chartype) == (op == OP_PROP))
+ MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case PT_SC:
+ if ((ecode[2] != prop->script) == (op == OP_PROP))
+ MRRETURN(MATCH_NOMATCH);
+ break;
+
+ /* These are specials */
+
+ case PT_ALNUM:
+ if ((_pcre_ucp_gentype[prop->chartype] == ucp_L ||
+ _pcre_ucp_gentype[prop->chartype] == ucp_N) == (op == OP_NOTPROP))
+ MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case PT_SPACE: /* Perl space */
+ if ((_pcre_ucp_gentype[prop->chartype] == ucp_Z ||
+ c == CHAR_HT || c == CHAR_NL || c == CHAR_FF || c == CHAR_CR)
+ == (op == OP_NOTPROP))
+ MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case PT_PXSPACE: /* POSIX space */
+ if ((_pcre_ucp_gentype[prop->chartype] == ucp_Z ||
+ c == CHAR_HT || c == CHAR_NL || c == CHAR_VT ||
+ c == CHAR_FF || c == CHAR_CR)
+ == (op == OP_NOTPROP))
+ MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case PT_WORD:
+ if ((_pcre_ucp_gentype[prop->chartype] == ucp_L ||
+ _pcre_ucp_gentype[prop->chartype] == ucp_N ||
+ c == CHAR_UNDERSCORE) == (op == OP_NOTPROP))
+ MRRETURN(MATCH_NOMATCH);
+ break;
+
+ /* This should never occur */
+
+ default:
+ RRETURN(PCRE_ERROR_INTERNAL);
+ }
+
+ ecode += 3;
+ }
+ break;
+
+ /* Match an extended Unicode sequence. We will get here only if the support
+ is in the binary; otherwise a compile-time error occurs. */
+
+ case OP_EXTUNI:
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if (UCD_CATEGORY(c) == ucp_M) MRRETURN(MATCH_NOMATCH);
+ while (eptr < md->end_subject)
+ {
+ int len = 1;
+ if (!utf8) c = *eptr; else { GETCHARLEN(c, eptr, len); }
+ if (UCD_CATEGORY(c) != ucp_M) break;
+ eptr += len;
+ }
+ ecode++;
+ break;
+#endif
+
+
+ /* Match a back reference, possibly repeatedly. Look past the end of the
+ item to see if there is repeat information following. The code is similar
+ to that for character classes, but repeated for efficiency. Then obey
+ similar code to character type repeats - written out again for speed.
+ However, if the referenced string is the empty string, always treat
+ it as matched, any number of times (otherwise there could be infinite
+ loops). */
+
+ case OP_REF:
+ case OP_REFI:
+ caseless = op == OP_REFI;
+ offset = GET2(ecode, 1) << 1; /* Doubled ref number */
+ ecode += 3;
+
+ /* If the reference is unset, there are two possibilities:
+
+ (a) In the default, Perl-compatible state, set the length negative;
+ this ensures that every attempt at a match fails. We can't just fail
+ here, because of the possibility of quantifiers with zero minima.
+
+ (b) If the JavaScript compatibility flag is set, set the length to zero
+ so that the back reference matches an empty string.
+
+ Otherwise, set the length to the length of what was matched by the
+ referenced subpattern. */
+
+ if (offset >= offset_top || md->offset_vector[offset] < 0)
+ length = (md->jscript_compat)? 0 : -1;
+ else
+ length = md->offset_vector[offset+1] - md->offset_vector[offset];
+
+ /* Set up for repetition, or handle the non-repeated case */
+
+ switch (*ecode)
+ {
+ case OP_CRSTAR:
+ case OP_CRMINSTAR:
+ case OP_CRPLUS:
+ case OP_CRMINPLUS:
+ case OP_CRQUERY:
+ case OP_CRMINQUERY:
+ c = *ecode++ - OP_CRSTAR;
+ minimize = (c & 1) != 0;
+ min = rep_min[c]; /* Pick up values from tables; */
+ max = rep_max[c]; /* zero for max => infinity */
+ if (max == 0) max = INT_MAX;
+ break;
+
+ case OP_CRRANGE:
+ case OP_CRMINRANGE:
+ minimize = (*ecode == OP_CRMINRANGE);
+ min = GET2(ecode, 1);
+ max = GET2(ecode, 3);
+ if (max == 0) max = INT_MAX;
+ ecode += 5;
+ break;
+
+ default: /* No repeat follows */
+ if ((length = match_ref(offset, eptr, length, md, caseless)) < 0)
+ {
+ CHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ eptr += length;
+ continue; /* With the main loop */
+ }
+
+ /* Handle repeated back references. If the length of the reference is
+ zero, just continue with the main loop. */
+
+ if (length == 0) continue;
+
+ /* First, ensure the minimum number of matches are present. We get back
+ the length of the reference string explicitly rather than passing the
+ address of eptr, so that eptr can be a register variable. */
+
+ for (i = 1; i <= min; i++)
+ {
+ int slength;
+ if ((slength = match_ref(offset, eptr, length, md, caseless)) < 0)
+ {
+ CHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ eptr += slength;
+ }
+
+ /* If min = max, continue at the same level without recursion.
+ They are not both allowed to be zero. */
+
+ if (min == max) continue;
+
+ /* If minimizing, keep trying and advancing the pointer */
+
+ if (minimize)
+ {
+ for (fi = min;; fi++)
+ {
+ int slength;
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM14);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if ((slength = match_ref(offset, eptr, length, md, caseless)) < 0)
+ {
+ CHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ eptr += slength;
+ }
+ /* Control never gets here */
+ }
+
+ /* If maximizing, find the longest string and work backwards */
+
+ else
+ {
+ pp = eptr;
+ for (i = min; i < max; i++)
+ {
+ int slength;
+ if ((slength = match_ref(offset, eptr, length, md, caseless)) < 0)
+ {
+ CHECK_PARTIAL();
+ break;
+ }
+ eptr += slength;
+ }
+ while (eptr >= pp)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM15);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ eptr -= length;
+ }
+ MRRETURN(MATCH_NOMATCH);
+ }
+ /* Control never gets here */
+
+ /* Match a bit-mapped character class, possibly repeatedly. This op code is
+ used when all the characters in the class have values in the range 0-255,
+ and either the matching is caseful, or the characters are in the range
+ 0-127 when UTF-8 processing is enabled. The only difference between
+ OP_CLASS and OP_NCLASS occurs when a data character outside the range is
+ encountered.
+
+ First, look past the end of the item to see if there is repeat information
+ following. Then obey similar code to character type repeats - written out
+ again for speed. */
+
+ case OP_NCLASS:
+ case OP_CLASS:
+ {
+ data = ecode + 1; /* Save for matching */
+ ecode += 33; /* Advance past the item */
+
+ switch (*ecode)
+ {
+ case OP_CRSTAR:
+ case OP_CRMINSTAR:
+ case OP_CRPLUS:
+ case OP_CRMINPLUS:
+ case OP_CRQUERY:
+ case OP_CRMINQUERY:
+ c = *ecode++ - OP_CRSTAR;
+ minimize = (c & 1) != 0;
+ min = rep_min[c]; /* Pick up values from tables; */
+ max = rep_max[c]; /* zero for max => infinity */
+ if (max == 0) max = INT_MAX;
+ break;
+
+ case OP_CRRANGE:
+ case OP_CRMINRANGE:
+ minimize = (*ecode == OP_CRMINRANGE);
+ min = GET2(ecode, 1);
+ max = GET2(ecode, 3);
+ if (max == 0) max = INT_MAX;
+ ecode += 5;
+ break;
+
+ default: /* No repeat follows */
+ min = max = 1;
+ break;
+ }
+
+ /* First, ensure the minimum number of matches are present. */
+
+#ifdef SUPPORT_UTF8
+ /* UTF-8 mode */
+ if (utf8)
+ {
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINC(c, eptr);
+ if (c > 255)
+ {
+ if (op == OP_CLASS) MRRETURN(MATCH_NOMATCH);
+ }
+ else
+ {
+ if ((data[c/8] & (1 << (c&7))) == 0) MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ }
+ else
+#endif
+ /* Not UTF-8 mode */
+ {
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ c = *eptr++;
+ if ((data[c/8] & (1 << (c&7))) == 0) MRRETURN(MATCH_NOMATCH);
+ }
+ }
+
+ /* If max == min we can continue with the main loop without the
+ need to recurse. */
+
+ if (min == max) continue;
+
+ /* If minimizing, keep testing the rest of the expression and advancing
+ the pointer while it matches the class. */
+
+ if (minimize)
+ {
+#ifdef SUPPORT_UTF8
+ /* UTF-8 mode */
+ if (utf8)
+ {
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM16);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINC(c, eptr);
+ if (c > 255)
+ {
+ if (op == OP_CLASS) MRRETURN(MATCH_NOMATCH);
+ }
+ else
+ {
+ if ((data[c/8] & (1 << (c&7))) == 0) MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ }
+ else
+#endif
+ /* Not UTF-8 mode */
+ {
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM17);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ c = *eptr++;
+ if ((data[c/8] & (1 << (c&7))) == 0) MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ /* Control never gets here */
+ }
+
+ /* If maximizing, find the longest possible run, then work backwards. */
+
+ else
+ {
+ pp = eptr;
+
+#ifdef SUPPORT_UTF8
+ /* UTF-8 mode */
+ if (utf8)
+ {
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLEN(c, eptr, len);
+ if (c > 255)
+ {
+ if (op == OP_CLASS) break;
+ }
+ else
+ {
+ if ((data[c/8] & (1 << (c&7))) == 0) break;
+ }
+ eptr += len;
+ }
+ for (;;)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM18);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (eptr-- == pp) break; /* Stop if tried at original pos */
+ BACKCHAR(eptr);
+ }
+ }
+ else
+#endif
+ /* Not UTF-8 mode */
+ {
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ c = *eptr;
+ if ((data[c/8] & (1 << (c&7))) == 0) break;
+ eptr++;
+ }
+ while (eptr >= pp)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM19);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ eptr--;
+ }
+ }
+
+ MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ /* Control never gets here */
+
+
+ /* Match an extended character class. This opcode is encountered only
+ when UTF-8 mode mode is supported. Nevertheless, we may not be in UTF-8
+ mode, because Unicode properties are supported in non-UTF-8 mode. */
+
+#ifdef SUPPORT_UTF8
+ case OP_XCLASS:
+ {
+ data = ecode + 1 + LINK_SIZE; /* Save for matching */
+ ecode += GET(ecode, 1); /* Advance past the item */
+
+ switch (*ecode)
+ {
+ case OP_CRSTAR:
+ case OP_CRMINSTAR:
+ case OP_CRPLUS:
+ case OP_CRMINPLUS:
+ case OP_CRQUERY:
+ case OP_CRMINQUERY:
+ c = *ecode++ - OP_CRSTAR;
+ minimize = (c & 1) != 0;
+ min = rep_min[c]; /* Pick up values from tables; */
+ max = rep_max[c]; /* zero for max => infinity */
+ if (max == 0) max = INT_MAX;
+ break;
+
+ case OP_CRRANGE:
+ case OP_CRMINRANGE:
+ minimize = (*ecode == OP_CRMINRANGE);
+ min = GET2(ecode, 1);
+ max = GET2(ecode, 3);
+ if (max == 0) max = INT_MAX;
+ ecode += 5;
+ break;
+
+ default: /* No repeat follows */
+ min = max = 1;
+ break;
+ }
+
+ /* First, ensure the minimum number of matches are present. */
+
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if (!_pcre_xclass(c, data)) MRRETURN(MATCH_NOMATCH);
+ }
+
+ /* If max == min we can continue with the main loop without the
+ need to recurse. */
+
+ if (min == max) continue;
+
+ /* If minimizing, keep testing the rest of the expression and advancing
+ the pointer while it matches the class. */
+
+ if (minimize)
+ {
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM20);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if (!_pcre_xclass(c, data)) MRRETURN(MATCH_NOMATCH);
+ }
+ /* Control never gets here */
+ }
+
+ /* If maximizing, find the longest possible run, then work backwards. */
+
+ else
+ {
+ pp = eptr;
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLENTEST(c, eptr, len);
+ if (!_pcre_xclass(c, data)) break;
+ eptr += len;
+ }
+ for(;;)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM21);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (eptr-- == pp) break; /* Stop if tried at original pos */
+ if (utf8) BACKCHAR(eptr);
+ }
+ MRRETURN(MATCH_NOMATCH);
+ }
+
+ /* Control never gets here */
+ }
+#endif /* End of XCLASS */
+
+ /* Match a single character, casefully */
+
+ case OP_CHAR:
+#ifdef SUPPORT_UTF8
+ if (utf8)
+ {
+ length = 1;
+ ecode++;
+ GETCHARLEN(fc, ecode, length);
+ if (length > md->end_subject - eptr)
+ {
+ CHECK_PARTIAL(); /* Not SCHECK_PARTIAL() */
+ MRRETURN(MATCH_NOMATCH);
+ }
+ while (length-- > 0) if (*ecode++ != *eptr++) MRRETURN(MATCH_NOMATCH);
+ }
+ else
+#endif
+
+ /* Non-UTF-8 mode */
+ {
+ if (md->end_subject - eptr < 1)
+ {
+ SCHECK_PARTIAL(); /* This one can use SCHECK_PARTIAL() */
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (ecode[1] != *eptr++) MRRETURN(MATCH_NOMATCH);
+ ecode += 2;
+ }
+ break;
+
+ /* Match a single character, caselessly */
+
+ case OP_CHARI:
+#ifdef SUPPORT_UTF8
+ if (utf8)
+ {
+ length = 1;
+ ecode++;
+ GETCHARLEN(fc, ecode, length);
+
+ if (length > md->end_subject - eptr)
+ {
+ CHECK_PARTIAL(); /* Not SCHECK_PARTIAL() */
+ MRRETURN(MATCH_NOMATCH);
+ }
+
+ /* If the pattern character's value is < 128, we have only one byte, and
+ can use the fast lookup table. */
+
+ if (fc < 128)
+ {
+ if (md->lcc[*ecode++] != md->lcc[*eptr++]) MRRETURN(MATCH_NOMATCH);
+ }
+
+ /* Otherwise we must pick up the subject character */
+
+ else
+ {
+ unsigned int dc;
+ GETCHARINC(dc, eptr);
+ ecode += length;
+
+ /* If we have Unicode property support, we can use it to test the other
+ case of the character, if there is one. */
+
+ if (fc != dc)
+ {
+#ifdef SUPPORT_UCP
+ if (dc != UCD_OTHERCASE(fc))
+#endif
+ MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ }
+ else
+#endif /* SUPPORT_UTF8 */
+
+ /* Non-UTF-8 mode */
+ {
+ if (md->end_subject - eptr < 1)
+ {
+ SCHECK_PARTIAL(); /* This one can use SCHECK_PARTIAL() */
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (md->lcc[ecode[1]] != md->lcc[*eptr++]) MRRETURN(MATCH_NOMATCH);
+ ecode += 2;
+ }
+ break;
+
+ /* Match a single character repeatedly. */
+
+ case OP_EXACT:
+ case OP_EXACTI:
+ min = max = GET2(ecode, 1);
+ ecode += 3;
+ goto REPEATCHAR;
+
+ case OP_POSUPTO:
+ case OP_POSUPTOI:
+ possessive = TRUE;
+ /* Fall through */
+
+ case OP_UPTO:
+ case OP_UPTOI:
+ case OP_MINUPTO:
+ case OP_MINUPTOI:
+ min = 0;
+ max = GET2(ecode, 1);
+ minimize = *ecode == OP_MINUPTO || *ecode == OP_MINUPTOI;
+ ecode += 3;
+ goto REPEATCHAR;
+
+ case OP_POSSTAR:
+ case OP_POSSTARI:
+ possessive = TRUE;
+ min = 0;
+ max = INT_MAX;
+ ecode++;
+ goto REPEATCHAR;
+
+ case OP_POSPLUS:
+ case OP_POSPLUSI:
+ possessive = TRUE;
+ min = 1;
+ max = INT_MAX;
+ ecode++;
+ goto REPEATCHAR;
+
+ case OP_POSQUERY:
+ case OP_POSQUERYI:
+ possessive = TRUE;
+ min = 0;
+ max = 1;
+ ecode++;
+ goto REPEATCHAR;
+
+ case OP_STAR:
+ case OP_STARI:
+ case OP_MINSTAR:
+ case OP_MINSTARI:
+ case OP_PLUS:
+ case OP_PLUSI:
+ case OP_MINPLUS:
+ case OP_MINPLUSI:
+ case OP_QUERY:
+ case OP_QUERYI:
+ case OP_MINQUERY:
+ case OP_MINQUERYI:
+ c = *ecode++ - ((op < OP_STARI)? OP_STAR : OP_STARI);
+ minimize = (c & 1) != 0;
+ min = rep_min[c]; /* Pick up values from tables; */
+ max = rep_max[c]; /* zero for max => infinity */
+ if (max == 0) max = INT_MAX;
+
+ /* Common code for all repeated single-character matches. */
+
+ REPEATCHAR:
+#ifdef SUPPORT_UTF8
+ if (utf8)
+ {
+ length = 1;
+ charptr = ecode;
+ GETCHARLEN(fc, ecode, length);
+ ecode += length;
+
+ /* Handle multibyte character matching specially here. There is
+ support for caseless matching if UCP support is present. */
+
+ if (length > 1)
+ {
+#ifdef SUPPORT_UCP
+ unsigned int othercase;
+ if (op >= OP_STARI && /* Caseless */
+ (othercase = UCD_OTHERCASE(fc)) != fc)
+ oclength = _pcre_ord2utf8(othercase, occhars);
+ else oclength = 0;
+#endif /* SUPPORT_UCP */
+
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr <= md->end_subject - length &&
+ memcmp(eptr, charptr, length) == 0) eptr += length;
+#ifdef SUPPORT_UCP
+ else if (oclength > 0 &&
+ eptr <= md->end_subject - oclength &&
+ memcmp(eptr, occhars, oclength) == 0) eptr += oclength;
+#endif /* SUPPORT_UCP */
+ else
+ {
+ CHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ }
+
+ if (min == max) continue;
+
+ if (minimize)
+ {
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM22);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr <= md->end_subject - length &&
+ memcmp(eptr, charptr, length) == 0) eptr += length;
+#ifdef SUPPORT_UCP
+ else if (oclength > 0 &&
+ eptr <= md->end_subject - oclength &&
+ memcmp(eptr, occhars, oclength) == 0) eptr += oclength;
+#endif /* SUPPORT_UCP */
+ else
+ {
+ CHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ /* Control never gets here */
+ }
+
+ else /* Maximize */
+ {
+ pp = eptr;
+ for (i = min; i < max; i++)
+ {
+ if (eptr <= md->end_subject - length &&
+ memcmp(eptr, charptr, length) == 0) eptr += length;
+#ifdef SUPPORT_UCP
+ else if (oclength > 0 &&
+ eptr <= md->end_subject - oclength &&
+ memcmp(eptr, occhars, oclength) == 0) eptr += oclength;
+#endif /* SUPPORT_UCP */
+ else
+ {
+ CHECK_PARTIAL();
+ break;
+ }
+ }
+
+ if (possessive) continue;
+
+ for(;;)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM23);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (eptr == pp) { MRRETURN(MATCH_NOMATCH); }
+#ifdef SUPPORT_UCP
+ eptr--;
+ BACKCHAR(eptr);
+#else /* without SUPPORT_UCP */
+ eptr -= length;
+#endif /* SUPPORT_UCP */
+ }
+ }
+ /* Control never gets here */
+ }
+
+ /* If the length of a UTF-8 character is 1, we fall through here, and
+ obey the code as for non-UTF-8 characters below, though in this case the
+ value of fc will always be < 128. */
+ }
+ else
+#endif /* SUPPORT_UTF8 */
+
+ /* When not in UTF-8 mode, load a single-byte character. */
+
+ fc = *ecode++;
+
+ /* The value of fc at this point is always less than 256, though we may or
+ may not be in UTF-8 mode. The code is duplicated for the caseless and
+ caseful cases, for speed, since matching characters is likely to be quite
+ common. First, ensure the minimum number of matches are present. If min =
+ max, continue at the same level without recursing. Otherwise, if
+ minimizing, keep trying the rest of the expression and advancing one
+ matching character if failing, up to the maximum. Alternatively, if
+ maximizing, find the maximum number of characters and work backwards. */
+
+ DPRINTF(("matching %c{%d,%d} against subject %.*s\n", fc, min, max,
+ max, eptr));
+
+ if (op >= OP_STARI) /* Caseless */
+ {
+ fc = md->lcc[fc];
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (fc != md->lcc[*eptr++]) MRRETURN(MATCH_NOMATCH);
+ }
+ if (min == max) continue;
+ if (minimize)
+ {
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM24);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (fc != md->lcc[*eptr++]) MRRETURN(MATCH_NOMATCH);
+ }
+ /* Control never gets here */
+ }
+ else /* Maximize */
+ {
+ pp = eptr;
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ if (fc != md->lcc[*eptr]) break;
+ eptr++;
+ }
+
+ if (possessive) continue;
+
+ while (eptr >= pp)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM25);
+ eptr--;
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ }
+ MRRETURN(MATCH_NOMATCH);
+ }
+ /* Control never gets here */
+ }
+
+ /* Caseful comparisons (includes all multi-byte characters) */
+
+ else
+ {
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (fc != *eptr++) MRRETURN(MATCH_NOMATCH);
+ }
+
+ if (min == max) continue;
+
+ if (minimize)
+ {
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM26);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (fc != *eptr++) MRRETURN(MATCH_NOMATCH);
+ }
+ /* Control never gets here */
+ }
+ else /* Maximize */
+ {
+ pp = eptr;
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ if (fc != *eptr) break;
+ eptr++;
+ }
+ if (possessive) continue;
+
+ while (eptr >= pp)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM27);
+ eptr--;
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ }
+ MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ /* Control never gets here */
+
+ /* Match a negated single one-byte character. The character we are
+ checking can be multibyte. */
+
+ case OP_NOT:
+ case OP_NOTI:
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ ecode++;
+ GETCHARINCTEST(c, eptr);
+ if (op == OP_NOTI) /* The caseless case */
+ {
+#ifdef SUPPORT_UTF8
+ if (c < 256)
+#endif
+ c = md->lcc[c];
+ if (md->lcc[*ecode++] == c) MRRETURN(MATCH_NOMATCH);
+ }
+ else /* Caseful */
+ {
+ if (*ecode++ == c) MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ /* Match a negated single one-byte character repeatedly. This is almost a
+ repeat of the code for a repeated single character, but I haven't found a
+ nice way of commoning these up that doesn't require a test of the
+ positive/negative option for each character match. Maybe that wouldn't add
+ very much to the time taken, but character matching *is* what this is all
+ about... */
+
+ case OP_NOTEXACT:
+ case OP_NOTEXACTI:
+ min = max = GET2(ecode, 1);
+ ecode += 3;
+ goto REPEATNOTCHAR;
+
+ case OP_NOTUPTO:
+ case OP_NOTUPTOI:
+ case OP_NOTMINUPTO:
+ case OP_NOTMINUPTOI:
+ min = 0;
+ max = GET2(ecode, 1);
+ minimize = *ecode == OP_NOTMINUPTO || *ecode == OP_NOTMINUPTOI;
+ ecode += 3;
+ goto REPEATNOTCHAR;
+
+ case OP_NOTPOSSTAR:
+ case OP_NOTPOSSTARI:
+ possessive = TRUE;
+ min = 0;
+ max = INT_MAX;
+ ecode++;
+ goto REPEATNOTCHAR;
+
+ case OP_NOTPOSPLUS:
+ case OP_NOTPOSPLUSI:
+ possessive = TRUE;
+ min = 1;
+ max = INT_MAX;
+ ecode++;
+ goto REPEATNOTCHAR;
+
+ case OP_NOTPOSQUERY:
+ case OP_NOTPOSQUERYI:
+ possessive = TRUE;
+ min = 0;
+ max = 1;
+ ecode++;
+ goto REPEATNOTCHAR;
+
+ case OP_NOTPOSUPTO:
+ case OP_NOTPOSUPTOI:
+ possessive = TRUE;
+ min = 0;
+ max = GET2(ecode, 1);
+ ecode += 3;
+ goto REPEATNOTCHAR;
+
+ case OP_NOTSTAR:
+ case OP_NOTSTARI:
+ case OP_NOTMINSTAR:
+ case OP_NOTMINSTARI:
+ case OP_NOTPLUS:
+ case OP_NOTPLUSI:
+ case OP_NOTMINPLUS:
+ case OP_NOTMINPLUSI:
+ case OP_NOTQUERY:
+ case OP_NOTQUERYI:
+ case OP_NOTMINQUERY:
+ case OP_NOTMINQUERYI:
+ c = *ecode++ - ((op >= OP_NOTSTARI)? OP_NOTSTARI: OP_NOTSTAR);
+ minimize = (c & 1) != 0;
+ min = rep_min[c]; /* Pick up values from tables; */
+ max = rep_max[c]; /* zero for max => infinity */
+ if (max == 0) max = INT_MAX;
+
+ /* Common code for all repeated single-byte matches. */
+
+ REPEATNOTCHAR:
+ fc = *ecode++;
+
+ /* The code is duplicated for the caseless and caseful cases, for speed,
+ since matching characters is likely to be quite common. First, ensure the
+ minimum number of matches are present. If min = max, continue at the same
+ level without recursing. Otherwise, if minimizing, keep trying the rest of
+ the expression and advancing one matching character if failing, up to the
+ maximum. Alternatively, if maximizing, find the maximum number of
+ characters and work backwards. */
+
+ DPRINTF(("negative matching %c{%d,%d} against subject %.*s\n", fc, min, max,
+ max, eptr));
+
+ if (op >= OP_NOTSTARI) /* Caseless */
+ {
+ fc = md->lcc[fc];
+
+#ifdef SUPPORT_UTF8
+ /* UTF-8 mode */
+ if (utf8)
+ {
+ register unsigned int d;
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINC(d, eptr);
+ if (d < 256) d = md->lcc[d];
+ if (fc == d) MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ else
+#endif
+
+ /* Not UTF-8 mode */
+ {
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (fc == md->lcc[*eptr++]) MRRETURN(MATCH_NOMATCH);
+ }
+ }
+
+ if (min == max) continue;
+
+ if (minimize)
+ {
+#ifdef SUPPORT_UTF8
+ /* UTF-8 mode */
+ if (utf8)
+ {
+ register unsigned int d;
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM28);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINC(d, eptr);
+ if (d < 256) d = md->lcc[d];
+ if (fc == d) MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ else
+#endif
+ /* Not UTF-8 mode */
+ {
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM29);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (fc == md->lcc[*eptr++]) MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ /* Control never gets here */
+ }
+
+ /* Maximize case */
+
+ else
+ {
+ pp = eptr;
+
+#ifdef SUPPORT_UTF8
+ /* UTF-8 mode */
+ if (utf8)
+ {
+ register unsigned int d;
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLEN(d, eptr, len);
+ if (d < 256) d = md->lcc[d];
+ if (fc == d) break;
+ eptr += len;
+ }
+ if (possessive) continue;
+ for(;;)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM30);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (eptr-- == pp) break; /* Stop if tried at original pos */
+ BACKCHAR(eptr);
+ }
+ }
+ else
+#endif
+ /* Not UTF-8 mode */
+ {
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ if (fc == md->lcc[*eptr]) break;
+ eptr++;
+ }
+ if (possessive) continue;
+ while (eptr >= pp)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM31);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ eptr--;
+ }
+ }
+
+ MRRETURN(MATCH_NOMATCH);
+ }
+ /* Control never gets here */
+ }
+
+ /* Caseful comparisons */
+
+ else
+ {
+#ifdef SUPPORT_UTF8
+ /* UTF-8 mode */
+ if (utf8)
+ {
+ register unsigned int d;
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINC(d, eptr);
+ if (fc == d) MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ else
+#endif
+ /* Not UTF-8 mode */
+ {
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (fc == *eptr++) MRRETURN(MATCH_NOMATCH);
+ }
+ }
+
+ if (min == max) continue;
+
+ if (minimize)
+ {
+#ifdef SUPPORT_UTF8
+ /* UTF-8 mode */
+ if (utf8)
+ {
+ register unsigned int d;
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM32);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINC(d, eptr);
+ if (fc == d) MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ else
+#endif
+ /* Not UTF-8 mode */
+ {
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM33);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (fc == *eptr++) MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ /* Control never gets here */
+ }
+
+ /* Maximize case */
+
+ else
+ {
+ pp = eptr;
+
+#ifdef SUPPORT_UTF8
+ /* UTF-8 mode */
+ if (utf8)
+ {
+ register unsigned int d;
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLEN(d, eptr, len);
+ if (fc == d) break;
+ eptr += len;
+ }
+ if (possessive) continue;
+ for(;;)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM34);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (eptr-- == pp) break; /* Stop if tried at original pos */
+ BACKCHAR(eptr);
+ }
+ }
+ else
+#endif
+ /* Not UTF-8 mode */
+ {
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ if (fc == *eptr) break;
+ eptr++;
+ }
+ if (possessive) continue;
+ while (eptr >= pp)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM35);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ eptr--;
+ }
+ }
+
+ MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ /* Control never gets here */
+
+ /* Match a single character type repeatedly; several different opcodes
+ share code. This is very similar to the code for single characters, but we
+ repeat it in the interests of efficiency. */
+
+ case OP_TYPEEXACT:
+ min = max = GET2(ecode, 1);
+ minimize = TRUE;
+ ecode += 3;
+ goto REPEATTYPE;
+
+ case OP_TYPEUPTO:
+ case OP_TYPEMINUPTO:
+ min = 0;
+ max = GET2(ecode, 1);
+ minimize = *ecode == OP_TYPEMINUPTO;
+ ecode += 3;
+ goto REPEATTYPE;
+
+ case OP_TYPEPOSSTAR:
+ possessive = TRUE;
+ min = 0;
+ max = INT_MAX;
+ ecode++;
+ goto REPEATTYPE;
+
+ case OP_TYPEPOSPLUS:
+ possessive = TRUE;
+ min = 1;
+ max = INT_MAX;
+ ecode++;
+ goto REPEATTYPE;
+
+ case OP_TYPEPOSQUERY:
+ possessive = TRUE;
+ min = 0;
+ max = 1;
+ ecode++;
+ goto REPEATTYPE;
+
+ case OP_TYPEPOSUPTO:
+ possessive = TRUE;
+ min = 0;
+ max = GET2(ecode, 1);
+ ecode += 3;
+ goto REPEATTYPE;
+
+ case OP_TYPESTAR:
+ case OP_TYPEMINSTAR:
+ case OP_TYPEPLUS:
+ case OP_TYPEMINPLUS:
+ case OP_TYPEQUERY:
+ case OP_TYPEMINQUERY:
+ c = *ecode++ - OP_TYPESTAR;
+ minimize = (c & 1) != 0;
+ min = rep_min[c]; /* Pick up values from tables; */
+ max = rep_max[c]; /* zero for max => infinity */
+ if (max == 0) max = INT_MAX;
+
+ /* Common code for all repeated single character type matches. Note that
+ in UTF-8 mode, '.' matches a character of any length, but for the other
+ character types, the valid characters are all one-byte long. */
+
+ REPEATTYPE:
+ ctype = *ecode++; /* Code for the character type */
+
+#ifdef SUPPORT_UCP
+ if (ctype == OP_PROP || ctype == OP_NOTPROP)
+ {
+ prop_fail_result = ctype == OP_NOTPROP;
+ prop_type = *ecode++;
+ prop_value = *ecode++;
+ }
+ else prop_type = -1;
+#endif
+
+ /* First, ensure the minimum number of matches are present. Use inline
+ code for maximizing the speed, and do the type test once at the start
+ (i.e. keep it out of the loop). Separate the UTF-8 code completely as that
+ is tidier. Also separate the UCP code, which can be the same for both UTF-8
+ and single-bytes. */
+
+ if (min > 0)
+ {
+#ifdef SUPPORT_UCP
+ if (prop_type >= 0)
+ {
+ switch(prop_type)
+ {
+ case PT_ANY:
+ if (prop_fail_result) MRRETURN(MATCH_NOMATCH);
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ }
+ break;
+
+ case PT_LAMP:
+ for (i = 1; i <= min; i++)
+ {
+ int chartype;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ chartype = UCD_CHARTYPE(c);
+ if ((chartype == ucp_Lu ||
+ chartype == ucp_Ll ||
+ chartype == ucp_Lt) == prop_fail_result)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case PT_GC:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if ((UCD_CATEGORY(c) == prop_value) == prop_fail_result)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case PT_PC:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if ((UCD_CHARTYPE(c) == prop_value) == prop_fail_result)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case PT_SC:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if ((UCD_SCRIPT(c) == prop_value) == prop_fail_result)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case PT_ALNUM:
+ for (i = 1; i <= min; i++)
+ {
+ int category;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ category = UCD_CATEGORY(c);
+ if ((category == ucp_L || category == ucp_N) == prop_fail_result)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case PT_SPACE: /* Perl space */
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if ((UCD_CATEGORY(c) == ucp_Z || c == CHAR_HT || c == CHAR_NL ||
+ c == CHAR_FF || c == CHAR_CR)
+ == prop_fail_result)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case PT_PXSPACE: /* POSIX space */
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if ((UCD_CATEGORY(c) == ucp_Z || c == CHAR_HT || c == CHAR_NL ||
+ c == CHAR_VT || c == CHAR_FF || c == CHAR_CR)
+ == prop_fail_result)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case PT_WORD:
+ for (i = 1; i <= min; i++)
+ {
+ int category;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ category = UCD_CATEGORY(c);
+ if ((category == ucp_L || category == ucp_N || c == CHAR_UNDERSCORE)
+ == prop_fail_result)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ /* This should not occur */
+
+ default:
+ RRETURN(PCRE_ERROR_INTERNAL);
+ }
+ }
+
+ /* Match extended Unicode sequences. We will get here only if the
+ support is in the binary; otherwise a compile-time error occurs. */
+
+ else if (ctype == OP_EXTUNI)
+ {
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if (UCD_CATEGORY(c) == ucp_M) MRRETURN(MATCH_NOMATCH);
+ while (eptr < md->end_subject)
+ {
+ int len = 1;
+ if (!utf8) c = *eptr; else { GETCHARLEN(c, eptr, len); }
+ if (UCD_CATEGORY(c) != ucp_M) break;
+ eptr += len;
+ }
+ }
+ }
+
+ else
+#endif /* SUPPORT_UCP */
+
+/* Handle all other cases when the coding is UTF-8 */
+
+#ifdef SUPPORT_UTF8
+ if (utf8) switch(ctype)
+ {
+ case OP_ANY:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (IS_NEWLINE(eptr)) MRRETURN(MATCH_NOMATCH);
+ eptr++;
+ while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++;
+ }
+ break;
+
+ case OP_ALLANY:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ eptr++;
+ while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++;
+ }
+ break;
+
+ case OP_ANYBYTE:
+ if (eptr > md->end_subject - min) MRRETURN(MATCH_NOMATCH);
+ eptr += min;
+ break;
+
+ case OP_ANYNL:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINC(c, eptr);
+ switch(c)
+ {
+ default: MRRETURN(MATCH_NOMATCH);
+
+ case 0x000d:
+ if (eptr < md->end_subject && *eptr == 0x0a) eptr++;
+ break;
+
+ case 0x000a:
+ break;
+
+ case 0x000b:
+ case 0x000c:
+ case 0x0085:
+ case 0x2028:
+ case 0x2029:
+ if (md->bsr_anycrlf) MRRETURN(MATCH_NOMATCH);
+ break;
+ }
+ }
+ break;
+
+ case OP_NOT_HSPACE:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINC(c, eptr);
+ switch(c)
+ {
+ default: break;
+ case 0x09: /* HT */
+ case 0x20: /* SPACE */
+ case 0xa0: /* NBSP */
+ case 0x1680: /* OGHAM SPACE MARK */
+ case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */
+ case 0x2000: /* EN QUAD */
+ case 0x2001: /* EM QUAD */
+ case 0x2002: /* EN SPACE */
+ case 0x2003: /* EM SPACE */
+ case 0x2004: /* THREE-PER-EM SPACE */
+ case 0x2005: /* FOUR-PER-EM SPACE */
+ case 0x2006: /* SIX-PER-EM SPACE */
+ case 0x2007: /* FIGURE SPACE */
+ case 0x2008: /* PUNCTUATION SPACE */
+ case 0x2009: /* THIN SPACE */
+ case 0x200A: /* HAIR SPACE */
+ case 0x202f: /* NARROW NO-BREAK SPACE */
+ case 0x205f: /* MEDIUM MATHEMATICAL SPACE */
+ case 0x3000: /* IDEOGRAPHIC SPACE */
+ MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ break;
+
+ case OP_HSPACE:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINC(c, eptr);
+ switch(c)
+ {
+ default: MRRETURN(MATCH_NOMATCH);
+ case 0x09: /* HT */
+ case 0x20: /* SPACE */
+ case 0xa0: /* NBSP */
+ case 0x1680: /* OGHAM SPACE MARK */
+ case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */
+ case 0x2000: /* EN QUAD */
+ case 0x2001: /* EM QUAD */
+ case 0x2002: /* EN SPACE */
+ case 0x2003: /* EM SPACE */
+ case 0x2004: /* THREE-PER-EM SPACE */
+ case 0x2005: /* FOUR-PER-EM SPACE */
+ case 0x2006: /* SIX-PER-EM SPACE */
+ case 0x2007: /* FIGURE SPACE */
+ case 0x2008: /* PUNCTUATION SPACE */
+ case 0x2009: /* THIN SPACE */
+ case 0x200A: /* HAIR SPACE */
+ case 0x202f: /* NARROW NO-BREAK SPACE */
+ case 0x205f: /* MEDIUM MATHEMATICAL SPACE */
+ case 0x3000: /* IDEOGRAPHIC SPACE */
+ break;
+ }
+ }
+ break;
+
+ case OP_NOT_VSPACE:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINC(c, eptr);
+ switch(c)
+ {
+ default: break;
+ case 0x0a: /* LF */
+ case 0x0b: /* VT */
+ case 0x0c: /* FF */
+ case 0x0d: /* CR */
+ case 0x85: /* NEL */
+ case 0x2028: /* LINE SEPARATOR */
+ case 0x2029: /* PARAGRAPH SEPARATOR */
+ MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ break;
+
+ case OP_VSPACE:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINC(c, eptr);
+ switch(c)
+ {
+ default: MRRETURN(MATCH_NOMATCH);
+ case 0x0a: /* LF */
+ case 0x0b: /* VT */
+ case 0x0c: /* FF */
+ case 0x0d: /* CR */
+ case 0x85: /* NEL */
+ case 0x2028: /* LINE SEPARATOR */
+ case 0x2029: /* PARAGRAPH SEPARATOR */
+ break;
+ }
+ }
+ break;
+
+ case OP_NOT_DIGIT:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINC(c, eptr);
+ if (c < 128 && (md->ctypes[c] & ctype_digit) != 0)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case OP_DIGIT:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (*eptr >= 128 || (md->ctypes[*eptr++] & ctype_digit) == 0)
+ MRRETURN(MATCH_NOMATCH);
+ /* No need to skip more bytes - we know it's a 1-byte character */
+ }
+ break;
+
+ case OP_NOT_WHITESPACE:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (*eptr < 128 && (md->ctypes[*eptr] & ctype_space) != 0)
+ MRRETURN(MATCH_NOMATCH);
+ while (++eptr < md->end_subject && (*eptr & 0xc0) == 0x80);
+ }
+ break;
+
+ case OP_WHITESPACE:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (*eptr >= 128 || (md->ctypes[*eptr++] & ctype_space) == 0)
+ MRRETURN(MATCH_NOMATCH);
+ /* No need to skip more bytes - we know it's a 1-byte character */
+ }
+ break;
+
+ case OP_NOT_WORDCHAR:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (*eptr < 128 && (md->ctypes[*eptr] & ctype_word) != 0)
+ MRRETURN(MATCH_NOMATCH);
+ while (++eptr < md->end_subject && (*eptr & 0xc0) == 0x80);
+ }
+ break;
+
+ case OP_WORDCHAR:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (*eptr >= 128 || (md->ctypes[*eptr++] & ctype_word) == 0)
+ MRRETURN(MATCH_NOMATCH);
+ /* No need to skip more bytes - we know it's a 1-byte character */
+ }
+ break;
+
+ default:
+ RRETURN(PCRE_ERROR_INTERNAL);
+ } /* End switch(ctype) */
+
+ else
+#endif /* SUPPORT_UTF8 */
+
+ /* Code for the non-UTF-8 case for minimum matching of operators other
+ than OP_PROP and OP_NOTPROP. */
+
+ switch(ctype)
+ {
+ case OP_ANY:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (IS_NEWLINE(eptr)) MRRETURN(MATCH_NOMATCH);
+ eptr++;
+ }
+ break;
+
+ case OP_ALLANY:
+ if (eptr > md->end_subject - min)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ eptr += min;
+ break;
+
+ case OP_ANYBYTE:
+ if (eptr > md->end_subject - min)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ eptr += min;
+ break;
+
+ case OP_ANYNL:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ switch(*eptr++)
+ {
+ default: MRRETURN(MATCH_NOMATCH);
+
+ case 0x000d:
+ if (eptr < md->end_subject && *eptr == 0x0a) eptr++;
+ break;
+
+ case 0x000a:
+ break;
+
+ case 0x000b:
+ case 0x000c:
+ case 0x0085:
+ if (md->bsr_anycrlf) MRRETURN(MATCH_NOMATCH);
+ break;
+ }
+ }
+ break;
+
+ case OP_NOT_HSPACE:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ switch(*eptr++)
+ {
+ default: break;
+ case 0x09: /* HT */
+ case 0x20: /* SPACE */
+ case 0xa0: /* NBSP */
+ MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ break;
+
+ case OP_HSPACE:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ switch(*eptr++)
+ {
+ default: MRRETURN(MATCH_NOMATCH);
+ case 0x09: /* HT */
+ case 0x20: /* SPACE */
+ case 0xa0: /* NBSP */
+ break;
+ }
+ }
+ break;
+
+ case OP_NOT_VSPACE:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ switch(*eptr++)
+ {
+ default: break;
+ case 0x0a: /* LF */
+ case 0x0b: /* VT */
+ case 0x0c: /* FF */
+ case 0x0d: /* CR */
+ case 0x85: /* NEL */
+ MRRETURN(MATCH_NOMATCH);
+ }
+ }
+ break;
+
+ case OP_VSPACE:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ switch(*eptr++)
+ {
+ default: MRRETURN(MATCH_NOMATCH);
+ case 0x0a: /* LF */
+ case 0x0b: /* VT */
+ case 0x0c: /* FF */
+ case 0x0d: /* CR */
+ case 0x85: /* NEL */
+ break;
+ }
+ }
+ break;
+
+ case OP_NOT_DIGIT:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if ((md->ctypes[*eptr++] & ctype_digit) != 0) MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case OP_DIGIT:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if ((md->ctypes[*eptr++] & ctype_digit) == 0) MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case OP_NOT_WHITESPACE:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if ((md->ctypes[*eptr++] & ctype_space) != 0) MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case OP_WHITESPACE:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if ((md->ctypes[*eptr++] & ctype_space) == 0) MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case OP_NOT_WORDCHAR:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if ((md->ctypes[*eptr++] & ctype_word) != 0)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case OP_WORDCHAR:
+ for (i = 1; i <= min; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if ((md->ctypes[*eptr++] & ctype_word) == 0)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ default:
+ RRETURN(PCRE_ERROR_INTERNAL);
+ }
+ }
+
+ /* If min = max, continue at the same level without recursing */
+
+ if (min == max) continue;
+
+ /* If minimizing, we have to test the rest of the pattern before each
+ subsequent match. Again, separate the UTF-8 case for speed, and also
+ separate the UCP cases. */
+
+ if (minimize)
+ {
+#ifdef SUPPORT_UCP
+ if (prop_type >= 0)
+ {
+ switch(prop_type)
+ {
+ case PT_ANY:
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM36);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if (prop_fail_result) MRRETURN(MATCH_NOMATCH);
+ }
+ /* Control never gets here */
+
+ case PT_LAMP:
+ for (fi = min;; fi++)
+ {
+ int chartype;
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM37);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ chartype = UCD_CHARTYPE(c);
+ if ((chartype == ucp_Lu ||
+ chartype == ucp_Ll ||
+ chartype == ucp_Lt) == prop_fail_result)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ /* Control never gets here */
+
+ case PT_GC:
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM38);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if ((UCD_CATEGORY(c) == prop_value) == prop_fail_result)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ /* Control never gets here */
+
+ case PT_PC:
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM39);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if ((UCD_CHARTYPE(c) == prop_value) == prop_fail_result)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ /* Control never gets here */
+
+ case PT_SC:
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM40);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if ((UCD_SCRIPT(c) == prop_value) == prop_fail_result)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ /* Control never gets here */
+
+ case PT_ALNUM:
+ for (fi = min;; fi++)
+ {
+ int category;
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM59);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ category = UCD_CATEGORY(c);
+ if ((category == ucp_L || category == ucp_N) == prop_fail_result)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ /* Control never gets here */
+
+ case PT_SPACE: /* Perl space */
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM60);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if ((UCD_CATEGORY(c) == ucp_Z || c == CHAR_HT || c == CHAR_NL ||
+ c == CHAR_FF || c == CHAR_CR)
+ == prop_fail_result)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ /* Control never gets here */
+
+ case PT_PXSPACE: /* POSIX space */
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM61);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if ((UCD_CATEGORY(c) == ucp_Z || c == CHAR_HT || c == CHAR_NL ||
+ c == CHAR_VT || c == CHAR_FF || c == CHAR_CR)
+ == prop_fail_result)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ /* Control never gets here */
+
+ case PT_WORD:
+ for (fi = min;; fi++)
+ {
+ int category;
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM62);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ category = UCD_CATEGORY(c);
+ if ((category == ucp_L ||
+ category == ucp_N ||
+ c == CHAR_UNDERSCORE)
+ == prop_fail_result)
+ MRRETURN(MATCH_NOMATCH);
+ }
+ /* Control never gets here */
+
+ /* This should never occur */
+
+ default:
+ RRETURN(PCRE_ERROR_INTERNAL);
+ }
+ }
+
+ /* Match extended Unicode sequences. We will get here only if the
+ support is in the binary; otherwise a compile-time error occurs. */
+
+ else if (ctype == OP_EXTUNI)
+ {
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM41);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ GETCHARINCTEST(c, eptr);
+ if (UCD_CATEGORY(c) == ucp_M) MRRETURN(MATCH_NOMATCH);
+ while (eptr < md->end_subject)
+ {
+ int len = 1;
+ if (!utf8) c = *eptr; else { GETCHARLEN(c, eptr, len); }
+ if (UCD_CATEGORY(c) != ucp_M) break;
+ eptr += len;
+ }
+ }
+ }
+ else
+#endif /* SUPPORT_UCP */
+
+#ifdef SUPPORT_UTF8
+ /* UTF-8 mode */
+ if (utf8)
+ {
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM42);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (ctype == OP_ANY && IS_NEWLINE(eptr))
+ MRRETURN(MATCH_NOMATCH);
+ GETCHARINC(c, eptr);
+ switch(ctype)
+ {
+ case OP_ANY: /* This is the non-NL case */
+ case OP_ALLANY:
+ case OP_ANYBYTE:
+ break;
+
+ case OP_ANYNL:
+ switch(c)
+ {
+ default: MRRETURN(MATCH_NOMATCH);
+ case 0x000d:
+ if (eptr < md->end_subject && *eptr == 0x0a) eptr++;
+ break;
+ case 0x000a:
+ break;
+
+ case 0x000b:
+ case 0x000c:
+ case 0x0085:
+ case 0x2028:
+ case 0x2029:
+ if (md->bsr_anycrlf) MRRETURN(MATCH_NOMATCH);
+ break;
+ }
+ break;
+
+ case OP_NOT_HSPACE:
+ switch(c)
+ {
+ default: break;
+ case 0x09: /* HT */
+ case 0x20: /* SPACE */
+ case 0xa0: /* NBSP */
+ case 0x1680: /* OGHAM SPACE MARK */
+ case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */
+ case 0x2000: /* EN QUAD */
+ case 0x2001: /* EM QUAD */
+ case 0x2002: /* EN SPACE */
+ case 0x2003: /* EM SPACE */
+ case 0x2004: /* THREE-PER-EM SPACE */
+ case 0x2005: /* FOUR-PER-EM SPACE */
+ case 0x2006: /* SIX-PER-EM SPACE */
+ case 0x2007: /* FIGURE SPACE */
+ case 0x2008: /* PUNCTUATION SPACE */
+ case 0x2009: /* THIN SPACE */
+ case 0x200A: /* HAIR SPACE */
+ case 0x202f: /* NARROW NO-BREAK SPACE */
+ case 0x205f: /* MEDIUM MATHEMATICAL SPACE */
+ case 0x3000: /* IDEOGRAPHIC SPACE */
+ MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case OP_HSPACE:
+ switch(c)
+ {
+ default: MRRETURN(MATCH_NOMATCH);
+ case 0x09: /* HT */
+ case 0x20: /* SPACE */
+ case 0xa0: /* NBSP */
+ case 0x1680: /* OGHAM SPACE MARK */
+ case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */
+ case 0x2000: /* EN QUAD */
+ case 0x2001: /* EM QUAD */
+ case 0x2002: /* EN SPACE */
+ case 0x2003: /* EM SPACE */
+ case 0x2004: /* THREE-PER-EM SPACE */
+ case 0x2005: /* FOUR-PER-EM SPACE */
+ case 0x2006: /* SIX-PER-EM SPACE */
+ case 0x2007: /* FIGURE SPACE */
+ case 0x2008: /* PUNCTUATION SPACE */
+ case 0x2009: /* THIN SPACE */
+ case 0x200A: /* HAIR SPACE */
+ case 0x202f: /* NARROW NO-BREAK SPACE */
+ case 0x205f: /* MEDIUM MATHEMATICAL SPACE */
+ case 0x3000: /* IDEOGRAPHIC SPACE */
+ break;
+ }
+ break;
+
+ case OP_NOT_VSPACE:
+ switch(c)
+ {
+ default: break;
+ case 0x0a: /* LF */
+ case 0x0b: /* VT */
+ case 0x0c: /* FF */
+ case 0x0d: /* CR */
+ case 0x85: /* NEL */
+ case 0x2028: /* LINE SEPARATOR */
+ case 0x2029: /* PARAGRAPH SEPARATOR */
+ MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case OP_VSPACE:
+ switch(c)
+ {
+ default: MRRETURN(MATCH_NOMATCH);
+ case 0x0a: /* LF */
+ case 0x0b: /* VT */
+ case 0x0c: /* FF */
+ case 0x0d: /* CR */
+ case 0x85: /* NEL */
+ case 0x2028: /* LINE SEPARATOR */
+ case 0x2029: /* PARAGRAPH SEPARATOR */
+ break;
+ }
+ break;
+
+ case OP_NOT_DIGIT:
+ if (c < 256 && (md->ctypes[c] & ctype_digit) != 0)
+ MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case OP_DIGIT:
+ if (c >= 256 || (md->ctypes[c] & ctype_digit) == 0)
+ MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case OP_NOT_WHITESPACE:
+ if (c < 256 && (md->ctypes[c] & ctype_space) != 0)
+ MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case OP_WHITESPACE:
+ if (c >= 256 || (md->ctypes[c] & ctype_space) == 0)
+ MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case OP_NOT_WORDCHAR:
+ if (c < 256 && (md->ctypes[c] & ctype_word) != 0)
+ MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case OP_WORDCHAR:
+ if (c >= 256 || (md->ctypes[c] & ctype_word) == 0)
+ MRRETURN(MATCH_NOMATCH);
+ break;
+
+ default:
+ RRETURN(PCRE_ERROR_INTERNAL);
+ }
+ }
+ }
+ else
+#endif
+ /* Not UTF-8 mode */
+ {
+ for (fi = min;; fi++)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM43);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (fi >= max) MRRETURN(MATCH_NOMATCH);
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ MRRETURN(MATCH_NOMATCH);
+ }
+ if (ctype == OP_ANY && IS_NEWLINE(eptr))
+ MRRETURN(MATCH_NOMATCH);
+ c = *eptr++;
+ switch(ctype)
+ {
+ case OP_ANY: /* This is the non-NL case */
+ case OP_ALLANY:
+ case OP_ANYBYTE:
+ break;
+
+ case OP_ANYNL:
+ switch(c)
+ {
+ default: MRRETURN(MATCH_NOMATCH);
+ case 0x000d:
+ if (eptr < md->end_subject && *eptr == 0x0a) eptr++;
+ break;
+
+ case 0x000a:
+ break;
+
+ case 0x000b:
+ case 0x000c:
+ case 0x0085:
+ if (md->bsr_anycrlf) MRRETURN(MATCH_NOMATCH);
+ break;
+ }
+ break;
+
+ case OP_NOT_HSPACE:
+ switch(c)
+ {
+ default: break;
+ case 0x09: /* HT */
+ case 0x20: /* SPACE */
+ case 0xa0: /* NBSP */
+ MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case OP_HSPACE:
+ switch(c)
+ {
+ default: MRRETURN(MATCH_NOMATCH);
+ case 0x09: /* HT */
+ case 0x20: /* SPACE */
+ case 0xa0: /* NBSP */
+ break;
+ }
+ break;
+
+ case OP_NOT_VSPACE:
+ switch(c)
+ {
+ default: break;
+ case 0x0a: /* LF */
+ case 0x0b: /* VT */
+ case 0x0c: /* FF */
+ case 0x0d: /* CR */
+ case 0x85: /* NEL */
+ MRRETURN(MATCH_NOMATCH);
+ }
+ break;
+
+ case OP_VSPACE:
+ switch(c)
+ {
+ default: MRRETURN(MATCH_NOMATCH);
+ case 0x0a: /* LF */
+ case 0x0b: /* VT */
+ case 0x0c: /* FF */
+ case 0x0d: /* CR */
+ case 0x85: /* NEL */
+ break;
+ }
+ break;
+
+ case OP_NOT_DIGIT:
+ if ((md->ctypes[c] & ctype_digit) != 0) MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case OP_DIGIT:
+ if ((md->ctypes[c] & ctype_digit) == 0) MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case OP_NOT_WHITESPACE:
+ if ((md->ctypes[c] & ctype_space) != 0) MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case OP_WHITESPACE:
+ if ((md->ctypes[c] & ctype_space) == 0) MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case OP_NOT_WORDCHAR:
+ if ((md->ctypes[c] & ctype_word) != 0) MRRETURN(MATCH_NOMATCH);
+ break;
+
+ case OP_WORDCHAR:
+ if ((md->ctypes[c] & ctype_word) == 0) MRRETURN(MATCH_NOMATCH);
+ break;
+
+ default:
+ RRETURN(PCRE_ERROR_INTERNAL);
+ }
+ }
+ }
+ /* Control never gets here */
+ }
+
+ /* If maximizing, it is worth using inline code for speed, doing the type
+ test once at the start (i.e. keep it out of the loop). Again, keep the
+ UTF-8 and UCP stuff separate. */
+
+ else
+ {
+ pp = eptr; /* Remember where we started */
+
+#ifdef SUPPORT_UCP
+ if (prop_type >= 0)
+ {
+ switch(prop_type)
+ {
+ case PT_ANY:
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLENTEST(c, eptr, len);
+ if (prop_fail_result) break;
+ eptr+= len;
+ }
+ break;
+
+ case PT_LAMP:
+ for (i = min; i < max; i++)
+ {
+ int chartype;
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLENTEST(c, eptr, len);
+ chartype = UCD_CHARTYPE(c);
+ if ((chartype == ucp_Lu ||
+ chartype == ucp_Ll ||
+ chartype == ucp_Lt) == prop_fail_result)
+ break;
+ eptr+= len;
+ }
+ break;
+
+ case PT_GC:
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLENTEST(c, eptr, len);
+ if ((UCD_CATEGORY(c) == prop_value) == prop_fail_result) break;
+ eptr+= len;
+ }
+ break;
+
+ case PT_PC:
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLENTEST(c, eptr, len);
+ if ((UCD_CHARTYPE(c) == prop_value) == prop_fail_result) break;
+ eptr+= len;
+ }
+ break;
+
+ case PT_SC:
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLENTEST(c, eptr, len);
+ if ((UCD_SCRIPT(c) == prop_value) == prop_fail_result) break;
+ eptr+= len;
+ }
+ break;
+
+ case PT_ALNUM:
+ for (i = min; i < max; i++)
+ {
+ int category;
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLENTEST(c, eptr, len);
+ category = UCD_CATEGORY(c);
+ if ((category == ucp_L || category == ucp_N) == prop_fail_result)
+ break;
+ eptr+= len;
+ }
+ break;
+
+ case PT_SPACE: /* Perl space */
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLENTEST(c, eptr, len);
+ if ((UCD_CATEGORY(c) == ucp_Z || c == CHAR_HT || c == CHAR_NL ||
+ c == CHAR_FF || c == CHAR_CR)
+ == prop_fail_result)
+ break;
+ eptr+= len;
+ }
+ break;
+
+ case PT_PXSPACE: /* POSIX space */
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLENTEST(c, eptr, len);
+ if ((UCD_CATEGORY(c) == ucp_Z || c == CHAR_HT || c == CHAR_NL ||
+ c == CHAR_VT || c == CHAR_FF || c == CHAR_CR)
+ == prop_fail_result)
+ break;
+ eptr+= len;
+ }
+ break;
+
+ case PT_WORD:
+ for (i = min; i < max; i++)
+ {
+ int category;
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLENTEST(c, eptr, len);
+ category = UCD_CATEGORY(c);
+ if ((category == ucp_L || category == ucp_N ||
+ c == CHAR_UNDERSCORE) == prop_fail_result)
+ break;
+ eptr+= len;
+ }
+ break;
+
+ default:
+ RRETURN(PCRE_ERROR_INTERNAL);
+ }
+
+ /* eptr is now past the end of the maximum run */
+
+ if (possessive) continue;
+ for(;;)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM44);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (eptr-- == pp) break; /* Stop if tried at original pos */
+ if (utf8) BACKCHAR(eptr);
+ }
+ }
+
+ /* Match extended Unicode sequences. We will get here only if the
+ support is in the binary; otherwise a compile-time error occurs. */
+
+ else if (ctype == OP_EXTUNI)
+ {
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ if (!utf8) c = *eptr; else { GETCHARLEN(c, eptr, len); }
+ if (UCD_CATEGORY(c) == ucp_M) break;
+ eptr += len;
+ while (eptr < md->end_subject)
+ {
+ len = 1;
+ if (!utf8) c = *eptr; else { GETCHARLEN(c, eptr, len); }
+ if (UCD_CATEGORY(c) != ucp_M) break;
+ eptr += len;
+ }
+ }
+
+ /* eptr is now past the end of the maximum run */
+
+ if (possessive) continue;
+
+ for(;;)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM45);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (eptr-- == pp) break; /* Stop if tried at original pos */
+ for (;;) /* Move back over one extended */
+ {
+ if (!utf8) c = *eptr; else
+ {
+ BACKCHAR(eptr);
+ GETCHAR(c, eptr);
+ }
+ if (UCD_CATEGORY(c) != ucp_M) break;
+ eptr--;
+ }
+ }
+ }
+
+ else
+#endif /* SUPPORT_UCP */
+
+#ifdef SUPPORT_UTF8
+ /* UTF-8 mode */
+
+ if (utf8)
+ {
+ switch(ctype)
+ {
+ case OP_ANY:
+ if (max < INT_MAX)
+ {
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ if (IS_NEWLINE(eptr)) break;
+ eptr++;
+ while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++;
+ }
+ }
+
+ /* Handle unlimited UTF-8 repeat */
+
+ else
+ {
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ if (IS_NEWLINE(eptr)) break;
+ eptr++;
+ while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++;
+ }
+ }
+ break;
+
+ case OP_ALLANY:
+ if (max < INT_MAX)
+ {
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ eptr++;
+ while (eptr < md->end_subject && (*eptr & 0xc0) == 0x80) eptr++;
+ }
+ }
+ else
+ {
+ eptr = md->end_subject; /* Unlimited UTF-8 repeat */
+ SCHECK_PARTIAL();
+ }
+ break;
+
+ /* The byte case is the same as non-UTF8 */
+
+ case OP_ANYBYTE:
+ c = max - min;
+ if (c > (unsigned int)(md->end_subject - eptr))
+ {
+ eptr = md->end_subject;
+ SCHECK_PARTIAL();
+ }
+ else eptr += c;
+ break;
+
+ case OP_ANYNL:
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLEN(c, eptr, len);
+ if (c == 0x000d)
+ {
+ if (++eptr >= md->end_subject) break;
+ if (*eptr == 0x000a) eptr++;
+ }
+ else
+ {
+ if (c != 0x000a &&
+ (md->bsr_anycrlf ||
+ (c != 0x000b && c != 0x000c &&
+ c != 0x0085 && c != 0x2028 && c != 0x2029)))
+ break;
+ eptr += len;
+ }
+ }
+ break;
+
+ case OP_NOT_HSPACE:
+ case OP_HSPACE:
+ for (i = min; i < max; i++)
+ {
+ BOOL gotspace;
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLEN(c, eptr, len);
+ switch(c)
+ {
+ default: gotspace = FALSE; break;
+ case 0x09: /* HT */
+ case 0x20: /* SPACE */
+ case 0xa0: /* NBSP */
+ case 0x1680: /* OGHAM SPACE MARK */
+ case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */
+ case 0x2000: /* EN QUAD */
+ case 0x2001: /* EM QUAD */
+ case 0x2002: /* EN SPACE */
+ case 0x2003: /* EM SPACE */
+ case 0x2004: /* THREE-PER-EM SPACE */
+ case 0x2005: /* FOUR-PER-EM SPACE */
+ case 0x2006: /* SIX-PER-EM SPACE */
+ case 0x2007: /* FIGURE SPACE */
+ case 0x2008: /* PUNCTUATION SPACE */
+ case 0x2009: /* THIN SPACE */
+ case 0x200A: /* HAIR SPACE */
+ case 0x202f: /* NARROW NO-BREAK SPACE */
+ case 0x205f: /* MEDIUM MATHEMATICAL SPACE */
+ case 0x3000: /* IDEOGRAPHIC SPACE */
+ gotspace = TRUE;
+ break;
+ }
+ if (gotspace == (ctype == OP_NOT_HSPACE)) break;
+ eptr += len;
+ }
+ break;
+
+ case OP_NOT_VSPACE:
+ case OP_VSPACE:
+ for (i = min; i < max; i++)
+ {
+ BOOL gotspace;
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLEN(c, eptr, len);
+ switch(c)
+ {
+ default: gotspace = FALSE; break;
+ case 0x0a: /* LF */
+ case 0x0b: /* VT */
+ case 0x0c: /* FF */
+ case 0x0d: /* CR */
+ case 0x85: /* NEL */
+ case 0x2028: /* LINE SEPARATOR */
+ case 0x2029: /* PARAGRAPH SEPARATOR */
+ gotspace = TRUE;
+ break;
+ }
+ if (gotspace == (ctype == OP_NOT_VSPACE)) break;
+ eptr += len;
+ }
+ break;
+
+ case OP_NOT_DIGIT:
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLEN(c, eptr, len);
+ if (c < 256 && (md->ctypes[c] & ctype_digit) != 0) break;
+ eptr+= len;
+ }
+ break;
+
+ case OP_DIGIT:
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLEN(c, eptr, len);
+ if (c >= 256 ||(md->ctypes[c] & ctype_digit) == 0) break;
+ eptr+= len;
+ }
+ break;
+
+ case OP_NOT_WHITESPACE:
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLEN(c, eptr, len);
+ if (c < 256 && (md->ctypes[c] & ctype_space) != 0) break;
+ eptr+= len;
+ }
+ break;
+
+ case OP_WHITESPACE:
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLEN(c, eptr, len);
+ if (c >= 256 ||(md->ctypes[c] & ctype_space) == 0) break;
+ eptr+= len;
+ }
+ break;
+
+ case OP_NOT_WORDCHAR:
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLEN(c, eptr, len);
+ if (c < 256 && (md->ctypes[c] & ctype_word) != 0) break;
+ eptr+= len;
+ }
+ break;
+
+ case OP_WORDCHAR:
+ for (i = min; i < max; i++)
+ {
+ int len = 1;
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ GETCHARLEN(c, eptr, len);
+ if (c >= 256 || (md->ctypes[c] & ctype_word) == 0) break;
+ eptr+= len;
+ }
+ break;
+
+ default:
+ RRETURN(PCRE_ERROR_INTERNAL);
+ }
+
+ /* eptr is now past the end of the maximum run. If possessive, we are
+ done (no backing up). Otherwise, match at this position; anything other
+ than no match is immediately returned. For nomatch, back up one
+ character, unless we are matching \R and the last thing matched was
+ \r\n, in which case, back up two bytes. */
+
+ if (possessive) continue;
+ for(;;)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM46);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ if (eptr-- == pp) break; /* Stop if tried at original pos */
+ BACKCHAR(eptr);
+ if (ctype == OP_ANYNL && eptr > pp && *eptr == '\n' &&
+ eptr[-1] == '\r') eptr--;
+ }
+ }
+ else
+#endif /* SUPPORT_UTF8 */
+
+ /* Not UTF-8 mode */
+ {
+ switch(ctype)
+ {
+ case OP_ANY:
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ if (IS_NEWLINE(eptr)) break;
+ eptr++;
+ }
+ break;
+
+ case OP_ALLANY:
+ case OP_ANYBYTE:
+ c = max - min;
+ if (c > (unsigned int)(md->end_subject - eptr))
+ {
+ eptr = md->end_subject;
+ SCHECK_PARTIAL();
+ }
+ else eptr += c;
+ break;
+
+ case OP_ANYNL:
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ c = *eptr;
+ if (c == 0x000d)
+ {
+ if (++eptr >= md->end_subject) break;
+ if (*eptr == 0x000a) eptr++;
+ }
+ else
+ {
+ if (c != 0x000a &&
+ (md->bsr_anycrlf ||
+ (c != 0x000b && c != 0x000c && c != 0x0085)))
+ break;
+ eptr++;
+ }
+ }
+ break;
+
+ case OP_NOT_HSPACE:
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ c = *eptr;
+ if (c == 0x09 || c == 0x20 || c == 0xa0) break;
+ eptr++;
+ }
+ break;
+
+ case OP_HSPACE:
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ c = *eptr;
+ if (c != 0x09 && c != 0x20 && c != 0xa0) break;
+ eptr++;
+ }
+ break;
+
+ case OP_NOT_VSPACE:
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ c = *eptr;
+ if (c == 0x0a || c == 0x0b || c == 0x0c || c == 0x0d || c == 0x85)
+ break;
+ eptr++;
+ }
+ break;
+
+ case OP_VSPACE:
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ c = *eptr;
+ if (c != 0x0a && c != 0x0b && c != 0x0c && c != 0x0d && c != 0x85)
+ break;
+ eptr++;
+ }
+ break;
+
+ case OP_NOT_DIGIT:
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ if ((md->ctypes[*eptr] & ctype_digit) != 0) break;
+ eptr++;
+ }
+ break;
+
+ case OP_DIGIT:
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ if ((md->ctypes[*eptr] & ctype_digit) == 0) break;
+ eptr++;
+ }
+ break;
+
+ case OP_NOT_WHITESPACE:
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ if ((md->ctypes[*eptr] & ctype_space) != 0) break;
+ eptr++;
+ }
+ break;
+
+ case OP_WHITESPACE:
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ if ((md->ctypes[*eptr] & ctype_space) == 0) break;
+ eptr++;
+ }
+ break;
+
+ case OP_NOT_WORDCHAR:
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ if ((md->ctypes[*eptr] & ctype_word) != 0) break;
+ eptr++;
+ }
+ break;
+
+ case OP_WORDCHAR:
+ for (i = min; i < max; i++)
+ {
+ if (eptr >= md->end_subject)
+ {
+ SCHECK_PARTIAL();
+ break;
+ }
+ if ((md->ctypes[*eptr] & ctype_word) == 0) break;
+ eptr++;
+ }
+ break;
+
+ default:
+ RRETURN(PCRE_ERROR_INTERNAL);
+ }
+
+ /* eptr is now past the end of the maximum run. If possessive, we are
+ done (no backing up). Otherwise, match at this position; anything other
+ than no match is immediately returned. For nomatch, back up one
+ character (byte), unless we are matching \R and the last thing matched
+ was \r\n, in which case, back up two bytes. */
+
+ if (possessive) continue;
+ while (eptr >= pp)
+ {
+ RMATCH(eptr, ecode, offset_top, md, eptrb, RM47);
+ if (rrc != MATCH_NOMATCH) RRETURN(rrc);
+ eptr--;
+ if (ctype == OP_ANYNL && eptr > pp && *eptr == '\n' &&
+ eptr[-1] == '\r') eptr--;
+ }
+ }
+
+ /* Get here if we can't make it match with any permitted repetitions */
+
+ MRRETURN(MATCH_NOMATCH);
+ }
+ /* Control never gets here */
+
+ /* There's been some horrible disaster. Arrival here can only mean there is
+ something seriously wrong in the code above or the OP_xxx definitions. */
+
+ default:
+ DPRINTF(("Unknown opcode %d\n", *ecode));
+ RRETURN(PCRE_ERROR_UNKNOWN_OPCODE);
+ }
+
+ /* Do not stick any code in here without much thought; it is assumed
+ that "continue" in the code above comes out to here to repeat the main
+ loop. */
+
+ } /* End of main loop */
+/* Control never reaches here */
+
+
+/* When compiling to use the heap rather than the stack for recursive calls to
+match(), the RRETURN() macro jumps here. The number that is saved in
+frame->Xwhere indicates which label we actually want to return to. */
+
+#ifdef NO_RECURSE
+#define LBL(val) case val: goto L_RM##val;
+HEAP_RETURN:
+switch (frame->Xwhere)
+ {
+ LBL( 1) LBL( 2) LBL( 3) LBL( 4) LBL( 5) LBL( 6) LBL( 7) LBL( 8)
+ LBL( 9) LBL(10) LBL(11) LBL(12) LBL(13) LBL(14) LBL(15) LBL(17)
+ LBL(19) LBL(24) LBL(25) LBL(26) LBL(27) LBL(29) LBL(31) LBL(33)
+ LBL(35) LBL(43) LBL(47) LBL(48) LBL(49) LBL(50) LBL(51) LBL(52)
+ LBL(53) LBL(54) LBL(55) LBL(56) LBL(57) LBL(58) LBL(63)
+#ifdef SUPPORT_UTF8
+ LBL(16) LBL(18) LBL(20) LBL(21) LBL(22) LBL(23) LBL(28) LBL(30)
+ LBL(32) LBL(34) LBL(42) LBL(46)
+#ifdef SUPPORT_UCP
+ LBL(36) LBL(37) LBL(38) LBL(39) LBL(40) LBL(41) LBL(44) LBL(45)
+ LBL(59) LBL(60) LBL(61) LBL(62)
+#endif /* SUPPORT_UCP */
+#endif /* SUPPORT_UTF8 */
+ default:
+ DPRINTF(("jump error in pcre match: label %d non-existent\n", frame->Xwhere));
+ return PCRE_ERROR_INTERNAL;
+ }
+#undef LBL
+#endif /* NO_RECURSE */
+}
+
+
+/***************************************************************************
+****************************************************************************
+ RECURSION IN THE match() FUNCTION
+
+Undefine all the macros that were defined above to handle this. */
+
+#ifdef NO_RECURSE
+#undef eptr
+#undef ecode
+#undef mstart
+#undef offset_top
+#undef eptrb
+#undef flags
+
+#undef callpat
+#undef charptr
+#undef data
+#undef next
+#undef pp
+#undef prev
+#undef saved_eptr
+
+#undef new_recursive
+
+#undef cur_is_word
+#undef condition
+#undef prev_is_word
+
+#undef ctype
+#undef length
+#undef max
+#undef min
+#undef number
+#undef offset
+#undef op
+#undef save_capture_last
+#undef save_offset1
+#undef save_offset2
+#undef save_offset3
+#undef stacksave
+
+#undef newptrb
+
+#endif
+
+/* These two are defined as macros in both cases */
+
+#undef fc
+#undef fi
+
+/***************************************************************************
+***************************************************************************/
+
+
+
+/*************************************************
+* Execute a Regular Expression *
+*************************************************/
+
+/* This function applies a compiled re to a subject string and picks out
+portions of the string if it matches. Two elements in the vector are set for
+each substring: the offsets to the start and end of the substring.
+
+Arguments:
+ argument_re points to the compiled expression
+ extra_data points to extra data or is NULL
+ subject points to the subject string
+ length length of subject string (may contain binary zeros)
+ start_offset where to start in the subject string
+ options option bits
+ offsets points to a vector of ints to be filled in with offsets
+ offsetcount the number of elements in the vector
+
+Returns: > 0 => success; value is the number of elements filled in
+ = 0 => success, but offsets is not big enough
+ -1 => failed to match
+ < -1 => some kind of unexpected problem
+*/
+
+PCRE_EXP_DEFN int PCRE_CALL_CONVENTION
+pcre_exec(const pcre *argument_re, const pcre_extra *extra_data,
+ PCRE_SPTR subject, int length, int start_offset, int options, int *offsets,
+ int offsetcount)
+{
+int rc, ocount;
+int first_byte = -1;
+int req_byte = -1;
+int req_byte2 = -1;
+int newline;
+BOOL using_temporary_offsets = FALSE;
+BOOL anchored;
+BOOL startline;
+BOOL firstline;
+BOOL first_byte_caseless = FALSE;
+BOOL req_byte_caseless = FALSE;
+BOOL utf8;
+match_data match_block;
+match_data *md = &match_block;
+const uschar *tables;
+const uschar *start_bits = NULL;
+USPTR start_match = (USPTR)subject + start_offset;
+USPTR end_subject;
+USPTR start_partial = NULL;
+USPTR req_byte_ptr = start_match - 1;
+
+pcre_study_data internal_study;
+const pcre_study_data *study;
+
+real_pcre internal_re;
+const real_pcre *external_re = (const real_pcre *)argument_re;
+const real_pcre *re = external_re;
+
+/* Plausibility checks */
+
+if ((options & ~PUBLIC_EXEC_OPTIONS) != 0) return PCRE_ERROR_BADOPTION;
+if (re == NULL || subject == NULL ||
+ (offsets == NULL && offsetcount > 0)) return PCRE_ERROR_NULL;
+if (offsetcount < 0) return PCRE_ERROR_BADCOUNT;
+if (start_offset < 0 || start_offset > length) return PCRE_ERROR_BADOFFSET;
+
+/* This information is for finding all the numbers associated with a given
+name, for condition testing. */
+
+md->name_table = (uschar *)re + re->name_table_offset;
+md->name_count = re->name_count;
+md->name_entry_size = re->name_entry_size;
+
+/* Fish out the optional data from the extra_data structure, first setting
+the default values. */
+
+study = NULL;
+md->match_limit = MATCH_LIMIT;
+md->match_limit_recursion = MATCH_LIMIT_RECURSION;
+md->callout_data = NULL;
+
+/* The table pointer is always in native byte order. */
+
+tables = external_re->tables;
+
+if (extra_data != NULL)
+ {
+ register unsigned int flags = extra_data->flags;
+ if ((flags & PCRE_EXTRA_STUDY_DATA) != 0)
+ study = (const pcre_study_data *)extra_data->study_data;
+ if ((flags & PCRE_EXTRA_MATCH_LIMIT) != 0)
+ md->match_limit = extra_data->match_limit;
+ if ((flags & PCRE_EXTRA_MATCH_LIMIT_RECURSION) != 0)
+ md->match_limit_recursion = extra_data->match_limit_recursion;
+ if ((flags & PCRE_EXTRA_CALLOUT_DATA) != 0)
+ md->callout_data = extra_data->callout_data;
+ if ((flags & PCRE_EXTRA_TABLES) != 0) tables = extra_data->tables;
+ }
+
+/* If the exec call supplied NULL for tables, use the inbuilt ones. This
+is a feature that makes it possible to save compiled regex and re-use them
+in other programs later. */
+
+if (tables == NULL) tables = _pcre_default_tables;
+
+/* Check that the first field in the block is the magic number. If it is not,
+test for a regex that was compiled on a host of opposite endianness. If this is
+the case, flipped values are put in internal_re and internal_study if there was
+study data too. */
+
+if (re->magic_number != MAGIC_NUMBER)
+ {
+ re = _pcre_try_flipped(re, &internal_re, study, &internal_study);
+ if (re == NULL) return PCRE_ERROR_BADMAGIC;
+ if (study != NULL) study = &internal_study;
+ }
+
+/* Set up other data */
+
+anchored = ((re->options | options) & PCRE_ANCHORED) != 0;
+startline = (re->flags & PCRE_STARTLINE) != 0;
+firstline = (re->options & PCRE_FIRSTLINE) != 0;
+
+/* The code starts after the real_pcre block and the capture name table. */
+
+md->start_code = (const uschar *)external_re + re->name_table_offset +
+ re->name_count * re->name_entry_size;
+
+md->start_subject = (USPTR)subject;
+md->start_offset = start_offset;
+md->end_subject = md->start_subject + length;
+end_subject = md->end_subject;
+
+md->endonly = (re->options & PCRE_DOLLAR_ENDONLY) != 0;
+utf8 = md->utf8 = (re->options & PCRE_UTF8) != 0;
+md->use_ucp = (re->options & PCRE_UCP) != 0;
+md->jscript_compat = (re->options & PCRE_JAVASCRIPT_COMPAT) != 0;
+
+/* Some options are unpacked into BOOL variables in the hope that testing
+them will be faster than individual option bits. */
+
+md->notbol = (options & PCRE_NOTBOL) != 0;
+md->noteol = (options & PCRE_NOTEOL) != 0;
+md->notempty = (options & PCRE_NOTEMPTY) != 0;
+md->notempty_atstart = (options & PCRE_NOTEMPTY_ATSTART) != 0;
+md->partial = ((options & PCRE_PARTIAL_HARD) != 0)? 2 :
+ ((options & PCRE_PARTIAL_SOFT) != 0)? 1 : 0;
+
+
+md->hitend = FALSE;
+md->mark = NULL; /* In case never set */
+
+md->recursive = NULL; /* No recursion at top level */
+
+md->lcc = tables + lcc_offset;
+md->ctypes = tables + ctypes_offset;
+
+/* Handle different \R options. */
+
+switch (options & (PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE))
+ {
+ case 0:
+ if ((re->options & (PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE)) != 0)
+ md->bsr_anycrlf = (re->options & PCRE_BSR_ANYCRLF) != 0;
+ else
+#ifdef BSR_ANYCRLF
+ md->bsr_anycrlf = TRUE;
+#else
+ md->bsr_anycrlf = FALSE;
+#endif
+ break;
+
+ case PCRE_BSR_ANYCRLF:
+ md->bsr_anycrlf = TRUE;
+ break;
+
+ case PCRE_BSR_UNICODE:
+ md->bsr_anycrlf = FALSE;
+ break;
+
+ default: return PCRE_ERROR_BADNEWLINE;
+ }
+
+/* Handle different types of newline. The three bits give eight cases. If
+nothing is set at run time, whatever was used at compile time applies. */
+
+switch ((((options & PCRE_NEWLINE_BITS) == 0)? re->options :
+ (pcre_uint32)options) & PCRE_NEWLINE_BITS)
+ {
+ case 0: newline = NEWLINE; break; /* Compile-time default */
+ case PCRE_NEWLINE_CR: newline = CHAR_CR; break;
+ case PCRE_NEWLINE_LF: newline = CHAR_NL; break;
+ case PCRE_NEWLINE_CR+
+ PCRE_NEWLINE_LF: newline = (CHAR_CR << 8) | CHAR_NL; break;
+ case PCRE_NEWLINE_ANY: newline = -1; break;
+ case PCRE_NEWLINE_ANYCRLF: newline = -2; break;
+ default: return PCRE_ERROR_BADNEWLINE;
+ }
+
+if (newline == -2)
+ {
+ md->nltype = NLTYPE_ANYCRLF;
+ }
+else if (newline < 0)
+ {
+ md->nltype = NLTYPE_ANY;
+ }
+else
+ {
+ md->nltype = NLTYPE_FIXED;
+ if (newline > 255)
+ {
+ md->nllen = 2;
+ md->nl[0] = (newline >> 8) & 255;
+ md->nl[1] = newline & 255;
+ }
+ else
+ {
+ md->nllen = 1;
+ md->nl[0] = newline;
+ }
+ }
+
+/* Partial matching was originally supported only for a restricted set of
+regexes; from release 8.00 there are no restrictions, but the bits are still
+defined (though never set). So there's no harm in leaving this code. */
+
+if (md->partial && (re->flags & PCRE_NOPARTIAL) != 0)
+ return PCRE_ERROR_BADPARTIAL;
+
+/* Check a UTF-8 string if required. Pass back the character offset and error
+code for an invalid string if a results vector is available. */
+
+#ifdef SUPPORT_UTF8
+if (utf8 && (options & PCRE_NO_UTF8_CHECK) == 0)
+ {
+ int erroroffset;
+ int errorcode = _pcre_valid_utf8((USPTR)subject, length, &erroroffset);
+ if (errorcode != 0)
+ {
+ if (offsetcount >= 2)
+ {
+ offsets[0] = erroroffset;
+ offsets[1] = errorcode;
+ }
+ return (errorcode <= PCRE_UTF8_ERR5 && md->partial > 1)?
+ PCRE_ERROR_SHORTUTF8 : PCRE_ERROR_BADUTF8;
+ }
+
+ /* Check that a start_offset points to the start of a UTF-8 character. */
+
+ if (start_offset > 0 && start_offset < length &&
+ (((USPTR)subject)[start_offset] & 0xc0) == 0x80)
+ return PCRE_ERROR_BADUTF8_OFFSET;
+ }
+#endif
+
+/* If the expression has got more back references than the offsets supplied can
+hold, we get a temporary chunk of working store to use during the matching.
+Otherwise, we can use the vector supplied, rounding down its size to a multiple
+of 3. */
+
+ocount = offsetcount - (offsetcount % 3);
+
+if (re->top_backref > 0 && re->top_backref >= ocount/3)
+ {
+ ocount = re->top_backref * 3 + 3;
+ md->offset_vector = (int *)(pcre_malloc)(ocount * sizeof(int));
+ if (md->offset_vector == NULL) return PCRE_ERROR_NOMEMORY;
+ using_temporary_offsets = TRUE;
+ DPRINTF(("Got memory to hold back references\n"));
+ }
+else md->offset_vector = offsets;
+
+md->offset_end = ocount;
+md->offset_max = (2*ocount)/3;
+md->offset_overflow = FALSE;
+md->capture_last = -1;
+
+/* Reset the working variable associated with each extraction. These should
+never be used unless previously set, but they get saved and restored, and so we
+initialize them to avoid reading uninitialized locations. Also, unset the
+offsets for the matched string. This is really just for tidiness with callouts,
+in case they inspect these fields. */
+
+if (md->offset_vector != NULL)
+ {
+ register int *iptr = md->offset_vector + ocount;
+ register int *iend = iptr - re->top_bracket;
+ if (iend < md->offset_vector + 2) iend = md->offset_vector + 2;
+ while (--iptr >= iend) *iptr = -1;
+ md->offset_vector[0] = md->offset_vector[1] = -1;
+ }
+
+/* Set up the first character to match, if available. The first_byte value is
+never set for an anchored regular expression, but the anchoring may be forced
+at run time, so we have to test for anchoring. The first char may be unset for
+an unanchored pattern, of course. If there's no first char and the pattern was
+studied, there may be a bitmap of possible first characters. */
+
+if (!anchored)
+ {
+ if ((re->flags & PCRE_FIRSTSET) != 0)
+ {
+ first_byte = re->first_byte & 255;
+ if ((first_byte_caseless = ((re->first_byte & REQ_CASELESS) != 0)) == TRUE)
+ first_byte = md->lcc[first_byte];
+ }
+ else
+ if (!startline && study != NULL &&
+ (study->flags & PCRE_STUDY_MAPPED) != 0)
+ start_bits = study->start_bits;
+ }
+
+/* For anchored or unanchored matches, there may be a "last known required
+character" set. */
+
+if ((re->flags & PCRE_REQCHSET) != 0)
+ {
+ req_byte = re->req_byte & 255;
+ req_byte_caseless = (re->req_byte & REQ_CASELESS) != 0;
+ req_byte2 = (tables + fcc_offset)[req_byte]; /* case flipped */
+ }
+
+
+
+
+/* ==========================================================================*/
+
+/* Loop for handling unanchored repeated matching attempts; for anchored regexs
+the loop runs just once. */
+
+for(;;)
+ {
+ USPTR save_end_subject = end_subject;
+ USPTR new_start_match;
+
+ /* If firstline is TRUE, the start of the match is constrained to the first
+ line of a multiline string. That is, the match must be before or at the first
+ newline. Implement this by temporarily adjusting end_subject so that we stop
+ scanning at a newline. If the match fails at the newline, later code breaks
+ this loop. */
+
+ if (firstline)
+ {
+ USPTR t = start_match;
+#ifdef SUPPORT_UTF8
+ if (utf8)
+ {
+ while (t < md->end_subject && !IS_NEWLINE(t))
+ {
+ t++;
+ while (t < end_subject && (*t & 0xc0) == 0x80) t++;
+ }
+ }
+ else
+#endif
+ while (t < md->end_subject && !IS_NEWLINE(t)) t++;
+ end_subject = t;
+ }
+
+ /* There are some optimizations that avoid running the match if a known
+ starting point is not found, or if a known later character is not present.
+ However, there is an option that disables these, for testing and for ensuring
+ that all callouts do actually occur. The option can be set in the regex by
+ (*NO_START_OPT) or passed in match-time options. */
+
+ if (((options | re->options) & PCRE_NO_START_OPTIMIZE) == 0)
+ {
+ /* Advance to a unique first byte if there is one. */
+
+ if (first_byte >= 0)
+ {
+ if (first_byte_caseless)
+ while (start_match < end_subject && md->lcc[*start_match] != first_byte)
+ start_match++;
+ else
+ while (start_match < end_subject && *start_match != first_byte)
+ start_match++;
+ }
+
+ /* Or to just after a linebreak for a multiline match */
+
+ else if (startline)
+ {
+ if (start_match > md->start_subject + start_offset)
+ {
+#ifdef SUPPORT_UTF8
+ if (utf8)
+ {
+ while (start_match < end_subject && !WAS_NEWLINE(start_match))
+ {
+ start_match++;
+ while(start_match < end_subject && (*start_match & 0xc0) == 0x80)
+ start_match++;
+ }
+ }
+ else
+#endif
+ while (start_match < end_subject && !WAS_NEWLINE(start_match))
+ start_match++;
+
+ /* If we have just passed a CR and the newline option is ANY or ANYCRLF,
+ and we are now at a LF, advance the match position by one more character.
+ */
+
+ if (start_match[-1] == CHAR_CR &&
+ (md->nltype == NLTYPE_ANY || md->nltype == NLTYPE_ANYCRLF) &&
+ start_match < end_subject &&
+ *start_match == CHAR_NL)
+ start_match++;
+ }
+ }
+
+ /* Or to a non-unique first byte after study */
+
+ else if (start_bits != NULL)
+ {
+ while (start_match < end_subject)
+ {
+ register unsigned int c = *start_match;
+ if ((start_bits[c/8] & (1 << (c&7))) == 0)
+ {
+ start_match++;
+#ifdef SUPPORT_UTF8
+ if (utf8)
+ while(start_match < end_subject && (*start_match & 0xc0) == 0x80)
+ start_match++;
+#endif
+ }
+ else break;
+ }
+ }
+ } /* Starting optimizations */
+
+ /* Restore fudged end_subject */
+
+ end_subject = save_end_subject;
+
+ /* The following two optimizations are disabled for partial matching or if
+ disabling is explicitly requested. */
+
+ if ((options & PCRE_NO_START_OPTIMIZE) == 0 && !md->partial)
+ {
+ /* If the pattern was studied, a minimum subject length may be set. This is
+ a lower bound; no actual string of that length may actually match the
+ pattern. Although the value is, strictly, in characters, we treat it as
+ bytes to avoid spending too much time in this optimization. */
+
+ if (study != NULL && (study->flags & PCRE_STUDY_MINLEN) != 0 &&
+ (pcre_uint32)(end_subject - start_match) < study->minlength)
+ {
+ rc = MATCH_NOMATCH;
+ break;
+ }
+
+ /* If req_byte is set, we know that that character must appear in the
+ subject for the match to succeed. If the first character is set, req_byte
+ must be later in the subject; otherwise the test starts at the match point.
+ This optimization can save a huge amount of backtracking in patterns with
+ nested unlimited repeats that aren't going to match. Writing separate code
+ for cased/caseless versions makes it go faster, as does using an
+ autoincrement and backing off on a match.
+
+ HOWEVER: when the subject string is very, very long, searching to its end
+ can take a long time, and give bad performance on quite ordinary patterns.
+ This showed up when somebody was matching something like /^\d+C/ on a
+ 32-megabyte string... so we don't do this when the string is sufficiently
+ long. */
+
+ if (req_byte >= 0 && end_subject - start_match < REQ_BYTE_MAX)
+ {
+ register USPTR p = start_match + ((first_byte >= 0)? 1 : 0);
+
+ /* We don't need to repeat the search if we haven't yet reached the
+ place we found it at last time. */
+
+ if (p > req_byte_ptr)
+ {
+ if (req_byte_caseless)
+ {
+ while (p < end_subject)
+ {
+ register int pp = *p++;
+ if (pp == req_byte || pp == req_byte2) { p--; break; }
+ }
+ }
+ else
+ {
+ while (p < end_subject)
+ {
+ if (*p++ == req_byte) { p--; break; }
+ }
+ }
+
+ /* If we can't find the required character, break the matching loop,
+ forcing a match failure. */
+
+ if (p >= end_subject)
+ {
+ rc = MATCH_NOMATCH;
+ break;
+ }
+
+ /* If we have found the required character, save the point where we
+ found it, so that we don't search again next time round the loop if
+ the start hasn't passed this character yet. */
+
+ req_byte_ptr = p;
+ }
+ }
+ }
+
+#ifdef PCRE_DEBUG /* Sigh. Some compilers never learn. */
+ printf(">>>> Match against: ");
+ pchars(start_match, end_subject - start_match, TRUE, md);
+ printf("\n");
+#endif
+
+ /* OK, we can now run the match. If "hitend" is set afterwards, remember the
+ first starting point for which a partial match was found. */
+
+ md->start_match_ptr = start_match;
+ md->start_used_ptr = start_match;
+ md->match_call_count = 0;
+ md->match_function_type = 0;
+ md->end_offset_top = 0;
+ rc = match(start_match, md->start_code, start_match, NULL, 2, md, NULL, 0);
+ if (md->hitend && start_partial == NULL) start_partial = md->start_used_ptr;
+
+ switch(rc)
+ {
+ /* SKIP passes back the next starting point explicitly, but if it is the
+ same as the match we have just done, treat it as NOMATCH. */
+
+ case MATCH_SKIP:
+ if (md->start_match_ptr != start_match)
+ {
+ new_start_match = md->start_match_ptr;
+ break;
+ }
+ /* Fall through */
+
+ /* If MATCH_SKIP_ARG reaches this level it means that a MARK that matched
+ the SKIP's arg was not found. We also treat this as NOMATCH. */
+
+ case MATCH_SKIP_ARG:
+ /* Fall through */
+
+ /* NOMATCH and PRUNE advance by one character. THEN at this level acts
+ exactly like PRUNE. */
+
+ case MATCH_NOMATCH:
+ case MATCH_PRUNE:
+ case MATCH_THEN:
+ new_start_match = start_match + 1;
+#ifdef SUPPORT_UTF8
+ if (utf8)
+ while(new_start_match < end_subject && (*new_start_match & 0xc0) == 0x80)
+ new_start_match++;
+#endif
+ break;
+
+ /* COMMIT disables the bumpalong, but otherwise behaves as NOMATCH. */
+
+ case MATCH_COMMIT:
+ rc = MATCH_NOMATCH;
+ goto ENDLOOP;
+
+ /* Any other return is either a match, or some kind of error. */
+
+ default:
+ goto ENDLOOP;
+ }
+
+ /* Control reaches here for the various types of "no match at this point"
+ result. Reset the code to MATCH_NOMATCH for subsequent checking. */
+
+ rc = MATCH_NOMATCH;
+
+ /* If PCRE_FIRSTLINE is set, the match must happen before or at the first
+ newline in the subject (though it may continue over the newline). Therefore,
+ if we have just failed to match, starting at a newline, do not continue. */
+
+ if (firstline && IS_NEWLINE(start_match)) break;
+
+ /* Advance to new matching position */
+
+ start_match = new_start_match;
+
+ /* Break the loop if the pattern is anchored or if we have passed the end of
+ the subject. */
+
+ if (anchored || start_match > end_subject) break;
+
+ /* If we have just passed a CR and we are now at a LF, and the pattern does
+ not contain any explicit matches for \r or \n, and the newline option is CRLF
+ or ANY or ANYCRLF, advance the match position by one more character. */
+
+ if (start_match[-1] == CHAR_CR &&
+ start_match < end_subject &&
+ *start_match == CHAR_NL &&
+ (re->flags & PCRE_HASCRORLF) == 0 &&
+ (md->nltype == NLTYPE_ANY ||
+ md->nltype == NLTYPE_ANYCRLF ||
+ md->nllen == 2))
+ start_match++;
+
+ md->mark = NULL; /* Reset for start of next match attempt */
+ } /* End of for(;;) "bumpalong" loop */
+
+/* ==========================================================================*/
+
+/* We reach here when rc is not MATCH_NOMATCH, or if one of the stopping
+conditions is true:
+
+(1) The pattern is anchored or the match was failed by (*COMMIT);
+
+(2) We are past the end of the subject;
+
+(3) PCRE_FIRSTLINE is set and we have failed to match at a newline, because
+ this option requests that a match occur at or before the first newline in
+ the subject.
+
+When we have a match and the offset vector is big enough to deal with any
+backreferences, captured substring offsets will already be set up. In the case
+where we had to get some local store to hold offsets for backreference
+processing, copy those that we can. In this case there need not be overflow if
+certain parts of the pattern were not used, even though there are more
+capturing parentheses than vector slots. */
+
+ENDLOOP:
+
+if (rc == MATCH_MATCH || rc == MATCH_ACCEPT)
+ {
+ if (using_temporary_offsets)
+ {
+ if (offsetcount >= 4)
+ {
+ memcpy(offsets + 2, md->offset_vector + 2,
+ (offsetcount - 2) * sizeof(int));
+ DPRINTF(("Copied offsets from temporary memory\n"));
+ }
+ if (md->end_offset_top > offsetcount) md->offset_overflow = TRUE;
+ DPRINTF(("Freeing temporary memory\n"));
+ (pcre_free)(md->offset_vector);
+ }
+
+ /* Set the return code to the number of captured strings, or 0 if there are
+ too many to fit into the vector. */
+
+ rc = md->offset_overflow? 0 : md->end_offset_top/2;
+
+ /* If there is space in the offset vector, set any unused pairs at the end of
+ the pattern to -1 for backwards compatibility. It is documented that this
+ happens. In earlier versions, the whole set of potential capturing offsets
+ was set to -1 each time round the loop, but this is handled differently now.
+ "Gaps" are set to -1 dynamically instead (this fixes a bug). Thus, it is only
+ those at the end that need unsetting here. We can't just unset them all at
+ the start of the whole thing because they may get set in one branch that is
+ not the final matching branch. */
+
+ if (md->end_offset_top/2 <= re->top_bracket && offsets != NULL)
+ {
+ register int *iptr, *iend;
+ int resetcount = 2 + re->top_bracket * 2;
+ if (resetcount > offsetcount) resetcount = ocount;
+ iptr = offsets + md->end_offset_top;
+ iend = offsets + resetcount;
+ while (iptr < iend) *iptr++ = -1;
+ }
+
+ /* If there is space, set up the whole thing as substring 0. The value of
+ md->start_match_ptr might be modified if \K was encountered on the success
+ matching path. */
+
+ if (offsetcount < 2) rc = 0; else
+ {
+ offsets[0] = (int)(md->start_match_ptr - md->start_subject);
+ offsets[1] = (int)(md->end_match_ptr - md->start_subject);
+ }
+
+ DPRINTF((">>>> returning %d\n", rc));
+ goto RETURN_MARK;
+ }
+
+/* Control gets here if there has been an error, or if the overall match
+attempt has failed at all permitted starting positions. */
+
+if (using_temporary_offsets)
+ {
+ DPRINTF(("Freeing temporary memory\n"));
+ (pcre_free)(md->offset_vector);
+ }
+
+/* For anything other than nomatch or partial match, just return the code. */
+
+if (rc != MATCH_NOMATCH && rc != PCRE_ERROR_PARTIAL)
+ {
+ DPRINTF((">>>> error: returning %d\n", rc));
+ return rc;
+ }
+
+/* Handle partial matches - disable any mark data */
+
+if (start_partial != NULL)
+ {
+ DPRINTF((">>>> returning PCRE_ERROR_PARTIAL\n"));
+ md->mark = NULL;
+ if (offsetcount > 1)
+ {
+ offsets[0] = (int)(start_partial - (USPTR)subject);
+ offsets[1] = (int)(end_subject - (USPTR)subject);
+ }
+ rc = PCRE_ERROR_PARTIAL;
+ }
+
+/* This is the classic nomatch case */
+
+else
+ {
+ DPRINTF((">>>> returning PCRE_ERROR_NOMATCH\n"));
+ rc = PCRE_ERROR_NOMATCH;
+ }
+
+/* Return the MARK data if it has been requested. */
+
+RETURN_MARK:
+
+if (extra_data != NULL && (extra_data->flags & PCRE_EXTRA_MARK) != 0)
+ *(extra_data->mark) = (unsigned char *)(md->mark);
+return rc;
+}
+
+/* End of pcre_exec.c */
diff --git a/usr.sbin/nginx/src/pcre/pcre_fullinfo.c b/usr.sbin/nginx/src/pcre/pcre_fullinfo.c
new file mode 100644
index 00000000000..6b8d789a232
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/pcre_fullinfo.c
@@ -0,0 +1,174 @@
+/*************************************************
+* Perl-Compatible Regular Expressions *
+*************************************************/
+
+/* PCRE is a library of functions to support regular expressions whose syntax
+and semantics are as close as possible to those of the Perl 5 language.
+
+ Written by Philip Hazel
+ Copyright (c) 1997-2009 University of Cambridge
+
+-----------------------------------------------------------------------------
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the University of Cambridge nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+-----------------------------------------------------------------------------
+*/
+
+
+/* This module contains the external function pcre_fullinfo(), which returns
+information about a compiled pattern. */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pcre_internal.h"
+
+
+/*************************************************
+* Return info about compiled pattern *
+*************************************************/
+
+/* This is a newer "info" function which has an extensible interface so
+that additional items can be added compatibly.
+
+Arguments:
+ argument_re points to compiled code
+ extra_data points extra data, or NULL
+ what what information is required
+ where where to put the information
+
+Returns: 0 if data returned, negative on error
+*/
+
+PCRE_EXP_DEFN int PCRE_CALL_CONVENTION
+pcre_fullinfo(const pcre *argument_re, const pcre_extra *extra_data, int what,
+ void *where)
+{
+real_pcre internal_re;
+pcre_study_data internal_study;
+const real_pcre *re = (const real_pcre *)argument_re;
+const pcre_study_data *study = NULL;
+
+if (re == NULL || where == NULL) return PCRE_ERROR_NULL;
+
+if (extra_data != NULL && (extra_data->flags & PCRE_EXTRA_STUDY_DATA) != 0)
+ study = (const pcre_study_data *)extra_data->study_data;
+
+if (re->magic_number != MAGIC_NUMBER)
+ {
+ re = _pcre_try_flipped(re, &internal_re, study, &internal_study);
+ if (re == NULL) return PCRE_ERROR_BADMAGIC;
+ if (study != NULL) study = &internal_study;
+ }
+
+switch (what)
+ {
+ case PCRE_INFO_OPTIONS:
+ *((unsigned long int *)where) = re->options & PUBLIC_COMPILE_OPTIONS;
+ break;
+
+ case PCRE_INFO_SIZE:
+ *((size_t *)where) = re->size;
+ break;
+
+ case PCRE_INFO_STUDYSIZE:
+ *((size_t *)where) = (study == NULL)? 0 : study->size;
+ break;
+
+ case PCRE_INFO_CAPTURECOUNT:
+ *((int *)where) = re->top_bracket;
+ break;
+
+ case PCRE_INFO_BACKREFMAX:
+ *((int *)where) = re->top_backref;
+ break;
+
+ case PCRE_INFO_FIRSTBYTE:
+ *((int *)where) =
+ ((re->flags & PCRE_FIRSTSET) != 0)? re->first_byte :
+ ((re->flags & PCRE_STARTLINE) != 0)? -1 : -2;
+ break;
+
+ /* Make sure we pass back the pointer to the bit vector in the external
+ block, not the internal copy (with flipped integer fields). */
+
+ case PCRE_INFO_FIRSTTABLE:
+ *((const uschar **)where) =
+ (study != NULL && (study->flags & PCRE_STUDY_MAPPED) != 0)?
+ ((const pcre_study_data *)extra_data->study_data)->start_bits : NULL;
+ break;
+
+ case PCRE_INFO_MINLENGTH:
+ *((int *)where) =
+ (study != NULL && (study->flags & PCRE_STUDY_MINLEN) != 0)?
+ study->minlength : -1;
+ break;
+
+ case PCRE_INFO_LASTLITERAL:
+ *((int *)where) =
+ ((re->flags & PCRE_REQCHSET) != 0)? re->req_byte : -1;
+ break;
+
+ case PCRE_INFO_NAMEENTRYSIZE:
+ *((int *)where) = re->name_entry_size;
+ break;
+
+ case PCRE_INFO_NAMECOUNT:
+ *((int *)where) = re->name_count;
+ break;
+
+ case PCRE_INFO_NAMETABLE:
+ *((const uschar **)where) = (const uschar *)re + re->name_table_offset;
+ break;
+
+ case PCRE_INFO_DEFAULT_TABLES:
+ *((const uschar **)where) = (const uschar *)(_pcre_default_tables);
+ break;
+
+ /* From release 8.00 this will always return TRUE because NOPARTIAL is
+ no longer ever set (the restrictions have been removed). */
+
+ case PCRE_INFO_OKPARTIAL:
+ *((int *)where) = (re->flags & PCRE_NOPARTIAL) == 0;
+ break;
+
+ case PCRE_INFO_JCHANGED:
+ *((int *)where) = (re->flags & PCRE_JCHANGED) != 0;
+ break;
+
+ case PCRE_INFO_HASCRORLF:
+ *((int *)where) = (re->flags & PCRE_HASCRORLF) != 0;
+ break;
+
+ default: return PCRE_ERROR_BADOPTION;
+ }
+
+return 0;
+}
+
+/* End of pcre_fullinfo.c */
diff --git a/usr.sbin/nginx/src/pcre/pcre_globals.c b/usr.sbin/nginx/src/pcre/pcre_globals.c
new file mode 100644
index 00000000000..4562e0a069a
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/pcre_globals.c
@@ -0,0 +1,84 @@
+/*************************************************
+* Perl-Compatible Regular Expressions *
+*************************************************/
+
+/* PCRE is a library of functions to support regular expressions whose syntax
+and semantics are as close as possible to those of the Perl 5 language.
+
+ Written by Philip Hazel
+ Copyright (c) 1997-2008 University of Cambridge
+
+-----------------------------------------------------------------------------
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the University of Cambridge nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+-----------------------------------------------------------------------------
+*/
+
+
+/* This module contains global variables that are exported by the PCRE library.
+PCRE is thread-clean and doesn't use any global variables in the normal sense.
+However, it calls memory allocation and freeing functions via the four
+indirections below, and it can optionally do callouts, using the fifth
+indirection. These values can be changed by the caller, but are shared between
+all threads.
+
+For MS Visual Studio and Symbian OS, there are problems in initializing these
+variables to non-local functions. In these cases, therefore, an indirection via
+a local function is used.
+
+Also, when compiling for Virtual Pascal, things are done differently, and
+global variables are not used. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pcre_internal.h"
+
+#if defined _MSC_VER || defined __SYMBIAN32__
+static void* LocalPcreMalloc(size_t aSize)
+ {
+ return malloc(aSize);
+ }
+static void LocalPcreFree(void* aPtr)
+ {
+ free(aPtr);
+ }
+PCRE_EXP_DATA_DEFN void *(*pcre_malloc)(size_t) = LocalPcreMalloc;
+PCRE_EXP_DATA_DEFN void (*pcre_free)(void *) = LocalPcreFree;
+PCRE_EXP_DATA_DEFN void *(*pcre_stack_malloc)(size_t) = LocalPcreMalloc;
+PCRE_EXP_DATA_DEFN void (*pcre_stack_free)(void *) = LocalPcreFree;
+PCRE_EXP_DATA_DEFN int (*pcre_callout)(pcre_callout_block *) = NULL;
+
+#elif !defined VPCOMPAT
+PCRE_EXP_DATA_DEFN void *(*pcre_malloc)(size_t) = malloc;
+PCRE_EXP_DATA_DEFN void (*pcre_free)(void *) = free;
+PCRE_EXP_DATA_DEFN void *(*pcre_stack_malloc)(size_t) = malloc;
+PCRE_EXP_DATA_DEFN void (*pcre_stack_free)(void *) = free;
+PCRE_EXP_DATA_DEFN int (*pcre_callout)(pcre_callout_block *) = NULL;
+#endif
+
+/* End of pcre_globals.c */
diff --git a/usr.sbin/nginx/src/pcre/pcre_internal.h b/usr.sbin/nginx/src/pcre/pcre_internal.h
new file mode 100644
index 00000000000..7f35828176e
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/pcre_internal.h
@@ -0,0 +1,1968 @@
+/*************************************************
+* Perl-Compatible Regular Expressions *
+*************************************************/
+
+
+/* PCRE is a library of functions to support regular expressions whose syntax
+and semantics are as close as possible to those of the Perl 5 language.
+
+ Written by Philip Hazel
+ Copyright (c) 1997-2011 University of Cambridge
+
+-----------------------------------------------------------------------------
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the University of Cambridge nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+-----------------------------------------------------------------------------
+*/
+
+/* This header contains definitions that are shared between the different
+modules, but which are not relevant to the exported API. This includes some
+functions whose names all begin with "_pcre_". */
+
+#ifndef PCRE_INTERNAL_H
+#define PCRE_INTERNAL_H
+
+/* Define PCRE_DEBUG to get debugging output on stdout. */
+
+#if 0
+#define PCRE_DEBUG
+#endif
+
+/* We do not support both EBCDIC and UTF-8 at the same time. The "configure"
+script prevents both being selected, but not everybody uses "configure". */
+
+#if defined EBCDIC && defined SUPPORT_UTF8
+#error The use of both EBCDIC and SUPPORT_UTF8 is not supported.
+#endif
+
+/* If SUPPORT_UCP is defined, SUPPORT_UTF8 must also be defined. The
+"configure" script ensures this, but not everybody uses "configure". */
+
+#if defined SUPPORT_UCP && !defined SUPPORT_UTF8
+#define SUPPORT_UTF8 1
+#endif
+
+/* Use a macro for debugging printing, 'cause that eliminates the use of #ifdef
+inline, and there are *still* stupid compilers about that don't like indented
+pre-processor statements, or at least there were when I first wrote this. After
+all, it had only been about 10 years then...
+
+It turns out that the Mac Debugging.h header also defines the macro DPRINTF, so
+be absolutely sure we get our version. */
+
+#undef DPRINTF
+#ifdef PCRE_DEBUG
+#define DPRINTF(p) printf p
+#else
+#define DPRINTF(p) /* Nothing */
+#endif
+
+
+/* Standard C headers plus the external interface definition. The only time
+setjmp and stdarg are used is when NO_RECURSE is set. */
+
+#include <ctype.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* When compiling a DLL for Windows, the exported symbols have to be declared
+using some MS magic. I found some useful information on this web page:
+http://msdn2.microsoft.com/en-us/library/y4h7bcy6(VS.80).aspx. According to the
+information there, using __declspec(dllexport) without "extern" we have a
+definition; with "extern" we have a declaration. The settings here override the
+setting in pcre.h (which is included below); it defines only PCRE_EXP_DECL,
+which is all that is needed for applications (they just import the symbols). We
+use:
+
+ PCRE_EXP_DECL for declarations
+ PCRE_EXP_DEFN for definitions of exported functions
+ PCRE_EXP_DATA_DEFN for definitions of exported variables
+
+The reason for the two DEFN macros is that in non-Windows environments, one
+does not want to have "extern" before variable definitions because it leads to
+compiler warnings. So we distinguish between functions and variables. In
+Windows, the two should always be the same.
+
+The reason for wrapping this in #ifndef PCRE_EXP_DECL is so that pcretest,
+which is an application, but needs to import this file in order to "peek" at
+internals, can #include pcre.h first to get an application's-eye view.
+
+In principle, people compiling for non-Windows, non-Unix-like (i.e. uncommon,
+special-purpose environments) might want to stick other stuff in front of
+exported symbols. That's why, in the non-Windows case, we set PCRE_EXP_DEFN and
+PCRE_EXP_DATA_DEFN only if they are not already set. */
+
+#ifndef PCRE_EXP_DECL
+# ifdef _WIN32
+# ifndef PCRE_STATIC
+# define PCRE_EXP_DECL extern __declspec(dllexport)
+# define PCRE_EXP_DEFN __declspec(dllexport)
+# define PCRE_EXP_DATA_DEFN __declspec(dllexport)
+# else
+# define PCRE_EXP_DECL extern
+# define PCRE_EXP_DEFN
+# define PCRE_EXP_DATA_DEFN
+# endif
+# else
+# ifdef __cplusplus
+# define PCRE_EXP_DECL extern "C"
+# else
+# define PCRE_EXP_DECL extern
+# endif
+# ifndef PCRE_EXP_DEFN
+# define PCRE_EXP_DEFN PCRE_EXP_DECL
+# endif
+# ifndef PCRE_EXP_DATA_DEFN
+# define PCRE_EXP_DATA_DEFN
+# endif
+# endif
+#endif
+
+/* When compiling with the MSVC compiler, it is sometimes necessary to include
+a "calling convention" before exported function names. (This is secondhand
+information; I know nothing about MSVC myself). For example, something like
+
+ void __cdecl function(....)
+
+might be needed. In order so make this easy, all the exported functions have
+PCRE_CALL_CONVENTION just before their names. It is rarely needed; if not
+set, we ensure here that it has no effect. */
+
+#ifndef PCRE_CALL_CONVENTION
+#define PCRE_CALL_CONVENTION
+#endif
+
+/* We need to have types that specify unsigned 16-bit and 32-bit integers. We
+cannot determine these outside the compilation (e.g. by running a program as
+part of "configure") because PCRE is often cross-compiled for use on other
+systems. Instead we make use of the maximum sizes that are available at
+preprocessor time in standard C environments. */
+
+#if USHRT_MAX == 65535
+ typedef unsigned short pcre_uint16;
+ typedef short pcre_int16;
+#elif UINT_MAX == 65535
+ typedef unsigned int pcre_uint16;
+ typedef int pcre_int16;
+#else
+ #error Cannot determine a type for 16-bit unsigned integers
+#endif
+
+#if UINT_MAX == 4294967295
+ typedef unsigned int pcre_uint32;
+ typedef int pcre_int32;
+#elif ULONG_MAX == 4294967295
+ typedef unsigned long int pcre_uint32;
+ typedef long int pcre_int32;
+#else
+ #error Cannot determine a type for 32-bit unsigned integers
+#endif
+
+/* When checking for integer overflow in pcre_compile(), we need to handle
+large integers. If a 64-bit integer type is available, we can use that.
+Otherwise we have to cast to double, which of course requires floating point
+arithmetic. Handle this by defining a macro for the appropriate type. If
+stdint.h is available, include it; it may define INT64_MAX. Systems that do not
+have stdint.h (e.g. Solaris) may have inttypes.h. The macro int64_t may be set
+by "configure". */
+
+#if HAVE_STDINT_H
+#include <stdint.h>
+#elif HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif
+
+#if defined INT64_MAX || defined int64_t
+#define INT64_OR_DOUBLE int64_t
+#else
+#define INT64_OR_DOUBLE double
+#endif
+
+/* All character handling must be done as unsigned characters. Otherwise there
+are problems with top-bit-set characters and functions such as isspace().
+However, we leave the interface to the outside world as char *, because that
+should make things easier for callers. We define a short type for unsigned char
+to save lots of typing. I tried "uchar", but it causes problems on Digital
+Unix, where it is defined in sys/types, so use "uschar" instead. */
+
+typedef unsigned char uschar;
+
+/* This is an unsigned int value that no character can ever have. UTF-8
+characters only go up to 0x7fffffff (though Unicode doesn't go beyond
+0x0010ffff). */
+
+#define NOTACHAR 0xffffffff
+
+/* PCRE is able to support several different kinds of newline (CR, LF, CRLF,
+"any" and "anycrlf" at present). The following macros are used to package up
+testing for newlines. NLBLOCK, PSSTART, and PSEND are defined in the various
+modules to indicate in which datablock the parameters exist, and what the
+start/end of string field names are. */
+
+#define NLTYPE_FIXED 0 /* Newline is a fixed length string */
+#define NLTYPE_ANY 1 /* Newline is any Unicode line ending */
+#define NLTYPE_ANYCRLF 2 /* Newline is CR, LF, or CRLF */
+
+/* This macro checks for a newline at the given position */
+
+#define IS_NEWLINE(p) \
+ ((NLBLOCK->nltype != NLTYPE_FIXED)? \
+ ((p) < NLBLOCK->PSEND && \
+ _pcre_is_newline((p), NLBLOCK->nltype, NLBLOCK->PSEND, &(NLBLOCK->nllen),\
+ utf8)) \
+ : \
+ ((p) <= NLBLOCK->PSEND - NLBLOCK->nllen && \
+ (p)[0] == NLBLOCK->nl[0] && \
+ (NLBLOCK->nllen == 1 || (p)[1] == NLBLOCK->nl[1]) \
+ ) \
+ )
+
+/* This macro checks for a newline immediately preceding the given position */
+
+#define WAS_NEWLINE(p) \
+ ((NLBLOCK->nltype != NLTYPE_FIXED)? \
+ ((p) > NLBLOCK->PSSTART && \
+ _pcre_was_newline((p), NLBLOCK->nltype, NLBLOCK->PSSTART, \
+ &(NLBLOCK->nllen), utf8)) \
+ : \
+ ((p) >= NLBLOCK->PSSTART + NLBLOCK->nllen && \
+ (p)[-NLBLOCK->nllen] == NLBLOCK->nl[0] && \
+ (NLBLOCK->nllen == 1 || (p)[-NLBLOCK->nllen+1] == NLBLOCK->nl[1]) \
+ ) \
+ )
+
+/* When PCRE is compiled as a C++ library, the subject pointer can be replaced
+with a custom type. This makes it possible, for example, to allow pcre_exec()
+to process subject strings that are discontinuous by using a smart pointer
+class. It must always be possible to inspect all of the subject string in
+pcre_exec() because of the way it backtracks. Two macros are required in the
+normal case, for sign-unspecified and unsigned char pointers. The former is
+used for the external interface and appears in pcre.h, which is why its name
+must begin with PCRE_. */
+
+#ifdef CUSTOM_SUBJECT_PTR
+#define PCRE_SPTR CUSTOM_SUBJECT_PTR
+#define USPTR CUSTOM_SUBJECT_PTR
+#else
+#define PCRE_SPTR const char *
+#define USPTR const unsigned char *
+#endif
+
+
+
+/* Include the public PCRE header and the definitions of UCP character property
+values. */
+
+#include "pcre.h"
+#include "ucp.h"
+
+/* When compiling for use with the Virtual Pascal compiler, these functions
+need to have their names changed. PCRE must be compiled with the -DVPCOMPAT
+option on the command line. */
+
+#ifdef VPCOMPAT
+#define strlen(s) _strlen(s)
+#define strncmp(s1,s2,m) _strncmp(s1,s2,m)
+#define memcmp(s,c,n) _memcmp(s,c,n)
+#define memcpy(d,s,n) _memcpy(d,s,n)
+#define memmove(d,s,n) _memmove(d,s,n)
+#define memset(s,c,n) _memset(s,c,n)
+#else /* VPCOMPAT */
+
+/* To cope with SunOS4 and other systems that lack memmove() but have bcopy(),
+define a macro for memmove() if HAVE_MEMMOVE is false, provided that HAVE_BCOPY
+is set. Otherwise, include an emulating function for those systems that have
+neither (there some non-Unix environments where this is the case). */
+
+#ifndef HAVE_MEMMOVE
+#undef memmove /* some systems may have a macro */
+#ifdef HAVE_BCOPY
+#define memmove(a, b, c) bcopy(b, a, c)
+#else /* HAVE_BCOPY */
+static void *
+pcre_memmove(void *d, const void *s, size_t n)
+{
+size_t i;
+unsigned char *dest = (unsigned char *)d;
+const unsigned char *src = (const unsigned char *)s;
+if (dest > src)
+ {
+ dest += n;
+ src += n;
+ for (i = 0; i < n; ++i) *(--dest) = *(--src);
+ return (void *)dest;
+ }
+else
+ {
+ for (i = 0; i < n; ++i) *dest++ = *src++;
+ return (void *)(dest - n);
+ }
+}
+#define memmove(a, b, c) pcre_memmove(a, b, c)
+#endif /* not HAVE_BCOPY */
+#endif /* not HAVE_MEMMOVE */
+#endif /* not VPCOMPAT */
+
+
+/* PCRE keeps offsets in its compiled code as 2-byte quantities (always stored
+in big-endian order) by default. These are used, for example, to link from the
+start of a subpattern to its alternatives and its end. The use of 2 bytes per
+offset limits the size of the compiled regex to around 64K, which is big enough
+for almost everybody. However, I received a request for an even bigger limit.
+For this reason, and also to make the code easier to maintain, the storing and
+loading of offsets from the byte string is now handled by the macros that are
+defined here.
+
+The macros are controlled by the value of LINK_SIZE. This defaults to 2 in
+the config.h file, but can be overridden by using -D on the command line. This
+is automated on Unix systems via the "configure" command. */
+
+#if LINK_SIZE == 2
+
+#define PUT(a,n,d) \
+ (a[n] = (d) >> 8), \
+ (a[(n)+1] = (d) & 255)
+
+#define GET(a,n) \
+ (((a)[n] << 8) | (a)[(n)+1])
+
+#define MAX_PATTERN_SIZE (1 << 16)
+
+
+#elif LINK_SIZE == 3
+
+#define PUT(a,n,d) \
+ (a[n] = (d) >> 16), \
+ (a[(n)+1] = (d) >> 8), \
+ (a[(n)+2] = (d) & 255)
+
+#define GET(a,n) \
+ (((a)[n] << 16) | ((a)[(n)+1] << 8) | (a)[(n)+2])
+
+#define MAX_PATTERN_SIZE (1 << 24)
+
+
+#elif LINK_SIZE == 4
+
+#define PUT(a,n,d) \
+ (a[n] = (d) >> 24), \
+ (a[(n)+1] = (d) >> 16), \
+ (a[(n)+2] = (d) >> 8), \
+ (a[(n)+3] = (d) & 255)
+
+#define GET(a,n) \
+ (((a)[n] << 24) | ((a)[(n)+1] << 16) | ((a)[(n)+2] << 8) | (a)[(n)+3])
+
+#define MAX_PATTERN_SIZE (1 << 30) /* Keep it positive */
+
+
+#else
+#error LINK_SIZE must be either 2, 3, or 4
+#endif
+
+
+/* Convenience macro defined in terms of the others */
+
+#define PUTINC(a,n,d) PUT(a,n,d), a += LINK_SIZE
+
+
+/* PCRE uses some other 2-byte quantities that do not change when the size of
+offsets changes. There are used for repeat counts and for other things such as
+capturing parenthesis numbers in back references. */
+
+#define PUT2(a,n,d) \
+ a[n] = (d) >> 8; \
+ a[(n)+1] = (d) & 255
+
+#define GET2(a,n) \
+ (((a)[n] << 8) | (a)[(n)+1])
+
+#define PUT2INC(a,n,d) PUT2(a,n,d), a += 2
+
+
+/* When UTF-8 encoding is being used, a character is no longer just a single
+byte. The macros for character handling generate simple sequences when used in
+byte-mode, and more complicated ones for UTF-8 characters. GETCHARLENTEST is
+not used when UTF-8 is not supported, so it is not defined, and BACKCHAR should
+never be called in byte mode. To make sure they can never even appear when
+UTF-8 support is omitted, we don't even define them. */
+
+#ifndef SUPPORT_UTF8
+#define GETCHAR(c, eptr) c = *eptr;
+#define GETCHARTEST(c, eptr) c = *eptr;
+#define GETCHARINC(c, eptr) c = *eptr++;
+#define GETCHARINCTEST(c, eptr) c = *eptr++;
+#define GETCHARLEN(c, eptr, len) c = *eptr;
+/* #define GETCHARLENTEST(c, eptr, len) */
+/* #define BACKCHAR(eptr) */
+
+#else /* SUPPORT_UTF8 */
+
+/* These macros were originally written in the form of loops that used data
+from the tables whose names start with _pcre_utf8_table. They were rewritten by
+a user so as not to use loops, because in some environments this gives a
+significant performance advantage, and it seems never to do any harm. */
+
+/* Base macro to pick up the remaining bytes of a UTF-8 character, not
+advancing the pointer. */
+
+#define GETUTF8(c, eptr) \
+ { \
+ if ((c & 0x20) == 0) \
+ c = ((c & 0x1f) << 6) | (eptr[1] & 0x3f); \
+ else if ((c & 0x10) == 0) \
+ c = ((c & 0x0f) << 12) | ((eptr[1] & 0x3f) << 6) | (eptr[2] & 0x3f); \
+ else if ((c & 0x08) == 0) \
+ c = ((c & 0x07) << 18) | ((eptr[1] & 0x3f) << 12) | \
+ ((eptr[2] & 0x3f) << 6) | (eptr[3] & 0x3f); \
+ else if ((c & 0x04) == 0) \
+ c = ((c & 0x03) << 24) | ((eptr[1] & 0x3f) << 18) | \
+ ((eptr[2] & 0x3f) << 12) | ((eptr[3] & 0x3f) << 6) | \
+ (eptr[4] & 0x3f); \
+ else \
+ c = ((c & 0x01) << 30) | ((eptr[1] & 0x3f) << 24) | \
+ ((eptr[2] & 0x3f) << 18) | ((eptr[3] & 0x3f) << 12) | \
+ ((eptr[4] & 0x3f) << 6) | (eptr[5] & 0x3f); \
+ }
+
+/* Get the next UTF-8 character, not advancing the pointer. This is called when
+we know we are in UTF-8 mode. */
+
+#define GETCHAR(c, eptr) \
+ c = *eptr; \
+ if (c >= 0xc0) GETUTF8(c, eptr);
+
+/* Get the next UTF-8 character, testing for UTF-8 mode, and not advancing the
+pointer. */
+
+#define GETCHARTEST(c, eptr) \
+ c = *eptr; \
+ if (utf8 && c >= 0xc0) GETUTF8(c, eptr);
+
+/* Base macro to pick up the remaining bytes of a UTF-8 character, advancing
+the pointer. */
+
+#define GETUTF8INC(c, eptr) \
+ { \
+ if ((c & 0x20) == 0) \
+ c = ((c & 0x1f) << 6) | (*eptr++ & 0x3f); \
+ else if ((c & 0x10) == 0) \
+ { \
+ c = ((c & 0x0f) << 12) | ((*eptr & 0x3f) << 6) | (eptr[1] & 0x3f); \
+ eptr += 2; \
+ } \
+ else if ((c & 0x08) == 0) \
+ { \
+ c = ((c & 0x07) << 18) | ((*eptr & 0x3f) << 12) | \
+ ((eptr[1] & 0x3f) << 6) | (eptr[2] & 0x3f); \
+ eptr += 3; \
+ } \
+ else if ((c & 0x04) == 0) \
+ { \
+ c = ((c & 0x03) << 24) | ((*eptr & 0x3f) << 18) | \
+ ((eptr[1] & 0x3f) << 12) | ((eptr[2] & 0x3f) << 6) | \
+ (eptr[3] & 0x3f); \
+ eptr += 4; \
+ } \
+ else \
+ { \
+ c = ((c & 0x01) << 30) | ((*eptr & 0x3f) << 24) | \
+ ((eptr[1] & 0x3f) << 18) | ((eptr[2] & 0x3f) << 12) | \
+ ((eptr[3] & 0x3f) << 6) | (eptr[4] & 0x3f); \
+ eptr += 5; \
+ } \
+ }
+
+/* Get the next UTF-8 character, advancing the pointer. This is called when we
+know we are in UTF-8 mode. */
+
+#define GETCHARINC(c, eptr) \
+ c = *eptr++; \
+ if (c >= 0xc0) GETUTF8INC(c, eptr);
+
+/* Get the next character, testing for UTF-8 mode, and advancing the pointer.
+This is called when we don't know if we are in UTF-8 mode. */
+
+#define GETCHARINCTEST(c, eptr) \
+ c = *eptr++; \
+ if (utf8 && c >= 0xc0) GETUTF8INC(c, eptr);
+
+/* Base macro to pick up the remaining bytes of a UTF-8 character, not
+advancing the pointer, incrementing the length. */
+
+#define GETUTF8LEN(c, eptr, len) \
+ { \
+ if ((c & 0x20) == 0) \
+ { \
+ c = ((c & 0x1f) << 6) | (eptr[1] & 0x3f); \
+ len++; \
+ } \
+ else if ((c & 0x10) == 0) \
+ { \
+ c = ((c & 0x0f) << 12) | ((eptr[1] & 0x3f) << 6) | (eptr[2] & 0x3f); \
+ len += 2; \
+ } \
+ else if ((c & 0x08) == 0) \
+ {\
+ c = ((c & 0x07) << 18) | ((eptr[1] & 0x3f) << 12) | \
+ ((eptr[2] & 0x3f) << 6) | (eptr[3] & 0x3f); \
+ len += 3; \
+ } \
+ else if ((c & 0x04) == 0) \
+ { \
+ c = ((c & 0x03) << 24) | ((eptr[1] & 0x3f) << 18) | \
+ ((eptr[2] & 0x3f) << 12) | ((eptr[3] & 0x3f) << 6) | \
+ (eptr[4] & 0x3f); \
+ len += 4; \
+ } \
+ else \
+ {\
+ c = ((c & 0x01) << 30) | ((eptr[1] & 0x3f) << 24) | \
+ ((eptr[2] & 0x3f) << 18) | ((eptr[3] & 0x3f) << 12) | \
+ ((eptr[4] & 0x3f) << 6) | (eptr[5] & 0x3f); \
+ len += 5; \
+ } \
+ }
+
+/* Get the next UTF-8 character, not advancing the pointer, incrementing length
+if there are extra bytes. This is called when we know we are in UTF-8 mode. */
+
+#define GETCHARLEN(c, eptr, len) \
+ c = *eptr; \
+ if (c >= 0xc0) GETUTF8LEN(c, eptr, len);
+
+/* Get the next UTF-8 character, testing for UTF-8 mode, not advancing the
+pointer, incrementing length if there are extra bytes. This is called when we
+do not know if we are in UTF-8 mode. */
+
+#define GETCHARLENTEST(c, eptr, len) \
+ c = *eptr; \
+ if (utf8 && c >= 0xc0) GETUTF8LEN(c, eptr, len);
+
+/* If the pointer is not at the start of a character, move it back until
+it is. This is called only in UTF-8 mode - we don't put a test within the macro
+because almost all calls are already within a block of UTF-8 only code. */
+
+#define BACKCHAR(eptr) while((*eptr & 0xc0) == 0x80) eptr--
+
+#endif /* SUPPORT_UTF8 */
+
+
+/* In case there is no definition of offsetof() provided - though any proper
+Standard C system should have one. */
+
+#ifndef offsetof
+#define offsetof(p_type,field) ((size_t)&(((p_type *)0)->field))
+#endif
+
+
+/* Private flags containing information about the compiled regex. They used to
+live at the top end of the options word, but that got almost full, so now they
+are in a 16-bit flags word. From release 8.00, PCRE_NOPARTIAL is unused, as
+the restrictions on partial matching have been lifted. It remains for backwards
+compatibility. */
+
+#define PCRE_NOPARTIAL 0x0001 /* can't use partial with this regex */
+#define PCRE_FIRSTSET 0x0002 /* first_byte is set */
+#define PCRE_REQCHSET 0x0004 /* req_byte is set */
+#define PCRE_STARTLINE 0x0008 /* start after \n for multiline */
+#define PCRE_JCHANGED 0x0010 /* j option used in regex */
+#define PCRE_HASCRORLF 0x0020 /* explicit \r or \n in pattern */
+
+/* Flags for the "extra" block produced by pcre_study(). */
+
+#define PCRE_STUDY_MAPPED 0x0001 /* a map of starting chars exists */
+#define PCRE_STUDY_MINLEN 0x0002 /* a minimum length field exists */
+
+/* Masks for identifying the public options that are permitted at compile
+time, run time, or study time, respectively. */
+
+#define PCRE_NEWLINE_BITS (PCRE_NEWLINE_CR|PCRE_NEWLINE_LF|PCRE_NEWLINE_ANY| \
+ PCRE_NEWLINE_ANYCRLF)
+
+#define PUBLIC_COMPILE_OPTIONS \
+ (PCRE_CASELESS|PCRE_EXTENDED|PCRE_ANCHORED|PCRE_MULTILINE| \
+ PCRE_DOTALL|PCRE_DOLLAR_ENDONLY|PCRE_EXTRA|PCRE_UNGREEDY|PCRE_UTF8| \
+ PCRE_NO_AUTO_CAPTURE|PCRE_NO_UTF8_CHECK|PCRE_AUTO_CALLOUT|PCRE_FIRSTLINE| \
+ PCRE_DUPNAMES|PCRE_NEWLINE_BITS|PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE| \
+ PCRE_JAVASCRIPT_COMPAT|PCRE_UCP|PCRE_NO_START_OPTIMIZE)
+
+#define PUBLIC_EXEC_OPTIONS \
+ (PCRE_ANCHORED|PCRE_NOTBOL|PCRE_NOTEOL|PCRE_NOTEMPTY|PCRE_NOTEMPTY_ATSTART| \
+ PCRE_NO_UTF8_CHECK|PCRE_PARTIAL_HARD|PCRE_PARTIAL_SOFT|PCRE_NEWLINE_BITS| \
+ PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE|PCRE_NO_START_OPTIMIZE)
+
+#define PUBLIC_DFA_EXEC_OPTIONS \
+ (PCRE_ANCHORED|PCRE_NOTBOL|PCRE_NOTEOL|PCRE_NOTEMPTY|PCRE_NOTEMPTY_ATSTART| \
+ PCRE_NO_UTF8_CHECK|PCRE_PARTIAL_HARD|PCRE_PARTIAL_SOFT|PCRE_DFA_SHORTEST| \
+ PCRE_DFA_RESTART|PCRE_NEWLINE_BITS|PCRE_BSR_ANYCRLF|PCRE_BSR_UNICODE| \
+ PCRE_NO_START_OPTIMIZE)
+
+#define PUBLIC_STUDY_OPTIONS 0 /* None defined */
+
+/* Magic number to provide a small check against being handed junk. Also used
+to detect whether a pattern was compiled on a host of different endianness. */
+
+#define MAGIC_NUMBER 0x50435245UL /* 'PCRE' */
+
+/* Negative values for the firstchar and reqchar variables */
+
+#define REQ_UNSET (-2)
+#define REQ_NONE (-1)
+
+/* The maximum remaining length of subject we are prepared to search for a
+req_byte match. */
+
+#define REQ_BYTE_MAX 1000
+
+/* Flags added to firstbyte or reqbyte; a "non-literal" item is either a
+variable-length repeat, or a anything other than literal characters. */
+
+#define REQ_CASELESS 0x0100 /* indicates caselessness */
+#define REQ_VARY 0x0200 /* reqbyte followed non-literal item */
+
+/* Miscellaneous definitions. The #ifndef is to pacify compiler warnings in
+environments where these macros are defined elsewhere. Unfortunately, there
+is no way to do the same for the typedef. */
+
+typedef int BOOL;
+
+#ifndef FALSE
+#define FALSE 0
+#define TRUE 1
+#endif
+
+/* If PCRE is to support UTF-8 on EBCDIC platforms, we cannot use normal
+character constants like '*' because the compiler would emit their EBCDIC code,
+which is different from their ASCII/UTF-8 code. Instead we define macros for
+the characters so that they always use the ASCII/UTF-8 code when UTF-8 support
+is enabled. When UTF-8 support is not enabled, the definitions use character
+literals. Both character and string versions of each character are needed, and
+there are some longer strings as well.
+
+This means that, on EBCDIC platforms, the PCRE library can handle either
+EBCDIC, or UTF-8, but not both. To support both in the same compiled library
+would need different lookups depending on whether PCRE_UTF8 was set or not.
+This would make it impossible to use characters in switch/case statements,
+which would reduce performance. For a theoretical use (which nobody has asked
+for) in a minority area (EBCDIC platforms), this is not sensible. Any
+application that did need both could compile two versions of the library, using
+macros to give the functions distinct names. */
+
+#ifndef SUPPORT_UTF8
+
+/* UTF-8 support is not enabled; use the platform-dependent character literals
+so that PCRE works on both ASCII and EBCDIC platforms, in non-UTF-mode only. */
+
+#define CHAR_HT '\t'
+#define CHAR_VT '\v'
+#define CHAR_FF '\f'
+#define CHAR_CR '\r'
+#define CHAR_NL '\n'
+#define CHAR_BS '\b'
+#define CHAR_BEL '\a'
+#ifdef EBCDIC
+#define CHAR_ESC '\047'
+#define CHAR_DEL '\007'
+#else
+#define CHAR_ESC '\033'
+#define CHAR_DEL '\177'
+#endif
+
+#define CHAR_SPACE ' '
+#define CHAR_EXCLAMATION_MARK '!'
+#define CHAR_QUOTATION_MARK '"'
+#define CHAR_NUMBER_SIGN '#'
+#define CHAR_DOLLAR_SIGN '$'
+#define CHAR_PERCENT_SIGN '%'
+#define CHAR_AMPERSAND '&'
+#define CHAR_APOSTROPHE '\''
+#define CHAR_LEFT_PARENTHESIS '('
+#define CHAR_RIGHT_PARENTHESIS ')'
+#define CHAR_ASTERISK '*'
+#define CHAR_PLUS '+'
+#define CHAR_COMMA ','
+#define CHAR_MINUS '-'
+#define CHAR_DOT '.'
+#define CHAR_SLASH '/'
+#define CHAR_0 '0'
+#define CHAR_1 '1'
+#define CHAR_2 '2'
+#define CHAR_3 '3'
+#define CHAR_4 '4'
+#define CHAR_5 '5'
+#define CHAR_6 '6'
+#define CHAR_7 '7'
+#define CHAR_8 '8'
+#define CHAR_9 '9'
+#define CHAR_COLON ':'
+#define CHAR_SEMICOLON ';'
+#define CHAR_LESS_THAN_SIGN '<'
+#define CHAR_EQUALS_SIGN '='
+#define CHAR_GREATER_THAN_SIGN '>'
+#define CHAR_QUESTION_MARK '?'
+#define CHAR_COMMERCIAL_AT '@'
+#define CHAR_A 'A'
+#define CHAR_B 'B'
+#define CHAR_C 'C'
+#define CHAR_D 'D'
+#define CHAR_E 'E'
+#define CHAR_F 'F'
+#define CHAR_G 'G'
+#define CHAR_H 'H'
+#define CHAR_I 'I'
+#define CHAR_J 'J'
+#define CHAR_K 'K'
+#define CHAR_L 'L'
+#define CHAR_M 'M'
+#define CHAR_N 'N'
+#define CHAR_O 'O'
+#define CHAR_P 'P'
+#define CHAR_Q 'Q'
+#define CHAR_R 'R'
+#define CHAR_S 'S'
+#define CHAR_T 'T'
+#define CHAR_U 'U'
+#define CHAR_V 'V'
+#define CHAR_W 'W'
+#define CHAR_X 'X'
+#define CHAR_Y 'Y'
+#define CHAR_Z 'Z'
+#define CHAR_LEFT_SQUARE_BRACKET '['
+#define CHAR_BACKSLASH '\\'
+#define CHAR_RIGHT_SQUARE_BRACKET ']'
+#define CHAR_CIRCUMFLEX_ACCENT '^'
+#define CHAR_UNDERSCORE '_'
+#define CHAR_GRAVE_ACCENT '`'
+#define CHAR_a 'a'
+#define CHAR_b 'b'
+#define CHAR_c 'c'
+#define CHAR_d 'd'
+#define CHAR_e 'e'
+#define CHAR_f 'f'
+#define CHAR_g 'g'
+#define CHAR_h 'h'
+#define CHAR_i 'i'
+#define CHAR_j 'j'
+#define CHAR_k 'k'
+#define CHAR_l 'l'
+#define CHAR_m 'm'
+#define CHAR_n 'n'
+#define CHAR_o 'o'
+#define CHAR_p 'p'
+#define CHAR_q 'q'
+#define CHAR_r 'r'
+#define CHAR_s 's'
+#define CHAR_t 't'
+#define CHAR_u 'u'
+#define CHAR_v 'v'
+#define CHAR_w 'w'
+#define CHAR_x 'x'
+#define CHAR_y 'y'
+#define CHAR_z 'z'
+#define CHAR_LEFT_CURLY_BRACKET '{'
+#define CHAR_VERTICAL_LINE '|'
+#define CHAR_RIGHT_CURLY_BRACKET '}'
+#define CHAR_TILDE '~'
+
+#define STR_HT "\t"
+#define STR_VT "\v"
+#define STR_FF "\f"
+#define STR_CR "\r"
+#define STR_NL "\n"
+#define STR_BS "\b"
+#define STR_BEL "\a"
+#ifdef EBCDIC
+#define STR_ESC "\047"
+#define STR_DEL "\007"
+#else
+#define STR_ESC "\033"
+#define STR_DEL "\177"
+#endif
+
+#define STR_SPACE " "
+#define STR_EXCLAMATION_MARK "!"
+#define STR_QUOTATION_MARK "\""
+#define STR_NUMBER_SIGN "#"
+#define STR_DOLLAR_SIGN "$"
+#define STR_PERCENT_SIGN "%"
+#define STR_AMPERSAND "&"
+#define STR_APOSTROPHE "'"
+#define STR_LEFT_PARENTHESIS "("
+#define STR_RIGHT_PARENTHESIS ")"
+#define STR_ASTERISK "*"
+#define STR_PLUS "+"
+#define STR_COMMA ","
+#define STR_MINUS "-"
+#define STR_DOT "."
+#define STR_SLASH "/"
+#define STR_0 "0"
+#define STR_1 "1"
+#define STR_2 "2"
+#define STR_3 "3"
+#define STR_4 "4"
+#define STR_5 "5"
+#define STR_6 "6"
+#define STR_7 "7"
+#define STR_8 "8"
+#define STR_9 "9"
+#define STR_COLON ":"
+#define STR_SEMICOLON ";"
+#define STR_LESS_THAN_SIGN "<"
+#define STR_EQUALS_SIGN "="
+#define STR_GREATER_THAN_SIGN ">"
+#define STR_QUESTION_MARK "?"
+#define STR_COMMERCIAL_AT "@"
+#define STR_A "A"
+#define STR_B "B"
+#define STR_C "C"
+#define STR_D "D"
+#define STR_E "E"
+#define STR_F "F"
+#define STR_G "G"
+#define STR_H "H"
+#define STR_I "I"
+#define STR_J "J"
+#define STR_K "K"
+#define STR_L "L"
+#define STR_M "M"
+#define STR_N "N"
+#define STR_O "O"
+#define STR_P "P"
+#define STR_Q "Q"
+#define STR_R "R"
+#define STR_S "S"
+#define STR_T "T"
+#define STR_U "U"
+#define STR_V "V"
+#define STR_W "W"
+#define STR_X "X"
+#define STR_Y "Y"
+#define STR_Z "Z"
+#define STR_LEFT_SQUARE_BRACKET "["
+#define STR_BACKSLASH "\\"
+#define STR_RIGHT_SQUARE_BRACKET "]"
+#define STR_CIRCUMFLEX_ACCENT "^"
+#define STR_UNDERSCORE "_"
+#define STR_GRAVE_ACCENT "`"
+#define STR_a "a"
+#define STR_b "b"
+#define STR_c "c"
+#define STR_d "d"
+#define STR_e "e"
+#define STR_f "f"
+#define STR_g "g"
+#define STR_h "h"
+#define STR_i "i"
+#define STR_j "j"
+#define STR_k "k"
+#define STR_l "l"
+#define STR_m "m"
+#define STR_n "n"
+#define STR_o "o"
+#define STR_p "p"
+#define STR_q "q"
+#define STR_r "r"
+#define STR_s "s"
+#define STR_t "t"
+#define STR_u "u"
+#define STR_v "v"
+#define STR_w "w"
+#define STR_x "x"
+#define STR_y "y"
+#define STR_z "z"
+#define STR_LEFT_CURLY_BRACKET "{"
+#define STR_VERTICAL_LINE "|"
+#define STR_RIGHT_CURLY_BRACKET "}"
+#define STR_TILDE "~"
+
+#define STRING_ACCEPT0 "ACCEPT\0"
+#define STRING_COMMIT0 "COMMIT\0"
+#define STRING_F0 "F\0"
+#define STRING_FAIL0 "FAIL\0"
+#define STRING_MARK0 "MARK\0"
+#define STRING_PRUNE0 "PRUNE\0"
+#define STRING_SKIP0 "SKIP\0"
+#define STRING_THEN "THEN"
+
+#define STRING_alpha0 "alpha\0"
+#define STRING_lower0 "lower\0"
+#define STRING_upper0 "upper\0"
+#define STRING_alnum0 "alnum\0"
+#define STRING_ascii0 "ascii\0"
+#define STRING_blank0 "blank\0"
+#define STRING_cntrl0 "cntrl\0"
+#define STRING_digit0 "digit\0"
+#define STRING_graph0 "graph\0"
+#define STRING_print0 "print\0"
+#define STRING_punct0 "punct\0"
+#define STRING_space0 "space\0"
+#define STRING_word0 "word\0"
+#define STRING_xdigit "xdigit"
+
+#define STRING_DEFINE "DEFINE"
+
+#define STRING_CR_RIGHTPAR "CR)"
+#define STRING_LF_RIGHTPAR "LF)"
+#define STRING_CRLF_RIGHTPAR "CRLF)"
+#define STRING_ANY_RIGHTPAR "ANY)"
+#define STRING_ANYCRLF_RIGHTPAR "ANYCRLF)"
+#define STRING_BSR_ANYCRLF_RIGHTPAR "BSR_ANYCRLF)"
+#define STRING_BSR_UNICODE_RIGHTPAR "BSR_UNICODE)"
+#define STRING_UTF8_RIGHTPAR "UTF8)"
+#define STRING_UCP_RIGHTPAR "UCP)"
+#define STRING_NO_START_OPT_RIGHTPAR "NO_START_OPT)"
+
+#else /* SUPPORT_UTF8 */
+
+/* UTF-8 support is enabled; always use UTF-8 (=ASCII) character codes. This
+works in both modes non-EBCDIC platforms, and on EBCDIC platforms in UTF-8 mode
+only. */
+
+#define CHAR_HT '\011'
+#define CHAR_VT '\013'
+#define CHAR_FF '\014'
+#define CHAR_CR '\015'
+#define CHAR_NL '\012'
+#define CHAR_BS '\010'
+#define CHAR_BEL '\007'
+#define CHAR_ESC '\033'
+#define CHAR_DEL '\177'
+
+#define CHAR_SPACE '\040'
+#define CHAR_EXCLAMATION_MARK '\041'
+#define CHAR_QUOTATION_MARK '\042'
+#define CHAR_NUMBER_SIGN '\043'
+#define CHAR_DOLLAR_SIGN '\044'
+#define CHAR_PERCENT_SIGN '\045'
+#define CHAR_AMPERSAND '\046'
+#define CHAR_APOSTROPHE '\047'
+#define CHAR_LEFT_PARENTHESIS '\050'
+#define CHAR_RIGHT_PARENTHESIS '\051'
+#define CHAR_ASTERISK '\052'
+#define CHAR_PLUS '\053'
+#define CHAR_COMMA '\054'
+#define CHAR_MINUS '\055'
+#define CHAR_DOT '\056'
+#define CHAR_SLASH '\057'
+#define CHAR_0 '\060'
+#define CHAR_1 '\061'
+#define CHAR_2 '\062'
+#define CHAR_3 '\063'
+#define CHAR_4 '\064'
+#define CHAR_5 '\065'
+#define CHAR_6 '\066'
+#define CHAR_7 '\067'
+#define CHAR_8 '\070'
+#define CHAR_9 '\071'
+#define CHAR_COLON '\072'
+#define CHAR_SEMICOLON '\073'
+#define CHAR_LESS_THAN_SIGN '\074'
+#define CHAR_EQUALS_SIGN '\075'
+#define CHAR_GREATER_THAN_SIGN '\076'
+#define CHAR_QUESTION_MARK '\077'
+#define CHAR_COMMERCIAL_AT '\100'
+#define CHAR_A '\101'
+#define CHAR_B '\102'
+#define CHAR_C '\103'
+#define CHAR_D '\104'
+#define CHAR_E '\105'
+#define CHAR_F '\106'
+#define CHAR_G '\107'
+#define CHAR_H '\110'
+#define CHAR_I '\111'
+#define CHAR_J '\112'
+#define CHAR_K '\113'
+#define CHAR_L '\114'
+#define CHAR_M '\115'
+#define CHAR_N '\116'
+#define CHAR_O '\117'
+#define CHAR_P '\120'
+#define CHAR_Q '\121'
+#define CHAR_R '\122'
+#define CHAR_S '\123'
+#define CHAR_T '\124'
+#define CHAR_U '\125'
+#define CHAR_V '\126'
+#define CHAR_W '\127'
+#define CHAR_X '\130'
+#define CHAR_Y '\131'
+#define CHAR_Z '\132'
+#define CHAR_LEFT_SQUARE_BRACKET '\133'
+#define CHAR_BACKSLASH '\134'
+#define CHAR_RIGHT_SQUARE_BRACKET '\135'
+#define CHAR_CIRCUMFLEX_ACCENT '\136'
+#define CHAR_UNDERSCORE '\137'
+#define CHAR_GRAVE_ACCENT '\140'
+#define CHAR_a '\141'
+#define CHAR_b '\142'
+#define CHAR_c '\143'
+#define CHAR_d '\144'
+#define CHAR_e '\145'
+#define CHAR_f '\146'
+#define CHAR_g '\147'
+#define CHAR_h '\150'
+#define CHAR_i '\151'
+#define CHAR_j '\152'
+#define CHAR_k '\153'
+#define CHAR_l '\154'
+#define CHAR_m '\155'
+#define CHAR_n '\156'
+#define CHAR_o '\157'
+#define CHAR_p '\160'
+#define CHAR_q '\161'
+#define CHAR_r '\162'
+#define CHAR_s '\163'
+#define CHAR_t '\164'
+#define CHAR_u '\165'
+#define CHAR_v '\166'
+#define CHAR_w '\167'
+#define CHAR_x '\170'
+#define CHAR_y '\171'
+#define CHAR_z '\172'
+#define CHAR_LEFT_CURLY_BRACKET '\173'
+#define CHAR_VERTICAL_LINE '\174'
+#define CHAR_RIGHT_CURLY_BRACKET '\175'
+#define CHAR_TILDE '\176'
+
+#define STR_HT "\011"
+#define STR_VT "\013"
+#define STR_FF "\014"
+#define STR_CR "\015"
+#define STR_NL "\012"
+#define STR_BS "\010"
+#define STR_BEL "\007"
+#define STR_ESC "\033"
+#define STR_DEL "\177"
+
+#define STR_SPACE "\040"
+#define STR_EXCLAMATION_MARK "\041"
+#define STR_QUOTATION_MARK "\042"
+#define STR_NUMBER_SIGN "\043"
+#define STR_DOLLAR_SIGN "\044"
+#define STR_PERCENT_SIGN "\045"
+#define STR_AMPERSAND "\046"
+#define STR_APOSTROPHE "\047"
+#define STR_LEFT_PARENTHESIS "\050"
+#define STR_RIGHT_PARENTHESIS "\051"
+#define STR_ASTERISK "\052"
+#define STR_PLUS "\053"
+#define STR_COMMA "\054"
+#define STR_MINUS "\055"
+#define STR_DOT "\056"
+#define STR_SLASH "\057"
+#define STR_0 "\060"
+#define STR_1 "\061"
+#define STR_2 "\062"
+#define STR_3 "\063"
+#define STR_4 "\064"
+#define STR_5 "\065"
+#define STR_6 "\066"
+#define STR_7 "\067"
+#define STR_8 "\070"
+#define STR_9 "\071"
+#define STR_COLON "\072"
+#define STR_SEMICOLON "\073"
+#define STR_LESS_THAN_SIGN "\074"
+#define STR_EQUALS_SIGN "\075"
+#define STR_GREATER_THAN_SIGN "\076"
+#define STR_QUESTION_MARK "\077"
+#define STR_COMMERCIAL_AT "\100"
+#define STR_A "\101"
+#define STR_B "\102"
+#define STR_C "\103"
+#define STR_D "\104"
+#define STR_E "\105"
+#define STR_F "\106"
+#define STR_G "\107"
+#define STR_H "\110"
+#define STR_I "\111"
+#define STR_J "\112"
+#define STR_K "\113"
+#define STR_L "\114"
+#define STR_M "\115"
+#define STR_N "\116"
+#define STR_O "\117"
+#define STR_P "\120"
+#define STR_Q "\121"
+#define STR_R "\122"
+#define STR_S "\123"
+#define STR_T "\124"
+#define STR_U "\125"
+#define STR_V "\126"
+#define STR_W "\127"
+#define STR_X "\130"
+#define STR_Y "\131"
+#define STR_Z "\132"
+#define STR_LEFT_SQUARE_BRACKET "\133"
+#define STR_BACKSLASH "\134"
+#define STR_RIGHT_SQUARE_BRACKET "\135"
+#define STR_CIRCUMFLEX_ACCENT "\136"
+#define STR_UNDERSCORE "\137"
+#define STR_GRAVE_ACCENT "\140"
+#define STR_a "\141"
+#define STR_b "\142"
+#define STR_c "\143"
+#define STR_d "\144"
+#define STR_e "\145"
+#define STR_f "\146"
+#define STR_g "\147"
+#define STR_h "\150"
+#define STR_i "\151"
+#define STR_j "\152"
+#define STR_k "\153"
+#define STR_l "\154"
+#define STR_m "\155"
+#define STR_n "\156"
+#define STR_o "\157"
+#define STR_p "\160"
+#define STR_q "\161"
+#define STR_r "\162"
+#define STR_s "\163"
+#define STR_t "\164"
+#define STR_u "\165"
+#define STR_v "\166"
+#define STR_w "\167"
+#define STR_x "\170"
+#define STR_y "\171"
+#define STR_z "\172"
+#define STR_LEFT_CURLY_BRACKET "\173"
+#define STR_VERTICAL_LINE "\174"
+#define STR_RIGHT_CURLY_BRACKET "\175"
+#define STR_TILDE "\176"
+
+#define STRING_ACCEPT0 STR_A STR_C STR_C STR_E STR_P STR_T "\0"
+#define STRING_COMMIT0 STR_C STR_O STR_M STR_M STR_I STR_T "\0"
+#define STRING_F0 STR_F "\0"
+#define STRING_FAIL0 STR_F STR_A STR_I STR_L "\0"
+#define STRING_MARK0 STR_M STR_A STR_R STR_K "\0"
+#define STRING_PRUNE0 STR_P STR_R STR_U STR_N STR_E "\0"
+#define STRING_SKIP0 STR_S STR_K STR_I STR_P "\0"
+#define STRING_THEN STR_T STR_H STR_E STR_N
+
+#define STRING_alpha0 STR_a STR_l STR_p STR_h STR_a "\0"
+#define STRING_lower0 STR_l STR_o STR_w STR_e STR_r "\0"
+#define STRING_upper0 STR_u STR_p STR_p STR_e STR_r "\0"
+#define STRING_alnum0 STR_a STR_l STR_n STR_u STR_m "\0"
+#define STRING_ascii0 STR_a STR_s STR_c STR_i STR_i "\0"
+#define STRING_blank0 STR_b STR_l STR_a STR_n STR_k "\0"
+#define STRING_cntrl0 STR_c STR_n STR_t STR_r STR_l "\0"
+#define STRING_digit0 STR_d STR_i STR_g STR_i STR_t "\0"
+#define STRING_graph0 STR_g STR_r STR_a STR_p STR_h "\0"
+#define STRING_print0 STR_p STR_r STR_i STR_n STR_t "\0"
+#define STRING_punct0 STR_p STR_u STR_n STR_c STR_t "\0"
+#define STRING_space0 STR_s STR_p STR_a STR_c STR_e "\0"
+#define STRING_word0 STR_w STR_o STR_r STR_d "\0"
+#define STRING_xdigit STR_x STR_d STR_i STR_g STR_i STR_t
+
+#define STRING_DEFINE STR_D STR_E STR_F STR_I STR_N STR_E
+
+#define STRING_CR_RIGHTPAR STR_C STR_R STR_RIGHT_PARENTHESIS
+#define STRING_LF_RIGHTPAR STR_L STR_F STR_RIGHT_PARENTHESIS
+#define STRING_CRLF_RIGHTPAR STR_C STR_R STR_L STR_F STR_RIGHT_PARENTHESIS
+#define STRING_ANY_RIGHTPAR STR_A STR_N STR_Y STR_RIGHT_PARENTHESIS
+#define STRING_ANYCRLF_RIGHTPAR STR_A STR_N STR_Y STR_C STR_R STR_L STR_F STR_RIGHT_PARENTHESIS
+#define STRING_BSR_ANYCRLF_RIGHTPAR STR_B STR_S STR_R STR_UNDERSCORE STR_A STR_N STR_Y STR_C STR_R STR_L STR_F STR_RIGHT_PARENTHESIS
+#define STRING_BSR_UNICODE_RIGHTPAR STR_B STR_S STR_R STR_UNDERSCORE STR_U STR_N STR_I STR_C STR_O STR_D STR_E STR_RIGHT_PARENTHESIS
+#define STRING_UTF8_RIGHTPAR STR_U STR_T STR_F STR_8 STR_RIGHT_PARENTHESIS
+#define STRING_UCP_RIGHTPAR STR_U STR_C STR_P STR_RIGHT_PARENTHESIS
+#define STRING_NO_START_OPT_RIGHTPAR STR_N STR_O STR_UNDERSCORE STR_S STR_T STR_A STR_R STR_T STR_UNDERSCORE STR_O STR_P STR_T STR_RIGHT_PARENTHESIS
+
+#endif /* SUPPORT_UTF8 */
+
+/* Escape items that are just an encoding of a particular data value. */
+
+#ifndef ESC_e
+#define ESC_e CHAR_ESC
+#endif
+
+#ifndef ESC_f
+#define ESC_f CHAR_FF
+#endif
+
+#ifndef ESC_n
+#define ESC_n CHAR_NL
+#endif
+
+#ifndef ESC_r
+#define ESC_r CHAR_CR
+#endif
+
+/* We can't officially use ESC_t because it is a POSIX reserved identifier
+(presumably because of all the others like size_t). */
+
+#ifndef ESC_tee
+#define ESC_tee CHAR_HT
+#endif
+
+/* Codes for different types of Unicode property */
+
+#define PT_ANY 0 /* Any property - matches all chars */
+#define PT_LAMP 1 /* L& - the union of Lu, Ll, Lt */
+#define PT_GC 2 /* Specified general characteristic (e.g. L) */
+#define PT_PC 3 /* Specified particular characteristic (e.g. Lu) */
+#define PT_SC 4 /* Script (e.g. Han) */
+#define PT_ALNUM 5 /* Alphanumeric - the union of L and N */
+#define PT_SPACE 6 /* Perl space - Z plus 9,10,12,13 */
+#define PT_PXSPACE 7 /* POSIX space - Z plus 9,10,11,12,13 */
+#define PT_WORD 8 /* Word - L plus N plus underscore */
+
+/* Flag bits and data types for the extended class (OP_XCLASS) for classes that
+contain UTF-8 characters with values greater than 255. */
+
+#define XCL_NOT 0x01 /* Flag: this is a negative class */
+#define XCL_MAP 0x02 /* Flag: a 32-byte map is present */
+
+#define XCL_END 0 /* Marks end of individual items */
+#define XCL_SINGLE 1 /* Single item (one multibyte char) follows */
+#define XCL_RANGE 2 /* A range (two multibyte chars) follows */
+#define XCL_PROP 3 /* Unicode property (2-byte property code follows) */
+#define XCL_NOTPROP 4 /* Unicode inverted property (ditto) */
+
+/* These are escaped items that aren't just an encoding of a particular data
+value such as \n. They must have non-zero values, as check_escape() returns
+their negation. Also, they must appear in the same order as in the opcode
+definitions below, up to ESC_z. There's a dummy for OP_ALLANY because it
+corresponds to "." in DOTALL mode rather than an escape sequence. It is also
+used for [^] in JavaScript compatibility mode. In non-DOTALL mode, "." behaves
+like \N.
+
+The special values ESC_DU, ESC_du, etc. are used instead of ESC_D, ESC_d, etc.
+when PCRE_UCP is set, when replacement of \d etc by \p sequences is required.
+They must be contiguous, and remain in order so that the replacements can be
+looked up from a table.
+
+The final escape must be ESC_REF as subsequent values are used for
+backreferences (\1, \2, \3, etc). There are two tests in the code for an escape
+greater than ESC_b and less than ESC_Z to detect the types that may be
+repeated. These are the types that consume characters. If any new escapes are
+put in between that don't consume a character, that code will have to change.
+*/
+
+enum { ESC_A = 1, ESC_G, ESC_K, ESC_B, ESC_b, ESC_D, ESC_d, ESC_S, ESC_s,
+ ESC_W, ESC_w, ESC_N, ESC_dum, ESC_C, ESC_P, ESC_p, ESC_R, ESC_H,
+ ESC_h, ESC_V, ESC_v, ESC_X, ESC_Z, ESC_z,
+ ESC_E, ESC_Q, ESC_g, ESC_k,
+ ESC_DU, ESC_du, ESC_SU, ESC_su, ESC_WU, ESC_wu,
+ ESC_REF };
+
+/* Opcode table: Starting from 1 (i.e. after OP_END), the values up to
+OP_EOD must correspond in order to the list of escapes immediately above.
+
+*** NOTE NOTE NOTE *** Whenever this list is updated, the two macro definitions
+that follow must also be updated to match. There are also tables called
+"coptable" and "poptable" in pcre_dfa_exec.c that must be updated. */
+
+enum {
+ OP_END, /* 0 End of pattern */
+
+ /* Values corresponding to backslashed metacharacters */
+
+ OP_SOD, /* 1 Start of data: \A */
+ OP_SOM, /* 2 Start of match (subject + offset): \G */
+ OP_SET_SOM, /* 3 Set start of match (\K) */
+ OP_NOT_WORD_BOUNDARY, /* 4 \B */
+ OP_WORD_BOUNDARY, /* 5 \b */
+ OP_NOT_DIGIT, /* 6 \D */
+ OP_DIGIT, /* 7 \d */
+ OP_NOT_WHITESPACE, /* 8 \S */
+ OP_WHITESPACE, /* 9 \s */
+ OP_NOT_WORDCHAR, /* 10 \W */
+ OP_WORDCHAR, /* 11 \w */
+
+ OP_ANY, /* 12 Match any character except newline */
+ OP_ALLANY, /* 13 Match any character */
+ OP_ANYBYTE, /* 14 Match any byte (\C); different to OP_ANY for UTF-8 */
+ OP_NOTPROP, /* 15 \P (not Unicode property) */
+ OP_PROP, /* 16 \p (Unicode property) */
+ OP_ANYNL, /* 17 \R (any newline sequence) */
+ OP_NOT_HSPACE, /* 18 \H (not horizontal whitespace) */
+ OP_HSPACE, /* 19 \h (horizontal whitespace) */
+ OP_NOT_VSPACE, /* 20 \V (not vertical whitespace) */
+ OP_VSPACE, /* 21 \v (vertical whitespace) */
+ OP_EXTUNI, /* 22 \X (extended Unicode sequence */
+ OP_EODN, /* 23 End of data or \n at end of data: \Z. */
+ OP_EOD, /* 24 End of data: \z */
+
+ OP_CIRC, /* 25 Start of line - not multiline */
+ OP_CIRCM, /* 26 Start of line - multiline */
+ OP_DOLL, /* 27 End of line - not multiline */
+ OP_DOLLM, /* 28 End of line - multiline */
+ OP_CHAR, /* 29 Match one character, casefully */
+ OP_CHARI, /* 30 Match one character, caselessly */
+ OP_NOT, /* 31 Match one character, not the given one, casefully */
+ OP_NOTI, /* 32 Match one character, not the given one, caselessly */
+
+ /* The following sets of 13 opcodes must always be kept in step because
+ the offset from the first one is used to generate the others. */
+
+ /**** Single characters, caseful, must precede the caseless ones ****/
+
+ OP_STAR, /* 33 The maximizing and minimizing versions of */
+ OP_MINSTAR, /* 34 these six opcodes must come in pairs, with */
+ OP_PLUS, /* 35 the minimizing one second. */
+ OP_MINPLUS, /* 36 */
+ OP_QUERY, /* 37 */
+ OP_MINQUERY, /* 38 */
+
+ OP_UPTO, /* 39 From 0 to n matches of one character, caseful*/
+ OP_MINUPTO, /* 40 */
+ OP_EXACT, /* 41 Exactly n matches */
+
+ OP_POSSTAR, /* 42 Possessified star, caseful */
+ OP_POSPLUS, /* 43 Possessified plus, caseful */
+ OP_POSQUERY, /* 44 Posesssified query, caseful */
+ OP_POSUPTO, /* 45 Possessified upto, caseful */
+
+ /**** Single characters, caseless, must follow the caseful ones */
+
+ OP_STARI, /* 46 */
+ OP_MINSTARI, /* 47 */
+ OP_PLUSI, /* 48 */
+ OP_MINPLUSI, /* 49 */
+ OP_QUERYI, /* 50 */
+ OP_MINQUERYI, /* 51 */
+
+ OP_UPTOI, /* 52 From 0 to n matches of one character, caseless */
+ OP_MINUPTOI, /* 53 */
+ OP_EXACTI, /* 54 */
+
+ OP_POSSTARI, /* 55 Possessified star, caseless */
+ OP_POSPLUSI, /* 56 Possessified plus, caseless */
+ OP_POSQUERYI, /* 57 Posesssified query, caseless */
+ OP_POSUPTOI, /* 58 Possessified upto, caseless */
+
+ /**** The negated ones must follow the non-negated ones, and match them ****/
+ /**** Negated single character, caseful; must precede the caseless ones ****/
+
+ OP_NOTSTAR, /* 59 The maximizing and minimizing versions of */
+ OP_NOTMINSTAR, /* 60 these six opcodes must come in pairs, with */
+ OP_NOTPLUS, /* 61 the minimizing one second. They must be in */
+ OP_NOTMINPLUS, /* 62 exactly the same order as those above. */
+ OP_NOTQUERY, /* 63 */
+ OP_NOTMINQUERY, /* 64 */
+
+ OP_NOTUPTO, /* 65 From 0 to n matches, caseful */
+ OP_NOTMINUPTO, /* 66 */
+ OP_NOTEXACT, /* 67 Exactly n matches */
+
+ OP_NOTPOSSTAR, /* 68 Possessified versions, caseful */
+ OP_NOTPOSPLUS, /* 69 */
+ OP_NOTPOSQUERY, /* 70 */
+ OP_NOTPOSUPTO, /* 71 */
+
+ /**** Negated single character, caseless; must follow the caseful ones ****/
+
+ OP_NOTSTARI, /* 72 */
+ OP_NOTMINSTARI, /* 73 */
+ OP_NOTPLUSI, /* 74 */
+ OP_NOTMINPLUSI, /* 75 */
+ OP_NOTQUERYI, /* 76 */
+ OP_NOTMINQUERYI, /* 77 */
+
+ OP_NOTUPTOI, /* 78 From 0 to n matches, caseless */
+ OP_NOTMINUPTOI, /* 79 */
+ OP_NOTEXACTI, /* 80 Exactly n matches */
+
+ OP_NOTPOSSTARI, /* 81 Possessified versions, caseless */
+ OP_NOTPOSPLUSI, /* 82 */
+ OP_NOTPOSQUERYI, /* 83 */
+ OP_NOTPOSUPTOI, /* 84 */
+
+ /**** Character types ****/
+
+ OP_TYPESTAR, /* 85 The maximizing and minimizing versions of */
+ OP_TYPEMINSTAR, /* 86 these six opcodes must come in pairs, with */
+ OP_TYPEPLUS, /* 87 the minimizing one second. These codes must */
+ OP_TYPEMINPLUS, /* 88 be in exactly the same order as those above. */
+ OP_TYPEQUERY, /* 89 */
+ OP_TYPEMINQUERY, /* 90 */
+
+ OP_TYPEUPTO, /* 91 From 0 to n matches */
+ OP_TYPEMINUPTO, /* 92 */
+ OP_TYPEEXACT, /* 93 Exactly n matches */
+
+ OP_TYPEPOSSTAR, /* 94 Possessified versions */
+ OP_TYPEPOSPLUS, /* 95 */
+ OP_TYPEPOSQUERY, /* 96 */
+ OP_TYPEPOSUPTO, /* 97 */
+
+ /* These are used for character classes and back references; only the
+ first six are the same as the sets above. */
+
+ OP_CRSTAR, /* 98 The maximizing and minimizing versions of */
+ OP_CRMINSTAR, /* 99 all these opcodes must come in pairs, with */
+ OP_CRPLUS, /* 100 the minimizing one second. These codes must */
+ OP_CRMINPLUS, /* 101 be in exactly the same order as those above. */
+ OP_CRQUERY, /* 102 */
+ OP_CRMINQUERY, /* 103 */
+
+ OP_CRRANGE, /* 104 These are different to the three sets above. */
+ OP_CRMINRANGE, /* 105 */
+
+ /* End of quantifier opcodes */
+
+ OP_CLASS, /* 106 Match a character class, chars < 256 only */
+ OP_NCLASS, /* 107 Same, but the bitmap was created from a negative
+ class - the difference is relevant only when a
+ UTF-8 character > 255 is encountered. */
+ OP_XCLASS, /* 108 Extended class for handling UTF-8 chars within the
+ class. This does both positive and negative. */
+ OP_REF, /* 109 Match a back reference, casefully */
+ OP_REFI, /* 110 Match a back reference, caselessly */
+ OP_RECURSE, /* 111 Match a numbered subpattern (possibly recursive) */
+ OP_CALLOUT, /* 112 Call out to external function if provided */
+
+ OP_ALT, /* 113 Start of alternation */
+ OP_KET, /* 114 End of group that doesn't have an unbounded repeat */
+ OP_KETRMAX, /* 115 These two must remain together and in this */
+ OP_KETRMIN, /* 116 order. They are for groups the repeat for ever. */
+ OP_KETRPOS, /* 117 Possessive unlimited repeat. */
+
+ /* The assertions must come before BRA, CBRA, ONCE, and COND, and the four
+ asserts must remain in order. */
+
+ OP_REVERSE, /* 118 Move pointer back - used in lookbehind assertions */
+ OP_ASSERT, /* 119 Positive lookahead */
+ OP_ASSERT_NOT, /* 120 Negative lookahead */
+ OP_ASSERTBACK, /* 121 Positive lookbehind */
+ OP_ASSERTBACK_NOT, /* 122 Negative lookbehind */
+
+ /* ONCE, BRA, BRAPOS, CBRA, CBRAPOS, and COND must come immediately after the
+ assertions, with ONCE first, as there's a test for >= ONCE for a subpattern
+ that isn't an assertion. The POS versions must immediately follow the non-POS
+ versions in each case. */
+
+ OP_ONCE, /* 123 Atomic group */
+ OP_BRA, /* 124 Start of non-capturing bracket */
+ OP_BRAPOS, /* 125 Ditto, with unlimited, possessive repeat */
+ OP_CBRA, /* 126 Start of capturing bracket */
+ OP_CBRAPOS, /* 127 Ditto, with unlimited, possessive repeat */
+ OP_COND, /* 128 Conditional group */
+
+ /* These five must follow the previous five, in the same order. There's a
+ check for >= SBRA to distinguish the two sets. */
+
+ OP_SBRA, /* 129 Start of non-capturing bracket, check empty */
+ OP_SBRAPOS, /* 130 Ditto, with unlimited, possessive repeat */
+ OP_SCBRA, /* 131 Start of capturing bracket, check empty */
+ OP_SCBRAPOS, /* 132 Ditto, with unlimited, possessive repeat */
+ OP_SCOND, /* 133 Conditional group, check empty */
+
+ /* The next two pairs must (respectively) be kept together. */
+
+ OP_CREF, /* 134 Used to hold a capture number as condition */
+ OP_NCREF, /* 135 Same, but generated by a name reference*/
+ OP_RREF, /* 136 Used to hold a recursion number as condition */
+ OP_NRREF, /* 137 Same, but generated by a name reference*/
+ OP_DEF, /* 138 The DEFINE condition */
+
+ OP_BRAZERO, /* 139 These two must remain together and in this */
+ OP_BRAMINZERO, /* 140 order. */
+ OP_BRAPOSZERO, /* 141 */
+
+ /* These are backtracking control verbs */
+
+ OP_MARK, /* 142 always has an argument */
+ OP_PRUNE, /* 143 */
+ OP_PRUNE_ARG, /* 144 same, but with argument */
+ OP_SKIP, /* 145 */
+ OP_SKIP_ARG, /* 146 same, but with argument */
+ OP_THEN, /* 147 */
+ OP_THEN_ARG, /* 148 same, but with argument */
+ OP_COMMIT, /* 149 */
+
+ /* These are forced failure and success verbs */
+
+ OP_FAIL, /* 150 */
+ OP_ACCEPT, /* 151 */
+ OP_ASSERT_ACCEPT, /* 152 Used inside assertions */
+ OP_CLOSE, /* 153 Used before OP_ACCEPT to close open captures */
+
+ /* This is used to skip a subpattern with a {0} quantifier */
+
+ OP_SKIPZERO, /* 154 */
+
+ /* This is not an opcode, but is used to check that tables indexed by opcode
+ are the correct length, in order to catch updating errors - there have been
+ some in the past. */
+
+ OP_TABLE_LENGTH
+};
+
+/* *** NOTE NOTE NOTE *** Whenever the list above is updated, the two macro
+definitions that follow must also be updated to match. There are also tables
+called "coptable" and "poptable" in pcre_dfa_exec.c that must be updated. */
+
+
+/* This macro defines textual names for all the opcodes. These are used only
+for debugging, and some of them are only partial names. The macro is referenced
+only in pcre_printint.c, which fills out the full names in many cases (and in
+some cases doesn't actually use these names at all). */
+
+#define OP_NAME_LIST \
+ "End", "\\A", "\\G", "\\K", "\\B", "\\b", "\\D", "\\d", \
+ "\\S", "\\s", "\\W", "\\w", "Any", "AllAny", "Anybyte", \
+ "notprop", "prop", "\\R", "\\H", "\\h", "\\V", "\\v", \
+ "extuni", "\\Z", "\\z", \
+ "^", "^", "$", "$", "char", "chari", "not", "noti", \
+ "*", "*?", "+", "+?", "?", "??", \
+ "{", "{", "{", \
+ "*+","++", "?+", "{", \
+ "*", "*?", "+", "+?", "?", "??", \
+ "{", "{", "{", \
+ "*+","++", "?+", "{", \
+ "*", "*?", "+", "+?", "?", "??", \
+ "{", "{", "{", \
+ "*+","++", "?+", "{", \
+ "*", "*?", "+", "+?", "?", "??", \
+ "{", "{", "{", \
+ "*+","++", "?+", "{", \
+ "*", "*?", "+", "+?", "?", "??", "{", "{", "{", \
+ "*+","++", "?+", "{", \
+ "*", "*?", "+", "+?", "?", "??", "{", "{", \
+ "class", "nclass", "xclass", "Ref", "Refi", \
+ "Recurse", "Callout", \
+ "Alt", "Ket", "KetRmax", "KetRmin", "KetRpos", \
+ "Reverse", "Assert", "Assert not", "AssertB", "AssertB not", \
+ "Once", \
+ "Bra", "BraPos", "CBra", "CBraPos", \
+ "Cond", \
+ "SBra", "SBraPos", "SCBra", "SCBraPos", \
+ "SCond", \
+ "Cond ref", "Cond nref", "Cond rec", "Cond nrec", "Cond def", \
+ "Brazero", "Braminzero", "Braposzero", \
+ "*MARK", "*PRUNE", "*PRUNE", "*SKIP", "*SKIP", \
+ "*THEN", "*THEN", "*COMMIT", "*FAIL", \
+ "*ACCEPT", "*ASSERT_ACCEPT", \
+ "Close", "Skip zero"
+
+
+/* This macro defines the length of fixed length operations in the compiled
+regex. The lengths are used when searching for specific things, and also in the
+debugging printing of a compiled regex. We use a macro so that it can be
+defined close to the definitions of the opcodes themselves.
+
+As things have been extended, some of these are no longer fixed lenths, but are
+minima instead. For example, the length of a single-character repeat may vary
+in UTF-8 mode. The code that uses this table must know about such things. */
+
+#define OP_LENGTHS \
+ 1, /* End */ \
+ 1, 1, 1, 1, 1, /* \A, \G, \K, \B, \b */ \
+ 1, 1, 1, 1, 1, 1, /* \D, \d, \S, \s, \W, \w */ \
+ 1, 1, 1, /* Any, AllAny, Anybyte */ \
+ 3, 3, /* \P, \p */ \
+ 1, 1, 1, 1, 1, /* \R, \H, \h, \V, \v */ \
+ 1, /* \X */ \
+ 1, 1, 1, 1, 1, 1, /* \Z, \z, ^, ^M, $, $M */ \
+ 2, /* Char - the minimum length */ \
+ 2, /* Chari - the minimum length */ \
+ 2, /* not */ \
+ 2, /* noti */ \
+ /* Positive single-char repeats ** These are */ \
+ 2, 2, 2, 2, 2, 2, /* *, *?, +, +?, ?, ?? ** minima in */ \
+ 4, 4, 4, /* upto, minupto, exact ** mode */ \
+ 2, 2, 2, 4, /* *+, ++, ?+, upto+ */ \
+ 2, 2, 2, 2, 2, 2, /* *I, *?I, +I, +?I, ?I, ??I ** UTF-8 */ \
+ 4, 4, 4, /* upto I, minupto I, exact I */ \
+ 2, 2, 2, 4, /* *+I, ++I, ?+I, upto+I */ \
+ /* Negative single-char repeats - only for chars < 256 */ \
+ 2, 2, 2, 2, 2, 2, /* NOT *, *?, +, +?, ?, ?? */ \
+ 4, 4, 4, /* NOT upto, minupto, exact */ \
+ 2, 2, 2, 4, /* Possessive NOT *, +, ?, upto */ \
+ 2, 2, 2, 2, 2, 2, /* NOT *I, *?I, +I, +?I, ?I, ??I */ \
+ 4, 4, 4, /* NOT upto I, minupto I, exact I */ \
+ 2, 2, 2, 4, /* Possessive NOT *I, +I, ?I, upto I */ \
+ /* Positive type repeats */ \
+ 2, 2, 2, 2, 2, 2, /* Type *, *?, +, +?, ?, ?? */ \
+ 4, 4, 4, /* Type upto, minupto, exact */ \
+ 2, 2, 2, 4, /* Possessive *+, ++, ?+, upto+ */ \
+ /* Character class & ref repeats */ \
+ 1, 1, 1, 1, 1, 1, /* *, *?, +, +?, ?, ?? */ \
+ 5, 5, /* CRRANGE, CRMINRANGE */ \
+ 33, /* CLASS */ \
+ 33, /* NCLASS */ \
+ 0, /* XCLASS - variable length */ \
+ 3, /* REF */ \
+ 3, /* REFI */ \
+ 1+LINK_SIZE, /* RECURSE */ \
+ 2+2*LINK_SIZE, /* CALLOUT */ \
+ 1+LINK_SIZE, /* Alt */ \
+ 1+LINK_SIZE, /* Ket */ \
+ 1+LINK_SIZE, /* KetRmax */ \
+ 1+LINK_SIZE, /* KetRmin */ \
+ 1+LINK_SIZE, /* KetRpos */ \
+ 1+LINK_SIZE, /* Reverse */ \
+ 1+LINK_SIZE, /* Assert */ \
+ 1+LINK_SIZE, /* Assert not */ \
+ 1+LINK_SIZE, /* Assert behind */ \
+ 1+LINK_SIZE, /* Assert behind not */ \
+ 1+LINK_SIZE, /* ONCE */ \
+ 1+LINK_SIZE, /* BRA */ \
+ 1+LINK_SIZE, /* BRAPOS */ \
+ 3+LINK_SIZE, /* CBRA */ \
+ 3+LINK_SIZE, /* CBRAPOS */ \
+ 1+LINK_SIZE, /* COND */ \
+ 1+LINK_SIZE, /* SBRA */ \
+ 1+LINK_SIZE, /* SBRAPOS */ \
+ 3+LINK_SIZE, /* SCBRA */ \
+ 3+LINK_SIZE, /* SCBRAPOS */ \
+ 1+LINK_SIZE, /* SCOND */ \
+ 3, 3, /* CREF, NCREF */ \
+ 3, 3, /* RREF, NRREF */ \
+ 1, /* DEF */ \
+ 1, 1, 1, /* BRAZERO, BRAMINZERO, BRAPOSZERO */ \
+ 3, 1, 3, /* MARK, PRUNE, PRUNE_ARG */ \
+ 1, 3, /* SKIP, SKIP_ARG */ \
+ 1+LINK_SIZE, 3+LINK_SIZE, /* THEN, THEN_ARG */ \
+ 1, 1, 1, 1, /* COMMIT, FAIL, ACCEPT, ASSERT_ACCEPT */ \
+ 3, 1 /* CLOSE, SKIPZERO */
+
+/* A magic value for OP_RREF and OP_NRREF to indicate the "any recursion"
+condition. */
+
+#define RREF_ANY 0xffff
+
+/* Compile time error code numbers. They are given names so that they can more
+easily be tracked. When a new number is added, the table called eint in
+pcreposix.c must be updated. */
+
+enum { ERR0, ERR1, ERR2, ERR3, ERR4, ERR5, ERR6, ERR7, ERR8, ERR9,
+ ERR10, ERR11, ERR12, ERR13, ERR14, ERR15, ERR16, ERR17, ERR18, ERR19,
+ ERR20, ERR21, ERR22, ERR23, ERR24, ERR25, ERR26, ERR27, ERR28, ERR29,
+ ERR30, ERR31, ERR32, ERR33, ERR34, ERR35, ERR36, ERR37, ERR38, ERR39,
+ ERR40, ERR41, ERR42, ERR43, ERR44, ERR45, ERR46, ERR47, ERR48, ERR49,
+ ERR50, ERR51, ERR52, ERR53, ERR54, ERR55, ERR56, ERR57, ERR58, ERR59,
+ ERR60, ERR61, ERR62, ERR63, ERR64, ERR65, ERR66, ERR67, ERR68, ERR69,
+ ERRCOUNT };
+
+/* The real format of the start of the pcre block; the index of names and the
+code vector run on as long as necessary after the end. We store an explicit
+offset to the name table so that if a regex is compiled on one host, saved, and
+then run on another where the size of pointers is different, all might still
+be well. For the case of compiled-on-4 and run-on-8, we include an extra
+pointer that is always NULL. For future-proofing, a few dummy fields were
+originally included - even though you can never get this planning right - but
+there is only one left now.
+
+NOTE NOTE NOTE:
+Because people can now save and re-use compiled patterns, any additions to this
+structure should be made at the end, and something earlier (e.g. a new
+flag in the options or one of the dummy fields) should indicate that the new
+fields are present. Currently PCRE always sets the dummy fields to zero.
+NOTE NOTE NOTE
+*/
+
+typedef struct real_pcre {
+ pcre_uint32 magic_number;
+ pcre_uint32 size; /* Total that was malloced */
+ pcre_uint32 options; /* Public options */
+ pcre_uint16 flags; /* Private flags */
+ pcre_uint16 dummy1; /* For future use */
+ pcre_uint16 top_bracket;
+ pcre_uint16 top_backref;
+ pcre_uint16 first_byte;
+ pcre_uint16 req_byte;
+ pcre_uint16 name_table_offset; /* Offset to name table that follows */
+ pcre_uint16 name_entry_size; /* Size of any name items */
+ pcre_uint16 name_count; /* Number of name items */
+ pcre_uint16 ref_count; /* Reference count */
+
+ const unsigned char *tables; /* Pointer to tables or NULL for std */
+ const unsigned char *nullpad; /* NULL padding */
+} real_pcre;
+
+/* The format of the block used to store data from pcre_study(). The same
+remark (see NOTE above) about extending this structure applies. */
+
+typedef struct pcre_study_data {
+ pcre_uint32 size; /* Total that was malloced */
+ pcre_uint32 flags; /* Private flags */
+ uschar start_bits[32]; /* Starting char bits */
+ pcre_uint32 minlength; /* Minimum subject length */
+} pcre_study_data;
+
+/* Structure for building a chain of open capturing subpatterns during
+compiling, so that instructions to close them can be compiled when (*ACCEPT) is
+encountered. This is also used to identify subpatterns that contain recursive
+back references to themselves, so that they can be made atomic. */
+
+typedef struct open_capitem {
+ struct open_capitem *next; /* Chain link */
+ pcre_uint16 number; /* Capture number */
+ pcre_uint16 flag; /* Set TRUE if recursive back ref */
+} open_capitem;
+
+/* Structure for passing "static" information around between the functions
+doing the compiling, so that they are thread-safe. */
+
+typedef struct compile_data {
+ const uschar *lcc; /* Points to lower casing table */
+ const uschar *fcc; /* Points to case-flipping table */
+ const uschar *cbits; /* Points to character type table */
+ const uschar *ctypes; /* Points to table of type maps */
+ const uschar *start_workspace;/* The start of working space */
+ const uschar *start_code; /* The start of the compiled code */
+ const uschar *start_pattern; /* The start of the pattern */
+ const uschar *end_pattern; /* The end of the pattern */
+ open_capitem *open_caps; /* Chain of open capture items */
+ uschar *hwm; /* High watermark of workspace */
+ uschar *name_table; /* The name/number table */
+ int names_found; /* Number of entries so far */
+ int name_entry_size; /* Size of each entry */
+ int bracount; /* Count of capturing parens as we compile */
+ int final_bracount; /* Saved value after first pass */
+ int top_backref; /* Maximum back reference */
+ unsigned int backref_map; /* Bitmap of low back refs */
+ int assert_depth; /* Depth of nested assertions */
+ int external_options; /* External (initial) options */
+ int external_flags; /* External flag bits to be set */
+ int req_varyopt; /* "After variable item" flag for reqbyte */
+ BOOL had_accept; /* (*ACCEPT) encountered */
+ BOOL check_lookbehind; /* Lookbehinds need later checking */
+ int nltype; /* Newline type */
+ int nllen; /* Newline string length */
+ uschar nl[4]; /* Newline string when fixed length */
+} compile_data;
+
+/* Structure for maintaining a chain of pointers to the currently incomplete
+branches, for testing for left recursion while compiling. */
+
+typedef struct branch_chain {
+ struct branch_chain *outer;
+ uschar *current_branch;
+} branch_chain;
+
+/* Structure for items in a linked list that represents an explicit recursive
+call within the pattern; used by pcre_exec(). */
+
+typedef struct recursion_info {
+ struct recursion_info *prevrec; /* Previous recursion record (or NULL) */
+ int group_num; /* Number of group that was called */
+ int *offset_save; /* Pointer to start of saved offsets */
+ int saved_max; /* Number of saved offsets */
+ USPTR subject_position; /* Position at start of recursion */
+} recursion_info;
+
+/* A similar structure for pcre_dfa_exec(). */
+
+typedef struct dfa_recursion_info {
+ struct dfa_recursion_info *prevrec;
+ int group_num;
+ USPTR subject_position;
+} dfa_recursion_info;
+
+/* Structure for building a chain of data for holding the values of the subject
+pointer at the start of each subpattern, so as to detect when an empty string
+has been matched by a subpattern - to break infinite loops; used by
+pcre_exec(). */
+
+typedef struct eptrblock {
+ struct eptrblock *epb_prev;
+ USPTR epb_saved_eptr;
+} eptrblock;
+
+
+/* Structure for passing "static" information around between the functions
+doing traditional NFA matching, so that they are thread-safe. */
+
+typedef struct match_data {
+ unsigned long int match_call_count; /* As it says */
+ unsigned long int match_limit; /* As it says */
+ unsigned long int match_limit_recursion; /* As it says */
+ int *offset_vector; /* Offset vector */
+ int offset_end; /* One past the end */
+ int offset_max; /* The maximum usable for return data */
+ int nltype; /* Newline type */
+ int nllen; /* Newline string length */
+ int name_count; /* Number of names in name table */
+ int name_entry_size; /* Size of entry in names table */
+ uschar *name_table; /* Table of names */
+ uschar nl[4]; /* Newline string when fixed */
+ const uschar *lcc; /* Points to lower casing table */
+ const uschar *ctypes; /* Points to table of type maps */
+ BOOL offset_overflow; /* Set if too many extractions */
+ BOOL notbol; /* NOTBOL flag */
+ BOOL noteol; /* NOTEOL flag */
+ BOOL utf8; /* UTF8 flag */
+ BOOL jscript_compat; /* JAVASCRIPT_COMPAT flag */
+ BOOL use_ucp; /* PCRE_UCP flag */
+ BOOL endonly; /* Dollar not before final \n */
+ BOOL notempty; /* Empty string match not wanted */
+ BOOL notempty_atstart; /* Empty string match at start not wanted */
+ BOOL hitend; /* Hit the end of the subject at some point */
+ BOOL bsr_anycrlf; /* \R is just any CRLF, not full Unicode */
+ const uschar *start_code; /* For use when recursing */
+ USPTR start_subject; /* Start of the subject string */
+ USPTR end_subject; /* End of the subject string */
+ USPTR start_match_ptr; /* Start of matched string */
+ USPTR end_match_ptr; /* Subject position at end match */
+ USPTR start_used_ptr; /* Earliest consulted character */
+ int partial; /* PARTIAL options */
+ int end_offset_top; /* Highwater mark at end of match */
+ int capture_last; /* Most recent capture number */
+ int start_offset; /* The start offset value */
+ int match_function_type; /* Set for certain special calls of MATCH() */
+ eptrblock *eptrchain; /* Chain of eptrblocks for tail recursions */
+ int eptrn; /* Next free eptrblock */
+ recursion_info *recursive; /* Linked list of recursion data */
+ void *callout_data; /* To pass back to callouts */
+ const uschar *mark; /* Mark pointer to pass back */
+ const uschar *once_target; /* Where to back up to for atomic groups */
+} match_data;
+
+/* A similar structure is used for the same purpose by the DFA matching
+functions. */
+
+typedef struct dfa_match_data {
+ const uschar *start_code; /* Start of the compiled pattern */
+ const uschar *start_subject; /* Start of the subject string */
+ const uschar *end_subject; /* End of subject string */
+ const uschar *start_used_ptr; /* Earliest consulted character */
+ const uschar *tables; /* Character tables */
+ int start_offset; /* The start offset value */
+ int moptions; /* Match options */
+ int poptions; /* Pattern options */
+ int nltype; /* Newline type */
+ int nllen; /* Newline string length */
+ uschar nl[4]; /* Newline string when fixed */
+ void *callout_data; /* To pass back to callouts */
+ dfa_recursion_info *recursive; /* Linked list of recursion data */
+} dfa_match_data;
+
+/* Bit definitions for entries in the pcre_ctypes table. */
+
+#define ctype_space 0x01
+#define ctype_letter 0x02
+#define ctype_digit 0x04
+#define ctype_xdigit 0x08
+#define ctype_word 0x10 /* alphanumeric or '_' */
+#define ctype_meta 0x80 /* regexp meta char or zero (end pattern) */
+
+/* Offsets for the bitmap tables in pcre_cbits. Each table contains a set
+of bits for a class map. Some classes are built by combining these tables. */
+
+#define cbit_space 0 /* [:space:] or \s */
+#define cbit_xdigit 32 /* [:xdigit:] */
+#define cbit_digit 64 /* [:digit:] or \d */
+#define cbit_upper 96 /* [:upper:] */
+#define cbit_lower 128 /* [:lower:] */
+#define cbit_word 160 /* [:word:] or \w */
+#define cbit_graph 192 /* [:graph:] */
+#define cbit_print 224 /* [:print:] */
+#define cbit_punct 256 /* [:punct:] */
+#define cbit_cntrl 288 /* [:cntrl:] */
+#define cbit_length 320 /* Length of the cbits table */
+
+/* Offsets of the various tables from the base tables pointer, and
+total length. */
+
+#define lcc_offset 0
+#define fcc_offset 256
+#define cbits_offset 512
+#define ctypes_offset (cbits_offset + cbit_length)
+#define tables_length (ctypes_offset + 256)
+
+/* Layout of the UCP type table that translates property names into types and
+codes. Each entry used to point directly to a name, but to reduce the number of
+relocations in shared libraries, it now has an offset into a single string
+instead. */
+
+typedef struct {
+ pcre_uint16 name_offset;
+ pcre_uint16 type;
+ pcre_uint16 value;
+} ucp_type_table;
+
+
+/* Internal shared data tables. These are tables that are used by more than one
+of the exported public functions. They have to be "external" in the C sense,
+but are not part of the PCRE public API. The data for these tables is in the
+pcre_tables.c module. */
+
+extern const int _pcre_utf8_table1[];
+extern const int _pcre_utf8_table2[];
+extern const int _pcre_utf8_table3[];
+extern const uschar _pcre_utf8_table4[];
+
+extern const int _pcre_utf8_table1_size;
+
+extern const char _pcre_utt_names[];
+extern const ucp_type_table _pcre_utt[];
+extern const int _pcre_utt_size;
+
+extern const uschar _pcre_default_tables[];
+
+extern const uschar _pcre_OP_lengths[];
+
+
+/* Internal shared functions. These are functions that are used by more than
+one of the exported public functions. They have to be "external" in the C
+sense, but are not part of the PCRE public API. */
+
+extern const uschar *_pcre_find_bracket(const uschar *, BOOL, int);
+extern BOOL _pcre_is_newline(USPTR, int, USPTR, int *, BOOL);
+extern int _pcre_ord2utf8(int, uschar *);
+extern real_pcre *_pcre_try_flipped(const real_pcre *, real_pcre *,
+ const pcre_study_data *, pcre_study_data *);
+extern int _pcre_valid_utf8(USPTR, int, int *);
+extern BOOL _pcre_was_newline(USPTR, int, USPTR, int *, BOOL);
+extern BOOL _pcre_xclass(int, const uschar *);
+
+
+/* Unicode character database (UCD) */
+
+typedef struct {
+ uschar script;
+ uschar chartype;
+ pcre_int32 other_case;
+} ucd_record;
+
+extern const ucd_record _pcre_ucd_records[];
+extern const uschar _pcre_ucd_stage1[];
+extern const pcre_uint16 _pcre_ucd_stage2[];
+extern const int _pcre_ucp_gentype[];
+
+
+/* UCD access macros */
+
+#define UCD_BLOCK_SIZE 128
+#define GET_UCD(ch) (_pcre_ucd_records + \
+ _pcre_ucd_stage2[_pcre_ucd_stage1[(ch) / UCD_BLOCK_SIZE] * \
+ UCD_BLOCK_SIZE + (ch) % UCD_BLOCK_SIZE])
+
+#define UCD_CHARTYPE(ch) GET_UCD(ch)->chartype
+#define UCD_SCRIPT(ch) GET_UCD(ch)->script
+#define UCD_CATEGORY(ch) _pcre_ucp_gentype[UCD_CHARTYPE(ch)]
+#define UCD_OTHERCASE(ch) (ch + GET_UCD(ch)->other_case)
+
+#endif
+
+/* End of pcre_internal.h */
diff --git a/usr.sbin/nginx/src/pcre/pcre_newline.c b/usr.sbin/nginx/src/pcre/pcre_newline.c
new file mode 100644
index 00000000000..38cf7f72f8d
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/pcre_newline.c
@@ -0,0 +1,162 @@
+/*************************************************
+* Perl-Compatible Regular Expressions *
+*************************************************/
+
+/* PCRE is a library of functions to support regular expressions whose syntax
+and semantics are as close as possible to those of the Perl 5 language.
+
+ Written by Philip Hazel
+ Copyright (c) 1997-2009 University of Cambridge
+
+-----------------------------------------------------------------------------
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the University of Cambridge nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+-----------------------------------------------------------------------------
+*/
+
+
+/* This module contains internal functions for testing newlines when more than
+one kind of newline is to be recognized. When a newline is found, its length is
+returned. In principle, we could implement several newline "types", each
+referring to a different set of newline characters. At present, PCRE supports
+only NLTYPE_FIXED, which gets handled without these functions, NLTYPE_ANYCRLF,
+and NLTYPE_ANY. The full list of Unicode newline characters is taken from
+http://unicode.org/unicode/reports/tr18/. */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pcre_internal.h"
+
+
+
+/*************************************************
+* Check for newline at given position *
+*************************************************/
+
+/* It is guaranteed that the initial value of ptr is less than the end of the
+string that is being processed.
+
+Arguments:
+ ptr pointer to possible newline
+ type the newline type
+ endptr pointer to the end of the string
+ lenptr where to return the length
+ utf8 TRUE if in utf8 mode
+
+Returns: TRUE or FALSE
+*/
+
+BOOL
+_pcre_is_newline(USPTR ptr, int type, USPTR endptr, int *lenptr, BOOL utf8)
+{
+int c;
+if (utf8) { GETCHAR(c, ptr); } else c = *ptr;
+
+if (type == NLTYPE_ANYCRLF) switch(c)
+ {
+ case 0x000a: *lenptr = 1; return TRUE; /* LF */
+ case 0x000d: *lenptr = (ptr < endptr - 1 && ptr[1] == 0x0a)? 2 : 1;
+ return TRUE; /* CR */
+ default: return FALSE;
+ }
+
+/* NLTYPE_ANY */
+
+else switch(c)
+ {
+ case 0x000a: /* LF */
+ case 0x000b: /* VT */
+ case 0x000c: *lenptr = 1; return TRUE; /* FF */
+ case 0x000d: *lenptr = (ptr < endptr - 1 && ptr[1] == 0x0a)? 2 : 1;
+ return TRUE; /* CR */
+ case 0x0085: *lenptr = utf8? 2 : 1; return TRUE; /* NEL */
+ case 0x2028: /* LS */
+ case 0x2029: *lenptr = 3; return TRUE; /* PS */
+ default: return FALSE;
+ }
+}
+
+
+
+/*************************************************
+* Check for newline at previous position *
+*************************************************/
+
+/* It is guaranteed that the initial value of ptr is greater than the start of
+the string that is being processed.
+
+Arguments:
+ ptr pointer to possible newline
+ type the newline type
+ startptr pointer to the start of the string
+ lenptr where to return the length
+ utf8 TRUE if in utf8 mode
+
+Returns: TRUE or FALSE
+*/
+
+BOOL
+_pcre_was_newline(USPTR ptr, int type, USPTR startptr, int *lenptr, BOOL utf8)
+{
+int c;
+ptr--;
+#ifdef SUPPORT_UTF8
+if (utf8)
+ {
+ BACKCHAR(ptr);
+ GETCHAR(c, ptr);
+ }
+else c = *ptr;
+#else /* no UTF-8 support */
+c = *ptr;
+#endif /* SUPPORT_UTF8 */
+
+if (type == NLTYPE_ANYCRLF) switch(c)
+ {
+ case 0x000a: *lenptr = (ptr > startptr && ptr[-1] == 0x0d)? 2 : 1;
+ return TRUE; /* LF */
+ case 0x000d: *lenptr = 1; return TRUE; /* CR */
+ default: return FALSE;
+ }
+
+else switch(c)
+ {
+ case 0x000a: *lenptr = (ptr > startptr && ptr[-1] == 0x0d)? 2 : 1;
+ return TRUE; /* LF */
+ case 0x000b: /* VT */
+ case 0x000c: /* FF */
+ case 0x000d: *lenptr = 1; return TRUE; /* CR */
+ case 0x0085: *lenptr = utf8? 2 : 1; return TRUE; /* NEL */
+ case 0x2028: /* LS */
+ case 0x2029: *lenptr = 3; return TRUE; /* PS */
+ default: return FALSE;
+ }
+}
+
+/* End of pcre_newline.c */
diff --git a/usr.sbin/nginx/src/pcre/pcre_ord2utf8.c b/usr.sbin/nginx/src/pcre/pcre_ord2utf8.c
new file mode 100644
index 00000000000..6f4eb9ebe95
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/pcre_ord2utf8.c
@@ -0,0 +1,87 @@
+/*************************************************
+* Perl-Compatible Regular Expressions *
+*************************************************/
+
+/* PCRE is a library of functions to support regular expressions whose syntax
+and semantics are as close as possible to those of the Perl 5 language.
+
+ Written by Philip Hazel
+ Copyright (c) 1997-2008 University of Cambridge
+
+-----------------------------------------------------------------------------
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the University of Cambridge nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+-----------------------------------------------------------------------------
+*/
+
+
+/* This file contains a private PCRE function that converts an ordinal
+character value into a UTF8 string. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pcre_internal.h"
+
+
+/*************************************************
+* Convert character value to UTF-8 *
+*************************************************/
+
+/* This function takes an integer value in the range 0 - 0x7fffffff
+and encodes it as a UTF-8 character in 0 to 6 bytes.
+
+Arguments:
+ cvalue the character value
+ buffer pointer to buffer for result - at least 6 bytes long
+
+Returns: number of characters placed in the buffer
+*/
+
+int
+_pcre_ord2utf8(int cvalue, uschar *buffer)
+{
+#ifdef SUPPORT_UTF8
+register int i, j;
+for (i = 0; i < _pcre_utf8_table1_size; i++)
+ if (cvalue <= _pcre_utf8_table1[i]) break;
+buffer += i;
+for (j = i; j > 0; j--)
+ {
+ *buffer-- = 0x80 | (cvalue & 0x3f);
+ cvalue >>= 6;
+ }
+*buffer = _pcre_utf8_table2[i] | cvalue;
+return i + 1;
+#else
+(void)(cvalue); /* Keep compiler happy; this function won't ever be */
+(void)(buffer); /* called when SUPPORT_UTF8 is not defined. */
+return 0;
+#endif
+}
+
+/* End of pcre_ord2utf8.c */
diff --git a/usr.sbin/nginx/src/pcre/pcre_tables.c b/usr.sbin/nginx/src/pcre/pcre_tables.c
new file mode 100644
index 00000000000..e3e6dc192f5
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/pcre_tables.c
@@ -0,0 +1,544 @@
+/*************************************************
+* Perl-Compatible Regular Expressions *
+*************************************************/
+
+/* PCRE is a library of functions to support regular expressions whose syntax
+and semantics are as close as possible to those of the Perl 5 language.
+
+ Written by Philip Hazel
+ Copyright (c) 1997-2009 University of Cambridge
+
+-----------------------------------------------------------------------------
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the University of Cambridge nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+-----------------------------------------------------------------------------
+*/
+
+
+/* This module contains some fixed tables that are used by more than one of the
+PCRE code modules. The tables are also #included by the pcretest program, which
+uses macros to change their names from _pcre_xxx to xxxx, thereby avoiding name
+clashes with the library. */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pcre_internal.h"
+
+
+/* Table of sizes for the fixed-length opcodes. It's defined in a macro so that
+the definition is next to the definition of the opcodes in pcre_internal.h. */
+
+const uschar _pcre_OP_lengths[] = { OP_LENGTHS };
+
+
+
+/*************************************************
+* Tables for UTF-8 support *
+*************************************************/
+
+/* These are the breakpoints for different numbers of bytes in a UTF-8
+character. */
+
+#ifdef SUPPORT_UTF8
+
+const int _pcre_utf8_table1[] =
+ { 0x7f, 0x7ff, 0xffff, 0x1fffff, 0x3ffffff, 0x7fffffff};
+
+const int _pcre_utf8_table1_size = sizeof(_pcre_utf8_table1)/sizeof(int);
+
+/* These are the indicator bits and the mask for the data bits to set in the
+first byte of a character, indexed by the number of additional bytes. */
+
+const int _pcre_utf8_table2[] = { 0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc};
+const int _pcre_utf8_table3[] = { 0xff, 0x1f, 0x0f, 0x07, 0x03, 0x01};
+
+/* Table of the number of extra bytes, indexed by the first byte masked with
+0x3f. The highest number for a valid UTF-8 first byte is in fact 0x3d. */
+
+const uschar _pcre_utf8_table4[] = {
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 };
+
+/* Table to translate from particular type value to the general value. */
+
+const int _pcre_ucp_gentype[] = {
+ ucp_C, ucp_C, ucp_C, ucp_C, ucp_C, /* Cc, Cf, Cn, Co, Cs */
+ ucp_L, ucp_L, ucp_L, ucp_L, ucp_L, /* Ll, Lu, Lm, Lo, Lt */
+ ucp_M, ucp_M, ucp_M, /* Mc, Me, Mn */
+ ucp_N, ucp_N, ucp_N, /* Nd, Nl, No */
+ ucp_P, ucp_P, ucp_P, ucp_P, ucp_P, /* Pc, Pd, Pe, Pf, Pi */
+ ucp_P, ucp_P, /* Ps, Po */
+ ucp_S, ucp_S, ucp_S, ucp_S, /* Sc, Sk, Sm, So */
+ ucp_Z, ucp_Z, ucp_Z /* Zl, Zp, Zs */
+};
+
+/* The pcre_utt[] table below translates Unicode property names into type and
+code values. It is searched by binary chop, so must be in collating sequence of
+name. Originally, the table contained pointers to the name strings in the first
+field of each entry. However, that leads to a large number of relocations when
+a shared library is dynamically loaded. A significant reduction is made by
+putting all the names into a single, large string and then using offsets in the
+table itself. Maintenance is more error-prone, but frequent changes to this
+data are unlikely.
+
+July 2008: There is now a script called maint/GenerateUtt.py that can be used
+to generate this data automatically instead of maintaining it by hand.
+
+The script was updated in March 2009 to generate a new EBCDIC-compliant
+version. Like all other character and string literals that are compared against
+the regular expression pattern, we must use STR_ macros instead of literal
+strings to make sure that UTF-8 support works on EBCDIC platforms. */
+
+#define STRING_Any0 STR_A STR_n STR_y "\0"
+#define STRING_Arabic0 STR_A STR_r STR_a STR_b STR_i STR_c "\0"
+#define STRING_Armenian0 STR_A STR_r STR_m STR_e STR_n STR_i STR_a STR_n "\0"
+#define STRING_Avestan0 STR_A STR_v STR_e STR_s STR_t STR_a STR_n "\0"
+#define STRING_Balinese0 STR_B STR_a STR_l STR_i STR_n STR_e STR_s STR_e "\0"
+#define STRING_Bamum0 STR_B STR_a STR_m STR_u STR_m "\0"
+#define STRING_Batak0 STR_B STR_a STR_t STR_a STR_k "\0"
+#define STRING_Bengali0 STR_B STR_e STR_n STR_g STR_a STR_l STR_i "\0"
+#define STRING_Bopomofo0 STR_B STR_o STR_p STR_o STR_m STR_o STR_f STR_o "\0"
+#define STRING_Brahmi0 STR_B STR_r STR_a STR_h STR_m STR_i "\0"
+#define STRING_Braille0 STR_B STR_r STR_a STR_i STR_l STR_l STR_e "\0"
+#define STRING_Buginese0 STR_B STR_u STR_g STR_i STR_n STR_e STR_s STR_e "\0"
+#define STRING_Buhid0 STR_B STR_u STR_h STR_i STR_d "\0"
+#define STRING_C0 STR_C "\0"
+#define STRING_Canadian_Aboriginal0 STR_C STR_a STR_n STR_a STR_d STR_i STR_a STR_n STR_UNDERSCORE STR_A STR_b STR_o STR_r STR_i STR_g STR_i STR_n STR_a STR_l "\0"
+#define STRING_Carian0 STR_C STR_a STR_r STR_i STR_a STR_n "\0"
+#define STRING_Cc0 STR_C STR_c "\0"
+#define STRING_Cf0 STR_C STR_f "\0"
+#define STRING_Cham0 STR_C STR_h STR_a STR_m "\0"
+#define STRING_Cherokee0 STR_C STR_h STR_e STR_r STR_o STR_k STR_e STR_e "\0"
+#define STRING_Cn0 STR_C STR_n "\0"
+#define STRING_Co0 STR_C STR_o "\0"
+#define STRING_Common0 STR_C STR_o STR_m STR_m STR_o STR_n "\0"
+#define STRING_Coptic0 STR_C STR_o STR_p STR_t STR_i STR_c "\0"
+#define STRING_Cs0 STR_C STR_s "\0"
+#define STRING_Cuneiform0 STR_C STR_u STR_n STR_e STR_i STR_f STR_o STR_r STR_m "\0"
+#define STRING_Cypriot0 STR_C STR_y STR_p STR_r STR_i STR_o STR_t "\0"
+#define STRING_Cyrillic0 STR_C STR_y STR_r STR_i STR_l STR_l STR_i STR_c "\0"
+#define STRING_Deseret0 STR_D STR_e STR_s STR_e STR_r STR_e STR_t "\0"
+#define STRING_Devanagari0 STR_D STR_e STR_v STR_a STR_n STR_a STR_g STR_a STR_r STR_i "\0"
+#define STRING_Egyptian_Hieroglyphs0 STR_E STR_g STR_y STR_p STR_t STR_i STR_a STR_n STR_UNDERSCORE STR_H STR_i STR_e STR_r STR_o STR_g STR_l STR_y STR_p STR_h STR_s "\0"
+#define STRING_Ethiopic0 STR_E STR_t STR_h STR_i STR_o STR_p STR_i STR_c "\0"
+#define STRING_Georgian0 STR_G STR_e STR_o STR_r STR_g STR_i STR_a STR_n "\0"
+#define STRING_Glagolitic0 STR_G STR_l STR_a STR_g STR_o STR_l STR_i STR_t STR_i STR_c "\0"
+#define STRING_Gothic0 STR_G STR_o STR_t STR_h STR_i STR_c "\0"
+#define STRING_Greek0 STR_G STR_r STR_e STR_e STR_k "\0"
+#define STRING_Gujarati0 STR_G STR_u STR_j STR_a STR_r STR_a STR_t STR_i "\0"
+#define STRING_Gurmukhi0 STR_G STR_u STR_r STR_m STR_u STR_k STR_h STR_i "\0"
+#define STRING_Han0 STR_H STR_a STR_n "\0"
+#define STRING_Hangul0 STR_H STR_a STR_n STR_g STR_u STR_l "\0"
+#define STRING_Hanunoo0 STR_H STR_a STR_n STR_u STR_n STR_o STR_o "\0"
+#define STRING_Hebrew0 STR_H STR_e STR_b STR_r STR_e STR_w "\0"
+#define STRING_Hiragana0 STR_H STR_i STR_r STR_a STR_g STR_a STR_n STR_a "\0"
+#define STRING_Imperial_Aramaic0 STR_I STR_m STR_p STR_e STR_r STR_i STR_a STR_l STR_UNDERSCORE STR_A STR_r STR_a STR_m STR_a STR_i STR_c "\0"
+#define STRING_Inherited0 STR_I STR_n STR_h STR_e STR_r STR_i STR_t STR_e STR_d "\0"
+#define STRING_Inscriptional_Pahlavi0 STR_I STR_n STR_s STR_c STR_r STR_i STR_p STR_t STR_i STR_o STR_n STR_a STR_l STR_UNDERSCORE STR_P STR_a STR_h STR_l STR_a STR_v STR_i "\0"
+#define STRING_Inscriptional_Parthian0 STR_I STR_n STR_s STR_c STR_r STR_i STR_p STR_t STR_i STR_o STR_n STR_a STR_l STR_UNDERSCORE STR_P STR_a STR_r STR_t STR_h STR_i STR_a STR_n "\0"
+#define STRING_Javanese0 STR_J STR_a STR_v STR_a STR_n STR_e STR_s STR_e "\0"
+#define STRING_Kaithi0 STR_K STR_a STR_i STR_t STR_h STR_i "\0"
+#define STRING_Kannada0 STR_K STR_a STR_n STR_n STR_a STR_d STR_a "\0"
+#define STRING_Katakana0 STR_K STR_a STR_t STR_a STR_k STR_a STR_n STR_a "\0"
+#define STRING_Kayah_Li0 STR_K STR_a STR_y STR_a STR_h STR_UNDERSCORE STR_L STR_i "\0"
+#define STRING_Kharoshthi0 STR_K STR_h STR_a STR_r STR_o STR_s STR_h STR_t STR_h STR_i "\0"
+#define STRING_Khmer0 STR_K STR_h STR_m STR_e STR_r "\0"
+#define STRING_L0 STR_L "\0"
+#define STRING_L_AMPERSAND0 STR_L STR_AMPERSAND "\0"
+#define STRING_Lao0 STR_L STR_a STR_o "\0"
+#define STRING_Latin0 STR_L STR_a STR_t STR_i STR_n "\0"
+#define STRING_Lepcha0 STR_L STR_e STR_p STR_c STR_h STR_a "\0"
+#define STRING_Limbu0 STR_L STR_i STR_m STR_b STR_u "\0"
+#define STRING_Linear_B0 STR_L STR_i STR_n STR_e STR_a STR_r STR_UNDERSCORE STR_B "\0"
+#define STRING_Lisu0 STR_L STR_i STR_s STR_u "\0"
+#define STRING_Ll0 STR_L STR_l "\0"
+#define STRING_Lm0 STR_L STR_m "\0"
+#define STRING_Lo0 STR_L STR_o "\0"
+#define STRING_Lt0 STR_L STR_t "\0"
+#define STRING_Lu0 STR_L STR_u "\0"
+#define STRING_Lycian0 STR_L STR_y STR_c STR_i STR_a STR_n "\0"
+#define STRING_Lydian0 STR_L STR_y STR_d STR_i STR_a STR_n "\0"
+#define STRING_M0 STR_M "\0"
+#define STRING_Malayalam0 STR_M STR_a STR_l STR_a STR_y STR_a STR_l STR_a STR_m "\0"
+#define STRING_Mandaic0 STR_M STR_a STR_n STR_d STR_a STR_i STR_c "\0"
+#define STRING_Mc0 STR_M STR_c "\0"
+#define STRING_Me0 STR_M STR_e "\0"
+#define STRING_Meetei_Mayek0 STR_M STR_e STR_e STR_t STR_e STR_i STR_UNDERSCORE STR_M STR_a STR_y STR_e STR_k "\0"
+#define STRING_Mn0 STR_M STR_n "\0"
+#define STRING_Mongolian0 STR_M STR_o STR_n STR_g STR_o STR_l STR_i STR_a STR_n "\0"
+#define STRING_Myanmar0 STR_M STR_y STR_a STR_n STR_m STR_a STR_r "\0"
+#define STRING_N0 STR_N "\0"
+#define STRING_Nd0 STR_N STR_d "\0"
+#define STRING_New_Tai_Lue0 STR_N STR_e STR_w STR_UNDERSCORE STR_T STR_a STR_i STR_UNDERSCORE STR_L STR_u STR_e "\0"
+#define STRING_Nko0 STR_N STR_k STR_o "\0"
+#define STRING_Nl0 STR_N STR_l "\0"
+#define STRING_No0 STR_N STR_o "\0"
+#define STRING_Ogham0 STR_O STR_g STR_h STR_a STR_m "\0"
+#define STRING_Ol_Chiki0 STR_O STR_l STR_UNDERSCORE STR_C STR_h STR_i STR_k STR_i "\0"
+#define STRING_Old_Italic0 STR_O STR_l STR_d STR_UNDERSCORE STR_I STR_t STR_a STR_l STR_i STR_c "\0"
+#define STRING_Old_Persian0 STR_O STR_l STR_d STR_UNDERSCORE STR_P STR_e STR_r STR_s STR_i STR_a STR_n "\0"
+#define STRING_Old_South_Arabian0 STR_O STR_l STR_d STR_UNDERSCORE STR_S STR_o STR_u STR_t STR_h STR_UNDERSCORE STR_A STR_r STR_a STR_b STR_i STR_a STR_n "\0"
+#define STRING_Old_Turkic0 STR_O STR_l STR_d STR_UNDERSCORE STR_T STR_u STR_r STR_k STR_i STR_c "\0"
+#define STRING_Oriya0 STR_O STR_r STR_i STR_y STR_a "\0"
+#define STRING_Osmanya0 STR_O STR_s STR_m STR_a STR_n STR_y STR_a "\0"
+#define STRING_P0 STR_P "\0"
+#define STRING_Pc0 STR_P STR_c "\0"
+#define STRING_Pd0 STR_P STR_d "\0"
+#define STRING_Pe0 STR_P STR_e "\0"
+#define STRING_Pf0 STR_P STR_f "\0"
+#define STRING_Phags_Pa0 STR_P STR_h STR_a STR_g STR_s STR_UNDERSCORE STR_P STR_a "\0"
+#define STRING_Phoenician0 STR_P STR_h STR_o STR_e STR_n STR_i STR_c STR_i STR_a STR_n "\0"
+#define STRING_Pi0 STR_P STR_i "\0"
+#define STRING_Po0 STR_P STR_o "\0"
+#define STRING_Ps0 STR_P STR_s "\0"
+#define STRING_Rejang0 STR_R STR_e STR_j STR_a STR_n STR_g "\0"
+#define STRING_Runic0 STR_R STR_u STR_n STR_i STR_c "\0"
+#define STRING_S0 STR_S "\0"
+#define STRING_Samaritan0 STR_S STR_a STR_m STR_a STR_r STR_i STR_t STR_a STR_n "\0"
+#define STRING_Saurashtra0 STR_S STR_a STR_u STR_r STR_a STR_s STR_h STR_t STR_r STR_a "\0"
+#define STRING_Sc0 STR_S STR_c "\0"
+#define STRING_Shavian0 STR_S STR_h STR_a STR_v STR_i STR_a STR_n "\0"
+#define STRING_Sinhala0 STR_S STR_i STR_n STR_h STR_a STR_l STR_a "\0"
+#define STRING_Sk0 STR_S STR_k "\0"
+#define STRING_Sm0 STR_S STR_m "\0"
+#define STRING_So0 STR_S STR_o "\0"
+#define STRING_Sundanese0 STR_S STR_u STR_n STR_d STR_a STR_n STR_e STR_s STR_e "\0"
+#define STRING_Syloti_Nagri0 STR_S STR_y STR_l STR_o STR_t STR_i STR_UNDERSCORE STR_N STR_a STR_g STR_r STR_i "\0"
+#define STRING_Syriac0 STR_S STR_y STR_r STR_i STR_a STR_c "\0"
+#define STRING_Tagalog0 STR_T STR_a STR_g STR_a STR_l STR_o STR_g "\0"
+#define STRING_Tagbanwa0 STR_T STR_a STR_g STR_b STR_a STR_n STR_w STR_a "\0"
+#define STRING_Tai_Le0 STR_T STR_a STR_i STR_UNDERSCORE STR_L STR_e "\0"
+#define STRING_Tai_Tham0 STR_T STR_a STR_i STR_UNDERSCORE STR_T STR_h STR_a STR_m "\0"
+#define STRING_Tai_Viet0 STR_T STR_a STR_i STR_UNDERSCORE STR_V STR_i STR_e STR_t "\0"
+#define STRING_Tamil0 STR_T STR_a STR_m STR_i STR_l "\0"
+#define STRING_Telugu0 STR_T STR_e STR_l STR_u STR_g STR_u "\0"
+#define STRING_Thaana0 STR_T STR_h STR_a STR_a STR_n STR_a "\0"
+#define STRING_Thai0 STR_T STR_h STR_a STR_i "\0"
+#define STRING_Tibetan0 STR_T STR_i STR_b STR_e STR_t STR_a STR_n "\0"
+#define STRING_Tifinagh0 STR_T STR_i STR_f STR_i STR_n STR_a STR_g STR_h "\0"
+#define STRING_Ugaritic0 STR_U STR_g STR_a STR_r STR_i STR_t STR_i STR_c "\0"
+#define STRING_Vai0 STR_V STR_a STR_i "\0"
+#define STRING_Xan0 STR_X STR_a STR_n "\0"
+#define STRING_Xps0 STR_X STR_p STR_s "\0"
+#define STRING_Xsp0 STR_X STR_s STR_p "\0"
+#define STRING_Xwd0 STR_X STR_w STR_d "\0"
+#define STRING_Yi0 STR_Y STR_i "\0"
+#define STRING_Z0 STR_Z "\0"
+#define STRING_Zl0 STR_Z STR_l "\0"
+#define STRING_Zp0 STR_Z STR_p "\0"
+#define STRING_Zs0 STR_Z STR_s "\0"
+
+const char _pcre_utt_names[] =
+ STRING_Any0
+ STRING_Arabic0
+ STRING_Armenian0
+ STRING_Avestan0
+ STRING_Balinese0
+ STRING_Bamum0
+ STRING_Batak0
+ STRING_Bengali0
+ STRING_Bopomofo0
+ STRING_Brahmi0
+ STRING_Braille0
+ STRING_Buginese0
+ STRING_Buhid0
+ STRING_C0
+ STRING_Canadian_Aboriginal0
+ STRING_Carian0
+ STRING_Cc0
+ STRING_Cf0
+ STRING_Cham0
+ STRING_Cherokee0
+ STRING_Cn0
+ STRING_Co0
+ STRING_Common0
+ STRING_Coptic0
+ STRING_Cs0
+ STRING_Cuneiform0
+ STRING_Cypriot0
+ STRING_Cyrillic0
+ STRING_Deseret0
+ STRING_Devanagari0
+ STRING_Egyptian_Hieroglyphs0
+ STRING_Ethiopic0
+ STRING_Georgian0
+ STRING_Glagolitic0
+ STRING_Gothic0
+ STRING_Greek0
+ STRING_Gujarati0
+ STRING_Gurmukhi0
+ STRING_Han0
+ STRING_Hangul0
+ STRING_Hanunoo0
+ STRING_Hebrew0
+ STRING_Hiragana0
+ STRING_Imperial_Aramaic0
+ STRING_Inherited0
+ STRING_Inscriptional_Pahlavi0
+ STRING_Inscriptional_Parthian0
+ STRING_Javanese0
+ STRING_Kaithi0
+ STRING_Kannada0
+ STRING_Katakana0
+ STRING_Kayah_Li0
+ STRING_Kharoshthi0
+ STRING_Khmer0
+ STRING_L0
+ STRING_L_AMPERSAND0
+ STRING_Lao0
+ STRING_Latin0
+ STRING_Lepcha0
+ STRING_Limbu0
+ STRING_Linear_B0
+ STRING_Lisu0
+ STRING_Ll0
+ STRING_Lm0
+ STRING_Lo0
+ STRING_Lt0
+ STRING_Lu0
+ STRING_Lycian0
+ STRING_Lydian0
+ STRING_M0
+ STRING_Malayalam0
+ STRING_Mandaic0
+ STRING_Mc0
+ STRING_Me0
+ STRING_Meetei_Mayek0
+ STRING_Mn0
+ STRING_Mongolian0
+ STRING_Myanmar0
+ STRING_N0
+ STRING_Nd0
+ STRING_New_Tai_Lue0
+ STRING_Nko0
+ STRING_Nl0
+ STRING_No0
+ STRING_Ogham0
+ STRING_Ol_Chiki0
+ STRING_Old_Italic0
+ STRING_Old_Persian0
+ STRING_Old_South_Arabian0
+ STRING_Old_Turkic0
+ STRING_Oriya0
+ STRING_Osmanya0
+ STRING_P0
+ STRING_Pc0
+ STRING_Pd0
+ STRING_Pe0
+ STRING_Pf0
+ STRING_Phags_Pa0
+ STRING_Phoenician0
+ STRING_Pi0
+ STRING_Po0
+ STRING_Ps0
+ STRING_Rejang0
+ STRING_Runic0
+ STRING_S0
+ STRING_Samaritan0
+ STRING_Saurashtra0
+ STRING_Sc0
+ STRING_Shavian0
+ STRING_Sinhala0
+ STRING_Sk0
+ STRING_Sm0
+ STRING_So0
+ STRING_Sundanese0
+ STRING_Syloti_Nagri0
+ STRING_Syriac0
+ STRING_Tagalog0
+ STRING_Tagbanwa0
+ STRING_Tai_Le0
+ STRING_Tai_Tham0
+ STRING_Tai_Viet0
+ STRING_Tamil0
+ STRING_Telugu0
+ STRING_Thaana0
+ STRING_Thai0
+ STRING_Tibetan0
+ STRING_Tifinagh0
+ STRING_Ugaritic0
+ STRING_Vai0
+ STRING_Xan0
+ STRING_Xps0
+ STRING_Xsp0
+ STRING_Xwd0
+ STRING_Yi0
+ STRING_Z0
+ STRING_Zl0
+ STRING_Zp0
+ STRING_Zs0;
+
+const ucp_type_table _pcre_utt[] = {
+ { 0, PT_ANY, 0 },
+ { 4, PT_SC, ucp_Arabic },
+ { 11, PT_SC, ucp_Armenian },
+ { 20, PT_SC, ucp_Avestan },
+ { 28, PT_SC, ucp_Balinese },
+ { 37, PT_SC, ucp_Bamum },
+ { 43, PT_SC, ucp_Batak },
+ { 49, PT_SC, ucp_Bengali },
+ { 57, PT_SC, ucp_Bopomofo },
+ { 66, PT_SC, ucp_Brahmi },
+ { 73, PT_SC, ucp_Braille },
+ { 81, PT_SC, ucp_Buginese },
+ { 90, PT_SC, ucp_Buhid },
+ { 96, PT_GC, ucp_C },
+ { 98, PT_SC, ucp_Canadian_Aboriginal },
+ { 118, PT_SC, ucp_Carian },
+ { 125, PT_PC, ucp_Cc },
+ { 128, PT_PC, ucp_Cf },
+ { 131, PT_SC, ucp_Cham },
+ { 136, PT_SC, ucp_Cherokee },
+ { 145, PT_PC, ucp_Cn },
+ { 148, PT_PC, ucp_Co },
+ { 151, PT_SC, ucp_Common },
+ { 158, PT_SC, ucp_Coptic },
+ { 165, PT_PC, ucp_Cs },
+ { 168, PT_SC, ucp_Cuneiform },
+ { 178, PT_SC, ucp_Cypriot },
+ { 186, PT_SC, ucp_Cyrillic },
+ { 195, PT_SC, ucp_Deseret },
+ { 203, PT_SC, ucp_Devanagari },
+ { 214, PT_SC, ucp_Egyptian_Hieroglyphs },
+ { 235, PT_SC, ucp_Ethiopic },
+ { 244, PT_SC, ucp_Georgian },
+ { 253, PT_SC, ucp_Glagolitic },
+ { 264, PT_SC, ucp_Gothic },
+ { 271, PT_SC, ucp_Greek },
+ { 277, PT_SC, ucp_Gujarati },
+ { 286, PT_SC, ucp_Gurmukhi },
+ { 295, PT_SC, ucp_Han },
+ { 299, PT_SC, ucp_Hangul },
+ { 306, PT_SC, ucp_Hanunoo },
+ { 314, PT_SC, ucp_Hebrew },
+ { 321, PT_SC, ucp_Hiragana },
+ { 330, PT_SC, ucp_Imperial_Aramaic },
+ { 347, PT_SC, ucp_Inherited },
+ { 357, PT_SC, ucp_Inscriptional_Pahlavi },
+ { 379, PT_SC, ucp_Inscriptional_Parthian },
+ { 402, PT_SC, ucp_Javanese },
+ { 411, PT_SC, ucp_Kaithi },
+ { 418, PT_SC, ucp_Kannada },
+ { 426, PT_SC, ucp_Katakana },
+ { 435, PT_SC, ucp_Kayah_Li },
+ { 444, PT_SC, ucp_Kharoshthi },
+ { 455, PT_SC, ucp_Khmer },
+ { 461, PT_GC, ucp_L },
+ { 463, PT_LAMP, 0 },
+ { 466, PT_SC, ucp_Lao },
+ { 470, PT_SC, ucp_Latin },
+ { 476, PT_SC, ucp_Lepcha },
+ { 483, PT_SC, ucp_Limbu },
+ { 489, PT_SC, ucp_Linear_B },
+ { 498, PT_SC, ucp_Lisu },
+ { 503, PT_PC, ucp_Ll },
+ { 506, PT_PC, ucp_Lm },
+ { 509, PT_PC, ucp_Lo },
+ { 512, PT_PC, ucp_Lt },
+ { 515, PT_PC, ucp_Lu },
+ { 518, PT_SC, ucp_Lycian },
+ { 525, PT_SC, ucp_Lydian },
+ { 532, PT_GC, ucp_M },
+ { 534, PT_SC, ucp_Malayalam },
+ { 544, PT_SC, ucp_Mandaic },
+ { 552, PT_PC, ucp_Mc },
+ { 555, PT_PC, ucp_Me },
+ { 558, PT_SC, ucp_Meetei_Mayek },
+ { 571, PT_PC, ucp_Mn },
+ { 574, PT_SC, ucp_Mongolian },
+ { 584, PT_SC, ucp_Myanmar },
+ { 592, PT_GC, ucp_N },
+ { 594, PT_PC, ucp_Nd },
+ { 597, PT_SC, ucp_New_Tai_Lue },
+ { 609, PT_SC, ucp_Nko },
+ { 613, PT_PC, ucp_Nl },
+ { 616, PT_PC, ucp_No },
+ { 619, PT_SC, ucp_Ogham },
+ { 625, PT_SC, ucp_Ol_Chiki },
+ { 634, PT_SC, ucp_Old_Italic },
+ { 645, PT_SC, ucp_Old_Persian },
+ { 657, PT_SC, ucp_Old_South_Arabian },
+ { 675, PT_SC, ucp_Old_Turkic },
+ { 686, PT_SC, ucp_Oriya },
+ { 692, PT_SC, ucp_Osmanya },
+ { 700, PT_GC, ucp_P },
+ { 702, PT_PC, ucp_Pc },
+ { 705, PT_PC, ucp_Pd },
+ { 708, PT_PC, ucp_Pe },
+ { 711, PT_PC, ucp_Pf },
+ { 714, PT_SC, ucp_Phags_Pa },
+ { 723, PT_SC, ucp_Phoenician },
+ { 734, PT_PC, ucp_Pi },
+ { 737, PT_PC, ucp_Po },
+ { 740, PT_PC, ucp_Ps },
+ { 743, PT_SC, ucp_Rejang },
+ { 750, PT_SC, ucp_Runic },
+ { 756, PT_GC, ucp_S },
+ { 758, PT_SC, ucp_Samaritan },
+ { 768, PT_SC, ucp_Saurashtra },
+ { 779, PT_PC, ucp_Sc },
+ { 782, PT_SC, ucp_Shavian },
+ { 790, PT_SC, ucp_Sinhala },
+ { 798, PT_PC, ucp_Sk },
+ { 801, PT_PC, ucp_Sm },
+ { 804, PT_PC, ucp_So },
+ { 807, PT_SC, ucp_Sundanese },
+ { 817, PT_SC, ucp_Syloti_Nagri },
+ { 830, PT_SC, ucp_Syriac },
+ { 837, PT_SC, ucp_Tagalog },
+ { 845, PT_SC, ucp_Tagbanwa },
+ { 854, PT_SC, ucp_Tai_Le },
+ { 861, PT_SC, ucp_Tai_Tham },
+ { 870, PT_SC, ucp_Tai_Viet },
+ { 879, PT_SC, ucp_Tamil },
+ { 885, PT_SC, ucp_Telugu },
+ { 892, PT_SC, ucp_Thaana },
+ { 899, PT_SC, ucp_Thai },
+ { 904, PT_SC, ucp_Tibetan },
+ { 912, PT_SC, ucp_Tifinagh },
+ { 921, PT_SC, ucp_Ugaritic },
+ { 930, PT_SC, ucp_Vai },
+ { 934, PT_ALNUM, 0 },
+ { 938, PT_PXSPACE, 0 },
+ { 942, PT_SPACE, 0 },
+ { 946, PT_WORD, 0 },
+ { 950, PT_SC, ucp_Yi },
+ { 953, PT_GC, ucp_Z },
+ { 955, PT_PC, ucp_Zl },
+ { 958, PT_PC, ucp_Zp },
+ { 961, PT_PC, ucp_Zs }
+};
+
+const int _pcre_utt_size = sizeof(_pcre_utt)/sizeof(ucp_type_table);
+
+#endif /* SUPPORT_UTF8 */
+
+/* End of pcre_tables.c */
diff --git a/usr.sbin/nginx/src/pcre/pcre_try_flipped.c b/usr.sbin/nginx/src/pcre/pcre_try_flipped.c
new file mode 100644
index 00000000000..606504c0b0d
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/pcre_try_flipped.c
@@ -0,0 +1,139 @@
+/*************************************************
+* Perl-Compatible Regular Expressions *
+*************************************************/
+
+/* PCRE is a library of functions to support regular expressions whose syntax
+and semantics are as close as possible to those of the Perl 5 language.
+
+ Written by Philip Hazel
+ Copyright (c) 1997-2009 University of Cambridge
+
+-----------------------------------------------------------------------------
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the University of Cambridge nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+-----------------------------------------------------------------------------
+*/
+
+
+/* This module contains an internal function that tests a compiled pattern to
+see if it was compiled with the opposite endianness. If so, it uses an
+auxiliary local function to flip the appropriate bytes. */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pcre_internal.h"
+
+
+/*************************************************
+* Flip bytes in an integer *
+*************************************************/
+
+/* This function is called when the magic number in a regex doesn't match, in
+order to flip its bytes to see if we are dealing with a pattern that was
+compiled on a host of different endianness. If so, this function is used to
+flip other byte values.
+
+Arguments:
+ value the number to flip
+ n the number of bytes to flip (assumed to be 2 or 4)
+
+Returns: the flipped value
+*/
+
+static unsigned long int
+byteflip(unsigned long int value, int n)
+{
+if (n == 2) return ((value & 0x00ff) << 8) | ((value & 0xff00) >> 8);
+return ((value & 0x000000ff) << 24) |
+ ((value & 0x0000ff00) << 8) |
+ ((value & 0x00ff0000) >> 8) |
+ ((value & 0xff000000) >> 24);
+}
+
+
+
+/*************************************************
+* Test for a byte-flipped compiled regex *
+*************************************************/
+
+/* This function is called from pcre_exec(), pcre_dfa_exec(), and also from
+pcre_fullinfo(). Its job is to test whether the regex is byte-flipped - that
+is, it was compiled on a system of opposite endianness. The function is called
+only when the native MAGIC_NUMBER test fails. If the regex is indeed flipped,
+we flip all the relevant values into a different data block, and return it.
+
+Arguments:
+ re points to the regex
+ study points to study data, or NULL
+ internal_re points to a new regex block
+ internal_study points to a new study block
+
+Returns: the new block if is is indeed a byte-flipped regex
+ NULL if it is not
+*/
+
+real_pcre *
+_pcre_try_flipped(const real_pcre *re, real_pcre *internal_re,
+ const pcre_study_data *study, pcre_study_data *internal_study)
+{
+if (byteflip(re->magic_number, sizeof(re->magic_number)) != MAGIC_NUMBER)
+ return NULL;
+
+*internal_re = *re; /* To copy other fields */
+internal_re->size = byteflip(re->size, sizeof(re->size));
+internal_re->options = byteflip(re->options, sizeof(re->options));
+internal_re->flags = (pcre_uint16)byteflip(re->flags, sizeof(re->flags));
+internal_re->top_bracket =
+ (pcre_uint16)byteflip(re->top_bracket, sizeof(re->top_bracket));
+internal_re->top_backref =
+ (pcre_uint16)byteflip(re->top_backref, sizeof(re->top_backref));
+internal_re->first_byte =
+ (pcre_uint16)byteflip(re->first_byte, sizeof(re->first_byte));
+internal_re->req_byte =
+ (pcre_uint16)byteflip(re->req_byte, sizeof(re->req_byte));
+internal_re->name_table_offset =
+ (pcre_uint16)byteflip(re->name_table_offset, sizeof(re->name_table_offset));
+internal_re->name_entry_size =
+ (pcre_uint16)byteflip(re->name_entry_size, sizeof(re->name_entry_size));
+internal_re->name_count =
+ (pcre_uint16)byteflip(re->name_count, sizeof(re->name_count));
+
+if (study != NULL)
+ {
+ *internal_study = *study; /* To copy other fields */
+ internal_study->size = byteflip(study->size, sizeof(study->size));
+ internal_study->flags = byteflip(study->flags, sizeof(study->flags));
+ internal_study->minlength = byteflip(study->minlength,
+ sizeof(study->minlength));
+ }
+
+return internal_re;
+}
+
+/* End of pcre_tryflipped.c */
diff --git a/usr.sbin/nginx/src/pcre/pcre_ucd.c b/usr.sbin/nginx/src/pcre/pcre_ucd.c
new file mode 100644
index 00000000000..112cfb41a62
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/pcre_ucd.c
@@ -0,0 +1,2981 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pcre_internal.h"
+
+/* Unicode character database. */
+/* This file was autogenerated by the MultiStage2.py script. */
+/* Total size: 60384 bytes, block size: 128. */
+
+/* The tables herein are needed only when UCP support is built */
+/* into PCRE. This module should not be referenced otherwise, so */
+/* it should not matter whether it is compiled or not. However */
+/* a comment was received about space saving - maybe the guy linked */
+/* all the modules rather than using a library - so we include a */
+/* condition to cut out the tables when not needed. But don't leave */
+/* a totally empty module because some compilers barf at that. */
+/* Instead, just supply small dummy tables. */
+
+#ifndef SUPPORT_UCP
+const ucd_record _pcre_ucd_records[] = {{0,0,0 }};
+const uschar _pcre_ucd_stage1[] = {0};
+const pcre_uint16 _pcre_ucd_stage2[] = {0};
+#else
+
+/* When recompiling tables with a new Unicode version,
+please check types in the structure definition from pcre_internal.h:
+typedef struct {
+uschar property_0;
+uschar property_1;
+pcre_int32 property_2;
+} ucd_record; */
+
+
+const ucd_record _pcre_ucd_records[] = { /* 4320 bytes, record size 8 */
+ { 9, 0, 0, }, /* 0 */
+ { 9, 29, 0, }, /* 1 */
+ { 9, 21, 0, }, /* 2 */
+ { 9, 23, 0, }, /* 3 */
+ { 9, 22, 0, }, /* 4 */
+ { 9, 18, 0, }, /* 5 */
+ { 9, 25, 0, }, /* 6 */
+ { 9, 17, 0, }, /* 7 */
+ { 9, 13, 0, }, /* 8 */
+ { 33, 9, 32, }, /* 9 */
+ { 9, 24, 0, }, /* 10 */
+ { 9, 16, 0, }, /* 11 */
+ { 33, 5, -32, }, /* 12 */
+ { 9, 26, 0, }, /* 13 */
+ { 33, 5, 0, }, /* 14 */
+ { 9, 20, 0, }, /* 15 */
+ { 9, 1, 0, }, /* 16 */
+ { 9, 15, 0, }, /* 17 */
+ { 9, 5, 743, }, /* 18 */
+ { 9, 19, 0, }, /* 19 */
+ { 33, 5, 121, }, /* 20 */
+ { 33, 9, 1, }, /* 21 */
+ { 33, 5, -1, }, /* 22 */
+ { 33, 9, -199, }, /* 23 */
+ { 33, 5, -232, }, /* 24 */
+ { 33, 9, -121, }, /* 25 */
+ { 33, 5, -300, }, /* 26 */
+ { 33, 5, 195, }, /* 27 */
+ { 33, 9, 210, }, /* 28 */
+ { 33, 9, 206, }, /* 29 */
+ { 33, 9, 205, }, /* 30 */
+ { 33, 9, 79, }, /* 31 */
+ { 33, 9, 202, }, /* 32 */
+ { 33, 9, 203, }, /* 33 */
+ { 33, 9, 207, }, /* 34 */
+ { 33, 5, 97, }, /* 35 */
+ { 33, 9, 211, }, /* 36 */
+ { 33, 9, 209, }, /* 37 */
+ { 33, 5, 163, }, /* 38 */
+ { 33, 9, 213, }, /* 39 */
+ { 33, 5, 130, }, /* 40 */
+ { 33, 9, 214, }, /* 41 */
+ { 33, 9, 218, }, /* 42 */
+ { 33, 9, 217, }, /* 43 */
+ { 33, 9, 219, }, /* 44 */
+ { 33, 7, 0, }, /* 45 */
+ { 33, 5, 56, }, /* 46 */
+ { 33, 9, 2, }, /* 47 */
+ { 33, 8, -1, }, /* 48 */
+ { 33, 5, -2, }, /* 49 */
+ { 33, 5, -79, }, /* 50 */
+ { 33, 9, -97, }, /* 51 */
+ { 33, 9, -56, }, /* 52 */
+ { 33, 9, -130, }, /* 53 */
+ { 33, 9, 10795, }, /* 54 */
+ { 33, 9, -163, }, /* 55 */
+ { 33, 9, 10792, }, /* 56 */
+ { 33, 5, 10815, }, /* 57 */
+ { 33, 9, -195, }, /* 58 */
+ { 33, 9, 69, }, /* 59 */
+ { 33, 9, 71, }, /* 60 */
+ { 33, 5, 10783, }, /* 61 */
+ { 33, 5, 10780, }, /* 62 */
+ { 33, 5, 10782, }, /* 63 */
+ { 33, 5, -210, }, /* 64 */
+ { 33, 5, -206, }, /* 65 */
+ { 33, 5, -205, }, /* 66 */
+ { 33, 5, -202, }, /* 67 */
+ { 33, 5, -203, }, /* 68 */
+ { 33, 5, -207, }, /* 69 */
+ { 33, 5, 42280, }, /* 70 */
+ { 33, 5, -209, }, /* 71 */
+ { 33, 5, -211, }, /* 72 */
+ { 33, 5, 10743, }, /* 73 */
+ { 33, 5, 10749, }, /* 74 */
+ { 33, 5, -213, }, /* 75 */
+ { 33, 5, -214, }, /* 76 */
+ { 33, 5, 10727, }, /* 77 */
+ { 33, 5, -218, }, /* 78 */
+ { 33, 5, -69, }, /* 79 */
+ { 33, 5, -217, }, /* 80 */
+ { 33, 5, -71, }, /* 81 */
+ { 33, 5, -219, }, /* 82 */
+ { 33, 6, 0, }, /* 83 */
+ { 9, 6, 0, }, /* 84 */
+ { 3, 24, 0, }, /* 85 */
+ { 27, 12, 0, }, /* 86 */
+ { 27, 12, 84, }, /* 87 */
+ { 19, 9, 1, }, /* 88 */
+ { 19, 5, -1, }, /* 89 */
+ { 19, 24, 0, }, /* 90 */
+ { 9, 2, 0, }, /* 91 */
+ { 19, 6, 0, }, /* 92 */
+ { 19, 5, 130, }, /* 93 */
+ { 19, 9, 38, }, /* 94 */
+ { 19, 9, 37, }, /* 95 */
+ { 19, 9, 64, }, /* 96 */
+ { 19, 9, 63, }, /* 97 */
+ { 19, 5, 0, }, /* 98 */
+ { 19, 9, 32, }, /* 99 */
+ { 19, 5, -38, }, /* 100 */
+ { 19, 5, -37, }, /* 101 */
+ { 19, 5, -32, }, /* 102 */
+ { 19, 5, -31, }, /* 103 */
+ { 19, 5, -64, }, /* 104 */
+ { 19, 5, -63, }, /* 105 */
+ { 19, 9, 8, }, /* 106 */
+ { 19, 5, -62, }, /* 107 */
+ { 19, 5, -57, }, /* 108 */
+ { 19, 9, 0, }, /* 109 */
+ { 19, 5, -47, }, /* 110 */
+ { 19, 5, -54, }, /* 111 */
+ { 19, 5, -8, }, /* 112 */
+ { 10, 9, 1, }, /* 113 */
+ { 10, 5, -1, }, /* 114 */
+ { 19, 5, -86, }, /* 115 */
+ { 19, 5, -80, }, /* 116 */
+ { 19, 5, 7, }, /* 117 */
+ { 19, 9, -60, }, /* 118 */
+ { 19, 5, -96, }, /* 119 */
+ { 19, 25, 0, }, /* 120 */
+ { 19, 9, -7, }, /* 121 */
+ { 19, 9, -130, }, /* 122 */
+ { 12, 9, 80, }, /* 123 */
+ { 12, 9, 32, }, /* 124 */
+ { 12, 5, -32, }, /* 125 */
+ { 12, 5, -80, }, /* 126 */
+ { 12, 9, 1, }, /* 127 */
+ { 12, 5, -1, }, /* 128 */
+ { 12, 26, 0, }, /* 129 */
+ { 12, 12, 0, }, /* 130 */
+ { 12, 11, 0, }, /* 131 */
+ { 12, 9, 15, }, /* 132 */
+ { 12, 5, -15, }, /* 133 */
+ { 1, 9, 48, }, /* 134 */
+ { 1, 6, 0, }, /* 135 */
+ { 1, 21, 0, }, /* 136 */
+ { 1, 5, -48, }, /* 137 */
+ { 1, 5, 0, }, /* 138 */
+ { 1, 17, 0, }, /* 139 */
+ { 25, 12, 0, }, /* 140 */
+ { 25, 17, 0, }, /* 141 */
+ { 25, 21, 0, }, /* 142 */
+ { 25, 7, 0, }, /* 143 */
+ { 0, 1, 0, }, /* 144 */
+ { 0, 25, 0, }, /* 145 */
+ { 0, 21, 0, }, /* 146 */
+ { 0, 23, 0, }, /* 147 */
+ { 0, 26, 0, }, /* 148 */
+ { 0, 12, 0, }, /* 149 */
+ { 0, 7, 0, }, /* 150 */
+ { 0, 6, 0, }, /* 151 */
+ { 0, 13, 0, }, /* 152 */
+ { 49, 21, 0, }, /* 153 */
+ { 49, 1, 0, }, /* 154 */
+ { 49, 7, 0, }, /* 155 */
+ { 49, 12, 0, }, /* 156 */
+ { 55, 7, 0, }, /* 157 */
+ { 55, 12, 0, }, /* 158 */
+ { 63, 13, 0, }, /* 159 */
+ { 63, 7, 0, }, /* 160 */
+ { 63, 12, 0, }, /* 161 */
+ { 63, 6, 0, }, /* 162 */
+ { 63, 26, 0, }, /* 163 */
+ { 63, 21, 0, }, /* 164 */
+ { 89, 7, 0, }, /* 165 */
+ { 89, 12, 0, }, /* 166 */
+ { 89, 6, 0, }, /* 167 */
+ { 89, 21, 0, }, /* 168 */
+ { 94, 7, 0, }, /* 169 */
+ { 94, 12, 0, }, /* 170 */
+ { 94, 21, 0, }, /* 171 */
+ { 14, 12, 0, }, /* 172 */
+ { 14, 10, 0, }, /* 173 */
+ { 14, 7, 0, }, /* 174 */
+ { 14, 13, 0, }, /* 175 */
+ { 14, 6, 0, }, /* 176 */
+ { 2, 12, 0, }, /* 177 */
+ { 2, 10, 0, }, /* 178 */
+ { 2, 7, 0, }, /* 179 */
+ { 2, 13, 0, }, /* 180 */
+ { 2, 23, 0, }, /* 181 */
+ { 2, 15, 0, }, /* 182 */
+ { 2, 26, 0, }, /* 183 */
+ { 21, 12, 0, }, /* 184 */
+ { 21, 10, 0, }, /* 185 */
+ { 21, 7, 0, }, /* 186 */
+ { 21, 13, 0, }, /* 187 */
+ { 20, 12, 0, }, /* 188 */
+ { 20, 10, 0, }, /* 189 */
+ { 20, 7, 0, }, /* 190 */
+ { 20, 13, 0, }, /* 191 */
+ { 20, 23, 0, }, /* 192 */
+ { 43, 12, 0, }, /* 193 */
+ { 43, 10, 0, }, /* 194 */
+ { 43, 7, 0, }, /* 195 */
+ { 43, 13, 0, }, /* 196 */
+ { 43, 26, 0, }, /* 197 */
+ { 43, 15, 0, }, /* 198 */
+ { 53, 12, 0, }, /* 199 */
+ { 53, 7, 0, }, /* 200 */
+ { 53, 10, 0, }, /* 201 */
+ { 53, 13, 0, }, /* 202 */
+ { 53, 15, 0, }, /* 203 */
+ { 53, 26, 0, }, /* 204 */
+ { 53, 23, 0, }, /* 205 */
+ { 54, 10, 0, }, /* 206 */
+ { 54, 7, 0, }, /* 207 */
+ { 54, 12, 0, }, /* 208 */
+ { 54, 13, 0, }, /* 209 */
+ { 54, 15, 0, }, /* 210 */
+ { 54, 26, 0, }, /* 211 */
+ { 28, 10, 0, }, /* 212 */
+ { 28, 7, 0, }, /* 213 */
+ { 28, 12, 0, }, /* 214 */
+ { 28, 13, 0, }, /* 215 */
+ { 36, 10, 0, }, /* 216 */
+ { 36, 7, 0, }, /* 217 */
+ { 36, 12, 0, }, /* 218 */
+ { 36, 13, 0, }, /* 219 */
+ { 36, 15, 0, }, /* 220 */
+ { 36, 26, 0, }, /* 221 */
+ { 47, 10, 0, }, /* 222 */
+ { 47, 7, 0, }, /* 223 */
+ { 47, 12, 0, }, /* 224 */
+ { 47, 21, 0, }, /* 225 */
+ { 56, 7, 0, }, /* 226 */
+ { 56, 12, 0, }, /* 227 */
+ { 56, 6, 0, }, /* 228 */
+ { 56, 21, 0, }, /* 229 */
+ { 56, 13, 0, }, /* 230 */
+ { 32, 7, 0, }, /* 231 */
+ { 32, 12, 0, }, /* 232 */
+ { 32, 6, 0, }, /* 233 */
+ { 32, 13, 0, }, /* 234 */
+ { 57, 7, 0, }, /* 235 */
+ { 57, 26, 0, }, /* 236 */
+ { 57, 21, 0, }, /* 237 */
+ { 57, 12, 0, }, /* 238 */
+ { 57, 13, 0, }, /* 239 */
+ { 57, 15, 0, }, /* 240 */
+ { 57, 22, 0, }, /* 241 */
+ { 57, 18, 0, }, /* 242 */
+ { 57, 10, 0, }, /* 243 */
+ { 38, 7, 0, }, /* 244 */
+ { 38, 10, 0, }, /* 245 */
+ { 38, 12, 0, }, /* 246 */
+ { 38, 13, 0, }, /* 247 */
+ { 38, 21, 0, }, /* 248 */
+ { 38, 26, 0, }, /* 249 */
+ { 16, 9, 7264, }, /* 250 */
+ { 16, 7, 0, }, /* 251 */
+ { 16, 6, 0, }, /* 252 */
+ { 23, 7, 0, }, /* 253 */
+ { 15, 7, 0, }, /* 254 */
+ { 15, 12, 0, }, /* 255 */
+ { 15, 26, 0, }, /* 256 */
+ { 15, 21, 0, }, /* 257 */
+ { 15, 15, 0, }, /* 258 */
+ { 8, 7, 0, }, /* 259 */
+ { 7, 17, 0, }, /* 260 */
+ { 7, 7, 0, }, /* 261 */
+ { 7, 21, 0, }, /* 262 */
+ { 40, 29, 0, }, /* 263 */
+ { 40, 7, 0, }, /* 264 */
+ { 40, 22, 0, }, /* 265 */
+ { 40, 18, 0, }, /* 266 */
+ { 45, 7, 0, }, /* 267 */
+ { 45, 14, 0, }, /* 268 */
+ { 50, 7, 0, }, /* 269 */
+ { 50, 12, 0, }, /* 270 */
+ { 24, 7, 0, }, /* 271 */
+ { 24, 12, 0, }, /* 272 */
+ { 6, 7, 0, }, /* 273 */
+ { 6, 12, 0, }, /* 274 */
+ { 51, 7, 0, }, /* 275 */
+ { 51, 12, 0, }, /* 276 */
+ { 31, 7, 0, }, /* 277 */
+ { 31, 1, 0, }, /* 278 */
+ { 31, 10, 0, }, /* 279 */
+ { 31, 12, 0, }, /* 280 */
+ { 31, 21, 0, }, /* 281 */
+ { 31, 6, 0, }, /* 282 */
+ { 31, 23, 0, }, /* 283 */
+ { 31, 13, 0, }, /* 284 */
+ { 31, 15, 0, }, /* 285 */
+ { 37, 21, 0, }, /* 286 */
+ { 37, 17, 0, }, /* 287 */
+ { 37, 12, 0, }, /* 288 */
+ { 37, 29, 0, }, /* 289 */
+ { 37, 13, 0, }, /* 290 */
+ { 37, 7, 0, }, /* 291 */
+ { 37, 6, 0, }, /* 292 */
+ { 34, 7, 0, }, /* 293 */
+ { 34, 12, 0, }, /* 294 */
+ { 34, 10, 0, }, /* 295 */
+ { 34, 26, 0, }, /* 296 */
+ { 34, 21, 0, }, /* 297 */
+ { 34, 13, 0, }, /* 298 */
+ { 52, 7, 0, }, /* 299 */
+ { 39, 7, 0, }, /* 300 */
+ { 39, 10, 0, }, /* 301 */
+ { 39, 13, 0, }, /* 302 */
+ { 39, 15, 0, }, /* 303 */
+ { 39, 26, 0, }, /* 304 */
+ { 31, 26, 0, }, /* 305 */
+ { 5, 7, 0, }, /* 306 */
+ { 5, 12, 0, }, /* 307 */
+ { 5, 10, 0, }, /* 308 */
+ { 5, 21, 0, }, /* 309 */
+ { 90, 7, 0, }, /* 310 */
+ { 90, 10, 0, }, /* 311 */
+ { 90, 12, 0, }, /* 312 */
+ { 90, 13, 0, }, /* 313 */
+ { 90, 21, 0, }, /* 314 */
+ { 90, 6, 0, }, /* 315 */
+ { 61, 12, 0, }, /* 316 */
+ { 61, 10, 0, }, /* 317 */
+ { 61, 7, 0, }, /* 318 */
+ { 61, 13, 0, }, /* 319 */
+ { 61, 21, 0, }, /* 320 */
+ { 61, 26, 0, }, /* 321 */
+ { 75, 12, 0, }, /* 322 */
+ { 75, 10, 0, }, /* 323 */
+ { 75, 7, 0, }, /* 324 */
+ { 75, 13, 0, }, /* 325 */
+ { 92, 7, 0, }, /* 326 */
+ { 92, 12, 0, }, /* 327 */
+ { 92, 10, 0, }, /* 328 */
+ { 92, 21, 0, }, /* 329 */
+ { 69, 7, 0, }, /* 330 */
+ { 69, 10, 0, }, /* 331 */
+ { 69, 12, 0, }, /* 332 */
+ { 69, 21, 0, }, /* 333 */
+ { 69, 13, 0, }, /* 334 */
+ { 72, 13, 0, }, /* 335 */
+ { 72, 7, 0, }, /* 336 */
+ { 72, 6, 0, }, /* 337 */
+ { 72, 21, 0, }, /* 338 */
+ { 9, 10, 0, }, /* 339 */
+ { 9, 7, 0, }, /* 340 */
+ { 12, 5, 0, }, /* 341 */
+ { 12, 6, 0, }, /* 342 */
+ { 33, 5, 35332, }, /* 343 */
+ { 33, 5, 3814, }, /* 344 */
+ { 33, 5, -59, }, /* 345 */
+ { 33, 9, -7615, }, /* 346 */
+ { 19, 5, 8, }, /* 347 */
+ { 19, 9, -8, }, /* 348 */
+ { 19, 5, 74, }, /* 349 */
+ { 19, 5, 86, }, /* 350 */
+ { 19, 5, 100, }, /* 351 */
+ { 19, 5, 128, }, /* 352 */
+ { 19, 5, 112, }, /* 353 */
+ { 19, 5, 126, }, /* 354 */
+ { 19, 8, -8, }, /* 355 */
+ { 19, 5, 9, }, /* 356 */
+ { 19, 9, -74, }, /* 357 */
+ { 19, 8, -9, }, /* 358 */
+ { 19, 5, -7205, }, /* 359 */
+ { 19, 9, -86, }, /* 360 */
+ { 19, 9, -100, }, /* 361 */
+ { 19, 9, -112, }, /* 362 */
+ { 19, 9, -128, }, /* 363 */
+ { 19, 9, -126, }, /* 364 */
+ { 27, 1, 0, }, /* 365 */
+ { 9, 27, 0, }, /* 366 */
+ { 9, 28, 0, }, /* 367 */
+ { 27, 11, 0, }, /* 368 */
+ { 9, 9, 0, }, /* 369 */
+ { 9, 5, 0, }, /* 370 */
+ { 19, 9, -7517, }, /* 371 */
+ { 33, 9, -8383, }, /* 372 */
+ { 33, 9, -8262, }, /* 373 */
+ { 33, 9, 28, }, /* 374 */
+ { 33, 5, -28, }, /* 375 */
+ { 33, 14, 16, }, /* 376 */
+ { 33, 14, -16, }, /* 377 */
+ { 33, 14, 0, }, /* 378 */
+ { 9, 26, 26, }, /* 379 */
+ { 9, 26, -26, }, /* 380 */
+ { 4, 26, 0, }, /* 381 */
+ { 17, 9, 48, }, /* 382 */
+ { 17, 5, -48, }, /* 383 */
+ { 33, 9, -10743, }, /* 384 */
+ { 33, 9, -3814, }, /* 385 */
+ { 33, 9, -10727, }, /* 386 */
+ { 33, 5, -10795, }, /* 387 */
+ { 33, 5, -10792, }, /* 388 */
+ { 33, 9, -10780, }, /* 389 */
+ { 33, 9, -10749, }, /* 390 */
+ { 33, 9, -10783, }, /* 391 */
+ { 33, 9, -10782, }, /* 392 */
+ { 33, 9, -10815, }, /* 393 */
+ { 10, 5, 0, }, /* 394 */
+ { 10, 26, 0, }, /* 395 */
+ { 10, 12, 0, }, /* 396 */
+ { 10, 21, 0, }, /* 397 */
+ { 10, 15, 0, }, /* 398 */
+ { 16, 5, -7264, }, /* 399 */
+ { 58, 7, 0, }, /* 400 */
+ { 58, 6, 0, }, /* 401 */
+ { 58, 21, 0, }, /* 402 */
+ { 58, 12, 0, }, /* 403 */
+ { 22, 26, 0, }, /* 404 */
+ { 22, 6, 0, }, /* 405 */
+ { 22, 14, 0, }, /* 406 */
+ { 23, 12, 0, }, /* 407 */
+ { 26, 7, 0, }, /* 408 */
+ { 26, 6, 0, }, /* 409 */
+ { 29, 7, 0, }, /* 410 */
+ { 29, 6, 0, }, /* 411 */
+ { 3, 7, 0, }, /* 412 */
+ { 23, 26, 0, }, /* 413 */
+ { 29, 26, 0, }, /* 414 */
+ { 22, 7, 0, }, /* 415 */
+ { 60, 7, 0, }, /* 416 */
+ { 60, 6, 0, }, /* 417 */
+ { 60, 26, 0, }, /* 418 */
+ { 85, 7, 0, }, /* 419 */
+ { 85, 6, 0, }, /* 420 */
+ { 85, 21, 0, }, /* 421 */
+ { 76, 7, 0, }, /* 422 */
+ { 76, 6, 0, }, /* 423 */
+ { 76, 21, 0, }, /* 424 */
+ { 76, 13, 0, }, /* 425 */
+ { 12, 7, 0, }, /* 426 */
+ { 12, 21, 0, }, /* 427 */
+ { 78, 7, 0, }, /* 428 */
+ { 78, 14, 0, }, /* 429 */
+ { 78, 12, 0, }, /* 430 */
+ { 78, 21, 0, }, /* 431 */
+ { 33, 9, -35332, }, /* 432 */
+ { 33, 9, -42280, }, /* 433 */
+ { 48, 7, 0, }, /* 434 */
+ { 48, 12, 0, }, /* 435 */
+ { 48, 10, 0, }, /* 436 */
+ { 48, 26, 0, }, /* 437 */
+ { 64, 7, 0, }, /* 438 */
+ { 64, 21, 0, }, /* 439 */
+ { 74, 10, 0, }, /* 440 */
+ { 74, 7, 0, }, /* 441 */
+ { 74, 12, 0, }, /* 442 */
+ { 74, 21, 0, }, /* 443 */
+ { 74, 13, 0, }, /* 444 */
+ { 14, 21, 0, }, /* 445 */
+ { 68, 13, 0, }, /* 446 */
+ { 68, 7, 0, }, /* 447 */
+ { 68, 12, 0, }, /* 448 */
+ { 68, 21, 0, }, /* 449 */
+ { 73, 7, 0, }, /* 450 */
+ { 73, 12, 0, }, /* 451 */
+ { 73, 10, 0, }, /* 452 */
+ { 73, 21, 0, }, /* 453 */
+ { 83, 12, 0, }, /* 454 */
+ { 83, 10, 0, }, /* 455 */
+ { 83, 7, 0, }, /* 456 */
+ { 83, 21, 0, }, /* 457 */
+ { 83, 6, 0, }, /* 458 */
+ { 83, 13, 0, }, /* 459 */
+ { 67, 7, 0, }, /* 460 */
+ { 67, 12, 0, }, /* 461 */
+ { 67, 10, 0, }, /* 462 */
+ { 67, 13, 0, }, /* 463 */
+ { 67, 21, 0, }, /* 464 */
+ { 38, 6, 0, }, /* 465 */
+ { 91, 7, 0, }, /* 466 */
+ { 91, 12, 0, }, /* 467 */
+ { 91, 6, 0, }, /* 468 */
+ { 91, 21, 0, }, /* 469 */
+ { 86, 7, 0, }, /* 470 */
+ { 86, 10, 0, }, /* 471 */
+ { 86, 12, 0, }, /* 472 */
+ { 86, 21, 0, }, /* 473 */
+ { 86, 13, 0, }, /* 474 */
+ { 9, 4, 0, }, /* 475 */
+ { 9, 3, 0, }, /* 476 */
+ { 25, 25, 0, }, /* 477 */
+ { 0, 24, 0, }, /* 478 */
+ { 35, 7, 0, }, /* 479 */
+ { 19, 14, 0, }, /* 480 */
+ { 19, 15, 0, }, /* 481 */
+ { 19, 26, 0, }, /* 482 */
+ { 70, 7, 0, }, /* 483 */
+ { 66, 7, 0, }, /* 484 */
+ { 41, 7, 0, }, /* 485 */
+ { 41, 15, 0, }, /* 486 */
+ { 18, 7, 0, }, /* 487 */
+ { 18, 14, 0, }, /* 488 */
+ { 59, 7, 0, }, /* 489 */
+ { 59, 21, 0, }, /* 490 */
+ { 42, 7, 0, }, /* 491 */
+ { 42, 21, 0, }, /* 492 */
+ { 42, 14, 0, }, /* 493 */
+ { 13, 9, 40, }, /* 494 */
+ { 13, 5, -40, }, /* 495 */
+ { 46, 7, 0, }, /* 496 */
+ { 44, 7, 0, }, /* 497 */
+ { 44, 13, 0, }, /* 498 */
+ { 11, 7, 0, }, /* 499 */
+ { 80, 7, 0, }, /* 500 */
+ { 80, 21, 0, }, /* 501 */
+ { 80, 15, 0, }, /* 502 */
+ { 65, 7, 0, }, /* 503 */
+ { 65, 15, 0, }, /* 504 */
+ { 65, 21, 0, }, /* 505 */
+ { 71, 7, 0, }, /* 506 */
+ { 71, 21, 0, }, /* 507 */
+ { 30, 7, 0, }, /* 508 */
+ { 30, 12, 0, }, /* 509 */
+ { 30, 15, 0, }, /* 510 */
+ { 30, 21, 0, }, /* 511 */
+ { 87, 7, 0, }, /* 512 */
+ { 87, 15, 0, }, /* 513 */
+ { 87, 21, 0, }, /* 514 */
+ { 77, 7, 0, }, /* 515 */
+ { 77, 21, 0, }, /* 516 */
+ { 82, 7, 0, }, /* 517 */
+ { 82, 15, 0, }, /* 518 */
+ { 81, 7, 0, }, /* 519 */
+ { 81, 15, 0, }, /* 520 */
+ { 88, 7, 0, }, /* 521 */
+ { 0, 15, 0, }, /* 522 */
+ { 93, 10, 0, }, /* 523 */
+ { 93, 12, 0, }, /* 524 */
+ { 93, 7, 0, }, /* 525 */
+ { 93, 21, 0, }, /* 526 */
+ { 93, 15, 0, }, /* 527 */
+ { 93, 13, 0, }, /* 528 */
+ { 84, 12, 0, }, /* 529 */
+ { 84, 10, 0, }, /* 530 */
+ { 84, 7, 0, }, /* 531 */
+ { 84, 21, 0, }, /* 532 */
+ { 84, 1, 0, }, /* 533 */
+ { 62, 7, 0, }, /* 534 */
+ { 62, 14, 0, }, /* 535 */
+ { 62, 21, 0, }, /* 536 */
+ { 79, 7, 0, }, /* 537 */
+ { 19, 12, 0, }, /* 538 */
+ { 26, 26, 0, }, /* 539 */
+};
+
+const uschar _pcre_ucd_stage1[] = { /* 8704 bytes */
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* U+0000 */
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, /* U+0800 */
+ 32, 33, 34, 34, 35, 36, 37, 38, 39, 40, 40, 40, 41, 42, 43, 44, /* U+1000 */
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, /* U+1800 */
+ 61, 62, 63, 64, 65, 65, 66, 67, 68, 69, 70, 71, 72, 70, 73, 74, /* U+2000 */
+ 75, 75, 65, 76, 65, 65, 77, 17, 78, 79, 80, 81, 82, 83, 84, 85, /* U+2800 */
+ 86, 87, 88, 89, 90, 91, 92, 70, 93, 93, 93, 93, 93, 93, 93, 93, /* U+3000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+3800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+4000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 94, 93, 93, 93, 93, /* U+4800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+5000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+5800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+6000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+6800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+7000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+7800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+8000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+8800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+9000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 95, /* U+9800 */
+ 96, 97, 97, 97, 97, 97, 97, 97, 97, 98, 99, 99,100,101,102,103, /* U+A000 */
+104,105,106,107,108,109,110,111, 34, 34, 34, 34, 34, 34, 34, 34, /* U+A800 */
+ 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, /* U+B000 */
+ 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, /* U+B800 */
+ 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, /* U+C000 */
+ 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, /* U+C800 */
+ 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,112, /* U+D000 */
+113,113,113,113,113,113,113,113,113,113,113,113,113,113,113,113, /* U+D800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+E000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+E800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F000 */
+114,114, 93, 93,115,116,117,118,119,119,120,121,122,123,124,125, /* U+F800 */
+126,127,128,129, 17,130,131,132,133,134, 17, 17, 17, 17, 17, 17, /* U+10000 */
+135, 17,136, 17,137, 17,138, 17,139, 17, 17, 17,140, 17, 17, 17, /* U+10800 */
+141,142, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+11000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+11800 */
+143,143,143,143,143,143,144, 17,145, 17, 17, 17, 17, 17, 17, 17, /* U+12000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+12800 */
+146,146,146,146,146,146,146,146,147, 17, 17, 17, 17, 17, 17, 17, /* U+13000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+13800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+14000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+14800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+15000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+15800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+16000 */
+148,148,148,148,149, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+16800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+17000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+17800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+18000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+18800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+19000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+19800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+1A000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+1A800 */
+150, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+1B000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+1B800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+1C000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+1C800 */
+ 70,151,152,153,154, 17,155, 17,156,157,158,159,160,161,162,163, /* U+1D000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+1D800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+1E000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+1E800 */
+164,165,166,167,168, 17,169,170,171,172,173,174,175,176,177, 17, /* U+1F000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+1F800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+20000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+20800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+21000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+21800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+22000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+22800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+23000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+23800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+24000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+24800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+25000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+25800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+26000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+26800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+27000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+27800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+28000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+28800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+29000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+29800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93,178, 93, 93, /* U+2A000 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, /* U+2A800 */
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93,179, 93, /* U+2B000 */
+180, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+2B800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+2C000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+2C800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+2D000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+2D800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+2E000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+2E800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+2F000 */
+ 93, 93, 93, 93,180, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+2F800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+30000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+30800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+31000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+31800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+32000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+32800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+33000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+33800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+34000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+34800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+35000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+35800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+36000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+36800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+37000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+37800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+38000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+38800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+39000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+39800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+3A000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+3A800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+3B000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+3B800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+3C000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+3C800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+3D000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+3D800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+3E000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+3E800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+3F000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+3F800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+40000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+40800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+41000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+41800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+42000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+42800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+43000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+43800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+44000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+44800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+45000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+45800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+46000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+46800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+47000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+47800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+48000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+48800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+49000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+49800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+4A000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+4A800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+4B000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+4B800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+4C000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+4C800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+4D000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+4D800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+4E000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+4E800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+4F000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+4F800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+50000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+50800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+51000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+51800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+52000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+52800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+53000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+53800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+54000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+54800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+55000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+55800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+56000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+56800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+57000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+57800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+58000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+58800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+59000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+59800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+5A000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+5A800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+5B000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+5B800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+5C000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+5C800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+5D000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+5D800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+5E000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+5E800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+5F000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+5F800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+60000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+60800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+61000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+61800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+62000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+62800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+63000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+63800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+64000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+64800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+65000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+65800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+66000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+66800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+67000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+67800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+68000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+68800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+69000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+69800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+6A000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+6A800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+6B000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+6B800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+6C000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+6C800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+6D000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+6D800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+6E000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+6E800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+6F000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+6F800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+70000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+70800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+71000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+71800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+72000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+72800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+73000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+73800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+74000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+74800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+75000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+75800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+76000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+76800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+77000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+77800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+78000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+78800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+79000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+79800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+7A000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+7A800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+7B000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+7B800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+7C000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+7C800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+7D000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+7D800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+7E000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+7E800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+7F000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+7F800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+80000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+80800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+81000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+81800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+82000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+82800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+83000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+83800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+84000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+84800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+85000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+85800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+86000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+86800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+87000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+87800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+88000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+88800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+89000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+89800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+8A000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+8A800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+8B000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+8B800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+8C000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+8C800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+8D000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+8D800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+8E000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+8E800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+8F000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+8F800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+90000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+90800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+91000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+91800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+92000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+92800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+93000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+93800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+94000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+94800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+95000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+95800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+96000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+96800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+97000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+97800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+98000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+98800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+99000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+99800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+9A000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+9A800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+9B000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+9B800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+9C000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+9C800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+9D000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+9D800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+9E000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+9E800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+9F000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+9F800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A0000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A0800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A1000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A1800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A2000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A2800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A3000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A3800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A4000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A4800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A5000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A5800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A6000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A6800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A7000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A7800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A8000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A8800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A9000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+A9800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+AA000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+AA800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+AB000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+AB800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+AC000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+AC800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+AD000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+AD800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+AE000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+AE800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+AF000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+AF800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B0000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B0800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B1000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B1800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B2000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B2800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B3000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B3800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B4000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B4800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B5000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B5800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B6000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B6800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B7000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B7800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B8000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B8800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B9000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+B9800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+BA000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+BA800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+BB000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+BB800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+BC000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+BC800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+BD000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+BD800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+BE000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+BE800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+BF000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+BF800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C0000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C0800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C1000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C1800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C2000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C2800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C3000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C3800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C4000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C4800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C5000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C5800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C6000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C6800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C7000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C7800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C8000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C8800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C9000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+C9800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+CA000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+CA800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+CB000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+CB800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+CC000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+CC800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+CD000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+CD800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+CE000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+CE800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+CF000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+CF800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D0000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D0800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D1000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D1800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D2000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D2800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D3000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D3800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D4000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D4800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D5000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D5800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D6000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D6800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D7000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D7800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D8000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D8800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D9000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+D9800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+DA000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+DA800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+DB000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+DB800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+DC000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+DC800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+DD000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+DD800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+DE000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+DE800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+DF000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+DF800 */
+181, 17,182,183, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E0000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E0800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E1000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E1800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E2000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E2800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E3000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E3800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E4000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E4800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E5000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E5800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E6000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E6800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E7000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E7800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E8000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E8800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E9000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+E9800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+EA000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+EA800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+EB000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+EB800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+EC000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+EC800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+ED000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+ED800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+EE000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+EE800 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+EF000 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, /* U+EF800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F0000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F0800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F1000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F1800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F2000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F2800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F3000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F3800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F4000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F4800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F5000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F5800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F6000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F6800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F7000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F7800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F8000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F8800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F9000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+F9800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+FA000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+FA800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+FB000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+FB800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+FC000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+FC800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+FD000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+FD800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+FE000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+FE800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+FF000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,184, /* U+FF800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+100000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+100800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+101000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+101800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+102000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+102800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+103000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+103800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+104000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+104800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+105000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+105800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+106000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+106800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+107000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+107800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+108000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+108800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+109000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+109800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+10A000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+10A800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+10B000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+10B800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+10C000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+10C800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+10D000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+10D800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+10E000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+10E800 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114, /* U+10F000 */
+114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,184, /* U+10F800 */
+};
+
+const pcre_uint16 _pcre_ucd_stage2[] = { /* 47360 bytes, block = 128 */
+/* block 0 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 2, 2, 2, 3, 2, 2, 2, 4, 5, 2, 6, 2, 7, 2, 2,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 2, 2, 6, 6, 6, 2,
+ 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 4, 2, 5, 10, 11,
+ 10, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 4, 6, 5, 6, 0,
+
+/* block 1 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 2, 3, 3, 3, 3, 13, 13, 10, 13, 14, 15, 6, 16, 13, 10,
+ 13, 6, 17, 17, 10, 18, 13, 2, 10, 17, 14, 19, 17, 17, 17, 2,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 6, 9, 9, 9, 9, 9, 9, 9, 14,
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 6, 12, 12, 12, 12, 12, 12, 12, 20,
+
+/* block 2 */
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 23, 24, 21, 22, 21, 22, 21, 22, 14, 21, 22, 21, 22, 21, 22, 21,
+ 22, 21, 22, 21, 22, 21, 22, 21, 22, 14, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 25, 21, 22, 21, 22, 21, 22, 26,
+
+/* block 3 */
+ 27, 28, 21, 22, 21, 22, 29, 21, 22, 30, 30, 21, 22, 14, 31, 32,
+ 33, 21, 22, 30, 34, 35, 36, 37, 21, 22, 38, 14, 36, 39, 40, 41,
+ 21, 22, 21, 22, 21, 22, 42, 21, 22, 42, 14, 14, 21, 22, 42, 21,
+ 22, 43, 43, 21, 22, 21, 22, 44, 21, 22, 14, 45, 21, 22, 14, 46,
+ 45, 45, 45, 45, 47, 48, 49, 47, 48, 49, 47, 48, 49, 21, 22, 21,
+ 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 50, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 14, 47, 48, 49, 21, 22, 51, 52, 21, 22, 21, 22, 21, 22, 21, 22,
+
+/* block 4 */
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 53, 14, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 14, 14, 14, 14, 14, 14, 54, 21, 22, 55, 56, 57,
+ 57, 21, 22, 58, 59, 60, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 61, 62, 63, 64, 65, 14, 66, 66, 14, 67, 14, 68, 14, 14, 14, 14,
+ 66, 14, 14, 69, 14, 70, 14, 14, 71, 72, 14, 73, 14, 14, 14, 72,
+ 14, 74, 75, 14, 14, 76, 14, 14, 14, 14, 14, 14, 14, 77, 14, 14,
+
+/* block 5 */
+ 78, 14, 14, 78, 14, 14, 14, 14, 78, 79, 80, 80, 81, 14, 14, 14,
+ 14, 14, 82, 14, 45, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 84, 84, 84, 84, 84, 84, 84,
+ 84, 84, 10, 10, 10, 10, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84,
+ 84, 84, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 83, 83, 83, 83, 83, 10, 10, 10, 10, 10, 85, 85, 84, 10, 84, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+
+/* block 6 */
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 87, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 88, 89, 88, 89, 84, 90, 88, 89, 91, 91, 92, 93, 93, 93, 2, 91,
+
+/* block 7 */
+ 91, 91, 91, 91, 90, 10, 94, 2, 95, 95, 95, 91, 96, 91, 97, 97,
+ 98, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 91, 99, 99, 99, 99, 99, 99, 99, 99, 99,100,101,101,101,
+ 98,102,102,102,102,102,102,102,102,102,102,102,102,102,102,102,
+102,102,103,102,102,102,102,102,102,102,102,102,104,105,105,106,
+107,108,109,109,109,110,111,112, 88, 89, 88, 89, 88, 89, 88, 89,
+ 88, 89,113,114,113,114,113,114,113,114,113,114,113,114,113,114,
+115,116,117, 98,118,119,120, 88, 89,121, 88, 89, 98,122,122,122,
+
+/* block 8 */
+123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,123,
+124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,
+124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,124,
+125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,
+125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,125,
+126,126,126,126,126,126,126,126,126,126,126,126,126,126,126,126,
+127,128,127,128,127,128,127,128,127,128,127,128,127,128,127,128,
+127,128,127,128,127,128,127,128,127,128,127,128,127,128,127,128,
+
+/* block 9 */
+127,128,129,130,130, 86, 86,130,131,131,127,128,127,128,127,128,
+127,128,127,128,127,128,127,128,127,128,127,128,127,128,127,128,
+127,128,127,128,127,128,127,128,127,128,127,128,127,128,127,128,
+127,128,127,128,127,128,127,128,127,128,127,128,127,128,127,128,
+132,127,128,127,128,127,128,127,128,127,128,127,128,127,128,133,
+127,128,127,128,127,128,127,128,127,128,127,128,127,128,127,128,
+127,128,127,128,127,128,127,128,127,128,127,128,127,128,127,128,
+127,128,127,128,127,128,127,128,127,128,127,128,127,128,127,128,
+
+/* block 10 */
+127,128,127,128,127,128,127,128,127,128,127,128,127,128,127,128,
+127,128,127,128,127,128,127,128,127,128,127,128,127,128,127,128,
+127,128,127,128,127,128,127,128, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91,134,134,134,134,134,134,134,134,134,134,134,134,134,134,134,
+134,134,134,134,134,134,134,134,134,134,134,134,134,134,134,134,
+134,134,134,134,134,134,134, 91, 91,135,136,136,136,136,136,136,
+ 91,137,137,137,137,137,137,137,137,137,137,137,137,137,137,137,
+137,137,137,137,137,137,137,137,137,137,137,137,137,137,137,137,
+
+/* block 11 */
+137,137,137,137,137,137,137,138, 91, 2,139, 91, 91, 91, 91, 91,
+ 91,140,140,140,140,140,140,140,140,140,140,140,140,140,140,140,
+140,140,140,140,140,140,140,140,140,140,140,140,140,140,140,140,
+140,140,140,140,140,140,140,140,140,140,140,140,140,140,141,140,
+142,140,140,142,140,140,142,140, 91, 91, 91, 91, 91, 91, 91, 91,
+143,143,143,143,143,143,143,143,143,143,143,143,143,143,143,143,
+143,143,143,143,143,143,143,143,143,143,143, 91, 91, 91, 91, 91,
+143,143,143,142,142, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 12 */
+144,144,144,144, 91, 91,145,145,145,146,146,147, 2,146,148,148,
+149,149,149,149,149,149,149,149,149,149,149, 2, 91, 91,146, 2,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+ 84,150,150,150,150,150,150,150,150,150,150, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86,149,149,149,149,149,149,149,149,149, 86,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,146,146,146,146,150,150,
+ 86,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+
+/* block 13 */
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,146,150,149,149,149,149,149,149,149, 16,148,149,
+149,149,149,149,149,151,151,149,149,148,149,149,149,149,150,150,
+152,152,152,152,152,152,152,152,152,152,150,150,150,148,148,150,
+
+/* block 14 */
+153,153,153,153,153,153,153,153,153,153,153,153,153,153, 91,154,
+155,156,155,155,155,155,155,155,155,155,155,155,155,155,155,155,
+155,155,155,155,155,155,155,155,155,155,155,155,155,155,155,155,
+156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,
+156,156,156,156,156,156,156,156,156,156,156, 91, 91,155,155,155,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+
+/* block 15 */
+157,157,157,157,157,157,157,157,157,157,157,157,157,157,157,157,
+157,157,157,157,157,157,157,157,157,157,157,157,157,157,157,157,
+157,157,157,157,157,157,158,158,158,158,158,158,158,158,158,158,
+158,157, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+159,159,159,159,159,159,159,159,159,159,160,160,160,160,160,160,
+160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,
+160,160,160,160,160,160,160,160,160,160,160,161,161,161,161,161,
+161,161,161,161,162,162,163,164,164,164,162, 91, 91, 91, 91, 91,
+
+/* block 16 */
+165,165,165,165,165,165,165,165,165,165,165,165,165,165,165,165,
+165,165,165,165,165,165,166,166,166,166,167,166,166,166,166,166,
+166,166,166,166,167,166,166,166,167,166,166,166,166,166, 91, 91,
+168,168,168,168,168,168,168,168,168,168,168,168,168,168,168, 91,
+169,169,169,169,169,169,169,169,169,169,169,169,169,169,169,169,
+169,169,169,169,169,169,169,169,169,170,170,170, 91, 91,171, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 17 */
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 18 */
+172,172,172,173,174,174,174,174,174,174,174,174,174,174,174,174,
+174,174,174,174,174,174,174,174,174,174,174,174,174,174,174,174,
+174,174,174,174,174,174,174,174,174,174,174,174,174,174,174,174,
+174,174,174,174,174,174,174,174,174,174,172,173,172,174,173,173,
+173,172,172,172,172,172,172,172,172,173,173,173,173,172,173,173,
+174, 86, 86,172,172,172,172,172,174,174,174,174,174,174,174,174,
+174,174,172,172, 2, 2,175,175,175,175,175,175,175,175,175,175,
+ 2,176,174,174,174,174,174,174, 91,174,174,174,174,174,174,174,
+
+/* block 19 */
+ 91,177,178,178, 91,179,179,179,179,179,179,179,179, 91, 91,179,
+179, 91, 91,179,179,179,179,179,179,179,179,179,179,179,179,179,
+179,179,179,179,179,179,179,179,179, 91,179,179,179,179,179,179,
+179, 91,179, 91, 91, 91,179,179,179,179, 91, 91,177,179,178,178,
+178,177,177,177,177, 91, 91,178,178, 91, 91,178,178,177,179, 91,
+ 91, 91, 91, 91, 91, 91, 91,178, 91, 91, 91, 91,179,179, 91,179,
+179,179,177,177, 91, 91,180,180,180,180,180,180,180,180,180,180,
+179,179,181,181,182,182,182,182,182,182,183,181, 91, 91, 91, 91,
+
+/* block 20 */
+ 91,184,184,185, 91,186,186,186,186,186,186, 91, 91, 91, 91,186,
+186, 91, 91,186,186,186,186,186,186,186,186,186,186,186,186,186,
+186,186,186,186,186,186,186,186,186, 91,186,186,186,186,186,186,
+186, 91,186,186, 91,186,186, 91,186,186, 91, 91,184, 91,185,185,
+185,184,184, 91, 91, 91, 91,184,184, 91, 91,184,184,184, 91, 91,
+ 91,184, 91, 91, 91, 91, 91, 91, 91,186,186,186,186, 91,186, 91,
+ 91, 91, 91, 91, 91, 91,187,187,187,187,187,187,187,187,187,187,
+184,184,186,186,186,184, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 21 */
+ 91,188,188,189, 91,190,190,190,190,190,190,190,190,190, 91,190,
+190,190, 91,190,190,190,190,190,190,190,190,190,190,190,190,190,
+190,190,190,190,190,190,190,190,190, 91,190,190,190,190,190,190,
+190, 91,190,190, 91,190,190,190,190,190, 91, 91,188,190,189,189,
+189,188,188,188,188,188, 91,188,188,189, 91,189,189,188, 91, 91,
+190, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+190,190,188,188, 91, 91,191,191,191,191,191,191,191,191,191,191,
+ 91,192, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 22 */
+ 91,193,194,194, 91,195,195,195,195,195,195,195,195, 91, 91,195,
+195, 91, 91,195,195,195,195,195,195,195,195,195,195,195,195,195,
+195,195,195,195,195,195,195,195,195, 91,195,195,195,195,195,195,
+195, 91,195,195, 91,195,195,195,195,195, 91, 91,193,195,194,193,
+194,193,193,193,193, 91, 91,194,194, 91, 91,194,194,193, 91, 91,
+ 91, 91, 91, 91, 91, 91,193,194, 91, 91, 91, 91,195,195, 91,195,
+195,195,193,193, 91, 91,196,196,196,196,196,196,196,196,196,196,
+197,195,198,198,198,198,198,198, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 23 */
+ 91, 91,199,200, 91,200,200,200,200,200,200, 91, 91, 91,200,200,
+200, 91,200,200,200,200, 91, 91, 91,200,200, 91,200, 91,200,200,
+ 91, 91, 91,200,200, 91, 91, 91,200,200,200, 91, 91, 91,200,200,
+200,200,200,200,200,200,200,200,200,200, 91, 91, 91, 91,201,201,
+199,201,201, 91, 91, 91,201,201,201, 91,201,201,201,199, 91, 91,
+200, 91, 91, 91, 91, 91, 91,201, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91,202,202,202,202,202,202,202,202,202,202,
+203,203,203,204,204,204,204,204,204,205,204, 91, 91, 91, 91, 91,
+
+/* block 24 */
+ 91,206,206,206, 91,207,207,207,207,207,207,207,207, 91,207,207,
+207, 91,207,207,207,207,207,207,207,207,207,207,207,207,207,207,
+207,207,207,207,207,207,207,207,207, 91,207,207,207,207,207,207,
+207,207,207,207, 91,207,207,207,207,207, 91, 91, 91,207,208,208,
+208,206,206,206,206, 91,208,208,208, 91,208,208,208,208, 91, 91,
+ 91, 91, 91, 91, 91,208,208, 91,207,207, 91, 91, 91, 91, 91, 91,
+207,207,208,208, 91, 91,209,209,209,209,209,209,209,209,209,209,
+ 91, 91, 91, 91, 91, 91, 91, 91,210,210,210,210,210,210,210,211,
+
+/* block 25 */
+ 91, 91,212,212, 91,213,213,213,213,213,213,213,213, 91,213,213,
+213, 91,213,213,213,213,213,213,213,213,213,213,213,213,213,213,
+213,213,213,213,213,213,213,213,213, 91,213,213,213,213,213,213,
+213,213,213,213, 91,213,213,213,213,213, 91, 91,214,213,212,214,
+212,212,212,212,212, 91,214,212,212, 91,212,212,214,214, 91, 91,
+ 91, 91, 91, 91, 91,212,212, 91, 91, 91, 91, 91, 91, 91,213, 91,
+213,213,214,214, 91, 91,215,215,215,215,215,215,215,215,215,215,
+ 91,213,213, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 26 */
+ 91, 91,216,216, 91,217,217,217,217,217,217,217,217, 91,217,217,
+217, 91,217,217,217,217,217,217,217,217,217,217,217,217,217,217,
+217,217,217,217,217,217,217,217,217,217,217,217,217,217,217,217,
+217,217,217,217,217,217,217,217,217,217,217, 91, 91,217,216,216,
+216,218,218,218,218, 91,216,216,216, 91,216,216,216,218,217, 91,
+ 91, 91, 91, 91, 91, 91, 91,216, 91, 91, 91, 91, 91, 91, 91, 91,
+217,217,218,218, 91, 91,219,219,219,219,219,219,219,219,219,219,
+220,220,220,220,220,220, 91, 91, 91,221,217,217,217,217,217,217,
+
+/* block 27 */
+ 91, 91,222,222, 91,223,223,223,223,223,223,223,223,223,223,223,
+223,223,223,223,223,223,223, 91, 91, 91,223,223,223,223,223,223,
+223,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223,
+223,223, 91,223,223,223,223,223,223,223,223,223, 91,223, 91, 91,
+223,223,223,223,223,223,223, 91, 91, 91,224, 91, 91, 91, 91,222,
+222,222,224,224,224, 91,224, 91,222,222,222,222,222,222,222,222,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91,222,222,225, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 28 */
+ 91,226,226,226,226,226,226,226,226,226,226,226,226,226,226,226,
+226,226,226,226,226,226,226,226,226,226,226,226,226,226,226,226,
+226,226,226,226,226,226,226,226,226,226,226,226,226,226,226,226,
+226,227,226,226,227,227,227,227,227,227,227, 91, 91, 91, 91, 3,
+226,226,226,226,226,226,228,227,227,227,227,227,227,227,227,229,
+230,230,230,230,230,230,230,230,230,230,229,229, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 29 */
+ 91,231,231, 91,231, 91, 91,231,231, 91,231, 91, 91,231, 91, 91,
+ 91, 91, 91, 91,231,231,231,231, 91,231,231,231,231,231,231,231,
+ 91,231,231,231, 91,231, 91,231, 91, 91,231,231, 91,231,231,231,
+231,232,231,231,232,232,232,232,232,232, 91,232,232,231, 91, 91,
+231,231,231,231,231, 91,233, 91,232,232,232,232,232,232, 91, 91,
+234,234,234,234,234,234,234,234,234,234, 91, 91,231,231, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 30 */
+235,236,236,236,237,237,237,237,237,237,237,237,237,237,237,237,
+237,237,237,236,236,236,236,236,238,238,236,236,236,236,236,236,
+239,239,239,239,239,239,239,239,239,239,240,240,240,240,240,240,
+240,240,240,240,236,238,236,238,236,238,241,242,241,242,243,243,
+235,235,235,235,235,235,235,235, 91,235,235,235,235,235,235,235,
+235,235,235,235,235,235,235,235,235,235,235,235,235,235,235,235,
+235,235,235,235,235,235,235,235,235,235,235,235,235, 91, 91, 91,
+ 91,238,238,238,238,238,238,238,238,238,238,238,238,238,238,243,
+
+/* block 31 */
+238,238,238,238,238,237,238,238,235,235,235,235,235,238,238,238,
+238,238,238,238,238,238,238,238, 91,238,238,238,238,238,238,238,
+238,238,238,238,238,238,238,238,238,238,238,238,238,238,238,238,
+238,238,238,238,238,238,238,238,238,238,238,238,238, 91,236,236,
+236,236,236,236,236,236,238,236,236,236,236,236,236, 91,236,236,
+237,237,237,237,237, 13, 13, 13, 13,237,237, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 32 */
+244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,
+244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,
+244,244,244,244,244,244,244,244,244,244,244,245,245,246,246,246,
+246,245,246,246,246,246,246,246,245,246,246,245,245,246,246,244,
+247,247,247,247,247,247,247,247,247,247,248,248,248,248,248,248,
+244,244,244,244,244,244,245,245,246,246,244,244,244,244,246,246,
+246,244,245,245,245,244,244,245,245,245,245,245,245,245,244,244,
+244,246,246,246,246,244,244,244,244,244,244,244,244,244,244,244,
+
+/* block 33 */
+244,244,246,245,245,246,246,245,245,245,245,245,245,246,244,245,
+247,247,247,247,247,247,247,247,247,247,245,245,245,246,249,249,
+250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
+250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,
+250,250,250,250,250,250, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,
+251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,
+251,251,251,251,251,251,251,251,251,251,251, 2,252, 91, 91, 91,
+
+/* block 34 */
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+
+/* block 35 */
+254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,
+254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,
+254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,
+254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,
+254,254,254,254,254,254,254,254,254, 91,254,254,254,254, 91, 91,
+254,254,254,254,254,254,254, 91,254, 91,254,254,254,254, 91, 91,
+254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,
+254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,
+
+/* block 36 */
+254,254,254,254,254,254,254,254,254, 91,254,254,254,254, 91, 91,
+254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,
+254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,
+254, 91,254,254,254,254, 91, 91,254,254,254,254,254,254,254, 91,
+254, 91,254,254,254,254, 91, 91,254,254,254,254,254,254,254,254,
+254,254,254,254,254,254,254, 91,254,254,254,254,254,254,254,254,
+254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,
+254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,
+
+/* block 37 */
+254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,
+254, 91,254,254,254,254, 91, 91,254,254,254,254,254,254,254,254,
+254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,
+254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,
+254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,
+254,254,254,254,254,254,254,254,254,254,254, 91, 91,255,255,255,
+256,257,257,257,257,257,257,257,257,258,258,258,258,258,258,258,
+258,258,258,258,258,258,258,258,258,258,258,258,258, 91, 91, 91,
+
+/* block 38 */
+254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,
+256,256,256,256,256,256,256,256,256,256, 91, 91, 91, 91, 91, 91,
+259,259,259,259,259,259,259,259,259,259,259,259,259,259,259,259,
+259,259,259,259,259,259,259,259,259,259,259,259,259,259,259,259,
+259,259,259,259,259,259,259,259,259,259,259,259,259,259,259,259,
+259,259,259,259,259,259,259,259,259,259,259,259,259,259,259,259,
+259,259,259,259,259,259,259,259,259,259,259,259,259,259,259,259,
+259,259,259,259,259, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 39 */
+260,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+
+/* block 40 */
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+
+/* block 41 */
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,262,262,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+
+/* block 42 */
+263,264,264,264,264,264,264,264,264,264,264,264,264,264,264,264,
+264,264,264,264,264,264,264,264,264,264,264,265,266, 91, 91, 91,
+267,267,267,267,267,267,267,267,267,267,267,267,267,267,267,267,
+267,267,267,267,267,267,267,267,267,267,267,267,267,267,267,267,
+267,267,267,267,267,267,267,267,267,267,267,267,267,267,267,267,
+267,267,267,267,267,267,267,267,267,267,267,267,267,267,267,267,
+267,267,267,267,267,267,267,267,267,267,267, 2, 2, 2,268,268,
+268, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 43 */
+269,269,269,269,269,269,269,269,269,269,269,269,269, 91,269,269,
+269,269,270,270,270, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+271,271,271,271,271,271,271,271,271,271,271,271,271,271,271,271,
+271,271,272,272,272, 2, 2, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+273,273,273,273,273,273,273,273,273,273,273,273,273,273,273,273,
+273,273,274,274, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+275,275,275,275,275,275,275,275,275,275,275,275,275, 91,275,275,
+275, 91,276,276, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 44 */
+277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,
+277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,
+277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,
+277,277,277,277,278,278,279,280,280,280,280,280,280,280,279,279,
+279,279,279,279,279,279,280,279,279,280,280,280,280,280,280,280,
+280,280,280,280,281,281,281,282,281,281,281,283,277,280, 91, 91,
+284,284,284,284,284,284,284,284,284,284, 91, 91, 91, 91, 91, 91,
+285,285,285,285,285,285,285,285,285,285, 91, 91, 91, 91, 91, 91,
+
+/* block 45 */
+286,286, 2, 2,286, 2,287,286,286,286,286,288,288,288,289, 91,
+290,290,290,290,290,290,290,290,290,290, 91, 91, 91, 91, 91, 91,
+291,291,291,291,291,291,291,291,291,291,291,291,291,291,291,291,
+291,291,291,291,291,291,291,291,291,291,291,291,291,291,291,291,
+291,291,291,292,291,291,291,291,291,291,291,291,291,291,291,291,
+291,291,291,291,291,291,291,291,291,291,291,291,291,291,291,291,
+291,291,291,291,291,291,291,291,291,291,291,291,291,291,291,291,
+291,291,291,291,291,291,291,291, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 46 */
+291,291,291,291,291,291,291,291,291,291,291,291,291,291,291,291,
+291,291,291,291,291,291,291,291,291,291,291,291,291,291,291,291,
+291,291,291,291,291,291,291,291,291,288,291, 91, 91, 91, 91, 91,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,261,
+261,261,261,261,261,261, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 47 */
+293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,293,
+293,293,293,293,293,293,293,293,293,293,293,293,293, 91, 91, 91,
+294,294,294,295,295,295,295,294,294,295,295,295, 91, 91, 91, 91,
+295,295,294,295,295,295,295,295,295,294,294,294, 91, 91, 91, 91,
+296, 91, 91, 91,297,297,298,298,298,298,298,298,298,298,298,298,
+299,299,299,299,299,299,299,299,299,299,299,299,299,299,299,299,
+299,299,299,299,299,299,299,299,299,299,299,299,299,299, 91, 91,
+299,299,299,299,299, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 48 */
+300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,
+300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,
+300,300,300,300,300,300,300,300,300,300,300,300, 91, 91, 91, 91,
+301,301,301,301,301,301,301,301,301,301,301,301,301,301,301,301,
+301,300,300,300,300,300,300,300,301,301, 91, 91, 91, 91, 91, 91,
+302,302,302,302,302,302,302,302,302,302,303, 91, 91, 91,304,304,
+305,305,305,305,305,305,305,305,305,305,305,305,305,305,305,305,
+305,305,305,305,305,305,305,305,305,305,305,305,305,305,305,305,
+
+/* block 49 */
+306,306,306,306,306,306,306,306,306,306,306,306,306,306,306,306,
+306,306,306,306,306,306,306,307,307,308,308,308, 91, 91,309,309,
+310,310,310,310,310,310,310,310,310,310,310,310,310,310,310,310,
+310,310,310,310,310,310,310,310,310,310,310,310,310,310,310,310,
+310,310,310,310,310,310,310,310,310,310,310,310,310,310,310,310,
+310,310,310,310,310,311,312,311,312,312,312,312,312,312,312, 91,
+312,311,312,311,311,312,312,312,312,312,312,312,312,311,311,311,
+311,311,311,312,312,312,312,312,312,312,312,312,312, 91, 91,312,
+
+/* block 50 */
+313,313,313,313,313,313,313,313,313,313, 91, 91, 91, 91, 91, 91,
+313,313,313,313,313,313,313,313,313,313, 91, 91, 91, 91, 91, 91,
+314,314,314,314,314,314,314,315,314,314,314,314,314,314, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 51 */
+316,316,316,316,317,318,318,318,318,318,318,318,318,318,318,318,
+318,318,318,318,318,318,318,318,318,318,318,318,318,318,318,318,
+318,318,318,318,318,318,318,318,318,318,318,318,318,318,318,318,
+318,318,318,318,316,317,316,316,316,316,316,317,316,317,317,317,
+317,317,316,317,317,318,318,318,318,318,318,318, 91, 91, 91, 91,
+319,319,319,319,319,319,319,319,319,319,320,320,320,320,320,320,
+320,321,321,321,321,321,321,321,321,321,321,316,316,316,316,316,
+316,316,316,316,321,321,321,321,321,321,321,321,321, 91, 91, 91,
+
+/* block 52 */
+322,322,323,324,324,324,324,324,324,324,324,324,324,324,324,324,
+324,324,324,324,324,324,324,324,324,324,324,324,324,324,324,324,
+324,323,322,322,322,322,323,323,322,322,323, 91, 91, 91,324,324,
+325,325,325,325,325,325,325,325,325,325, 91, 91, 91, 91, 91, 91,
+326,326,326,326,326,326,326,326,326,326,326,326,326,326,326,326,
+326,326,326,326,326,326,326,326,326,326,326,326,326,326,326,326,
+326,326,326,326,326,326,327,328,327,327,328,328,328,327,328,327,
+327,327,328,328, 91, 91, 91, 91, 91, 91, 91, 91,329,329,329,329,
+
+/* block 53 */
+330,330,330,330,330,330,330,330,330,330,330,330,330,330,330,330,
+330,330,330,330,330,330,330,330,330,330,330,330,330,330,330,330,
+330,330,330,330,331,331,331,331,331,331,331,331,332,332,332,332,
+332,332,332,332,331,331,332,332, 91, 91, 91,333,333,333,333,333,
+334,334,334,334,334,334,334,334,334,334, 91, 91, 91,330,330,330,
+335,335,335,335,335,335,335,335,335,335,336,336,336,336,336,336,
+336,336,336,336,336,336,336,336,336,336,336,336,336,336,336,336,
+336,336,336,336,336,336,336,336,337,337,337,337,337,337,338,338,
+
+/* block 54 */
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 86, 86, 86, 2, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86,339, 86, 86, 86, 86, 86, 86, 86,340,340,340,340, 86,340,340,
+340,340,339, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 55 */
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 98, 98, 98, 98, 98,341, 83, 83, 83, 83,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 92, 92, 92,
+ 92, 92, 14, 14, 14, 14, 98, 98, 98, 98, 98, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14,342,343, 14, 14, 14,344, 14, 14,
+
+/* block 56 */
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 83, 83, 83, 83, 83,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 92,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 86, 86, 86, 86,
+
+/* block 57 */
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+
+/* block 58 */
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 14, 14, 14, 14, 14,345, 14, 14,346, 14,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+
+/* block 59 */
+347,347,347,347,347,347,347,347,348,348,348,348,348,348,348,348,
+347,347,347,347,347,347, 91, 91,348,348,348,348,348,348, 91, 91,
+347,347,347,347,347,347,347,347,348,348,348,348,348,348,348,348,
+347,347,347,347,347,347,347,347,348,348,348,348,348,348,348,348,
+347,347,347,347,347,347, 91, 91,348,348,348,348,348,348, 91, 91,
+ 98,347, 98,347, 98,347, 98,347, 91,348, 91,348, 91,348, 91,348,
+347,347,347,347,347,347,347,347,348,348,348,348,348,348,348,348,
+349,349,350,350,350,350,351,351,352,352,353,353,354,354, 91, 91,
+
+/* block 60 */
+347,347,347,347,347,347,347,347,355,355,355,355,355,355,355,355,
+347,347,347,347,347,347,347,347,355,355,355,355,355,355,355,355,
+347,347,347,347,347,347,347,347,355,355,355,355,355,355,355,355,
+347,347, 98,356, 98, 91, 98, 98,348,348,357,357,358, 90,359, 90,
+ 90, 90, 98,356, 98, 91, 98, 98,360,360,360,360,358, 90, 90, 90,
+347,347, 98, 98, 91, 91, 98, 98,348,348,361,361, 91, 90, 90, 90,
+347,347, 98, 98, 98,117, 98, 98,348,348,362,362,121, 90, 90, 90,
+ 91, 91, 98,356, 98, 91, 98, 98,363,363,364,364,358, 90, 90, 91,
+
+/* block 61 */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16,365,365, 16, 16,
+ 7, 7, 7, 7, 7, 7, 2, 2, 15, 19, 4, 15, 15, 19, 4, 15,
+ 2, 2, 2, 2, 2, 2, 2, 2,366,367, 16, 16, 16, 16, 16, 1,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 15, 19, 2, 2, 2, 2, 11,
+ 11, 2, 2, 2, 6, 4, 5, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 6, 2, 11, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1,
+ 16, 16, 16, 16, 16, 91, 91, 91, 91, 91, 16, 16, 16, 16, 16, 16,
+ 17, 83, 91, 91, 17, 17, 17, 17, 17, 17, 6, 6, 6, 4, 5, 83,
+
+/* block 62 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 6, 6, 6, 4, 5, 91,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 91, 91, 91,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,368,368,368,
+368, 86,368,368,368, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 63 */
+ 13, 13,369, 13, 13, 13, 13,369, 13, 13,370,369,369,369,370,370,
+369,369,369,370, 13,369, 13, 13, 6,369,369,369,369,369, 13, 13,
+ 13, 13, 13, 13,369, 13,371, 13,369, 13,372,373,369,369, 13,370,
+369,369,374,369,370,340,340,340,340,370, 13, 13,370,370,369,369,
+ 6, 6, 6, 6, 6,369,370,370,370,370, 13, 6, 13, 13,375, 13,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+376,376,376,376,376,376,376,376,376,376,376,376,376,376,376,376,
+377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,
+
+/* block 64 */
+378,378,378, 21, 22,378,378,378,378, 17, 91, 91, 91, 91, 91, 91,
+ 6, 6, 6, 6, 6, 13, 13, 13, 13, 13, 6, 6, 13, 13, 13, 13,
+ 6, 13, 13, 6, 13, 13, 6, 13, 13, 13, 13, 13, 13, 13, 6, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 6, 6,
+ 13, 13, 6, 13, 6, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+/* block 65 */
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+/* block 66 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 6, 6, 6, 6, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 6, 6, 13, 13, 13, 13, 13, 13, 13, 4, 5, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 6, 13, 13, 13,
+
+/* block 67 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 6, 6, 6, 6,
+ 6, 6, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 68 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+
+/* block 69 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13,379,379,379,379,379,379,379,379,379,379,
+379,379,379,379,379,379,379,379,379,379,379,379,379,379,379,379,
+380,380,380,380,380,380,380,380,380,380,380,380,380,380,380,380,
+380,380,380,380,380,380,380,380,380,380, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+
+/* block 70 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+
+/* block 71 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 6, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 6, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 6, 6, 6, 6, 6, 6, 6, 6,
+
+/* block 72 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 6,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+
+/* block 73 */
+ 91, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 4, 5, 4, 5, 4, 5, 4, 5,
+ 4, 5, 4, 5, 4, 5, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+
+/* block 74 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 6, 6, 6, 6, 6, 4, 5, 6, 6, 6, 6, 91, 6, 91, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+/* block 75 */
+381,381,381,381,381,381,381,381,381,381,381,381,381,381,381,381,
+381,381,381,381,381,381,381,381,381,381,381,381,381,381,381,381,
+381,381,381,381,381,381,381,381,381,381,381,381,381,381,381,381,
+381,381,381,381,381,381,381,381,381,381,381,381,381,381,381,381,
+381,381,381,381,381,381,381,381,381,381,381,381,381,381,381,381,
+381,381,381,381,381,381,381,381,381,381,381,381,381,381,381,381,
+381,381,381,381,381,381,381,381,381,381,381,381,381,381,381,381,
+381,381,381,381,381,381,381,381,381,381,381,381,381,381,381,381,
+
+/* block 76 */
+ 6, 6, 6, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4,
+ 5, 4, 5, 4, 5, 4, 5, 4, 5, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 4, 5, 4, 5, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 5, 6, 6,
+
+/* block 77 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 13, 13, 6, 6, 6, 6, 6, 6, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 78 */
+382,382,382,382,382,382,382,382,382,382,382,382,382,382,382,382,
+382,382,382,382,382,382,382,382,382,382,382,382,382,382,382,382,
+382,382,382,382,382,382,382,382,382,382,382,382,382,382,382, 91,
+383,383,383,383,383,383,383,383,383,383,383,383,383,383,383,383,
+383,383,383,383,383,383,383,383,383,383,383,383,383,383,383,383,
+383,383,383,383,383,383,383,383,383,383,383,383,383,383,383, 91,
+ 21, 22,384,385,386,387,388, 21, 22, 21, 22, 21, 22,389,390,391,
+392, 14, 21, 22, 14, 21, 22, 14, 14, 14, 14, 14, 14, 83,393,393,
+
+/* block 79 */
+113,114,113,114,113,114,113,114,113,114,113,114,113,114,113,114,
+113,114,113,114,113,114,113,114,113,114,113,114,113,114,113,114,
+113,114,113,114,113,114,113,114,113,114,113,114,113,114,113,114,
+113,114,113,114,113,114,113,114,113,114,113,114,113,114,113,114,
+113,114,113,114,113,114,113,114,113,114,113,114,113,114,113,114,
+113,114,113,114,113,114,113,114,113,114,113,114,113,114,113,114,
+113,114,113,114,394,395,395,395,395,395,395,113,114,113,114,396,
+396,396, 91, 91, 91, 91, 91, 91, 91,397,397,397,397,398,397,397,
+
+/* block 80 */
+399,399,399,399,399,399,399,399,399,399,399,399,399,399,399,399,
+399,399,399,399,399,399,399,399,399,399,399,399,399,399,399,399,
+399,399,399,399,399,399, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,
+400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,
+400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,400,
+400,400,400,400,400,400, 91, 91, 91, 91, 91, 91, 91, 91, 91,401,
+402, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,403,
+
+/* block 81 */
+254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,
+254,254,254,254,254,254,254, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+254,254,254,254,254,254,254, 91,254,254,254,254,254,254,254, 91,
+254,254,254,254,254,254,254, 91,254,254,254,254,254,254,254, 91,
+254,254,254,254,254,254,254, 91,254,254,254,254,254,254,254, 91,
+254,254,254,254,254,254,254, 91,254,254,254,254,254,254,254, 91,
+130,130,130,130,130,130,130,130,130,130,130,130,130,130,130,130,
+130,130,130,130,130,130,130,130,130,130,130,130,130,130,130,130,
+
+/* block 82 */
+ 2, 2, 15, 19, 15, 19, 2, 2, 2, 15, 19, 2, 15, 19, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 7, 2, 2, 7, 2, 15, 19, 2, 2,
+ 15, 19, 4, 5, 4, 5, 4, 5, 4, 5, 2, 2, 2, 2, 2, 84,
+ 2, 2, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 83 */
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404, 91,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 84 */
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+
+/* block 85 */
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,404,
+404,404,404,404,404,404, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 91, 91, 91, 91,
+
+/* block 86 */
+ 1, 2, 2, 2, 13,405,340,406, 4, 5, 4, 5, 4, 5, 4, 5,
+ 4, 5, 13, 13, 4, 5, 4, 5, 4, 5, 4, 5, 7, 4, 5, 5,
+ 13,406,406,406,406,406,406,406,406,406, 86, 86, 86, 86,407,407,
+ 7, 84, 84, 84, 84, 84, 13, 13,406,406,406,405,340, 2, 13, 13,
+ 91,408,408,408,408,408,408,408,408,408,408,408,408,408,408,408,
+408,408,408,408,408,408,408,408,408,408,408,408,408,408,408,408,
+408,408,408,408,408,408,408,408,408,408,408,408,408,408,408,408,
+408,408,408,408,408,408,408,408,408,408,408,408,408,408,408,408,
+
+/* block 87 */
+408,408,408,408,408,408,408,408,408,408,408,408,408,408,408,408,
+408,408,408,408,408,408,408, 91, 91, 86, 86, 10, 10,409,409,408,
+ 7,410,410,410,410,410,410,410,410,410,410,410,410,410,410,410,
+410,410,410,410,410,410,410,410,410,410,410,410,410,410,410,410,
+410,410,410,410,410,410,410,410,410,410,410,410,410,410,410,410,
+410,410,410,410,410,410,410,410,410,410,410,410,410,410,410,410,
+410,410,410,410,410,410,410,410,410,410,410,410,410,410,410,410,
+410,410,410,410,410,410,410,410,410,410,410, 2, 84,411,411,410,
+
+/* block 88 */
+ 91, 91, 91, 91, 91,412,412,412,412,412,412,412,412,412,412,412,
+412,412,412,412,412,412,412,412,412,412,412,412,412,412,412,412,
+412,412,412,412,412,412,412,412,412,412,412,412,412,412, 91, 91,
+ 91,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+
+/* block 89 */
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, 91,
+ 13, 13, 17, 17, 17, 17, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+412,412,412,412,412,412,412,412,412,412,412,412,412,412,412,412,
+412,412,412,412,412,412,412,412,412,412,412, 91, 91, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+410,410,410,410,410,410,410,410,410,410,410,410,410,410,410,410,
+
+/* block 90 */
+413,413,413,413,413,413,413,413,413,413,413,413,413,413,413,413,
+413,413,413,413,413,413,413,413,413,413,413,413,413,413,413, 91,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+413,413,413,413,413,413,413,413,413,413,413,413,413,413,413,413,
+413,413,413,413,413,413,413,413,413,413,413,413,413,413,413, 13,
+
+/* block 91 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+414,414,414,414,414,414,414,414,414,414,414,414,414,414,414,414,
+414,414,414,414,414,414,414,414,414,414,414,414,414,414,414,414,
+414,414,414,414,414,414,414,414,414,414,414,414,414,414,414, 91,
+
+/* block 92 */
+414,414,414,414,414,414,414,414,414,414,414,414,414,414,414,414,
+414,414,414,414,414,414,414,414,414,414,414,414,414,414,414,414,
+414,414,414,414,414,414,414,414,414,414,414,414,414,414,414,414,
+414,414,414,414,414,414,414,414,414,414,414,414,414,414,414,414,
+414,414,414,414,414,414,414,414,414,414,414,414,414,414,414,414,
+414,414,414,414,414,414,414,414, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+
+/* block 93 */
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+
+/* block 94 */
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+
+/* block 95 */
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 96 */
+416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,
+416,416,416,416,416,417,416,416,416,416,416,416,416,416,416,416,
+416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,
+416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,
+416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,
+416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,
+416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,
+416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,
+
+/* block 97 */
+416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,
+416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,
+416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,
+416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,
+416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,
+416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,
+416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,
+416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,416,
+
+/* block 98 */
+416,416,416,416,416,416,416,416,416,416,416,416,416, 91, 91, 91,
+418,418,418,418,418,418,418,418,418,418,418,418,418,418,418,418,
+418,418,418,418,418,418,418,418,418,418,418,418,418,418,418,418,
+418,418,418,418,418,418,418,418,418,418,418,418,418,418,418,418,
+418,418,418,418,418,418,418, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+419,419,419,419,419,419,419,419,419,419,419,419,419,419,419,419,
+419,419,419,419,419,419,419,419,419,419,419,419,419,419,419,419,
+419,419,419,419,419,419,419,419,420,420,420,420,420,420,421,421,
+
+/* block 99 */
+422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,
+422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,
+422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,
+422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,
+422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,
+422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,
+422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,
+422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,
+
+/* block 100 */
+422,422,422,422,422,422,422,422,422,422,422,422,423,424,424,424,
+422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,422,
+425,425,425,425,425,425,425,425,425,425,422,422, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+127,128,127,128,127,128,127,128,127,128,127,128,127,128,127,128,
+127,128,127,128,127,128,127,128,127,128,127,128,127,128,127,128,
+127,128,127,128,127,128,127,128,127,128,127,128,127,128,426,130,
+131,131,131,427, 91, 91, 91, 91, 91, 91, 91, 91,130,130,427,342,
+
+/* block 101 */
+127,128,127,128,127,128,127,128,127,128,127,128,127,128,127,128,
+127,128,127,128,127,128,127,128, 91, 91, 91, 91, 91, 91, 91, 91,
+428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,
+428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,
+428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,
+428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,
+428,428,428,428,428,428,429,429,429,429,429,429,429,429,429,429,
+430,430,431,431,431,431,431,431, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 102 */
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 84, 84, 84, 84, 84, 84, 84, 84, 84,
+ 10, 10, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 14, 14, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22,
+ 83, 14, 14, 14, 14, 14, 14, 14, 14, 21, 22, 21, 22,432, 21, 22,
+
+/* block 103 */
+ 21, 22, 21, 22, 21, 22, 21, 22, 84, 10, 10, 21, 22,433, 14, 91,
+ 21, 22, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 14, 45, 45, 45, 45, 45,
+
+/* block 104 */
+434,434,435,434,434,434,435,434,434,434,434,435,434,434,434,434,
+434,434,434,434,434,434,434,434,434,434,434,434,434,434,434,434,
+434,434,434,436,436,435,435,436,437,437,437,437, 91, 91, 91, 91,
+ 17, 17, 17, 17, 17, 17, 13, 13, 3, 13, 91, 91, 91, 91, 91, 91,
+438,438,438,438,438,438,438,438,438,438,438,438,438,438,438,438,
+438,438,438,438,438,438,438,438,438,438,438,438,438,438,438,438,
+438,438,438,438,438,438,438,438,438,438,438,438,438,438,438,438,
+438,438,438,438,439,439,439,439, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 105 */
+440,440,441,441,441,441,441,441,441,441,441,441,441,441,441,441,
+441,441,441,441,441,441,441,441,441,441,441,441,441,441,441,441,
+441,441,441,441,441,441,441,441,441,441,441,441,441,441,441,441,
+441,441,441,441,440,440,440,440,440,440,440,440,440,440,440,440,
+440,440,440,440,442, 91, 91, 91, 91, 91, 91, 91, 91, 91,443,443,
+444,444,444,444,444,444,444,444,444,444, 91, 91, 91, 91, 91, 91,
+172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,
+172,172,174,174,174,174,174,174,445,445,445,174, 91, 91, 91, 91,
+
+/* block 106 */
+446,446,446,446,446,446,446,446,446,446,447,447,447,447,447,447,
+447,447,447,447,447,447,447,447,447,447,447,447,447,447,447,447,
+447,447,447,447,447,447,448,448,448,448,448,448,448,448,449,449,
+450,450,450,450,450,450,450,450,450,450,450,450,450,450,450,450,
+450,450,450,450,450,450,450,451,451,451,451,451,451,451,451,451,
+451,451,452,452, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,453,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253,253, 91, 91, 91,
+
+/* block 107 */
+454,454,454,455,456,456,456,456,456,456,456,456,456,456,456,456,
+456,456,456,456,456,456,456,456,456,456,456,456,456,456,456,456,
+456,456,456,456,456,456,456,456,456,456,456,456,456,456,456,456,
+456,456,456,454,455,455,454,454,454,454,455,455,454,455,455,455,
+455,457,457,457,457,457,457,457,457,457,457,457,457,457, 91,458,
+459,459,459,459,459,459,459,459,459,459, 91, 91, 91, 91,457,457,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 108 */
+460,460,460,460,460,460,460,460,460,460,460,460,460,460,460,460,
+460,460,460,460,460,460,460,460,460,460,460,460,460,460,460,460,
+460,460,460,460,460,460,460,460,460,461,461,461,461,461,461,462,
+462,461,461,462,462,461,461, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+460,460,460,461,460,460,460,460,460,460,460,460,461,462, 91, 91,
+463,463,463,463,463,463,463,463,463,463, 91, 91,464,464,464,464,
+244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,
+465,244,244,244,244,244,244,249,249,249,244,245, 91, 91, 91, 91,
+
+/* block 109 */
+466,466,466,466,466,466,466,466,466,466,466,466,466,466,466,466,
+466,466,466,466,466,466,466,466,466,466,466,466,466,466,466,466,
+466,466,466,466,466,466,466,466,466,466,466,466,466,466,466,466,
+467,466,467,467,467,466,466,467,467,466,466,466,466,466,467,467,
+466,467,466, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,466,466,468,469,469,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 110 */
+ 91,254,254,254,254,254,254, 91, 91,254,254,254,254,254,254, 91,
+ 91,254,254,254,254,254,254, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+254,254,254,254,254,254,254, 91,254,254,254,254,254,254,254, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 111 */
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+470,470,470,470,470,470,470,470,470,470,470,470,470,470,470,470,
+470,470,470,470,470,470,470,470,470,470,470,470,470,470,470,470,
+470,470,470,471,471,472,471,471,472,471,471,473,471,472, 91, 91,
+474,474,474,474,474,474,474,474,474,474, 91, 91, 91, 91, 91, 91,
+
+/* block 112 */
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253, 91, 91, 91, 91,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253, 91, 91, 91, 91,
+
+/* block 113 */
+475,475,475,475,475,475,475,475,475,475,475,475,475,475,475,475,
+475,475,475,475,475,475,475,475,475,475,475,475,475,475,475,475,
+475,475,475,475,475,475,475,475,475,475,475,475,475,475,475,475,
+475,475,475,475,475,475,475,475,475,475,475,475,475,475,475,475,
+475,475,475,475,475,475,475,475,475,475,475,475,475,475,475,475,
+475,475,475,475,475,475,475,475,475,475,475,475,475,475,475,475,
+475,475,475,475,475,475,475,475,475,475,475,475,475,475,475,475,
+475,475,475,475,475,475,475,475,475,475,475,475,475,475,475,475,
+
+/* block 114 */
+476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,
+476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,
+476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,
+476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,
+476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,
+476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,
+476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,
+476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,
+
+/* block 115 */
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415, 91, 91,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415, 91, 91,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+
+/* block 116 */
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 117 */
+ 14, 14, 14, 14, 14, 14, 14, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91,138,138,138,138,138, 91, 91, 91, 91, 91,143,140,143,
+143,143,143,143,143,143,143,143,143,477,143,143,143,143,143,143,
+143,143,143,143,143,143,143, 91,143,143,143,143,143, 91,143, 91,
+143,143, 91,143,143, 91,143,143,143,143,143,143,143,143,143,143,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+
+/* block 118 */
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,478,478,478,478,478,478,478,478,478,478,478,478,478,478,
+478,478, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+
+/* block 119 */
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+
+/* block 120 */
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150, 4, 5,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+
+/* block 121 */
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+ 91, 91,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+150,150,150,150,150,150,150,150,150,150,150,150,147, 13, 91, 91,
+
+/* block 122 */
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 2, 2, 2, 2, 2, 2, 2, 4, 5, 2, 91, 91, 91, 91, 91, 91,
+ 86, 86, 86, 86, 86, 86, 86, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 2, 7, 7, 11, 11, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4,
+ 5, 4, 5, 4, 5, 2, 2, 4, 5, 2, 2, 2, 2, 11, 11, 11,
+ 2, 2, 2, 91, 2, 2, 2, 2, 7, 4, 5, 4, 5, 4, 5, 2,
+ 2, 2, 6, 7, 6, 6, 6, 91, 2, 3, 2, 2, 91, 91, 91, 91,
+150,150,150,150,150, 91,150,150,150,150,150,150,150,150,150,150,
+
+/* block 123 */
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,150,
+150,150,150,150,150,150,150,150,150,150,150,150,150, 91, 91, 16,
+
+/* block 124 */
+ 91, 2, 2, 2, 3, 2, 2, 2, 4, 5, 2, 6, 2, 7, 2, 2,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 2, 2, 6, 6, 6, 2,
+ 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 4, 2, 5, 10, 11,
+ 10, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 4, 6, 5, 6, 4,
+ 5, 2, 4, 5, 2, 2,410,410,410,410,410,410,410,410,410,410,
+ 84,410,410,410,410,410,410,410,410,410,410,410,410,410,410,410,
+
+/* block 125 */
+410,410,410,410,410,410,410,410,410,410,410,410,410,410,410,410,
+410,410,410,410,410,410,410,410,410,410,410,410,410,410, 84, 84,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,
+253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, 91,
+ 91, 91,253,253,253,253,253,253, 91, 91,253,253,253,253,253,253,
+ 91, 91,253,253,253,253,253,253, 91, 91,253,253,253, 91, 91, 91,
+ 3, 3, 6, 10, 13, 3, 3, 91, 13, 6, 6, 6, 6, 13, 13, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 16, 16, 16, 13, 13, 91, 91,
+
+/* block 126 */
+479,479,479,479,479,479,479,479,479,479,479,479, 91,479,479,479,
+479,479,479,479,479,479,479,479,479,479,479,479,479,479,479,479,
+479,479,479,479,479,479,479, 91,479,479,479,479,479,479,479,479,
+479,479,479,479,479,479,479,479,479,479,479, 91,479,479, 91,479,
+479,479,479,479,479,479,479,479,479,479,479,479,479,479, 91, 91,
+479,479,479,479,479,479,479,479,479,479,479,479,479,479, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 127 */
+479,479,479,479,479,479,479,479,479,479,479,479,479,479,479,479,
+479,479,479,479,479,479,479,479,479,479,479,479,479,479,479,479,
+479,479,479,479,479,479,479,479,479,479,479,479,479,479,479,479,
+479,479,479,479,479,479,479,479,479,479,479,479,479,479,479,479,
+479,479,479,479,479,479,479,479,479,479,479,479,479,479,479,479,
+479,479,479,479,479,479,479,479,479,479,479,479,479,479,479,479,
+479,479,479,479,479,479,479,479,479,479,479,479,479,479,479,479,
+479,479,479,479,479,479,479,479,479,479,479, 91, 91, 91, 91, 91,
+
+/* block 128 */
+ 2, 2, 13, 91, 91, 91, 91, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 91, 91, 91, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+480,480,480,480,480,480,480,480,480,480,480,480,480,480,480,480,
+480,480,480,480,480,480,480,480,480,480,480,480,480,480,480,480,
+480,480,480,480,480,480,480,480,480,480,480,480,480,480,480,480,
+480,480,480,480,480,481,481,481,481,482,482,482,482,482,482,482,
+
+/* block 129 */
+482,482,482,482,482,482,482,482,482,482,481, 91, 91, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 86, 91, 91,
+
+/* block 130 */
+483,483,483,483,483,483,483,483,483,483,483,483,483,483,483,483,
+483,483,483,483,483,483,483,483,483,483,483,483,483, 91, 91, 91,
+484,484,484,484,484,484,484,484,484,484,484,484,484,484,484,484,
+484,484,484,484,484,484,484,484,484,484,484,484,484,484,484,484,
+484,484,484,484,484,484,484,484,484,484,484,484,484,484,484,484,
+484, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 131 */
+485,485,485,485,485,485,485,485,485,485,485,485,485,485,485,485,
+485,485,485,485,485,485,485,485,485,485,485,485,485,485,485, 91,
+486,486,486,486, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+487,487,487,487,487,487,487,487,487,487,487,487,487,487,487,487,
+487,488,487,487,487,487,487,487,487,487,488, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 132 */
+489,489,489,489,489,489,489,489,489,489,489,489,489,489,489,489,
+489,489,489,489,489,489,489,489,489,489,489,489,489,489, 91,490,
+491,491,491,491,491,491,491,491,491,491,491,491,491,491,491,491,
+491,491,491,491,491,491,491,491,491,491,491,491,491,491,491,491,
+491,491,491,491, 91, 91, 91, 91,491,491,491,491,491,491,491,491,
+492,493,493,493,493,493, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 133 */
+494,494,494,494,494,494,494,494,494,494,494,494,494,494,494,494,
+494,494,494,494,494,494,494,494,494,494,494,494,494,494,494,494,
+494,494,494,494,494,494,494,494,495,495,495,495,495,495,495,495,
+495,495,495,495,495,495,495,495,495,495,495,495,495,495,495,495,
+495,495,495,495,495,495,495,495,495,495,495,495,495,495,495,495,
+496,496,496,496,496,496,496,496,496,496,496,496,496,496,496,496,
+496,496,496,496,496,496,496,496,496,496,496,496,496,496,496,496,
+496,496,496,496,496,496,496,496,496,496,496,496,496,496,496,496,
+
+/* block 134 */
+497,497,497,497,497,497,497,497,497,497,497,497,497,497,497,497,
+497,497,497,497,497,497,497,497,497,497,497,497,497,497, 91, 91,
+498,498,498,498,498,498,498,498,498,498, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 135 */
+499,499,499,499,499,499, 91, 91,499, 91,499,499,499,499,499,499,
+499,499,499,499,499,499,499,499,499,499,499,499,499,499,499,499,
+499,499,499,499,499,499,499,499,499,499,499,499,499,499,499,499,
+499,499,499,499,499,499, 91,499,499, 91, 91, 91,499, 91, 91,499,
+500,500,500,500,500,500,500,500,500,500,500,500,500,500,500,500,
+500,500,500,500,500,500, 91,501,502,502,502,502,502,502,502,502,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 136 */
+503,503,503,503,503,503,503,503,503,503,503,503,503,503,503,503,
+503,503,503,503,503,503,504,504,504,504,504,504, 91, 91, 91,505,
+506,506,506,506,506,506,506,506,506,506,506,506,506,506,506,506,
+506,506,506,506,506,506,506,506,506,506, 91, 91, 91, 91, 91,507,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 137 */
+508,509,509,509, 91,509,509, 91, 91, 91, 91, 91,509,509,509,509,
+508,508,508,508, 91,508,508,508, 91,508,508,508,508,508,508,508,
+508,508,508,508,508,508,508,508,508,508,508,508,508,508,508,508,
+508,508,508,508, 91, 91, 91, 91,509,509,509, 91, 91, 91, 91,509,
+510,510,510,510,510,510,510,510, 91, 91, 91, 91, 91, 91, 91, 91,
+511,511,511,511,511,511,511,511,511, 91, 91, 91, 91, 91, 91, 91,
+512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,
+512,512,512,512,512,512,512,512,512,512,512,512,512,513,513,514,
+
+/* block 138 */
+515,515,515,515,515,515,515,515,515,515,515,515,515,515,515,515,
+515,515,515,515,515,515,515,515,515,515,515,515,515,515,515,515,
+515,515,515,515,515,515,515,515,515,515,515,515,515,515,515,515,
+515,515,515,515,515,515, 91, 91, 91,516,516,516,516,516,516,516,
+517,517,517,517,517,517,517,517,517,517,517,517,517,517,517,517,
+517,517,517,517,517,517, 91, 91,518,518,518,518,518,518,518,518,
+519,519,519,519,519,519,519,519,519,519,519,519,519,519,519,519,
+519,519,519, 91, 91, 91, 91, 91,520,520,520,520,520,520,520,520,
+
+/* block 139 */
+521,521,521,521,521,521,521,521,521,521,521,521,521,521,521,521,
+521,521,521,521,521,521,521,521,521,521,521,521,521,521,521,521,
+521,521,521,521,521,521,521,521,521,521,521,521,521,521,521,521,
+521,521,521,521,521,521,521,521,521,521,521,521,521,521,521,521,
+521,521,521,521,521,521,521,521,521, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 140 */
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+522,522,522,522,522,522,522,522,522,522,522,522,522,522,522,522,
+522,522,522,522,522,522,522,522,522,522,522,522,522,522,522, 91,
+
+/* block 141 */
+523,524,523,525,525,525,525,525,525,525,525,525,525,525,525,525,
+525,525,525,525,525,525,525,525,525,525,525,525,525,525,525,525,
+525,525,525,525,525,525,525,525,525,525,525,525,525,525,525,525,
+525,525,525,525,525,525,525,525,524,524,524,524,524,524,524,524,
+524,524,524,524,524,524,524,526,526,526,526,526,526,526, 91, 91,
+ 91, 91,527,527,527,527,527,527,527,527,527,527,527,527,527,527,
+527,527,527,527,527,527,528,528,528,528,528,528,528,528,528,528,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 142 */
+529,529,530,531,531,531,531,531,531,531,531,531,531,531,531,531,
+531,531,531,531,531,531,531,531,531,531,531,531,531,531,531,531,
+531,531,531,531,531,531,531,531,531,531,531,531,531,531,531,531,
+530,530,530,529,529,529,529,530,530,529,529,532,532,533,532,532,
+532,532, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 143 */
+534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,
+534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,
+534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,
+534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,
+534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,
+534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,
+534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,
+534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,
+
+/* block 144 */
+534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,
+534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,
+534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,
+534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,
+534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,
+534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,534,
+534,534,534,534,534,534,534,534,534,534,534,534,534,534,534, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 145 */
+535,535,535,535,535,535,535,535,535,535,535,535,535,535,535,535,
+535,535,535,535,535,535,535,535,535,535,535,535,535,535,535,535,
+535,535,535,535,535,535,535,535,535,535,535,535,535,535,535,535,
+535,535,535,535,535,535,535,535,535,535,535,535,535,535,535,535,
+535,535,535,535,535,535,535,535,535,535,535,535,535,535,535,535,
+535,535,535,535,535,535,535,535,535,535,535,535,535,535,535,535,
+535,535,535, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+536,536,536,536, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 146 */
+537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,
+537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,
+537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,
+537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,
+537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,
+537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,
+537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,
+537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,
+
+/* block 147 */
+537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,
+537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,537,
+537,537,537,537,537,537,537,537,537,537,537,537,537,537,537, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 148 */
+428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,
+428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,
+428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,
+428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,
+428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,
+428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,
+428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,
+428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,
+
+/* block 149 */
+428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,
+428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,
+428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,428,
+428,428,428,428,428,428,428,428,428, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 150 */
+410,408, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 151 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 152 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 91, 91, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13,339,339, 86, 86, 86, 13, 13, 13,339,339,339,
+339,339,339, 16, 16, 16, 16, 16, 16, 16, 16, 86, 86, 86, 86, 86,
+
+/* block 153 */
+ 86, 86, 86, 13, 13, 86, 86, 86, 86, 86, 86, 86, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 86, 86, 86, 86, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 154 */
+482,482,482,482,482,482,482,482,482,482,482,482,482,482,482,482,
+482,482,482,482,482,482,482,482,482,482,482,482,482,482,482,482,
+482,482,482,482,482,482,482,482,482,482,482,482,482,482,482,482,
+482,482,482,482,482,482,482,482,482,482,482,482,482,482,482,482,
+482,482,538,538,538,482, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 155 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 156 */
+369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,
+369,369,369,369,369,369,369,369,369,369,370,370,370,370,370,370,
+370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,
+370,370,370,370,369,369,369,369,369,369,369,369,369,369,369,369,
+369,369,369,369,369,369,369,369,369,369,369,369,369,369,370,370,
+370,370,370,370,370, 91,370,370,370,370,370,370,370,370,370,370,
+370,370,370,370,370,370,370,370,369,369,369,369,369,369,369,369,
+369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,
+
+/* block 157 */
+369,369,370,370,370,370,370,370,370,370,370,370,370,370,370,370,
+370,370,370,370,370,370,370,370,370,370,370,370,369, 91,369,369,
+ 91, 91,369, 91, 91,369,369, 91, 91,369,369,369,369, 91,369,369,
+369,369,369,369,369,369,370,370,370,370, 91,370, 91,370,370,370,
+370,370,370,370, 91,370,370,370,370,370,370,370,370,370,370,370,
+369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,
+369,369,369,369,369,369,369,369,369,369,370,370,370,370,370,370,
+370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,
+
+/* block 158 */
+370,370,370,370,369,369, 91,369,369,369,369, 91, 91,369,369,369,
+369,369,369,369,369, 91,369,369,369,369,369,369,369, 91,370,370,
+370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,
+370,370,370,370,370,370,370,370,369,369, 91,369,369,369,369, 91,
+369,369,369,369,369, 91,369, 91, 91, 91,369,369,369,369,369,369,
+369, 91,370,370,370,370,370,370,370,370,370,370,370,370,370,370,
+370,370,370,370,370,370,370,370,370,370,370,370,369,369,369,369,
+369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,
+
+/* block 159 */
+369,369,369,369,369,369,370,370,370,370,370,370,370,370,370,370,
+370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,
+369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,
+369,369,369,369,369,369,369,369,369,369,370,370,370,370,370,370,
+370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,
+370,370,370,370,369,369,369,369,369,369,369,369,369,369,369,369,
+369,369,369,369,369,369,369,369,369,369,369,369,369,369,370,370,
+370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,
+
+/* block 160 */
+370,370,370,370,370,370,370,370,369,369,369,369,369,369,369,369,
+369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,
+369,369,370,370,370,370,370,370,370,370,370,370,370,370,370,370,
+370,370,370,370,370,370,370,370,370,370,370,370,369,369,369,369,
+369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,
+369,369,369,369,369,369,370,370,370,370,370,370,370,370,370,370,
+370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,
+369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,
+
+/* block 161 */
+369,369,369,369,369,369,369,369,369,369,370,370,370,370,370,370,
+370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,
+370,370,370,370,370,370, 91, 91,369,369,369,369,369,369,369,369,
+369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,
+369, 6,370,370,370,370,370,370,370,370,370,370,370,370,370,370,
+370,370,370,370,370,370,370,370,370,370,370, 6,370,370,370,370,
+370,370,369,369,369,369,369,369,369,369,369,369,369,369,369,369,
+369,369,369,369,369,369,369,369,369,369,369, 6,370,370,370,370,
+
+/* block 162 */
+370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,
+370,370,370,370,370, 6,370,370,370,370,370,370,369,369,369,369,
+369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,
+369,369,369,369,369, 6,370,370,370,370,370,370,370,370,370,370,
+370,370,370,370,370,370,370,370,370,370,370,370,370,370,370, 6,
+370,370,370,370,370,370,369,369,369,369,369,369,369,369,369,369,
+369,369,369,369,369,369,369,369,369,369,369,369,369,369,369, 6,
+370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,
+
+/* block 163 */
+370,370,370,370,370,370,370,370,370, 6,370,370,370,370,370,370,
+369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,369,
+369,369,369,369,369,369,369,369,369, 6,370,370,370,370,370,370,
+370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,370,
+370,370,370, 6,370,370,370,370,370,370,369,370, 91, 91, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+
+/* block 164 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 91, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+
+/* block 165 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 91,
+ 91, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 91,
+ 91, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 91, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 166 */
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 91, 91, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 91, 91, 91, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+
+/* block 167 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+
+/* block 168 */
+539, 13, 13, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 91, 91, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 91, 91, 91, 91, 91, 91, 91,
+ 13, 13, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 169 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 91, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 91, 91, 91,
+
+/* block 170 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 91, 13, 13, 13, 13, 13, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 171 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 91,
+ 13, 91, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+
+/* block 172 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 91, 13, 13, 13, 13, 91, 91, 91,
+
+/* block 173 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 174 */
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 13, 13, 13, 13, 13,
+
+/* block 175 */
+ 91, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 91, 13, 13, 13, 91, 13, 91, 13, 91, 13, 91, 13, 13, 13, 91,
+ 13, 13, 13, 13, 13, 13, 91, 91, 13, 13, 13, 13, 91, 13, 91, 91,
+ 13, 13, 13, 13, 91, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 91, 91, 91, 91, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 176 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 177 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 178 */
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 179 */
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+
+/* block 180 */
+415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,415,
+415,415,415,415,415,415,415,415,415,415,415,415,415,415, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 181 */
+ 91, 16, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+
+/* block 182 */
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+
+/* block 183 */
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+
+/* block 184 */
+476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,
+476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,
+476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,
+476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,
+476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,
+476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,
+476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,476,
+476,476,476,476,476,476,476,476,476,476,476,476,476,476, 91, 91,
+
+};
+
+#if UCD_BLOCK_SIZE != 128
+#error Please correct UCD_BLOCK_SIZE in pcre_internal.h
+#endif
+#endif /* SUPPORT_UCP */
diff --git a/usr.sbin/nginx/src/pcre/pcre_valid_utf8.c b/usr.sbin/nginx/src/pcre/pcre_valid_utf8.c
new file mode 100644
index 00000000000..fef65383738
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/pcre_valid_utf8.c
@@ -0,0 +1,299 @@
+/*************************************************
+* Perl-Compatible Regular Expressions *
+*************************************************/
+
+/* PCRE is a library of functions to support regular expressions whose syntax
+and semantics are as close as possible to those of the Perl 5 language.
+
+ Written by Philip Hazel
+ Copyright (c) 1997-2009 University of Cambridge
+
+-----------------------------------------------------------------------------
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the University of Cambridge nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+-----------------------------------------------------------------------------
+*/
+
+
+/* This module contains an internal function for validating UTF-8 character
+strings. */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pcre_internal.h"
+
+
+/*************************************************
+* Validate a UTF-8 string *
+*************************************************/
+
+/* This function is called (optionally) at the start of compile or match, to
+check that a supposed UTF-8 string is actually valid. The early check means
+that subsequent code can assume it is dealing with a valid string. The check
+can be turned off for maximum performance, but the consequences of supplying an
+invalid string are then undefined.
+
+Originally, this function checked according to RFC 2279, allowing for values in
+the range 0 to 0x7fffffff, up to 6 bytes long, but ensuring that they were in
+the canonical format. Once somebody had pointed out RFC 3629 to me (it
+obsoletes 2279), additional restrictions were applied. The values are now
+limited to be between 0 and 0x0010ffff, no more than 4 bytes long, and the
+subrange 0xd000 to 0xdfff is excluded. However, the format of 5-byte and 6-byte
+characters is still checked.
+
+From release 8.13 more information about the details of the error are passed
+back in the returned value:
+
+PCRE_UTF8_ERR0 No error
+PCRE_UTF8_ERR1 Missing 1 byte at the end of the string
+PCRE_UTF8_ERR2 Missing 2 bytes at the end of the string
+PCRE_UTF8_ERR3 Missing 3 bytes at the end of the string
+PCRE_UTF8_ERR4 Missing 4 bytes at the end of the string
+PCRE_UTF8_ERR5 Missing 5 bytes at the end of the string
+PCRE_UTF8_ERR6 2nd-byte's two top bits are not 0x80
+PCRE_UTF8_ERR7 3rd-byte's two top bits are not 0x80
+PCRE_UTF8_ERR8 4th-byte's two top bits are not 0x80
+PCRE_UTF8_ERR9 5th-byte's two top bits are not 0x80
+PCRE_UTF8_ERR10 6th-byte's two top bits are not 0x80
+PCRE_UTF8_ERR11 5-byte character is not permitted by RFC 3629
+PCRE_UTF8_ERR12 6-byte character is not permitted by RFC 3629
+PCRE_UTF8_ERR13 4-byte character with value > 0x10ffff is not permitted
+PCRE_UTF8_ERR14 3-byte character with value 0xd000-0xdfff is not permitted
+PCRE_UTF8_ERR15 Overlong 2-byte sequence
+PCRE_UTF8_ERR16 Overlong 3-byte sequence
+PCRE_UTF8_ERR17 Overlong 4-byte sequence
+PCRE_UTF8_ERR18 Overlong 5-byte sequence (won't ever occur)
+PCRE_UTF8_ERR19 Overlong 6-byte sequence (won't ever occur)
+PCRE_UTF8_ERR20 Isolated 0x80 byte (not within UTF-8 character)
+PCRE_UTF8_ERR21 Byte with the illegal value 0xfe or 0xff
+
+Arguments:
+ string points to the string
+ length length of string, or -1 if the string is zero-terminated
+ errp pointer to an error position offset variable
+
+Returns: = 0 if the string is a valid UTF-8 string
+ > 0 otherwise, setting the offset of the bad character
+*/
+
+int
+_pcre_valid_utf8(USPTR string, int length, int *erroroffset)
+{
+#ifdef SUPPORT_UTF8
+register USPTR p;
+
+if (length < 0)
+ {
+ for (p = string; *p != 0; p++);
+ length = p - string;
+ }
+
+for (p = string; length-- > 0; p++)
+ {
+ register int ab, c, d;
+
+ c = *p;
+ if (c < 128) continue; /* ASCII character */
+
+ if (c < 0xc0) /* Isolated 10xx xxxx byte */
+ {
+ *erroroffset = p - string;
+ return PCRE_UTF8_ERR20;
+ }
+
+ if (c >= 0xfe) /* Invalid 0xfe or 0xff bytes */
+ {
+ *erroroffset = p - string;
+ return PCRE_UTF8_ERR21;
+ }
+
+ ab = _pcre_utf8_table4[c & 0x3f]; /* Number of additional bytes */
+ if (length < ab)
+ {
+ *erroroffset = p - string; /* Missing bytes */
+ return ab - length; /* Codes ERR1 to ERR5 */
+ }
+ length -= ab; /* Length remaining */
+
+ /* Check top bits in the second byte */
+
+ if (((d = *(++p)) & 0xc0) != 0x80)
+ {
+ *erroroffset = p - string - 1;
+ return PCRE_UTF8_ERR6;
+ }
+
+ /* For each length, check that the remaining bytes start with the 0x80 bit
+ set and not the 0x40 bit. Then check for an overlong sequence, and for the
+ excluded range 0xd800 to 0xdfff. */
+
+ switch (ab)
+ {
+ /* 2-byte character. No further bytes to check for 0x80. Check first byte
+ for for xx00 000x (overlong sequence). */
+
+ case 1: if ((c & 0x3e) == 0)
+ {
+ *erroroffset = p - string - 1;
+ return PCRE_UTF8_ERR15;
+ }
+ break;
+
+ /* 3-byte character. Check third byte for 0x80. Then check first 2 bytes
+ for 1110 0000, xx0x xxxx (overlong sequence) or
+ 1110 1101, 1010 xxxx (0xd800 - 0xdfff) */
+
+ case 2:
+ if ((*(++p) & 0xc0) != 0x80) /* Third byte */
+ {
+ *erroroffset = p - string - 2;
+ return PCRE_UTF8_ERR7;
+ }
+ if (c == 0xe0 && (d & 0x20) == 0)
+ {
+ *erroroffset = p - string - 2;
+ return PCRE_UTF8_ERR16;
+ }
+ if (c == 0xed && d >= 0xa0)
+ {
+ *erroroffset = p - string - 2;
+ return PCRE_UTF8_ERR14;
+ }
+ break;
+
+ /* 4-byte character. Check 3rd and 4th bytes for 0x80. Then check first 2
+ bytes for for 1111 0000, xx00 xxxx (overlong sequence), then check for a
+ character greater than 0x0010ffff (f4 8f bf bf) */
+
+ case 3:
+ if ((*(++p) & 0xc0) != 0x80) /* Third byte */
+ {
+ *erroroffset = p - string - 2;
+ return PCRE_UTF8_ERR7;
+ }
+ if ((*(++p) & 0xc0) != 0x80) /* Fourth byte */
+ {
+ *erroroffset = p - string - 3;
+ return PCRE_UTF8_ERR8;
+ }
+ if (c == 0xf0 && (d & 0x30) == 0)
+ {
+ *erroroffset = p - string - 3;
+ return PCRE_UTF8_ERR17;
+ }
+ if (c > 0xf4 || (c == 0xf4 && d > 0x8f))
+ {
+ *erroroffset = p - string - 3;
+ return PCRE_UTF8_ERR13;
+ }
+ break;
+
+ /* 5-byte and 6-byte characters are not allowed by RFC 3629, and will be
+ rejected by the length test below. However, we do the appropriate tests
+ here so that overlong sequences get diagnosed, and also in case there is
+ ever an option for handling these larger code points. */
+
+ /* 5-byte character. Check 3rd, 4th, and 5th bytes for 0x80. Then check for
+ 1111 1000, xx00 0xxx */
+
+ case 4:
+ if ((*(++p) & 0xc0) != 0x80) /* Third byte */
+ {
+ *erroroffset = p - string - 2;
+ return PCRE_UTF8_ERR7;
+ }
+ if ((*(++p) & 0xc0) != 0x80) /* Fourth byte */
+ {
+ *erroroffset = p - string - 3;
+ return PCRE_UTF8_ERR8;
+ }
+ if ((*(++p) & 0xc0) != 0x80) /* Fifth byte */
+ {
+ *erroroffset = p - string - 4;
+ return PCRE_UTF8_ERR9;
+ }
+ if (c == 0xf8 && (d & 0x38) == 0)
+ {
+ *erroroffset = p - string - 4;
+ return PCRE_UTF8_ERR18;
+ }
+ break;
+
+ /* 6-byte character. Check 3rd-6th bytes for 0x80. Then check for
+ 1111 1100, xx00 00xx. */
+
+ case 5:
+ if ((*(++p) & 0xc0) != 0x80) /* Third byte */
+ {
+ *erroroffset = p - string - 2;
+ return PCRE_UTF8_ERR7;
+ }
+ if ((*(++p) & 0xc0) != 0x80) /* Fourth byte */
+ {
+ *erroroffset = p - string - 3;
+ return PCRE_UTF8_ERR8;
+ }
+ if ((*(++p) & 0xc0) != 0x80) /* Fifth byte */
+ {
+ *erroroffset = p - string - 4;
+ return PCRE_UTF8_ERR9;
+ }
+ if ((*(++p) & 0xc0) != 0x80) /* Sixth byte */
+ {
+ *erroroffset = p - string - 5;
+ return PCRE_UTF8_ERR10;
+ }
+ if (c == 0xfc && (d & 0x3c) == 0)
+ {
+ *erroroffset = p - string - 5;
+ return PCRE_UTF8_ERR19;
+ }
+ break;
+ }
+
+ /* Character is valid under RFC 2279, but 4-byte and 5-byte characters are
+ excluded by RFC 3629. The pointer p is currently at the last byte of the
+ character. */
+
+ if (ab > 3)
+ {
+ *erroroffset = p - string - ab;
+ return (ab == 4)? PCRE_UTF8_ERR11 : PCRE_UTF8_ERR12;
+ }
+ }
+
+#else /* SUPPORT_UTF8 */
+(void)(string); /* Keep picky compilers happy */
+(void)(length);
+#endif
+
+return PCRE_UTF8_ERR0; /* This indicates success */
+}
+
+/* End of pcre_valid_utf8.c */
diff --git a/usr.sbin/nginx/src/pcre/pcre_xclass.c b/usr.sbin/nginx/src/pcre/pcre_xclass.c
new file mode 100644
index 00000000000..64c1b0043fa
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/pcre_xclass.c
@@ -0,0 +1,174 @@
+/*************************************************
+* Perl-Compatible Regular Expressions *
+*************************************************/
+
+/* PCRE is a library of functions to support regular expressions whose syntax
+and semantics are as close as possible to those of the Perl 5 language.
+
+ Written by Philip Hazel
+ Copyright (c) 1997-2010 University of Cambridge
+
+-----------------------------------------------------------------------------
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the University of Cambridge nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+-----------------------------------------------------------------------------
+*/
+
+
+/* This module contains an internal function that is used to match an extended
+class. It is used by both pcre_exec() and pcre_def_exec(). */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pcre_internal.h"
+
+
+/*************************************************
+* Match character against an XCLASS *
+*************************************************/
+
+/* This function is called to match a character against an extended class that
+might contain values > 255 and/or Unicode properties.
+
+Arguments:
+ c the character
+ data points to the flag byte of the XCLASS data
+
+Returns: TRUE if character matches, else FALSE
+*/
+
+BOOL
+_pcre_xclass(int c, const uschar *data)
+{
+int t;
+BOOL negated = (*data & XCL_NOT) != 0;
+
+/* Character values < 256 are matched against a bitmap, if one is present. If
+not, we still carry on, because there may be ranges that start below 256 in the
+additional data. */
+
+if (c < 256)
+ {
+ if ((*data & XCL_MAP) != 0 && (data[1 + c/8] & (1 << (c&7))) != 0)
+ return !negated; /* char found */
+ }
+
+/* First skip the bit map if present. Then match against the list of Unicode
+properties or large chars or ranges that end with a large char. We won't ever
+encounter XCL_PROP or XCL_NOTPROP when UCP support is not compiled. */
+
+if ((*data++ & XCL_MAP) != 0) data += 32;
+
+while ((t = *data++) != XCL_END)
+ {
+ int x, y;
+ if (t == XCL_SINGLE)
+ {
+ GETCHARINC(x, data);
+ if (c == x) return !negated;
+ }
+ else if (t == XCL_RANGE)
+ {
+ GETCHARINC(x, data);
+ GETCHARINC(y, data);
+ if (c >= x && c <= y) return !negated;
+ }
+
+#ifdef SUPPORT_UCP
+ else /* XCL_PROP & XCL_NOTPROP */
+ {
+ const ucd_record *prop = GET_UCD(c);
+
+ switch(*data)
+ {
+ case PT_ANY:
+ if (t == XCL_PROP) return !negated;
+ break;
+
+ case PT_LAMP:
+ if ((prop->chartype == ucp_Lu || prop->chartype == ucp_Ll ||
+ prop->chartype == ucp_Lt) == (t == XCL_PROP)) return !negated;
+ break;
+
+ case PT_GC:
+ if ((data[1] == _pcre_ucp_gentype[prop->chartype]) == (t == XCL_PROP))
+ return !negated;
+ break;
+
+ case PT_PC:
+ if ((data[1] == prop->chartype) == (t == XCL_PROP)) return !negated;
+ break;
+
+ case PT_SC:
+ if ((data[1] == prop->script) == (t == XCL_PROP)) return !negated;
+ break;
+
+ case PT_ALNUM:
+ if ((_pcre_ucp_gentype[prop->chartype] == ucp_L ||
+ _pcre_ucp_gentype[prop->chartype] == ucp_N) == (t == XCL_PROP))
+ return !negated;
+ break;
+
+ case PT_SPACE: /* Perl space */
+ if ((_pcre_ucp_gentype[prop->chartype] == ucp_Z ||
+ c == CHAR_HT || c == CHAR_NL || c == CHAR_FF || c == CHAR_CR)
+ == (t == XCL_PROP))
+ return !negated;
+ break;
+
+ case PT_PXSPACE: /* POSIX space */
+ if ((_pcre_ucp_gentype[prop->chartype] == ucp_Z ||
+ c == CHAR_HT || c == CHAR_NL || c == CHAR_VT ||
+ c == CHAR_FF || c == CHAR_CR) == (t == XCL_PROP))
+ return !negated;
+ break;
+
+ case PT_WORD:
+ if ((_pcre_ucp_gentype[prop->chartype] == ucp_L ||
+ _pcre_ucp_gentype[prop->chartype] == ucp_N || c == CHAR_UNDERSCORE)
+ == (t == XCL_PROP))
+ return !negated;
+ break;
+
+ /* This should never occur, but compilers may mutter if there is no
+ default. */
+
+ default:
+ return FALSE;
+ }
+
+ data += 2;
+ }
+#endif /* SUPPORT_UCP */
+ }
+
+return negated; /* char did not match */
+}
+
+/* End of pcre_xclass.c */
diff --git a/usr.sbin/nginx/src/pcre/ucp.h b/usr.sbin/nginx/src/pcre/ucp.h
new file mode 100644
index 00000000000..34077fe07e6
--- /dev/null
+++ b/usr.sbin/nginx/src/pcre/ucp.h
@@ -0,0 +1,165 @@
+/*************************************************
+* Unicode Property Table handler *
+*************************************************/
+
+#ifndef _UCP_H
+#define _UCP_H
+
+/* This file contains definitions of the property values that are returned by
+the UCD access macros. New values that are added for new releases of Unicode
+should always be at the end of each enum, for backwards compatibility. */
+
+/* These are the general character categories. */
+
+enum {
+ ucp_C, /* Other */
+ ucp_L, /* Letter */
+ ucp_M, /* Mark */
+ ucp_N, /* Number */
+ ucp_P, /* Punctuation */
+ ucp_S, /* Symbol */
+ ucp_Z /* Separator */
+};
+
+/* These are the particular character types. */
+
+enum {
+ ucp_Cc, /* Control */
+ ucp_Cf, /* Format */
+ ucp_Cn, /* Unassigned */
+ ucp_Co, /* Private use */
+ ucp_Cs, /* Surrogate */
+ ucp_Ll, /* Lower case letter */
+ ucp_Lm, /* Modifier letter */
+ ucp_Lo, /* Other letter */
+ ucp_Lt, /* Title case letter */
+ ucp_Lu, /* Upper case letter */
+ ucp_Mc, /* Spacing mark */
+ ucp_Me, /* Enclosing mark */
+ ucp_Mn, /* Non-spacing mark */
+ ucp_Nd, /* Decimal number */
+ ucp_Nl, /* Letter number */
+ ucp_No, /* Other number */
+ ucp_Pc, /* Connector punctuation */
+ ucp_Pd, /* Dash punctuation */
+ ucp_Pe, /* Close punctuation */
+ ucp_Pf, /* Final punctuation */
+ ucp_Pi, /* Initial punctuation */
+ ucp_Po, /* Other punctuation */
+ ucp_Ps, /* Open punctuation */
+ ucp_Sc, /* Currency symbol */
+ ucp_Sk, /* Modifier symbol */
+ ucp_Sm, /* Mathematical symbol */
+ ucp_So, /* Other symbol */
+ ucp_Zl, /* Line separator */
+ ucp_Zp, /* Paragraph separator */
+ ucp_Zs /* Space separator */
+};
+
+/* These are the script identifications. */
+
+enum {
+ ucp_Arabic,
+ ucp_Armenian,
+ ucp_Bengali,
+ ucp_Bopomofo,
+ ucp_Braille,
+ ucp_Buginese,
+ ucp_Buhid,
+ ucp_Canadian_Aboriginal,
+ ucp_Cherokee,
+ ucp_Common,
+ ucp_Coptic,
+ ucp_Cypriot,
+ ucp_Cyrillic,
+ ucp_Deseret,
+ ucp_Devanagari,
+ ucp_Ethiopic,
+ ucp_Georgian,
+ ucp_Glagolitic,
+ ucp_Gothic,
+ ucp_Greek,
+ ucp_Gujarati,
+ ucp_Gurmukhi,
+ ucp_Han,
+ ucp_Hangul,
+ ucp_Hanunoo,
+ ucp_Hebrew,
+ ucp_Hiragana,
+ ucp_Inherited,
+ ucp_Kannada,
+ ucp_Katakana,
+ ucp_Kharoshthi,
+ ucp_Khmer,
+ ucp_Lao,
+ ucp_Latin,
+ ucp_Limbu,
+ ucp_Linear_B,
+ ucp_Malayalam,
+ ucp_Mongolian,
+ ucp_Myanmar,
+ ucp_New_Tai_Lue,
+ ucp_Ogham,
+ ucp_Old_Italic,
+ ucp_Old_Persian,
+ ucp_Oriya,
+ ucp_Osmanya,
+ ucp_Runic,
+ ucp_Shavian,
+ ucp_Sinhala,
+ ucp_Syloti_Nagri,
+ ucp_Syriac,
+ ucp_Tagalog,
+ ucp_Tagbanwa,
+ ucp_Tai_Le,
+ ucp_Tamil,
+ ucp_Telugu,
+ ucp_Thaana,
+ ucp_Thai,
+ ucp_Tibetan,
+ ucp_Tifinagh,
+ ucp_Ugaritic,
+ ucp_Yi,
+ /* New for Unicode 5.0: */
+ ucp_Balinese,
+ ucp_Cuneiform,
+ ucp_Nko,
+ ucp_Phags_Pa,
+ ucp_Phoenician,
+ /* New for Unicode 5.1: */
+ ucp_Carian,
+ ucp_Cham,
+ ucp_Kayah_Li,
+ ucp_Lepcha,
+ ucp_Lycian,
+ ucp_Lydian,
+ ucp_Ol_Chiki,
+ ucp_Rejang,
+ ucp_Saurashtra,
+ ucp_Sundanese,
+ ucp_Vai,
+ /* New for Unicode 5.2: */
+ ucp_Avestan,
+ ucp_Bamum,
+ ucp_Egyptian_Hieroglyphs,
+ ucp_Imperial_Aramaic,
+ ucp_Inscriptional_Pahlavi,
+ ucp_Inscriptional_Parthian,
+ ucp_Javanese,
+ ucp_Kaithi,
+ ucp_Lisu,
+ ucp_Meetei_Mayek,
+ ucp_Old_South_Arabian,
+ ucp_Old_Turkic,
+ ucp_Samaritan,
+ ucp_Tai_Tham,
+ ucp_Tai_Viet,
+ /* New for Unicode 6.0.0: */
+ ucp_Batak,
+ ucp_Brahmi,
+ ucp_Mandaic
+};
+
+#endif
+
+/* End of ucp.h */