diff options
Diffstat (limited to 'usr.sbin/nginx/src')
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(">") - 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, "..></a>", sizeof("..></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("") - 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, ©.path, &root, 0); + + r->uri = uri; + + copy.path.len--; /* omit "\0" */ + + if (copy.path.data[copy.path.len - 1] == '/') { + slash = 1; + copy.path.len--; + copy.path.data[copy.path.len] = '\0'; + + } else { + slash = 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http copy to: \"%s\"", copy.path.data); + + if (ngx_link_info(copy.path.data, &fi) == NGX_FILE_ERROR) { + err = ngx_errno; + + if (err != NGX_ENOENT) { + return ngx_http_dav_error(r->connection->log, err, + NGX_HTTP_NOT_FOUND, ngx_link_info_n, + copy.path.data); + } + + /* destination does not exist */ + + overwrite = 0; + dir = 0; + + } else { + + /* destination exists */ + + if (ngx_is_dir(&fi) && !slash) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "\"%V\" could not be %Ved to collection \"%V\"", + &r->uri, &r->method_name, &dest->value); + return NGX_HTTP_CONFLICT; + } + + if (!overwrite) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_EEXIST, + "\"%s\" could not be created", copy.path.data); + return NGX_HTTP_PRECONDITION_FAILED; + } + + dir = ngx_is_dir(&fi); + } + + if (ngx_link_info(path.data, &fi) == NGX_FILE_ERROR) { + return ngx_http_dav_error(r->connection->log, ngx_errno, + NGX_HTTP_NOT_FOUND, ngx_link_info_n, + path.data); + } + + if (ngx_is_dir(&fi)) { + + if (r->uri.data[r->uri.len - 1] != '/') { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "\"%V\" is collection", &r->uri); + return NGX_HTTP_BAD_REQUEST; + } + + if (overwrite) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http delete: \"%s\"", copy.path.data); + + rc = ngx_http_dav_delete_path(r, ©.path, dir); + + if (rc != NGX_OK) { + return rc; + } + } + } + + if (ngx_is_dir(&fi)) { + + path.len -= 2; /* omit "/\0" */ + + if (r->method == NGX_HTTP_MOVE) { + if (ngx_rename_file(path.data, copy.path.data) != NGX_FILE_ERROR) { + return NGX_HTTP_CREATED; + } + } + + if (ngx_create_dir(copy.path.data, ngx_file_access(&fi)) + == NGX_FILE_ERROR) + { + return ngx_http_dav_error(r->connection->log, ngx_errno, + NGX_HTTP_NOT_FOUND, + ngx_create_dir_n, copy.path.data); + } + + copy.len = path.len; + + tree.init_handler = NULL; + tree.file_handler = ngx_http_dav_copy_tree_file; + tree.pre_tree_handler = ngx_http_dav_copy_dir; + tree.post_tree_handler = ngx_http_dav_copy_dir_time; + tree.spec_handler = ngx_http_dav_noop; + tree.data = © + tree.alloc = 0; + tree.log = r->connection->log; + + if (ngx_walk_tree(&tree, &path) == NGX_OK) { + + if (r->method == NGX_HTTP_MOVE) { + rc = ngx_http_dav_delete_path(r, &path, 1); + + if (rc != NGX_OK) { + return rc; + } + } + + return NGX_HTTP_CREATED; + } + + } else { + + if (r->method == NGX_HTTP_MOVE) { + + dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module); + + ext.access = 0; + ext.path_access = dlcf->access; + ext.time = -1; + ext.create_path = 1; + ext.delete_file = 0; + ext.log = r->connection->log; + + if (ngx_ext_rename_file(&path, ©.path, &ext) == NGX_OK) { + return NGX_HTTP_NO_CONTENT; + } + + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module); + + cf.size = ngx_file_size(&fi); + cf.buf_size = 0; + cf.access = dlcf->access; + cf.time = ngx_file_mtime(&fi); + cf.log = r->connection->log; + + if (ngx_copy_file(path.data, copy.path.data, &cf) == NGX_OK) { + return NGX_HTTP_NO_CONTENT; + } + } + + return NGX_HTTP_INTERNAL_SERVER_ERROR; +} + + +static ngx_int_t +ngx_http_dav_copy_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + u_char *p, *dir; + size_t len; + ngx_http_dav_copy_ctx_t *copy; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy dir: \"%s\"", path->data); + + copy = ctx->data; + + len = copy->path.len + path->len; + + dir = ngx_alloc(len + 1, ctx->log); + if (dir == NULL) { + return NGX_ABORT; + } + + p = ngx_cpymem(dir, copy->path.data, copy->path.len); + (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy dir to: \"%s\"", dir); + + if (ngx_create_dir(dir, ngx_dir_access(ctx->access)) == NGX_FILE_ERROR) { + (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_create_dir_n, + dir); + } + + ngx_free(dir); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_dav_copy_dir_time(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + u_char *p, *dir; + size_t len; + ngx_http_dav_copy_ctx_t *copy; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy dir time: \"%s\"", path->data); + + copy = ctx->data; + + len = copy->path.len + path->len; + + dir = ngx_alloc(len + 1, ctx->log); + if (dir == NULL) { + return NGX_ABORT; + } + + p = ngx_cpymem(dir, copy->path.data, copy->path.len); + (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy dir time to: \"%s\"", dir); + +#if (NGX_WIN32) + { + ngx_fd_t fd; + + fd = ngx_open_file(dir, NGX_FILE_RDWR, NGX_FILE_OPEN, 0); + + if (fd == NGX_INVALID_FILE) { + (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_open_file_n, dir); + goto failed; + } + + if (ngx_set_file_time(NULL, fd, ctx->mtime) != NGX_OK) { + ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno, + ngx_set_file_time_n " \"%s\" failed", dir); + } + + if (ngx_close_file(fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno, + ngx_close_file_n " \"%s\" failed", dir); + } + } + +failed: + +#else + + if (ngx_set_file_time(dir, 0, ctx->mtime) != NGX_OK) { + ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno, + ngx_set_file_time_n " \"%s\" failed", dir); + } + +#endif + + ngx_free(dir); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_dav_copy_tree_file(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + u_char *p, *file; + size_t len; + ngx_copy_file_t cf; + ngx_http_dav_copy_ctx_t *copy; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy file: \"%s\"", path->data); + + copy = ctx->data; + + len = copy->path.len + path->len; + + file = ngx_alloc(len + 1, ctx->log); + if (file == NULL) { + return NGX_ABORT; + } + + p = ngx_cpymem(file, copy->path.data, copy->path.len); + (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy file to: \"%s\"", file); + + cf.size = ctx->size; + cf.buf_size = 0; + cf.access = ctx->access; + cf.time = ctx->mtime; + cf.log = ctx->log; + + (void) ngx_copy_file(path->data, file, &cf); + + ngx_free(file); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_dav_depth(ngx_http_request_t *r, ngx_int_t dflt) +{ + ngx_table_elt_t *depth; + + depth = r->headers_in.depth; + + if (depth == NULL) { + return dflt; + } + + if (depth->value.len == 1) { + + if (depth->value.data[0] == '0') { + return 0; + } + + if (depth->value.data[0] == '1') { + return 1; + } + + } else { + + if (depth->value.len == sizeof("infinity") - 1 + && ngx_strcmp(depth->value.data, "infinity") == 0) + { + return NGX_HTTP_DAV_INFINITY_DEPTH; + } + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent invalid \"Depth\" header: \"%V\"", + &depth->value); + + return NGX_HTTP_DAV_INVALID_DEPTH; +} + + +static ngx_int_t +ngx_http_dav_error(ngx_log_t *log, ngx_err_t err, ngx_int_t not_found, + char *failed, u_char *path) +{ + ngx_int_t rc; + ngx_uint_t level; + + if (err == NGX_ENOENT || err == NGX_ENOTDIR || err == NGX_ENAMETOOLONG) { + level = NGX_LOG_ERR; + rc = not_found; + + } else if (err == NGX_EACCES || err == NGX_EPERM) { + level = NGX_LOG_ERR; + rc = NGX_HTTP_FORBIDDEN; + + } else if (err == NGX_EEXIST) { + level = NGX_LOG_ERR; + rc = NGX_HTTP_NOT_ALLOWED; + + } else if (err == NGX_ENOSPC) { + level = NGX_LOG_CRIT; + rc = NGX_HTTP_INSUFFICIENT_STORAGE; + + } else { + level = NGX_LOG_CRIT; + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_log_error(level, log, err, "%s \"%s\" failed", failed, path); + + return rc; +} + + +static ngx_int_t +ngx_http_dav_location(ngx_http_request_t *r, u_char *path) +{ + u_char *location; + ngx_http_core_loc_conf_t *clcf; + + r->headers_out.location = ngx_palloc(r->pool, sizeof(ngx_table_elt_t)); + if (r->headers_out.location == NULL) { + return NGX_ERROR; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (!clcf->alias && clcf->root_lengths == NULL) { + location = path + clcf->root.len; + + } else { + location = ngx_pnalloc(r->pool, r->uri.len); + if (location == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(location, r->uri.data, r->uri.len); + } + + /* + * we do not need to set the r->headers_out.location->hash and + * r->headers_out.location->key fields + */ + + r->headers_out.location->value.len = r->uri.len; + r->headers_out.location->value.data = location; + + return NGX_OK; +} + + +static void * +ngx_http_dav_create_loc_conf(ngx_conf_t *cf) +{ + ngx_http_dav_loc_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_dav_loc_conf_t)); + if (conf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * conf->methods = 0; + */ + + conf->min_delete_depth = NGX_CONF_UNSET_UINT; + conf->access = NGX_CONF_UNSET_UINT; + conf->create_full_put_path = NGX_CONF_UNSET; + + return conf; +} + + +static char * +ngx_http_dav_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_dav_loc_conf_t *prev = parent; + ngx_http_dav_loc_conf_t *conf = child; + + ngx_conf_merge_bitmask_value(conf->methods, prev->methods, + (NGX_CONF_BITMASK_SET|NGX_HTTP_DAV_OFF)); + + ngx_conf_merge_uint_value(conf->min_delete_depth, + prev->min_delete_depth, 0); + + ngx_conf_merge_uint_value(conf->access, prev->access, 0600); + + ngx_conf_merge_value(conf->create_full_put_path, + prev->create_full_put_path, 0); + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_http_dav_init(ngx_conf_t *cf) +{ + ngx_http_handler_pt *h; + ngx_http_core_main_conf_t *cmcf; + + cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); + + h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_http_dav_handler; + + return NGX_OK; +} 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 = ®ex->lengths; + sc.values = &lcf->codes; + sc.variables = ngx_http_script_variables_count(&value[2]); + sc.main = regex; + sc.complete_lengths = 1; + sc.compile_args = !regex->redirect; + + if (ngx_http_script_compile(&sc) != NGX_OK) { + return NGX_CONF_ERROR; + } + + regex = sc.main; + + regex->size = sc.size; + regex->args = sc.args; + + if (sc.variables == 0 && !sc.dup_capture) { + regex->lengths = NULL; + } + + regex_end = ngx_http_script_add_code(lcf->codes, + sizeof(ngx_http_script_regex_end_code_t), + ®ex); + if (regex_end == NULL) { + return NGX_CONF_ERROR; + } + + regex_end->code = ngx_http_script_regex_end_code; + regex_end->uri = regex->uri; + regex_end->args = regex->args; + regex_end->add_args = regex->add_args; + regex_end->redirect = regex->redirect; + + if (last) { + code = ngx_http_script_add_code(lcf->codes, sizeof(uintptr_t), ®ex); + if (code == NULL) { + return NGX_CONF_ERROR; + } + + *code = NULL; + } + + regex->next = (u_char *) lcf->codes->elts + lcf->codes->nelts + - (u_char *) regex; + + return NGX_CONF_OK; +} + + +static char * +ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_rewrite_loc_conf_t *lcf = conf; + + u_char *p; + ngx_str_t *value, *v; + ngx_http_script_return_code_t *ret; + ngx_http_compile_complex_value_t ccv; + + ret = ngx_http_script_start_code(cf->pool, &lcf->codes, + sizeof(ngx_http_script_return_code_t)); + if (ret == NULL) { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + ngx_memzero(ret, sizeof(ngx_http_script_return_code_t)); + + ret->code = ngx_http_script_return_code; + + p = value[1].data; + + ret->status = ngx_atoi(p, value[1].len); + + if (ret->status == (uintptr_t) NGX_ERROR) { + + if (cf->args->nelts == 2 + && (ngx_strncmp(p, "http://", sizeof("http://") - 1) == 0 + || ngx_strncmp(p, "https://", sizeof("https://") - 1) == 0 + || ngx_strncmp(p, "$scheme", sizeof("$scheme") - 1) == 0)) + { + ret->status = NGX_HTTP_MOVED_TEMPORARILY; + v = &value[1]; + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid return code \"%V\"", &value[1]); + return NGX_CONF_ERROR; + } + + } else { + + if (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", + ¶m[i].key, &ctx->command); + + goto ssi_error; + } + + params[prm->index] = ¶m[i].value; + + break; + } + + for (index = prm->index; params[index]; index++) { + /* void */ + } + + params[index] = ¶m[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", + ¶m[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, ¶m[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 = ¶ms[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 */ |
