summaryrefslogtreecommitdiff
path: root/src/http
diff options
context:
space:
mode:
authorIgor Sysoev <igor@sysoev.ru>2004-10-05 15:39:18 +0000
committerJonathan Kolb <jon@b0g.us>2004-10-05 15:39:18 +0000
commit7d66b1f69bb81e48c7efb79884771ea66b3685ca (patch)
treee0cf023d6eebb4e422e62c5403e74e8218dc3192 /src/http
downloadnginx-7d66b1f69bb81e48c7efb79884771ea66b3685ca.tar.gz
The first public versionv0.1.0
Diffstat (limited to 'src/http')
-rw-r--r--src/http/modules/ngx_http_access_handler.c213
-rw-r--r--src/http/modules/ngx_http_charset_filter.c553
-rw-r--r--src/http/modules/ngx_http_chunked_filter.c156
-rw-r--r--src/http/modules/ngx_http_gzip_filter.c1036
-rw-r--r--src/http/modules/ngx_http_headers_filter.c239
-rw-r--r--src/http/modules/ngx_http_index_handler.c529
-rw-r--r--src/http/modules/ngx_http_not_modified_filter.c85
-rw-r--r--src/http/modules/ngx_http_range_filter.c523
-rw-r--r--src/http/modules/ngx_http_rewrite_handler.c466
-rw-r--r--src/http/modules/ngx_http_ssl_module.c160
-rw-r--r--src/http/modules/ngx_http_ssl_module.h36
-rw-r--r--src/http/modules/ngx_http_static_handler.c584
-rw-r--r--src/http/modules/ngx_http_userid_filter.c588
-rw-r--r--src/http/modules/proxy/ngx_http_proxy_cache.c629
-rw-r--r--src/http/modules/proxy/ngx_http_proxy_handler.c1280
-rw-r--r--src/http/modules/proxy/ngx_http_proxy_handler.h260
-rw-r--r--src/http/modules/proxy/ngx_http_proxy_header.c198
-rw-r--r--src/http/modules/proxy/ngx_http_proxy_parse.c213
-rw-r--r--src/http/modules/proxy/ngx_http_proxy_upstream.c1492
-rw-r--r--src/http/ngx_http.c642
-rw-r--r--src/http/ngx_http.h112
-rw-r--r--src/http/ngx_http_busy_lock.c300
-rw-r--r--src/http/ngx_http_busy_lock.h53
-rw-r--r--src/http/ngx_http_cache.c480
-rw-r--r--src/http/ngx_http_cache.h131
-rw-r--r--src/http/ngx_http_config.h70
-rw-r--r--src/http/ngx_http_copy_filter.c132
-rw-r--r--src/http/ngx_http_core_module.c1819
-rw-r--r--src/http/ngx_http_core_module.h214
-rw-r--r--src/http/ngx_http_file_cache.c239
-rw-r--r--src/http/ngx_http_header_filter.c459
-rw-r--r--src/http/ngx_http_log_handler.c978
-rw-r--r--src/http/ngx_http_log_handler.h65
-rw-r--r--src/http/ngx_http_parse.c868
-rw-r--r--src/http/ngx_http_parse_time.c287
-rw-r--r--src/http/ngx_http_request.c2149
-rw-r--r--src/http/ngx_http_request.h372
-rw-r--r--src/http/ngx_http_request_body.c227
-rw-r--r--src/http/ngx_http_special_response.c358
-rw-r--r--src/http/ngx_http_write_filter.c166
40 files changed, 19361 insertions, 0 deletions
diff --git a/src/http/modules/ngx_http_access_handler.c b/src/http/modules/ngx_http_access_handler.c
new file mode 100644
index 000000000..3a323b149
--- /dev/null
+++ b/src/http/modules/ngx_http_access_handler.c
@@ -0,0 +1,213 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+/* AF_INET only */
+
+typedef struct {
+ in_addr_t mask;
+ in_addr_t addr;
+ unsigned deny;
+} ngx_http_access_rule_t;
+
+
+typedef struct {
+ ngx_array_t *rules; /* array of ngx_http_access_rule_t */
+} ngx_http_access_loc_conf_t;
+
+
+static ngx_int_t ngx_http_access_handler(ngx_http_request_t *r);
+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_cycle_t *cycle);
+
+
+static ngx_command_t ngx_http_access_commands[] = {
+
+ { ngx_string("allow"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_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_CONF_TAKE1,
+ ngx_http_access_rule,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+
+ngx_http_module_t ngx_http_access_module_ctx = {
+ NULL, /* pre conf */
+
+ 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,
+ &ngx_http_access_module_ctx, /* module context */
+ ngx_http_access_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ ngx_http_access_init, /* init module */
+ NULL /* init process */
+};
+
+
+static ngx_int_t ngx_http_access_handler(ngx_http_request_t *r)
+{
+ ngx_uint_t i;
+ struct sockaddr_in *addr_in;
+ ngx_http_access_rule_t *rule;
+ ngx_http_access_loc_conf_t *alcf;
+
+ alcf = ngx_http_get_module_loc_conf(r, ngx_http_access_module);
+
+ if (alcf->rules == NULL) {
+ return NGX_OK;
+ }
+
+ /* AF_INET only */
+
+ addr_in = (struct sockaddr_in *) r->connection->sockaddr;
+
+ rule = alcf->rules->elts;
+ for (i = 0; i < alcf->rules->nelts; i++) {
+
+ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "%08X %08X %08X",
+ addr_in->sin_addr.s_addr, rule[i].mask, rule[i].addr);
+
+ if ((addr_in->sin_addr.s_addr & rule[i].mask) == rule[i].addr) {
+ if (rule[i].deny) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "access forbidden by rule");
+
+ return NGX_HTTP_FORBIDDEN;
+ }
+
+ return NGX_OK;
+ }
+ }
+
+ 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_str_t *value;
+ ngx_inet_cidr_t in_cidr;
+ ngx_http_access_rule_t *rule;
+
+ if (alcf->rules == NULL) {
+ alcf->rules = ngx_create_array(cf->pool, 5,
+ sizeof(ngx_http_access_rule_t));
+ if (alcf->rules == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (!(rule = ngx_push_array(alcf->rules))) {
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ rule->deny = (value[0].data[0] == 'd') ? 1 : 0;
+
+ if (value[1].len == 3 && ngx_strcmp(value[1].data, "all") == 0) {
+ rule->mask = 0;
+ rule->addr = 0;
+
+ return NGX_CONF_OK;
+ }
+
+ rule->addr = inet_addr((char *) value[1].data);
+
+ if (rule->addr != INADDR_NONE) {
+ rule->mask = 0xffffffff;
+
+ return NGX_CONF_OK;
+ }
+
+ if (ngx_ptocidr(&value[1], &in_cidr) == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid paramter \"%s\"",
+ value[1].data);
+ return NGX_CONF_ERROR;
+ }
+
+ rule->mask = in_cidr.mask;
+ rule->addr = in_cidr.addr;
+
+ return NGX_CONF_OK;
+}
+
+
+static void *ngx_http_access_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_access_loc_conf_t *conf;
+
+ if (!(conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_access_loc_conf_t)))) {
+ return NGX_CONF_ERROR;
+ }
+
+ 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;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t ngx_http_access_init(ngx_cycle_t *cycle)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_core_module);
+
+ h = ngx_push_array(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_access_handler;
+
+ return NGX_OK;
+}
diff --git a/src/http/modules/ngx_http_charset_filter.c b/src/http/modules/ngx_http_charset_filter.c
new file mode 100644
index 000000000..d22a86b5b
--- /dev/null
+++ b/src/http/modules/ngx_http_charset_filter.c
@@ -0,0 +1,553 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ char **tables;
+ ngx_str_t name;
+ unsigned server;
+} ngx_http_charset_t;
+
+
+typedef struct {
+ ngx_int_t src;
+ ngx_int_t dst;
+ char *src2dst;
+ 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_http_charset_main_conf_t;
+
+
+typedef struct {
+ ngx_flag_t enable;
+ ngx_flag_t autodetect;
+
+ ngx_int_t default_charset;
+ ngx_int_t source_charset;
+} ngx_http_charset_loc_conf_t;
+
+
+typedef struct {
+ ngx_int_t server;
+ ngx_int_t client;
+} ngx_http_charset_ctx_t;
+
+
+static void ngx_charset_recode(ngx_buf_t *b, char *table);
+
+static char *ngx_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_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 ngx_int_t ngx_http_charset_filter_init(ngx_cycle_t *cycle);
+
+static void *ngx_http_charset_create_main_conf(ngx_conf_t *cf);
+static char *ngx_http_charset_init_main_conf(ngx_conf_t *cf, void *conf);
+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_command_t ngx_http_charset_filter_commands[] = {
+
+ { ngx_string("charset_map"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2,
+ ngx_charset_map_block,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("default_charset"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_set_charset_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_charset_loc_conf_t, default_charset),
+ NULL },
+
+ { ngx_string("source_charset"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_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("charset"),
+ 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_charset_loc_conf_t, enable),
+ NULL },
+
+ { ngx_string("autodetect_charset"),
+ 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_charset_loc_conf_t, autodetect),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_charset_filter_module_ctx = {
+ NULL, /* pre conf */
+
+ ngx_http_charset_create_main_conf, /* create main configuration */
+ ngx_http_charset_init_main_conf, /* 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,
+ &ngx_http_charset_filter_module_ctx, /* module context */
+ ngx_http_charset_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ ngx_http_charset_filter_init, /* init module */
+ NULL /* init child */
+};
+
+
+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_http_charset_t *charsets;
+ ngx_http_charset_ctx_t *ctx;
+ ngx_http_charset_loc_conf_t *lcf;
+ ngx_http_charset_main_conf_t *mcf;
+
+ mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module);
+ lcf = ngx_http_get_module_loc_conf(r, ngx_http_charset_filter_module);
+
+ if (lcf->enable == 0) {
+ return ngx_http_next_header_filter(r);
+ }
+
+#if 0
+ if (lcf->default_charset.len == 0) {
+ return ngx_http_next_header_filter(r);
+ }
+#endif
+
+ if (r->headers_out.content_type == NULL) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ if (ngx_strncasecmp(r->headers_out.content_type->value.data,
+ "text/", 5) != 0
+ && ngx_strncasecmp(r->headers_out.content_type->value.data,
+ "application/x-javascript", 24) != 0)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ if (ngx_strstr(r->headers_out.content_type->value.data, "charset") != NULL)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ 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 uses this
+ * charset instead of the next page charset
+ */
+
+ r->headers_out.charset.len = 0;
+ return ngx_http_next_header_filter(r);
+ }
+
+ if (r->headers_out.charset.len) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ charsets = mcf->charsets.elts;
+ r->headers_out.charset = charsets[lcf->default_charset].name;
+
+ if (lcf->default_charset == lcf->source_charset) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ngx_http_create_ctx(r, ctx, ngx_http_charset_filter_module,
+ sizeof(ngx_http_charset_ctx_t), NGX_ERROR);
+
+ r->filter_need_in_memory = 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)
+{
+ char *table;
+ ngx_chain_t *cl;
+ ngx_http_charset_t *charsets;
+ ngx_http_charset_ctx_t *ctx;
+ ngx_http_charset_loc_conf_t *lcf;
+ ngx_http_charset_main_conf_t *mcf;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_charset_filter_module);
+
+ if (ctx == NULL) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module);
+ lcf = ngx_http_get_module_loc_conf(r, ngx_http_charset_filter_module);
+
+ charsets = mcf->charsets.elts;
+ table = charsets[lcf->source_charset].tables[lcf->default_charset];
+
+ for (cl = in; cl; cl = cl->next) {
+ ngx_charset_recode(cl->buf, table);
+ }
+
+ return ngx_http_next_body_filter(r, in);
+}
+
+
+static void ngx_charset_recode(ngx_buf_t *b, char *table)
+{
+ u_char *p, c;
+
+ for (p = b->pos; p < b->last; p++) {
+ c = *p;
+ *p = table[c];
+ }
+}
+
+
+static char *ngx_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf)
+{
+ ngx_http_charset_main_conf_t *mcf = conf;
+
+ char *rv;
+ ngx_int_t src, dst;
+ ngx_uint_t i;
+ ngx_str_t *value;
+ ngx_conf_t pvcf;
+ ngx_http_charset_tables_t *table;
+
+ 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 "
+ "\"%s\" and \"%s\"",
+ value[1].data, value[2].data);
+ 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 "
+ "\"%s\" and \"%s\"",
+ value[1].data, value[2].data);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (!(table = ngx_push_array(&mcf->tables))) {
+ return NGX_CONF_ERROR;
+ }
+
+ table->src = src;
+ table->dst = dst;
+
+ if (!(table->src2dst = ngx_palloc(cf->pool, 256))) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (!(table->dst2src = ngx_palloc(cf->pool, 256))) {
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 0; i < 128; i++) {
+ table->src2dst[i] = (char) i;
+ table->dst2src[i] = (char) i;
+ }
+
+ for (/* void */; i < 256; i++) {
+ table->src2dst[i] = '?';
+ table->dst2src[i] = '?';
+ }
+
+ pvcf = *cf;
+ cf->ctx = table;
+ cf->handler = ngx_charset_map;
+ cf->handler_conf = conf;
+ rv = ngx_conf_parse(cf, NULL);
+ *cf = pvcf;
+
+ return rv;
+}
+
+
+static char *ngx_charset_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+ ngx_int_t src, dst;
+ ngx_str_t *value;
+ ngx_http_charset_tables_t *table;
+
+ 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 \"%s\"", value[0].data);
+ return NGX_CONF_ERROR;
+ }
+
+ 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 \"%s\"", value[1].data);
+ return NGX_CONF_ERROR;
+ }
+
+ table = cf->ctx;
+
+ table->src2dst[src] = (char) dst;
+ table->dst2src[dst] = (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;
+ ngx_http_charset_t *charset;
+ ngx_http_charset_main_conf_t *mcf;
+
+ cp = (ngx_int_t *) (p + cmd->offset);
+
+ if (*cp != NGX_CONF_UNSET) {
+ return "is duplicate";
+ }
+
+ mcf = ngx_http_conf_get_module_main_conf(cf,
+ ngx_http_charset_filter_module);
+
+ value = cf->args->elts;
+
+ *cp = ngx_http_add_charset(&mcf->charsets, &value[1]);
+ if (*cp == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cmd->offset == offsetof(ngx_http_charset_loc_conf_t, source_charset)) {
+ charset = mcf->charsets.elts;
+ charset[*cp].server = 1;
+ }
+
+ 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;
+ }
+
+ if (!(c = ngx_push_array(charsets))) {
+ return NGX_ERROR;
+ }
+
+ c->name = *name;
+
+ return i;
+}
+
+
+static ngx_int_t ngx_http_charset_filter_init(ngx_cycle_t *cycle)
+{
+ 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;
+}
+
+
+static void *ngx_http_charset_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_charset_main_conf_t *mcf;
+
+ if (!(mcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_charset_main_conf_t)))) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_init_array(mcf->charsets, cf->pool, 5, sizeof(ngx_http_charset_t),
+ NGX_CONF_ERROR);
+
+ ngx_init_array(mcf->tables, cf->pool, 10, sizeof(ngx_http_charset_tables_t),
+ NGX_CONF_ERROR);
+
+ return mcf;
+}
+
+
+static char *ngx_http_charset_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+ ngx_http_charset_main_conf_t *mcf = conf;
+
+ ngx_uint_t i, n;
+ ngx_http_charset_t *charset;
+ ngx_http_charset_tables_t *tables;
+
+ tables = mcf->tables.elts;
+ charset = mcf->charsets.elts;
+
+ for (i = 0; i < mcf->charsets.nelts; i++) {
+ if (!charset[i].server) {
+ continue;
+ }
+
+ charset[i].tables = ngx_pcalloc(cf->pool,
+ sizeof(char *) * mcf->charsets.nelts);
+
+ if (charset[i].tables == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ for (n = 0; n < mcf->tables.nelts; n++) {
+ if ((ngx_int_t) i == tables[n].src) {
+ charset[i].tables[tables[n].dst] = tables[n].src2dst;
+ continue;
+ }
+
+ if ((ngx_int_t) i == tables[n].dst) {
+ charset[i].tables[tables[n].src] = tables[n].dst2src;
+ }
+ }
+ }
+
+ for (i = 0; i < mcf->charsets.nelts; i++) {
+ if (!charset[i].server) {
+ continue;
+ }
+
+ for (n = 0; n < mcf->charsets.nelts; n++) {
+ if (i == n) {
+ continue;
+ }
+
+ if (charset[i].tables[n]) {
+ continue;
+ }
+
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ " no \"charset_map\" between the charsets "
+ "\"%s\" and \"%s\"",
+ charset[i].name.data, charset[n].name.data);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static void *ngx_http_charset_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_charset_loc_conf_t *lcf;
+
+ if (!(lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_charset_loc_conf_t)))) {
+ return NGX_CONF_ERROR;
+ }
+
+ lcf->enable = NGX_CONF_UNSET;
+ lcf->autodetect = NGX_CONF_UNSET;
+ lcf->default_charset = NGX_CONF_UNSET;
+ lcf->source_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_conf_merge_value(conf->enable, prev->enable, 0);
+ ngx_conf_merge_value(conf->autodetect, prev->autodetect, 0);
+
+ if (conf->source_charset == NGX_CONF_UNSET) {
+ conf->source_charset = prev->source_charset;
+ }
+
+ ngx_conf_merge_value(conf->default_charset, prev->default_charset,
+ conf->source_charset);
+
+ return NGX_CONF_OK;
+}
diff --git a/src/http/modules/ngx_http_chunked_filter.c b/src/http/modules/ngx_http_chunked_filter.c
new file mode 100644
index 000000000..a71839a29
--- /dev/null
+++ b/src/http/modules/ngx_http_chunked_filter.c
@@ -0,0 +1,156 @@
+
+/*
+ * 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_cycle_t *cycle);
+
+
+static ngx_http_module_t ngx_http_chunked_filter_module_ctx = {
+ NULL, /* pre conf */
+
+ 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,
+ &ngx_http_chunked_filter_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ ngx_http_chunked_filter_init, /* init module */
+ NULL /* init child */
+};
+
+
+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)
+{
+ if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
+ 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 {
+ r->chunked = 1;
+ }
+ }
+
+ 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;
+ size_t size, len;
+ ngx_buf_t *b;
+ ngx_chain_t out, tail, *cl, *tl, **ll;
+
+ if (in == NULL || !r->chunked) {
+ 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);
+
+ ngx_test_null(tl, ngx_alloc_chain_link(r->pool), NGX_ERROR);
+ tl->buf = cl->buf;
+ *ll = tl;
+ ll = &tl->next;
+
+ if (cl->next == NULL) {
+ break;
+ }
+
+ cl = cl->next;
+ }
+
+ if (size) {
+ ngx_test_null(chunk, ngx_palloc(r->pool, 11), NGX_ERROR);
+ len = ngx_snprintf((char *) chunk, 11, SIZE_T_X_FMT CRLF, size);
+
+ ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR);
+ b->temporary = 1;
+ b->pos = chunk;
+ b->last = chunk + len;
+
+ out.buf = b;
+ }
+
+ if (cl->buf->last_buf) {
+ ngx_test_null(b, ngx_calloc_buf(r->pool), 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);
+ }
+
+ ngx_test_null(b, ngx_calloc_buf(r->pool), 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_cycle_t *cycle)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_chunked_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_chunked_body_filter;
+
+ return NGX_OK;
+}
diff --git a/src/http/modules/ngx_http_gzip_filter.c b/src/http/modules/ngx_http_gzip_filter.c
new file mode 100644
index 000000000..72ccb0a0f
--- /dev/null
+++ b/src/http/modules/ngx_http_gzip_filter.c
@@ -0,0 +1,1036 @@
+
+/*
+ * 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_bufs_t bufs;
+
+ ngx_uint_t http_version;
+ ngx_uint_t proxied;
+
+ int level;
+ size_t wbits;
+ size_t memlevel;
+ ssize_t min_length;
+} ngx_http_gzip_conf_t;
+
+
+#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
+
+
+typedef struct {
+ ngx_chain_t *in;
+ ngx_chain_t *free;
+ ngx_chain_t *busy;
+ ngx_chain_t *out;
+ ngx_chain_t **last_out;
+ ngx_buf_t *in_buf;
+ ngx_buf_t *out_buf;
+ ngx_int_t bufs;
+
+ off_t length;
+
+ void *preallocated;
+ char *free_mem;
+ ngx_uint_t allocated;
+
+ unsigned flush:4;
+ unsigned redo:1;
+ unsigned done:1;
+#if 0
+ unsigned pass:1;
+ unsigned blocked:1;
+#endif
+
+ size_t zin;
+ size_t zout;
+
+ uint32_t crc32;
+ z_stream zstream;
+ ngx_http_request_t *request;
+} ngx_http_gzip_ctx_t;
+
+
+static ngx_int_t ngx_http_gzip_proxied(ngx_http_request_t *r,
+ ngx_http_gzip_conf_t *conf);
+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);
+ngx_inline static int ngx_http_gzip_error(ngx_http_gzip_ctx_t *ctx);
+
+static u_char *ngx_http_gzip_log_ratio(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+
+static ngx_int_t ngx_http_gzip_pre_conf(ngx_conf_t *cf);
+static ngx_int_t ngx_http_gzip_filter_init(ngx_cycle_t *cycle);
+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_set_window(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_gzip_set_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_set_window_p =
+ ngx_http_gzip_set_window;
+static ngx_conf_post_handler_pt ngx_http_gzip_set_hash_p =
+ ngx_http_gzip_set_hash;
+
+
+
+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_command_t ngx_http_gzip_filter_commands[] = {
+
+ { ngx_string("gzip"),
+ 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, 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_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_set_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_set_hash_p },
+
+ { 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_http_version"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_ANY,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_gzip_conf_t, 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_ANY,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_gzip_conf_t, proxied),
+ &ngx_http_gzip_proxied_mask },
+
+ { 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_pre_conf, /* pre conf */
+
+ 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,
+ &ngx_http_gzip_filter_module_ctx, /* module context */
+ ngx_http_gzip_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ ngx_http_gzip_filter_init, /* init module */
+ NULL /* init child */
+};
+
+
+static ngx_http_log_op_name_t ngx_http_gzip_log_fmt_ops[] = {
+ { ngx_string("gzip_ratio"), NGX_INT32_LEN + 3, ngx_http_gzip_log_ratio },
+ { ngx_null_string, 0, NULL }
+};
+
+
+
+static u_char gzheader[10] = { 0x1f, 0x8b, Z_DEFLATED, 0, 0, 0, 0, 0, 0, 3 };
+
+#if (HAVE_LITTLE_ENDIAN)
+
+struct gztrailer {
+ uint32_t crc32;
+ uint32_t zlen;
+};
+
+#else /* HAVE_BIG_ENDIAN */
+
+struct gztrailer {
+ u_char crc32[4];
+ u_char zlen[4];
+};
+
+#endif
+
+
+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_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->header_only
+ || r->http_version < conf->http_version
+ || (r->headers_out.content_encoding
+ && r->headers_out.content_encoding->value.len)
+ || r->headers_in.accept_encoding == NULL
+ || (r->headers_out.content_length_n != -1
+ && r->headers_out.content_length_n < conf->min_length)
+ || ngx_strstr(r->headers_in.accept_encoding->value.data, "gzip") == NULL
+ )
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ /* TODO: "text/html" -> custom types */
+ if (r->headers_out.content_type
+ && ngx_strncasecmp(r->headers_out.content_type->value.data,
+ "text/html", 9) != 0)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+
+ if (r->headers_in.via) {
+ if (conf->proxied & NGX_HTTP_GZIP_PROXIED_OFF) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ if (!(conf->proxied & NGX_HTTP_GZIP_PROXIED_ANY)
+ && ngx_http_gzip_proxied(r, conf) == NGX_DECLINED)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+ }
+
+
+ /*
+ * if the URL (without the "http://" prefix) is longer than 253 bytes
+ * then MSIE 4.x can not handle the compressed stream - it waits too long,
+ * hangs up or crashes
+ */
+
+ if (r->headers_in.msie4 && r->unparsed_uri.len > 200) {
+ return ngx_http_next_header_filter(r);
+ }
+
+
+ ngx_http_create_ctx(r, ctx, ngx_http_gzip_filter_module,
+ sizeof(ngx_http_gzip_ctx_t), NGX_ERROR);
+ ctx->request = r;
+
+ r->headers_out.content_encoding = ngx_list_push(&r->headers_out.headers);
+ if (r->headers_out.content_encoding == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->headers_out.content_encoding->key.len = sizeof("Content-Encoding") - 1;
+ r->headers_out.content_encoding->key.data = (u_char *) "Content-Encoding";
+ r->headers_out.content_encoding->value.len = sizeof("gzip") - 1;
+ r->headers_out.content_encoding->value.data = (u_char *) "gzip";
+
+ ctx->length = r->headers_out.content_length_n;
+ r->headers_out.content_length_n = -1;
+ if (r->headers_out.content_length) {
+ r->headers_out.content_length->key.len = 0;
+ r->headers_out.content_length = NULL;
+ }
+ r->filter_need_in_memory = 1;
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t ngx_http_gzip_proxied(ngx_http_request_t *r,
+ ngx_http_gzip_conf_t *conf)
+{
+ time_t date, expires;
+
+ if (r->headers_in.authorization
+ && (conf->proxied & NGX_HTTP_GZIP_PROXIED_AUTH))
+ {
+ return NGX_OK;
+ }
+
+ if (r->headers_out.expires) {
+
+ if (!(conf->proxied & NGX_HTTP_GZIP_PROXIED_EXPIRED)) {
+ return NGX_DECLINED;
+ }
+
+ expires = ngx_http_parse_time(r->headers_out.expires->value.data,
+ r->headers_out.expires->value.len);
+ if (expires == NGX_ERROR) {
+ return NGX_DECLINED;
+ }
+
+ if (r->headers_out.date) {
+ date = ngx_http_parse_time(r->headers_out.date->value.data,
+ r->headers_out.date->value.len);
+ if (date == NGX_ERROR) {
+ return NGX_DECLINED;
+ }
+
+ } else {
+ date = ngx_time();
+ }
+
+ if (expires < date) {
+ return NGX_OK;
+ }
+
+ return NGX_DECLINED;
+ }
+
+ if (r->headers_out.cache_control) {
+
+ if ((conf->proxied & NGX_HTTP_GZIP_PROXIED_NO_CACHE)
+ && ngx_strstr(r->headers_out.cache_control->value.data, "no-cache"))
+ {
+ return NGX_OK;
+ }
+
+ if ((conf->proxied & NGX_HTTP_GZIP_PROXIED_NO_STORE)
+ && ngx_strstr(r->headers_out.cache_control->value.data, "no-store"))
+ {
+ return NGX_OK;
+ }
+
+ if ((conf->proxied & NGX_HTTP_GZIP_PROXIED_PRIVATE)
+ && ngx_strstr(r->headers_out.cache_control->value.data, "private"))
+ {
+ return NGX_OK;
+ }
+
+ return NGX_DECLINED;
+ }
+
+ if ((conf->proxied & NGX_HTTP_GZIP_PROXIED_NO_LM)
+ && r->headers_out.last_modified)
+ {
+ return NGX_DECLINED;
+ }
+
+ if ((conf->proxied & NGX_HTTP_GZIP_PROXIED_NO_ETAG)
+ && r->headers_out.etag)
+ {
+ return NGX_DECLINED;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t ngx_http_gzip_body_filter(ngx_http_request_t *r,
+ ngx_chain_t *in)
+{
+ int rc, wbits, memlevel, last;
+ struct gztrailer *trailer;
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+ ngx_http_gzip_ctx_t *ctx;
+ ngx_http_gzip_conf_t *conf;
+
+ 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);
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+ if (ctx->preallocated == NULL) {
+ wbits = conf->wbits;
+ memlevel = conf->memlevel;
+
+ if (ctx->length > 0) {
+
+ /* the actual zlib window size is smaller by 262 bytes */
+
+ while (ctx->length < ((1 << (wbits - 1)) - 262)) {
+ wbits--;
+ memlevel--;
+ }
+ }
+
+ /*
+ * We preallocate a memory for zlib in one buffer (200K-400K), this
+ * dicreases a number of malloc() and free() calls and also probably
+ * dicreases a number of syscalls (sbrk() or so).
+ * Besides we free this memory as soon as the 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 x86 and sparc64 (32-bit mode)
+ * * 5920 bytes on amd64 and sparc64
+ */
+
+ ctx->allocated = 8192 + (1 << (wbits + 2)) + (1 << (memlevel + 9));
+
+ if (!(ctx->preallocated = ngx_palloc(r->pool, ctx->allocated))) {
+ 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, conf->level, Z_DEFLATED,
+ -wbits, memlevel, Z_DEFAULT_STRATEGY);
+
+ if (rc != Z_OK) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "deflateInit2() failed: %d", rc);
+ return ngx_http_gzip_error(ctx);
+ }
+
+ if (!(b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)))) {
+ return ngx_http_gzip_error(ctx);
+ }
+
+ b->memory = 1;
+ b->pos = gzheader;
+ b->last = b->pos + 10;
+
+ ngx_alloc_link_and_set_buf(cl, b, r->pool, ngx_http_gzip_error(ctx));
+ ctx->out = cl;
+ ctx->last_out = &cl->next;
+
+ ctx->crc32 = crc32(0L, Z_NULL, 0);
+ ctx->flush = Z_NO_FLUSH;
+ }
+
+ if (in) {
+ if (ngx_chain_add_copy(r->pool, &ctx->in, in) == NGX_ERROR) {
+ return ngx_http_gzip_error(ctx);
+ }
+ }
+
+ last = NGX_NONE;
+
+ for ( ;; ) {
+
+ for ( ;; ) {
+
+ /* does zlib need a new data ? */
+
+ if (ctx->zstream.avail_in == 0
+ && ctx->flush == Z_NO_FLUSH
+ && !ctx->redo)
+ {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "gzip in: " PTR_FMT, ctx->in);
+
+ if (ctx->in == NULL) {
+ break;
+ }
+
+ ctx->in_buf = ctx->in->buf;
+ ctx->in = ctx->in->next;
+
+ ctx->zstream.next_in = ctx->in_buf->pos;
+ ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "gzip in_buf:" PTR_FMT " ni:" PTR_FMT " ai:%d",
+ ctx->in_buf,
+ ctx->zstream.next_in, ctx->zstream.avail_in);
+
+ /* STUB */
+ if (ctx->in_buf->last < ctx->in_buf->pos) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "zstream.avail_in is huge");
+ ctx->done = 1;
+ return NGX_ERROR;
+ }
+ /**/
+
+ 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 == 0) {
+ if (ctx->flush == Z_NO_FLUSH) {
+ continue;
+ }
+
+ } else {
+ ctx->crc32 = crc32(ctx->crc32, ctx->zstream.next_in,
+ ctx->zstream.avail_in);
+ }
+ }
+
+
+ /* is there a space for the gzipped data ? */
+
+ if (ctx->zstream.avail_out == 0) {
+
+ 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_http_gzip_error(ctx);
+ }
+
+ ctx->out_buf->tag = (ngx_buf_tag_t)
+ &ngx_http_gzip_filter_module;
+ ctx->out_buf->recycled = 1;
+ ctx->bufs++;
+
+ } else {
+#if 0
+ ctx->blocked = 1;
+#endif
+ break;
+ }
+
+#if 0
+ ctx->blocked = 0;
+#endif
+ ctx->zstream.next_out = ctx->out_buf->pos;
+ ctx->zstream.avail_out = conf->bufs.size;
+ }
+
+ ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "deflate in: ni:%X no:%X ai:%d ao:%d 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_http_gzip_error(ctx);
+ }
+
+ ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "deflate out: ni:%X no:%X ai:%d ao:%d 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:" PTR_FMT " pos:" PTR_FMT,
+ 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 */
+
+ ngx_alloc_link_and_set_buf(cl, ctx->out_buf, r->pool,
+ ngx_http_gzip_error(ctx));
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ ctx->redo = 1;
+
+ continue;
+ }
+
+ ctx->redo = 0;
+
+ if (ctx->flush == Z_SYNC_FLUSH) {
+
+ ctx->out_buf->flush = 0;
+ ctx->flush = Z_NO_FLUSH;
+
+ ngx_alloc_link_and_set_buf(cl, ctx->out_buf, r->pool,
+ ngx_http_gzip_error(ctx));
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+#if 0
+ ctx->pass = 1;
+#endif
+
+ break;
+ }
+
+ if (rc == Z_STREAM_END) {
+
+ 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_http_gzip_error(ctx);
+ }
+
+ ngx_pfree(r->pool, ctx->preallocated);
+
+ ngx_alloc_link_and_set_buf(cl, ctx->out_buf, r->pool,
+ ngx_http_gzip_error(ctx));
+ *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 {
+ if (!(b = ngx_create_temp_buf(r->pool, 8))) {
+ return ngx_http_gzip_error(ctx);
+ }
+
+ b->last_buf = 1;
+
+ ngx_alloc_link_and_set_buf(cl, b, r->pool,
+ ngx_http_gzip_error(ctx));
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+ trailer = (struct gztrailer *) b->pos;
+ b->last += 8;
+ }
+
+#if (HAVE_LITTLE_ENDIAN)
+ trailer->crc32 = ctx->crc32;
+ trailer->zlen = ctx->zin;
+#else
+ trailer->crc32[0] = ctx->crc32 & 0xff;
+ trailer->crc32[1] = (ctx->crc32 >> 8) & 0xff;
+ trailer->crc32[2] = (ctx->crc32 >> 16) & 0xff;
+ trailer->crc32[3] = (ctx->crc32 >> 24) & 0xff;
+
+ trailer->zlen[0] = ctx->zin & 0xff;
+ trailer->zlen[1] = (ctx->zin >> 8) & 0xff;
+ trailer->zlen[2] = (ctx->zin >> 16) & 0xff;
+ trailer->zlen[3] = (ctx->zin >> 24) & 0xff;
+#endif
+
+ ctx->zstream.avail_in = 0;
+ ctx->zstream.avail_out = 0;
+
+ ctx->done = 1;
+#if 0
+ ctx->pass = 1;
+#endif
+
+ break;
+ }
+
+ if (conf->no_buffer && ctx->in == NULL) {
+ ngx_alloc_link_and_set_buf(cl, ctx->out_buf, r->pool,
+ ngx_http_gzip_error(ctx));
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+#if 0
+ ctx->pass = 1;
+#endif
+
+ break;
+ }
+ }
+
+#if 0
+
+ /* OLD CODE */
+
+ if (ctx->out) {
+ if (ctx->pass) {
+ ctx->pass = 0;
+
+ } else if (last == NGX_AGAIN) {
+ return last;
+ }
+
+ } else if (ctx->busy->buf && ngx_buf_size(ctx->busy->buf)) {
+ if (last != NGX_NONE) {
+ return last;
+ }
+
+ } else if (ctx->blocked) {
+ if (last != NGX_NONE) {
+ return last;
+ }
+
+ } else {
+ if (last == NGX_NONE) {
+ return NGX_OK;
+ }
+
+ return last;
+ }
+#endif
+
+ /* NEW CODE */
+
+ if (last == NGX_AGAIN) {
+ return NGX_AGAIN;
+ }
+
+ if (ctx->out == NULL && ctx->busy == NULL) {
+ return NGX_OK;
+ }
+
+ /**/
+
+ last = ngx_http_next_body_filter(r, ctx->out);
+
+ /*
+ * we do not check NGX_AGAIN here because the downstream filters
+ * may free some buffers and zlib may compress some data into them
+ */
+
+ if (last == NGX_ERROR) {
+ return ngx_http_gzip_error(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;
+
+ if (ctx->done) {
+ return last;
+ }
+ }
+}
+
+
+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) {
+
+ /*
+ * the zlib deflate_state allocation, it takes about 6K, we allocate 8K
+ */
+
+ alloc = (alloc + ngx_pagesize - 1) & ~(ngx_pagesize - 1);
+ }
+
+ 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:%d s:%d a:%d p:" PTR_FMT,
+ items, size, alloc, p);
+
+ return p;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, ctx->request->connection->log, 0,
+ "gzip filter failed to use preallocated memory: %d of %d",
+ 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: %X", address);
+#endif
+}
+
+
+static u_char *ngx_http_gzip_log_ratio(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data)
+{
+ ngx_uint_t zint, zfrac;
+ ngx_http_gzip_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_gzip_filter_module);
+
+ if (ctx == NULL || ctx->zout == 0) {
+ *buf = '-';
+ return buf + 1;
+ }
+
+#if 0
+ return buf + ngx_snprintf((char *) buf, NGX_INT32_LEN + 4, "%.2f",
+ (float) ctx->zin / ctx->zout);
+#endif
+
+ /* we prefer do not use FPU */
+
+ 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) {
+ if (++zfrac > 99) {
+ zint++;
+ zfrac = 0;
+ }
+ }
+
+ return buf + ngx_snprintf((char *) buf, NGX_INT32_LEN + 4,
+ "%" NGX_UINT_T_FMT ".%02" NGX_UINT_T_FMT,
+ zint, zfrac);
+}
+
+
+ngx_inline static int ngx_http_gzip_error(ngx_http_gzip_ctx_t *ctx)
+{
+ deflateEnd(&ctx->zstream);
+
+ ngx_pfree(ctx->request->pool, ctx->preallocated);
+
+ ctx->zstream.avail_in = 0;
+ ctx->zstream.avail_out = 0;
+
+ ctx->done = 1;
+
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t ngx_http_gzip_pre_conf(ngx_conf_t *cf)
+{
+ ngx_http_log_op_name_t *op;
+
+ for (op = ngx_http_gzip_log_fmt_ops; op->name.len; op++) { /* void */ }
+ op->op = NULL;
+
+ op = ngx_http_log_fmt_ops;
+
+ for (op = ngx_http_log_fmt_ops; op->op; op++) {
+ if (op->name.len == 0) {
+ op = (ngx_http_log_op_name_t *) op->op;
+ }
+ }
+
+ op->op = (ngx_http_log_op_pt) ngx_http_gzip_log_fmt_ops;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t ngx_http_gzip_filter_init(ngx_cycle_t *cycle)
+{
+ 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 void *ngx_http_gzip_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_gzip_conf_t *conf;
+
+ if (!(conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_gzip_conf_t)))) {
+ return NGX_CONF_ERROR;
+ }
+
+ /*
+
+ set by ngx_pcalloc():
+
+ conf->bufs.num = 0;
+ conf->proxied = 0;
+
+ */
+
+ conf->enable = NGX_CONF_UNSET;
+ conf->no_buffer = NGX_CONF_UNSET;
+
+ conf->http_version = NGX_CONF_UNSET_UINT;
+
+ conf->level = NGX_CONF_UNSET;
+ conf->wbits = (size_t) NGX_CONF_UNSET;
+ conf->memlevel = (size_t) NGX_CONF_UNSET;
+ 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_bufs_value(conf->bufs, prev->bufs, 4, ngx_pagesize);
+
+ ngx_conf_merge_unsigned_value(conf->http_version, prev->http_version,
+ NGX_HTTP_VERSION_11);
+ ngx_conf_merge_bitmask_value(conf->proxied, prev->proxied,
+ (NGX_CONF_BITMASK_SET
+ |NGX_HTTP_GZIP_PROXIED_OFF));
+
+ 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, 0);
+ ngx_conf_merge_value(conf->no_buffer, prev->no_buffer, 0);
+
+ return NGX_CONF_OK;
+}
+
+
+static char *ngx_http_gzip_set_window(ngx_conf_t *cf, void *post, void *data)
+{
+ int *np = data;
+
+ int 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_set_hash(ngx_conf_t *cf, void *post, void *data)
+{
+ int *np = data;
+
+ int memlevel, hsize;
+
+ memlevel = 9;
+
+ for (hsize = 128 * 1024; hsize > 256; hsize >>= 1) {
+
+ if (hsize == *np) {
+ *np = memlevel;
+
+ return NGX_CONF_OK;
+ }
+
+ memlevel--;
+ }
+
+ return "must be 512, 1k, 2k, 4k, 8k, 16k, 32k, 64k, or 128k";
+}
diff --git a/src/http/modules/ngx_http_headers_filter.c b/src/http/modules/ngx_http_headers_filter.c
new file mode 100644
index 000000000..f7fe52c8e
--- /dev/null
+++ b/src/http/modules/ngx_http_headers_filter.c
@@ -0,0 +1,239 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ time_t expires;
+} ngx_http_headers_conf_t;
+
+
+#define NGX_HTTP_EXPIRES_UNSET -2147483647
+#define NGX_HTTP_EXPIRES_OFF -2147483646
+#define NGX_HTTP_EXPIRES_EPOCH -2147483645
+
+
+static ngx_int_t ngx_http_headers_filter_init(ngx_cycle_t *cycle);
+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);
+char *ngx_http_headers_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+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_CONF_TAKE1,
+ ngx_http_headers_expires,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL},
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_headers_filter_module_ctx = {
+ NULL, /* pre conf */
+
+ 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,
+ &ngx_http_headers_filter_module_ctx, /* module context */
+ ngx_http_headers_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ ngx_http_headers_filter_init, /* init module */
+ NULL /* init child */
+};
+
+
+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)
+{
+ size_t len;
+ ngx_table_elt_t *expires, *cc;
+ ngx_http_headers_conf_t *conf;
+
+ if (r->headers_out.status != NGX_HTTP_OK) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module);
+
+ if (conf->expires != NGX_HTTP_EXPIRES_OFF) {
+
+ if (!(expires = ngx_list_push(&r->headers_out.headers))) {
+ return NGX_ERROR;
+ }
+
+ r->headers_out.expires = expires;
+
+ if (!(cc = ngx_list_push(&r->headers_out.headers))) {
+ return NGX_ERROR;
+ }
+
+ r->headers_out.cache_control = cc;
+
+ len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT");
+
+ expires->key.len = sizeof("Expires") - 1;
+ expires->key.data = (u_char *) "Expires";
+ expires->value.len = len - 1;
+
+ cc->key.len = sizeof("Cache-Control") - 1;
+ cc->key.data = (u_char *) "Cache-Control";
+
+ if (conf->expires == NGX_HTTP_EXPIRES_EPOCH) {
+ expires->value.data = (u_char *) "Thu, 01 Jan 1970 00:00:01 GMT";
+
+ cc->value.len = sizeof("no-cache") - 1;
+ cc->value.data = (u_char *) "no-cache";
+
+ } else {
+ expires->value.data = ngx_palloc(r->pool, len);
+ if (expires->value.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (conf->expires == 0) {
+ ngx_memcpy(expires->value.data, ngx_cached_http_time.data,
+ ngx_cached_http_time.len + 1);
+
+ cc->value.len = sizeof("max-age=0") - 1;
+ cc->value.data = (u_char *) "max-age=0";
+
+ } else {
+ ngx_http_time(expires->value.data, ngx_time() + conf->expires);
+
+ if (conf->expires < 0) {
+ cc->value.len = sizeof("no-cache") - 1;
+ cc->value.data = (u_char *) "no-cache";
+
+ } else {
+ cc->value.data = ngx_palloc(r->pool,
+ sizeof("max-age=") + TIME_T_LEN + 1);
+ if (cc->value.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ cc->value.len = ngx_snprintf((char *) cc->value.data,
+ sizeof("max-age=") + TIME_T_LEN,
+ "max-age=" TIME_T_FMT,
+ conf->expires);
+ }
+ }
+ }
+ }
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t ngx_http_headers_filter_init(ngx_cycle_t *cycle)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_headers_filter;
+
+ return NGX_OK;
+}
+
+
+static void *ngx_http_headers_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_headers_conf_t *conf;
+
+ if (!(conf = ngx_palloc(cf->pool, sizeof(ngx_http_headers_conf_t)))) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->expires = NGX_HTTP_EXPIRES_UNSET;
+
+ return conf;
+}
+
+
+static char *ngx_http_headers_merge_conf(ngx_conf_t *cf,
+ void *parent, void *child)
+{
+ ngx_http_headers_conf_t *prev = parent;
+ ngx_http_headers_conf_t *conf = child;
+
+ if (conf->expires == NGX_HTTP_EXPIRES_UNSET) {
+ conf->expires = (prev->expires == NGX_HTTP_EXPIRES_UNSET) ?
+ NGX_HTTP_EXPIRES_OFF : prev->expires;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+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;
+ ngx_str_t *value;
+
+ if (hcf->expires != NGX_HTTP_EXPIRES_UNSET) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "epoch") == 0) {
+ hcf->expires = NGX_HTTP_EXPIRES_EPOCH;
+ return NGX_CONF_OK;
+ }
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ hcf->expires = NGX_HTTP_EXPIRES_OFF;
+ return NGX_CONF_OK;
+ }
+
+ if (value[1].data[0] == '+') {
+ value[1].data++;
+ value[1].len--;
+ minus = 0;
+
+ } else if (value[1].data[0] == '-') {
+ value[1].data++;
+ value[1].len--;
+ minus = 1;
+
+ } else {
+ minus = 0;
+ }
+
+ hcf->expires = ngx_parse_time(&value[1], 1);
+ if (hcf->expires == NGX_ERROR) {
+ return "invalid value";
+ }
+
+ if (hcf->expires == NGX_PARSE_LARGE_TIME) {
+ return "value must be less than 68 years";
+ }
+
+ if (minus) {
+ hcf->expires = - hcf->expires;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/src/http/modules/ngx_http_index_handler.c b/src/http/modules/ngx_http_index_handler.c
new file mode 100644
index 000000000..68a9d3627
--- /dev/null
+++ b/src/http/modules/ngx_http_index_handler.c
@@ -0,0 +1,529 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_array_t indices;
+ size_t max_index_len;
+ ngx_http_cache_hash_t *index_cache;
+} ngx_http_index_loc_conf_t;
+
+
+typedef struct {
+ ngx_uint_t index;
+ u_char *last;
+ ngx_str_t path;
+ ngx_str_t redirect;
+ ngx_http_cache_t *cache;
+ unsigned tested:1;
+} ngx_http_index_ctx_t;
+
+
+#define NGX_HTTP_DEFAULT_INDEX "index.html"
+
+
+static ngx_int_t ngx_http_index_test_dir(ngx_http_request_t *r,
+ ngx_http_index_ctx_t *ctx);
+static ngx_int_t ngx_http_index_error(ngx_http_request_t *r,
+ ngx_http_index_ctx_t *ctx, ngx_err_t err);
+
+static ngx_int_t ngx_http_index_init(ngx_cycle_t *cycle);
+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_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_index_set_index,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+#if (NGX_HTTP_CACHE)
+
+ { ngx_string("index_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE3,
+ ngx_http_set_cache_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_index_loc_conf_t, index_cache),
+ NULL },
+
+#endif
+
+ ngx_null_command
+};
+
+
+ngx_http_module_t ngx_http_index_module_ctx = {
+ NULL, /* pre conf */
+
+ 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,
+ &ngx_http_index_module_ctx, /* module context */
+ ngx_http_index_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ ngx_http_index_init, /* init module */
+ NULL /* init child */
+};
+
+
+/*
+ * Try to open the first index file before the test of the directory existence
+ * because the valid requests should be many more than invalid ones.
+ * If open() failed then stat() should be more quickly because some data
+ * is already cached in the kernel.
+ * Besides Win32 has ERROR_PATH_NOT_FOUND (NGX_ENOTDIR).
+ * Unix has ENOTDIR error, although it less helpfull - it shows only
+ * that path contains the usual file in place of the directory.
+ */
+
+ngx_int_t ngx_http_index_handler(ngx_http_request_t *r)
+{
+ u_char *name;
+ ngx_fd_t fd;
+ ngx_int_t rc;
+ ngx_str_t *index;
+ ngx_err_t err;
+ ngx_log_t *log;
+ ngx_http_index_ctx_t *ctx;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_index_loc_conf_t *ilcf;
+#if (NGX_HTTP_CACHE0)
+ /* crc must be in ctx !! */
+ uint32_t crc;
+#endif
+
+ if (r->uri.data[r->uri.len - 1] != '/') {
+ return NGX_DECLINED;
+ }
+
+ log = r->connection->log;
+
+ /*
+ * we use context because the handler supports an async file opening
+ * and thus can be called several times
+ */
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+ ilcf = ngx_http_get_module_loc_conf(r, ngx_http_index_module);
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_index_module);
+ if (ctx == NULL) {
+ ngx_http_create_ctx(r, ctx, ngx_http_index_module,
+ sizeof(ngx_http_index_ctx_t),
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+#if (NGX_HTTP_CACHE)
+
+ if (ilcf->index_cache) {
+ ctx->cache = ngx_http_cache_get(ilcf->index_cache, NULL,
+ &r->uri, &crc);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "http index cache get: " PTR_FMT, ctx->cache);
+
+ if (ctx->cache && !ctx->cache->expired) {
+
+ ctx->cache->accessed = ngx_cached_time;
+
+ ctx->redirect.len = ctx->cache->data.value.len;
+ ctx->redirect.data = ngx_palloc(r->pool, ctx->redirect.len + 1);
+ if (ctx->redirect.data == NULL) {
+ ngx_http_cache_unlock(ilcf->index_cache, ctx->cache, log);
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_memcpy(ctx->redirect.data, ctx->cache->data.value.data,
+ ctx->redirect.len + 1);
+ ngx_http_cache_unlock(ilcf->index_cache, ctx->cache, log);
+
+ return ngx_http_internal_redirect(r, &ctx->redirect, NULL);
+ }
+ }
+
+#endif
+
+#if 0
+ ctx->path.data = ngx_palloc(r->pool, clcf->root.len + r->uri.len
+ + ilcf->max_index_len
+ - clcf->alias * clcf->name.len);
+ if (ctx->path.data == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ctx->redirect.data = ngx_cpymem(ctx->path.data, clcf->root.data,
+ clcf->root.len);
+#endif
+
+ if (clcf->alias) {
+ ctx->path.data = ngx_palloc(r->pool, clcf->root.len
+ + r->uri.len + 1 - clcf->name.len
+ + ilcf->max_index_len);
+ if (ctx->path.data == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ctx->redirect.data = ngx_palloc(r->pool, r->uri.len
+ + ilcf->max_index_len);
+ if (ctx->redirect.data == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_memcpy(ctx->path.data, clcf->root.data, clcf->root.len);
+
+ ctx->last = ngx_cpystrn(ctx->path.data + clcf->root.len,
+ r->uri.data + clcf->name.len,
+ r->uri.len + 1 - clcf->name.len);
+
+#if 0
+ /*
+ * aliases usually have trailling "/",
+ * set it in the start of the possible redirect
+ */
+
+ if (*ctx->redirect.data != '/') {
+ ctx->redirect.data--;
+ }
+#endif
+
+ } else {
+ ctx->path.data = ngx_palloc(r->pool, clcf->root.len + r->uri.len
+ + ilcf->max_index_len);
+ if (ctx->path.data == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ctx->redirect.data = ngx_cpymem(ctx->path.data, clcf->root.data,
+ clcf->root.len);
+
+ ctx->last = ngx_cpystrn(ctx->redirect.data, r->uri.data,
+ r->uri.len + 1);
+ }
+ }
+
+ ctx->path.len = ctx->last - ctx->path.data;
+
+ index = ilcf->indices.elts;
+ for (/* void */; ctx->index < ilcf->indices.nelts; ctx->index++) {
+
+ if (index[ctx->index].data[0] == '/') {
+ name = index[ctx->index].data;
+
+ } else {
+ ngx_memcpy(ctx->last, index[ctx->index].data,
+ index[ctx->index].len + 1);
+ name = ctx->path.data;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "open index \"%s\"", name);
+
+ fd = ngx_open_file(name, NGX_FILE_RDONLY, NGX_FILE_OPEN);
+
+ if (fd == (ngx_fd_t) NGX_AGAIN) {
+ return NGX_AGAIN;
+ }
+
+ if (fd == NGX_INVALID_FILE) {
+ err = ngx_errno;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, err,
+ ngx_open_file_n " %s failed", name);
+
+ if (err == NGX_ENOTDIR) {
+ return ngx_http_index_error(r, ctx, err);
+
+ } else if (err == NGX_EACCES) {
+ return ngx_http_index_error(r, ctx, err);
+ }
+
+ if (!ctx->tested) {
+ rc = ngx_http_index_test_dir(r, ctx);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ ctx->tested = 1;
+ }
+
+ if (err == NGX_ENOENT) {
+ continue;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, log, err,
+ ngx_open_file_n " %s failed", name);
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+
+ /* STUB: open file cache */
+
+ r->file.name.data = name;
+ r->file.fd = fd;
+
+ if (index[ctx->index].data[0] == '/') {
+ r->file.name.len = index[ctx->index].len;
+ ctx->redirect.len = index[ctx->index].len;
+ ctx->redirect.data = index[ctx->index].data;
+
+ } else {
+ if (clcf->alias) {
+ name = ngx_cpymem(ctx->redirect.data, r->uri.data, r->uri.len);
+ ngx_memcpy(name, index[ctx->index].data,
+ index[ctx->index].len + 1);
+ }
+
+ ctx->redirect.len = r->uri.len + index[ctx->index].len;
+ r->file.name.len = clcf->root.len + r->uri.len
+ - clcf->alias * clcf->name.len
+ + index[ctx->index].len;
+ }
+
+ /**/
+
+
+#if (NGX_HTTP_CACHE)
+
+ if (ilcf->index_cache) {
+
+ if (ctx->cache) {
+ if (ctx->redirect.len == ctx->cache->data.value.len
+ && ngx_memcmp(ctx->cache->data.value.data,
+ ctx->redirect.data, ctx->redirect.len) == 0)
+ {
+ ctx->cache->accessed = ngx_cached_time;
+ ctx->cache->updated = ngx_cached_time;
+ ngx_http_cache_unlock(ilcf->index_cache, ctx->cache, log);
+
+ return ngx_http_internal_redirect(r, &ctx->redirect, NULL);
+ }
+ }
+
+ ctx->redirect.len++;
+ ctx->cache = ngx_http_cache_alloc(ilcf->index_cache, ctx->cache,
+ NULL, &r->uri, crc,
+ &ctx->redirect, log);
+ ctx->redirect.len--;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "http index cache alloc: " PTR_FMT, ctx->cache);
+
+ if (ctx->cache) {
+ ctx->cache->fd = NGX_INVALID_FILE;
+ ctx->cache->accessed = ngx_cached_time;
+ ctx->cache->last_modified = 0;
+ ctx->cache->updated = ngx_cached_time;
+ ctx->cache->memory = 1;
+ ngx_http_cache_unlock(ilcf->index_cache, ctx->cache, log);
+ }
+ }
+
+#endif
+
+ return ngx_http_internal_redirect(r, &ctx->redirect, NULL);
+ }
+
+ return NGX_DECLINED;
+}
+
+
+static ngx_int_t ngx_http_index_test_dir(ngx_http_request_t *r,
+ ngx_http_index_ctx_t *ctx)
+{
+ ngx_err_t err;
+
+ ctx->path.data[ctx->path.len - 1] = '\0';
+ ctx->path.data[ctx->path.len] = '\0';
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http check dir: \"%s\"", ctx->path.data);
+
+ if (ngx_file_info(ctx->path.data, &r->file.info) == -1) {
+
+ err = ngx_errno;
+
+ if (err == NGX_ENOENT) {
+ ctx->path.data[ctx->path.len - 1] = '/';
+ return ngx_http_index_error(r, ctx, err);
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
+ ngx_file_info_n " %s failed", ctx->path.data);
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ctx->path.data[ctx->path.len - 1] = '/';
+
+ if (ngx_is_dir(&r->file.info)) {
+ return NGX_OK;
+ }
+
+ /* THINK: not reached ??? */
+ return ngx_http_index_error(r, ctx, 0);
+}
+
+
+static ngx_int_t ngx_http_index_error(ngx_http_request_t *r,
+ ngx_http_index_ctx_t *ctx, ngx_err_t err)
+{
+ if (err == NGX_EACCES) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, err,
+ "\"%s\" is forbidden", ctx->path.data);
+
+ return NGX_HTTP_FORBIDDEN;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, err,
+ "\"%s\" is not found", ctx->path.data);
+ return NGX_HTTP_NOT_FOUND;
+}
+
+
+static ngx_int_t ngx_http_index_init(ngx_cycle_t *cycle)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_core_module);
+
+ h = ngx_push_array(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_index_handler;
+
+ return NGX_OK;
+}
+
+
+static void *ngx_http_index_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_index_loc_conf_t *conf;
+
+ ngx_test_null(conf, ngx_palloc(cf->pool, sizeof(ngx_http_index_loc_conf_t)),
+ NGX_CONF_ERROR);
+
+ ngx_init_array(conf->indices, cf->pool, 3, sizeof(ngx_str_t),
+ NGX_CONF_ERROR);
+ conf->max_index_len = 0;
+
+ conf->index_cache = NULL;
+
+ return conf;
+}
+
+
+/* TODO: remove duplicate indices */
+
+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_uint_t i;
+ ngx_str_t *index, *prev_index;
+
+ if (conf->max_index_len == 0) {
+ if (prev->max_index_len != 0) {
+ ngx_memcpy(conf, prev, sizeof(ngx_http_index_loc_conf_t));
+ return NGX_CONF_OK;
+ }
+
+ ngx_test_null(index, ngx_push_array(&conf->indices), NGX_CONF_ERROR);
+ index->len = sizeof(NGX_HTTP_DEFAULT_INDEX) - 1;
+ index->data = (u_char *) NGX_HTTP_DEFAULT_INDEX;
+ conf->max_index_len = sizeof(NGX_HTTP_DEFAULT_INDEX);
+
+ return NGX_CONF_OK;
+ }
+
+ if (prev->max_index_len != 0) {
+
+ prev_index = prev->indices.elts;
+ for (i = 0; i < prev->indices.nelts; i++) {
+ ngx_test_null(index, ngx_push_array(&conf->indices),
+ NGX_CONF_ERROR);
+ index->len = prev_index[i].len;
+ index->data = prev_index[i].data;
+ }
+ }
+
+ if (conf->max_index_len < prev->max_index_len) {
+ conf->max_index_len = prev->max_index_len;
+ }
+
+ if (conf->index_cache == NULL) {
+ conf->index_cache = prev->index_cache;
+ }
+
+ return NGX_CONF_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_uint_t i;
+ ngx_str_t *index, *value;
+
+ value = cf->args->elts;
+
+ if (value[1].data[0] == '/' && ilcf->indices.nelts == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "first index \"%s\" in \"%s\" directive "
+ "must not be absolute",
+ value[1].data, cmd->name.data);
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 1; i < cf->args->nelts; i++) {
+ if (value[i].len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "index \"%s\" in \"%s\" directive is invalid",
+ value[1].data, cmd->name.data);
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_test_null(index, ngx_push_array(&ilcf->indices), NGX_CONF_ERROR);
+ index->len = value[i].len;
+ index->data = value[i].data;
+
+ if (ilcf->max_index_len < index->len + 1) {
+ ilcf->max_index_len = index->len + 1;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/src/http/modules/ngx_http_not_modified_filter.c b/src/http/modules/ngx_http_not_modified_filter.c
new file mode 100644
index 000000000..04d1fde6b
--- /dev/null
+++ b/src/http/modules/ngx_http_not_modified_filter.c
@@ -0,0 +1,85 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+
+static ngx_int_t ngx_http_not_modified_filter_init(ngx_cycle_t *cycle);
+
+
+static ngx_http_module_t ngx_http_not_modified_filter_module_ctx = {
+ NULL, /* pre conf */
+
+ 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,
+ &ngx_http_not_modified_filter_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ ngx_http_not_modified_filter_init, /* init module */
+ NULL /* init child */
+};
+
+
+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)
+{
+ time_t ims;
+
+ if (r->headers_out.status != NGX_HTTP_OK
+ || r->headers_in.if_modified_since == NULL
+ || r->headers_out.last_modified_time == -1)
+ {
+ 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);
+
+ /*
+ * I think that the equality of the dates is correcter
+ */
+
+ if (ims != NGX_ERROR && ims == r->headers_out.last_modified_time) {
+ r->headers_out.status = NGX_HTTP_NOT_MODIFIED;
+ r->headers_out.content_type->key.len = 0;
+ r->headers_out.content_type = NULL;
+ r->headers_out.content_length_n = -1;
+ r->headers_out.content_length = NULL;
+#if 0
+ r->headers_out.accept_ranges->key.len = 0;
+#endif
+ }
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t ngx_http_not_modified_filter_init(ngx_cycle_t *cycle)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_not_modified_header_filter;
+
+ return NGX_OK;
+}
diff --git a/src/http/modules/ngx_http_range_filter.c b/src/http/modules/ngx_http_range_filter.c
new file mode 100644
index 000000000..a08e25f7a
--- /dev/null
+++ b/src/http/modules/ngx_http_range_filter.c
@@ -0,0 +1,523 @@
+
+/*
+ * 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 {
+ ngx_str_t boundary_header;
+} ngx_http_range_filter_ctx_t;
+
+
+static ngx_int_t ngx_http_range_header_filter_init(ngx_cycle_t *cycle);
+static ngx_int_t ngx_http_range_body_filter_init(ngx_cycle_t *cycle);
+
+
+static ngx_http_module_t ngx_http_range_header_filter_module_ctx = {
+ NULL, /* pre conf */
+
+ 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,
+ &ngx_http_range_header_filter_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ ngx_http_range_header_filter_init, /* init module */
+ NULL /* init child */
+};
+
+
+static ngx_http_module_t ngx_http_range_body_filter_module_ctx = {
+ NULL, /* pre conf */
+
+ 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,
+ &ngx_http_range_body_filter_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ ngx_http_range_body_filter_init, /* init module */
+ NULL /* init child */
+};
+
+
+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)
+{
+ ngx_int_t rc;
+ ngx_uint_t boundary, suffix, i;
+ u_char *p;
+ size_t len;
+ off_t start, end;
+ ngx_http_range_t *range;
+ ngx_http_range_filter_ctx_t *ctx;
+
+ if (r->http_version < NGX_HTTP_VERSION_10
+ || r->headers_out.status != NGX_HTTP_OK
+ || r->headers_out.content_length_n == -1
+ || !r->filter_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, "bytes=", 6) != 0)
+ {
+
+ 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->key.len = sizeof("Accept-Ranges") - 1;
+ r->headers_out.accept_ranges->key.data = (u_char *) "Accept-Ranges";
+ r->headers_out.accept_ranges->value.len = sizeof("bytes") - 1;
+ r->headers_out.accept_ranges->value.data = (u_char *) "bytes";
+
+ return ngx_http_next_header_filter(r);
+ }
+
+ ngx_init_array(r->headers_out.ranges, r->pool, 5, sizeof(ngx_http_range_t),
+ NGX_ERROR);
+
+ rc = 0;
+ range = NULL;
+ 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') {
+ rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ break;
+ }
+
+ while (*p >= '0' && *p <= '9') {
+ start = start * 10 + *p++ - '0';
+ }
+
+ while (*p == ' ') { p++; }
+
+ if (*p++ != '-') {
+ rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ break;
+ }
+
+ if (start >= r->headers_out.content_length_n) {
+ rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ break;
+ }
+
+ while (*p == ' ') { p++; }
+
+ if (*p == ',' || *p == '\0') {
+ ngx_test_null(range, ngx_push_array(&r->headers_out.ranges),
+ NGX_ERROR);
+ range->start = start;
+ range->end = r->headers_out.content_length_n;
+
+ if (*p++ != ',') {
+ break;
+ }
+
+ continue;
+ }
+
+ } else {
+ suffix = 1;
+ p++;
+ }
+
+ if (*p < '0' || *p > '9') {
+ rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ break;
+ }
+
+ while (*p >= '0' && *p <= '9') {
+ end = end * 10 + *p++ - '0';
+ }
+
+ while (*p == ' ') { p++; }
+
+ if (*p != ',' && *p != '\0') {
+ rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ break;
+ }
+
+ if (suffix) {
+ start = r->headers_out.content_length_n - end;
+ end = r->headers_out.content_length_n - 1;
+ }
+
+ if (start > end) {
+ rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ break;
+ }
+
+ ngx_test_null(range, ngx_push_array(&r->headers_out.ranges), 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++ != ',') {
+ break;
+ }
+ }
+
+ if (rc) {
+
+ /* rc == NGX_HTTP_RANGE_NOT_SATISFIABLE */
+
+ r->headers_out.status = rc;
+ r->headers_out.ranges.nelts = 0;
+
+ r->headers_out.content_range = ngx_list_push(&r->headers_out.headers);
+ if (r->headers_out.content_range == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->headers_out.content_range->key.len = sizeof("Content-Range") - 1;
+ r->headers_out.content_range->key.data = (u_char *) "Content-Range";
+
+ r->headers_out.content_range->value.data =
+ ngx_palloc(r->pool, 8 + 20 + 1);
+ if (r->headers_out.content_range->value.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->headers_out.content_range->value.len =
+ ngx_snprintf((char *) r->headers_out.content_range->value.data,
+ 8 + 20 + 1, "bytes */" OFF_T_FMT,
+ r->headers_out.content_length_n);
+
+ r->headers_out.content_length_n = -1;
+ if (r->headers_out.content_length) {
+ r->headers_out.content_length->key.len = 0;
+ r->headers_out.content_length = NULL;
+ }
+
+ return rc;
+
+ } else {
+ r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
+
+ if (r->headers_out.ranges.nelts == 1) {
+
+ r->headers_out.content_range =
+ ngx_list_push(&r->headers_out.headers);
+ if (r->headers_out.content_range == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->headers_out.content_range->key.len = sizeof("Content-Range") - 1;
+ r->headers_out.content_range->key.data = (u_char *) "Content-Range";
+
+ ngx_test_null(r->headers_out.content_range->value.data,
+ ngx_palloc(r->pool, 6 + 20 + 1 + 20 + 1 + 20 + 1),
+ NGX_ERROR);
+
+ /* "Content-Range: bytes SSSS-EEEE/TTTT" header */
+
+ r->headers_out.content_range->value.len =
+ ngx_snprintf((char *)
+ r->headers_out.content_range->value.data,
+ 6 + 20 + 1 + 20 + 1 + 20 + 1,
+ "bytes " OFF_T_FMT "-" OFF_T_FMT "/" OFF_T_FMT,
+ range->start, range->end - 1,
+ r->headers_out.content_length_n);
+
+ r->headers_out.content_length_n = range->end - range->start;
+
+ } else {
+
+#if 0
+ /* TODO: what if no content_type ?? */
+
+ if (!(r->headers_out.content_type =
+ ngx_http_add_header(&r->headers_out, ngx_http_headers_out)))
+ {
+ return NGX_ERROR;
+ }
+#endif
+
+ ngx_http_create_ctx(r, ctx, ngx_http_range_body_filter_module,
+ sizeof(ngx_http_range_filter_ctx_t), NGX_ERROR);
+
+ len = 4 + 10 + 2 + 14 + r->headers_out.content_type->value.len
+ + 2 + 21 + 1;
+
+ if (r->headers_out.charset.len) {
+ len += 10 + r->headers_out.charset.len;
+ }
+
+ ngx_test_null(ctx->boundary_header.data, ngx_palloc(r->pool, len),
+ 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_snprintf((char *) ctx->boundary_header.data, len,
+ CRLF "--%010" NGX_UINT_T_FMT CRLF
+ "Content-Type: %s; charset=%s" CRLF
+ "Content-Range: bytes ",
+ boundary,
+ r->headers_out.content_type->value.data,
+ r->headers_out.charset.data);
+
+ r->headers_out.charset.len = 0;
+
+ } else {
+ ctx->boundary_header.len =
+ ngx_snprintf((char *) ctx->boundary_header.data, len,
+ CRLF "--%010" NGX_UINT_T_FMT CRLF
+ "Content-Type: %s" CRLF
+ "Content-Range: bytes ",
+ boundary,
+ r->headers_out.content_type->value.data);
+ }
+
+ ngx_test_null(r->headers_out.content_type->value.data,
+ ngx_palloc(r->pool, 31 + 10 + 1),
+ NGX_ERROR);
+
+ /* "Content-Type: multipart/byteranges; boundary=0123456789" */
+
+ r->headers_out.content_type->value.len =
+ ngx_snprintf((char *)
+ r->headers_out.content_type->value.data,
+ 31 + 10 + 1,
+ "multipart/byteranges; boundary=%010"
+ NGX_UINT_T_FMT,
+ boundary);
+
+ /* the size of the last boundary CRLF "--0123456789--" CRLF */
+ len = 4 + 10 + 4;
+
+ range = r->headers_out.ranges.elts;
+ for (i = 0; i < r->headers_out.ranges.nelts; i++) {
+ ngx_test_null(range[i].content_range.data,
+ ngx_palloc(r->pool, 20 + 1 + 20 + 1 + 20 + 5),
+ NGX_ERROR);
+
+ /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */
+
+ range[i].content_range.len =
+ ngx_snprintf((char *) range[i].content_range.data,
+ 20 + 1 + 20 + 1 + 20 + 5,
+ OFF_T_FMT "-" OFF_T_FMT "/" OFF_T_FMT CRLF CRLF,
+ range[i].start, range[i].end - 1,
+ r->headers_out.content_length_n);
+
+ 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;
+ r->headers_out.content_length = NULL;
+ }
+ }
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t ngx_http_range_body_filter(ngx_http_request_t *r,
+ ngx_chain_t *in)
+{
+ ngx_uint_t i;
+ ngx_buf_t *b;
+ ngx_chain_t *out, *hcl, *rcl, *dcl, **ll;
+ ngx_http_range_t *range;
+ ngx_http_range_filter_ctx_t *ctx;
+
+ if (r->headers_out.ranges.nelts == 0) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ /*
+ * the optimized version for the static files only
+ * that are passed in the single file buf
+ */
+
+ if (in && in->buf->in_file && in->buf->last_buf) {
+ range = r->headers_out.ranges.elts;
+
+ if (r->headers_out.ranges.nelts == 1) {
+ in->buf->file_pos = range->start;
+ in->buf->file_last = range->end;
+
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module);
+ ll = &out;
+
+ for (i = 0; i < r->headers_out.ranges.nelts; i++) {
+
+ /*
+ * The boundary header of the range:
+ * CRLF
+ * "--0123456789" CRLF
+ * "Content-Type: image/jpeg" CRLF
+ * "Content-Range: bytes "
+ */
+
+ ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR);
+ b->memory = 1;
+ b->pos = ctx->boundary_header.data;
+ b->last = ctx->boundary_header.data + ctx->boundary_header.len;
+
+ ngx_test_null(hcl, ngx_alloc_chain_link(r->pool), NGX_ERROR);
+ hcl->buf = b;
+
+ /* "SSSS-EEEE/TTTT" CRLF CRLF */
+
+ ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR);
+ b->temporary = 1;
+ b->pos = range[i].content_range.data;
+ b->last = range[i].content_range.data + range[i].content_range.len;
+
+ ngx_test_null(rcl, ngx_alloc_chain_link(r->pool), NGX_ERROR);
+ rcl->buf = b;
+
+ /* the range data */
+
+ ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR);
+ b->in_file = 1;
+ b->file_pos = range[i].start;
+ b->file_last = range[i].end;
+ b->file = in->buf->file;
+
+ ngx_alloc_link_and_set_buf(dcl, b, r->pool, NGX_ERROR);
+
+ *ll = hcl;
+ hcl->next = rcl;
+ rcl->next = dcl;
+ ll = &dcl->next;
+ }
+
+ /* the last boundary CRLF "--0123456789--" CRLF */
+
+ ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR);
+ b->temporary = 1;
+ b->last_buf = 1;
+ ngx_test_null(b->pos, ngx_palloc(r->pool, 4 + 10 + 4), NGX_ERROR);
+ b->last = ngx_cpymem(b->pos, ctx->boundary_header.data, 4 + 10);
+ *b->last++ = '-'; *b->last++ = '-';
+ *b->last++ = CR; *b->last++ = LF;
+
+ ngx_alloc_link_and_set_buf(hcl, b, r->pool, NGX_ERROR);
+ *ll = hcl;
+
+ return ngx_http_next_body_filter(r, out);
+ }
+
+ /* TODO: alert */
+
+ return ngx_http_next_body_filter(r, in);
+}
+
+
+static ngx_int_t ngx_http_range_header_filter_init(ngx_cycle_t *cycle)
+{
+ 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_cycle_t *cycle)
+{
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_range_body_filter;
+
+ return NGX_OK;
+}
diff --git a/src/http/modules/ngx_http_rewrite_handler.c b/src/http/modules/ngx_http_rewrite_handler.c
new file mode 100644
index 000000000..aa3a65648
--- /dev/null
+++ b/src/http/modules/ngx_http_rewrite_handler.c
@@ -0,0 +1,466 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_REWRITE_COPY_MATCH 0
+#define NGX_HTTP_REWRITE_COPY_SHORT 1
+#define NGX_HTTP_REWRITE_COPY_LONG 2
+
+
+typedef struct {
+ ngx_int_t op;
+ size_t len;
+ uintptr_t data;
+} ngx_http_rewrite_op_t;
+
+
+typedef struct {
+ ngx_regex_t *regex;
+ ngx_uint_t msize;
+
+ ngx_array_t ops;
+ ngx_uint_t size;
+
+ ngx_str_t re_name;
+ ngx_str_t s_name;
+
+ ngx_uint_t status;
+ unsigned last:1;
+} ngx_http_rewrite_rule_t;
+
+
+typedef struct {
+ ngx_array_t rules;
+ ngx_flag_t log;
+} ngx_http_rewrite_srv_conf_t;
+
+
+typedef struct {
+ ngx_str_t redirect;
+} ngx_http_rewrite_loc_conf_t;
+
+
+static void *ngx_http_rewrite_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_rewrite_merge_srv_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static void *ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_rewrite_rule(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_redirect(ngx_conf_t *cf, void *post, void *data);
+static ngx_int_t ngx_http_rewrite_init(ngx_cycle_t *cycle);
+
+
+static ngx_conf_post_handler_pt ngx_http_redirect_p = ngx_http_redirect;
+
+
+static ngx_command_t ngx_http_rewrite_commands[] = {
+
+ { ngx_string("rewrite"),
+ NGX_HTTP_SRV_CONF|NGX_CONF_TAKE23,
+ ngx_http_rewrite_rule,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("redirect"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ &ngx_http_redirect_p },
+
+ { ngx_string("rewrite_log"),
+ NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_rewrite_srv_conf_t, log),
+ NULL },
+
+ ngx_null_command
+};
+
+
+ngx_http_module_t ngx_http_rewrite_module_ctx = {
+ NULL, /* pre conf */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_http_rewrite_create_srv_conf, /* create server configuration */
+ ngx_http_rewrite_merge_srv_conf, /* merge server configuration */
+
+ ngx_http_rewrite_create_loc_conf, /* create location configration */
+ NULL, /* merge location configration */
+};
+
+
+ngx_module_t ngx_http_rewrite_module = {
+ NGX_MODULE,
+ &ngx_http_rewrite_module_ctx, /* module context */
+ ngx_http_rewrite_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ ngx_http_rewrite_init, /* init module */
+ NULL /* init child */
+};
+
+
+static ngx_int_t ngx_http_rewrite_handler(ngx_http_request_t *r)
+{
+ int *matches;
+ u_char *p;
+ size_t len;
+ uintptr_t data;
+ ngx_int_t rc;
+ ngx_uint_t i, m, n;
+ ngx_str_t uri;
+ ngx_http_rewrite_op_t *op;
+ ngx_http_rewrite_rule_t *rule;
+ ngx_http_rewrite_srv_conf_t *scf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http rewrite handler");
+
+ scf = ngx_http_get_module_srv_conf(r, ngx_http_rewrite_module);
+
+ rule = scf->rules.elts;
+ for (i = 0; i < scf->rules.nelts; i++) {
+
+ if (rule[i].msize) {
+ if (!(matches = ngx_palloc(r->pool, rule[i].msize * sizeof(int)))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ } else {
+ matches = NULL;
+ }
+
+ rc = ngx_regex_exec(rule[i].regex, &r->uri, matches, rule[i].msize);
+
+ if (rc == NGX_DECLINED) {
+ if (scf->log) {
+ ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+ "\"%s\" does not match \"%s\"",
+ rule[i].re_name.data, r->uri.data);
+ }
+
+ continue;
+ }
+
+ if (rc < 0) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ ngx_regex_exec_n
+ " failed: %d on \"%s\" using \"%s\"",
+ rc, r->uri.data, rule[i].re_name.data);
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (scf->log) {
+ ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+ "\"%s\" matches \"%s\"",
+ rule[i].re_name.data, r->uri.data);
+ }
+
+ if (rule[i].status) {
+ return rule[i].status;
+ }
+
+ uri.len = rule[i].size;
+
+ for (n = 1; n < (ngx_uint_t) rc; n++) {
+ uri.len += matches[2 * n + 1] - matches[2 * n];
+ }
+
+ if (!(uri.data = ngx_palloc(r->pool, uri.len + 1))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ p = uri.data;
+
+ op = rule[i].ops.elts;
+ for (n = 0; n < rule[i].ops.nelts; n++) {
+ if (op[n].op == NGX_HTTP_REWRITE_COPY_SHORT) {
+ len = op[n].len;
+ data = op[n].data;
+ while (len--) {
+ *p++ = (char) (data & 0xff);
+ data >>= 8;
+ }
+
+ } else if (op[n].op == NGX_HTTP_REWRITE_COPY_LONG) {
+ p = ngx_cpymem(p, (void *) op[n].data, op[n].len);
+
+ } else { /* NGX_HTTP_REWRITE_COPY_MATCH */
+ m = 2 * op[n].data;
+ p = ngx_cpymem(p, &r->uri.data[matches[m]],
+ matches[m + 1] - matches[m]);
+ }
+ }
+
+ *p = '\0';
+
+ if (scf->log) {
+ ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+ "rewritten uri: \"%s\"", uri.data);
+ }
+
+ r->uri = uri;
+
+ if (ngx_http_set_exten(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (rule[i].last) {
+ return NGX_DECLINED;
+ }
+ }
+
+ return NGX_DECLINED;
+}
+
+
+static ngx_int_t ngx_http_redirect_handler(ngx_http_request_t *r)
+{
+ u_char *p;
+ ngx_http_rewrite_loc_conf_t *rlcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http redirect handler");
+
+ rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module);
+
+ r->headers_out.location = ngx_list_push(&r->headers_out.headers);
+ if (r->headers_out.location == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (rlcf->redirect.data[0] != '/') {
+ r->headers_out.location->key.len = sizeof("Location") - 1;
+ r->headers_out.location->key.data = (u_char *) "Location";
+ }
+
+ r->headers_out.location->value.len = rlcf->redirect.len
+ + r->unparsed_uri.len;
+ r->headers_out.location->value.data = ngx_palloc(r->pool,
+ r->headers_out.location->value.len);
+
+ if (r->headers_out.location->value.data == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ p = ngx_cpymem(r->headers_out.location->value.data, rlcf->redirect.data,
+ rlcf->redirect.len);
+ p = ngx_cpystrn(p, r->unparsed_uri.data + 1, r->unparsed_uri.len);
+
+ return NGX_HTTP_MOVED_TEMPORARILY;
+}
+
+
+static void *ngx_http_rewrite_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_http_rewrite_srv_conf_t *conf;
+
+ if (!(conf = ngx_palloc(cf->pool, sizeof(ngx_http_rewrite_srv_conf_t)))) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_init_array(conf->rules, cf->pool, 5, sizeof(ngx_http_rewrite_rule_t),
+ NGX_CONF_ERROR);
+
+ conf->log = NGX_CONF_UNSET;
+
+ return conf;
+}
+
+
+static char *ngx_http_rewrite_merge_srv_conf(ngx_conf_t *cf,
+ void *parent, void *child)
+{
+ ngx_http_rewrite_srv_conf_t *prev = parent;
+ ngx_http_rewrite_srv_conf_t *conf = child;
+
+ ngx_conf_merge_value(conf->log, prev->log, 0);
+
+ return NGX_CONF_OK;
+}
+
+
+static void *ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_rewrite_loc_conf_t *conf;
+
+ if (!(conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_rewrite_loc_conf_t)))) {
+ return NGX_CONF_ERROR;
+ }
+
+ return conf;
+}
+
+
+static char *ngx_http_rewrite_rule(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf)
+{
+ ngx_http_rewrite_srv_conf_t *scf = conf;
+
+ u_char *data, *p;
+ size_t len;
+ ngx_str_t *value, err;
+ ngx_uint_t i;
+ ngx_http_rewrite_op_t *op;
+ ngx_http_rewrite_rule_t *rule;
+ u_char errstr[NGX_MAX_CONF_ERRSTR];
+
+ if (!(rule = ngx_push_array(&scf->rules))) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_init_array(rule->ops, cf->pool, 5, sizeof(ngx_http_rewrite_op_t),
+ NGX_CONF_ERROR);
+
+ rule->msize = 0;
+ rule->size = 0;
+ rule->status = 0;
+ rule->last = 0;
+
+ value = cf->args->elts;
+
+ /* STUB */ {
+ err.len = NGX_MAX_CONF_ERRSTR;
+ err.data = errstr;
+
+ rule->regex = ngx_regex_compile(&value[1], 0, cf->pool, &err);
+
+ if (rule->regex == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s", err.data);
+ return NGX_CONF_ERROR;
+ }
+
+ rule->re_name = value[1];
+ rule->s_name = value[2];
+
+ if (ngx_strcasecmp(value[2].data, "forbidden:") == 0) {
+
+ if (cf->args->nelts == 3) {
+ rule->status = NGX_HTTP_FORBIDDEN;
+ rule->last = 1;
+ return NGX_CONF_OK;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%s\"", value[3].data);
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 0; i < value[2].len; /* void */) {
+
+ if (!(op = ngx_push_array(&rule->ops))) {
+ return NGX_CONF_ERROR;
+ }
+
+ data = &value[2].data[i];
+
+ if (value[2].data[i] == '$'
+ && i < value[2].len
+ && value[2].data[i + 1] >= '1'
+ && value[2].data[i + 1] <= '9')
+ {
+ op->op = NGX_HTTP_REWRITE_COPY_MATCH;
+ op->data = value[2].data[++i] - '0';
+
+ if (rule->msize < op->data) {
+ rule->msize = op->data;
+ }
+
+ i++;
+
+ } else {
+ i++;
+
+ while (i < value[2].len && value[2].data[i] != '$') {
+ i++;
+ }
+
+ len = &value[2].data[i] - data;
+ rule->size += len;
+
+ if (len) {
+
+ op->len = len;
+
+ if (len <= sizeof(uintptr_t)) {
+ op->op = NGX_HTTP_REWRITE_COPY_SHORT;
+ op->data = 0;
+
+ while (len--) {
+ op->data <<= 8;
+ op->data |= data[len];
+ }
+
+ } else {
+ op->op = NGX_HTTP_REWRITE_COPY_LONG;
+
+ if (!(p = ngx_palloc(cf->pool, len))) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memcpy(p, data, len);
+ op->data = (uintptr_t) p;
+ }
+ }
+ }
+ }
+
+ if (rule->msize) {
+ rule->msize++;
+ rule->msize *= 3;
+ }
+
+ if (cf->args->nelts > 3) {
+ if (ngx_strcmp(value[3].data, "last") == 0) {
+ rule->last = 1;
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%s\"", value[3].data);
+ return NGX_CONF_ERROR;
+ }
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *ngx_http_redirect(ngx_conf_t *cf, void *post, void *data)
+{
+ ngx_http_core_loc_conf_t *clcf;
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+ clcf->handler = ngx_http_redirect_handler;
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t ngx_http_rewrite_init(ngx_cycle_t *cycle)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_core_module);
+
+ h = ngx_push_array(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_rewrite_handler;
+
+ return NGX_OK;
+}
diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c
new file mode 100644
index 000000000..35ab2c46c
--- /dev/null
+++ b/src/http/modules/ngx_http_ssl_module.c
@@ -0,0 +1,160 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_DEFLAUT_CERTIFICATE "cert.pem"
+#define NGX_DEFLAUT_CERTIFICATE_KEY "cert.pem"
+
+
+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 ngx_command_t ngx_http_ssl_commands[] = {
+
+ { ngx_string("ssl"),
+ NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, enable),
+ NULL },
+
+ { ngx_string("ssl_certificate"),
+ 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_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_null_command
+};
+
+
+static ngx_http_module_t ngx_http_ssl_module_ctx = {
+ NULL, /* pre conf */
+
+ 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,
+ &ngx_http_ssl_module_ctx, /* module context */
+ ngx_http_ssl_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init module */
+ NULL /* init process */
+};
+
+
+static void *ngx_http_ssl_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_http_ssl_srv_conf_t *scf;
+
+ if (!(scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssl_srv_conf_t)))) {
+ return NGX_CONF_ERROR;
+ }
+
+ scf->enable = NGX_CONF_UNSET;
+
+ return scf;
+}
+
+
+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_conf_merge_value(conf->enable, prev->enable, 0);
+
+ if (conf->enable == 0) {
+ return NGX_CONF_OK;
+ }
+
+ ngx_conf_merge_str_value(conf->certificate, prev->certificate,
+ NGX_DEFLAUT_CERTIFICATE);
+
+ ngx_conf_merge_str_value(conf->certificate_key, prev->certificate_key,
+ NGX_DEFLAUT_CERTIFICATE_KEY);
+
+ /* TODO: configure methods */
+
+ conf->ssl_ctx = SSL_CTX_new(SSLv23_server_method());
+
+ if (conf->ssl_ctx == NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, "SSL_CTX_new() failed");
+ return NGX_CONF_ERROR;
+ }
+
+ if (SSL_CTX_use_certificate_file(conf->ssl_ctx,
+ (char *) conf->certificate.data,
+ SSL_FILETYPE_PEM) == 0) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "SSL_CTX_use_certificate_file(\"%s\") failed",
+ conf->certificate.data);
+ return NGX_CONF_ERROR;
+ }
+
+ if (SSL_CTX_use_PrivateKey_file(conf->ssl_ctx,
+ (char *) conf->certificate_key.data,
+ SSL_FILETYPE_PEM) == 0) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "SSL_CTX_use_PrivateKey_file(\"%s\") failed",
+ conf->certificate_key.data);
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+#if 0
+
+static ngx_int_t ngx_http_ssl_init_process(ngx_cycle_t *cycle)
+{
+ ngx_uint_t i;
+ ngx_http_ssl_srv_conf_t *sscf;
+ ngx_http_core_srv_conf_t **cscfp;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_core_module);
+
+ cscfp = cmcf->servers.elts;
+
+ for (i = 0; i < cmcf->servers.nelts; i++) {
+ sscf = cscfp[i]->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
+
+ if (sscf->enable) {
+ cscfp[i]->recv = ngx_ssl_recv;
+ cscfp[i]->send_chain = ngx_ssl_send_chain;
+ }
+ }
+
+ return NGX_OK;
+}
+
+#endif
diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h
new file mode 100644
index 000000000..eaca2a6c5
--- /dev/null
+++ b/src/http/modules/ngx_http_ssl_module.h
@@ -0,0 +1,36 @@
+
+/*
+ * 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_str_t certificate;
+ ngx_str_t certificate_key;
+
+ ngx_ssl_ctx_t *ssl_ctx;
+} ngx_http_ssl_srv_conf_t;
+
+
+ngx_int_t ngx_http_ssl_read(ngx_http_request_t *r, u_char *buf, size_t size);
+ngx_int_t ngx_http_ssl_shutdown(ngx_http_request_t *r);
+ngx_chain_t *ngx_http_ssl_write(ngx_connection_t *c, ngx_chain_t *in,
+ off_t limit);
+
+void ngx_http_ssl_close_connection(SSL *ssl, ngx_log_t *log);
+
+
+extern ngx_module_t ngx_http_ssl_module;
+
+
+#endif /* _NGX_HTTP_SSL_H_INCLUDED_ */
diff --git a/src/http/modules/ngx_http_static_handler.c b/src/http/modules/ngx_http_static_handler.c
new file mode 100644
index 000000000..c4ffde3b2
--- /dev/null
+++ b/src/http/modules/ngx_http_static_handler.c
@@ -0,0 +1,584 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_http_cache_hash_t *redirect_cache;
+} ngx_http_static_loc_conf_t;
+
+
+static ngx_int_t ngx_http_static_handler(ngx_http_request_t *r);
+static void *ngx_http_static_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_static_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_static_init(ngx_cycle_t *cycle);
+
+
+static ngx_command_t ngx_http_static_commands[] = {
+
+#if (NGX_HTTP_CACHE)
+
+ { ngx_string("redirect_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE3,
+ ngx_http_set_cache_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_static_loc_conf_t, redirect_cache),
+ NULL },
+
+#endif
+
+ ngx_null_command
+};
+
+
+
+ngx_http_module_t ngx_http_static_module_ctx = {
+ NULL, /* pre conf */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_static_create_loc_conf, /* create location configuration */
+ ngx_http_static_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_static_module = {
+ NGX_MODULE,
+ &ngx_http_static_module_ctx, /* module context */
+ ngx_http_static_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ ngx_http_static_init, /* init module */
+ NULL /* init child */
+};
+
+
+static ngx_int_t ngx_http_static_handler(ngx_http_request_t *r)
+{
+ u_char *last;
+ ngx_fd_t fd;
+ ngx_int_t rc;
+ ngx_uint_t level;
+ ngx_str_t name, location;
+ ngx_err_t err;
+ ngx_log_t *log;
+ ngx_buf_t *b;
+ ngx_chain_t out;
+ ngx_file_info_t fi;
+ ngx_http_cleanup_t *file_cleanup, *redirect_cleanup;
+ ngx_http_log_ctx_t *ctx;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_static_loc_conf_t *slcf;
+#if (NGX_HTTP_CACHE)
+ uint32_t file_crc, redirect_crc;
+ ngx_http_cache_t *file, *redirect;
+#endif
+
+ if (r->uri.data[r->uri.len - 1] == '/') {
+ return NGX_DECLINED;
+ }
+
+ if (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_HEAD) {
+ return NGX_HTTP_NOT_ALLOWED;
+ }
+
+ rc = ngx_http_discard_body(r);
+
+ if (rc != NGX_OK && rc != NGX_AGAIN) {
+ return rc;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ /*
+ * there is a valid cached open file, i.e by the index handler,
+ * and it should be already registered in r->cleanup
+ */
+
+ if (r->cache && !r->cache->expired) {
+ return ngx_http_send_cached(r);
+ }
+
+#endif
+
+ log = r->connection->log;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ /*
+ * make a file name, reserve 2 bytes for a trailing '/'
+ * in a possible redirect and for the last '\0'
+ */
+
+ if (clcf->alias) {
+ name.data = ngx_palloc(r->pool, clcf->root.len + r->uri.len + 2
+ - clcf->name.len);
+ if (name.data == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ last = ngx_cpymem(name.data, clcf->root.data, clcf->root.len);
+ last = ngx_cpystrn(last, r->uri.data + clcf->name.len,
+ r->uri.len + 1 - clcf->name.len);
+
+ name.len = last - name.data;
+
+ location.data = ngx_palloc(r->pool, r->uri.len + 2);
+ if (location.data == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ last = ngx_cpystrn(location.data, r->uri.data, r->uri.len + 1);
+
+#if 0
+ /*
+ * aliases usually have trailling "/",
+ * set it in the start of the possible redirect
+ */
+
+ if (*location.data != '/') {
+ location.data--;
+ }
+#endif
+
+ location.len = last - location.data + 1;
+
+ } else {
+ name.data = ngx_palloc(r->pool, clcf->root.len + r->uri.len + 2);
+ if (name.data == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ location.data = ngx_cpymem(name.data, clcf->root.data, clcf->root.len);
+ last = ngx_cpystrn(location.data, r->uri.data, r->uri.len + 1);
+
+ name.len = last - name.data;
+ location.len = last - location.data + 1;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "http filename: \"%s\"", name.data);
+
+
+ /* allocate cleanups */
+
+ if (!(file_cleanup = ngx_push_array(&r->cleanup))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ file_cleanup->valid = 0;
+
+ slcf = ngx_http_get_module_loc_conf(r, ngx_http_static_module);
+ if (slcf->redirect_cache) {
+ if (!(redirect_cleanup = ngx_push_array(&r->cleanup))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ redirect_cleanup->valid = 0;
+
+ } else {
+ redirect_cleanup = NULL;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ /* look up an open files cache */
+
+ if (clcf->open_files) {
+ file = ngx_http_cache_get(clcf->open_files, file_cleanup,
+ &name, &file_crc);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "http open file cache get: " PTR_FMT, file);
+
+ if (file && !file->expired) {
+ r->cache = file;
+ return ngx_http_send_cached(r);
+ }
+
+ } else {
+ file = NULL;
+ }
+
+
+ /* look up an redirect cache */
+
+ if (slcf->redirect_cache) {
+ redirect = ngx_http_cache_get(slcf->redirect_cache, redirect_cleanup,
+ &name, &redirect_crc);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "http redirect cache get: " PTR_FMT, redirect);
+
+ if (redirect && !redirect->expired) {
+
+ /*
+ * We do not copy a cached value so the cache entry is locked
+ * until the end of the request. In a single threaded model
+ * the redirected request should complete before other event
+ * will be processed. In a multithreaded model this locking
+ * should keep more popular redirects in cache.
+ */
+
+ if (!(r->headers_out.location =
+ ngx_http_add_header(&r->headers_out, ngx_http_headers_out)))
+ {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ r->headers_out.location->value = redirect->data.value;
+
+ return NGX_HTTP_MOVED_PERMANENTLY;
+ }
+
+ } else {
+ redirect = NULL;
+ }
+
+#endif
+
+ /* open file */
+
+#if (WIN9X)
+
+ /* TODO: redirect cache */
+
+ if (ngx_win32_version < NGX_WIN_NT) {
+
+ /*
+ * there is no way to open a file or a directory in Win9X with
+ * one syscall because Win9X has no FILE_FLAG_BACKUP_SEMANTICS flag
+ * so we need to check its type before the opening
+ */
+
+ if (ngx_file_info(name.data, &fi) == NGX_FILE_ERROR) {
+ err = ngx_errno;
+ ngx_log_error(NGX_LOG_ERR, log, err,
+ ngx_file_info_n " \"%s\" failed", name.data);
+
+ if (err == NGX_ENOENT || err == NGX_ENOTDIR) {
+ return NGX_HTTP_NOT_FOUND;
+
+ } else if (err == NGX_EACCES) {
+ return NGX_HTTP_FORBIDDEN;
+
+ } else {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ if (ngx_is_dir(&fi)) {
+ ngx_log_debug(log, "HTTP DIR: '%s'" _ name.data);
+
+ if (!(r->headers_out.location =
+ ngx_http_add_header(&r->headers_out, ngx_http_headers_out)))
+ {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ *last++ = '/';
+ *last = '\0';
+ r->headers_out.location->value.len = last - location;
+ r->headers_out.location->value.data = location;
+
+ return NGX_HTTP_MOVED_PERMANENTLY;
+ }
+ }
+
+#endif
+
+
+ fd = ngx_open_file(name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN);
+
+ if (fd == NGX_INVALID_FILE) {
+ err = ngx_errno;
+
+ if (err == NGX_ENOENT || err == NGX_ENOTDIR) {
+ 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, log, err,
+ ngx_open_file_n " \"%s\" failed", name.data);
+
+ return rc;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", fd);
+
+ if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_CRIT, log, ngx_errno,
+ ngx_fd_info_n " \"%s\" failed", name.data);
+
+ if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", name.data);
+ }
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_is_dir(&fi)) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir");
+
+ if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", name.data);
+ }
+
+ *last++ = '/';
+ *last = '\0';
+
+ 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->value = location;
+
+#if (NGX_HTTP_CACHE)
+
+ if (slcf->redirect_cache) {
+ if (redirect) {
+ if (location.len == redirect->data.value.len
+ && ngx_memcmp(redirect->data.value.data, location.data,
+ location.len) == 0)
+ {
+ redirect->accessed = ngx_cached_time;
+ redirect->updated = ngx_cached_time;
+
+ /*
+ * we can unlock the cache entry because
+ * we have the local copy anyway
+ */
+
+ ngx_http_cache_unlock(slcf->redirect_cache, redirect, log);
+ redirect_cleanup->valid = 0;
+
+ return NGX_HTTP_MOVED_PERMANENTLY;
+ }
+ }
+
+ location.len++;
+ redirect = ngx_http_cache_alloc(slcf->redirect_cache, redirect,
+ redirect_cleanup,
+ &name, redirect_crc,
+ &location, log);
+ location.len--;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "http redirect cache alloc: " PTR_FMT, redirect);
+
+ if (redirect) {
+ redirect->fd = NGX_INVALID_FILE;
+ redirect->accessed = ngx_cached_time;
+ redirect->last_modified = 0;
+ redirect->updated = ngx_cached_time;
+ redirect->memory = 1;
+ ngx_http_cache_unlock(slcf->redirect_cache, redirect, log);
+ redirect_cleanup->valid = 0;
+ }
+
+ }
+
+#endif
+
+ return NGX_HTTP_MOVED_PERMANENTLY;
+ }
+
+#if !(WIN32) /* the not regular files are probably Unix specific */
+
+ if (!ngx_is_file(&fi)) {
+ ngx_log_error(NGX_LOG_CRIT, log, ngx_errno,
+ "%s is not a regular file", name.data);
+
+ if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", name.data);
+ }
+
+ return NGX_HTTP_NOT_FOUND;
+ }
+
+#endif
+
+
+#if (NGX_HTTP_CACHE)
+
+ if (clcf->open_files) {
+
+#if (NGX_USE_HTTP_FILE_CACHE_UNIQ)
+
+ if (file && file->uniq == ngx_file_uniq(&fi)) {
+ if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", name.data);
+ }
+ file->accessed = ngx_cached_time;
+ file->updated = ngx_cached_time;
+ file->expired = 0;
+ r->cache = file;
+
+ return ngx_http_send_cached(r);
+
+ } else {
+ if (file) {
+ ngx_http_cache_unlock(clcf->open_files, file, log);
+ file = NULL;
+ }
+
+ file = ngx_http_cache_alloc(clcf->open_files, file,
+ file_cleanup,
+ &name, file_crc, NULL, log);
+ if (file) {
+ file->uniq = ngx_file_uniq(&fi);
+ }
+ }
+
+#else
+ file = ngx_http_cache_alloc(clcf->open_files, file,
+ file_cleanup,
+ &name, file_crc, NULL, log);
+#endif
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "http open file cache alloc: " PTR_FMT, file);
+
+ if (file) {
+ file->fd = fd;
+ file->data.size = ngx_file_size(&fi);
+ file->accessed = ngx_cached_time;
+ file->last_modified = ngx_file_mtime(&fi);
+ file->updated = ngx_cached_time;
+ r->cache = file;
+ }
+
+ return ngx_http_send_cached(r);
+ }
+
+#endif
+
+ ctx = log->data;
+ ctx->action = "sending response to client";
+
+ file_cleanup->data.file.fd = fd;
+ file_cleanup->data.file.name = name.data;
+ file_cleanup->valid = 1;
+ file_cleanup->cache = 0;
+
+ r->headers_out.status = NGX_HTTP_OK;
+ r->headers_out.content_length_n = ngx_file_size(&fi);
+ r->headers_out.last_modified_time = ngx_file_mtime(&fi);
+
+ if (r->headers_out.content_length_n == 0) {
+ r->header_only = 1;
+ }
+
+ if (ngx_http_set_content_type(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+#if (NGX_SUPPRESS_WARN)
+ b = NULL;
+#endif
+
+ if (!r->header_only) {
+ /* we need to allocate all before the header would be sent */
+
+ if (!(b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (!(b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t)))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ r->filter_allow_ranges = 1;
+ }
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ return rc;
+ }
+
+ b->in_file = 1;
+
+ if (!r->main) {
+ b->last_buf = 1;
+ }
+
+ b->file_pos = 0;
+ b->file_last = ngx_file_size(&fi);
+
+ b->file->fd = fd;
+ b->file->log = log;
+
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_output_filter(r, &out);
+}
+
+
+static void *ngx_http_static_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_static_loc_conf_t *conf;
+
+ if (!(conf = ngx_palloc(cf->pool, sizeof(ngx_http_static_loc_conf_t)))) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->redirect_cache = NULL;
+
+ return conf;
+}
+
+
+static char *ngx_http_static_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child)
+{
+ ngx_http_static_loc_conf_t *prev = parent;
+ ngx_http_static_loc_conf_t *conf = child;
+
+ if (conf->redirect_cache == NULL) {
+ conf->redirect_cache = prev->redirect_cache;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t ngx_http_static_init(ngx_cycle_t *cycle)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_core_module);
+
+ h = ngx_push_array(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_static_handler;
+
+ return NGX_OK;
+}
diff --git a/src/http/modules/ngx_http_userid_filter.c b/src/http/modules/ngx_http_userid_filter.c
new file mode 100644
index 000000000..6cbad26c7
--- /dev/null
+++ b/src/http/modules/ngx_http_userid_filter.c
@@ -0,0 +1,588 @@
+
+/*
+ * 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_flag_t enable;
+
+ ngx_int_t service;
+
+ ngx_str_t name;
+ ngx_str_t domain;
+ ngx_str_t path;
+ time_t expires;
+
+ ngx_int_t p3p;
+ ngx_str_t p3p_string;
+} ngx_http_userid_conf_t;
+
+
+typedef struct {
+ uint32_t uid_got[4];
+ uint32_t uid_set[4];
+} ngx_http_userid_ctx_t;
+
+
+static ngx_int_t ngx_http_userid_get_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_set_uid(ngx_http_request_t *r,
+ ngx_http_userid_ctx_t *ctx,
+ ngx_http_userid_conf_t *conf);
+
+static u_char *ngx_http_userid_log_uid_got(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+static u_char *ngx_http_userid_log_uid_set(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+
+static ngx_int_t ngx_http_userid_init(ngx_cycle_t *cycle);
+static ngx_int_t ngx_http_userid_pre_conf(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);
+char *ngx_conf_check_domain(ngx_conf_t *cf, void *post, void *data);
+char *ngx_http_userid_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+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_conf_check_domain_p =
+ ngx_conf_check_domain;
+
+
+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_FLAG,
+ 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_FLAG,
+ 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_FLAG,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_userid_conf_t, domain),
+ &ngx_conf_check_domain_p },
+
+ { ngx_string("userid_path"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_userid_conf_t, path),
+ NULL },
+
+ { 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_null_command
+};
+
+
+ngx_http_module_t ngx_http_userid_filter_module_ctx = {
+ ngx_http_userid_pre_conf, /* pre conf */
+
+ 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,
+ &ngx_http_userid_filter_module_ctx, /* module context */
+ ngx_http_userid_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ ngx_http_userid_init, /* init module */
+ NULL /* init process */
+};
+
+
+static ngx_http_log_op_name_t ngx_http_userid_log_fmt_ops[] = {
+ { ngx_string("uid_got"), 0, ngx_http_userid_log_uid_got },
+ { ngx_string("uid_set"), 0, ngx_http_userid_log_uid_set },
+ { ngx_null_string, 0, NULL }
+};
+
+
+static ngx_int_t ngx_http_userid_filter(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_http_userid_ctx_t *ctx;
+ ngx_http_userid_conf_t *conf;
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_userid_filter_module);
+
+ if (conf->enable == NGX_HTTP_USERID_OFF) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ngx_http_create_ctx(r, ctx, ngx_http_userid_filter_module,
+ sizeof(ngx_http_userid_ctx_t), NGX_ERROR);
+
+ rc = ngx_http_userid_get_uid(r, ctx, conf);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ if (conf->enable == NGX_HTTP_USERID_LOG || ctx->uid_got[3] != 0) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ rc = ngx_http_userid_set_uid(r, ctx, conf);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t ngx_http_userid_get_uid(ngx_http_request_t *r,
+ ngx_http_userid_ctx_t *ctx,
+ ngx_http_userid_conf_t *conf)
+{
+ u_char *start, *last, *end;
+ ngx_uint_t i;
+ ngx_str_t src, dst;
+ ngx_table_elt_t **cookies;
+
+ cookies = r->headers_in.cookies.elts;
+
+ for (i = 0; i < r->headers_in.cookies.nelts; i++) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "cookie: \"%s\"", cookies[i]->value.data);
+
+ end = cookies[i]->value.data + cookies[i]->value.len;
+
+ for (start = cookies[i]->value.data; start < end; /* void */) {
+
+ if (conf->name.len >= cookies[i]->value.len
+ || ngx_strncmp(start, conf->name.data, conf->name.len) != 0)
+ {
+ start += conf->name.len;
+ while (start < end && *start++ != ';') { /* void */ }
+
+ for (/* void */; start < end && *start == ' '; start++) { /**/ }
+
+ continue;
+ }
+
+ for (start += conf->name.len; start < end && *start == ' '; start++)
+ {
+ /* void */
+ }
+
+ if (*start != '=') {
+ break;
+ }
+
+ for (start++; start < end && *start == ' '; start++) { /* void */ }
+
+ for (last = start; last < end && *last != ';'; last++) { /**/ }
+
+ if (last - start < 22) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client sent too short userid cookie \"%s\"",
+ cookies[i]->value.data);
+ break;
+ }
+
+ /*
+ * we have to limit encoded string to 22 characters
+ * because there are already the millions cookies with a garbage
+ * instead of the correct base64 trail "=="
+ */
+
+ src.len = 22;
+ src.data = start;
+ dst.data = (u_char *) ctx->uid_got;
+
+ if (ngx_decode_base64(&src, &dst) == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client sent invalid userid cookie \"%s\"",
+ cookies[i]->value.data);
+ break;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "uid: %08X%08X%08X%08X",
+ ctx->uid_got[0], ctx->uid_got[1],
+ ctx->uid_got[2], ctx->uid_got[3]);
+
+ return NGX_OK;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+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;
+ socklen_t slen;
+ struct sockaddr_in addr_in;
+ ngx_str_t src, dst;
+ ngx_table_elt_t *set_cookie;
+
+ /* TODO: mutex for sequencers */
+
+ if (conf->enable == NGX_HTTP_USERID_V1) {
+ if (conf->service == NGX_CONF_UNSET) {
+ ctx->uid_set[0] = 0;
+ } else {
+ ctx->uid_set[0] = htonl(conf->service);
+ }
+
+ ctx->uid_set[1] = ngx_time();
+ ctx->uid_set[2] = ngx_pid;
+ ctx->uid_set[3] = sequencer_v1;
+ sequencer_v1 += 0x100;
+
+ } else {
+ if (conf->service == NGX_CONF_UNSET) {
+ if (r->in_addr == 0) {
+ slen = sizeof(struct sockaddr_in);
+ if (getsockname(r->connection->fd,
+ (struct sockaddr *) &addr_in, &slen) == -1)
+ {
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log,
+ ngx_socket_errno,
+ "getsockname() failed");
+ }
+
+ r->in_addr = addr_in.sin_addr.s_addr;
+ }
+
+ ctx->uid_set[0] = htonl(r->in_addr);
+
+ } else {
+ ctx->uid_set[0] = htonl(conf->service);
+ }
+
+ ctx->uid_set[1] = htonl(ngx_time());
+ ctx->uid_set[2] = htonl(ngx_pid);
+ ctx->uid_set[3] = htonl(sequencer_v2);
+ sequencer_v2 += 0x100;
+ if (sequencer_v2 < 0x03030302) {
+ sequencer_v2 = 0x03030302;
+ }
+ }
+
+ len = conf->name.len + 1 + ngx_base64_encoded_length(16) + 1;
+
+ if (conf->expires) {
+ len += sizeof(expires) - 1 + 2;
+ }
+
+ if (conf->domain.len > 1) {
+ len += sizeof("; domain=") - 1 + conf->domain.len;
+ }
+
+ if (conf->path.len) {
+ len += sizeof("; path=") - 1 + conf->path.len;
+ }
+
+ if (!(cookie = ngx_palloc(r->pool, len))) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_cpymem(cookie, conf->name.data, conf->name.len);
+ *p++ = '=';
+
+ src.len = 16;
+ src.data = (u_char *) ctx->uid_set;
+ dst.data = p;
+
+ ngx_encode_base64(&src, &dst);
+
+ p += dst.len;
+
+ 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);
+ }
+
+ if (conf->domain.len > 1) {
+ p = ngx_cpymem(p, "; domain=", sizeof("; domain=") - 1);
+ p = ngx_cpymem(p, conf->domain.data, conf->domain.len);
+ }
+
+ if (conf->path.len) {
+ p = ngx_cpymem(p, "; path=", sizeof("; path=") - 1);
+ p = ngx_cpymem(p, conf->path.data, conf->path.len);
+ }
+
+ *p = '\0';
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "uid cookie: \"%s\"", cookie);
+
+ if (!(set_cookie = ngx_list_push(&r->headers_out.headers))) {
+ return NGX_ERROR;
+ }
+
+ set_cookie->key.len = sizeof("Set-Cookie") - 1;
+ set_cookie->key.data = (u_char *) "Set-Cookie";
+ set_cookie->value.len = p - cookie;
+ set_cookie->value.data = cookie;
+
+ return NGX_OK;
+}
+
+
+static u_char *ngx_http_userid_log_uid_got(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data)
+{
+ ngx_http_userid_ctx_t *ctx;
+ ngx_http_userid_conf_t *conf;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_userid_filter_module);
+
+ if (ctx == NULL || ctx->uid_got[3] == 0) {
+ if (buf == NULL) {
+ return (u_char *) 1;
+ }
+
+ *buf = '-';
+ return buf + 1;
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_userid_filter_module);
+
+ if (buf == NULL) {
+ return (u_char *) (conf->name.len + 1 + 32);
+ }
+
+ buf = ngx_cpymem(buf, conf->name.data, conf->name.len);
+
+ *buf++ = '=';
+
+ return buf + ngx_snprintf((char *) buf, 33, "%08X%08X%08X%08X",
+ ctx->uid_got[0], ctx->uid_got[1],
+ ctx->uid_got[2], ctx->uid_got[3]);
+}
+
+
+static u_char *ngx_http_userid_log_uid_set(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data)
+{
+ ngx_http_userid_ctx_t *ctx;
+ ngx_http_userid_conf_t *conf;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_userid_filter_module);
+
+ if (ctx == NULL || ctx->uid_set[3] == 0) {
+ if (buf == NULL) {
+ return (u_char *) 1;
+ }
+
+ *buf = '-';
+ return buf + 1;
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_userid_filter_module);
+
+ if (buf == NULL) {
+ return (u_char *) (conf->name.len + 1 + 32);
+ }
+
+ buf = ngx_cpymem(buf, conf->name.data, conf->name.len);
+
+ *buf++ = '=';
+
+ return buf + ngx_snprintf((char *) buf, 33, "%08X%08X%08X%08X",
+ ctx->uid_set[0], ctx->uid_set[1],
+ ctx->uid_set[2], ctx->uid_set[3]);
+}
+
+
+static ngx_int_t ngx_http_userid_init(ngx_cycle_t *cycle)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_userid_filter;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t ngx_http_userid_pre_conf(ngx_conf_t *cf)
+{
+ ngx_http_log_op_name_t *op;
+
+ for (op = ngx_http_userid_log_fmt_ops; op->name.len; op++) { /* void */ }
+ op->op = NULL;
+
+ op = ngx_http_log_fmt_ops;
+
+ for (op = ngx_http_log_fmt_ops; op->op; op++) {
+ if (op->name.len == 0) {
+ op = (ngx_http_log_op_name_t *) op->op;
+ }
+ }
+
+ op->op = (ngx_http_log_op_pt) ngx_http_userid_log_fmt_ops;
+
+ return NGX_OK;
+}
+
+
+static void *ngx_http_userid_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_userid_conf_t *conf;
+
+ if (!(conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_userid_conf_t)))) {
+ return NGX_CONF_ERROR;
+ }
+
+ /* set by ngx_pcalloc():
+
+ conf->name.len = 0;
+ conf->name.date = NULL;
+ conf->domain.len = 0;
+ conf->domain.date = NULL;
+ conf->path.len = 0;
+ conf->path.date = NULL;
+
+ */
+
+ conf->enable = NGX_CONF_UNSET;
+ conf->service = NGX_CONF_UNSET;
+ conf->expires = NGX_CONF_UNSET;
+
+ 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_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, "/");
+
+ ngx_conf_merge_value(conf->service, prev->service, NGX_CONF_UNSET);
+ ngx_conf_merge_sec_value(conf->expires, prev->expires, 0);
+
+ return NGX_CONF_OK;
+}
+
+
+char *ngx_conf_check_domain(ngx_conf_t *cf, void *post, void *data)
+{
+ ngx_str_t *domain = data;
+
+ if (domain->len == 4 && ngx_strcmp(domain->data, "none") == 0) {
+ domain->len = 1;
+ domain->data = (u_char *) ".";
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+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;
+}
diff --git a/src/http/modules/proxy/ngx_http_proxy_cache.c b/src/http/modules/proxy/ngx_http_proxy_cache.c
new file mode 100644
index 000000000..0a2a20025
--- /dev/null
+++ b/src/http/modules/proxy/ngx_http_proxy_cache.c
@@ -0,0 +1,629 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_proxy_handler.h>
+
+
+static int ngx_http_proxy_process_cached_response(ngx_http_proxy_ctx_t *p,
+ int rc);
+static int ngx_http_proxy_process_cached_header(ngx_http_proxy_ctx_t *p);
+static void ngx_http_proxy_cache_look_complete_request(ngx_http_proxy_ctx_t *p);
+
+
+int ngx_http_proxy_get_cached_response(ngx_http_proxy_ctx_t *p)
+{
+ char *last;
+ ngx_http_request_t *r;
+ ngx_http_proxy_cache_t *c;
+ ngx_http_proxy_upstream_conf_t *u;
+
+ r = p->request;
+
+ if (!(c = ngx_pcalloc(r->pool, sizeof(ngx_http_proxy_cache_t)))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ p->cache = c;
+
+ c->ctx.file.fd = NGX_INVALID_FILE;
+ c->ctx.file.log = r->connection->log;
+ c->ctx.path = p->lcf->cache_path;
+
+ u = p->lcf->upstream;
+
+ c->ctx.key.len = u->url.len + r->uri.len - u->location->len + r->args.len;
+ if (!(c->ctx.key.data = ngx_palloc(r->pool, c->ctx.key.len + 1))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ last = ngx_cpymem(c->ctx.key.data, u->url.data, u->url.len);
+
+ last = ngx_cpymem(last, r->uri.data + u->location->len,
+ r->uri.len - u->location->len);
+
+ if (r->args.len > 0) {
+ *(last++) = '?';
+ last = ngx_cpymem(last, r->args.data, r->args.len);
+ }
+ *last = '\0';
+
+ p->header_in = ngx_create_temp_hunk(r->pool, p->lcf->header_buffer_size);
+ if (p->header_in == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ p->header_in->tag = (ngx_hunk_tag_t) &ngx_http_proxy_module;
+
+ c->ctx.buf = p->header_in;
+ c->ctx.log = r->connection->log;
+
+ return ngx_http_proxy_process_cached_response(p,
+ ngx_http_cache_get_file(r, &c->ctx));
+}
+
+
+static int ngx_http_proxy_process_cached_response(ngx_http_proxy_ctx_t *p,
+ int rc)
+{
+ if (rc == NGX_OK) {
+ p->state->cache_state = NGX_HTTP_PROXY_CACHE_HIT;
+ p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
+
+ if (ngx_http_proxy_process_cached_header(p) == NGX_ERROR) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ p->valid_header_in = 1;
+
+ return ngx_http_proxy_send_cached_response(p);
+ }
+
+ if (rc == NGX_HTTP_CACHE_STALE) {
+ p->state->cache_state = NGX_HTTP_PROXY_CACHE_EXPR;
+
+ } else if (rc == NGX_HTTP_CACHE_AGED) {
+ p->state->cache_state = NGX_HTTP_PROXY_CACHE_AGED;
+ }
+
+ if (rc == NGX_HTTP_CACHE_STALE || rc == NGX_HTTP_CACHE_AGED) {
+ p->state->expired = ngx_time() - p->cache->ctx.expires;
+ p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
+
+ if (ngx_http_proxy_process_cached_header(p) == NGX_ERROR) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
+ p->header_in->last = p->header_in->pos;
+
+ p->stale = 1;
+ p->valid_header_in = 1;
+
+ } else if (rc == NGX_DECLINED) {
+ p->state->cache_state = NGX_HTTP_PROXY_CACHE_MISS;
+ p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
+ p->header_in->last = p->header_in->pos;
+ }
+
+ if (p->lcf->busy_lock) {
+ p->try_busy_lock = 1;
+
+ p->header_in->pos = p->header_in->start;
+ p->header_in->last = p->header_in->start;
+
+ p->busy_lock.time = 0;
+ p->busy_lock.event = p->request->connection->read;
+ p->busy_lock.event_handler = ngx_http_proxy_busy_lock_handler;
+ p->busy_lock.md5 = p->cache->ctx.md5;
+
+ ngx_http_proxy_cache_busy_lock(p);
+ return NGX_DONE;
+ }
+
+ return ngx_http_proxy_request_upstream(p);
+}
+
+
+static int ngx_http_proxy_process_cached_header(ngx_http_proxy_ctx_t *p)
+{
+ int rc, i;
+ ngx_table_elt_t *h;
+ ngx_http_request_t *r;
+ ngx_http_proxy_cache_t *c;
+
+ rc = ngx_http_proxy_parse_status_line(p);
+
+ c = p->cache;
+ r = p->request;
+
+ if (rc == NGX_AGAIN) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "\"proxy_header_buffer_size\" "
+ "is too small to read header from \"%s\"",
+ c->ctx.file.name.data);
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_HTTP_PROXY_PARSE_NO_HEADER) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "no valid HTTP/1.0 header in \"%s\"",
+ c->ctx.file.name.data);
+ return NGX_ERROR;
+ }
+
+ /* rc == NGX_OK */
+
+ c->status = p->status;
+ c->status_line.len = p->status_end - p->status_start;
+ c->status_line.data = ngx_palloc(r->pool, c->status_line.len + 1);
+ if (c->status_line.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ /* reset for the possible parsing the upstream header */
+
+ p->status = 0;
+ p->status_count = 0;
+
+ ngx_cpystrn(c->status_line.data, p->status_start, c->status_line.len + 1);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http cache status %d \"%s\"",
+ c->status, c->status_line.data);
+
+ /* TODO: ngx_init_table */
+ c->headers_in.headers = ngx_create_table(r->pool, 20);
+
+ for ( ;; ) {
+ rc = ngx_http_parse_header_line(r, p->header_in);
+
+ if (rc == NGX_OK) {
+
+ /* a header line has been parsed successfully */
+
+ h = ngx_http_add_header(&c->headers_in, ngx_http_proxy_headers_in);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->key.len = r->header_name_end - r->header_name_start;
+ h->value.len = r->header_end - r->header_start;
+
+ h->key.data = ngx_palloc(r->pool,
+ h->key.len + 1 + h->value.len + 1);
+ if (h->key.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->value.data = h->key.data + h->key.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);
+
+ for (i = 0; ngx_http_proxy_headers_in[i].name.len != 0; i++) {
+ if (ngx_http_proxy_headers_in[i].name.len != h->key.len) {
+ continue;
+ }
+
+ if (ngx_strcasecmp(ngx_http_proxy_headers_in[i].name.data,
+ h->key.data) == 0)
+ {
+ *((ngx_table_elt_t **) ((char *) &c->headers_in
+ + ngx_http_proxy_headers_in[i].offset)) = h;
+ break;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http cache header: \"%s: %s\"",
+ h->key.data, h->value.data);
+
+ continue;
+
+ } else 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 cache header done");
+
+ c->ctx.file_start = p->header_in->pos - p->header_in->start;
+
+ return NGX_OK;
+
+ } else if (rc == NGX_HTTP_PARSE_INVALID_HEADER) {
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid header in \"%s\"",
+ c->ctx.file.name.data);
+ return NGX_ERROR;
+ }
+
+ /* rc == NGX_AGAIN || rc == NGX_HTTP_PARSE_TOO_LONG_HEADER */
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "\"proxy_header_buffer_size\" "
+ "is too small to read header from \"%s\"",
+ c->ctx.file.name.data);
+ return NGX_ERROR;
+ }
+}
+
+
+void ngx_http_proxy_cache_busy_lock(ngx_http_proxy_ctx_t *p)
+{
+ int rc, ft_type;
+
+ rc = ngx_http_busy_lock_cachable(p->lcf->busy_lock, &p->busy_lock,
+ p->try_busy_lock);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, p->request->connection->log, 0,
+ "http cache busy lock cachable: %d", rc);
+
+ if (rc == NGX_OK) {
+ if (p->try_busy_lock) {
+ p->busy_locked = 1;
+ p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
+ p->header_in->last = p->header_in->pos;
+
+ ngx_http_proxy_request_upstream(p);
+ return;
+ }
+
+ ngx_http_proxy_cache_look_complete_request(p);
+ return;
+ }
+
+ p->try_busy_lock = 0;
+
+ if (p->cache->ctx.file.fd != NGX_INVALID_FILE
+ && !p->cache->ctx.file.info_valid)
+ {
+ if (ngx_fd_info(p->cache->ctx.file.fd, &p->cache->ctx.file.info)
+ == NGX_FILE_ERROR)
+ {
+ ngx_log_error(NGX_LOG_CRIT, p->request->connection->log, ngx_errno,
+ ngx_fd_info_n " \"%s\" failed",
+ p->cache->ctx.file.name.data);
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ p->cache->ctx.file.info_valid = 1;
+ }
+
+ if (rc == NGX_AGAIN) {
+
+ if ((ngx_event_flags & (NGX_USE_CLEAR_EVENT|NGX_HAVE_KQUEUE_EVENT))
+ && !p->request->connection->write->active)
+ {
+ /*
+ * kqueue allows to detect when client closes prematurely
+ * connection
+ */
+
+ p->request->connection->write->event_handler =
+ ngx_http_proxy_check_broken_connection;
+
+ if (ngx_add_event(p->request->connection->write, NGX_WRITE_EVENT,
+ NGX_CLEAR_EVENT) == NGX_ERROR)
+ {
+ ngx_http_proxy_finalize_request(p,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ }
+
+ return;
+ }
+
+ ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
+
+ if (rc == NGX_DONE) {
+ ft_type = NGX_HTTP_PROXY_FT_BUSY_LOCK;
+
+ } else {
+ /* rc == NGX_ERROR */
+ ft_type = NGX_HTTP_PROXY_FT_MAX_WAITING;
+ }
+
+ if (p->stale && (p->lcf->use_stale & ft_type)) {
+ ngx_http_proxy_finalize_request(p,
+ ngx_http_proxy_send_cached_response(p));
+ return;
+ }
+
+ p->state->status = NGX_HTTP_SERVICE_UNAVAILABLE;
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_SERVICE_UNAVAILABLE);
+}
+
+
+static void ngx_http_proxy_cache_look_complete_request(ngx_http_proxy_ctx_t *p)
+{
+ int rc;
+ ngx_http_cache_ctx_t *ctx;
+
+ if (!(ctx = ngx_pcalloc(p->request->pool, sizeof(ngx_http_cache_ctx_t)))) {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ *ctx = p->cache->ctx;
+
+ rc = ngx_http_cache_open_file(ctx, ngx_file_uniq(&p->cache->ctx.file.info));
+
+ if (rc == NGX_DECLINED || rc == NGX_HTTP_CACHE_THE_SAME) {
+ p->try_busy_lock = 1;
+ p->busy_lock.time = 0;
+ ngx_http_proxy_cache_busy_lock(p);
+ return;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, p->request->connection->log, 0,
+ "http cache old fd:%d, new fd:%d",
+ p->cache->ctx.file.fd, ctx->file.fd);
+
+ if (p->cache->ctx.file.fd != NGX_INVALID_FILE) {
+ if (ngx_close_file(p->cache->ctx.file.fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, p->request->connection->log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed",
+ p->cache->ctx.file.name.data);
+ }
+ }
+
+ p->cache->ctx = *ctx;
+
+ p->status = 0;
+ p->status_count = 0;
+
+ ngx_http_proxy_finalize_request(p,
+ ngx_http_proxy_process_cached_response(p, rc));
+}
+
+
+int ngx_http_proxy_send_cached_response(ngx_http_proxy_ctx_t *p)
+{
+ int rc, len, i;
+ off_t rest;
+ ngx_hunk_t *h0, *h1;
+ ngx_chain_t out[2];
+ ngx_http_request_t *r;
+
+ r = p->request;
+
+ r->headers_out.status = p->cache->status;
+
+#if 0
+ r->headers_out.content_length_n = -1;
+ r->headers_out.content_length = NULL;
+#endif
+
+ /* copy an cached header to r->headers_out */
+
+ if (ngx_http_proxy_copy_header(p, &p->cache->headers_in) == NGX_ERROR) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* we need to allocate all before the header would be sent */
+
+ len = p->header_in->end - (p->header_in->start + p->cache->ctx.file_start);
+
+ h0 = NULL;
+ h1 = NULL;
+
+ if (len) {
+ if (!((h0 = ngx_calloc_hunk(r->pool)))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (!((h0->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t))))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ if (len < p->cache->ctx.length) {
+ if (!((h1 = ngx_calloc_hunk(r->pool)))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (!((h1->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t))))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ rc = ngx_http_send_header(r);
+
+ /* NEEDED ??? */ p->header_sent = 1;
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ return rc;
+ }
+
+ rest = p->cache->ctx.length;
+
+ if (len) {
+ if (p->valid_header_in) {
+ h0->pos = p->header_in->start + p->cache->ctx.file_start;
+
+ if (len > p->cache->ctx.length) {
+ h0->last = h0->pos + p->cache->ctx.length;
+
+ } else {
+ h0->last = p->header_in->end;
+ }
+
+ h0->type = NGX_HUNK_IN_MEMORY|NGX_HUNK_TEMP;
+ }
+
+ h0->type |= NGX_HUNK_FILE;
+ h0->file_pos = p->cache->ctx.file_start;
+
+ h0->file->fd = p->cache->ctx.file.fd;
+ h0->file->log = r->connection->log;
+
+ if (len > p->cache->ctx.length) {
+ h0->file_last = h0->file_pos + p->cache->ctx.length;
+ rest = 0;
+
+ } else {
+ h0->file_last = h0->file_pos + len;
+ rest -= len;
+ }
+
+ out[0].hunk = h0;
+ out[0].next = &out[1];
+ i = 0;
+
+ } else {
+ i = -1;
+ }
+
+ if (rest) {
+ h1->file_pos = p->cache->ctx.file_start + len;
+ h1->file_last = h1->file_pos + rest;
+ h1->type = NGX_HUNK_FILE;
+
+ h1->file->fd = p->cache->ctx.file.fd;
+ h1->file->log = r->connection->log;
+
+ out[++i].hunk = h1;
+ }
+
+ out[i].next = NULL;
+ if (!r->main) {
+ out[i].hunk->type |= NGX_HUNK_LAST;
+ }
+
+ r->file.fd = p->cache->ctx.file.fd;
+
+ return ngx_http_output_filter(r, out);
+}
+
+
+int ngx_http_proxy_is_cachable(ngx_http_proxy_ctx_t *p)
+{
+ time_t date, last_modified, expires, t;
+ ngx_http_proxy_headers_in_t *h;
+
+ switch (p->upstream->status) {
+ case NGX_HTTP_OK:
+ case NGX_HTTP_MOVED_PERMANENTLY:
+ case NGX_HTTP_MOVED_TEMPORARILY:
+ break;
+
+#if 0
+ case NGX_HTTP_NOT_MODIFIED:
+ return 1;
+#endif
+
+ default:
+ return 0;
+ }
+
+ h = &p->upstream->headers_in;
+
+ date = NGX_ERROR;
+ if (h->date) {
+ date = ngx_http_parse_time(h->date->value.data, h->date->value.len);
+ }
+ if (date == NGX_ERROR) {
+ date = ngx_time();
+ }
+ p->cache->ctx.date = date;
+
+ last_modified = NGX_ERROR;
+ if (h->last_modified) {
+ last_modified = ngx_http_parse_time(h->last_modified->value.data,
+ h->last_modified->value.len);
+ p->cache->ctx.last_modified = last_modified;
+ }
+
+ if (h->x_accel_expires) {
+ expires = ngx_atoi(h->x_accel_expires->value.data,
+ h->x_accel_expires->value.len);
+ if (expires != NGX_ERROR) {
+ p->state->reason = NGX_HTTP_PROXY_CACHE_XAE;
+ p->state->expires = expires;
+ p->cache->ctx.expires = date + expires;
+ return (expires > 0);
+ }
+ }
+
+ if (!p->lcf->ignore_expires) {
+
+ /* TODO: Cache-Control: no-cache, max-age= */
+
+ if (h->expires) {
+ expires = ngx_http_parse_time(h->expires->value.data,
+ h->expires->value.len);
+ if (expires != NGX_ERROR) {
+ p->state->reason = NGX_HTTP_PROXY_CACHE_EXP;
+ p->state->expires = expires - date;
+ p->cache->ctx.expires = expires;
+ return (date < expires);
+ }
+ }
+ }
+
+ if (p->upstream->status == NGX_HTTP_MOVED_PERMANENTLY) {
+ p->state->reason = NGX_HTTP_PROXY_CACHE_MVD;
+ p->state->expires = /* STUB: 1 hour */ 60 * 60;
+ p->cache->ctx.expires = /* STUB: 1 hour */ 60 * 60;
+ return 1;
+ }
+
+ if (p->upstream->status == NGX_HTTP_MOVED_TEMPORARILY) {
+ return 1;
+ }
+
+ if (last_modified != NGX_ERROR && p->lcf->lm_factor > 0) {
+
+ /* FIXME: time_t == int_64_t, we can use fpu */
+
+ p->state->reason = NGX_HTTP_PROXY_CACHE_LMF;
+ t = (time_t)
+ ((((int64_t) (date - last_modified)) * p->lcf->lm_factor) / 100);
+ p->state->expires = t;
+ p->cache->ctx.expires = ngx_time() + t;
+ return 1;
+ }
+
+ if (p->lcf->default_expires > 0) {
+ p->state->reason = NGX_HTTP_PROXY_CACHE_PDE;
+ p->state->expires = p->lcf->default_expires;
+ p->cache->ctx.expires = ngx_time() + p->lcf->default_expires;
+ return 1;
+ }
+
+ return 0;
+}
+
+
+int ngx_http_proxy_update_cache(ngx_http_proxy_ctx_t *p)
+{
+ ngx_event_pipe_t *ep;
+
+ if (p->cache == NULL) {
+ return NGX_OK;
+ }
+
+ ep = p->upstream->event_pipe;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, p->request->connection->log, 0,
+ "http cache update len: " OFF_T_FMT ":" OFF_T_FMT,
+ p->cache->ctx.length, ep->read_length);
+
+ if (p->cache->ctx.length == -1) {
+ /* TODO: test rc */
+ ngx_write_file(&ep->temp_file->file,
+ (char *) &ep->read_length, sizeof(off_t),
+ offsetof(ngx_http_cache_header_t, length));
+ }
+
+ return ngx_http_cache_update_file(p->request, &p->cache->ctx,
+ &ep->temp_file->file.name);
+}
diff --git a/src/http/modules/proxy/ngx_http_proxy_handler.c b/src/http/modules/proxy/ngx_http_proxy_handler.c
new file mode 100644
index 000000000..3fa2e0bf3
--- /dev/null
+++ b/src/http/modules/proxy/ngx_http_proxy_handler.c
@@ -0,0 +1,1280 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_proxy_handler.h>
+
+
+static ngx_int_t ngx_http_proxy_handler(ngx_http_request_t *r);
+
+static u_char *ngx_http_proxy_log_proxy_state(ngx_http_request_t *r,
+ u_char *buf, uintptr_t data);
+static u_char *ngx_http_proxy_log_cache_state(ngx_http_request_t *r,
+ u_char *buf, uintptr_t data);
+static u_char *ngx_http_proxy_log_reason(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+
+static ngx_int_t ngx_http_proxy_pre_conf(ngx_conf_t *cf);
+static void *ngx_http_proxy_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+
+static char *ngx_http_proxy_set_pass(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_proxy_parse_upstream(ngx_str_t *url,
+ ngx_http_proxy_upstream_conf_t *u);
+
+
+static ngx_conf_bitmask_t next_upstream_masks[] = {
+ { ngx_string("error"), NGX_HTTP_PROXY_FT_ERROR },
+ { ngx_string("timeout"), NGX_HTTP_PROXY_FT_TIMEOUT },
+ { ngx_string("invalid_header"), NGX_HTTP_PROXY_FT_INVALID_HEADER },
+ { ngx_string("http_500"), NGX_HTTP_PROXY_FT_HTTP_500 },
+ { ngx_string("http_404"), NGX_HTTP_PROXY_FT_HTTP_404 },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_bitmask_t use_stale_masks[] = {
+ { ngx_string("error"), NGX_HTTP_PROXY_FT_ERROR },
+ { ngx_string("timeout"), NGX_HTTP_PROXY_FT_TIMEOUT },
+ { ngx_string("invalid_header"), NGX_HTTP_PROXY_FT_INVALID_HEADER },
+ { ngx_string("http_500"), NGX_HTTP_PROXY_FT_HTTP_500 },
+ { ngx_string("busy_lock"), NGX_HTTP_PROXY_FT_BUSY_LOCK },
+ { ngx_string("max_waiting"), NGX_HTTP_PROXY_FT_MAX_WAITING },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_num_bounds_t ngx_http_proxy_lm_factor_bounds = {
+ ngx_conf_check_num_bounds, 0, 100
+};
+
+
+static ngx_command_t ngx_http_proxy_commands[] = {
+
+ { ngx_string("proxy_pass"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_proxy_set_pass,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ 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, 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, send_timeout),
+ NULL },
+
+ { ngx_string("proxy_preserve_host"),
+ 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, preserve_host),
+ NULL },
+
+ { ngx_string("proxy_set_x_real_ip"),
+ 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, set_x_real_ip),
+ NULL },
+
+ { ngx_string("proxy_add_x_forwarded_for"),
+ 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, add_x_forwarded_for),
+ NULL },
+
+ { ngx_string("proxy_header_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, header_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, 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, 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, busy_buffers_size),
+ NULL },
+
+#if (NGX_HTTP_FILE_CACHE)
+
+ { ngx_string("proxy_cache_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, cache_path),
+ ngx_garbage_collector_http_cache_handler },
+
+#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, temp_path),
+ (void *) ngx_garbage_collector_temp_handler },
+
+ { 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, temp_file_write_size),
+ NULL },
+
+ { ngx_string("proxy_cache"),
+ 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, cache),
+ NULL },
+
+
+ { ngx_string("proxy_busy_lock"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE13,
+ ngx_http_set_busy_lock_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, busy_lock),
+ NULL },
+
+
+ { ngx_string("proxy_pass_server"),
+ 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, pass_server),
+ NULL },
+
+ { ngx_string("proxy_pass_x_accel_expires"),
+ 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, pass_x_accel_expires),
+ NULL },
+
+ { ngx_string("proxy_ignore_expires"),
+ 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, ignore_expires),
+ NULL },
+
+ { ngx_string("proxy_lm_factor"),
+ 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, lm_factor),
+ &ngx_http_proxy_lm_factor_bounds },
+
+ { ngx_string("proxy_default_expires"),
+ 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_proxy_loc_conf_t, default_expires),
+ NULL },
+
+
+ { ngx_string("proxy_next_upstream"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_ANY,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, next_upstream),
+ &next_upstream_masks },
+
+ { ngx_string("proxy_use_stale"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_ANY,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, use_stale),
+ &use_stale_masks },
+
+ ngx_null_command
+};
+
+
+ngx_http_module_t ngx_http_proxy_module_ctx = {
+ ngx_http_proxy_pre_conf, /* pre conf */
+
+ 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,
+ &ngx_http_proxy_module_ctx, /* module context */
+ ngx_http_proxy_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init module */
+ NULL /* init child */
+};
+
+
+
+static ngx_http_log_op_name_t ngx_http_proxy_log_fmt_ops[] = {
+ { ngx_string("proxy"), /* STUB */ 100,
+ ngx_http_proxy_log_proxy_state },
+ { ngx_string("proxy_cache_state"), sizeof("BYPASS") - 1,
+ ngx_http_proxy_log_cache_state },
+ { ngx_string("proxy_reason"), sizeof("BPS") - 1,
+ ngx_http_proxy_log_reason },
+ { ngx_null_string, 0, NULL }
+};
+
+
+
+ngx_http_header_t ngx_http_proxy_headers_in[] = {
+ { ngx_string("Date"), offsetof(ngx_http_proxy_headers_in_t, date) },
+ { ngx_string("Server"), offsetof(ngx_http_proxy_headers_in_t, server) },
+
+ { ngx_string("Expires"), offsetof(ngx_http_proxy_headers_in_t, expires) },
+ { ngx_string("Cache-Control"),
+ offsetof(ngx_http_proxy_headers_in_t, cache_control) },
+ { ngx_string("ETag"), offsetof(ngx_http_proxy_headers_in_t, etag) },
+ { ngx_string("X-Accel-Expires"),
+ offsetof(ngx_http_proxy_headers_in_t, x_accel_expires) },
+
+ { ngx_string("Connection"),
+ offsetof(ngx_http_proxy_headers_in_t, connection) },
+ { ngx_string("Content-Type"),
+ offsetof(ngx_http_proxy_headers_in_t, content_type) },
+ { ngx_string("Content-Length"),
+ offsetof(ngx_http_proxy_headers_in_t, content_length) },
+ { ngx_string("Last-Modified"),
+ offsetof(ngx_http_proxy_headers_in_t, last_modified) },
+ { ngx_string("Location"),
+ offsetof(ngx_http_proxy_headers_in_t, location) },
+ { ngx_string("Accept-Ranges"),
+ offsetof(ngx_http_proxy_headers_in_t, accept_ranges) },
+ { ngx_string("X-Pad"), offsetof(ngx_http_proxy_headers_in_t, x_pad) },
+
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_str_t cache_states[] = {
+ ngx_string("PASS"),
+ ngx_string("BYPASS"),
+ ngx_string("AUTH"),
+ ngx_string("PGNC"),
+ ngx_string("MISS"),
+ ngx_string("EXPR"),
+ ngx_string("AGED"),
+ ngx_string("HIT")
+};
+
+
+static ngx_str_t cache_reasons[] = {
+ ngx_string("BPS"),
+ ngx_string("XAE"),
+ ngx_string("CTL"),
+ ngx_string("EXP"),
+ ngx_string("MVD"),
+ ngx_string("LMF"),
+ ngx_string("PDE")
+};
+
+
+static ngx_int_t ngx_http_proxy_handler(ngx_http_request_t *r)
+{
+ ngx_http_proxy_ctx_t *p;
+
+ ngx_http_create_ctx(r, p, ngx_http_proxy_module,
+ sizeof(ngx_http_proxy_ctx_t),
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+ p->lcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);
+ p->request = r;
+
+ /* TODO: we currently support reverse proxy only */
+ p->accel = 1;
+
+ ngx_init_array(p->states, r->pool, p->lcf->peers->number,
+ sizeof(ngx_http_proxy_state_t),
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+ if (!(p->state = ngx_push_array(&p->states))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_memzero(p->state, sizeof(ngx_http_proxy_state_t));
+
+#if (NGX_HTTP_FILE_CACHE)
+
+ if (!p->lcf->cache
+ || (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_HEAD))
+ {
+ p->state->cache_state = NGX_HTTP_PROXY_CACHE_PASS;
+
+ } else if (r->bypass_cache) {
+ p->state->cache_state = NGX_HTTP_PROXY_CACHE_BYPASS;
+
+ } else if (r->headers_in.authorization) {
+ p->state->cache_state = NGX_HTTP_PROXY_CACHE_AUTH;
+
+ } else if (r->no_cache) {
+ p->state->cache_state = NGX_HTTP_PROXY_CACHE_PGNC;
+ p->cachable = 1;
+
+ } else {
+ p->cachable = 1;
+ }
+
+
+ if (p->state->cache_state != 0) {
+ return ngx_http_proxy_request_upstream(p);
+ }
+
+ return ngx_http_proxy_get_cached_response(p);
+
+#else
+
+ p->state->cache_state = NGX_HTTP_PROXY_CACHE_PASS;
+
+ return ngx_http_proxy_request_upstream(p);
+
+#endif
+}
+
+
+void ngx_http_proxy_check_broken_connection(ngx_event_t *ev)
+{
+ int n;
+ char buf[1];
+ ngx_err_t err;
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+ ngx_http_proxy_ctx_t *p;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+ "http proxy check client, write event:%d", ev->write);
+
+#if (HAVE_KQUEUE)
+
+ if (ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) {
+
+ if (!ev->pending_eof) {
+ return;
+ }
+
+ c = ev->data;
+ r = c->data;
+ p = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+ ev->eof = 1;
+
+ if (ev->kq_errno) {
+ ev->error = 1;
+ }
+
+ if (!p->cachable && p->upstream->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_proxy_finalize_request(p, 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 (p->upstream == NULL || p->upstream->peer.connection == NULL) {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ }
+
+ return;
+ }
+
+#endif
+
+ c = ev->data;
+ r = c->data;
+ p = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+ n = recv(c->fd, buf, 1, MSG_PEEK);
+
+ err = ngx_socket_errno;
+
+ /*
+ * we do not need to disable the write event because
+ * that event has NGX_USE_CLEAR_EVENT type
+ */
+
+ if (ev->write && (n >= 0 || err == NGX_EAGAIN)) {
+ return;
+ }
+
+ if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && ev->active) {
+ if (ngx_del_event(ev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ if (n > 0) {
+ return;
+ }
+
+ ev->eof = 1;
+
+ if (n == -1) {
+ if (err == NGX_EAGAIN) {
+ return;
+ }
+
+ ev->error = 1;
+
+ } else {
+ /* n == 0 */
+ err = 0;
+ }
+
+ if (!p->cachable && p->upstream->peer.connection) {
+ ngx_log_error(NGX_LOG_INFO, ev->log, err,
+ "client closed prematurely connection, "
+ "so upstream connection is closed too");
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ return;
+ }
+
+ ngx_log_error(NGX_LOG_INFO, ev->log, err,
+ "client closed prematurely connection");
+
+ if (p->upstream == NULL || p->upstream->peer.connection == NULL) {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ }
+}
+
+
+void ngx_http_proxy_busy_lock_handler(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+ ngx_http_proxy_ctx_t *p;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http proxy busy lock");
+
+ c = rev->data;
+ r = c->data;
+ p = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+ p->action = "waiting upstream in busy lock";
+
+ if (p->request->connection->write->eof) {
+ ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ return;
+ }
+
+ if (rev->timedout) {
+ rev->timedout = 0;
+ p->busy_lock.time++;
+ p->state->bl_time = p->busy_lock.time;
+
+#if (NGX_HTTP_FILE_CACHE)
+
+ if (p->state->cache_state < NGX_HTTP_PROXY_CACHE_MISS) {
+ ngx_http_proxy_upstream_busy_lock(p);
+
+ } else {
+ ngx_http_proxy_cache_busy_lock(p);
+ }
+#else
+
+ ngx_http_proxy_upstream_busy_lock(p);
+
+#endif
+
+ return;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+ "http proxy: client sent while busy lock");
+
+ /*
+ * TODO: kevent() notify about error, otherwise we need to
+ * call ngx_peek(): recv(MSG_PEEK) to get errno. THINK about aio.
+ * if there's no error we need to disable event.
+ */
+
+#if 0
+#if (HAVE_KQUEUE)
+
+ if ((ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) && rev->kq_eof) {
+ ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
+
+ ngx_del_timer(rev);
+
+ ngx_log_error(NGX_LOG_ERR, c->log, rev->kq_errno,
+ "client() closed connection");
+
+ if (ngx_del_event(rev, NGX_READ_EVENT, NGX_CLOSE_EVENT) == NGX_ERROR) {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ return;
+ }
+
+#endif
+#endif
+
+}
+
+
+void ngx_http_proxy_finalize_request(ngx_http_proxy_ctx_t *p, int rc)
+{
+ ngx_http_request_t *r;
+
+ r = p->request;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "finalize http proxy request");
+
+ if (p->upstream && p->upstream->peer.connection) {
+ ngx_http_proxy_close_connection(p);
+ }
+
+ if (p->header_sent
+ && (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE))
+ {
+ rc = 0;
+ }
+
+ if (p->saved_ctx) {
+ r->connection->log->data = p->saved_ctx;
+ r->connection->log->handler = p->saved_handler;
+ }
+
+ if (p->upstream && p->upstream->event_pipe) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http proxy temp fd: %d",
+ p->upstream->event_pipe->temp_file->file.fd);
+ }
+
+ if (p->cache) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http proxy cache fd: %d",
+ p->cache->ctx.file.fd);
+ }
+
+ if (p->upstream && p->upstream->event_pipe) {
+ r->file.fd = p->upstream->event_pipe->temp_file->file.fd;
+
+ } else if (p->cache) {
+ r->file.fd = p->cache->ctx.file.fd;
+ }
+
+ if (rc == 0 && r->main == NULL) {
+ rc = ngx_http_send_last(r);
+ }
+
+ ngx_http_finalize_request(r, rc);
+}
+
+
+void ngx_http_proxy_close_connection(ngx_http_proxy_ctx_t *p)
+{
+ ngx_socket_t fd;
+ ngx_connection_t *c;
+
+ c = p->upstream->peer.connection;
+ p->upstream->peer.connection = NULL;
+
+ if (p->lcf->busy_lock) {
+ p->lcf->busy_lock->busy--;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http proxy close connection: %d", c->fd);
+
+ if (c->fd == -1) {
+#if 0
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0, "connection already closed");
+#endif
+ return;
+ }
+
+ if (c->read->timer_set) {
+ ngx_del_timer(c->read);
+ }
+
+ if (c->write->timer_set) {
+ ngx_del_timer(c->write);
+ }
+
+ /* TODO: move connection to the connection pool */
+
+ if (ngx_del_conn) {
+ ngx_del_conn(c, NGX_CLOSE_EVENT);
+
+ } else {
+ if (c->read->active || c->read->disabled) {
+ ngx_del_event(c->read, NGX_READ_EVENT, NGX_CLOSE_EVENT);
+ }
+
+ if (c->write->active || c->read->disabled) {
+ ngx_del_event(c->write, NGX_WRITE_EVENT, NGX_CLOSE_EVENT);
+ }
+ }
+
+ /*
+ * we have to clean the connection information before the closing
+ * because another thread may reopen the same file descriptor
+ * before we clean the connection
+ */
+
+ if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_OK) {
+
+ if (c->read->prev) {
+ ngx_delete_posted_event(c->read);
+ }
+
+ if (c->write->prev) {
+ ngx_delete_posted_event(c->write);
+ }
+
+ c->read->closed = 1;
+ c->write->closed = 1;
+
+ ngx_mutex_unlock(ngx_posted_events_mutex);
+ }
+
+ fd = c->fd;
+ c->fd = (ngx_socket_t) -1;
+ c->data = NULL;
+
+ if (ngx_close_socket(fd) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno,
+ ngx_close_socket_n " failed");
+ }
+}
+
+
+size_t ngx_http_proxy_log_error(void *data, char *buf, size_t len)
+{
+ ngx_http_proxy_log_ctx_t *ctx = data;
+
+ ngx_http_request_t *r;
+ ngx_peer_connection_t *peer;
+
+ r = ctx->proxy->request;
+ peer = &ctx->proxy->upstream->peer;
+
+ return ngx_snprintf(buf, len,
+ " while %s, client: %s, URL: %s, upstream: %s%s%s%s%s",
+ ctx->proxy->action,
+ r->connection->addr_text.data,
+ r->unparsed_uri.data,
+ peer->peers->peers[peer->cur_peer].addr_port_text.data,
+ ctx->proxy->lcf->upstream->uri.data,
+ r->uri.data + ctx->proxy->lcf->upstream->location->len,
+ r->args.len ? "?" : "",
+ r->args.len ? r->args.data : (u_char *) "");
+}
+
+
+static u_char *ngx_http_proxy_log_proxy_state(ngx_http_request_t *r,
+ u_char *buf, uintptr_t data)
+{
+ ngx_http_proxy_ctx_t *p;
+
+ p = ngx_http_get_module_err_ctx(r, ngx_http_proxy_module);
+
+ if (p == NULL) {
+ *buf = '-';
+ return buf + 1;
+ }
+
+ if (p->state->cache_state == 0) {
+ *buf++ = '-';
+
+ } else {
+ buf = ngx_cpymem(buf, cache_states[p->state->cache_state - 1].data,
+ cache_states[p->state->cache_state - 1].len);
+ }
+
+ *buf++ = '/';
+
+ if (p->state->expired == 0) {
+ *buf++ = '-';
+
+ } else {
+ buf += ngx_snprintf((char *) buf, TIME_T_LEN,
+ TIME_T_FMT, p->state->expired);
+ }
+
+ *buf++ = '/';
+
+ if (p->state->bl_time == 0) {
+ *buf++ = '-';
+
+ } else {
+ buf += ngx_snprintf((char *) buf, TIME_T_LEN,
+ TIME_T_FMT, p->state->bl_time);
+ }
+
+ *buf++ = '/';
+
+ *buf++ = '*';
+
+ *buf++ = ' ';
+
+ if (p->state->status == 0) {
+ *buf++ = '-';
+
+ } else {
+ buf += ngx_snprintf((char *) buf, 4, "%" NGX_UINT_T_FMT,
+ p->state->status);
+ }
+
+ *buf++ = '/';
+
+ if (p->state->reason == 0) {
+ *buf++ = '-';
+
+ } else {
+ buf = ngx_cpymem(buf, cache_reasons[p->state->reason - 1].data,
+ cache_reasons[p->state->reason - 1].len);
+ }
+
+ *buf++ = '/';
+
+ if (p->state->reason < NGX_HTTP_PROXY_CACHE_XAE) {
+ *buf++ = '-';
+
+ } else {
+ buf += ngx_snprintf((char *) buf, TIME_T_LEN,
+ TIME_T_FMT, p->state->expires);
+ }
+
+ *buf++ = ' ';
+ *buf++ = '*';
+
+ return buf;
+}
+
+
+static u_char *ngx_http_proxy_log_cache_state(ngx_http_request_t *r,
+ u_char *buf, uintptr_t data)
+{
+ ngx_http_proxy_ctx_t *p;
+
+ p = ngx_http_get_module_err_ctx(r, ngx_http_proxy_module);
+
+ if (p == NULL || p->state->cache_state == 0) {
+ *buf = '-';
+ return buf + 1;
+ }
+
+ return ngx_cpymem(buf, cache_states[p->state->cache_state - 1].data,
+ cache_states[p->state->cache_state - 1].len);
+}
+
+
+static u_char *ngx_http_proxy_log_reason(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data)
+{
+ ngx_http_proxy_ctx_t *p;
+
+ p = ngx_http_get_module_err_ctx(r, ngx_http_proxy_module);
+
+ if (p == NULL || p->state->reason == 0) {
+ *buf = '-';
+ return buf + 1;
+ }
+
+ return ngx_cpymem(buf, cache_reasons[p->state->reason - 1].data,
+ cache_reasons[p->state->reason - 1].len);
+}
+
+
+static ngx_int_t ngx_http_proxy_pre_conf(ngx_conf_t *cf)
+{
+ ngx_http_log_op_name_t *op;
+
+ for (op = ngx_http_proxy_log_fmt_ops; op->name.len; op++) { /* void */ }
+ op->op = NULL;
+
+ op = ngx_http_log_fmt_ops;
+
+ for (op = ngx_http_log_fmt_ops; op->op; op++) {
+ if (op->name.len == 0) {
+ op = (ngx_http_log_op_name_t *) op->op;
+ }
+ }
+
+ op->op = (ngx_http_log_op_pt) ngx_http_proxy_log_fmt_ops;
+
+ return NGX_OK;
+}
+
+
+static void *ngx_http_proxy_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_proxy_loc_conf_t *conf;
+
+ ngx_test_null(conf,
+ ngx_pcalloc(cf->pool, sizeof(ngx_http_proxy_loc_conf_t)),
+ NGX_CONF_ERROR);
+
+ /* set by ngx_pcalloc():
+
+ conf->bufs.num = 0;
+
+ conf->path = NULL;
+
+ conf->next_upstream = 0;
+ conf->use_stale = 0;
+
+ conf->upstreams = NULL;
+ conf->peers = NULL;
+
+ conf->cache_path = NULL;
+ conf->temp_path = NULL;
+
+ conf->busy_lock = NULL;
+
+ */
+
+ conf->connect_timeout = NGX_CONF_UNSET_MSEC;
+ conf->send_timeout = NGX_CONF_UNSET_MSEC;
+
+ conf->preserve_host = NGX_CONF_UNSET;
+ conf->set_x_real_ip = NGX_CONF_UNSET;
+ conf->add_x_forwarded_for = NGX_CONF_UNSET;
+
+ conf->header_buffer_size = NGX_CONF_UNSET_SIZE;
+ conf->read_timeout = NGX_CONF_UNSET_MSEC;
+ conf->busy_buffers_size = NGX_CONF_UNSET_SIZE;
+
+ /*
+ * "proxy_max_temp_file_size" is hardcoded to 1G for reverse proxy,
+ * it should be configurable in the generic proxy
+ */
+ conf->max_temp_file_size = 1024 * 1024 * 1024;
+
+ conf->temp_file_write_size = NGX_CONF_UNSET_SIZE;
+
+ /* "proxy_cyclic_temp_file" is disabled */
+ conf->cyclic_temp_file = 0;
+
+ conf->cache = NGX_CONF_UNSET;
+
+ conf->pass_server = NGX_CONF_UNSET;
+ conf->pass_x_accel_expires = NGX_CONF_UNSET;
+ conf->ignore_expires = NGX_CONF_UNSET;
+ conf->lm_factor = NGX_CONF_UNSET;
+ conf->default_expires = NGX_CONF_UNSET;
+
+ 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;
+
+ size_t size;
+
+ ngx_conf_merge_msec_value(conf->connect_timeout,
+ prev->connect_timeout, 60000);
+ ngx_conf_merge_msec_value(conf->send_timeout, prev->send_timeout, 60000);
+
+ ngx_conf_merge_value(conf->preserve_host, prev->preserve_host, 0);
+ ngx_conf_merge_value(conf->set_x_real_ip, prev->set_x_real_ip, 0);
+ ngx_conf_merge_value(conf->add_x_forwarded_for,
+ prev->add_x_forwarded_for, 0);
+
+ ngx_conf_merge_msec_value(conf->read_timeout, prev->read_timeout, 60000);
+
+ ngx_conf_merge_size_value(conf->header_buffer_size,
+ prev->header_buffer_size, (size_t) ngx_pagesize);
+
+ ngx_conf_merge_bufs_value(conf->bufs, prev->bufs, 8, ngx_pagesize);
+
+ if (conf->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->header_buffer_size;
+ if (size < conf->bufs.size) {
+ size = conf->bufs.size;
+ }
+
+
+ ngx_conf_merge_size_value(conf->busy_buffers_size,
+ prev->busy_buffers_size, NGX_CONF_UNSET_SIZE);
+
+ if (conf->busy_buffers_size == NGX_CONF_UNSET_SIZE) {
+ conf->busy_buffers_size = 2 * size;
+
+ } else if (conf->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_header_buffer_size\" and "
+ "one of the \"proxy_buffers\"");
+
+ return NGX_CONF_ERROR;
+
+ } else if (conf->busy_buffers_size > (conf->bufs.num - 1) * conf->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->temp_file_write_size,
+ prev->temp_file_write_size, NGX_CONF_UNSET_SIZE);
+
+ if (conf->temp_file_write_size == NGX_CONF_UNSET_SIZE) {
+ conf->temp_file_write_size = 2 * size;
+
+ } else if (conf->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_header_buffer_size\" and "
+ "one of the \"proxy_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+
+ ngx_conf_merge_size_value(conf->max_temp_file_size,
+ prev->max_temp_file_size, NGX_CONF_UNSET_SIZE);
+
+ if (conf->max_temp_file_size == NGX_CONF_UNSET_SIZE) {
+ conf->max_temp_file_size = 2 * size;
+
+ } else if (conf->max_temp_file_size < size) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"proxy_max_temp_file_size\" must be equal or bigger than "
+ "maximum of the value of \"proxy_header_buffer_size\" and "
+ "one of the \"proxy_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+
+ ngx_conf_merge_bitmask_value(conf->next_upstream, prev->next_upstream,
+ (NGX_CONF_BITMASK_SET
+ |NGX_HTTP_PROXY_FT_ERROR
+ |NGX_HTTP_PROXY_FT_TIMEOUT));
+
+ ngx_conf_merge_bitmask_value(conf->use_stale, prev->use_stale,
+ NGX_CONF_BITMASK_SET);
+
+ ngx_conf_merge_path_value(conf->cache_path, prev->cache_path,
+ "cache", 1, 2, 0, cf->pool);
+
+ ngx_conf_merge_path_value(conf->temp_path, prev->temp_path,
+ "temp", 1, 2, 0, cf->pool);
+
+ ngx_conf_merge_value(conf->cache, prev->cache, 0);
+
+
+ /* conf->cache must be merged */
+
+ if (conf->busy_lock == NULL) {
+ conf->busy_lock = prev->busy_lock;
+ }
+
+ if (conf->busy_lock && conf->cache && conf->busy_lock->md5 == NULL) {
+
+ /* ngx_calloc_shared() */
+ conf->busy_lock->md5_mask =
+ ngx_pcalloc(cf->pool, (conf->busy_lock->max_busy + 7) / 8);
+ if (conf->busy_lock->md5_mask == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ /* 16 bytes are 128 bits of the md5 */
+
+ /* ngx_alloc_shared() */
+ conf->busy_lock->md5 = ngx_palloc(cf->pool,
+ 16 * conf->busy_lock->max_busy);
+ if (conf->busy_lock->md5 == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+
+ ngx_conf_merge_value(conf->pass_server, prev->pass_server, 0);
+ ngx_conf_merge_value(conf->pass_x_accel_expires,
+ prev->pass_x_accel_expires, 0);
+ ngx_conf_merge_value(conf->ignore_expires, prev->ignore_expires, 0);
+ ngx_conf_merge_value(conf->lm_factor, prev->lm_factor, 0);
+ ngx_conf_merge_sec_value(conf->default_expires, prev->default_expires, 0);
+
+ return NULL;
+}
+
+
+
+static char *ngx_http_proxy_set_pass(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf)
+{
+ ngx_http_proxy_loc_conf_t *lcf = conf;
+
+ ngx_uint_t i, len;
+ char *err;
+ u_char *host;
+ in_addr_t addr;
+ ngx_str_t *value;
+ struct hostent *h;
+ ngx_http_core_loc_conf_t *clcf;
+
+
+ value = cf->args->elts;
+
+ if (ngx_strncasecmp(value[1].data, "http://", 7) != 0) {
+ return "invalid URL prefix";
+ }
+
+ ngx_test_null(lcf->upstream,
+ ngx_pcalloc(cf->pool, sizeof(ngx_http_proxy_upstream_conf_t)),
+ NGX_CONF_ERROR);
+
+ lcf->upstream->url.len = value[1].len;
+ if (!(lcf->upstream->url.data = ngx_palloc(cf->pool, value[1].len + 1))) {
+ return NGX_CONF_ERROR;
+ }
+ ngx_cpystrn(lcf->upstream->url.data, value[1].data, value[1].len + 1);
+
+ value[1].data += 7;
+ value[1].len -= 7;
+
+ err = ngx_http_proxy_parse_upstream(&value[1], lcf->upstream);
+
+ if (err) {
+ return err;
+ }
+
+ ngx_test_null(host, ngx_palloc(cf->pool, lcf->upstream->host.len + 1),
+ NGX_CONF_ERROR);
+ ngx_cpystrn(host, lcf->upstream->host.data, lcf->upstream->host.len + 1);
+
+ /* AF_INET only */
+
+ addr = inet_addr((char *) host);
+
+ if (addr == INADDR_NONE) {
+ h = gethostbyname((char *) host);
+
+ if (h == NULL || h->h_addr_list[0] == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "host %s not found", host);
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 0; h->h_addr_list[i] != NULL; i++) { /* void */ }
+
+ /* MP: ngx_shared_palloc() */
+
+ ngx_test_null(lcf->peers,
+ ngx_pcalloc(cf->pool,
+ sizeof(ngx_peers_t)
+ + sizeof(ngx_peer_t) * (i - 1)),
+ NGX_CONF_ERROR);
+
+ lcf->peers->number = i;
+
+ for (i = 0; h->h_addr_list[i] != NULL; i++) {
+ lcf->peers->peers[i].host.data = host;
+ lcf->peers->peers[i].host.len = lcf->upstream->host.len;
+ lcf->peers->peers[i].addr = *(in_addr_t *)(h->h_addr_list[i]);
+ lcf->peers->peers[i].port = lcf->upstream->port;
+
+ len = INET_ADDRSTRLEN + lcf->upstream->port_text.len + 1;
+ ngx_test_null(lcf->peers->peers[i].addr_port_text.data,
+ ngx_palloc(cf->pool, len),
+ NGX_CONF_ERROR);
+
+ len = ngx_inet_ntop(AF_INET,
+ &lcf->peers->peers[i].addr,
+ lcf->peers->peers[i].addr_port_text.data,
+ len);
+
+ lcf->peers->peers[i].addr_port_text.data[len++] = ':';
+
+ ngx_cpystrn(lcf->peers->peers[i].addr_port_text.data + len,
+ lcf->upstream->port_text.data,
+ lcf->upstream->port_text.len + 1);
+
+ lcf->peers->peers[i].addr_port_text.len =
+ len + lcf->upstream->port_text.len + 1;
+ }
+
+ } else {
+
+ /* MP: ngx_shared_palloc() */
+
+ ngx_test_null(lcf->peers, ngx_pcalloc(cf->pool, sizeof(ngx_peers_t)),
+ NGX_CONF_ERROR);
+
+ lcf->peers->number = 1;
+
+ lcf->peers->peers[0].host.data = host;
+ lcf->peers->peers[0].host.len = lcf->upstream->host.len;
+ lcf->peers->peers[0].addr = addr;
+ lcf->peers->peers[0].port = lcf->upstream->port;
+
+ len = lcf->upstream->host.len + lcf->upstream->port_text.len + 1;
+
+ ngx_test_null(lcf->peers->peers[0].addr_port_text.data,
+ ngx_palloc(cf->pool, len + 1),
+ NGX_CONF_ERROR);
+
+ len = lcf->upstream->host.len;
+
+ ngx_memcpy(lcf->peers->peers[0].addr_port_text.data,
+ lcf->upstream->host.data, len);
+
+ lcf->peers->peers[0].addr_port_text.data[len++] = ':';
+
+ ngx_cpystrn(lcf->peers->peers[0].addr_port_text.data + len,
+ lcf->upstream->port_text.data,
+ lcf->upstream->port_text.len + 1);
+ }
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+ lcf->upstream->location = &clcf->name;
+ clcf->handler = ngx_http_proxy_handler;
+
+ if (clcf->name.data[clcf->name.len - 1] == '/') {
+ clcf->auto_redirect = 1;
+ }
+
+ return NULL;
+}
+
+
+static char *ngx_http_proxy_parse_upstream(ngx_str_t *url,
+ ngx_http_proxy_upstream_conf_t *u)
+{
+ size_t i;
+
+ if (url->data[0] == ':' || url->data[0] == '/') {
+ return "invalid upstream URL";
+ }
+
+ u->host.data = url->data;
+ u->host_header.data = url->data;
+
+ for (i = 1; i < url->len; i++) {
+ if (url->data[i] == ':') {
+ u->port_text.data = &url->data[i] + 1;
+ u->host.len = i;
+ }
+
+ if (url->data[i] == '/') {
+ u->uri.data = &url->data[i];
+ u->uri.len = url->len - i;
+ u->host_header.len = i;
+
+ if (u->host.len == 0) {
+ u->host.len = i;
+ }
+
+ if (u->port_text.data == NULL) {
+ u->default_port = 1;
+ u->port = htons(80);
+ u->port_text.len = 2;
+ u->port_text.data = (u_char *) "80";
+ return NULL;
+ }
+
+ u->port_text.len = &url->data[i] - u->port_text.data;
+
+ if (u->port_text.len > 0) {
+ u->port = (in_port_t) ngx_atoi(u->port_text.data,
+ u->port_text.len);
+ if (u->port > 0) {
+
+ if (u->port == 80) {
+ u->default_port = 1;
+ }
+
+ u->port = htons(u->port);
+ return NULL;
+ }
+ }
+
+ return "invalid port in upstream URL";
+ }
+ }
+
+ if (u->host.len == 0) {
+ u->host.len = i;
+ }
+
+ u->host_header.len = i;
+
+ u->uri.data = (u_char *) "/";
+ u->uri.len = 1;
+
+ if (u->port_text.data == NULL) {
+ u->default_port = 1;
+ u->port = htons(80);
+ u->port_text.len = 2;
+ u->port_text.data = (u_char *) "80";
+ return NULL;
+ }
+
+ u->port_text.len = &url->data[i] - u->port_text.data;
+
+ if (u->port_text.len > 0) {
+ u->port = (in_port_t) ngx_atoi(u->port_text.data, u->port_text.len);
+ if (u->port > 0) {
+ u->port = htons(u->port);
+ return NULL;
+ }
+ }
+
+ return "invalid port in upstream URL";
+}
diff --git a/src/http/modules/proxy/ngx_http_proxy_handler.h b/src/http/modules/proxy/ngx_http_proxy_handler.h
new file mode 100644
index 000000000..4dcc65387
--- /dev/null
+++ b/src/http/modules/proxy/ngx_http_proxy_handler.h
@@ -0,0 +1,260 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_PROXY_HANDLER_H_INCLUDED_
+#define _NGX_HTTP_PROXY_HANDLER_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>
+
+
+typedef enum {
+ NGX_HTTP_PROXY_CACHE_PASS = 1,
+ NGX_HTTP_PROXY_CACHE_BYPASS,
+ NGX_HTTP_PROXY_CACHE_AUTH,
+ NGX_HTTP_PROXY_CACHE_PGNC,
+ NGX_HTTP_PROXY_CACHE_MISS,
+ NGX_HTTP_PROXY_CACHE_EXPR,
+ NGX_HTTP_PROXY_CACHE_AGED,
+ NGX_HTTP_PROXY_CACHE_HIT
+} ngx_http_proxy_state_e;
+
+
+typedef enum {
+ NGX_HTTP_PROXY_CACHE_BPS = 1,
+ NGX_HTTP_PROXY_CACHE_XAE,
+ NGX_HTTP_PROXY_CACHE_CTL,
+ NGX_HTTP_PROXY_CACHE_EXP,
+ NGX_HTTP_PROXY_CACHE_MVD,
+ NGX_HTTP_PROXY_CACHE_LMF,
+ NGX_HTTP_PROXY_CACHE_PDE
+} ngx_http_proxy_reason_e;
+
+
+typedef struct {
+ ngx_str_t url;
+ ngx_str_t host;
+ ngx_str_t uri;
+ ngx_str_t host_header;
+ ngx_str_t port_text;
+ ngx_str_t *location;
+
+ in_port_t port;
+
+ unsigned default_port:1;
+} ngx_http_proxy_upstream_conf_t;
+
+
+typedef struct {
+ size_t header_buffer_size;
+ size_t busy_buffers_size;
+ size_t max_temp_file_size;
+ size_t temp_file_write_size;
+
+ ngx_msec_t connect_timeout;
+ ngx_msec_t send_timeout;
+ ngx_msec_t read_timeout;
+ time_t default_expires;
+
+ ngx_int_t lm_factor;
+
+ ngx_uint_t next_upstream;
+ ngx_uint_t use_stale;
+
+ ngx_bufs_t bufs;
+
+ ngx_flag_t cyclic_temp_file;
+ ngx_flag_t cache;
+ ngx_flag_t preserve_host;
+ ngx_flag_t set_x_real_ip;
+ ngx_flag_t add_x_forwarded_for;
+ ngx_flag_t pass_server;
+ ngx_flag_t pass_x_accel_expires;
+ ngx_flag_t ignore_expires;
+
+ ngx_path_t *cache_path;
+ ngx_path_t *temp_path;
+
+ ngx_http_busy_lock_t *busy_lock;
+
+ ngx_http_proxy_upstream_conf_t *upstream;
+ ngx_peers_t *peers;
+} ngx_http_proxy_loc_conf_t;
+
+
+/*
+ * "EXPR/10/5/- 200/EXP/60 4"
+ * "MISS/-/-/B 503/-/- -"
+ * "EXPR/10/20/SB HIT/-/- -"
+ * "EXPR/10/15/NB HIT/-/- -"
+ */
+
+typedef struct {
+ ngx_http_proxy_state_e cache_state;
+ time_t expired;
+ time_t bl_time;
+ ngx_uint_t bl_state;
+
+ ngx_uint_t status;
+ ngx_http_proxy_reason_e reason;
+ time_t time;
+ time_t expires;
+
+ ngx_str_t *peer;
+} ngx_http_proxy_state_t;
+
+
+typedef struct {
+ ngx_list_t headers;
+#if 0
+ ngx_table_t headers; /* it must be first field */
+#endif
+
+ ngx_table_elt_t *date;
+ ngx_table_elt_t *server;
+
+ ngx_table_elt_t *expires;
+ ngx_table_elt_t *cache_control;
+ ngx_table_elt_t *etag;
+ ngx_table_elt_t *x_accel_expires;
+
+ ngx_table_elt_t *connection;
+ 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 *x_pad;
+
+ off_t content_length_n;
+} ngx_http_proxy_headers_in_t;
+
+
+typedef struct {
+ ngx_http_cache_ctx_t ctx;
+ ngx_uint_t status;
+ ngx_str_t status_line;
+
+ ngx_http_proxy_headers_in_t headers_in;
+} ngx_http_proxy_cache_t;
+
+
+typedef struct {
+ ngx_peer_connection_t peer;
+ ngx_uint_t status;
+ ngx_str_t status_line;
+ ngx_uint_t method;
+
+ ngx_output_chain_ctx_t *output_chain_ctx;
+ ngx_event_pipe_t *event_pipe;
+
+ ngx_http_proxy_headers_in_t headers_in;
+} ngx_http_proxy_upstream_t;
+
+
+typedef struct ngx_http_proxy_ctx_s ngx_http_proxy_ctx_t;
+
+struct ngx_http_proxy_ctx_s {
+ ngx_http_request_t *request;
+ ngx_http_proxy_loc_conf_t *lcf;
+ ngx_http_proxy_upstream_t *upstream;
+ ngx_http_proxy_cache_t *cache;
+
+ ngx_buf_t *header_in;
+
+ ngx_http_busy_lock_ctx_t busy_lock;
+
+ unsigned accel:1;
+
+ unsigned cachable:1;
+ unsigned stale:1;
+ unsigned try_busy_lock:1;
+ unsigned busy_locked:1;
+ unsigned valid_header_in:1;
+
+ unsigned request_sent:1;
+ unsigned header_sent:1;
+
+
+ /* used to parse an upstream HTTP header */
+ ngx_uint_t status;
+ u_char *status_start;
+ u_char *status_end;
+ ngx_uint_t status_count;
+ ngx_uint_t parse_state;
+
+ ngx_http_proxy_state_t *state;
+ ngx_array_t states; /* of ngx_http_proxy_state_t */
+
+ /*
+ * we declare "action" as "char *" because the actions are usually
+ * the static strings and in the "u_char *" case we have to override
+ * all the time their types
+ */
+
+ char *action;
+ ngx_http_log_ctx_t *saved_ctx;
+ ngx_log_handler_pt saved_handler;
+};
+
+
+typedef struct {
+ ngx_uint_t connection;
+ ngx_http_proxy_ctx_t *proxy;
+} ngx_http_proxy_log_ctx_t;
+
+
+#define NGX_HTTP_PROXY_PARSE_NO_HEADER 30
+
+
+#define NGX_HTTP_PROXY_FT_ERROR 0x02
+#define NGX_HTTP_PROXY_FT_TIMEOUT 0x04
+#define NGX_HTTP_PROXY_FT_INVALID_HEADER 0x08
+#define NGX_HTTP_PROXY_FT_HTTP_500 0x10
+#define NGX_HTTP_PROXY_FT_HTTP_404 0x20
+#define NGX_HTTP_PROXY_FT_BUSY_LOCK 0x40
+#define NGX_HTTP_PROXY_FT_MAX_WAITING 0x80
+
+
+int ngx_http_proxy_request_upstream(ngx_http_proxy_ctx_t *p);
+
+#if (NGX_HTTP_FILE_CACHE)
+
+int ngx_http_proxy_get_cached_response(ngx_http_proxy_ctx_t *p);
+int ngx_http_proxy_send_cached_response(ngx_http_proxy_ctx_t *p);
+int ngx_http_proxy_is_cachable(ngx_http_proxy_ctx_t *p);
+int ngx_http_proxy_update_cache(ngx_http_proxy_ctx_t *p);
+
+void ngx_http_proxy_cache_busy_lock(ngx_http_proxy_ctx_t *p);
+
+#endif
+
+void ngx_http_proxy_check_broken_connection(ngx_event_t *ev);
+
+void ngx_http_proxy_busy_lock_handler(ngx_event_t *rev);
+void ngx_http_proxy_upstream_busy_lock(ngx_http_proxy_ctx_t *p);
+
+size_t ngx_http_proxy_log_error(void *data, char *buf, size_t len);
+void ngx_http_proxy_finalize_request(ngx_http_proxy_ctx_t *p, int rc);
+void ngx_http_proxy_close_connection(ngx_http_proxy_ctx_t *p);
+
+int ngx_http_proxy_parse_status_line(ngx_http_proxy_ctx_t *p);
+int ngx_http_proxy_copy_header(ngx_http_proxy_ctx_t *p,
+ ngx_http_proxy_headers_in_t *headers_in);
+
+
+
+extern ngx_module_t ngx_http_proxy_module;
+extern ngx_http_header_t ngx_http_proxy_headers_in[];
+
+
+
+#endif /* _NGX_HTTP_PROXY_HANDLER_H_INCLUDED_ */
diff --git a/src/http/modules/proxy/ngx_http_proxy_header.c b/src/http/modules/proxy/ngx_http_proxy_header.c
new file mode 100644
index 000000000..038001240
--- /dev/null
+++ b/src/http/modules/proxy/ngx_http_proxy_header.c
@@ -0,0 +1,198 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_proxy_handler.h>
+
+
+static int ngx_http_proxy_rewrite_location_header(ngx_http_proxy_ctx_t *p,
+ ngx_table_elt_t *loc);
+
+int ngx_http_proxy_copy_header(ngx_http_proxy_ctx_t *p,
+ ngx_http_proxy_headers_in_t *headers_in)
+{
+ ngx_uint_t i;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *ho, *h;
+ ngx_http_request_t *r;
+
+ r = p->request;
+
+ part = &headers_in->headers.part;
+ h = part->elts;
+
+#if 0
+ h = headers_in->headers.elts;
+ for (i = 0; i < headers_in->headers.nelts; i++) {
+#endif
+
+ for (i = 0 ; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ h = part->elts;
+ i = 0;
+ }
+
+ /* ignore some headers */
+
+ if (&h[i] == headers_in->connection) {
+ continue;
+ }
+
+ if (&h[i] == headers_in->x_pad) {
+ continue;
+ }
+
+ if (p->accel) {
+ if (&h[i] == headers_in->date
+ || &h[i] == headers_in->accept_ranges) {
+ continue;
+ }
+
+ if (&h[i] == headers_in->x_accel_expires
+ && !p->lcf->pass_x_accel_expires)
+ {
+ continue;
+ }
+
+ if (&h[i] == headers_in->server && !p->lcf->pass_server) {
+ continue;
+ }
+
+ if (&h[i] == headers_in->location) {
+ if (ngx_http_proxy_rewrite_location_header(p, &h[i])
+ == NGX_ERROR)
+ {
+ return NGX_ERROR;
+ }
+
+ continue;
+ }
+ }
+
+
+ /* "Content-Type" is handled specially */
+
+ if (&h[i] == headers_in->content_type) {
+ r->headers_out.content_type = &h[i];
+ r->headers_out.content_type->key.len = 0;
+ continue;
+ }
+
+
+ /* copy some header pointers and set up r->headers_out */
+
+ if (!(ho = ngx_list_push(&r->headers_out.headers))) {
+ return NGX_ERROR;
+ }
+
+ *ho = h[i];
+
+ if (&h[i] == headers_in->expires) {
+ r->headers_out.expires = ho;
+ continue;
+ }
+
+ if (&h[i] == headers_in->cache_control) {
+ r->headers_out.cache_control = ho;
+ continue;
+ }
+
+ if (&h[i] == headers_in->etag) {
+ r->headers_out.etag = ho;
+ continue;
+ }
+
+ if (&h[i] == headers_in->last_modified) {
+ r->headers_out.last_modified = ho;
+ /* TODO: update r->headers_out.last_modified_time */
+ continue;
+ }
+
+ /*
+ * ngx_http_header_filter() passes the following headers as is
+ * and does not handle them specially if they are set:
+ * r->headers_out.server,
+ * r->headers_out.date,
+ * r->headers_out.content_length
+ */
+
+ if (&h[i] == headers_in->server) {
+ r->headers_out.server = ho;
+ continue;
+ }
+
+ if (&h[i] == headers_in->date) {
+ r->headers_out.date = ho;
+ continue;
+ }
+
+ if (&h[i] == headers_in->content_length) {
+ r->headers_out.content_length = ho;
+ r->headers_out.content_length_n = ngx_atoi(ho->value.data,
+ ho->value.len);
+ continue;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static int ngx_http_proxy_rewrite_location_header(ngx_http_proxy_ctx_t *p,
+ ngx_table_elt_t *loc)
+{
+ u_char *last;
+ ngx_table_elt_t *location;
+ ngx_http_request_t *r;
+ ngx_http_proxy_upstream_conf_t *uc;
+
+ r = p->request;
+ uc = p->lcf->upstream;
+
+ if (!(location = ngx_list_push(&r->headers_out.headers))) {
+ return NGX_ERROR;
+ }
+
+ /*
+ * we do not set r->headers_out.location to avoid the handling
+ * the local redirects without a host name by ngx_http_header_filter()
+ */
+
+#if 0
+ r->headers_out.location = location;
+#endif
+
+ if (uc->url.len > loc->value.len
+ || ngx_rstrncmp(loc->value.data, uc->url.data, uc->url.len) != 0)
+ {
+ *location = *loc;
+ return NGX_OK;
+ }
+
+ /* TODO: proxy_reverse */
+
+ location->value.len = uc->location->len
+ + (loc->value.len - uc->url.len) + 1;
+ if (!(location->value.data = ngx_palloc(r->pool, location->value.len))) {
+ return NGX_ERROR;
+ }
+
+ last = ngx_cpymem(location->value.data,
+ uc->location->data, uc->location->len);
+
+ ngx_cpystrn(last, loc->value.data + uc->url.len,
+ loc->value.len - uc->url.len + 1);
+
+ return NGX_OK;
+}
diff --git a/src/http/modules/proxy/ngx_http_proxy_parse.c b/src/http/modules/proxy/ngx_http_proxy_parse.c
new file mode 100644
index 000000000..3718ab050
--- /dev/null
+++ b/src/http/modules/proxy/ngx_http_proxy_parse.c
@@ -0,0 +1,213 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_proxy_handler.h>
+
+
+int ngx_http_proxy_parse_status_line(ngx_http_proxy_ctx_t *p)
+{
+ u_char ch;
+ u_char *pos;
+ 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,
+ sw_done
+ } state;
+
+ state = p->parse_state;
+ pos = p->header_in->pos;
+
+ while (pos < p->header_in->last && state < sw_done) {
+ ch = *pos++;
+
+ switch (state) {
+
+ /* "HTTP/" */
+ case sw_start:
+ switch (ch) {
+ case 'H':
+ state = sw_H;
+ break;
+ default:
+ return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+ }
+ break;
+
+ case sw_H:
+ switch (ch) {
+ case 'T':
+ state = sw_HT;
+ break;
+ default:
+ return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+ }
+ break;
+
+ case sw_HT:
+ switch (ch) {
+ case 'T':
+ state = sw_HTT;
+ break;
+ default:
+ return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+ }
+ break;
+
+ case sw_HTT:
+ switch (ch) {
+ case 'P':
+ state = sw_HTTP;
+ break;
+ default:
+ return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+ }
+ break;
+
+ case sw_HTTP:
+ switch (ch) {
+ case '/':
+ state = sw_first_major_digit;
+ break;
+ default:
+ return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+ }
+ break;
+
+ /* the first digit of major HTTP version */
+ case sw_first_major_digit:
+ if (ch < '1' || ch > '9') {
+ return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+ }
+
+ 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_HTTP_PROXY_PARSE_NO_HEADER;
+ }
+
+ break;
+
+ /* the first digit of minor HTTP version */
+ case sw_first_minor_digit:
+ if (ch < '0' || ch > '9') {
+ return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+ }
+
+ 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_HTTP_PROXY_PARSE_NO_HEADER;
+ }
+
+ break;
+
+ /* HTTP status code */
+ case sw_status:
+ if (ch < '0' || ch > '9') {
+ return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+ }
+
+ p->status = p->status * 10 + ch - '0';
+
+ if (++p->status_count == 3) {
+ state = sw_space_after_status;
+ p->status_start = pos - 3;
+ }
+
+ break;
+
+ /* space or end of line */
+ case sw_space_after_status:
+ switch (ch) {
+ case ' ':
+ state = sw_status_text;
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ state = sw_done;
+ break;
+ default:
+ return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+ }
+ break;
+
+ /* any text until end of line */
+ case sw_status_text:
+ switch (ch) {
+ case CR:
+ state = sw_almost_done;
+
+ break;
+ case LF:
+ state = sw_done;
+ break;
+ }
+ break;
+
+ /* end of request line */
+ case sw_almost_done:
+ p->status_end = pos - 2;
+ switch (ch) {
+ case LF:
+ state = sw_done;
+ break;
+ default:
+ return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+ }
+ break;
+
+ /* suppress warning */
+ case sw_done:
+ break;
+ }
+ }
+
+ p->header_in->pos = pos;
+
+ if (state == sw_done) {
+ if (p->status_end == NULL) {
+ p->status_end = pos - 1;
+ }
+
+ p->parse_state = sw_start;
+ return NGX_OK;
+ }
+
+ p->parse_state = state;
+ return NGX_AGAIN;
+}
diff --git a/src/http/modules/proxy/ngx_http_proxy_upstream.c b/src/http/modules/proxy/ngx_http_proxy_upstream.c
new file mode 100644
index 000000000..c1a8fb621
--- /dev/null
+++ b/src/http/modules/proxy/ngx_http_proxy_upstream.c
@@ -0,0 +1,1492 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#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_http_proxy_handler.h>
+
+
+static ngx_chain_t *ngx_http_proxy_create_request(ngx_http_proxy_ctx_t *p);
+static void ngx_http_proxy_init_upstream(void *data);
+static void ngx_http_proxy_reinit_upstream(ngx_http_proxy_ctx_t *p);
+static void ngx_http_proxy_connect(ngx_http_proxy_ctx_t *p);
+static void ngx_http_proxy_send_request(ngx_http_proxy_ctx_t *p);
+static void ngx_http_proxy_send_request_handler(ngx_event_t *wev);
+static void ngx_http_proxy_dummy_handler(ngx_event_t *wev);
+static void ngx_http_proxy_process_upstream_status_line(ngx_event_t *rev);
+static void ngx_http_proxy_process_upstream_headers(ngx_event_t *rev);
+static ssize_t ngx_http_proxy_read_upstream_header(ngx_http_proxy_ctx_t *);
+static void ngx_http_proxy_send_response(ngx_http_proxy_ctx_t *p);
+static void ngx_http_proxy_process_body(ngx_event_t *ev);
+static void ngx_http_proxy_next_upstream(ngx_http_proxy_ctx_t *p, int ft_type);
+
+
+static ngx_str_t http_methods[] = {
+ ngx_string("GET "),
+ ngx_string("HEAD "),
+ ngx_string("POST ")
+};
+
+
+static char *upstream_header_errors[] = {
+ "upstream sent invalid header",
+ "upstream sent too long header line"
+};
+
+
+static char http_version[] = " HTTP/1.0" CRLF;
+static char host_header[] = "Host: ";
+static char x_real_ip_header[] = "X-Real-IP: ";
+static char x_forwarded_for_header[] = "X-Forwarded-For: ";
+static char connection_close_header[] = "Connection: close" CRLF;
+
+
+int ngx_http_proxy_request_upstream(ngx_http_proxy_ctx_t *p)
+{
+ int rc;
+ ngx_temp_file_t *tf;
+ ngx_http_request_t *r;
+ ngx_http_request_body_t *rb;
+ ngx_http_proxy_upstream_t *u;
+
+ r = p->request;
+
+ if (!(u = ngx_pcalloc(r->pool, sizeof(ngx_http_proxy_upstream_t)))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ p->upstream = u;
+
+ u->peer.log_error = NGX_ERROR_ERR;
+ u->peer.peers = p->lcf->peers;
+ u->peer.tries = p->lcf->peers->number;
+#if (NGX_THREADS)
+ u->peer.lock = &r->connection->lock;
+#endif
+
+ u->method = r->method;
+
+ if (!(rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ r->request_body = rb;
+
+ if (r->headers_in.content_length_n <= 0) {
+ ngx_http_proxy_init_upstream(p);
+ return NGX_DONE;
+ }
+
+ if (!(tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ tf->file.fd = NGX_INVALID_FILE;
+ tf->file.log = r->connection->log;
+ tf->path = p->lcf->temp_path;
+ tf->pool = r->pool;
+ tf->warn = "a client request body is buffered to a temporary file";
+ /* tf->persistent = 0; */
+
+ rb->handler = ngx_http_proxy_init_upstream;
+ rb->data = p;
+ /* rb->bufs = NULL; */
+ /* rb->buf = NULL; */
+ /* rb->rest = 0; */
+
+ rb->temp_file = tf;
+
+ rc = ngx_http_read_client_request_body(r);
+
+ if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+ return rc;
+ }
+
+ return NGX_DONE;
+}
+
+
+static ngx_chain_t *ngx_http_proxy_create_request(ngx_http_proxy_ctx_t *p)
+{
+ size_t len;
+ ngx_uint_t i;
+ ngx_buf_t *b;
+ ngx_chain_t *chain;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header;
+ ngx_http_request_t *r;
+ ngx_http_proxy_upstream_conf_t *uc;
+
+ r = p->request;
+ uc = p->lcf->upstream;
+
+ if (p->upstream->method) {
+ len = http_methods[p->upstream->method - 1].len;
+
+ } else {
+ len = r->method_name.len;
+ }
+
+ len += uc->uri.len
+ + r->uri.len - uc->location->len
+ + 1 + r->args.len /* 1 is for "?" */
+ + sizeof(http_version) - 1
+ + sizeof(connection_close_header) - 1
+ + 2; /* 2 is for "\r\n" at the header end */
+
+
+ if (p->lcf->preserve_host && r->headers_in.host) {
+ len += sizeof(host_header) - 1
+ + r->headers_in.host_name_len
+ + 1 /* 1 is for ":" */
+ + uc->port_text.len
+ + 2; /* 2 is for "\r\n" */
+ } else { /* 2 is for "\r\n" */
+ len += sizeof(host_header) - 1 + uc->host_header.len + 2;
+ }
+
+
+ if (p->lcf->set_x_real_ip) { /* 2 is for "\r\n" */
+ len += sizeof(x_real_ip_header) - 1 + INET_ADDRSTRLEN - 1 + 2;
+ }
+
+
+ if (p->lcf->add_x_forwarded_for) {
+ if (r->headers_in.x_forwarded_for) {
+ len += sizeof(x_forwarded_for_header) - 1
+ + r->headers_in.x_forwarded_for->value.len
+ + 2 /* 2 is ofr ", " */
+ + INET_ADDRSTRLEN - 1
+ + 2; /* 2 is for "\r\n" */
+ } else {
+ len += sizeof(x_forwarded_for_header) - 1 + INET_ADDRSTRLEN - 1 + 2;
+ /* 2 is for "\r\n" */
+ }
+ }
+
+
+ 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 (&header[i] == r->headers_in.host) {
+ continue;
+ }
+
+ if (&header[i] == r->headers_in.connection) {
+ continue;
+ }
+
+ /* 2 is for ": " and 2 is for "\r\n" */
+ len += header[i].key.len + 2 + header[i].value.len + 2;
+ }
+
+#if (NGX_DEBUG)
+ len++;
+#endif
+
+ ngx_test_null(b, ngx_create_temp_buf(r->pool, len), NULL);
+ ngx_alloc_link_and_set_buf(chain, b, r->pool, NULL);
+
+
+ /* the request line */
+
+ if (p->upstream->method) {
+ b->last = ngx_cpymem(b->last,
+ http_methods[p->upstream->method - 1].data,
+ http_methods[p->upstream->method - 1].len);
+ } else {
+ b->last = ngx_cpymem(b->last, r->method_name.data, r->method_name.len);
+ }
+
+ b->last = ngx_cpymem(b->last, uc->uri.data, uc->uri.len);
+
+ b->last = ngx_cpymem(b->last,
+ r->uri.data + uc->location->len,
+ r->uri.len - uc->location->len);
+
+ if (r->args.len > 0) {
+ *(b->last++) = '?';
+ b->last = ngx_cpymem(b->last, r->args.data, r->args.len);
+ }
+
+ b->last = ngx_cpymem(b->last, http_version, sizeof(http_version) - 1);
+
+
+ /* the "Connection: close" header */
+
+ b->last = ngx_cpymem(b->last, connection_close_header,
+ sizeof(connection_close_header) - 1);
+
+
+ /* the "Host" header */
+
+ b->last = ngx_cpymem(b->last, host_header, sizeof(host_header) - 1);
+
+ if (p->lcf->preserve_host && r->headers_in.host) {
+ b->last = ngx_cpymem(b->last, r->headers_in.host->value.data,
+ r->headers_in.host_name_len);
+
+ if (!uc->default_port) {
+ *(b->last++) = ':';
+ b->last = ngx_cpymem(b->last, uc->port_text.data,
+ uc->port_text.len);
+ }
+
+ } else {
+ b->last = ngx_cpymem(b->last, uc->host_header.data,
+ uc->host_header.len);
+ }
+ *(b->last++) = CR; *(b->last++) = LF;
+
+
+ /* the "X-Real-IP" header */
+
+ if (p->lcf->set_x_real_ip) {
+ b->last = ngx_cpymem(b->last, x_real_ip_header,
+ sizeof(x_real_ip_header) - 1);
+ b->last = ngx_cpymem(b->last, r->connection->addr_text.data,
+ r->connection->addr_text.len);
+ *(b->last++) = CR; *(b->last++) = LF;
+ }
+
+
+ /* the "X-Forwarded-For" header */
+
+ if (p->lcf->add_x_forwarded_for) {
+ if (r->headers_in.x_forwarded_for) {
+ b->last = ngx_cpymem(b->last, x_forwarded_for_header,
+ sizeof(x_forwarded_for_header) - 1);
+
+ b->last = ngx_cpymem(b->last,
+ r->headers_in.x_forwarded_for->value.data,
+ r->headers_in.x_forwarded_for->value.len);
+
+ *(b->last++) = ','; *(b->last++) = ' ';
+
+ } else {
+ b->last = ngx_cpymem(b->last, x_forwarded_for_header,
+ sizeof(x_forwarded_for_header) - 1);
+ }
+
+ b->last = ngx_cpymem(b->last, r->connection->addr_text.data,
+ r->connection->addr_text.len);
+ *(b->last++) = CR; *(b->last++) = LF;
+ }
+
+
+ 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 (&header[i] == r->headers_in.host) {
+ continue;
+ }
+
+ if (&header[i] == r->headers_in.connection) {
+ continue;
+ }
+
+ if (&header[i] == r->headers_in.keep_alive) {
+ continue;
+ }
+
+ if (&header[i] == r->headers_in.x_forwarded_for
+ && p->lcf->add_x_forwarded_for)
+ {
+ continue;
+ }
+
+ b->last = ngx_cpymem(b->last, header[i].key.data, header[i].key.len);
+
+ *(b->last++) = ':'; *(b->last++) = ' ';
+
+ b->last = ngx_cpymem(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: \"%s: %s\"",
+ header[i].key.data, header[i].value.data);
+ }
+
+ /* add "\r\n" at the header end */
+ *(b->last++) = CR; *(b->last++) = LF;
+
+#if (NGX_DEBUG)
+ *(b->last) = '\0';
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http proxy header:\n\"%s\"", b->pos);
+#endif
+
+ return chain;
+}
+
+
+static void ngx_http_proxy_init_upstream(void *data)
+{
+ ngx_http_proxy_ctx_t *p = data;
+
+ ngx_chain_t *cl;
+ ngx_http_request_t *r;
+ ngx_output_chain_ctx_t *output;
+ ngx_chain_writer_ctx_t *writer;
+ ngx_http_proxy_log_ctx_t *ctx;
+
+ r = p->request;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http proxy init upstream, client timer: %d",
+ r->connection->read->timer_set);
+
+ if (r->connection->read->timer_set) {
+ ngx_del_timer(r->connection->read);
+ }
+
+ r->connection->read->event_handler = ngx_http_proxy_check_broken_connection;
+
+ if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+
+ r->connection->write->event_handler =
+ ngx_http_proxy_check_broken_connection;
+
+ if (!r->connection->write->active) {
+ if (ngx_add_event(r->connection->write, NGX_WRITE_EVENT,
+ NGX_CLEAR_EVENT) == NGX_ERROR)
+ {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ }
+ }
+
+
+ if (!(cl = ngx_http_proxy_create_request(p))) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ if (r->request_body->bufs) {
+ cl->next = r->request_body->bufs;
+ }
+
+ r->request_body->bufs = cl;
+
+ if (!(ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_proxy_log_ctx_t)))) {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ ctx->connection = r->connection->number;
+ ctx->proxy = p;
+
+ p->upstream->peer.log = r->connection->log;
+ p->saved_ctx = r->connection->log->data;
+ p->saved_handler = r->connection->log->handler;
+ r->connection->log->data = ctx;
+ r->connection->log->handler = ngx_http_proxy_log_error;
+ p->action = "connecting to upstream";
+
+ if (!(output = ngx_pcalloc(r->pool, sizeof(ngx_output_chain_ctx_t)))) {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ p->upstream->output_chain_ctx = output;
+
+ output->sendfile = r->sendfile;
+ output->pool = r->pool;
+ output->bufs.num = 1;
+ output->tag = (ngx_buf_tag_t) &ngx_http_proxy_module;
+ output->output_filter = (ngx_output_chain_filter_pt) ngx_chain_writer;
+
+ if (!(writer = ngx_palloc(r->pool, sizeof(ngx_chain_writer_ctx_t)))) {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ output->filter_ctx = writer;
+ writer->pool = r->pool;
+
+#if 0
+ if (p->lcf->busy_lock && p->busy_lock == NULL) {
+#else
+ if (p->lcf->busy_lock && !p->busy_locked) {
+#endif
+ ngx_http_proxy_upstream_busy_lock(p);
+ } else {
+ ngx_http_proxy_connect(p);
+ }
+}
+
+
+static void ngx_http_proxy_reinit_upstream(ngx_http_proxy_ctx_t *p)
+{
+ ngx_chain_t *cl;
+ ngx_output_chain_ctx_t *output;
+
+ /* reinit the request chain */
+
+ for (cl = p->request->request_body->bufs; cl; cl = cl->next) {
+ cl->buf->pos = cl->buf->start;
+ cl->buf->file_pos = 0;
+ }
+
+ /* reinit the ngx_output_chain() context */
+
+ output = p->upstream->output_chain_ctx;
+
+ output->buf = NULL;
+ output->in = NULL;
+ output->free = NULL;
+ output->busy = NULL;
+
+ /* reinit r->header_in buffer */
+
+ if (p->header_in) {
+ if (p->cache) {
+ p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
+ p->header_in->last = p->header_in->pos;
+
+ } else {
+ p->header_in->pos = p->header_in->start;
+ p->header_in->last = p->header_in->start;
+ }
+ }
+
+ /* add one more state */
+
+ if (!(p->state = ngx_push_array(&p->states))) {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ p->status = 0;
+ p->status_count = 0;
+}
+
+
+#if 0
+
+void ngx_http_proxy_upstream_busy_lock(ngx_http_proxy_ctx_t *p)
+{
+ ngx_int_t rc;
+
+ rc = ngx_event_busy_lock(p->lcf->busy_lock, p->busy_lock);
+
+ if (rc == NGX_AGAIN) {
+ return;
+ }
+
+ if (rc == NGX_OK) {
+ ngx_http_proxy_connect(p);
+ return;
+ }
+
+ if (rc == NGX_ERROR) {
+ p->state->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ /* rc == NGX_BUSY */
+
+#if (NGX_HTTP_CACHE)
+
+ if (p->busy_lock->timer) {
+ ft_type = NGX_HTTP_PROXY_FT_MAX_WAITING;
+ } else {
+ ft_type = NGX_HTTP_PROXY_FT_BUSY_LOCK;
+ }
+
+ if (p->stale && (p->lcf->use_stale & ft_type)) {
+ ngx_http_proxy_finalize_request(p,
+ ngx_http_proxy_send_cached_response(p));
+ return;
+ }
+
+#endif
+
+ p->state->status = NGX_HTTP_SERVICE_UNAVAILABLE;
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_SERVICE_UNAVAILABLE);
+}
+
+#endif
+
+
+#if 1
+
+void ngx_http_proxy_upstream_busy_lock(ngx_http_proxy_ctx_t *p)
+{
+ ngx_int_t rc;
+#if (NGX_HTTP_CACHE)
+ ngx_int_t ft_type;
+#endif
+
+ if (p->busy_lock.time == 0) {
+ p->busy_lock.event = p->request->connection->read;
+ p->busy_lock.event_handler = ngx_http_proxy_busy_lock_handler;
+ }
+
+ rc = ngx_http_busy_lock(p->lcf->busy_lock, &p->busy_lock);
+
+ if (rc == NGX_AGAIN) {
+ return;
+ }
+
+ if (rc == NGX_OK) {
+ ngx_http_proxy_connect(p);
+ return;
+ }
+
+ ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
+
+#if (NGX_HTTP_CACHE)
+
+ if (rc == NGX_DONE) {
+ ft_type = NGX_HTTP_PROXY_FT_BUSY_LOCK;
+
+ } else {
+ /* rc == NGX_ERROR */
+ ft_type = NGX_HTTP_PROXY_FT_MAX_WAITING;
+ }
+
+ if (p->stale && (p->lcf->use_stale & ft_type)) {
+ ngx_http_proxy_finalize_request(p,
+ ngx_http_proxy_send_cached_response(p));
+ return;
+ }
+
+#endif
+
+ p->state->status = NGX_HTTP_SERVICE_UNAVAILABLE;
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_SERVICE_UNAVAILABLE);
+}
+
+#endif
+
+
+static void ngx_http_proxy_connect(ngx_http_proxy_ctx_t *p)
+{
+ int rc;
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+ ngx_output_chain_ctx_t *output;
+ ngx_chain_writer_ctx_t *writer;
+
+ p->action = "connecting to upstream";
+
+ p->request->connection->single_connection = 0;
+
+ rc = ngx_event_connect_peer(&p->upstream->peer);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, p->request->connection->log, 0,
+ "http proxy connect: %d", rc);
+
+ if (rc == NGX_ERROR) {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ p->state->peer =
+ &p->upstream->peer.peers->peers[p->upstream->peer.cur_peer].addr_port_text;
+
+ if (rc == NGX_CONNECT_ERROR) {
+ ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_ERROR);
+ return;
+ }
+
+ r = p->request;
+ c = p->upstream->peer.connection;
+
+ c->data = p;
+ c->write->event_handler = ngx_http_proxy_send_request_handler;
+ c->read->event_handler = ngx_http_proxy_process_upstream_status_line;
+
+ c->pool = r->pool;
+ c->read->log = c->write->log = c->log = r->connection->log;
+
+ /* init or reinit the ngx_output_chain() and ngx_chain_writer() contexts */
+
+ output = p->upstream->output_chain_ctx;
+ writer = output->filter_ctx;
+ writer->out = NULL;
+ writer->last = &writer->out;
+ writer->connection = c;
+ writer->limit = OFF_T_MAX_VALUE;
+
+ if (p->upstream->peer.tries > 1 && p->request_sent) {
+ ngx_http_proxy_reinit_upstream(p);
+ }
+
+ if (r->request_body->buf) {
+ if (r->request_body->temp_file->file.fd != NGX_INVALID_FILE) {
+
+ if (!(output->free = ngx_alloc_chain_link(r->pool))) {
+ ngx_http_proxy_finalize_request(p,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ output->free->buf = r->request_body->buf;
+ output->free->next = NULL;
+ 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 = (ngx_buf_tag_t) &ngx_http_proxy_module;
+
+ } else {
+ r->request_body->buf->pos = r->request_body->buf->start;
+ }
+ }
+
+ p->request_sent = 0;
+
+ if (rc == NGX_AGAIN) {
+ ngx_add_timer(c->write, p->lcf->connect_timeout);
+ return;
+ }
+
+ /* rc == NGX_OK */
+
+#if 1 /* test only, see below about "post aio operation" */
+
+ if (c->read->ready) {
+ /* post aio operation */
+ ngx_http_proxy_process_upstream_status_line(c->read);
+ return;
+ }
+
+#endif
+
+ ngx_http_proxy_send_request(p);
+}
+
+
+static void ngx_http_proxy_send_request(ngx_http_proxy_ctx_t *p)
+{
+ int rc;
+ ngx_connection_t *c;
+
+ c = p->upstream->peer.connection;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http proxy send request");
+
+#if (HAVE_KQUEUE)
+
+ if ((ngx_event_flags & NGX_HAVE_KQUEUE_EVENT)
+ && !p->request_sent
+ && c->write->pending_eof)
+ {
+ ngx_log_error(NGX_LOG_ERR, c->log, c->write->kq_errno,
+ "connect() failed");
+
+ ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_ERROR);
+ return;
+ }
+
+#endif
+
+ p->action = "sending request to upstream";
+
+ rc = ngx_output_chain(p->upstream->output_chain_ctx,
+ p->request_sent ? NULL:
+ p->request->request_body->bufs);
+
+ if (rc == NGX_ERROR) {
+ ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_ERROR);
+ return;
+ }
+
+ p->request_sent = 1;
+
+ if (c->write->timer_set) {
+ ngx_del_timer(c->write);
+ }
+
+ if (rc == NGX_AGAIN) {
+ ngx_add_timer(c->write, p->lcf->send_timeout);
+
+ c->write->available = /* STUB: lowat */ 0;
+ if (ngx_handle_write_event(c->write, NGX_LOWAT_EVENT) == NGX_ERROR) {
+ ngx_http_proxy_finalize_request(p, 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_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ c->tcp_nopush = NGX_TCP_NOPUSH_UNSET;
+ return;
+ }
+
+ ngx_add_timer(c->read, p->lcf->read_timeout);
+
+#if 0
+ if (c->read->ready) {
+
+ /* post aio operation */
+
+ /*
+ * although we can post aio operation just in the end
+ * of ngx_http_proxy_connect() CHECK IT !!!
+ * it's better to do here because we postpone header buffer allocation
+ */
+
+ ngx_http_proxy_process_upstream_status_line(c->read);
+ return;
+ }
+#endif
+
+ c->write->event_handler = ngx_http_proxy_dummy_handler;
+
+ if (ngx_handle_level_write_event(c->write) == NGX_ERROR) {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+}
+
+
+static void ngx_http_proxy_send_request_handler(ngx_event_t *wev)
+{
+ ngx_connection_t *c;
+ ngx_http_proxy_ctx_t *p;
+
+ c = wev->data;
+ p = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0,
+ "http proxy send request handler");
+
+ if (wev->timedout) {
+ p->action = "sending request to upstream";
+ ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_TIMEOUT);
+ return;
+ }
+
+ if (p->request->connection->write->eof
+ && (!p->cachable || !p->request_sent))
+ {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ return;
+ }
+
+ ngx_http_proxy_send_request(p);
+}
+
+
+static void ngx_http_proxy_dummy_handler(ngx_event_t *wev)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0, "http proxy dummy handler");
+}
+
+
+static void ngx_http_proxy_process_upstream_status_line(ngx_event_t *rev)
+{
+ int rc;
+ ssize_t n;
+ ngx_connection_t *c;
+ ngx_http_proxy_ctx_t *p;
+
+ c = rev->data;
+ p = c->data;
+ p->action = "reading upstream status line";
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+ "http proxy process status line");
+
+ if (rev->timedout) {
+ ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_TIMEOUT);
+ return;
+ }
+
+ if (p->header_in == NULL) {
+ p->header_in = ngx_create_temp_buf(p->request->pool,
+ p->lcf->header_buffer_size);
+ if (p->header_in == NULL) {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ p->header_in->tag = (ngx_buf_tag_t) &ngx_http_proxy_module;
+
+ if (p->cache) {
+ p->header_in->pos += p->cache->ctx.header_size;
+ p->header_in->last = p->header_in->pos;
+ }
+ }
+
+ n = ngx_http_proxy_read_upstream_header(p);
+
+ if (n == NGX_AGAIN) {
+ return;
+ }
+
+ if (n == 0) {
+ ngx_log_error(NGX_LOG_ERR, rev->log, 0,
+ "upstream prematurely closed connection");
+ }
+
+ if (n == NGX_ERROR || n == 0) {
+ ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_ERROR);
+ return;
+ }
+
+ p->valid_header_in = 0;
+
+ p->upstream->peer.cached = 0;
+
+ rc = ngx_http_proxy_parse_status_line(p);
+
+ if (rc == NGX_AGAIN) {
+ if (p->header_in->pos == p->header_in->last) {
+ ngx_log_error(NGX_LOG_ERR, rev->log, 0,
+ "upstream sent too long status line");
+ ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_INVALID_HEADER);
+ }
+ return;
+ }
+
+ if (rc == NGX_HTTP_PROXY_PARSE_NO_HEADER) {
+ ngx_log_error(NGX_LOG_ERR, rev->log, 0,
+ "upstream sent no valid HTTP/1.0 header");
+
+ if (p->accel) {
+ ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_INVALID_HEADER);
+
+ } else {
+ p->request->http_version = NGX_HTTP_VERSION_9;
+ p->upstream->status = NGX_HTTP_OK;
+ ngx_http_proxy_send_response(p);
+ }
+
+ return;
+ }
+
+ /* rc == NGX_OK */
+
+ p->upstream->status = p->status;
+ p->state->status = p->status;
+
+ if (p->status == NGX_HTTP_INTERNAL_SERVER_ERROR) {
+
+ if (p->upstream->peer.tries > 1
+ && (p->lcf->next_upstream & NGX_HTTP_PROXY_FT_HTTP_500))
+ {
+ ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_HTTP_500);
+ return;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ if (p->upstream->peer.tries == 0
+ && p->stale
+ && (p->lcf->use_stale & NGX_HTTP_PROXY_FT_HTTP_500))
+ {
+ ngx_http_proxy_finalize_request(p,
+ ngx_http_proxy_send_cached_response(p));
+
+ return;
+ }
+
+#endif
+ }
+
+ if (p->status == NGX_HTTP_NOT_FOUND
+ && p->upstream->peer.tries > 1
+ && p->lcf->next_upstream & NGX_HTTP_PROXY_FT_HTTP_404)
+ {
+ ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_HTTP_404);
+ return;
+ }
+
+ /* TODO: "proxy_error_page" */
+
+ p->upstream->status_line.len = p->status_end - p->status_start;
+ p->upstream->status_line.data = ngx_palloc(p->request->pool,
+ p->upstream->status_line.len + 1);
+ if (p->upstream->status_line.data == NULL) {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ ngx_cpystrn(p->upstream->status_line.data, p->status_start,
+ p->upstream->status_line.len + 1);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+ "http proxy status %d \"%s\"",
+ p->upstream->status, p->upstream->status_line.data);
+
+
+ /* init or reinit the p->upstream->headers_in.headers table */
+
+ if (p->upstream->headers_in.headers.part.elts) {
+ p->upstream->headers_in.headers.part.nelts = 0;
+ p->upstream->headers_in.headers.part.next = NULL;
+ p->upstream->headers_in.headers.last =
+ &p->upstream->headers_in.headers.part;
+
+ ngx_memzero(&p->upstream->headers_in.date,
+ sizeof(ngx_http_proxy_headers_in_t) - sizeof(ngx_list_t));
+
+ } else {
+ if (ngx_list_init(&p->upstream->headers_in.headers, p->request->pool,
+ 20, sizeof(ngx_table_elt_t)) == NGX_ERROR)
+ {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ }
+
+
+ c->read->event_handler = ngx_http_proxy_process_upstream_headers;
+ ngx_http_proxy_process_upstream_headers(rev);
+}
+
+
+static void ngx_http_proxy_process_upstream_headers(ngx_event_t *rev)
+{
+ int i, rc;
+ ssize_t n;
+ ngx_table_elt_t *h;
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+ ngx_http_proxy_ctx_t *p;
+
+ c = rev->data;
+ p = c->data;
+ r = p->request;
+ p->action = "reading upstream headers";
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+ "http proxy process header line");
+
+ if (rev->timedout) {
+ ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_TIMEOUT);
+ return;
+ }
+
+ rc = NGX_AGAIN;
+
+ for ( ;; ) {
+ if (rc == NGX_AGAIN) {
+ n = ngx_http_proxy_read_upstream_header(p);
+
+ if (n == 0) {
+ ngx_log_error(NGX_LOG_ERR, rev->log, 0,
+ "upstream prematurely closed connection");
+ }
+
+ if (n == NGX_ERROR || n == 0) {
+ ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_ERROR);
+ return;
+ }
+
+ if (n == NGX_AGAIN) {
+ return;
+ }
+ }
+
+ rc = ngx_http_parse_header_line(p->request, p->header_in);
+
+ if (rc == NGX_OK) {
+
+ /* a header line has been parsed successfully */
+
+ if (!(h = ngx_list_push(&p->upstream->headers_in.headers))) {
+ ngx_http_proxy_finalize_request(p,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ h->key.len = r->header_name_end - r->header_name_start;
+ h->value.len = r->header_end - r->header_start;
+
+ h->key.data = ngx_palloc(p->request->pool,
+ h->key.len + 1 + h->value.len + 1);
+ if (h->key.data == NULL) {
+ ngx_http_proxy_finalize_request(p,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ h->value.data = h->key.data + h->key.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);
+
+ for (i = 0; ngx_http_proxy_headers_in[i].name.len != 0; i++) {
+ if (ngx_http_proxy_headers_in[i].name.len != h->key.len) {
+ continue;
+ }
+
+ if (ngx_strcasecmp(ngx_http_proxy_headers_in[i].name.data,
+ h->key.data) == 0)
+ {
+ *((ngx_table_elt_t **) ((char *) &p->upstream->headers_in
+ + ngx_http_proxy_headers_in[i].offset)) = h;
+ break;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http proxy header: \"%s: %s\"",
+ h->key.data, h->value.data);
+
+ continue;
+
+ } else if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+ /* a whole header has been parsed successfully */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http proxy header done");
+
+ /* TODO: hook to process the upstream header */
+
+#if (NGX_HTTP_CACHE)
+
+ if (p->cachable) {
+ p->cachable = ngx_http_proxy_is_cachable(p);
+ }
+
+#endif
+
+ ngx_http_proxy_send_response(p);
+ return;
+
+ } else if (rc != NGX_AGAIN) {
+
+ /* there was error while a header line parsing */
+
+ ngx_log_error(NGX_LOG_ERR, rev->log, 0,
+ upstream_header_errors[rc - NGX_HTTP_PARSE_HEADER_ERROR]);
+
+ ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_INVALID_HEADER);
+ return;
+ }
+
+ /* rc == NGX_AGAIN: a header line parsing is still not complete */
+
+ if (p->header_in->last == p->header_in->end) {
+ ngx_log_error(NGX_LOG_ERR, rev->log, 0,
+ "upstream sent too big header");
+
+ ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_INVALID_HEADER);
+ return;
+ }
+ }
+}
+
+
+static ssize_t ngx_http_proxy_read_upstream_header(ngx_http_proxy_ctx_t *p)
+{
+ ssize_t n;
+ ngx_event_t *rev;
+
+ rev = p->upstream->peer.connection->read;
+
+ n = p->header_in->last - p->header_in->pos;
+
+ if (n > 0) {
+ return n;
+ }
+
+ n = ngx_recv(p->upstream->peer.connection, p->header_in->last,
+ p->header_in->end - p->header_in->last);
+
+ if (n == NGX_AGAIN) {
+#if 0
+ ngx_add_timer(rev, p->lcf->read_timeout);
+#endif
+
+ if (ngx_handle_read_event(rev, 0) == NGX_ERROR) {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ if (n == 0) {
+ ngx_log_error(NGX_LOG_ERR, rev->log, 0,
+ "upstream closed prematurely connection");
+ }
+
+ if (n == 0 || n == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ p->header_in->last += n;
+
+ return n;
+}
+
+
+static void ngx_http_proxy_send_response(ngx_http_proxy_ctx_t *p)
+{
+ int rc;
+ ngx_event_pipe_t *ep;
+ ngx_http_request_t *r;
+ ngx_http_cache_header_t *header;
+ ngx_http_core_loc_conf_t *clcf;
+
+ r = p->request;
+
+ r->headers_out.status = p->upstream->status;
+
+#if 0
+ r->headers_out.content_length_n = -1;
+ r->headers_out.content_length = NULL;
+#endif
+
+ /* copy an upstream header to r->headers_out */
+
+ if (ngx_http_proxy_copy_header(p, &p->upstream->headers_in) == NGX_ERROR) {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ /* TODO: preallocate event_pipe bufs, look "Content-Length" */
+
+ rc = ngx_http_send_header(r);
+
+ p->header_sent = 1;
+
+ if (p->cache && p->cache->ctx.file.fd != NGX_INVALID_FILE) {
+ if (ngx_close_file(p->cache->ctx.file.fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed",
+ p->cache->ctx.file.name.data);
+ }
+ }
+
+ if (p->cachable) {
+ header = (ngx_http_cache_header_t *) p->header_in->start;
+
+ header->expires = p->cache->ctx.expires;
+ header->last_modified = p->cache->ctx.last_modified;
+ header->date = p->cache->ctx.date;
+ header->length = r->headers_out.content_length_n;
+ p->cache->ctx.length = r->headers_out.content_length_n;
+
+ header->key_len = p->cache->ctx.key.len;
+ ngx_memcpy(&header->key, p->cache->ctx.key.data, header->key_len);
+ header->key[header->key_len] = LF;
+ }
+
+ ep = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t));
+ if (ep == NULL) {
+ ngx_http_proxy_finalize_request(p, 0);
+ return;
+ }
+
+ p->upstream->event_pipe = ep;
+
+ ep->input_filter = ngx_event_pipe_copy_input_filter;
+ ep->output_filter = (ngx_event_pipe_output_filter_pt)
+ ngx_http_output_filter;
+ ep->output_ctx = r;
+ ep->tag = (ngx_buf_tag_t) &ngx_http_proxy_module;
+ ep->bufs = p->lcf->bufs;
+ ep->busy_size = p->lcf->busy_buffers_size;
+ ep->upstream = p->upstream->peer.connection;
+ ep->downstream = r->connection;
+ ep->pool = r->pool;
+ ep->log = r->connection->log;
+
+ ep->cachable = p->cachable;
+
+ if (!(ep->temp_file = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)))) {
+ ngx_http_proxy_finalize_request(p, 0);
+ return;
+ }
+
+ ep->temp_file->file.fd = NGX_INVALID_FILE;
+ ep->temp_file->file.log = r->connection->log;
+ ep->temp_file->path = p->lcf->temp_path;
+ ep->temp_file->pool = r->pool;
+
+ if (p->cachable) {
+ ep->temp_file->persistent = 1;
+ } else {
+ ep->temp_file->warn = "an upstream response is buffered "
+ "to a temporary file";
+ }
+
+ ep->max_temp_file_size = p->lcf->max_temp_file_size;
+ ep->temp_file_write_size = p->lcf->temp_file_write_size;
+
+ if (!(ep->preread_bufs = ngx_alloc_chain_link(r->pool))) {
+ ngx_http_proxy_finalize_request(p, 0);
+ return;
+ }
+ ep->preread_bufs->buf = p->header_in;
+ ep->preread_bufs->next = NULL;
+
+ ep->preread_size = p->header_in->last - p->header_in->pos;
+
+ if (p->cachable) {
+ ep->buf_to_file = ngx_calloc_buf(r->pool);
+ if (ep->buf_to_file == NULL) {
+ ngx_http_proxy_finalize_request(p, 0);
+ return;
+ }
+ ep->buf_to_file->pos = p->header_in->start;
+ ep->buf_to_file->last = p->header_in->pos;
+ ep->buf_to_file->temporary = 1;
+ }
+
+ if (ngx_event_flags & NGX_USE_AIO_EVENT) {
+ /* the posted aio operation can currupt a shadow buffer */
+ ep->single_buf = 1;
+ }
+
+ /* TODO: ep->free_bufs = 0 if use ngx_create_chain_of_bufs() */
+ ep->free_bufs = 1;
+
+ /*
+ * event_pipe would do p->header_in->last += ep->preread_size
+ * as though these bytes were read.
+ */
+ p->header_in->last = p->header_in->pos;
+
+ if (p->lcf->cyclic_temp_file) {
+
+ /*
+ * we need to disable the use of sendfile() if we use cyclic temp file
+ * because the writing a new data can interfere with sendfile()
+ * that uses the same kernel file pages (at least on FreeBSD)
+ */
+
+ ep->cyclic_temp_file = 1;
+ r->sendfile = 0;
+
+ } else {
+ ep->cyclic_temp_file = 0;
+ r->sendfile = 1;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ep->read_timeout = p->lcf->read_timeout;
+ ep->send_timeout = clcf->send_timeout;
+ ep->send_lowat = clcf->send_lowat;
+
+ p->upstream->peer.connection->read->event_handler =
+ ngx_http_proxy_process_body;
+ r->connection->write->event_handler = ngx_http_proxy_process_body;
+
+ ngx_http_proxy_process_body(p->upstream->peer.connection->read);
+
+ return;
+}
+
+
+static void ngx_http_proxy_process_body(ngx_event_t *ev)
+{
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+ ngx_http_proxy_ctx_t *p;
+ ngx_event_pipe_t *ep;
+
+ c = ev->data;
+
+ if (ev->write) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+ "http proxy process downstream");
+ r = c->data;
+ p = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+ p->action = "sending to client";
+
+ } else {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+ "http proxy process upstream");
+ p = c->data;
+ r = p->request;
+ p->action = "reading upstream body";
+ }
+
+ ep = p->upstream->event_pipe;
+
+ if (ev->timedout) {
+ if (ev->write) {
+ ep->downstream_error = 1;
+ ngx_log_error(NGX_LOG_ERR, c->log, NGX_ETIMEDOUT,
+ "client timed out");
+
+ } else {
+ ep->upstream_error = 1;
+ ngx_log_error(NGX_LOG_ERR, c->log, NGX_ETIMEDOUT,
+ "upstream timed out");
+ }
+
+ } else {
+ if (ngx_event_pipe(ep, ev->write) == NGX_ABORT) {
+ ngx_http_proxy_finalize_request(p, 0);
+ return;
+ }
+ }
+
+ if (p->upstream->peer.connection) {
+
+#if (NGX_HTTP_FILE_CACHE)
+
+ if (ep->upstream_done && p->cachable) {
+ if (ngx_http_proxy_update_cache(p) == NGX_ERROR) {
+ ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
+ ngx_http_proxy_finalize_request(p, 0);
+ return;
+ }
+
+ } else if (ep->upstream_eof && p->cachable) {
+
+ /* TODO: check length & update cache */
+
+ if (ngx_http_proxy_update_cache(p) == NGX_ERROR) {
+ ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
+ ngx_http_proxy_finalize_request(p, 0);
+ return;
+ }
+ }
+
+#endif
+
+ if (ep->upstream_done || ep->upstream_eof || ep->upstream_error) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+ "http proxy upstream exit: " PTR_FMT, ep->out);
+ ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
+ ngx_http_proxy_finalize_request(p, 0);
+ return;
+ }
+ }
+
+ if (ep->downstream_error) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+ "http proxy downstream error");
+ if (!p->cachable && p->upstream->peer.connection) {
+ ngx_http_proxy_finalize_request(p, 0);
+ }
+ }
+}
+
+
+static void ngx_http_proxy_next_upstream(ngx_http_proxy_ctx_t *p, int ft_type)
+{
+ int status;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, p->request->connection->log, 0,
+ "http proxy next upstream: %d", ft_type);
+
+ ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
+
+ if (ft_type != NGX_HTTP_PROXY_FT_HTTP_404) {
+ ngx_event_connect_peer_failed(&p->upstream->peer);
+ }
+
+ if (ft_type == NGX_HTTP_PROXY_FT_TIMEOUT) {
+ ngx_log_error(NGX_LOG_ERR, p->request->connection->log, NGX_ETIMEDOUT,
+ "upstream timed out");
+ }
+
+ if (p->upstream->peer.cached && ft_type == NGX_HTTP_PROXY_FT_ERROR) {
+ status = 0;
+
+ } else {
+ switch(ft_type) {
+ case NGX_HTTP_PROXY_FT_TIMEOUT:
+ status = NGX_HTTP_GATEWAY_TIME_OUT;
+ break;
+
+ case NGX_HTTP_PROXY_FT_HTTP_500:
+ status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ break;
+
+ case NGX_HTTP_PROXY_FT_HTTP_404:
+ status = NGX_HTTP_NOT_FOUND;
+ break;
+
+ /*
+ * NGX_HTTP_PROXY_FT_BUSY_LOCK and NGX_HTTP_PROXY_FT_MAX_WAITING
+ * never reach here
+ */
+
+ default:
+ status = NGX_HTTP_BAD_GATEWAY;
+ }
+ }
+
+ if (p->upstream->peer.connection) {
+ ngx_http_proxy_close_connection(p);
+ }
+
+ if (p->request->connection->write->eof) {
+ ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ return;
+ }
+
+ if (status) {
+ p->state->status = status;
+
+ if (p->upstream->peer.tries == 0 || !(p->lcf->next_upstream & ft_type))
+ {
+
+#if (NGX_HTTP_CACHE)
+
+ if (p->stale && (p->lcf->use_stale & ft_type)) {
+ ngx_http_proxy_finalize_request(p,
+ ngx_http_proxy_send_cached_response(p));
+ return;
+ }
+
+#endif
+
+ ngx_http_proxy_finalize_request(p, status);
+ return;
+ }
+ }
+
+ if (p->lcf->busy_lock && !p->busy_locked) {
+ ngx_http_proxy_upstream_busy_lock(p);
+ } else {
+ ngx_http_proxy_connect(p);
+ }
+}
diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c
new file mode 100644
index 000000000..a37ffc6eb
--- /dev/null
+++ b/src/http/ngx_http.c
@@ -0,0 +1,642 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_http.h>
+
+
+static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_merge_locations(ngx_conf_t *cf,
+ ngx_array_t *locations,
+ void **loc_conf,
+ ngx_http_module_t *module,
+ ngx_uint_t ctx_index);
+
+int ngx_http_max_module;
+
+ngx_uint_t ngx_http_total_requests;
+uint64_t ngx_http_total_sent;
+
+
+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);
+
+
+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,
+ &ngx_http_module_ctx, /* module context */
+ ngx_http_commands, /* module directives */
+ NGX_CORE_MODULE, /* module type */
+ NULL, /* init module */
+ NULL /* init child */
+};
+
+
+static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *rv;
+ ngx_uint_t mi, m, s, l, p, a, n;
+ ngx_uint_t port_found, addr_found, virtual_names;
+ ngx_conf_t pcf;
+ ngx_array_t in_ports;
+ ngx_listening_t *ls;
+ ngx_http_listen_t *lscf;
+ ngx_http_module_t *module;
+ ngx_http_handler_pt *h;
+ ngx_http_conf_ctx_t *ctx;
+ ngx_http_in_port_t *in_port, *inport;
+ ngx_http_in_addr_t *in_addr, *inaddr;
+ ngx_http_server_name_t *s_name, *name;
+ ngx_http_core_srv_conf_t **cscfp, *cscf;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_core_main_conf_t *cmcf;
+#if (WIN32)
+ ngx_iocp_conf_t *iocpcf;
+#endif
+
+ /* the main http context */
+ ngx_test_null(ctx,
+ ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)),
+ 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 main http main_conf, it's the same in the all http contexts */
+ ngx_test_null(ctx->main_conf,
+ ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module),
+ NGX_CONF_ERROR);
+
+ /* the http null srv_conf, it's used to merge the server{}s' srv_conf's */
+ ngx_test_null(ctx->srv_conf,
+ ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module),
+ NGX_CONF_ERROR);
+
+ /* the http null loc_conf, it's used to merge the server{}s' loc_conf's */
+ ngx_test_null(ctx->loc_conf,
+ ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module),
+ NGX_CONF_ERROR);
+
+
+ /* create the main_conf, srv_conf and loc_conf in 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->pre_conf) {
+ if (module->pre_conf(cf) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (module->create_main_conf) {
+ ngx_test_null(ctx->main_conf[mi], module->create_main_conf(cf),
+ NGX_CONF_ERROR);
+ }
+
+ if (module->create_srv_conf) {
+ ngx_test_null(ctx->srv_conf[mi], module->create_srv_conf(cf),
+ NGX_CONF_ERROR);
+ }
+
+ if (module->create_loc_conf) {
+ ngx_test_null(ctx->loc_conf[mi], module->create_loc_conf(cf),
+ NGX_CONF_ERROR);
+ }
+ }
+
+ /* parse inside the http{} block */
+
+ pcf = *cf;
+ cf->ctx = ctx;
+ cf->module_type = NGX_HTTP_MODULE;
+ cf->cmd_type = NGX_HTTP_MAIN_CONF;
+ rv = ngx_conf_parse(cf, NULL);
+
+ if (rv != NGX_CONF_OK) {
+ *cf = pcf;
+ return rv;
+ }
+
+ /*
+ * 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) {
+ *cf = pcf;
+ return rv;
+ }
+ }
+
+ for (s = 0; s < cmcf->servers.nelts; s++) {
+
+ /* merge the server{}s' srv_conf's */
+
+ 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;
+ }
+ }
+
+ if (module->merge_loc_conf) {
+
+ /* merge the server{}'s loc_conf */
+
+ rv = module->merge_loc_conf(cf,
+ ctx->loc_conf[mi],
+ cscfp[s]->ctx->loc_conf[mi]);
+ if (rv != NGX_CONF_OK) {
+ *cf = pcf;
+ return rv;
+ }
+
+ /* merge the locations{}' loc_conf's */
+
+ rv = ngx_http_merge_locations(cf, &cscfp[s]->locations,
+ cscfp[s]->ctx->loc_conf,
+ module, mi);
+ if (rv != NGX_CONF_OK) {
+ *cf = pcf;
+ return rv;
+ }
+
+#if 0
+ clcfp = (ngx_http_core_loc_conf_t **) cscfp[s]->locations.elts;
+
+ for (l = 0; l < cscfp[s]->locations.nelts; l++) {
+ rv = module->merge_loc_conf(cf,
+ cscfp[s]->ctx->loc_conf[mi],
+ clcfp[l]->loc_conf[mi]);
+ if (rv != NGX_CONF_OK) {
+ *cf = pcf;
+ return rv;
+ }
+ }
+#endif
+ }
+ }
+ }
+
+ /* we needed "http"'s cf->ctx while merging configuration */
+ *cf = pcf;
+
+ /* init lists of the handlers */
+
+ ngx_init_array(cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers,
+ cf->cycle->pool, 10, sizeof(ngx_http_handler_pt),
+ NGX_CONF_ERROR);
+ cmcf->phases[NGX_HTTP_REWRITE_PHASE].type = NGX_OK;
+
+
+ /* the special find config phase for single handler */
+
+ ngx_init_array(cmcf->phases[NGX_HTTP_FIND_CONFIG_PHASE].handlers,
+ cf->cycle->pool, 1, sizeof(ngx_http_handler_pt),
+ NGX_CONF_ERROR);
+ cmcf->phases[NGX_HTTP_FIND_CONFIG_PHASE].type = NGX_OK;
+
+ ngx_test_null(h, ngx_push_array(
+ &cmcf->phases[NGX_HTTP_FIND_CONFIG_PHASE].handlers),
+ NGX_CONF_ERROR);
+ *h = ngx_http_find_location_config;
+
+
+ ngx_init_array(cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers,
+ cf->cycle->pool, 10, sizeof(ngx_http_handler_pt),
+ NGX_CONF_ERROR);
+ cmcf->phases[NGX_HTTP_ACCESS_PHASE].type = NGX_DECLINED;
+
+
+ ngx_init_array(cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers,
+ cf->cycle->pool, 10, sizeof(ngx_http_handler_pt),
+ NGX_CONF_ERROR);
+ cmcf->phases[NGX_HTTP_CONTENT_PHASE].type = NGX_OK;
+
+
+ /*
+ * create the lists of the ports, the addresses and the server names
+ * to allow quickly find the server core module configuration at run-time
+ */
+
+ ngx_init_array(in_ports, cf->pool, 10, sizeof(ngx_http_in_port_t),
+ NGX_CONF_ERROR);
+
+ /* "server" directives */
+ cscfp = cmcf->servers.elts;
+ for (s = 0; s < cmcf->servers.nelts; s++) {
+
+ /* "listen" directives */
+ lscf = cscfp[s]->listen.elts;
+ for (l = 0; l < cscfp[s]->listen.nelts; l++) {
+
+ port_found = 0;
+
+ /* AF_INET only */
+
+ in_port = in_ports.elts;
+ for (p = 0; p < in_ports.nelts; p++) {
+
+ if (lscf[l].port == in_port[p].port) {
+
+ /* the port is already in the port list */
+
+ port_found = 1;
+ addr_found = 0;
+
+ in_addr = in_port[p].addrs.elts;
+ for (a = 0; a < in_port[p].addrs.nelts; a++) {
+
+ if (lscf[l].addr == in_addr[a].addr) {
+
+ /* the address is already bound to this port */
+
+ /* "server_name" directives */
+ s_name = cscfp[s]->server_names.elts;
+ for (n = 0; n < cscfp[s]->server_names.nelts; n++) {
+
+ /*
+ * add the server name and server core module
+ * configuration to the address:port
+ */
+
+ /* TODO: duplicate names can be checked here */
+
+ ngx_test_null(name,
+ ngx_push_array(&in_addr[a].names),
+ NGX_CONF_ERROR);
+
+ name->name = s_name[n].name;
+ name->core_srv_conf = s_name[n].core_srv_conf;
+ }
+
+ /*
+ * check duplicate "default" server that
+ * serves this address:port
+ */
+
+ if (lscf[l].default_server) {
+ if (in_addr[a].default_server) {
+ ngx_log_error(NGX_LOG_ERR, cf->log, 0,
+ "duplicate default server in %s:%d",
+ lscf[l].file_name.data,
+ lscf[l].line);
+
+ return NGX_CONF_ERROR;
+ }
+
+ in_addr[a].core_srv_conf = cscfp[s];
+ in_addr[a].default_server = 1;
+ }
+
+ addr_found = 1;
+
+ break;
+
+ } else if (in_addr[a].addr == INADDR_ANY) {
+
+ /*
+ * "*:port" must be the last resort so move it
+ * to the end of the address list and add
+ * the new address at its place
+ */
+
+ ngx_test_null(inaddr,
+ ngx_push_array(&in_port[p].addrs),
+ NGX_CONF_ERROR);
+
+ ngx_memcpy(inaddr, &in_addr[a],
+ sizeof(ngx_http_in_addr_t));
+
+ in_addr[a].addr = lscf[l].addr;
+ in_addr[a].default_server = lscf[l].default_server;
+ in_addr[a].core_srv_conf = cscfp[s];
+
+ /*
+ * create the empty list of the server names that
+ * can be served on this address:port
+ */
+
+ ngx_init_array(inaddr->names, cf->pool, 10,
+ sizeof(ngx_http_server_name_t),
+ NGX_CONF_ERROR);
+
+ addr_found = 1;
+
+ break;
+ }
+ }
+
+ if (!addr_found) {
+
+ /*
+ * add the address to the addresses list that
+ * bound to this port
+ */
+
+ ngx_test_null(inaddr,
+ ngx_push_array(&in_port[p].addrs),
+ NGX_CONF_ERROR);
+
+ inaddr->addr = lscf[l].addr;
+ inaddr->default_server = lscf[l].default_server;
+ inaddr->core_srv_conf = cscfp[s];
+
+ /*
+ * create the empty list of the server names that
+ * can be served on this address:port
+ */
+
+ ngx_init_array(inaddr->names, cf->pool, 10,
+ sizeof(ngx_http_server_name_t),
+ NGX_CONF_ERROR);
+ }
+ }
+ }
+
+ if (!port_found) {
+
+ /* add the port to the in_port list */
+
+ ngx_test_null(in_port,
+ ngx_push_array(&in_ports),
+ NGX_CONF_ERROR);
+
+ in_port->port = lscf[l].port;
+
+ ngx_test_null(in_port->port_text.data, ngx_palloc(cf->pool, 7),
+ NGX_CONF_ERROR);
+ in_port->port_text.len = ngx_snprintf((char *)
+ in_port->port_text.data,
+ 7, ":%d",
+ in_port->port);
+
+ /* create list of the addresses that bound to this port ... */
+
+ ngx_init_array(in_port->addrs, cf->pool, 10,
+ sizeof(ngx_http_in_addr_t),
+ NGX_CONF_ERROR);
+
+ ngx_test_null(inaddr, ngx_push_array(&in_port->addrs),
+ NGX_CONF_ERROR);
+
+ /* ... and add the address to this list */
+
+ inaddr->addr = lscf[l].addr;
+ inaddr->default_server = lscf[l].default_server;
+ inaddr->core_srv_conf = cscfp[s];
+
+ /*
+ * create the empty list of the server names that
+ * can be served on this address:port
+ */
+
+ ngx_init_array(inaddr->names, cf->pool, 10,
+ sizeof(ngx_http_server_name_t),
+ NGX_CONF_ERROR);
+ }
+ }
+ }
+
+ /* optimize the lists of the ports, the addresses and the server names */
+
+ /* AF_INET only */
+
+ in_port = in_ports.elts;
+ for (p = 0; p < in_ports.nelts; p++) {
+
+ /* check whether the all server names point to the same server */
+
+ in_addr = in_port[p].addrs.elts;
+ for (a = 0; a < in_port[p].addrs.nelts; a++) {
+
+ virtual_names = 0;
+
+ name = in_addr[a].names.elts;
+ for (n = 0; n < in_addr[a].names.nelts; n++) {
+ if (in_addr[a].core_srv_conf != name[n].core_srv_conf) {
+ virtual_names = 1;
+ break;
+ }
+ }
+
+ /*
+ * if the all server names point to the same server
+ * then we do not need to check them at run-time
+ */
+
+ if (!virtual_names) {
+ in_addr[a].names.nelts = 0;
+ }
+ }
+
+ /*
+ * if there's the binding to "*:port" then we need to bind()
+ * to "*:port" only and ignore the other bindings
+ */
+
+ if (in_addr[a - 1].addr == INADDR_ANY) {
+ a--;
+
+ } else {
+ a = 0;
+ }
+
+ in_addr = in_port[p].addrs.elts;
+ while (a < in_port[p].addrs.nelts) {
+
+ ls = ngx_listening_inet_stream_socket(cf, in_addr[a].addr,
+ in_port[p].port);
+ if (ls == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ls->backlog = -1;
+#if 0
+#if 0
+ ls->nonblocking = 1;
+#else
+ ls->nonblocking = 0;
+#endif
+#endif
+ ls->addr_ntop = 1;
+
+ ls->handler = ngx_http_init_connection;
+
+ cscf = in_addr[a].core_srv_conf;
+ ls->pool_size = cscf->connection_pool_size;
+ ls->post_accept_timeout = cscf->post_accept_timeout;
+
+ clcf = cscf->ctx->loc_conf[ngx_http_core_module.ctx_index];
+ ls->log = clcf->err_log;
+
+#if (WIN32)
+ 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->ctx = ctx;
+
+ if (in_port[p].addrs.nelts > 1) {
+
+ in_addr = in_port[p].addrs.elts;
+ if (in_addr[in_port[p].addrs.nelts - 1].addr != INADDR_ANY) {
+
+ /*
+ * if this port has not the "*:port" binding then create
+ * the separate ngx_http_in_port_t for the all bindings
+ */
+
+ ngx_test_null(inport,
+ ngx_palloc(cf->pool,
+ sizeof(ngx_http_in_port_t)),
+ NGX_CONF_ERROR);
+
+ inport->port = in_port[p].port;
+ inport->port_text = in_port[p].port_text;
+
+ /* init list of the addresses ... */
+
+ ngx_init_array(inport->addrs, cf->pool, 1,
+ sizeof(ngx_http_in_addr_t),
+ NGX_CONF_ERROR);
+
+ /* ... and set up it with the first address */
+
+ inport->addrs.nelts = 1;
+ inport->addrs.elts = in_port[p].addrs.elts;
+
+ ls->servers = inport;
+
+ /* prepare for the next cycle */
+
+ in_port[p].addrs.elts = (char *) in_port[p].addrs.elts
+ + in_port[p].addrs.size;
+ in_port[p].addrs.nelts--;
+
+ in_addr = (ngx_http_in_addr_t *) in_port[p].addrs.elts;
+ a = 0;
+
+ continue;
+ }
+ }
+
+ ls->servers = &in_port[p];
+ a++;
+ }
+ }
+
+#if (NGX_DEBUG)
+ in_port = in_ports.elts;
+ for (p = 0; p < in_ports.nelts; p++) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, cf->log, 0,
+ "port: %d %08x", in_port[p].port, &in_port[p]);
+ in_addr = in_port[p].addrs.elts;
+ for (a = 0; a < in_port[p].addrs.nelts; a++) {
+ u_char ip[20];
+ ngx_inet_ntop(AF_INET, &in_addr[a].addr, ip, 20);
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, cf->log, 0,
+ "%s %08x", ip, in_addr[a].core_srv_conf);
+ s_name = in_addr[a].names.elts;
+ for (n = 0; n < in_addr[a].names.nelts; n++) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, cf->log, 0,
+ "%s %08x", s_name[n].name.data,
+ s_name[n].core_srv_conf);
+ }
+ }
+ }
+#endif
+
+ return NGX_CONF_OK;
+}
+
+
+static char *ngx_http_merge_locations(ngx_conf_t *cf,
+ ngx_array_t *locations,
+ void **loc_conf,
+ ngx_http_module_t *module,
+ ngx_uint_t ctx_index)
+{
+ char *rv;
+ ngx_uint_t i;
+ ngx_http_core_loc_conf_t **clcfp;
+
+ clcfp = /* (ngx_http_core_loc_conf_t **) */ locations->elts;
+
+ for (i = 0; i < locations->nelts; i++) {
+ rv = module->merge_loc_conf(cf, loc_conf[ctx_index],
+ clcfp[i]->loc_conf[ctx_index]);
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+
+ rv = ngx_http_merge_locations(cf, &clcfp[i]->locations,
+ clcfp[i]->loc_conf, module, ctx_index);
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h
new file mode 100644
index 000000000..303a4daef
--- /dev/null
+++ b/src/http/ngx_http.h
@@ -0,0 +1,112 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_H_INCLUDED_
+#define _NGX_HTTP_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_garbage_collector.h>
+
+typedef struct ngx_http_request_s ngx_http_request_t;
+typedef struct ngx_http_cleanup_s ngx_http_cleanup_t;
+
+#if (NGX_HTTP_CACHE)
+#include <ngx_http_cache.h>
+#endif
+/* STUB */
+#include <ngx_http_cache.h>
+
+#include <ngx_http_request.h>
+#include <ngx_http_config.h>
+#include <ngx_http_busy_lock.h>
+#include <ngx_http_log_handler.h>
+#include <ngx_http_core_module.h>
+
+#if (NGX_HTTP_SSL)
+#include <ngx_http_ssl_module.h>
+#endif
+
+
+typedef struct {
+ u_int connection;
+
+ /*
+ * we declare "action" as "char *" because the actions are usually
+ * the static strings and in the "u_char *" case we have to override
+ * all the time their types
+ */
+
+ char *action;
+ u_char *client;
+ u_char *url;
+} ngx_http_log_ctx_t;
+
+
+#define ngx_http_get_module_ctx(r, module) r->ctx[module.ctx_index]
+#define ngx_http_get_module_err_ctx(r, module) \
+ (r->err_ctx ? r->err_ctx[module.ctx_index] : r->ctx[module.ctx_index])
+
+#define ngx_http_create_ctx(r, cx, module, size, error) \
+ do { \
+ ngx_test_null(cx, ngx_pcalloc(r->pool, size), error); \
+ r->ctx[module.ctx_index] = cx; \
+ } while (0)
+
+#define ngx_http_delete_ctx(r, module) \
+ r->ctx[module.ctx_index] = NULL;
+
+
+void ngx_http_init_connection(ngx_connection_t *c);
+
+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_int_t ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b);
+
+ngx_int_t ngx_http_find_server_conf(ngx_http_request_t *r);
+void ngx_http_handler(ngx_http_request_t *r);
+void ngx_http_finalize_request(ngx_http_request_t *r, int error);
+void ngx_http_writer(ngx_event_t *wev);
+
+void ngx_http_empty_handler(ngx_event_t *wev);
+
+ngx_int_t ngx_http_send_last(ngx_http_request_t *r);
+void ngx_http_close_request(ngx_http_request_t *r, int error);
+void ngx_http_close_connection(ngx_connection_t *c);
+
+
+ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r);
+
+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, int error);
+
+
+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_body(ngx_http_request_t *r);
+
+
+extern ngx_module_t ngx_http_module;
+
+
+extern ngx_uint_t ngx_http_total_requests;
+extern uint64_t ngx_http_total_sent;
+
+
+extern ngx_http_output_header_filter_pt ngx_http_top_header_filter;
+extern ngx_http_output_body_filter_pt ngx_http_top_body_filter;
+
+
+/* STUB */
+ngx_int_t ngx_http_log_handler(ngx_http_request_t *r);
+/**/
+
+
+#endif /* _NGX_HTTP_H_INCLUDED_ */
diff --git a/src/http/ngx_http_busy_lock.c b/src/http/ngx_http_busy_lock.c
new file mode 100644
index 000000000..2b3ee105d
--- /dev/null
+++ b/src/http/ngx_http_busy_lock.c
@@ -0,0 +1,300 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+
+static int ngx_http_busy_lock_look_cachable(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++;
+
+ ngx_add_timer(bc->event, 1000);
+ bc->event->event_handler = bc->event_handler;
+
+ /* TODO: ngx_handle_level_read_event() */
+
+ return NGX_AGAIN;
+ }
+
+ return NGX_ERROR;
+}
+
+
+int ngx_http_busy_lock_cachable(ngx_http_busy_lock_t *bl,
+ ngx_http_busy_lock_ctx_t *bc, int lock)
+{
+ int rc;
+
+ rc = ngx_http_busy_lock_look_cachable(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) {
+ bl->waiting++;
+ ngx_add_timer(bc->event, 1000);
+ bc->event->event_handler = bc->event_handler;
+
+ /* 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->cachable--;
+ }
+
+ bl->busy--;
+}
+
+
+static int ngx_http_busy_lock_look_cachable(ngx_http_busy_lock_t *bl,
+ ngx_http_busy_lock_ctx_t *bc,
+ int lock)
+{
+ int i, b, cachable, free;
+ u_int mask;
+
+ b = 0;
+ cachable = 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;
+ }
+ cachable++;
+
+ } else if (free == -1) {
+ free = i;
+ }
+
+#if 1
+ if (cachable == bl->cachable) {
+ if (free == -1 && cachable < 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->cachable++;
+ 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() */
+ if (!(bl = ngx_pcalloc(cf->pool, sizeof(ngx_http_busy_lock_t)))) {
+ return NGX_CONF_ERROR;
+ }
+ *blp = bl;
+
+ /* ngx_calloc_shared() */
+ if (!(bl->mutex = ngx_pcalloc(cf->pool, sizeof(ngx_event_mutex_t)))) {
+ 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/src/http/ngx_http_busy_lock.h b/src/http/ngx_http_busy_lock.h
new file mode 100644
index 000000000..05e2667b9
--- /dev/null
+++ b/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 cachable;
+
+ 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_cachable(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/src/http/ngx_http_cache.c b/src/http/ngx_http_cache.c
new file mode 100644
index 000000000..22572a58b
--- /dev/null
+++ b/src/http/ngx_http_cache.c
@@ -0,0 +1,480 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+
+static ngx_http_module_t ngx_http_cache_module_ctx = {
+ NULL, /* pre conf */
+
+ 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_cache_module = {
+ NGX_MODULE,
+ &ngx_http_cache_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init module */
+ NULL /* init child */
+};
+
+
+ngx_http_cache_t *ngx_http_cache_get(ngx_http_cache_hash_t *hash,
+ ngx_http_cleanup_t *cleanup,
+ ngx_str_t *key, uint32_t *crc)
+{
+ ngx_uint_t i;
+ ngx_http_cache_t *c;
+
+ *crc = ngx_crc(key->data, key->len);
+
+ c = hash->elts + *crc % hash->hash * hash->nelts;
+
+ if (ngx_mutex_lock(&hash->mutex) == NGX_ERROR) {
+ return (void *) NGX_ERROR;
+ }
+
+ for (i = 0; i < hash->nelts; i++) {
+ if (c[i].crc == *crc
+ && c[i].key.len == key->len
+ && ngx_rstrncmp(c[i].key.data, key->data, key->len) == 0)
+ {
+#if 0
+ if (c[i].expired) {
+ ngx_mutex_unlock(&hash->mutex);
+ return (void *) NGX_AGAIN;
+ }
+#endif
+
+ c[i].refs++;
+
+ if ((!(c[i].notify && (ngx_event_flags & NGX_HAVE_KQUEUE_EVENT)))
+ && (ngx_cached_time - c[i].updated >= hash->update))
+ {
+ c[i].expired = 1;
+ }
+
+ ngx_mutex_unlock(&hash->mutex);
+
+ if (cleanup) {
+ cleanup->data.cache.hash = hash;
+ cleanup->data.cache.cache = &c[i];
+ cleanup->valid = 1;
+ cleanup->cache = 1;
+ }
+
+ return &c[i];
+ }
+ }
+
+ ngx_mutex_unlock(&hash->mutex);
+
+ return NULL;
+}
+
+
+ngx_http_cache_t *ngx_http_cache_alloc(ngx_http_cache_hash_t *hash,
+ ngx_http_cache_t *cache,
+ ngx_http_cleanup_t *cleanup,
+ ngx_str_t *key, uint32_t crc,
+ ngx_str_t *value, ngx_log_t *log)
+{
+ time_t old;
+ ngx_uint_t i;
+ ngx_http_cache_t *c;
+
+ old = ngx_cached_time + 1;
+
+ c = hash->elts + crc % hash->hash * hash->nelts;
+
+ if (ngx_mutex_lock(&hash->mutex) == NGX_ERROR) {
+ return (void *) NGX_ERROR;
+ }
+
+ if (cache == NULL) {
+
+ /* allocate a new entry */
+
+ for (i = 0; i < hash->nelts; i++) {
+ if (c[i].refs > 0) {
+ /* a busy entry */
+ continue;
+ }
+
+ if (c[i].key.len == 0) {
+ /* a free entry is found */
+ cache = &c[i];
+ break;
+ }
+
+ /* looking for the oldest cache entry */
+
+ if (old > c[i].accessed) {
+
+ old = c[i].accessed;
+ cache = &c[i];
+ }
+ }
+
+ if (cache == NULL) {
+ ngx_mutex_unlock(&hash->mutex);
+ return NULL;
+ }
+
+ ngx_http_cache_free(cache, key, value, log);
+
+ if (cache->key.data == NULL) {
+ cache->key.data = ngx_alloc(key->len, log);
+ if (cache->key.data == NULL) {
+ ngx_http_cache_free(cache, NULL, NULL, log);
+ ngx_mutex_unlock(&hash->mutex);
+ return NULL;
+ }
+ }
+
+ cache->key.len = key->len;
+ ngx_memcpy(cache->key.data, key->data, key->len);
+
+ } else if (value) {
+ ngx_http_cache_free(cache, key, value, log);
+ }
+
+ if (value) {
+ if (cache->data.value.data == NULL) {
+ cache->data.value.data = ngx_alloc(value->len, log);
+ if (cache->data.value.data == NULL) {
+ ngx_http_cache_free(cache, NULL, NULL, log);
+ ngx_mutex_unlock(&hash->mutex);
+ return NULL;
+ }
+ }
+
+ cache->data.value.len = value->len;
+ ngx_memcpy(cache->data.value.data, value->data, value->len);
+ }
+
+ cache->crc = crc;
+ cache->key.len = key->len;
+
+ cache->refs = 1;
+ cache->count = 0;
+
+ cache->deleted = 0;
+ cache->expired = 0;
+ cache->memory = 0;
+ cache->mmap = 0;
+ cache->notify = 0;
+
+ if (cleanup) {
+ cleanup->data.cache.hash = hash;
+ cleanup->data.cache.cache = cache;
+ cleanup->valid = 1;
+ cleanup->cache = 1;
+ }
+
+ ngx_mutex_unlock(&hash->mutex);
+
+ return cache;
+}
+
+
+void ngx_http_cache_free(ngx_http_cache_t *cache,
+ ngx_str_t *key, ngx_str_t *value, ngx_log_t *log)
+{
+ if (cache->memory) {
+ if (cache->data.value.data
+ && (value == NULL || value->len > cache->data.value.len))
+ {
+ ngx_free(cache->data.value.data);
+ cache->data.value.data = NULL;
+ }
+ }
+
+ /* TODO: mmap */
+
+ cache->data.value.len = 0;
+
+ if (cache->fd != NGX_INVALID_FILE) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "http cache close fd: %d", cache->fd);
+
+ if (ngx_close_file(cache->fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed",
+ cache->key.data);
+ }
+
+ cache->fd = NGX_INVALID_FILE;
+ }
+
+ if (cache->key.data && (key == NULL || key->len > cache->key.len)) {
+ ngx_free(cache->key.data);
+ cache->key.data = NULL;
+ }
+
+ cache->key.len = 0;
+
+ cache->refs = 0;
+}
+
+
+void ngx_http_cache_lock(ngx_http_cache_hash_t *hash, ngx_http_cache_t *cache)
+{
+ if (ngx_mutex_lock(&hash->mutex) == NGX_ERROR) {
+ return;
+ }
+}
+
+
+void ngx_http_cache_unlock(ngx_http_cache_hash_t *hash,
+ ngx_http_cache_t *cache, ngx_log_t *log)
+{
+ if (ngx_mutex_lock(&hash->mutex) == NGX_ERROR) {
+ return;
+ }
+
+ cache->refs--;
+
+ if (cache->refs == 0 && cache->deleted) {
+ ngx_http_cache_free(cache, NULL, NULL, log);
+ }
+
+ ngx_mutex_unlock(&hash->mutex);
+}
+
+
+#if 0
+
+ngx_http_cache_add_file_event(ngx_http_cache_hash_t *hash,
+ ngx_http_cache_t *cache)
+{
+ ngx_event_t *ev;
+ ngx_http_cache_event_ctx_t *ctx;
+
+ ev = &ngx_cycle->read_events[fd];
+ ngx_memzero(ev, sizeof(ngx_event_t);
+
+ ev->data = data;
+ ev->event_handler = ngx_http_cache_invalidate;
+
+ return ngx_add_event(ev, NGX_VNODE_EVENT, 0);
+}
+
+
+void ngx_http_cache_invalidate(ngx_event_t *ev)
+{
+ ngx_http_cache_event_ctx_t *ctx;
+
+ ctx = ev->data;
+
+ ngx_http_cache_lock(&ctx->hash->mutex);
+
+ if (ctx->cache->refs == 0)
+ ngx_http_cache_free(ctx->cache, NULL, NULL, ctx->log);
+
+ } else {
+ ctx->cache->deleted = 1;
+ }
+
+ ngx_http_cache_unlock(&ctx->hash->mutex);
+}
+
+#endif
+
+
+/* TODO: currently fd only */
+
+ngx_int_t ngx_http_send_cached(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_hunk_t *h;
+ ngx_chain_t out;
+ ngx_http_log_ctx_t *ctx;
+
+ ctx = r->connection->log->data;
+ ctx->action = "sending response to client";
+
+ r->headers_out.status = NGX_HTTP_OK;
+ r->headers_out.content_length_n = r->cache->data.size;
+ r->headers_out.last_modified_time = r->cache->last_modified;
+
+ if (ngx_http_set_content_type(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* we need to allocate all before the header would be sent */
+
+ if (!(h = ngx_pcalloc(r->pool, sizeof(ngx_hunk_t)))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (!(h->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t)))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ return rc;
+ }
+
+ h->type = r->main ? NGX_HUNK_FILE : NGX_HUNK_FILE|NGX_HUNK_LAST;
+
+ h->file_pos = 0;
+ h->file_last = r->cache->data.size;
+
+ h->file->fd = r->cache->fd;
+ h->file->log = r->connection->log;
+
+ out.hunk = h;
+ out.next = NULL;
+
+ return ngx_http_output_filter(r, &out);
+}
+
+
+char *ngx_http_set_cache_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *p = conf;
+
+ ngx_int_t i, j, dup, invalid;
+ ngx_str_t *value, line;
+ ngx_http_cache_t *c;
+ ngx_http_cache_hash_t *ch, **chp;
+
+ chp = (ngx_http_cache_hash_t **) (p + cmd->offset);
+ if (*chp) {
+ return "is duplicate";
+ }
+
+ if (!(ch = ngx_pcalloc(cf->pool, sizeof(ngx_http_cache_hash_t)))) {
+ return NGX_CONF_ERROR;
+ }
+ *chp = ch;
+
+ 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 'h':
+ if (ch->hash) {
+ dup = 1;
+ break;
+ }
+
+ ch->hash = ngx_atoi(value[i].data + 2, value[i].len - 2);
+ if (ch->hash == (size_t) NGX_ERROR || ch->hash == 0) {
+ invalid = 1;
+ break;
+ }
+
+ continue;
+
+ case 'n':
+ if (ch->nelts) {
+ dup = 1;
+ break;
+ }
+
+ ch->nelts = ngx_atoi(value[i].data + 2, value[i].len - 2);
+ if (ch->nelts == (size_t) NGX_ERROR || ch->nelts == 0) {
+ invalid = 1;
+ break;
+ }
+
+ continue;
+
+ case 'l':
+ if (ch->life) {
+ dup = 1;
+ break;
+ }
+
+ line.len = value[i].len - 2;
+ line.data = value[i].data + 2;
+
+ ch->life = ngx_parse_time(&line, 1);
+ if (ch->life == NGX_ERROR || ch->life == 0) {
+ invalid = 1;
+ break;
+ }
+
+ continue;
+
+ case 'u':
+ if (ch->update) {
+ dup = 1;
+ break;
+ }
+
+ line.len = value[i].len - 2;
+ line.data = value[i].data + 2;
+
+ ch->update = ngx_parse_time(&line, 1);
+ if (ch->update == NGX_ERROR || ch->update == 0) {
+ 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;
+ }
+ }
+
+ ch->elts = ngx_pcalloc(cf->pool,
+ ch->hash * ch->nelts * sizeof(ngx_http_cache_t));
+ if (ch->elts == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 0; i < (ngx_int_t) ch->hash; i++) {
+ c = ch->elts + i * ch->nelts;
+
+ for (j = 0; j < (ngx_int_t) ch->nelts; j++) {
+ c[j].fd = NGX_INVALID_FILE;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/src/http/ngx_http_cache.h b/src/http/ngx_http_cache.h
new file mode 100644
index 000000000..40f4852dd
--- /dev/null
+++ b/src/http/ngx_http_cache.h
@@ -0,0 +1,131 @@
+
+/*
+ * 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>
+
+
+/*
+ * The 7 uses before an allocation.
+ * We can use maximum 7 bits, i.e up to the 127 uses.
+ */
+#define NGX_HTTP_CACHE_LAZY_ALLOCATION_BITS 3
+
+typedef struct {
+ uint32_t crc;
+ ngx_str_t key;
+ time_t accessed;
+
+ unsigned refs:20; /* 1048576 references */
+
+ unsigned count:NGX_HTTP_CACHE_LAZY_ALLOCATION_BITS;
+
+ unsigned deleted:1;
+ unsigned expired:1;
+ unsigned memory:1;
+ unsigned mmap:1;
+ unsigned notify:1;
+
+ ngx_fd_t fd;
+#if (NGX_USE_HTTP_FILE_CACHE_UNIQ)
+ ngx_file_uniq_t uniq; /* no needed with kqueue */
+#endif
+ time_t last_modified;
+ time_t updated;
+
+ union {
+ off_t size;
+ ngx_str_t value;
+ } data;
+} ngx_http_cache_t;
+
+
+typedef struct {
+ time_t expires;
+ time_t last_modified;
+ time_t date;
+ off_t length;
+ size_t key_len;
+ char key[1];
+} ngx_http_cache_header_t;
+
+
+#define NGX_HTTP_CACHE_HASH 7
+#define NGX_HTTP_CACHE_NELTS 4
+
+typedef struct {
+ ngx_http_cache_t *elts;
+ size_t hash;
+ size_t nelts;
+ time_t life;
+ time_t update;
+#if (NGX_THREADS)
+ ngx_mutex_t mutex;
+#endif
+ ngx_pool_t *pool;
+} ngx_http_cache_hash_t;
+
+
+typedef struct {
+ ngx_http_cache_hash_t *hash;
+ ngx_http_cache_t *cache;
+ ngx_file_t file;
+ ngx_str_t key;
+ uint32_t crc;
+ u_char md5[16];
+ ngx_path_t *path;
+ ngx_buf_t *buf;
+ time_t expires;
+ time_t last_modified;
+ time_t date;
+ off_t length;
+ ssize_t header_size;
+ size_t file_start;
+ ngx_log_t *log;
+} ngx_http_cache_ctx_t;
+
+
+
+#define NGX_HTTP_CACHE_STALE 1
+#define NGX_HTTP_CACHE_AGED 2
+#define NGX_HTTP_CACHE_THE_SAME 3
+
+
+ngx_http_cache_t *ngx_http_cache_get(ngx_http_cache_hash_t *cache,
+ ngx_http_cleanup_t *cleanup,
+ ngx_str_t *key, uint32_t *crc);
+
+ngx_http_cache_t *ngx_http_cache_alloc(ngx_http_cache_hash_t *hash,
+ ngx_http_cache_t *cache,
+ ngx_http_cleanup_t *cleanup,
+ ngx_str_t *key, uint32_t crc,
+ ngx_str_t *value, ngx_log_t *log);
+void ngx_http_cache_free(ngx_http_cache_t *cache,
+ ngx_str_t *key, ngx_str_t *value, ngx_log_t *log);
+void ngx_http_cache_lock(ngx_http_cache_hash_t *hash, ngx_http_cache_t *cache);
+void ngx_http_cache_unlock(ngx_http_cache_hash_t *hash,
+ ngx_http_cache_t *cache, ngx_log_t *log);
+
+int ngx_http_cache_get_file(ngx_http_request_t *r, ngx_http_cache_ctx_t *ctx);
+int ngx_http_cache_open_file(ngx_http_cache_ctx_t *ctx, ngx_file_uniq_t uniq);
+int ngx_http_cache_update_file(ngx_http_request_t *r,ngx_http_cache_ctx_t *ctx,
+ ngx_str_t *temp_file);
+
+int ngx_http_send_cached(ngx_http_request_t *r);
+
+
+int ngx_garbage_collector_http_cache_handler(ngx_gc_t *gc, ngx_str_t *name,
+ ngx_dir_t *dir);
+
+char *ngx_http_set_cache_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+#endif /* _NGX_HTTP_CACHE_H_INCLUDED_ */
diff --git a/src/http/ngx_http_config.h b/src/http/ngx_http_config.h
new file mode 100644
index 000000000..be0052e59
--- /dev/null
+++ b/src/http/ngx_http_config.h
@@ -0,0 +1,70 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_CONFIG_H_INCLUDED_
+#define _NGX_HTTP_CONFIG_H_INCLUDED_
+
+
+#include <ngx_alloc.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 (*pre_conf)(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_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]
+
+/*
+ * ngx_http_conf_get_module_srv_conf() and ngx_http_conf_get_module_loc_conf()
+ * must not be used at the merge phase because cf->ctx points to http{}'s ctx
+ */
+
+#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) \
+ ((ngx_http_conf_ctx_t *) \
+ cycle->conf_ctx[ngx_http_module.index])->main_conf[module.ctx_index]
+
+
+
+#endif /* _NGX_HTTP_CONFIG_H_INCLUDED_ */
diff --git a/src/http/ngx_http_copy_filter.c b/src/http/ngx_http_copy_filter.c
new file mode 100644
index 000000000..d015c2144
--- /dev/null
+++ b/src/http/ngx_http_copy_filter.c
@@ -0,0 +1,132 @@
+
+/*
+ * 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;
+
+
+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_cycle_t *cycle);
+
+
+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, /* pre conf */
+
+ 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,
+ &ngx_http_copy_filter_module_ctx, /* module context */
+ ngx_http_copy_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ ngx_http_copy_filter_init, /* init module */
+ NULL /* init process */
+};
+
+
+static ngx_http_output_body_filter_pt ngx_http_next_filter;
+
+
+ngx_int_t ngx_http_copy_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ ngx_output_chain_ctx_t *ctx;
+ ngx_http_copy_filter_conf_t *conf;
+
+ if (r->connection->write->error) {
+ return NGX_ERROR;
+ }
+
+ ctx = ngx_http_get_module_ctx(r->main ? r->main : r,
+ ngx_http_copy_filter_module);
+
+ if (ctx == NULL) {
+ conf = ngx_http_get_module_loc_conf(r->main ? r->main : r,
+ ngx_http_copy_filter_module);
+
+ ngx_http_create_ctx(r, ctx, ngx_http_copy_filter_module,
+ sizeof(ngx_output_chain_ctx_t), NGX_ERROR);
+
+ ctx->sendfile = r->sendfile;
+ ctx->need_in_memory = r->filter_need_in_memory;
+ ctx->need_in_temp = r->filter_need_temporary;
+
+ 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;
+
+ }
+
+ return ngx_output_chain(ctx, in);
+}
+
+
+static void *ngx_http_copy_filter_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_copy_filter_conf_t *conf;
+
+ ngx_test_null(conf,
+ ngx_palloc(cf->pool, sizeof(ngx_http_copy_filter_conf_t)),
+ 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_cycle_t *cycle)
+{
+ ngx_http_next_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_copy_filter;
+
+ return NGX_OK;
+}
+
diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c
new file mode 100644
index 000000000..6df6edf45
--- /dev/null
+++ b/src/http/ngx_http_core_module.c
@@ -0,0 +1,1819 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+/* STUB */
+#define NGX_HTTP_LOCATION_EXACT 1
+#define NGX_HTTP_LOCATION_AUTO_REDIRECT 2
+#define NGX_HTTP_LOCATION_REGEX 3
+
+
+static void ngx_http_phase_event_handler(ngx_event_t *rev);
+static void ngx_http_run_phases(ngx_http_request_t *r);
+static ngx_int_t ngx_http_find_location(ngx_http_request_t *r,
+ ngx_array_t *locations, size_t len);
+
+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_server_block(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy);
+static int ngx_cmp_locations(const void *first, const void *second);
+static char *ngx_location_block(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *dummy);
+static char *ngx_types_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_set_type(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
+static char *ngx_set_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_set_server_name(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_set_root(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_set_error_page(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_set_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_set_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+static char *ngx_http_lowat_check(ngx_conf_t *cf, void *post, void *data);
+
+static ngx_conf_post_t ngx_http_lowat_post = { ngx_http_lowat_check } ;
+
+
+static ngx_conf_enum_t ngx_http_restrict_host_names[] = {
+ { ngx_string("off"), NGX_HTTP_RESTRICT_HOST_OFF },
+ { ngx_string("on"), NGX_HTTP_RESTRICT_HOST_ON },
+ { ngx_string("close"), NGX_HTTP_RESTRICT_HOST_CLOSE },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t ngx_http_core_commands[] = {
+
+ { ngx_string("server"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+ ngx_server_block,
+ 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),
+ NULL },
+
+ { ngx_string("post_accept_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, post_accept_timeout),
+ NULL },
+
+ { 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),
+ NULL },
+
+ { 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("restrict_host_names"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_core_srv_conf_t, restrict_host_names),
+ &ngx_http_restrict_host_names },
+
+ { ngx_string("location"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
+ ngx_location_block,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("listen"),
+#if 0
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+#else
+ NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+#endif
+ ngx_set_listen,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("server_name"),
+ NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
+ ngx_set_server_name,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("types"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
+ |NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+ ngx_types_block,
+ 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_CONF_TAKE1,
+ ngx_set_root,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("alias"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_set_root,
+ 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_size_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("sendfile"),
+ 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, sendfile),
+ 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("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_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_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, limit_rate),
+ NULL },
+
+ { ngx_string("keepalive_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+ ngx_set_keepalive,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { 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("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("error_page"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_2MORE,
+ ngx_set_error_page,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("error_log"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_set_error_log,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+#if (NGX_HTTP_CACHE)
+
+ { ngx_string("open_file_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE4,
+ ngx_http_set_cache_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_core_loc_conf_t, open_files),
+ NULL },
+
+#endif
+
+ ngx_null_command
+};
+
+
+ngx_http_module_t ngx_http_core_module_ctx = {
+ NULL, /* pre conf */
+
+ 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,
+ &ngx_http_core_module_ctx, /* module context */
+ ngx_http_core_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init module */
+ NULL /* init process */
+};
+
+
+void ngx_http_handler(ngx_http_request_t *r)
+{
+ ngx_http_log_ctx_t *lcx;
+
+ r->connection->unexpected_eof = 0;
+
+ lcx = r->connection->log->data;
+ lcx->action = NULL;
+
+ switch (r->headers_in.connection_type) {
+ case 0:
+ if (r->http_version > NGX_HTTP_VERSION_10) {
+ r->keepalive = 1;
+ } else {
+ r->keepalive = 0;
+ }
+ break;
+
+ case NGX_HTTP_CONNECTION_CLOSE:
+ r->keepalive = 0;
+ break;
+
+ case NGX_HTTP_CONNECTION_KEEP_ALIVE:
+ r->keepalive = 1;
+ break;
+ }
+
+ if (r->keepalive && r->headers_in.msie && r->method == NGX_HTTP_POST) {
+
+ /*
+ * MSIE may wait for some time if the response for the POST request
+ * is sent over the keepalive connection
+ */
+
+ r->keepalive = 0;
+ }
+
+#if 0
+ /* TEST STUB */ r->http_version = NGX_HTTP_VERSION_10;
+ /* TEST STUB */ r->keepalive = 0;
+#endif
+
+ if (r->headers_in.content_length_n > 0) {
+ r->lingering_close = 1;
+
+ } else {
+ r->lingering_close = 0;
+ }
+
+#if 0
+ /* TEST STUB */ r->lingering_close = 1;
+#endif
+
+ r->connection->write->event_handler = ngx_http_phase_event_handler;
+
+ ngx_http_run_phases(r);
+
+ return;
+}
+
+
+static void ngx_http_phase_event_handler(ngx_event_t *ev)
+{
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+
+ c = ev->data;
+ r = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0, "phase event handler");
+
+ ngx_http_run_phases(r);
+
+ return;
+}
+
+
+static void ngx_http_run_phases(ngx_http_request_t *r)
+{
+ char *path;
+ ngx_int_t rc;
+ ngx_http_handler_pt *h;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ for (/* void */; r->phase < NGX_HTTP_LAST_PHASE; r->phase++) {
+
+ if (r->phase == NGX_HTTP_CONTENT_PHASE && r->content_handler) {
+ r->connection->write->event_handler = ngx_http_empty_handler;
+ rc = r->content_handler(r);
+ ngx_http_finalize_request(r, rc);
+ return;
+ }
+
+ h = cmcf->phases[r->phase].handlers.elts;
+ for (r->phase_handler = cmcf->phases[r->phase].handlers.nelts - 1;
+ r->phase_handler >= 0;
+ r->phase_handler--)
+ {
+ rc = h[r->phase_handler](r);
+
+ if (rc == NGX_DONE) {
+
+ /*
+ * we should never use r here because
+ * it could point to already freed data
+ */
+
+ return;
+ }
+
+ if (rc == NGX_DECLINED) {
+ continue;
+ }
+
+ if (rc >= NGX_HTTP_SPECIAL_RESPONSE || rc == NGX_ERROR) {
+ ngx_http_finalize_request(r, rc);
+ return;
+ }
+
+ if (r->phase == NGX_HTTP_CONTENT_PHASE) {
+ ngx_http_finalize_request(r, rc);
+ return;
+ }
+
+ if (rc == NGX_AGAIN) {
+ return;
+ }
+
+ if (rc == NGX_OK && cmcf->phases[r->phase].type == NGX_OK) {
+ break;
+ }
+ }
+ }
+
+
+ if (r->uri.data[r->uri.len - 1] == '/') {
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (!(path = ngx_palloc(r->pool, clcf->root.len + r->uri.len))) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ ngx_cpystrn(ngx_cpymem(path, clcf->root.data, clcf->root.len),
+ r->uri.data, r->uri.len + 1);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "directory index of \"%s\" is forbidden", path);
+
+ ngx_http_finalize_request(r, NGX_HTTP_FORBIDDEN);
+ return;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no handler found");
+
+ ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
+ return;
+}
+
+
+ngx_int_t ngx_http_find_location_config(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_core_srv_conf_t *cscf;
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ rc = ngx_http_find_location(r, &cscf->locations, 0);
+
+ if (rc == NGX_HTTP_INTERNAL_SERVER_ERROR) {
+ return rc;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ r->connection->log->file = clcf->err_log->file;
+ if (!(r->connection->log->log_level & NGX_LOG_DEBUG_CONNECTION)) {
+ r->connection->log->log_level = clcf->err_log->log_level;
+ }
+
+ if (!(ngx_io.flags & NGX_IO_SENDFILE) || !clcf->sendfile) {
+ r->sendfile = 0;
+
+ } else {
+ r->sendfile = 1;
+ }
+
+ if (!clcf->tcp_nopush) {
+ /* disable TCP_NOPUSH/TCP_CORK use */
+ r->connection->tcp_nopush = NGX_TCP_NOPUSH_DISABLED;
+ }
+
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http cl: " SIZE_T_FMT " max: " SIZE_T_FMT,
+ r->headers_in.content_length_n,
+ clcf->client_max_body_size);
+
+ if (r->headers_in.content_length_n != -1
+ && clcf->client_max_body_size
+ && clcf->client_max_body_size < (size_t) r->headers_in.content_length_n)
+ {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client intented to send too large body: "
+ SIZE_T_FMT " bytes",
+ r->headers_in.content_length_n);
+
+ return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+
+
+ if (rc == NGX_HTTP_LOCATION_AUTO_REDIRECT) {
+ 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->value = clcf->name;
+
+ return NGX_HTTP_MOVED_PERMANENTLY;
+ }
+
+ if (clcf->handler) {
+ r->content_handler = clcf->handler;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t ngx_http_find_location(ngx_http_request_t *r,
+ ngx_array_t *locations, size_t len)
+{
+ ngx_int_t n, rc;
+ ngx_uint_t i, found;
+ ngx_http_core_loc_conf_t *clcf, **clcfp;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "find location");
+
+ found = 0;
+
+ clcfp = locations->elts;
+ for (i = 0; i < locations->nelts; i++) {
+
+#if (HAVE_PCRE)
+ if (clcfp[i]->regex) {
+ break;
+ }
+#endif
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "find location: %s\"%s\"",
+ clcfp[i]->exact_match ? "= " : "",
+ clcfp[i]->name.data);
+
+ if (clcfp[i]->auto_redirect
+ && r->uri.len == clcfp[i]->name.len - 1
+ && ngx_strncmp(r->uri.data, clcfp[i]->name.data,
+ clcfp[i]->name.len - 1) == 0)
+ {
+ /* the locations are lexicographically sorted */
+
+ r->loc_conf = clcfp[i]->loc_conf;
+
+ return NGX_HTTP_LOCATION_AUTO_REDIRECT;
+ }
+
+ if (r->uri.len < clcfp[i]->name.len) {
+ continue;
+ }
+
+ n = ngx_strncmp(r->uri.data, clcfp[i]->name.data, clcfp[i]->name.len);
+
+ if (n < 0) {
+ /* the locations are lexicographically sorted */
+ break;
+ }
+
+ if (n == 0) {
+ if (clcfp[i]->exact_match && r->uri.len == clcfp[i]->name.len) {
+ r->loc_conf = clcfp[i]->loc_conf;
+ return NGX_HTTP_LOCATION_EXACT;
+ }
+
+ if (len > clcfp[i]->name.len) {
+ /* the previous match is longer */
+ break;
+ }
+
+ r->loc_conf = clcfp[i]->loc_conf;
+ found = 1;
+ }
+ }
+
+ if (found) {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->locations.nelts) {
+ rc = ngx_http_find_location(r, &clcf->locations, len);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+ }
+ }
+
+#if (HAVE_PCRE)
+
+ /* regex matches */
+
+ for (/* void */; i < locations->nelts; i++) {
+
+ if (!clcfp[i]->regex) {
+ continue;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "find location: ~ \"%s\"",
+ clcfp[i]->name.data);
+
+ n = ngx_regex_exec(clcfp[i]->regex, &r->uri, NULL, 0);
+
+ if (n == NGX_DECLINED) {
+ continue;
+ }
+
+ if (n < 0) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ ngx_regex_exec_n
+ " failed: %d on \"%s\" using \"%s\"",
+ n, r->uri.data, clcfp[i]->name.data);
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* match */
+
+ r->loc_conf = clcfp[i]->loc_conf;
+
+ return NGX_HTTP_LOCATION_REGEX;
+ }
+
+#endif /* HAVE_PCRE */
+
+ return NGX_OK;
+}
+
+
+ngx_int_t ngx_http_set_content_type(ngx_http_request_t *r)
+{
+ uint32_t key;
+ ngx_uint_t i;
+ ngx_http_type_t *type;
+ ngx_http_core_loc_conf_t *clcf;
+
+ r->headers_out.content_type = ngx_list_push(&r->headers_out.headers);
+ if (r->headers_out.content_type == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ r->headers_out.content_type->key.len = 0;
+ r->headers_out.content_type->key.data = NULL;
+ r->headers_out.content_type->value.len = 0;
+ r->headers_out.content_type->value.data = NULL;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (r->exten.len) {
+#if 0
+ key = ngx_crc(r->exten.data, r->exten.key);
+#endif
+ ngx_http_types_hash_key(key, r->exten);
+
+ type = clcf->types[key].elts;
+ for (i = 0; i < clcf->types[key].nelts; i++) {
+ if (r->exten.len != type[i].exten.len) {
+ continue;
+ }
+
+ if (ngx_memcmp(r->exten.data, type[i].exten.data, r->exten.len)
+ == 0)
+ {
+ r->headers_out.content_type->value = type[i].type;
+ break;
+ }
+ }
+ }
+
+ if (r->headers_out.content_type->value.len == 0) {
+ r->headers_out.content_type->value = clcf->default_type;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t ngx_http_send_header(ngx_http_request_t *r)
+{
+ if (r->main) {
+ return NGX_OK;
+ }
+
+ if (r->err_ctx) {
+ 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;
+
+ if (r->connection->write->error) {
+ return NGX_ERROR;
+ }
+
+ rc = ngx_http_top_body_filter(r, in);
+
+ if (rc == NGX_ERROR) {
+
+ /* NGX_ERROR could be returned by any filter */
+
+ r->connection->write->error = 1;
+ }
+
+ return rc;
+}
+
+
+int ngx_http_redirect(ngx_http_request_t *r, int redirect)
+{
+ /* STUB */
+
+ /* log request */
+
+ ngx_http_close_request(r, 0);
+ return NGX_OK;
+}
+
+
+ngx_int_t ngx_http_set_exten(ngx_http_request_t *r)
+{
+ ngx_int_t i;
+
+ r->exten.len = 0;
+ r->exten.data = NULL;
+
+ 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;
+
+ if (r->exten.len > 0) {
+ if (!(r->exten.data = ngx_palloc(r->pool, r->exten.len + 1))) {
+ return NGX_ERROR;
+ }
+
+ ngx_cpystrn(r->exten.data, &r->uri.data[i + 1],
+ r->exten.len + 1);
+ }
+
+ break;
+
+ } else if (r->uri.data[i] == '/') {
+ break;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t ngx_http_internal_redirect(ngx_http_request_t *r,
+ ngx_str_t *uri, ngx_str_t *args)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "internal redirect: \"%s\"", uri->data);
+
+ r->uri.len = uri->len;
+ r->uri.data = uri->data;
+
+ if (args) {
+ r->args.len = args->len;
+ r->args.data = args->data;
+ }
+
+ if (ngx_http_set_exten(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (r->err_ctx) {
+
+ /* allocate the new modules contexts */
+
+ r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
+ if (r->ctx == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ } else {
+
+ /* clear the modules contexts */
+
+ ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module);
+ }
+
+ r->phase = 0;
+ r->phase_handler = 0;
+
+ ngx_http_handler(r);
+
+ return NGX_DONE;
+}
+
+
+#if 0 /* STUB: test the delay http handler */
+
+int ngx_http_delay_handler(ngx_http_request_t *r)
+{
+ static int on;
+
+ if (on++ == 0) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http set delay");
+ ngx_add_timer(r->connection->write, 10000);
+ return NGX_AGAIN;
+ }
+
+ r->connection->write->timedout = 0;
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http reset delay");
+ return NGX_DECLINED;
+}
+
+#endif
+
+
+#if 0
+
+static ngx_int_t ngx_http_core_init_process(ngx_cycle_t *cycle)
+{
+ ngx_uint_t i;
+ ngx_http_core_srv_conf_t **cscfp;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_core_module);
+
+#if 0
+ ngx_http_core_init_module:
+
+ ngx_http_handler_pt *h;
+
+ ngx_test_null(h, ngx_push_array(
+ &cmcf->phases[NGX_HTTP_TRANSLATE_PHASE].handlers),
+ NGX_ERROR);
+ *h = ngx_http_delay_handler;
+#endif
+
+ cscfp = cmcf->servers.elts;
+
+ for (i = 0; i < cmcf->servers.nelts; i++) {
+ if (cscfp[i]->recv == NULL) {
+ cscfp[i]->recv = ngx_io.recv;
+ cscfp[i]->send_chain = ngx_io.send_chain;
+ }
+ }
+
+ return NGX_OK;
+}
+
+#endif
+
+
+static char *ngx_server_block(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
+{
+ int m;
+ char *rv;
+ ngx_http_module_t *module;
+ ngx_conf_t pvcf;
+ ngx_http_conf_ctx_t *ctx, *http_ctx;
+ ngx_http_core_main_conf_t *cmcf;
+ ngx_http_core_srv_conf_t *cscf, **cscfp;
+
+ ngx_test_null(ctx,
+ ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)),
+ NGX_CONF_ERROR);
+
+ http_ctx = cf->ctx;
+ ctx->main_conf = http_ctx->main_conf;
+
+ /* the server{}'s srv_conf */
+
+ ngx_test_null(ctx->srv_conf,
+ ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module),
+ NGX_CONF_ERROR);
+
+ /* the server{}'s loc_conf */
+
+ ngx_test_null(ctx->loc_conf,
+ ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module),
+ 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) {
+ ngx_test_null(ctx->srv_conf[ngx_modules[m]->ctx_index],
+ module->create_srv_conf(cf),
+ NGX_CONF_ERROR);
+ }
+
+ if (module->create_loc_conf) {
+ ngx_test_null(ctx->loc_conf[ngx_modules[m]->ctx_index],
+ module->create_loc_conf(cf),
+ NGX_CONF_ERROR);
+ }
+ }
+
+ /* create links of the srv_conf's */
+
+ cscf = ctx->srv_conf[ngx_http_core_module.ctx_index];
+ cscf->ctx = ctx;
+
+ cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
+ ngx_test_null(cscfp, ngx_push_array(&cmcf->servers), NGX_CONF_ERROR);
+ *cscfp = cscf;
+
+ /* parse inside server{} */
+
+ pvcf = *cf;
+ cf->ctx = ctx;
+ cf->cmd_type = NGX_HTTP_SRV_CONF;
+ rv = ngx_conf_parse(cf, NULL);
+ *cf = pvcf;
+
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+
+ ngx_qsort(cscf->locations.elts, (size_t) cscf->locations.nelts,
+ sizeof(ngx_http_core_loc_conf_t *), ngx_cmp_locations);
+
+ return rv;
+}
+
+
+static int ngx_cmp_locations(const void *one, const void *two)
+{
+ ngx_int_t rc;
+ ngx_http_core_loc_conf_t *first, *second;
+
+ first = *(ngx_http_core_loc_conf_t **) one;
+ second = *(ngx_http_core_loc_conf_t **) two;
+
+#if (HAVE_PCRE)
+
+ 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 && second->exact_match) {
+ /* an exact match must be before the same inclusive one */
+ return 1;
+ }
+
+ return rc;
+}
+
+
+static char *ngx_location_block(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
+{
+ char *rv;
+ ngx_int_t m;
+ ngx_str_t *value;
+ ngx_conf_t pcf;
+ ngx_http_module_t *module;
+ ngx_http_conf_ctx_t *ctx, *pctx;
+ ngx_http_core_srv_conf_t *cscf;
+ ngx_http_core_loc_conf_t *clcf, *pclcf, **clcfp;
+#if (HAVE_PCRE)
+ ngx_str_t err;
+ u_char errstr[NGX_MAX_CONF_ERRSTR];
+#endif
+
+ if (!(ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)))) {
+ 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 (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+
+ if (module->create_loc_conf) {
+ ctx->loc_conf[ngx_modules[m]->ctx_index] =
+ module->create_loc_conf(cf);
+ if (ctx->loc_conf[ngx_modules[m]->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) {
+ if (value[1].len == 1 && value[1].data[0] == '=') {
+ clcf->name.len = value[2].len;
+ clcf->name.data = value[2].data;
+ clcf->exact_match = 1;
+
+ } else if ((value[1].len == 1 && value[1].data[0] == '~')
+ || (value[1].len == 2
+ && value[1].data[0] == '~'
+ && value[1].data[1] == '*'))
+ {
+#if (HAVE_PCRE)
+ err.len = NGX_MAX_CONF_ERRSTR;
+ err.data = errstr;
+
+ clcf->regex = ngx_regex_compile(&value[2],
+ value[1].len == 2 ? NGX_REGEX_CASELESS: 0,
+ cf->pool, &err);
+
+ if (clcf->regex == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s", err.data);
+ return NGX_CONF_ERROR;
+ }
+
+ clcf->name = value[2];
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the using of the regex \"%s\" "
+ "requires PCRE library",
+ value[2].data);
+ return NGX_CONF_ERROR;
+#endif
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid location modifier \"%s\"",
+ value[1].data);
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+ clcf->name.len = value[1].len;
+ clcf->name.data = value[1].data;
+ }
+
+ pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index];
+
+ if (pclcf->name.len == 0) {
+ cscf = ctx->srv_conf[ngx_http_core_module.ctx_index];
+ if (!(clcfp = ngx_push_array(&cscf->locations))) {
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+ clcf->prev_location = pclcf;
+
+ if (pclcf->exact_match) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "location \"%s\" could not be inside "
+ "the exact location \"%s\"",
+ clcf->name.data, pclcf->name.data);
+ return NGX_CONF_ERROR;
+ }
+
+#if (HAVE_PCRE)
+ if (clcf->regex == NULL
+ && ngx_strncmp(clcf->name.data, pclcf->name.data, pclcf->name.len)
+ != 0)
+#else
+ if (ngx_strncmp(clcf->name.data, pclcf->name.data, pclcf->name.len)
+ != 0)
+#endif
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "location \"%s\" is outside location \"%s\"",
+ clcf->name.data, pclcf->name.data);
+ return NGX_CONF_ERROR;
+ }
+
+ if (pclcf->locations.elts == NULL) {
+ ngx_init_array(pclcf->locations, cf->pool, 5, sizeof(void *),
+ NGX_CONF_ERROR);
+ }
+
+ if (!(clcfp = ngx_push_array(&pclcf->locations))) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ *clcfp = clcf;
+
+ pcf = *cf;
+ cf->ctx = ctx;
+ cf->cmd_type = NGX_HTTP_LOC_CONF;
+ rv = ngx_conf_parse(cf, NULL);
+ *cf = pcf;
+
+ return rv;
+}
+
+
+static char *ngx_types_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *rv;
+ ngx_conf_t pcf;
+
+ pcf = *cf;
+ cf->handler = ngx_set_type;
+ cf->handler_conf = conf;
+ rv = ngx_conf_parse(cf, NULL);
+ *cf = pcf;
+
+ return rv;
+}
+
+
+static char *ngx_set_type(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+ ngx_http_core_loc_conf_t *lcf = conf;
+
+ uint32_t key;
+ ngx_uint_t i;
+ ngx_str_t *args;
+ ngx_http_type_t *type;
+
+ if (lcf->types == NULL) {
+ lcf->types = ngx_palloc(cf->pool, NGX_HTTP_TYPES_HASH_PRIME
+ * sizeof(ngx_array_t));
+ if (lcf->types == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 0; i < NGX_HTTP_TYPES_HASH_PRIME; i++) {
+ if (ngx_array_init(&lcf->types[i], cf->pool, 5,
+ sizeof(ngx_http_type_t)) == NGX_ERROR)
+ {
+ return NGX_CONF_ERROR;
+ }
+ }
+ }
+
+ args = (ngx_str_t *) cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+ ngx_http_types_hash_key(key, args[i]);
+
+ if (!(type = ngx_array_push(&lcf->types[key]))) {
+ return NGX_CONF_ERROR;
+ }
+
+ type->exten = args[i];
+ type->type = args[0];
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static void *ngx_http_core_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_core_main_conf_t *cmcf;
+
+ ngx_test_null(cmcf,
+ ngx_pcalloc(cf->pool, sizeof(ngx_http_core_main_conf_t)),
+ NGX_CONF_ERROR);
+
+ ngx_init_array(cmcf->servers, cf->pool,
+ 5, sizeof(ngx_http_core_srv_conf_t *),
+ NGX_CONF_ERROR);
+
+ return cmcf;
+}
+
+
+static char *ngx_http_core_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+#if 0
+ ngx_http_core_main_conf_t *cmcf = conf;
+
+ /* TODO: remove it if no directives */
+#endif
+
+ return NGX_CONF_OK;
+}
+
+
+static void *ngx_http_core_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_http_core_srv_conf_t *cscf;
+
+ ngx_test_null(cscf,
+ ngx_pcalloc(cf->pool, sizeof(ngx_http_core_srv_conf_t)),
+ NGX_CONF_ERROR);
+
+ /*
+
+ set by ngx_pcalloc():
+
+ conf->client_large_buffers.num = 0;
+
+ */
+
+
+ ngx_init_array(cscf->locations, cf->pool,
+ 5, sizeof(void *), NGX_CONF_ERROR);
+ ngx_init_array(cscf->listen, cf->pool, 5, sizeof(ngx_http_listen_t),
+ NGX_CONF_ERROR);
+ ngx_init_array(cscf->server_names, cf->pool,
+ 5, sizeof(ngx_http_server_name_t), NGX_CONF_ERROR);
+
+ cscf->connection_pool_size = NGX_CONF_UNSET_SIZE;
+ cscf->post_accept_timeout = NGX_CONF_UNSET_MSEC;
+ 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->restrict_host_names = NGX_CONF_UNSET_UINT;
+
+ 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_http_listen_t *l;
+ ngx_http_server_name_t *n;
+ ngx_http_core_main_conf_t *cmcf;
+
+ /* TODO: it does not merge, it inits only */
+
+ if (conf->listen.nelts == 0) {
+ ngx_test_null(l, ngx_push_array(&conf->listen), NGX_CONF_ERROR);
+ l->addr = INADDR_ANY;
+#if (WIN32)
+ l->port = 80;
+#else
+ /* STUB: getuid() should be cached */
+ l->port = (getuid() == 0) ? 80 : 8000;
+#endif
+ l->family = AF_INET;
+ }
+
+ if (conf->server_names.nelts == 0) {
+ ngx_test_null(n, ngx_push_array(&conf->server_names), NGX_CONF_ERROR);
+ ngx_test_null(n->name.data, ngx_palloc(cf->pool, NGX_MAXHOSTNAMELEN),
+ NGX_CONF_ERROR);
+
+ if (gethostname((char *) n->name.data, NGX_MAXHOSTNAMELEN) == -1) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
+ "gethostname() failed");
+ return NGX_CONF_ERROR;
+ }
+
+ n->name.len = ngx_strlen(n->name.data);
+ n->core_srv_conf = conf;
+
+#if 0
+ ctx = (ngx_http_conf_ctx_t *)
+ cf->cycle->conf_ctx[ngx_http_module.index];
+ cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
+#endif
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ if (cmcf->max_server_name_len < n->name.len) {
+ cmcf->max_server_name_len = n->name.len;
+ }
+ }
+
+ ngx_conf_merge_size_value(conf->connection_pool_size,
+ prev->connection_pool_size, 256);
+ ngx_conf_merge_msec_value(conf->post_accept_timeout,
+ prev->post_accept_timeout, 60000);
+ 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, ngx_pagesize);
+
+ 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_unsigned_value(conf->restrict_host_names,
+ prev->restrict_host_names, 0);
+
+ return NGX_CONF_OK;
+}
+
+
+static void *ngx_http_core_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_core_loc_conf_t *lcf;
+
+ ngx_test_null(lcf,
+ ngx_pcalloc(cf->pool, sizeof(ngx_http_core_loc_conf_t)),
+ NGX_CONF_ERROR);
+
+ /* set by ngx_pcalloc():
+
+ lcf->root.len = 0;
+ lcf->root.data = NULL;
+ lcf->types = NULL;
+ lcf->default_type.len = 0;
+ lcf->default_type.data = NULL;
+ lcf->err_log = NULL;
+ lcf->error_pages = NULL;
+
+ lcf->regex = NULL;
+ lcf->exact_match = 0;
+ lcf->auto_redirect = 0;
+ lcf->alias = 0;
+
+ */
+
+ lcf->client_max_body_size = NGX_CONF_UNSET_SIZE;
+ lcf->client_body_buffer_size = NGX_CONF_UNSET_SIZE;
+ lcf->client_body_timeout = NGX_CONF_UNSET_MSEC;
+ lcf->sendfile = NGX_CONF_UNSET;
+ lcf->tcp_nopush = NGX_CONF_UNSET;
+ lcf->send_timeout = NGX_CONF_UNSET_MSEC;
+ lcf->send_lowat = NGX_CONF_UNSET_SIZE;
+ lcf->postpone_output = NGX_CONF_UNSET_SIZE;
+ lcf->limit_rate = NGX_CONF_UNSET_SIZE;
+ lcf->keepalive_timeout = NGX_CONF_UNSET_MSEC;
+ lcf->keepalive_header = NGX_CONF_UNSET;
+ lcf->lingering_time = NGX_CONF_UNSET_MSEC;
+ lcf->lingering_timeout = NGX_CONF_UNSET_MSEC;
+ lcf->reset_timedout_connection = NGX_CONF_UNSET;
+ lcf->msie_padding = NGX_CONF_UNSET;
+
+ return lcf;
+}
+
+
+static ngx_http_type_t default_types[] = {
+ { ngx_string("html"), ngx_string("text/html") },
+ { ngx_string("gif"), ngx_string("image/gif") },
+ { ngx_string("jpg"), ngx_string("image/jpeg") },
+ { ngx_null_string, ngx_null_string }
+};
+
+
+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;
+
+ int i, key;
+ ngx_http_type_t *t;
+
+ ngx_conf_merge_str_value(conf->root, prev->root, "html");
+
+ if (ngx_conf_full_name(cf->cycle, &conf->root) == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (conf->types == NULL) {
+ if (prev->types) {
+ conf->types = prev->types;
+
+ } else {
+ ngx_test_null(conf->types,
+ ngx_palloc(cf->pool, NGX_HTTP_TYPES_HASH_PRIME
+ * sizeof(ngx_array_t)),
+ NGX_CONF_ERROR);
+
+ for (i = 0; i < NGX_HTTP_TYPES_HASH_PRIME; i++) {
+ ngx_init_array(conf->types[i], cf->pool,
+ 5, sizeof(ngx_http_type_t), NGX_CONF_ERROR);
+ }
+
+ for (i = 0; default_types[i].exten.len; i++) {
+ ngx_http_types_hash_key(key, default_types[i].exten);
+
+ ngx_test_null(t, ngx_push_array(&conf->types[key]),
+ NGX_CONF_ERROR);
+ t->exten.len = default_types[i].exten.len;
+ t->exten.data = default_types[i].exten.data;
+ t->type.len = default_types[i].type.len;
+ t->type.data = default_types[i].type.data;
+ }
+ }
+ }
+
+ if (conf->err_log == NULL) {
+ if (prev->err_log) {
+ conf->err_log = prev->err_log;
+ } else {
+ conf->err_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_size_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_value(conf->sendfile, prev->sendfile, 0);
+ ngx_conf_merge_value(conf->tcp_nopush, prev->tcp_nopush, 0);
+ 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_msec_value(conf->keepalive_timeout,
+ prev->keepalive_timeout, 75000);
+ ngx_conf_merge_sec_value(conf->keepalive_header,
+ prev->keepalive_header, 0);
+ 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_value(conf->reset_timedout_connection,
+ prev->reset_timedout_connection, 0);
+ ngx_conf_merge_value(conf->msie_padding, prev->msie_padding, 1);
+
+ if (conf->open_files == NULL) {
+ conf->open_files = prev->open_files;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *ngx_set_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_srv_conf_t *scf = conf;
+
+ u_char *addr;
+ ngx_int_t port;
+ ngx_uint_t p;
+ struct hostent *h;
+ ngx_str_t *args;
+ ngx_http_listen_t *ls;
+
+ /*
+ * TODO: check duplicate 'listen' directives,
+ * add resolved name to server names ???
+ */
+
+ if (!(ls = ngx_array_push(&scf->listen))) {
+ return NGX_CONF_ERROR;
+ }
+
+ /* AF_INET only */
+
+ ls->family = AF_INET;
+ ls->default_server = 0;
+ ls->file_name = cf->conf_file->file.name;
+ ls->line = cf->conf_file->line;
+
+ args = cf->args->elts;
+ addr = args[1].data;
+
+ for (p = 0; p < args[1].len; p++) {
+ if (addr[p] == ':') {
+ addr[p++] = '\0';
+ break;
+ }
+ }
+
+ if (p == args[1].len) {
+ /* no ":" in the "listen" */
+ p = 0;
+ }
+
+ port = ngx_atoi(&addr[p], args[1].len - p);
+
+ if (port == NGX_ERROR && p == 0) {
+
+ /* "listen host" */
+ ls->port = 80;
+
+ } else if ((port == NGX_ERROR && p != 0) /* "listen host:NONNUMBER" */
+ || (port < 1 || port > 65536)) { /* "listen 99999" */
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid port \"%s\" in \"%s\" directive, "
+ "it must be a number between 1 and 65535",
+ &addr[p], cmd->name.data);
+
+ return NGX_CONF_ERROR;
+
+ } else if (p == 0) {
+ ls->addr = INADDR_ANY;
+ ls->port = (in_port_t) port;
+ return NGX_CONF_OK;
+
+ } else {
+ ls->port = (in_port_t) port;
+ }
+
+ ls->addr = inet_addr((const char *) addr);
+ if (ls->addr == INADDR_NONE) {
+ h = gethostbyname((const char *) addr);
+
+ if (h == NULL || h->h_addr_list[0] == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "can not resolve host \"%s\" "
+ "in \"%s\" directive", addr, cmd->name.data);
+ return NGX_CONF_ERROR;
+ }
+
+ ls->addr = *(in_addr_t *)(h->h_addr_list[0]);
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *ngx_set_server_name(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_srv_conf_t *scf = conf;
+
+ ngx_uint_t i;
+ ngx_str_t *value;
+ ngx_http_server_name_t *sn;
+ ngx_http_core_main_conf_t *cmcf;
+
+ /* TODO: several names */
+ /* TODO: warn about duplicate 'server_name' directives */
+
+#if 0
+ ctx = (ngx_http_conf_ctx_t *) cf->cycle->conf_ctx[ngx_http_module.index];
+ cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
+#endif
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ 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,
+ "server name \"%s\" is invalid "
+ "in \"%s\" directive",
+ value[i].data, cmd->name.data);
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_test_null(sn, ngx_push_array(&scf->server_names), NGX_CONF_ERROR);
+
+ sn->name.len = value[i].len;
+ sn->name.data = value[i].data;
+ sn->core_srv_conf = scf;
+
+ if (cmcf->max_server_name_len < sn->name.len) {
+ cmcf->max_server_name_len = sn->name.len;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *ngx_set_root(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *lcf = conf;
+
+ ngx_uint_t alias;
+ ngx_str_t *value;
+
+ alias = (cmd->name.len == sizeof("alias") - 1) ? 1 : 0;
+
+ if (lcf->root.data) {
+
+ /* the (ngx_uint_t) cast is required by gcc 2.7.2.3 */
+
+ if ((ngx_uint_t) lcf->alias == alias) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"%s\" directive is duplicate",
+ cmd->name.data);
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"%s\" directive is duplicate, "
+ "\"%s\" directive is specified before",
+ cmd->name.data, lcf->alias ? "alias" : "root");
+ }
+
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ lcf->alias = alias;
+ lcf->root = value[1];
+
+ if (!alias && lcf->root.data[lcf->root.len - 1] == '/') {
+ lcf->root.len--;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *ngx_set_error_page(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *lcf = conf;
+
+ int overwrite;
+ ngx_uint_t i, n;
+ ngx_str_t *value;
+ ngx_http_err_page_t *err;
+
+ if (lcf->error_pages == NULL) {
+ lcf->error_pages = ngx_create_array(cf->pool, 5,
+ sizeof(ngx_http_err_page_t));
+ if (lcf->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 \"%s\"", value[i].data);
+ return NGX_CONF_ERROR;
+ }
+
+ 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 \"%s\"", value[i].data);
+ return NGX_CONF_ERROR;
+ }
+
+ n = 2;
+
+ } else {
+ overwrite = 0;
+ n = 1;
+ }
+
+ for (i = 1; i < cf->args->nelts - n; i++) {
+ if (!(err = ngx_push_array(lcf->error_pages))) {
+ return NGX_CONF_ERROR;
+ }
+
+ err->status = ngx_atoi(value[i].data, value[i].len);
+ if (err->status == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid value \"%s\"", value[i].data);
+ return NGX_CONF_ERROR;
+ }
+
+ if (err->status < 400 || err->status > 599) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "value \"%s\" must be between 400 and 599",
+ value[i].data);
+ return NGX_CONF_ERROR;
+ }
+
+ err->overwrite = overwrite;
+ err->uri = value[cf->args->nelts - 1];
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *ngx_set_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *lcf = conf;
+
+ ngx_str_t *value;
+
+ if (lcf->keepalive_timeout != NGX_CONF_UNSET_MSEC) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ lcf->keepalive_timeout = ngx_parse_time(&value[1], 0);
+ if (lcf->keepalive_timeout == (ngx_msec_t) NGX_ERROR) {
+ return "invalid value";
+ }
+
+ if (lcf->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;
+ }
+
+ lcf->keepalive_header = ngx_parse_time(&value[2], 1);
+ if (lcf->keepalive_header == NGX_ERROR) {
+ return "invalid value";
+ }
+
+ if (lcf->keepalive_header == NGX_PARSE_LARGE_TIME) {
+ return "value must be less than 68 years";
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *ngx_set_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *lcf = conf;
+
+ if (!(lcf->err_log = ngx_log_create_errlog(cf->cycle, cf->args))) {
+ return NGX_CONF_ERROR;
+ }
+
+ return ngx_set_error_log_levels(cf, lcf->err_log);
+}
+
+
+static char *ngx_http_lowat_check(ngx_conf_t *cf, void *post, void *data)
+{
+#if (HAVE_LOWAT_EVENT)
+
+ ssize_t *np = data;
+
+ if (*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;
+ }
+
+#else
+
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "\"send_lowat\" is not supported, ignored");
+
+#endif
+
+ return NGX_CONF_OK;
+}
diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h
new file mode 100644
index 000000000..7468db59c
--- /dev/null
+++ b/src/http/ngx_http_core_module.h
@@ -0,0 +1,214 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_CORE_H_INCLUDED_
+#define _NGX_HTTP_CORE_H_INCLUDED_
+
+
+#include <ngx_string.h>
+#include <ngx_array.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ in_addr_t addr;
+ in_port_t port;
+ int family;
+ ngx_str_t file_name;
+ int line;
+
+ unsigned default_server:1;
+} ngx_http_listen_t;
+
+
+typedef enum {
+ NGX_HTTP_REWRITE_PHASE = 0,
+
+ NGX_HTTP_FIND_CONFIG_PHASE,
+
+ NGX_HTTP_ACCESS_PHASE,
+ NGX_HTTP_CONTENT_PHASE,
+
+ NGX_HTTP_LAST_PHASE
+} ngx_http_phases;
+
+
+typedef struct {
+ ngx_array_t handlers;
+ ngx_int_t type; /* NGX_OK, NGX_DECLINED */
+} ngx_http_phase_t;
+
+
+typedef struct {
+ ngx_array_t servers; /* array of ngx_http_core_srv_conf_t */
+
+ ngx_http_phase_t phases[NGX_HTTP_LAST_PHASE];
+ ngx_array_t index_handlers;
+
+ size_t max_server_name_len;
+} ngx_http_core_main_conf_t;
+
+
+typedef struct {
+ /*
+ * array of ngx_http_core_loc_conf_t, used in the translation handler
+ * and in the merge phase
+ */
+ ngx_array_t locations;
+
+ /* "listen", array of ngx_http_listen_t */
+ ngx_array_t listen;
+
+ /* "server_name", array of ngx_http_server_name_t */
+ ngx_array_t server_names;
+
+ /* server ctx */
+ ngx_http_conf_ctx_t *ctx;
+
+ 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 post_accept_timeout;
+ ngx_msec_t client_header_timeout;
+
+ ngx_uint_t restrict_host_names;
+} ngx_http_core_srv_conf_t;
+
+
+/* list of structures to find core_srv_conf quickly at run time */
+
+typedef struct {
+ in_port_t port;
+ ngx_str_t port_text;
+ ngx_array_t addrs; /* array of ngx_http_in_addr_t */
+} ngx_http_in_port_t;
+
+
+typedef struct {
+ in_addr_t addr;
+ ngx_array_t names; /* array of ngx_http_server_name_t */
+ ngx_http_core_srv_conf_t *core_srv_conf; /* default server conf
+ for this address:port */
+
+ unsigned default_server:1;
+} ngx_http_in_addr_t;
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_http_core_srv_conf_t *core_srv_conf; /* virtual name server conf */
+} ngx_http_server_name_t;
+
+
+#define NGX_HTTP_TYPES_HASH_PRIME 13
+
+#define ngx_http_types_hash_key(key, ext) \
+ { \
+ u_int n; \
+ for (key = 0, n = 0; n < ext.len; n++) { \
+ key += ext.data[n]; \
+ } \
+ key %= NGX_HTTP_TYPES_HASH_PRIME; \
+ }
+
+typedef struct {
+ ngx_str_t exten;
+ ngx_str_t type;
+} ngx_http_type_t;
+
+
+typedef struct {
+ ngx_int_t status;
+ ngx_int_t overwrite;
+ ngx_str_t uri;
+} ngx_http_err_page_t;
+
+
+typedef struct ngx_http_core_loc_conf_s ngx_http_core_loc_conf_t;
+
+struct ngx_http_core_loc_conf_s {
+ ngx_str_t name; /* location name */
+
+#if (HAVE_PCRE)
+ ngx_regex_t *regex;
+#endif
+
+ unsigned exact_match:1;
+ unsigned auto_redirect:1;
+ unsigned alias:1;
+
+ /* array of inclusive ngx_http_core_loc_conf_t */
+ ngx_array_t locations;
+
+ /* pointer to the modules' loc_conf */
+ void **loc_conf ;
+
+ ngx_http_handler_pt handler;
+
+ ngx_str_t root; /* root, alias */
+
+ ngx_array_t *types;
+ ngx_str_t default_type;
+
+ size_t client_max_body_size; /* client_max_body_size */
+ 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 */
+
+ 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 */
+
+ time_t keepalive_header; /* keepalive_timeout */
+
+ ngx_flag_t sendfile; /* sendfile */
+ ngx_flag_t tcp_nopush; /* tcp_nopush */
+ ngx_flag_t reset_timedout_connection; /* reset_timedout_connection */
+ ngx_flag_t msie_padding; /* msie_padding */
+
+ ngx_array_t *error_pages; /* error_page */
+
+ ngx_http_cache_hash_t *open_files;
+
+ ngx_log_t *err_log;
+
+ ngx_http_core_loc_conf_t *prev_location;
+};
+
+
+extern ngx_http_module_t ngx_http_core_module_ctx;
+extern ngx_module_t ngx_http_core_module;
+
+extern int ngx_http_max_module;
+
+
+
+ngx_int_t ngx_http_find_location_config(ngx_http_request_t *r);
+ngx_int_t ngx_http_core_translate_handler(ngx_http_request_t *r);
+
+ngx_int_t ngx_http_set_content_type(ngx_http_request_t *r);
+ngx_int_t ngx_http_set_exten(ngx_http_request_t *r);
+
+ngx_int_t ngx_http_internal_redirect(ngx_http_request_t *r,
+ ngx_str_t *uri, ngx_str_t *args);
+
+
+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);
+
+
+#endif /* _NGX_HTTP_CORE_H_INCLUDED_ */
diff --git a/src/http/ngx_http_file_cache.c b/src/http/ngx_http_file_cache.c
new file mode 100644
index 000000000..acd8dbd3b
--- /dev/null
+++ b/src/http/ngx_http_file_cache.c
@@ -0,0 +1,239 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#if (HAVE_OPENSSL_MD5_H)
+#include <openssl/md5.h>
+#else
+#include <md5.h>
+#endif
+
+#if (HAVE_OPENSSL_MD5)
+#define MD5Init MD5_Init
+#define MD5Update MD5_Update
+#define MD5Final MD5_Final
+#endif
+
+
+
+int ngx_http_cache_get_file(ngx_http_request_t *r, ngx_http_cache_ctx_t *ctx)
+{
+ MD5_CTX md5;
+
+ /* we use offsetof() because sizeof() pads struct size to int size */
+ ctx->header_size = offsetof(ngx_http_cache_header_t, key)
+ + ctx->key.len + 1;
+
+ ctx->file.name.len = ctx->path->name.len + 1 + ctx->path->len + 32;
+ if (!(ctx->file.name.data = ngx_palloc(r->pool, ctx->file.name.len + 1))) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(ctx->file.name.data, ctx->path->name.data, ctx->path->name.len);
+
+ MD5Init(&md5);
+ MD5Update(&md5, (u_char *) ctx->key.data, ctx->key.len);
+ MD5Final(ctx->md5, &md5);
+
+ ngx_md5_text(ctx->file.name.data + ctx->path->name.len + 1 + ctx->path->len,
+ ctx->md5);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "file cache uri: %s, md5: %s", ctx->key.data,
+ ctx->file.name.data + ctx->path->name.len + 1 + ctx->path->len);
+
+ ngx_create_hashed_filename(&ctx->file, ctx->path);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "file cache name: %s", ctx->file.name.data);
+
+ /* TODO: look open files cache */
+
+ return ngx_http_cache_open_file(ctx, 0);
+}
+
+
+int ngx_http_cache_open_file(ngx_http_cache_ctx_t *ctx, ngx_file_uniq_t uniq)
+{
+ ssize_t n;
+ ngx_err_t err;
+ ngx_http_cache_header_t *h;
+
+ ctx->file.fd = ngx_open_file(ctx->file.name.data,
+ NGX_FILE_RDONLY, NGX_FILE_OPEN);
+
+ if (ctx->file.fd == NGX_INVALID_FILE) {
+ err = ngx_errno;
+
+ if (err == NGX_ENOENT || err == NGX_ENOTDIR) {
+ return NGX_DECLINED;
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+ ngx_open_file_n " \"%s\" failed", ctx->file.name.data);
+ return NGX_ERROR;
+ }
+
+ if (uniq) {
+ if (ngx_fd_info(ctx->file.fd, &ctx->file.info) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+ ngx_fd_info_n " \"%s\" failed", ctx->file.name.data);
+
+ return NGX_ERROR;
+ }
+
+ if (ngx_file_uniq(&ctx->file.info) == uniq) {
+ if (ngx_close_file(ctx->file.fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed",
+ ctx->file.name.data);
+ }
+
+ return NGX_HTTP_CACHE_THE_SAME;
+ }
+ }
+
+ n = ngx_read_file(&ctx->file, ctx->buf->pos,
+ ctx->buf->end - ctx->buf->last, 0);
+
+ if (n == NGX_ERROR || n == NGX_AGAIN) {
+ return n;
+ }
+
+ if (n <= ctx->header_size) {
+ ngx_log_error(NGX_LOG_CRIT, ctx->log, 0,
+ "cache file \"%s\" is too small", ctx->file.name.data);
+ return NGX_ERROR;
+ }
+
+ h = (ngx_http_cache_header_t *) ctx->buf->pos;
+ ctx->expires = h->expires;
+ ctx->last_modified= h->last_modified;
+ ctx->date = h->date;
+ ctx->length = h->length;
+
+ if (h->key_len > (size_t) (ctx->buf->end - ctx->buf->pos)) {
+ ngx_log_error(NGX_LOG_ALERT, ctx->log, 0,
+ "cache file \"%s\" is probably invalid",
+ ctx->file.name.data);
+ return NGX_DECLINED;
+ }
+
+ if (ctx->key.len
+ && (h->key_len != ctx->key.len
+ || ngx_strncmp(h->key, ctx->key.data, h->key_len) != 0))
+ {
+ h->key[h->key_len] = '\0';
+ ngx_log_error(NGX_LOG_ALERT, ctx->log, 0,
+ "md5 collision: \"%s\" and \"%s\"",
+ h->key, ctx->key.data);
+ return NGX_DECLINED;
+ }
+
+ ctx->buf->last += n;
+
+ if (ctx->expires < ngx_time()) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "http file cache expired");
+
+ return NGX_HTTP_CACHE_STALE;
+ }
+
+ /* TODO: NGX_HTTP_CACHE_AGED */
+
+ return NGX_OK;
+}
+
+
+int ngx_http_cache_update_file(ngx_http_request_t *r, ngx_http_cache_ctx_t *ctx,
+ ngx_str_t *temp_file)
+{
+ int retry;
+ ngx_err_t err;
+
+ retry = 0;
+
+ for ( ;; ) {
+ if (ngx_rename_file(temp_file->data, ctx->file.name.data) == NGX_OK) {
+ return NGX_OK;
+ }
+
+ err = ngx_errno;
+
+#if (WIN32)
+ if (err == NGX_EEXIST) {
+ if (ngx_win32_rename_file(temp_file, &ctx->file.name, r->pool)
+ == NGX_ERROR)
+ {
+ return NGX_ERROR;
+ }
+ }
+#endif
+
+ if (retry || (err != NGX_ENOENT && err != NGX_ENOTDIR)) {
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+ ngx_rename_file_n "(\"%s\", \"%s\") failed",
+ temp_file->data, ctx->file.name.data);
+
+ return NGX_ERROR;
+ }
+
+ if (ngx_create_path(&ctx->file, ctx->path) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ retry = 1;
+ }
+}
+
+
+int ngx_garbage_collector_http_cache_handler(ngx_gc_t *gc, ngx_str_t *name,
+ ngx_dir_t *dir)
+{
+ int rc;
+ char data[sizeof(ngx_http_cache_header_t)];
+ ngx_hunk_t buf;
+ ngx_http_cache_ctx_t ctx;
+
+ ctx.file.fd = NGX_INVALID_FILE;
+ ctx.file.name = *name;
+ ctx.file.log = gc->log;
+
+ ctx.header_size = sizeof(ngx_http_cache_header_t);
+ ctx.buf = &buf;
+ ctx.log = gc->log;
+ ctx.key.len = 0;
+
+ buf.type = NGX_HUNK_IN_MEMORY|NGX_HUNK_TEMP;
+ buf.pos = data;
+ buf.last = data;
+ buf.start = data;
+ buf.end = data + sizeof(ngx_http_cache_header_t);
+
+ rc = ngx_http_cache_open_file(&ctx, 0);
+
+ /* TODO: NGX_AGAIN */
+
+ if (rc != NGX_ERROR && rc != NGX_DECLINED && rc != NGX_HTTP_CACHE_STALE) {
+ return NGX_OK;
+ }
+
+ if (ngx_delete_file(name->data) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_CRIT, gc->log, ngx_errno,
+ ngx_delete_file_n " \"%s\" failed", name->data);
+ return NGX_ERROR;
+ }
+
+ gc->deleted++;
+ gc->freed += ngx_de_size(dir);
+
+ return NGX_OK;
+}
diff --git a/src/http/ngx_http_header_filter.c b/src/http/ngx_http_header_filter.c
new file mode 100644
index 000000000..9876a05da
--- /dev/null
+++ b/src/http/ngx_http_header_filter.c
@@ -0,0 +1,459 @@
+
+/*
+ * 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_cycle_t *cycle);
+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, /* pre conf */
+
+ 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,
+ &ngx_http_header_filter_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ ngx_http_header_filter_init, /* init module */
+ NULL /* init child */
+};
+
+
+static char server_string[] = "Server: " NGINX_VER CRLF;
+
+
+static ngx_str_t http_codes[] = {
+
+ ngx_string("200 OK"),
+ ngx_null_string, /* "201 Created" */
+ ngx_null_string, /* "202 Accepted" */
+ ngx_null_string, /* "203 Non-Authoritative Information" */
+ ngx_null_string, /* "204 No Content" */
+ ngx_null_string, /* "205 Reset Content" */
+ ngx_string("206 Partial Content"),
+ ngx_null_string, /* "207 Multi-Status" */
+
+#if 0
+ ngx_null_string, /* "300 Multiple Choices" */
+#endif
+
+ ngx_string("301 Moved Permanently"),
+#if 0
+ ngx_string("302 Moved Temporarily"),
+#else
+ ngx_string("302 Found"),
+#endif
+ ngx_null_string, /* "303 See Other" */
+ ngx_string("304 Not Modified"),
+
+ ngx_string("400 Bad Request"),
+ ngx_string("401 Unauthorized"),
+ ngx_null_string, /* "402 Payment Required" */
+ ngx_string("403 Forbidden"),
+ ngx_string("404 Not Found"),
+ ngx_string("405 Not Allowed"),
+ ngx_null_string, /* "406 Not Acceptable" */
+ ngx_null_string, /* "407 Proxy Authentication Required" */
+ ngx_string("408 Request Time-out"),
+ ngx_null_string, /* "409 Conflict" */
+ ngx_null_string, /* "410 Gone" */
+ ngx_string("411 Length Required"),
+ ngx_null_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_null_string, /* "415 Unsupported Media Type" */
+ ngx_string("416 Requested Range Not Satisfiable"),
+
+ 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_http_header_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-Type"),
+ offsetof(ngx_http_headers_out_t, content_type) },
+ { 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_uint_t status, i;
+ ngx_buf_t *b;
+ ngx_chain_t *ln;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header;
+ ngx_http_core_loc_conf_t *clcf;
+
+ 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_NOT_MODIFIED
+ && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT)
+ {
+ r->headers_out.last_modified_time = -1;
+ r->headers_out.last_modified = NULL;
+ }
+ }
+
+ /* 2 is for trailing "\r\n" and 2 is for "\r\n" in the end of header */
+ len = sizeof("HTTP/1.x ") - 1 + 2 + 2;
+
+ /* status line */
+ if (r->headers_out.status_line.len) {
+ len += r->headers_out.status_line.len;
+#if (NGX_SUPPRESS_WARN)
+ status = NGX_INVALID_ARRAY_INDEX;
+#endif
+
+ } else {
+
+ if (r->headers_out.status < NGX_HTTP_MOVED_PERMANENTLY) {
+ /* 2XX */
+ status = r->headers_out.status - NGX_HTTP_OK;
+
+ } else if (r->headers_out.status < NGX_HTTP_BAD_REQUEST) {
+ /* 3XX */
+ status = r->headers_out.status - NGX_HTTP_MOVED_PERMANENTLY + 8;
+
+ if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
+ r->header_only = 1;
+ }
+
+ } else if (r->headers_out.status < NGX_HTTP_INTERNAL_SERVER_ERROR) {
+ /* 4XX */
+ status = r->headers_out.status - NGX_HTTP_BAD_REQUEST + 8 + 4;
+
+ } else {
+ /* 5XX */
+ status = r->headers_out.status
+ - NGX_HTTP_INTERNAL_SERVER_ERROR + 8 + 4 + 17;
+ }
+
+ len += http_codes[status].len;
+ }
+
+ if (r->headers_out.server && r->headers_out.server->key.len) {
+ len += r->headers_out.server->key.len
+ + r->headers_out.server->value.len + 2;
+ } else {
+ len += sizeof(server_string) - 1;
+ }
+
+ if (r->headers_out.date && r->headers_out.date->key.len) {
+ len += r->headers_out.date->key.len
+ + r->headers_out.date->value.len + 2;
+ } else {
+ len += sizeof("Date: Mon, 28 Sep 1970 06:00:00 GMT" CRLF) - 1;
+ }
+
+ if (r->headers_out.content_length == NULL) {
+ if (r->headers_out.content_length_n >= 0) {
+ len += sizeof("Content-Length: ") - 1 + NGX_OFF_T_LEN + 2;
+ }
+ }
+
+ if (r->headers_out.content_type && r->headers_out.content_type->value.len) {
+ r->headers_out.content_type->key.len = 0;
+ len += sizeof("Content-Type: ") - 1
+ + r->headers_out.content_type->value.len + 2;
+
+ if (r->headers_out.charset.len) {
+ len += sizeof("; charset=") - 1 + r->headers_out.charset.len;
+ }
+ }
+
+ if (r->headers_out.location
+ && r->headers_out.location->value.len
+ && r->headers_out.location->value.data[0] == '/')
+ {
+ r->headers_out.location->key.len = 0;
+ len += sizeof("Location: http://") - 1
+ + r->server_name->len + r->headers_out.location->value.len + 2;
+
+ if (r->port != 80) {
+ len += r->port_text->len;
+ }
+ }
+
+ if (r->headers_out.last_modified && r->headers_out.last_modified->key.len) {
+ len += r->headers_out.last_modified->key.len
+ + r->headers_out.last_modified->value.len + 2;
+
+ } else if (r->headers_out.last_modified_time != -1) {
+ len += sizeof("Last-Modified: Mon, 28 Sep 1970 06:00:00 GMT" CRLF) - 1;
+ }
+
+ if (r->chunked) {
+ len += sizeof("Transfer-Encoding: chunked" CRLF) - 1;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ 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
+ && (r->headers_in.gecko || r->headers_in.konqueror))
+ {
+ len += sizeof("Keep-Alive: timeout=") - 1 + TIME_T_LEN + 2;
+ }
+
+ } else {
+ len += sizeof("Connection: closed" CRLF) - 1;
+ }
+
+ 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].key.len == 0) {
+ continue;
+ }
+
+ /* 2 is for ": " and 2 is for "\r\n" */
+ len += header[i].key.len + 2 + header[i].value.len + 2;
+ }
+
+ if (!(b = ngx_create_temp_buf(r->pool, len))) {
+ return NGX_ERROR;
+ }
+
+ /* "HTTP/1.x " */
+ b->last = ngx_cpymem(b->last, "HTTP/1.1 ", sizeof("HTTP/1.x ") - 1);
+
+ /* status line */
+ if (r->headers_out.status_line.len) {
+ b->last = ngx_cpymem(b->last, r->headers_out.status_line.data,
+ r->headers_out.status_line.len);
+
+ } else {
+ b->last = ngx_cpymem(b->last, http_codes[status].data,
+ http_codes[status].len);
+ }
+ *(b->last++) = CR; *(b->last++) = LF;
+
+ if (!(r->headers_out.server && r->headers_out.server->key.len)) {
+ b->last = ngx_cpymem(b->last, server_string, sizeof(server_string) - 1);
+ }
+
+ if (!(r->headers_out.date && r->headers_out.date->key.len)) {
+ 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_length == NULL) {
+ if (r->headers_out.content_length_n >= 0) {
+ b->last += ngx_snprintf((char *) b->last,
+ sizeof("Content-Length: ") + NGX_OFF_T_LEN + 2,
+ "Content-Length: " OFF_T_FMT CRLF,
+ r->headers_out.content_length_n);
+ }
+ }
+
+ if (r->headers_out.content_type && r->headers_out.content_type->value.len) {
+ b->last = ngx_cpymem(b->last, "Content-Type: ",
+ sizeof("Content-Type: ") - 1);
+ p = b->last;
+ b->last = ngx_cpymem(b->last, r->headers_out.content_type->value.data,
+ r->headers_out.content_type->value.len);
+
+ if (r->headers_out.charset.len) {
+ b->last = ngx_cpymem(b->last, "; charset=",
+ sizeof("; charset=") - 1);
+ b->last = ngx_cpymem(b->last, r->headers_out.charset.data,
+ r->headers_out.charset.len);
+
+ r->headers_out.content_type->value.len = b->last - p;
+ r->headers_out.content_type->value.data = p;
+ }
+
+ *(b->last++) = CR; *(b->last++) = LF;
+ }
+
+ if (r->headers_out.location
+ && r->headers_out.location->value.len
+ && r->headers_out.location->value.data[0] == '/')
+ {
+ p = b->last + sizeof("Location: ") - 1;
+ b->last = ngx_cpymem(b->last, "Location: http://",
+ sizeof("Location: http://") - 1);
+ b->last = ngx_cpymem(b->last, r->server_name->data,
+ r->server_name->len);
+ if (r->port != 80) {
+ b->last = ngx_cpymem(b->last, r->port_text->data,
+ r->port_text->len);
+ }
+
+ b->last = ngx_cpymem(b->last, r->headers_out.location->value.data,
+ r->headers_out.location->value.len);
+
+ r->headers_out.location->value.len = b->last - p;
+ r->headers_out.location->value.data = p;
+
+ *(b->last++) = CR; *(b->last++) = LF;
+ }
+
+ if (!(r->headers_out.last_modified && r->headers_out.last_modified->key.len)
+ && 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 (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
+ && (r->headers_in.gecko || r->headers_in.konqueror))
+ {
+ b->last += ngx_snprintf((char *) b->last,
+ sizeof("Keep-Alive: timeout=") + TIME_T_LEN + 2,
+ "Keep-Alive: timeout=" TIME_T_FMT CRLF,
+ clcf->keepalive_header);
+ }
+
+ } else {
+ b->last = ngx_cpymem(b->last, "Connection: close" CRLF,
+ sizeof("Connection: close" CRLF) - 1);
+ }
+
+ 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].key.len == 0) {
+ continue;
+ }
+
+ b->last = ngx_cpymem(b->last, header[i].key.data, header[i].key.len);
+ *(b->last++) = ':' ; *(b->last++) = ' ' ;
+
+ b->last = ngx_cpymem(b->last, header[i].value.data,
+ header[i].value.len);
+ *(b->last++) = CR; *(b->last++) = LF;
+ }
+
+#if (NGX_DEBUG)
+ *(b->last) = '\0';
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "%s\n", b->pos);
+#endif
+
+ /* 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;
+ }
+
+ if (!(ln = ngx_alloc_chain_link(r->pool))) {
+ return NGX_ERROR;
+ }
+
+ ln->buf = b;
+ ln->next = NULL;
+
+ return ngx_http_write_filter(r, ln);
+}
+
+
+static ngx_int_t ngx_http_header_filter_init(ngx_cycle_t *cycle)
+{
+ ngx_http_top_header_filter = ngx_http_header_filter;
+
+ return NGX_OK;
+}
diff --git a/src/http/ngx_http_log_handler.c b/src/http/ngx_http_log_handler.c
new file mode 100644
index 000000000..51166cf0c
--- /dev/null
+++ b/src/http/ngx_http_log_handler.c
@@ -0,0 +1,978 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+
+static u_char *ngx_http_log_addr(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+static u_char *ngx_http_log_connection(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+static u_char *ngx_http_log_pipe(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+static u_char *ngx_http_log_time(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+static u_char *ngx_http_log_msec(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+static u_char *ngx_http_log_request(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+static u_char *ngx_http_log_status(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+static u_char *ngx_http_log_length(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+static u_char *ngx_http_log_apache_length(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+static u_char *ngx_http_log_header_in(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+static u_char *ngx_http_log_connection_header_out(ngx_http_request_t *r,
+ u_char *buf, uintptr_t data);
+static u_char *ngx_http_log_transfer_encoding_header_out(ngx_http_request_t *r,
+ u_char *buf,
+ uintptr_t data);
+static u_char *ngx_http_log_unknown_header_in(ngx_http_request_t *r,
+ u_char *buf, uintptr_t data);
+static u_char *ngx_http_log_header_out(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+static u_char *ngx_http_log_unknown_header_out(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+
+static ngx_int_t ngx_http_log_pre_conf(ngx_conf_t *cf);
+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 ngx_int_t ngx_http_log_parse_format(ngx_conf_t *cf, ngx_array_t *ops,
+ ngx_str_t *line);
+
+
+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_CONF_TAKE12,
+ ngx_http_log_set_log,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL},
+
+ ngx_null_command
+};
+
+
+ngx_http_module_t ngx_http_log_module_ctx = {
+ ngx_http_log_pre_conf, /* pre conf */
+
+ 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,
+ &ngx_http_log_module_ctx, /* module context */
+ ngx_http_log_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init module */
+ NULL /* init child */
+};
+
+
+static ngx_str_t http_access_log = ngx_string(NGX_HTTP_LOG_PATH);
+
+
+static ngx_str_t ngx_http_combined_fmt =
+ ngx_string("%addr - - [%time] \"%request\" %status %apache_length "
+ "\"%{Referer}i\" \"%{User-Agent}i\"");
+
+
+ngx_http_log_op_name_t ngx_http_log_fmt_ops[] = {
+ { ngx_string("addr"), INET_ADDRSTRLEN - 1, ngx_http_log_addr },
+ { ngx_string("conn"), NGX_INT32_LEN, ngx_http_log_connection },
+ { ngx_string("pipe"), 1, ngx_http_log_pipe },
+ { ngx_string("time"), sizeof("28/Sep/1970:12:00:00") - 1,
+ ngx_http_log_time },
+ { ngx_string("msec"), TIME_T_LEN + 4, ngx_http_log_msec },
+ { ngx_string("request"), 0, ngx_http_log_request },
+ { ngx_string("status"), 3, ngx_http_log_status },
+ { ngx_string("length"), NGX_OFF_T_LEN, ngx_http_log_length },
+ { ngx_string("apache_length"), NGX_OFF_T_LEN, ngx_http_log_apache_length },
+ { ngx_string("i"), NGX_HTTP_LOG_ARG, ngx_http_log_header_in },
+ { ngx_string("o"), NGX_HTTP_LOG_ARG, ngx_http_log_header_out },
+ { ngx_null_string, 0, NULL }
+};
+
+
+ngx_int_t ngx_http_log_handler(ngx_http_request_t *r)
+{
+ ngx_uint_t i, l;
+ uintptr_t data;
+ u_char *line, *p;
+ size_t len;
+ ngx_http_log_t *log;
+ ngx_http_log_op_t *op;
+ ngx_http_log_loc_conf_t *lcf;
+#if (WIN32)
+ u_long written;
+#endif
+
+ 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++) {
+
+ len = 0;
+ op = log[l].ops->elts;
+ for (i = 0; i < log[l].ops->nelts; i++) {
+ if (op[i].len == 0) {
+ len += (size_t) op[i].op(r, NULL, op[i].data);
+
+ } else {
+ len += op[i].len;
+ }
+ }
+
+#if (WIN32)
+ len += 2;
+#else
+ len++;
+#endif
+
+ ngx_test_null(line, ngx_palloc(r->pool, len), NGX_ERROR);
+ p = line;
+
+ for (i = 0; i < log[l].ops->nelts; i++) {
+ if (op[i].op == NGX_HTTP_LOG_COPY_SHORT) {
+ len = op[i].len;
+ data = op[i].data;
+ while (len--) {
+ *p++ = (char) (data & 0xff);
+ data >>= 8;
+ }
+
+ } else if (op[i].op == NGX_HTTP_LOG_COPY_LONG) {
+ p = ngx_cpymem(p, (void *) op[i].data, op[i].len);
+
+ } else {
+ p = op[i].op(r, p, op[i].data);
+ }
+ }
+
+#if (WIN32)
+ *p++ = CR; *p++ = LF;
+ WriteFile(log[l].file->fd, line, p - line, &written, NULL);
+#else
+ *p++ = LF;
+ write(log[l].file->fd, line, p - line);
+#endif
+ }
+
+ return NGX_OK;
+}
+
+
+static u_char *ngx_http_log_addr(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data)
+{
+ return ngx_cpymem(buf, r->connection->addr_text.data,
+ r->connection->addr_text.len);
+}
+
+
+static u_char *ngx_http_log_connection(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data)
+{
+ return buf + ngx_snprintf((char *) buf, NGX_INT_T_LEN + 1,
+ "%" NGX_UINT_T_FMT,
+ r->connection->number);
+}
+
+
+static u_char *ngx_http_log_pipe(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data)
+{
+ 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,
+ uintptr_t data)
+{
+ return ngx_cpymem(buf, ngx_cached_http_log_time.data,
+ ngx_cached_http_log_time.len);
+}
+
+
+static u_char *ngx_http_log_msec(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data)
+{
+ struct timeval tv;
+
+ ngx_gettimeofday(&tv);
+
+ return buf + ngx_snprintf((char *) buf, TIME_T_LEN + 5, "%ld.%03ld",
+ tv.tv_sec, tv.tv_usec / 1000);
+}
+
+
+static u_char *ngx_http_log_request(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data)
+{
+ if (buf == NULL) {
+ /* find the request line length */
+ return (u_char *) r->request_line.len;
+ }
+
+ return ngx_cpymem(buf, r->request_line.data, r->request_line.len);
+}
+
+
+static u_char *ngx_http_log_status(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data)
+{
+ return buf + ngx_snprintf((char *) buf, 4, "%" NGX_UINT_T_FMT,
+ r->err_status ? r->err_status : r->headers_out.status);
+}
+
+
+static u_char *ngx_http_log_length(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data)
+{
+ return buf + ngx_snprintf((char *) buf, NGX_OFF_T_LEN + 1, OFF_T_FMT,
+ r->connection->sent);
+}
+
+
+static u_char *ngx_http_log_apache_length(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data)
+{
+ return buf + ngx_snprintf((char *) buf, NGX_OFF_T_LEN + 1, OFF_T_FMT,
+ r->connection->sent - r->header_size);
+}
+
+
+static u_char *ngx_http_log_header_in(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data)
+{
+ ngx_uint_t i;
+ ngx_str_t *s;
+ ngx_table_elt_t *h;
+ ngx_http_log_op_t *op;
+
+ if (r) {
+ h = *(ngx_table_elt_t **) ((char *) &r->headers_in + data);
+
+ if (h == NULL) {
+
+ /* no header */
+
+ if (buf) {
+ *buf = '-';
+ }
+
+ return buf + 1;
+ }
+
+ if (buf == NULL) {
+ /* find the header length */
+ return (u_char *) h->value.len;
+ }
+
+ return ngx_cpymem(buf, h->value.data, h->value.len);
+ }
+
+ /* find an offset while a format string compilation */
+
+ op = (ngx_http_log_op_t *) buf;
+ s = (ngx_str_t *) data;
+
+ op->len = 0;
+
+ for (i = 0; ngx_http_headers_in[i].name.len != 0; i++) {
+ if (ngx_http_headers_in[i].name.len != s->len) {
+ continue;
+ }
+
+ if (ngx_strncasecmp(ngx_http_headers_in[i].name.data, s->data, s->len)
+ == 0)
+ {
+ op->op = ngx_http_log_header_in;
+ op->data = ngx_http_headers_in[i].offset;
+ return NULL;
+ }
+ }
+
+ op->op = ngx_http_log_unknown_header_in;
+ op->data = (uintptr_t) s;
+
+ return NULL;
+}
+
+
+static u_char *ngx_http_log_unknown_header_in(ngx_http_request_t *r,
+ u_char *buf, uintptr_t data)
+{
+ ngx_uint_t i;
+ ngx_str_t *s;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *h;
+
+ s = (ngx_str_t *) data;
+
+ 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 (h[i].key.len != s->len) {
+ continue;
+ }
+
+ if (ngx_strncasecmp(h[i].key.data, s->data, s->len) == 0) {
+ if (buf == NULL) {
+ /* find the header length */
+ return (u_char *) h[i].value.len;
+ }
+
+ return ngx_cpymem(buf, h[i].value.data, h[i].value.len);
+ }
+ }
+
+ /* no header */
+
+ if (buf) {
+ *buf = '-';
+ }
+
+ return buf + 1;
+}
+
+
+static u_char *ngx_http_log_header_out(ngx_http_request_t *r, u_char *buf,
+ uintptr_t data)
+{
+ ngx_uint_t i;
+ ngx_str_t *s;
+ ngx_table_elt_t *h;
+ ngx_http_log_op_t *op;
+
+ if (r) {
+
+ /* run-time execution */
+
+ if (r->http_version < NGX_HTTP_VERSION_10) {
+ if (buf) {
+ *buf = '-';
+ }
+
+ return buf + 1;
+ }
+
+ h = *(ngx_table_elt_t **) ((char *) &r->headers_out + data);
+
+ if (h == NULL) {
+
+ /*
+ * No header pointer was found.
+ * However, some headers: "Date", "Server", "Content-Length",
+ * and "Last-Modified" have a special handling in the header filter
+ * but we do not set up their pointers in the filter because
+ * they are too seldom needed to be logged.
+ */
+
+ if (data == offsetof(ngx_http_headers_out_t, date)) {
+ if (buf == NULL) {
+ return (u_char *) ngx_cached_http_time.len;
+ }
+ return ngx_cpymem(buf, ngx_cached_http_time.data,
+ ngx_cached_http_time.len);
+ }
+
+ if (data == offsetof(ngx_http_headers_out_t, server)) {
+ if (buf == NULL) {
+ return (u_char *) (sizeof(NGINX_VER) - 1);
+ }
+ return ngx_cpymem(buf, NGINX_VER, sizeof(NGINX_VER) - 1);
+ }
+
+ if (data == offsetof(ngx_http_headers_out_t, content_length)) {
+ if (r->headers_out.content_length_n == -1) {
+ if (buf) {
+ *buf = '-';
+ }
+ return buf + 1;
+ }
+
+ if (buf == NULL) {
+ return (u_char *) NGX_OFF_T_LEN;
+ }
+ return buf + ngx_snprintf((char *) buf,
+ NGX_OFF_T_LEN + 2, OFF_T_FMT,
+ r->headers_out.content_length_n);
+ }
+
+ if (data == offsetof(ngx_http_headers_out_t, last_modified)) {
+ if (r->headers_out.last_modified_time == -1) {
+ if (buf) {
+ *buf = '-';
+ }
+ return buf + 1;
+ }
+
+ if (buf == NULL) {
+ return (u_char *)
+ sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1;
+ }
+ return buf + ngx_http_time(buf,
+ r->headers_out.last_modified_time);
+ }
+
+ if (buf) {
+ *buf = '-';
+ }
+
+ return buf + 1;
+ }
+
+ if (buf == NULL) {
+ /* find the header length */
+ return (u_char *) h->value.len;
+ }
+
+ return ngx_cpymem(buf, h->value.data, h->value.len);
+ }
+
+ /* find an offset while a format string compilation */
+
+ op = (ngx_http_log_op_t *) buf;
+ s = (ngx_str_t *) data;
+
+ op->len = 0;
+
+ for (i = 0; ngx_http_headers_out[i].name.len != 0; i++) {
+ if (ngx_http_headers_out[i].name.len != s->len) {
+ continue;
+ }
+
+ if (ngx_strncasecmp(ngx_http_headers_out[i].name.data, s->data, s->len)
+ == 0)
+ {
+ op->op = ngx_http_log_header_out;
+ op->data = ngx_http_headers_out[i].offset;
+ return NULL;
+ }
+ }
+
+ if (s->len == sizeof("Connection") - 1
+ && ngx_strncasecmp(s->data, "Connection", s->len) == 0)
+ {
+ op->op = ngx_http_log_connection_header_out;
+ op->data = (uintptr_t) NULL;
+ return NULL;
+ }
+
+ if (s->len == sizeof("Transfer-Encoding") - 1
+ && ngx_strncasecmp(s->data, "Transfer-Encoding", s->len) == 0) {
+ op->op = ngx_http_log_transfer_encoding_header_out;
+ op->data = (uintptr_t) NULL;
+ return NULL;
+ }
+
+ op->op = ngx_http_log_unknown_header_out;
+ op->data = (uintptr_t) s;
+
+ return NULL;
+}
+
+
+static u_char *ngx_http_log_connection_header_out(ngx_http_request_t *r,
+ u_char *buf, uintptr_t data)
+{
+ if (buf == NULL) {
+ return (u_char *) ((r->keepalive) ? sizeof("keep-alive") - 1:
+ sizeof("close") - 1);
+ }
+
+ if (r->keepalive) {
+ return ngx_cpymem(buf, "keep-alive", sizeof("keep-alive") - 1);
+
+ } else {
+ return ngx_cpymem(buf, "close", sizeof("close") - 1);
+ }
+}
+
+
+static u_char *ngx_http_log_transfer_encoding_header_out(ngx_http_request_t *r,
+ u_char *buf,
+ uintptr_t data)
+{
+ if (buf == NULL) {
+ return (u_char *) ((r->chunked) ? sizeof("chunked") - 1 : 1);
+ }
+
+ if (r->chunked) {
+ return ngx_cpymem(buf, "chunked", sizeof("chunked") - 1);
+ }
+
+ *buf = '-';
+
+ return buf + 1;
+}
+
+
+static u_char *ngx_http_log_unknown_header_out(ngx_http_request_t *r,
+ u_char *buf,
+ uintptr_t data)
+{
+ ngx_uint_t i;
+ ngx_str_t *s;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *h;
+
+ s = (ngx_str_t *) data;
+
+ part = &r->headers_out.headers.part;
+ h = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ h = part->elts;
+ i = 0;
+ }
+
+ if (h[i].key.len != s->len) {
+ continue;
+ }
+
+ if (ngx_strncasecmp(h[i].key.data, s->data, s->len) == 0) {
+ if (buf == NULL) {
+ /* find the header length */
+ return (u_char *) h[i].value.len;
+ }
+
+ return ngx_cpymem(buf, h[i].value.data, h[i].value.len);
+ }
+ }
+
+ /* no header */
+
+ if (buf) {
+ *buf = '-';
+ }
+
+ return buf + 1;
+}
+
+
+static ngx_int_t ngx_http_log_pre_conf(ngx_conf_t *cf)
+{
+ ngx_http_log_op_name_t *op;
+
+ for (op = ngx_http_log_fmt_ops; op->name.len; op++) { /* void */ }
+ op->op = NULL;
+
+ return NGX_OK;
+}
+
+
+static void *ngx_http_log_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_log_main_conf_t *conf;
+
+ char *rc;
+ ngx_str_t *value;
+
+ if (!(conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_main_conf_t)))) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_init_array(conf->formats, cf->pool, 5, sizeof(ngx_http_log_fmt_t),
+ NGX_CONF_ERROR);
+
+ cf->args->nelts = 0;
+
+ if (!(value = ngx_push_array(cf->args))) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (!(value = ngx_push_array(cf->args))) {
+ return NGX_CONF_ERROR;
+ }
+
+ value->len = sizeof("combined") - 1;
+ value->data = (u_char *) "combined";
+
+ if (!(value = ngx_push_array(cf->args))) {
+ return NGX_CONF_ERROR;
+ }
+
+ *value = ngx_http_combined_fmt;
+
+ rc = ngx_http_log_set_format(cf, NULL, conf);
+ if (rc != NGX_CONF_OK) {
+ return NULL;
+ }
+
+ return conf;
+}
+
+
+static void *ngx_http_log_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_log_loc_conf_t *conf;
+
+ if (!(conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_loc_conf_t)))) {
+ return NGX_CONF_ERROR;
+ }
+
+ 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->logs == NULL) {
+
+ if (conf->off) {
+ return NGX_CONF_OK;
+ }
+
+ if (prev->logs) {
+ conf->logs = prev->logs;
+
+ } else {
+
+ if (prev->off) {
+ conf->off = prev->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;
+ }
+
+ if (!(log = ngx_array_push(conf->logs))) {
+ return NGX_CONF_ERROR;
+ }
+
+ log->file = ngx_conf_open_file(cf->cycle, &http_access_log);
+ if (log->file == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_log_module);
+ fmt = lmcf->formats.elts;
+
+ /* the default "combined" format */
+ log->ops = fmt[0].ops;
+ }
+ }
+
+ 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;
+
+ ngx_uint_t i;
+ ngx_str_t *value, name;
+ ngx_http_log_t *log;
+ ngx_http_log_fmt_t *fmt;
+ ngx_http_log_main_conf_t *lmcf;
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ llcf->off = 1;
+ return NGX_CONF_OK;
+ }
+
+ 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);
+
+ if (!(log = ngx_array_push(llcf->logs))) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (!(log->file = ngx_conf_open_file(cf->cycle, &value[1]))) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cf->args->nelts == 3) {
+ name = value[2];
+ } else {
+ name.len = sizeof("combined") - 1;
+ name.data = (u_char *) "combined";
+ }
+
+ 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->ops = fmt[i].ops;
+ return NGX_CONF_OK;
+ }
+ }
+
+ 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_uint_t s, f, invalid;
+ u_char *data, *p, *fname;
+ size_t i, len, fname_len;
+ ngx_str_t *value, arg, *a;
+ ngx_http_log_op_t *op;
+ ngx_http_log_fmt_t *fmt;
+ ngx_http_log_op_name_t *name;
+
+ value = cf->args->elts;
+
+ fmt = lmcf->formats.elts;
+ for (f = 0; f < lmcf->formats.nelts; f++) {
+ if (fmt[f].name.len == value[1].len
+ && ngx_strcmp(fmt->name.data, value[1].data) == 0)
+ {
+ return "duplicate \"log_format\" name";
+ }
+ }
+
+ if (!(fmt = ngx_push_array(&lmcf->formats))) {
+ return NGX_CONF_ERROR;
+ }
+
+ fmt->name = value[1];
+
+ if (!(fmt->ops = ngx_create_array(cf->pool, 20,
+ sizeof(ngx_http_log_op_t)))) {
+ return NGX_CONF_ERROR;
+ }
+
+ invalid = 0;
+ data = NULL;
+
+ for (s = 2; s < cf->args->nelts && !invalid; s++) {
+
+ i = 0;
+
+ while (i < value[s].len) {
+
+ if (!(op = ngx_push_array(fmt->ops))) {
+ return NGX_CONF_ERROR;
+ }
+
+ data = &value[s].data[i];
+
+ if (value[s].data[i] == '%') {
+ i++;
+
+ if (i == value[s].len) {
+ invalid = 1;
+ break;
+ }
+
+ if (value[s].data[i] == '{') {
+ i++;
+
+ arg.data = &value[s].data[i];
+
+ while (i < value[s].len && value[s].data[i] != '}') {
+ i++;
+ }
+
+ arg.len = &value[s].data[i] - arg.data;
+
+ if (i == value[s].len || arg.len == 0) {
+ invalid = 1;
+ break;
+ }
+
+ i++;
+
+ } else {
+ arg.len = 0;
+ }
+
+ fname = &value[s].data[i];
+
+ while (i < value[s].len
+ && ((value[s].data[i] >= 'a' && value[s].data[i] <= 'z')
+ || value[s].data[i] == '_'))
+ {
+ i++;
+ }
+
+ fname_len = &value[s].data[i] - fname;
+
+ if (fname_len == 0) {
+ invalid = 1;
+ break;
+ }
+
+ for (name = ngx_http_log_fmt_ops; name->op; name++) {
+ if (name->name.len == 0) {
+ name = (ngx_http_log_op_name_t *) name->op;
+ }
+
+ if (name->name.len == fname_len
+ && ngx_strncmp(name->name.data, fname, fname_len) == 0)
+ {
+ if (name->len != NGX_HTTP_LOG_ARG) {
+ if (arg.len) {
+ fname[fname_len] = '\0';
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"%s\" must not have argument",
+ data);
+ return NGX_CONF_ERROR;
+ }
+
+ op->len = name->len;
+ op->op = name->op;
+ op->data = 0;
+
+ break;
+ }
+
+ if (arg.len == 0) {
+ fname[fname_len] = '\0';
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"%s\" requires argument",
+ data);
+ return NGX_CONF_ERROR;
+ }
+
+ if (!(a = ngx_palloc(cf->pool, sizeof(ngx_str_t)))) {
+ return NGX_CONF_ERROR;
+ }
+
+ *a = arg;
+ name->op(NULL, (u_char *) op, (uintptr_t) a);
+
+ break;
+ }
+ }
+
+ if (name->name.len == 0) {
+ invalid = 1;
+ break;
+ }
+
+ } else {
+ i++;
+
+ while (i < value[s].len && value[s].data[i] != '%') {
+ i++;
+ }
+
+ len = &value[s].data[i] - data;
+
+ if (len) {
+
+ op->len = len;
+
+ if (len <= sizeof(uintptr_t)) {
+ op->op = NGX_HTTP_LOG_COPY_SHORT;
+ op->data = 0;
+
+ while (len--) {
+ op->data <<= 8;
+ op->data |= data[len];
+ }
+
+ } else {
+ op->op = NGX_HTTP_LOG_COPY_LONG;
+
+ if (!(p = ngx_palloc(cf->pool, len))) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memcpy(p, data, len);
+ op->data = (uintptr_t) p;
+ }
+ }
+ }
+ }
+ }
+
+ if (invalid) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%s\"", data);
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/src/http/ngx_http_log_handler.h b/src/http/ngx_http_log_handler.h
new file mode 100644
index 000000000..8eb74ba1c
--- /dev/null
+++ b/src/http/ngx_http_log_handler.h
@@ -0,0 +1,65 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_LOG_HANDLER_H_INCLUDED_
+#define _NGX_HTTP_LOG_HANDLER_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef u_char *(*ngx_http_log_op_pt) (ngx_http_request_t *r, u_char *buf,
+ uintptr_t data);
+
+#define NGX_HTTP_LOG_COPY_SHORT (ngx_http_log_op_pt) 0
+#define NGX_HTTP_LOG_COPY_LONG (ngx_http_log_op_pt) -1
+
+#define NGX_HTTP_LOG_ARG (u_int) -1
+
+
+typedef struct {
+ size_t len;
+ ngx_http_log_op_pt op;
+ uintptr_t data;
+} ngx_http_log_op_t;
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_array_t *ops; /* array of ngx_http_log_op_t */
+} ngx_http_log_fmt_t;
+
+
+typedef struct {
+ ngx_str_t name;
+ size_t len;
+ ngx_http_log_op_pt op;
+} ngx_http_log_op_name_t;
+
+
+typedef struct {
+ ngx_array_t formats; /* array of ngx_http_log_fmt_t */
+} ngx_http_log_main_conf_t;
+
+
+typedef struct {
+ ngx_open_file_t *file;
+ ngx_array_t *ops; /* array of ngx_http_log_op_t */
+} ngx_http_log_t;
+
+
+typedef struct {
+ ngx_array_t *logs; /* array of ngx_http_log_t */
+ ngx_uint_t off; /* unsigned off:1 */
+} ngx_http_log_loc_conf_t;
+
+
+extern ngx_http_log_op_name_t ngx_http_log_fmt_ops[];
+
+
+#endif /* _NGX_HTTP_LOG_HANDLER_H_INCLUDED_ */
diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c
new file mode 100644
index 000000000..ba77ffb62
--- /dev/null
+++ b/src/http/ngx_http_parse.c
@@ -0,0 +1,868 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+ngx_int_t ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
+{
+ u_char ch, *p, *m;
+ enum {
+ sw_start = 0,
+ sw_method,
+ sw_space_after_method,
+ sw_spaces_before_uri,
+ sw_schema,
+ sw_schema_slash,
+ sw_schema_slash_slash,
+ sw_host,
+ sw_port,
+ sw_after_slash_in_uri,
+ sw_check_uri,
+ 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_almost_done,
+ sw_done
+ } state;
+
+ state = r->state;
+ p = b->pos;
+
+ while (p < b->last && state < sw_done) {
+ ch = *p++;
+
+ /* gcc 2.95.2 and msvc 6.0 compile this switch as an jump table */
+
+ switch (state) {
+
+ /* HTTP methods: GET, HEAD, POST */
+ case sw_start:
+ r->request_start = p - 1;
+
+ if (ch == CR || ch == LF) {
+ break;
+ }
+
+ if (ch < 'A' || ch > 'Z') {
+ return NGX_HTTP_PARSE_INVALID_METHOD;
+ }
+
+ state = sw_method;
+ break;
+
+ case sw_method:
+ if (ch == ' ') {
+ r->method_end = p - 1;
+ m = r->request_start;
+
+ if (r->method_end - m == 3) {
+
+ if (m[0] == 'G' && m[1] == 'E' && m[2] == 'T') {
+ r->method = NGX_HTTP_GET;
+ }
+
+ } else if (r->method_end - m == 4) {
+
+ if (m[0] == 'P' && m[1] == 'O'
+ && m[2] == 'T' && m[3] == 'T')
+ {
+ r->method = NGX_HTTP_POST;
+
+ } else if (m[0] == 'H' && m[1] == 'E'
+ && m[2] == 'A' && m[3] == 'D')
+ {
+ r->method = NGX_HTTP_HEAD;
+ }
+ }
+
+ state = sw_spaces_before_uri;
+ break;
+ }
+
+ if (ch < 'A' || ch > 'Z') {
+ return NGX_HTTP_PARSE_INVALID_METHOD;
+ }
+
+ break;
+
+ /* single space after method */
+ case sw_space_after_method:
+ switch (ch) {
+ case ' ':
+ state = sw_spaces_before_uri;
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_METHOD;
+ }
+ break;
+
+ /* space* before URI */
+ case sw_spaces_before_uri:
+ switch (ch) {
+ case '/':
+ r->uri_start = p - 1;
+ state = sw_after_slash_in_uri;
+ break;
+ case ' ':
+ break;
+ default:
+ if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
+ r->schema_start = p - 1;
+ state = sw_schema;
+ break;
+ }
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+ case sw_schema:
+ switch (ch) {
+ case ':':
+ r->schema_end = p - 1;
+ state = sw_schema_slash;
+ break;
+ default:
+ if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
+ break;
+ }
+ 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:
+ switch (ch) {
+ case ':':
+ r->host_end = p - 1;
+ state = sw_port;
+ break;
+ case '/':
+ r->host_end = p - 1;
+ r->uri_start = p - 1;
+ state = sw_after_slash_in_uri;
+ break;
+ default:
+ if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
+ || (ch >= '0' && ch <= '9') || ch == '.' || ch == '-')
+ {
+ break;
+ }
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+ case sw_port:
+ switch (ch) {
+ case '/':
+ r->port_end = p - 1;
+ r->uri_start = p - 1;
+ state = sw_after_slash_in_uri;
+ break;
+ default:
+ if (ch < '0' && ch > '9') {
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+ }
+ break;
+
+ /* check "/.", "//", and "%" in URI */
+ case sw_after_slash_in_uri:
+ switch (ch) {
+ case CR:
+ r->uri_end = p - 1;
+ r->http_minor = 9;
+ state = sw_almost_done;
+ break;
+ case LF:
+ r->uri_end = p - 1;
+ r->http_minor = 9;
+ state = sw_done;
+ break;
+ case ' ':
+ r->uri_end = p - 1;
+ state = sw_http_09;
+ break;
+ case '.':
+ case '%':
+ r->complex_uri = 1;
+ state = sw_uri;
+ break;
+ case '/':
+ r->complex_uri = 1;
+ break;
+ case '?':
+ r->args_start = p;
+ state = sw_uri;
+ break;
+ default:
+ state = sw_check_uri;
+ break;
+ }
+ break;
+
+ /* check "/" and "%" in URI */
+ case sw_check_uri:
+ switch (ch) {
+ case CR:
+ r->uri_end = p - 1;
+ r->http_minor = 9;
+ state = sw_almost_done;
+ break;
+ case LF:
+ r->uri_end = p - 1;
+ r->http_minor = 9;
+ state = sw_done;
+ break;
+ case ' ':
+ r->uri_end = p - 1;
+ state = sw_http_09;
+ break;
+ case '.':
+ r->uri_ext = p;
+ break;
+ case '/':
+ r->uri_ext = NULL;
+ state = sw_after_slash_in_uri;
+ break;
+ case '%':
+ r->complex_uri = 1;
+ state = sw_uri;
+ break;
+ case '?':
+ r->args_start = p;
+ state = sw_uri;
+ break;
+ }
+ break;
+
+ /* URI */
+ case sw_uri:
+ switch (ch) {
+ case CR:
+ r->uri_end = p - 1;
+ r->http_minor = 9;
+ state = sw_almost_done;
+ break;
+ case LF:
+ r->uri_end = p - 1;
+ r->http_minor = 9;
+ state = sw_done;
+ break;
+ case ' ':
+ r->uri_end = p - 1;
+ state = sw_http_09;
+ break;
+ }
+ 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;
+ state = sw_done;
+ break;
+ case 'H':
+ state = sw_http_H;
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ 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) {
+ state = sw_done;
+ break;
+ }
+
+ if (ch < '0' || ch > '9') {
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+
+ r->http_minor = r->http_minor * 10 + ch - '0';
+ break;
+
+ /* end of request line */
+ case sw_almost_done:
+ r->request_end = p - 2;
+ switch (ch) {
+ case LF:
+ state = sw_done;
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ break;
+
+ /* suppress warning */
+ case sw_done:
+ break;
+ }
+ }
+
+ b->pos = p;
+
+ if (state == sw_done) {
+ if (r->request_end == NULL) {
+ r->request_end = p - 1;
+ }
+
+ 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;
+
+ } else {
+ r->state = state;
+ return NGX_AGAIN;
+ }
+}
+
+
+ngx_int_t ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b)
+{
+ 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,
+ sw_ignore_line,
+ sw_done,
+ sw_header_done
+ } state;
+
+ state = r->state;
+ p = b->pos;
+
+ while (p < b->last && state < sw_done) {
+ ch = *p++;
+
+ switch (state) {
+
+ /* first char */
+ case sw_start:
+ switch (ch) {
+ case CR:
+ r->header_end = p - 1;
+ state = sw_header_almost_done;
+ break;
+ case LF:
+ r->header_end = p - 1;
+ state = sw_header_done;
+ break;
+ default:
+ state = sw_name;
+ r->header_name_start = p - 1;
+
+ c = (u_char) (ch | 0x20);
+ if (c >= 'a' && c <= 'z') {
+ break;
+ }
+
+ if (ch == '-' || ch == '_' || ch == '~' || ch == '.') {
+ break;
+ }
+
+ if (ch >= '0' && ch <= '9') {
+ break;
+ }
+
+ return NGX_HTTP_PARSE_INVALID_HEADER;
+
+ }
+ break;
+
+ /* header name */
+ case sw_name:
+ c = (u_char) (ch | 0x20);
+ if (c >= 'a' && c <= 'z') {
+ break;
+ }
+
+ if (ch == ':') {
+ r->header_name_end = p - 1;
+ state = sw_space_before_value;
+ break;
+ }
+
+ if (ch == '-' || ch == '_' || ch == '~' || ch == '.') {
+ break;
+ }
+
+ if (ch >= '0' && ch <= '9') {
+ break;
+ }
+
+ /* IIS can send duplicate "HTTP/1.1 ..." lines */
+ if (ch == '/'
+ && r->proxy
+ && p - r->header_start == 5
+ && ngx_strncmp(r->header_start, "HTTP", 4) == 0)
+ {
+ state = sw_ignore_line;
+ break;
+ }
+
+ return NGX_HTTP_PARSE_INVALID_HEADER;
+
+ /* space* before header value */
+ case sw_space_before_value:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ r->header_start = r->header_end = p - 1;
+ state = sw_almost_done;
+ break;
+ case LF:
+ r->header_start = r->header_end = p - 1;
+ state = sw_done;
+ break;
+ default:
+ r->header_start = p - 1;
+ state = sw_value;
+ break;
+ }
+ break;
+
+ /* header value */
+ case sw_value:
+ switch (ch) {
+ case ' ':
+ r->header_end = p - 1;
+ state = sw_space_after_value;
+ break;
+ case CR:
+ r->header_end = p - 1;
+ state = sw_almost_done;
+ break;
+ case LF:
+ r->header_end = p - 1;
+ state = sw_done;
+ break;
+ }
+ 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:
+ state = sw_done;
+ break;
+ 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:
+ state = sw_done;
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_HEADER;
+ }
+ break;
+
+ /* end of header */
+ case sw_header_almost_done:
+ switch (ch) {
+ case LF:
+ state = sw_header_done;
+ break;
+ default:
+ return NGX_HTTP_PARSE_INVALID_HEADER;
+ }
+ break;
+
+ /* suppress warning */
+ case sw_done:
+ case sw_header_done:
+ break;
+ }
+ }
+
+ b->pos = p;
+
+ if (state == sw_done) {
+ r->state = sw_start;
+ return NGX_OK;
+
+ } else if (state == sw_header_done) {
+ r->state = sw_start;
+ return NGX_HTTP_PARSE_HEADER_DONE;
+
+ } else {
+ r->state = state;
+ return NGX_AGAIN;
+ }
+}
+
+
+ngx_int_t ngx_http_parse_complex_uri(ngx_http_request_t *r)
+{
+ u_char c, ch, decoded, *p, *u;
+ enum {
+ sw_usual = 0,
+ sw_slash,
+ sw_dot,
+ sw_dot_dot,
+#if (WIN32)
+ sw_dot_dot_dot,
+#endif
+ sw_quoted,
+ sw_quoted_second
+ } state, quoted_state;
+
+ decoded = '\0';
+ quoted_state = sw_usual;
+
+ state = sw_usual;
+ p = r->uri_start;
+ u = r->uri.data;
+ r->uri_ext = NULL;
+
+ ch = *p++;
+
+ while (p < r->uri_start + r->uri.len + 1) {
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "s:%d in:'%x:%c', out:'%c'", state, ch, ch, *u);
+
+ switch (state) {
+ case sw_usual:
+ switch(ch) {
+ case '/':
+ r->uri_ext = NULL;
+ state = sw_slash;
+ *u++ = ch;
+ break;
+ case '%':
+ quoted_state = state;
+ state = sw_quoted;
+ break;
+ case '.':
+ r->uri_ext = u + 1;
+ default:
+ *u++ = ch;
+ break;
+ }
+ ch = *p++;
+ break;
+
+ case sw_slash:
+ switch(ch) {
+ case '/':
+ break;
+ case '.':
+ state = sw_dot;
+ *u++ = ch;
+ break;
+ case '%':
+ quoted_state = state;
+ state = sw_quoted;
+ break;
+ default:
+ state = sw_usual;
+ *u++ = ch;
+ break;
+ }
+ ch = *p++;
+ break;
+
+ case sw_dot:
+ switch(ch) {
+ case '/':
+ state = sw_slash;
+ u--;
+ break;
+ case '.':
+ state = sw_dot_dot;
+ *u++ = ch;
+ break;
+ case '%':
+ quoted_state = state;
+ state = sw_quoted;
+ break;
+ default:
+ state = sw_usual;
+ *u++ = ch;
+ break;
+ }
+ ch = *p++;
+ break;
+
+ case sw_dot_dot:
+ switch(ch) {
+ case '/':
+ state = sw_slash;
+ u -= 4;
+ if (u < r->uri.data) {
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ while (*(u - 1) != '/') {
+ u--;
+ }
+ break;
+ case '%':
+ quoted_state = state;
+ state = sw_quoted;
+ break;
+#if (WIN32)
+ case '.':
+ state = sw_dot_dot_dot;
+ *u++ = ch;
+ break;
+#endif
+ default:
+ state = sw_usual;
+ *u++ = ch;
+ break;
+ }
+ ch = *p++;
+ break;
+
+#if (WIN32)
+ case sw_dot_dot_dot:
+ switch(ch) {
+ case '/':
+ state = sw_slash;
+ u -= 5;
+ if (u < r->uri.data) {
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ while (*u != '/') {
+ u--;
+ }
+ if (u < r->uri.data) {
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ while (*(u - 1) != '/') {
+ u--;
+ }
+ break;
+ case '%':
+ quoted_state = state;
+ state = sw_quoted;
+ break;
+ default:
+ state = sw_usual;
+ *u++ = ch;
+ break;
+ }
+ ch = *p++;
+ break;
+#endif
+
+ case sw_quoted:
+ 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 == '%') {
+ state = sw_usual;
+ *u++ = ch;
+ ch = *p++;
+ break;
+ }
+ 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;
+ }
+ state = quoted_state;
+ break;
+ }
+
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+ }
+ }
+
+ r->uri.len = u - r->uri.data;
+ r->uri.data[r->uri.len] = '\0';
+
+ if (r->uri_ext) {
+ r->exten.len = u - r->uri_ext;
+
+ if (!(r->exten.data = ngx_palloc(r->pool, r->exten.len + 1))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_cpystrn(r->exten.data, r->uri_ext, r->exten.len + 1);
+ }
+
+ r->uri_ext = NULL;
+
+ return NGX_OK;
+}
diff --git a/src/http/ngx_http_parse_time.c b/src/http/ngx_http_parse_time.c
new file mode 100644
index 000000000..38bbe2e85
--- /dev/null
+++ b/src/http/ngx_http_parse_time.c
@@ -0,0 +1,287 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_types.h>
+
+
+static int 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;
+ int day, month, year, hour, min, sec;
+ 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 0
+ printf("%d.%d.%d %d:%d:%d\n", day, month + 1, year, hour, min, sec);
+#endif
+
+ 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;
+ }
+
+ if (sizeof(time_t) <= 4 && year >= 2038) {
+ return NGX_ERROR;
+ }
+
+ /*
+ * shift new year to March 1 and start months from 1 (not 0),
+ * it's needed for Gauss's formula
+ */
+
+ if (--month <= 0) {
+ month += 12;
+ year -= 1;
+ }
+
+ /* Gauss's formula for Grigorian days from 1 March 1 BC */
+
+ return (365 * year + year / 4 - year / 100 + year / 400
+ + 367 * month / 12 - 31
+ + day
+
+ /*
+ * 719527 days were between March 1, 1 BC and March 1, 1970,
+ * 31 and 28 days in January and February 1970
+ */
+
+ - 719527 + 31 + 28) * 86400 + hour * 3600 + min * 60 + sec;
+}
+
+#if 0
+char zero[] = "Sun, 01 Jan 1970 08:49:30";
+char one[] = "Sunday, 11-Dec-02 08:49:30";
+char two[] = "Sun Mar 1 08:49:37 2000";
+char thr[] = "Sun Dec 11 08:49:37 2002";
+
+main()
+{
+ int rc;
+
+ rc = ngx_http_parse_time(zero, sizeof(zero) - 1);
+ printf("rc: %d\n", rc);
+
+ rc = ngx_http_parse_time(one, sizeof(one) - 1);
+ printf("rc: %d\n", rc);
+
+ rc = ngx_http_parse_time(two, sizeof(two) - 1);
+ printf("rc: %d\n", rc);
+
+ rc = ngx_http_parse_time(thr, sizeof(thr) - 1);
+ printf("rc: %d\n", rc);
+}
+
+#endif
diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c
new file mode 100644
index 000000000..e889449ae
--- /dev/null
+++ b/src/http/ngx_http_request.c
@@ -0,0 +1,2149 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_http.h>
+
+
+static void ngx_http_init_request(ngx_event_t *ev);
+#if (NGX_HTTP_SSL)
+static void ngx_http_ssl_handshake(ngx_event_t *rev);
+#endif
+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_request_header(ngx_http_request_t *r);
+
+static void ngx_http_set_write_handler(ngx_http_request_t *r);
+
+static void ngx_http_block_read(ngx_event_t *ev);
+static void ngx_http_read_discarded_body_event(ngx_event_t *rev);
+static ngx_int_t ngx_http_read_discarded_body(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 void ngx_http_client_error(ngx_http_request_t *r,
+ int client_error, int error);
+static size_t ngx_http_log_error(void *data, char *buf, size_t len);
+
+
+/* NGX_HTTP_PARSE_... errors */
+
+static char *client_header_errors[] = {
+ "client %s sent invalid method",
+ "client %s sent invalid request",
+ "client %s sent too long URI",
+ "client %s sent invalid method in HTTP/0.9 request",
+
+ "client %s sent invalid header, URL: %s",
+ "client %s sent too long header line, URL: %s",
+ "client %s sent HTTP/1.1 request without \"Host\" header, URL: %s",
+ "client %s sent invalid \"Content-Length\" header, URL: %s",
+ "client %s sent POST method without \"Content-Length\" header, URL: %s",
+ "client %s sent plain HTTP request to HTTPS port, URL: %s",
+ "client %s sent invalid \"Host\" header \"%s\", URL: %s"
+};
+
+
+ngx_http_header_t ngx_http_headers_in[] = {
+ { ngx_string("Host"), offsetof(ngx_http_headers_in_t, host) },
+ { ngx_string("Connection"), offsetof(ngx_http_headers_in_t, connection) },
+ { ngx_string("If-Modified-Since"),
+ offsetof(ngx_http_headers_in_t, if_modified_since) },
+ { ngx_string("User-Agent"), offsetof(ngx_http_headers_in_t, user_agent) },
+ { ngx_string("Referer"), offsetof(ngx_http_headers_in_t, referer) },
+ { ngx_string("Content-Length"),
+ offsetof(ngx_http_headers_in_t, content_length) },
+
+ { ngx_string("Range"), offsetof(ngx_http_headers_in_t, range) },
+#if 0
+ { ngx_string("If-Range"), offsetof(ngx_http_headers_in_t, if_range) },
+#endif
+
+#if (NGX_HTTP_GZIP)
+ { ngx_string("Accept-Encoding"),
+ offsetof(ngx_http_headers_in_t, accept_encoding) },
+ { ngx_string("Via"), offsetof(ngx_http_headers_in_t, via) },
+#endif
+
+ { ngx_string("Authorization"),
+ offsetof(ngx_http_headers_in_t, authorization) },
+
+ { ngx_string("Keep-Alive"), offsetof(ngx_http_headers_in_t, keep_alive) },
+
+#if (NGX_HTTP_PROXY)
+ { ngx_string("X-Forwarded-For"),
+ offsetof(ngx_http_headers_in_t, x_forwarded_for) },
+#endif
+
+ { ngx_null_string, 0 }
+};
+
+
+#if 0
+static void ngx_http_dummy(ngx_event_t *wev)
+{
+ return;
+}
+#endif
+
+
+void ngx_http_init_connection(ngx_connection_t *c)
+{
+ ngx_event_t *rev;
+ ngx_http_log_ctx_t *ctx;
+
+ if (!(ctx = ngx_pcalloc(c->pool, sizeof(ngx_http_log_ctx_t)))) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ ctx->connection = c->number;
+ ctx->client = c->addr_text.data;
+ ctx->action = "reading client request line";
+ c->log->data = ctx;
+ c->log->handler = ngx_http_log_error;
+ c->log_error = NGX_ERROR_INFO;
+
+ rev = c->read;
+ rev->event_handler = ngx_http_init_request;
+
+ /* STUB: epoll edge */ c->write->event_handler = ngx_http_empty_handler;
+
+ if (rev->ready) {
+ /* the deferred accept(), rtsig, aio, iocp */
+
+ if (ngx_accept_mutex) {
+ if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ ngx_post_event(rev);
+
+ ngx_mutex_unlock(ngx_posted_events_mutex);
+ return;
+ }
+
+#if (NGX_STAT_STUB)
+ (*ngx_stat_reading)++;
+#endif
+
+ ngx_http_init_request(rev);
+ return;
+ }
+
+ ngx_add_timer(rev, c->listening->post_accept_timeout);
+
+ if (ngx_handle_read_event(rev, 0) == NGX_ERROR) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+#if 0
+ /* TODO: learn SO_SNDBUF (to use in zerocopy) via kqueue's EV_CLEAR event */
+
+ c->write->ready = 0;
+ c->write->event_handler = ngx_http_dummy;
+
+ if (ngx_handle_write_event(c->write, 0) == NGX_ERROR) {
+ ngx_http_close_connection(c);
+ return;
+ }
+#endif
+
+#if (NGX_STAT_STUB)
+ (*ngx_stat_reading)++;
+#endif
+
+}
+
+
+static void ngx_http_init_request(ngx_event_t *rev)
+{
+ ngx_uint_t i;
+ socklen_t len;
+ struct sockaddr_in addr_in;
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+ ngx_http_in_port_t *in_port;
+ ngx_http_in_addr_t *in_addr;
+ ngx_http_connection_t *hc;
+ ngx_http_server_name_t *server_name;
+ ngx_http_core_srv_conf_t *cscf;
+ ngx_http_core_loc_conf_t *clcf;
+#if (NGX_HTTP_SSL)
+ ngx_http_ssl_srv_conf_t *sscf;
+#endif
+
+ c = rev->data;
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+
+#if (NGX_STAT_STUB)
+ (*ngx_stat_reading)--;
+#endif
+
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ hc = c->data;
+
+ if (hc) {
+
+#if (NGX_STAT_STUB)
+ (*ngx_stat_reading)++;
+#endif
+
+ } else {
+ if (!(hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t)))) {
+
+#if (NGX_STAT_STUB)
+ (*ngx_stat_reading)--;
+#endif
+
+ 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 {
+ if (!(r = ngx_pcalloc(c->pool, sizeof(ngx_http_request_t)))) {
+
+#if (NGX_STAT_STUB)
+ (*ngx_stat_reading)--;
+#endif
+
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ hc->request = r;
+ }
+
+#if (NGX_STAT_STUB)
+ r->stat_reading = 1;
+#endif
+
+ c->data = r;
+ r->http_connection = hc;
+
+ c->sent = 0;
+ r->signature = NGX_HTTP_MODULE;
+
+ /* find the server configuration for the address:port */
+
+ /* AF_INET only */
+
+ in_port = c->servers;
+ in_addr = in_port->addrs.elts;
+
+ r->port = in_port->port;
+ r->port_text = &in_port->port_text;
+
+ i = 0;
+
+ if (in_port->addrs.nelts > 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 (WIN32)
+ if (c->local_sockaddr) {
+ r->in_addr =
+ ((struct sockaddr_in *) c->local_sockaddr)->sin_addr.s_addr;
+
+ } else {
+#endif
+ len = sizeof(struct sockaddr_in);
+ if (getsockname(c->fd, (struct sockaddr *) &addr_in, &len) == -1) {
+ ngx_connection_error(c, ngx_socket_errno,
+ "getsockname() failed");
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ r->in_addr = addr_in.sin_addr.s_addr;
+
+#if (WIN32)
+ }
+#endif
+
+ /* the last in_port->addrs address is "*" */
+
+ for ( /* void */ ; i < in_port->addrs.nelts - 1; i++) {
+ if (in_addr[i].addr == r->in_addr) {
+ break;
+ }
+ }
+
+ } else {
+ r->in_addr = in_addr[0].addr;
+ }
+
+ r->virtual_names = &in_addr[i].names;
+
+ /* the default server configuration for the address:port */
+ cscf = in_addr[i].core_srv_conf;
+
+ r->main_conf = cscf->ctx->main_conf;
+ r->srv_conf = cscf->ctx->srv_conf;
+ r->loc_conf = cscf->ctx->loc_conf;
+
+ rev->event_handler = ngx_http_process_request_line;
+
+#if (NGX_HTTP_SSL)
+
+ sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module);
+ if (sscf->enable) {
+
+ if (c->ssl == NULL) {
+ if (ngx_ssl_create_session(sscf->ssl_ctx, c, NGX_SSL_BUFFER)
+ == NGX_ERROR)
+ {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ /*
+ * The majority of browsers do not send the "close notify" alert.
+ * Among them are MSIE, Mozilla, Netscape 4, Konqueror, and Links.
+ * And what is more MSIE ignores the server's alert.
+ *
+ * Opera always sends the alert.
+ */
+
+ c->ssl->no_rcv_shut = 1;
+ rev->event_handler = ngx_http_ssl_handshake;
+ }
+
+ r->filter_need_in_memory = 1;
+ }
+
+#endif
+
+ server_name = cscf->server_names.elts;
+ r->server_name = &server_name->name;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+ c->log->file = clcf->err_log->file;
+ if (!(c->log->log_level & NGX_LOG_DEBUG_CONNECTION)) {
+ c->log->log_level = clcf->err_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;
+ }
+
+ if (!(r->pool = ngx_create_pool(cscf->request_pool_size, c->log))) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ if (ngx_array_init(&r->cleanup, r->pool, 5, sizeof(ngx_http_cleanup_t))
+ == NGX_ERROR)
+ {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+
+ if (ngx_list_init(&r->headers_out.headers, r->pool, 20,
+ sizeof(ngx_table_elt_t)) == NGX_ERROR)
+ {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+
+ r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
+ if (r->ctx == NULL) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ c->single_connection = 1;
+ r->connection = c;
+
+ r->file.fd = NGX_INVALID_FILE;
+
+ 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->http_state = NGX_HTTP_READING_REQUEST_STATE;
+
+#if (NGX_STAT_STUB)
+ (*ngx_stat_requests)++;
+#endif
+
+ rev->event_handler(rev);
+}
+
+
+#if (NGX_HTTP_SSL)
+
+static void ngx_http_ssl_handshake(ngx_event_t *rev)
+{
+ int n;
+ ngx_int_t rc;
+ u_char buf[1];
+ 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_http_client_error(r, 0, NGX_HTTP_REQUEST_TIME_OUT);
+ return;
+ }
+
+ n = recv(c->fd, buf, 1, MSG_PEEK);
+
+ if (n == -1 && ngx_socket_errno == NGX_EAGAIN) {
+ 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%X", buf[0]);
+
+ c->recv = ngx_ssl_recv;
+ c->send_chain = ngx_ssl_send_chain;
+
+ rc = ngx_ssl_handshake(c);
+
+ if (rc == NGX_ERROR) {
+ ngx_http_close_request(r, NGX_HTTP_BAD_REQUEST);
+ ngx_http_close_connection(r->connection);
+ return;
+ }
+
+ if (rc != NGX_OK) {
+ return;
+ }
+
+ } else {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+ "plain http");
+
+ r->plain_http = 1;
+ }
+ }
+
+ rev->event_handler = ngx_http_process_request_line;
+ ngx_http_process_request_line(rev);
+}
+
+#endif
+
+
+static void ngx_http_process_request_line(ngx_event_t *rev)
+{
+ u_char *p;
+ ssize_t n;
+ ngx_int_t rc, rv;
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+ ngx_http_log_ctx_t *ctx;
+
+ c = rev->data;
+ r = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+ "http process request line");
+
+ if (rev->timedout) {
+ ngx_http_client_error(r, 0, 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 */
+
+ /* copy unparsed URI */
+
+ r->unparsed_uri.len = r->uri_end - r->uri_start;
+ r->unparsed_uri.data = ngx_palloc(r->pool, r->unparsed_uri.len + 1);
+ if (r->unparsed_uri.data == NULL) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ ngx_cpystrn(r->unparsed_uri.data, r->uri_start,
+ r->unparsed_uri.len + 1);
+
+
+ /* copy URI */
+
+ 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->uri.data = ngx_palloc(r->pool, r->uri.len + 1))) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ if (r->complex_uri) {
+ rc = ngx_http_parse_complex_uri(r);
+
+ if (rc == NGX_HTTP_INTERNAL_SERVER_ERROR) {
+ ngx_http_close_request(r, rc);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ if (rc != NGX_OK) {
+ r->request_line.len = r->request_end - r->request_start;
+ r->request_line.data = r->request_start;
+
+ ngx_http_client_error(r, rc, NGX_HTTP_BAD_REQUEST);
+ return;
+ }
+
+ } else {
+ ngx_cpystrn(r->uri.data, r->uri_start, r->uri.len + 1);
+ }
+
+
+ r->request_line.len = r->request_end - r->request_start;
+ r->request_line.data = r->request_start;
+ r->request_line.data[r->request_line.len] = '\0';
+
+ if (r->method == 0) {
+ r->method_name.len = r->method_end - r->request_start + 1;
+ r->method_name.data = r->request_line.data;
+ }
+
+ if (r->uri_ext) {
+
+ /* copy URI extention */
+
+ if (r->args_start) {
+ r->exten.len = r->args_start - 1 - r->uri_ext;
+ } else {
+ r->exten.len = r->uri_end - r->uri_ext;
+ }
+
+ if (!(r->exten.data = ngx_palloc(r->pool, r->exten.len + 1))) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ ngx_cpystrn(r->exten.data, r->uri_ext, r->exten.len + 1);
+ }
+
+ if (r->args_start && r->uri_end > r->args_start) {
+
+ /* copy URI arguments */
+
+ r->args.len = r->uri_end - r->args_start;
+
+ if (!(r->args.data = ngx_palloc(r->pool, r->args.len + 1))) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ ngx_cpystrn(r->args.data, r->args_start, r->args.len + 1);
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http request line: \"%s\"", r->request_line.data);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http uri: \"%s\"", r->uri.data);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http args: \"%s\"",
+ r->args.data ? r->args.data : (u_char *) "");
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http exten: \"%s\"",
+ r->exten.data ? r->exten.data : (u_char *) "");
+
+ if (r->http_version < NGX_HTTP_VERSION_10) {
+ rev->event_handler = ngx_http_block_read;
+ ngx_http_handler(r);
+ return;
+ }
+
+
+ if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
+ sizeof(ngx_table_elt_t)) == NGX_ERROR)
+ {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+
+ if (ngx_array_init(&r->headers_in.cookies, r->pool, 5,
+ sizeof(ngx_table_elt_t *)) == NGX_ERROR)
+ {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+
+ ctx = c->log->data;
+ ctx->action = "reading client request headers";
+ ctx->url = r->unparsed_uri.data;
+
+ rev->event_handler = ngx_http_process_request_headers;
+ ngx_http_process_request_headers(rev);
+
+ return;
+
+ } else if (rc != NGX_AGAIN) {
+
+ /* there was error while a request line parsing */
+
+ 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 (rc == NGX_HTTP_PARSE_INVALID_METHOD) {
+ r->http_version = NGX_HTTP_VERSION_10;
+ }
+
+ ngx_http_client_error(r, rc,
+ (rc == NGX_HTTP_PARSE_INVALID_METHOD) ?
+ NGX_HTTP_NOT_IMPLEMENTED:
+ 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);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ if (rv == NGX_DECLINED) {
+ ngx_http_client_error(r, NGX_HTTP_PARSE_TOO_LONG_URI,
+ NGX_HTTP_REQUEST_URI_TOO_LARGE);
+ return;
+ }
+ }
+ }
+}
+
+
+static void ngx_http_process_request_headers(ngx_event_t *rev)
+{
+ ssize_t n;
+ ngx_int_t rc, rv, i;
+ ngx_table_elt_t *h, **cookie;
+ 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 process request header line");
+
+ if (rev->timedout) {
+ ngx_http_client_error(r, 0, NGX_HTTP_REQUEST_TIME_OUT);
+ return;
+ }
+
+ 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);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ if (rv == NGX_DECLINED) {
+ ngx_http_client_error(r, NGX_HTTP_PARSE_TOO_LONG_HEADER,
+ NGX_HTTP_BAD_REQUEST);
+ 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);
+
+ if (rc == NGX_OK) {
+
+ /* a header line has been parsed successfully */
+
+ r->headers_n++;
+
+ if (!(h = ngx_list_push(&r->headers_in.headers))) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ 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';
+
+ if (h->key.len == sizeof("Cookie") - 1
+ && ngx_strcasecmp(h->key.data, "Cookie") == 0)
+ {
+ if (!(cookie = ngx_array_push(&r->headers_in.cookies))) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ *cookie = h;
+
+ } else {
+
+ for (i = 0; ngx_http_headers_in[i].name.len != 0; i++) {
+ if (ngx_http_headers_in[i].name.len != h->key.len) {
+ continue;
+ }
+
+ if (ngx_strcasecmp(ngx_http_headers_in[i].name.data,
+ h->key.data) == 0)
+ {
+ *((ngx_table_elt_t **) ((char *) &r->headers_in
+ + ngx_http_headers_in[i].offset)) = h;
+ break;
+ }
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http header: \"%s: %s\"",
+ h->key.data, h->value.data);
+
+ continue;
+
+ } else 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->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
+
+ rc = ngx_http_process_request_header(r);
+
+ if (rc != NGX_OK) {
+ ngx_http_client_error(r, rc, NGX_HTTP_BAD_REQUEST);
+ return;
+ }
+
+ if (rev->timer_set) {
+ ngx_del_timer(rev);
+ }
+
+#if (NGX_STAT_STUB)
+ (*ngx_stat_reading)--;
+ r->stat_reading = 0;
+ (*ngx_stat_writing)++;
+ r->stat_writing = 1;
+#endif
+
+ rev->event_handler = ngx_http_block_read;
+ ngx_http_handler(r);
+ return;
+
+ } else if (rc != NGX_AGAIN) {
+
+ /* there was error while a header line parsing */
+
+#if (NGX_DEBUG)
+ if (rc == NGX_HTTP_PARSE_INVALID_HEADER
+ && (rev->log->log_level & NGX_LOG_DEBUG_HTTP))
+ {
+ u_char *p;
+ for (p = r->header_name_start;
+ p < r->header_in->last - 1;
+ p++)
+ {
+ if (*p == LF) {
+ break;
+ }
+ }
+ *p = '\0';
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+ "http invalid header: \"%s\"",
+ r->header_name_start);
+ }
+#endif
+
+ ngx_http_client_error(r, rc, NGX_HTTP_BAD_REQUEST);
+ return;
+ }
+
+ /* NGX_AGAIN: a header line parsing is still not complete */
+
+ }
+}
+
+
+static ssize_t ngx_http_read_request_header(ngx_http_request_t *r)
+{
+ ssize_t n;
+ ngx_event_t *rev;
+ ngx_http_core_srv_conf_t *cscf;
+
+ rev = r->connection->read;
+
+ n = r->header_in->last - r->header_in->pos;
+
+ if (n > 0) {
+ return n;
+ }
+
+ if (!rev->ready) {
+ return NGX_AGAIN;
+ }
+
+ n = r->connection->recv(r->connection, r->header_in->last,
+ r->header_in->end - r->header_in->last);
+
+ if (n == NGX_AGAIN) {
+ if (!r->header_timeout_set) {
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+ ngx_add_timer(rev, cscf->client_header_timeout);
+ r->header_timeout_set = 1;
+ }
+
+ if (ngx_handle_read_event(rev, 0) == NGX_ERROR) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ ngx_http_close_connection(r->connection);
+ return NGX_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ if (n == 0) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client closed prematurely connection");
+ }
+
+ if (n == 0 || n == NGX_ERROR) {
+ ngx_http_close_request(r, NGX_HTTP_BAD_REQUEST);
+ ngx_http_close_connection(r->connection);
+ 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->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: " PTR_FMT " " SIZE_T_FMT,
+ 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: " PTR_FMT " " SIZE_T_FMT,
+ 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->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);
+
+ 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);
+ 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);
+ }
+
+ } 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_request_header(ngx_http_request_t *r)
+{
+ u_char *ua, *user_agent;
+ size_t len;
+ ngx_uint_t i;
+ ngx_http_server_name_t *name;
+ ngx_http_core_srv_conf_t *cscf;
+ ngx_http_core_loc_conf_t *clcf;
+
+ if (r->headers_in.host) {
+ for (len = 0; len < r->headers_in.host->value.len; len++) {
+ if (r->headers_in.host->value.data[len] == ':') {
+ break;
+ }
+ }
+ r->headers_in.host_name_len = len;
+
+ /* find the name based server configuration */
+
+ name = r->virtual_names->elts;
+ for (i = 0; i < r->virtual_names->nelts; i++) {
+ if (r->headers_in.host_name_len != name[i].name.len) {
+ continue;
+ }
+
+ if (ngx_strncasecmp(r->headers_in.host->value.data,
+ name[i].name.data,
+ r->headers_in.host_name_len) == 0)
+ {
+ r->srv_conf = name[i].core_srv_conf->ctx->srv_conf;
+ r->loc_conf = name[i].core_srv_conf->ctx->loc_conf;
+ r->server_name = &name[i].name;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+ r->connection->log->file = clcf->err_log->file;
+ if (!(r->connection->log->log_level & NGX_LOG_DEBUG_CONNECTION))
+ {
+ r->connection->log->log_level = clcf->err_log->log_level;
+ }
+
+ break;
+ }
+ }
+
+ if (i == r->virtual_names->nelts) {
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ if (cscf->restrict_host_names != NGX_HTTP_RESTRICT_HOST_OFF) {
+ return NGX_HTTP_PARSE_INVALID_HOST;
+ }
+ }
+
+ } else {
+ if (r->http_version > NGX_HTTP_VERSION_10) {
+ return NGX_HTTP_PARSE_NO_HOST_HEADER;
+ }
+ r->headers_in.host_name_len = 0;
+ }
+
+ if (r->headers_in.content_length) {
+ r->headers_in.content_length_n =
+ ngx_atoi(r->headers_in.content_length->value.data,
+ r->headers_in.content_length->value.len);
+
+ if (r->headers_in.content_length_n == NGX_ERROR) {
+ return NGX_HTTP_PARSE_INVALID_CL_HEADER;
+ }
+ }
+
+ if (r->method == NGX_HTTP_POST && r->headers_in.content_length_n <= 0) {
+ return NGX_HTTP_PARSE_POST_WO_CL_HEADER;
+ }
+
+ if (r->plain_http) {
+ return NGX_HTTP_PARSE_HTTP_TO_HTTPS;
+ }
+
+ if (r->headers_in.connection) {
+ if (r->headers_in.connection->value.len == 5
+ && ngx_strcasecmp(r->headers_in.connection->value.data, "close")
+ == 0)
+ {
+ r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
+
+ } else if (r->headers_in.connection->value.len == 10
+ && ngx_strcasecmp(r->headers_in.connection->value.data,
+ "keep-alive") == 0)
+ {
+ r->headers_in.connection_type = NGX_HTTP_CONNECTION_KEEP_ALIVE;
+
+ if (r->headers_in.keep_alive) {
+ r->headers_in.keep_alive_n =
+ ngx_atoi(r->headers_in.keep_alive->value.data,
+ r->headers_in.keep_alive->value.len);
+ }
+ }
+ }
+
+ if (r->headers_in.user_agent) {
+
+ /*
+ * check some widespread browsers while the headers are still
+ * in CPU cache
+ */
+
+ user_agent = r->headers_in.user_agent->value.data;
+
+ ua = (u_char *) ngx_strstr(user_agent, "MSIE");
+
+ if (ua && ua + 8 < user_agent + r->headers_in.user_agent->value.len) {
+
+ r->headers_in.msie = 1;
+
+ if (ua[4] == ' ' && ua[5] == '4' && ua[6] == '.') {
+ r->headers_in.msie4 = 1;
+ }
+
+#if 0
+ /* MSIE ignores the SSL "close notify" alert */
+
+ ngx_ssl_set_nosendshut(r->connection->ssl);
+#endif
+ }
+
+ if (ngx_strstr(user_agent, "Opera")) {
+ r->headers_in.opera = 1;
+ r->headers_in.msie = 0;
+ r->headers_in.msie4 = 0;
+ }
+
+ if (!r->headers_in.msie && !r->headers_in.opera) {
+
+ if (ngx_strstr(user_agent, "Gecko/")) {
+ r->headers_in.gecko = 1;
+
+ } else if (ngx_strstr(user_agent, "Konqueror")) {
+ r->headers_in.konqueror = 1;
+ }
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+void ngx_http_finalize_request(ngx_http_request_t *r, int rc)
+{
+ ngx_http_core_loc_conf_t *clcf;
+
+ /* r can be already destroyed when rc == NGX_DONE */
+
+ if (rc == NGX_DONE || r->main) {
+ return;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http finalize request: %d", rc);
+
+ if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+
+ if (r->connection->read->timer_set) {
+ ngx_del_timer(r->connection->read);
+ }
+
+ if (r->connection->write->timer_set) {
+ ngx_del_timer(r->connection->write);
+ }
+
+ if (rc == NGX_HTTP_CLIENT_CLOSED_REQUEST || r->closed) {
+ ngx_http_close_request(r, 0);
+ ngx_http_close_connection(r->connection);
+ return;
+ }
+
+ ngx_http_finalize_request(r, ngx_http_special_response_handler(r, rc));
+
+ return;
+
+ } else if (rc == NGX_ERROR) {
+ ngx_http_close_request(r, 0);
+ ngx_http_close_connection(r->connection);
+ return;
+
+ } else if (rc == NGX_AGAIN) {
+ ngx_http_set_write_handler(r);
+ return;
+ }
+
+ if (r->connection->read->timer_set) {
+ ngx_del_timer(r->connection->read);
+ }
+
+ if (r->connection->write->timer_set) {
+ r->connection->write->delayed = 0;
+ ngx_del_timer(r->connection->write);
+ }
+
+ if (r->connection->read->pending_eof) {
+#if (NGX_KQUEUE)
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log,
+ r->connection->read->kq_errno,
+ "kevent() reported about an closed connection");
+#endif
+ ngx_http_close_request(r, 0);
+ ngx_http_close_connection(r->connection);
+ return;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (!ngx_terminate
+ && !ngx_exiting
+ && r->keepalive != 0
+ && clcf->keepalive_timeout > 0)
+ {
+ ngx_http_set_keepalive(r);
+ return;
+
+ } else if (r->lingering_close && clcf->lingering_timeout > 0) {
+ ngx_http_set_lingering_close(r);
+ return;
+ }
+
+ ngx_http_close_request(r, 0);
+ ngx_http_close_connection(r->connection);
+}
+
+
+static void ngx_http_set_write_handler(ngx_http_request_t *r)
+{
+ ngx_event_t *wev;
+ ngx_http_core_loc_conf_t *clcf;
+
+ wev = r->connection->write;
+ wev->event_handler = ngx_http_writer;
+
+ r->http_state = NGX_HTTP_WRITING_REQUEST_STATE;
+
+ if (wev->ready && wev->delayed) {
+ return;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r->main ? r->main : r,
+ ngx_http_core_module);
+ if (!wev->delayed) {
+ ngx_add_timer(wev, clcf->send_timeout);
+ }
+
+ wev->available = clcf->send_lowat;
+ if (ngx_handle_write_event(wev, NGX_LOWAT_EVENT) == NGX_ERROR) {
+ ngx_http_close_request(r, 0);
+ ngx_http_close_connection(r->connection);
+ }
+
+ return;
+}
+
+
+void ngx_http_writer(ngx_event_t *wev)
+{
+ int rc;
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+ ngx_http_core_loc_conf_t *clcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0, "http writer handler");
+
+ c = wev->data;
+ r = c->data;
+
+ if (wev->timedout) {
+ if (!wev->delayed) {
+ ngx_http_client_error(r, 0, NGX_HTTP_REQUEST_TIME_OUT);
+ return;
+ }
+
+ wev->timedout = 0;
+ wev->delayed = 0;
+
+ if (!wev->ready) {
+ clcf = ngx_http_get_module_loc_conf(r->main ? r->main : r,
+ ngx_http_core_module);
+ ngx_add_timer(wev, clcf->send_timeout);
+
+ wev->available = clcf->send_lowat;
+
+ if (ngx_handle_write_event(wev, NGX_LOWAT_EVENT) == NGX_ERROR) {
+ ngx_http_close_request(r, 0);
+ ngx_http_close_connection(r->connection);
+ }
+
+ return;
+ }
+
+ } else {
+ if (wev->delayed) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0,
+ "http writer delayed");
+
+ clcf = ngx_http_get_module_loc_conf(r->main ? r->main : r,
+ ngx_http_core_module);
+ wev->available = clcf->send_lowat;
+
+ if (ngx_handle_write_event(wev, NGX_LOWAT_EVENT) == NGX_ERROR) {
+ ngx_http_close_request(r, 0);
+ ngx_http_close_connection(r->connection);
+ }
+
+ return;
+ }
+ }
+
+ rc = ngx_http_output_filter(r, NULL);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http writer output filter: %d", rc);
+
+ if (rc == NGX_AGAIN) {
+ clcf = ngx_http_get_module_loc_conf(r->main ? r->main : r,
+ ngx_http_core_module);
+ if (!wev->ready && !wev->delayed) {
+ ngx_add_timer(wev, clcf->send_timeout);
+ }
+
+ wev->available = clcf->send_lowat;
+
+ if (ngx_handle_write_event(wev, NGX_LOWAT_EVENT) == NGX_ERROR) {
+ ngx_http_close_request(r, 0);
+ ngx_http_close_connection(r->connection);
+ }
+
+ return;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http writer done");
+
+ ngx_http_finalize_request(r, rc);
+}
+
+
+static void ngx_http_block_read(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http read blocked");
+
+ /* 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_ERROR) {
+ c = rev->data;
+ r = c->data;
+ ngx_http_close_request(r, 0);
+ ngx_http_close_connection(c);
+ }
+ }
+}
+
+
+ngx_int_t ngx_http_discard_body(ngx_http_request_t *r)
+{
+ ssize_t size;
+ ngx_event_t *rev;
+
+ 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) {
+ return NGX_OK;
+ }
+
+ size = r->header_in->last - r->header_in->pos;
+
+ if (size) {
+ if (r->headers_in.content_length_n > size) {
+ r->headers_in.content_length_n -= size;
+
+ } else {
+ r->header_in->pos += r->headers_in.content_length_n;
+ r->headers_in.content_length_n = 0;
+ return NGX_OK;
+ }
+ }
+
+ rev->event_handler = ngx_http_read_discarded_body_event;
+
+ if (ngx_handle_level_read_event(rev) == NGX_ERROR) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ return ngx_http_read_discarded_body(r);
+}
+
+
+static void ngx_http_read_discarded_body_event(ngx_event_t *rev)
+{
+ ngx_int_t rc;
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+
+ c = rev->data;
+ r = c->data;
+
+ rc = ngx_http_read_discarded_body(r);
+
+ if (rc == NGX_AGAIN) {
+ if (ngx_handle_level_read_event(rev) == NGX_ERROR) {
+ ngx_http_close_request(r, rc);
+ ngx_http_close_connection(c);
+ return;
+ }
+ }
+
+ if (rc != NGX_OK) {
+ ngx_http_close_request(r, rc);
+ ngx_http_close_connection(c);
+ }
+}
+
+
+static ngx_int_t ngx_http_read_discarded_body(ngx_http_request_t *r)
+{
+ ssize_t size, n;
+ u_char buffer[NGX_HTTP_DISCARD_BUFFER_SIZE];
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http read discarded body");
+
+ if (r->headers_in.content_length_n == 0) {
+ return NGX_OK;
+ }
+
+
+ size = r->headers_in.content_length_n;
+
+ if (size > NGX_HTTP_DISCARD_BUFFER_SIZE) {
+ size = NGX_HTTP_DISCARD_BUFFER_SIZE;
+ }
+
+ n = r->connection->recv(r->connection, buffer, size);
+
+ if (n == NGX_ERROR) {
+
+ r->closed = 1;
+
+ /*
+ * if a client request body is discarded then we already set
+ * some HTTP response code for client and we can ignore the error
+ */
+
+ return NGX_OK;
+ }
+
+ if (n == NGX_AGAIN) {
+ return NGX_AGAIN;
+ }
+
+ r->headers_in.content_length_n -= n;
+
+ return NGX_OK;
+}
+
+
+static void ngx_http_set_keepalive(ngx_http_request_t *r)
+{
+ 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_log_ctx_t *ctx;
+ ngx_http_core_srv_conf_t *cscf;
+ ngx_http_core_loc_conf_t *clcf;
+
+ c = r->connection;
+ rev = c->read;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "set http keepalive handler");
+
+ ctx = (ngx_http_log_ctx_t *) c->log->data;
+ ctx->action = "closing request";
+
+ hc = r->http_connection;
+ b = r->header_in;
+
+ if (b->pos < b->last) {
+
+ /* the pipelined request */
+
+ if (b != c->buffer) {
+
+ /* 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_connection(c);
+ 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;
+ }
+ }
+
+ ngx_http_close_request(r, 0);
+ c->data = hc;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+ ngx_add_timer(rev, clcf->keepalive_timeout);
+
+ if (ngx_handle_level_read_event(rev) == NGX_ERROR) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ wev = c->write;
+ wev->event_handler = ngx_http_empty_handler;
+
+ if (b->pos < b->last) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "pipelined request");
+
+ hc->pipeline = 1;
+ ctx->action = "reading client pipelined request line";
+ ngx_http_init_request(rev);
+ return;
+ }
+
+ hc->pipeline = 0;
+
+ if (ngx_pfree(c->pool, r) == NGX_OK) {
+ hc->request = NULL;
+ }
+
+ b = c->buffer;
+
+ if (ngx_pfree(c->pool, b->start) == NGX_OK) {
+ b->pos = NULL;
+
+ } else {
+ b->pos = b->start;
+ b->last = b->start;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "hc free: " PTR_FMT " %d",
+ hc->free, hc->nfree);
+
+ if (hc->free) {
+ for (i = 0; i < hc->nfree; i++) {
+ ngx_pfree(c->pool, hc->free[i]);
+ hc->free[i] = NULL;
+ }
+
+ hc->nfree = 0;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "hc busy: " PTR_FMT " %d",
+ hc->busy, hc->nbusy);
+
+ if (hc->busy) {
+ for (i = 0; i < hc->nbusy; i++) {
+ ngx_pfree(c->pool, hc->busy[i]);
+ hc->busy[i] = NULL;
+ }
+
+ hc->nbusy = 0;
+ }
+
+ rev->event_handler = ngx_http_keepalive_handler;
+
+ if (wev->active) {
+ if (ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) {
+ if (ngx_del_event(wev, NGX_WRITE_EVENT, NGX_DISABLE_EVENT)
+ == NGX_ERROR)
+ {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ } else if (ngx_event_flags & NGX_USE_LEVEL_EVENT) {
+ if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) == NGX_ERROR) {
+ ngx_http_close_connection(c);
+ return;
+ }
+ }
+ }
+
+ ctx->action = "keepalive";
+
+ if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) {
+ if (ngx_tcp_push(c->fd) == NGX_ERROR) {
+ 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;
+ }
+
+#if 0
+ /* if "keepalive_buffers off" then we need some other place */
+ r->http_state = NGX_HTTP_KEEPALIVE_STATE;
+#endif
+
+ if (rev->ready) {
+ ngx_http_keepalive_handler(rev);
+ }
+}
+
+
+static void ngx_http_keepalive_handler(ngx_event_t *rev)
+{
+ size_t size;
+ ssize_t n;
+ ngx_buf_t *b;
+ ngx_connection_t *c;
+ ngx_http_log_ctx_t *ctx;
+ ngx_http_connection_t *hc;
+
+ c = rev->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http keepalive handler");
+
+ if (rev->timedout) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ ctx = (ngx_http_log_ctx_t *) rev->log->data;
+
+#if (HAVE_KQUEUE)
+
+ if (ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) {
+ if (rev->pending_eof) {
+ ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno,
+ "kevent() reported that client %s closed "
+ "keepalive connection", ctx->client);
+ ngx_http_close_connection(c);
+ return;
+ }
+ }
+
+#endif
+
+ hc = c->data;
+ b = c->buffer;
+ size = b->end - b->start;
+
+ if (b->pos == NULL) {
+ if (!(b->pos = ngx_palloc(c->pool, size))) {
+ 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) {
+ return;
+ }
+
+ if (n == NGX_ERROR) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ rev->log->handler = NULL;
+
+ if (n == 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, ngx_socket_errno,
+ "client %s closed keepalive connection", ctx->client);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ b->last += n;
+ rev->log->handler = ngx_http_log_error;
+ ctx->action = "reading client request line";
+
+ 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->event_handler = ngx_http_lingering_close_handler;
+
+ r->lingering_time = ngx_time() + clcf->lingering_time / 1000;
+ ngx_add_timer(rev, clcf->lingering_timeout);
+
+ if (ngx_handle_level_read_event(rev) == NGX_ERROR) {
+ ngx_http_close_request(r, 0);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ wev = c->write;
+ wev->event_handler = ngx_http_empty_handler;
+
+ if (wev->active) {
+ if (ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) {
+ if (ngx_del_event(wev, NGX_WRITE_EVENT, NGX_DISABLE_EVENT)
+ == NGX_ERROR)
+ {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ } else if (ngx_event_flags & NGX_USE_LEVEL_EVENT) {
+ if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) == NGX_ERROR) {
+ ngx_http_close_connection(c);
+ return;
+ }
+ }
+ }
+
+ if (ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN) == -1) {
+ ngx_connection_error(c, ngx_socket_errno,
+ ngx_shutdown_socket_n " failed");
+ ngx_http_close_request(r, 0);
+ ngx_http_close_connection(c);
+ 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);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ timer = r->lingering_time - ngx_time();
+ if (timer <= 0) {
+ ngx_http_close_request(r, 0);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ do {
+ n = c->recv(c, buffer, NGX_HTTP_LINGERING_BUFFER_SIZE);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "lingering read: %d", n);
+
+ if (n == NGX_ERROR || n == 0) {
+ ngx_http_close_request(r, 0);
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ } while (rev->ready);
+
+ 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);
+
+ return;
+}
+
+
+void ngx_http_empty_handler(ngx_event_t *wev)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0, "http empty handler");
+
+ return;
+}
+
+
+ngx_int_t ngx_http_send_last(ngx_http_request_t *r)
+{
+ ngx_buf_t *b;
+ ngx_chain_t out;
+
+ if (!(b = ngx_calloc_buf(r->pool))) {
+ return NGX_ERROR;
+ }
+
+ b->last_buf = 1;
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_output_filter(r, &out);
+}
+
+
+void ngx_http_close_request(ngx_http_request_t *r, int error)
+{
+ ngx_uint_t i;
+ ngx_log_t *log;
+ ngx_http_log_ctx_t *ctx;
+ ngx_http_cleanup_t *cleanup;
+ ngx_http_core_loc_conf_t *clcf;
+ struct linger l;
+
+ 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;
+ }
+
+#if (NGX_STAT_STUB)
+ if (r->stat_reading) {
+ (*ngx_stat_reading)--;
+ }
+
+ if (r->stat_writing) {
+ (*ngx_stat_writing)--;
+ }
+#endif
+
+ if (error && r->headers_out.status == 0) {
+ r->headers_out.status = error;
+ }
+
+ ngx_http_log_handler(r);
+
+ cleanup = r->cleanup.elts;
+ for (i = 0; i < r->cleanup.nelts; i++) {
+ if (!cleanup[i].valid) {
+ continue;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ if (cleanup[i].cache) {
+ ngx_http_cache_unlock(cleanup[i].data.cache.hash,
+ cleanup[i].data.cache.cache, log);
+ continue;
+ }
+
+#endif
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http cleanup fd: %d",
+ cleanup[i].data.file.fd);
+
+ if (ngx_close_file(cleanup[i].data.file.fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed",
+ cleanup[i].data.file.name);
+ }
+ }
+
+ /* STUB */
+ if (r->file.fd != NGX_INVALID_FILE) {
+ if (ngx_close_file(r->file.fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", r->file.name.data);
+ }
+ }
+
+ if (r->request_body
+ && r->request_body->temp_file
+ && r->request_body->temp_file->file.fd != NGX_INVALID_FILE)
+ {
+ if (ngx_close_file(r->request_body->temp_file->file.fd)
+ == NGX_FILE_ERROR)
+ {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ ngx_close_file_n " deleted file \"%s\" failed",
+ r->request_body->temp_file->file.name.data);
+ }
+ }
+
+ if (r->connection->timedout) {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->reset_timedout_connection) {
+ l.l_onoff = 1;
+ l.l_linger = 0;
+
+ if (setsockopt(r->connection->fd, SOL_SOCKET, SO_LINGER,
+ (const void *) &l, sizeof(struct linger)) == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
+ "setsockopt(SO_LINGER) failed");
+ }
+ }
+ }
+
+ /* ctx->url was allocated from r->pool */
+ ctx = log->data;
+ ctx->url = NULL;
+
+ r->request_line.len = 0;
+
+ ngx_destroy_pool(r->pool);
+
+ return;
+}
+
+
+#if (NGX_HTTP_SSL)
+
+void ngx_ssl_close_handler(ngx_event_t *ev)
+{
+ ngx_connection_t *c;
+
+ c = ev->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0, "http ssl close handler");
+
+ if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
+ return;
+ }
+
+ ngx_http_close_connection(c);
+}
+
+#endif
+
+
+void ngx_http_close_connection(ngx_connection_t *c)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "close http connection: %d", c->fd);
+
+#if (NGX_STAT_STUB)
+ (*ngx_stat_active)--;
+#endif
+
+ ngx_close_connection(c);
+}
+
+
+static void ngx_http_client_error(ngx_http_request_t *r,
+ int client_error, int error)
+{
+ ngx_http_log_ctx_t *ctx;
+ ngx_http_core_srv_conf_t *cscf;
+
+ ctx = r->connection->log->data;
+
+ if (error == NGX_HTTP_REQUEST_TIME_OUT) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, NGX_ETIMEDOUT,
+ "client timed out");
+ r->connection->timedout = 1;
+ ngx_http_close_request(r, error);
+ ngx_http_close_connection(r->connection);
+ return;
+ }
+
+ r->connection->log->handler = NULL;
+
+ if (ctx->url) {
+ switch (client_error) {
+
+ case NGX_HTTP_PARSE_INVALID_HOST:
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ client_header_errors[client_error - NGX_HTTP_CLIENT_ERROR],
+ ctx->client, r->headers_in.host->value.data, ctx->url);
+
+ error = NGX_HTTP_INVALID_HOST;
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ if (cscf->restrict_host_names == NGX_HTTP_RESTRICT_HOST_CLOSE) {
+ ngx_http_close_request(r, error);
+ ngx_http_close_connection(r->connection);
+ return;
+ }
+
+ break;
+
+ case NGX_HTTP_PARSE_HTTP_TO_HTTPS:
+ error = NGX_HTTP_TO_HTTPS;
+
+ /* fall through */
+
+ default:
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ client_header_errors[client_error - NGX_HTTP_CLIENT_ERROR],
+ ctx->client, ctx->url);
+ }
+
+ } else {
+ if (error == NGX_HTTP_REQUEST_URI_TOO_LARGE) {
+ r->request_line.len = r->header_in->end - r->request_start;
+ r->request_line.data = r->request_start;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ client_header_errors[client_error - NGX_HTTP_CLIENT_ERROR],
+ ctx->client);
+ }
+
+ r->connection->log->handler = ngx_http_log_error;
+
+ ngx_http_finalize_request(r, error);
+}
+
+
+static size_t ngx_http_log_error(void *data, char *buf, size_t len)
+{
+ ngx_http_log_ctx_t *ctx = data;
+
+ if (ctx->action && ctx->url) {
+ return ngx_snprintf(buf, len, " while %s, client: %s, URL: %s",
+ ctx->action, ctx->client, ctx->url);
+
+ } else if (ctx->action == NULL && ctx->url) {
+ return ngx_snprintf(buf, len, ", client: %s, URL: %s",
+ ctx->client, ctx->url);
+
+ } else {
+ return ngx_snprintf(buf, len, " while %s, client: %s",
+ ctx->action, ctx->client);
+ }
+}
diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h
new file mode 100644
index 000000000..ef169f587
--- /dev/null
+++ b/src/http/ngx_http_request.h
@@ -0,0 +1,372 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_REQUEST_H_INCLUDED_
+#define _NGX_HTTP_REQUEST_H_INCLUDED_
+
+
+#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_GET 1
+#define NGX_HTTP_HEAD 2
+#define NGX_HTTP_POST 3
+
+#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_TOO_LONG_URI 12
+#define NGX_HTTP_PARSE_INVALID_09_METHOD 13
+
+#define NGX_HTTP_PARSE_HEADER_ERROR 14
+#define NGX_HTTP_PARSE_INVALID_HEADER 14
+#define NGX_HTTP_PARSE_TOO_LONG_HEADER 15
+#define NGX_HTTP_PARSE_NO_HOST_HEADER 17
+#define NGX_HTTP_PARSE_INVALID_CL_HEADER 18
+#define NGX_HTTP_PARSE_POST_WO_CL_HEADER 19
+#define NGX_HTTP_PARSE_HTTP_TO_HTTPS 20
+#define NGX_HTTP_PARSE_INVALID_HOST 21
+
+
+#define NGX_HTTP_OK 200
+#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_NOT_MODIFIED 304
+
+#define NGX_HTTP_BAD_REQUEST 400
+#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_REQUEST_ENTITY_TOO_LARGE 413
+#define NGX_HTTP_REQUEST_URI_TOO_LARGE 414
+#define NGX_HTTP_RANGE_NOT_SATISFIABLE 416
+
+
+/* Our own HTTP codes */
+
+#define NGX_HTTP_NGX_CODES NGX_HTTP_TO_HTTPS
+
+/*
+ * 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
+
+/*
+ * We use the special code for the requests with invalid host name
+ * to distinguish it from 4XX in an error page redirection
+ */
+#define NGX_HTTP_INVALID_HOST 498
+
+/*
+ * 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
+
+
+typedef enum {
+ NGX_HTTP_RESTRICT_HOST_OFF = 0,
+ NGX_HTTP_RESTRICT_HOST_ON,
+ NGX_HTTP_RESTRICT_HOST_CLOSE
+} ngx_http_restrict_host_e;
+
+
+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_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 *user_agent;
+ ngx_table_elt_t *referer;
+ ngx_table_elt_t *content_length;
+
+ ngx_table_elt_t *range;
+
+#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_table_elt_t *x_forwarded_for;
+#endif
+
+ ngx_array_t cookies;
+
+ size_t host_name_len;
+ ssize_t content_length_n;
+ size_t connection_type;
+ ssize_t keep_alive_n;
+
+ unsigned msie:1;
+ unsigned msie4:1;
+ unsigned opera:1;
+ unsigned gecko:1;
+ unsigned konqueror:1;
+} ngx_http_headers_in_t;
+
+
+typedef struct {
+ off_t start;
+ off_t end;
+ ngx_str_t content_range;
+} ngx_http_range_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_type;
+ ngx_table_elt_t *content_length;
+ ngx_table_elt_t *content_encoding;
+ ngx_table_elt_t *location;
+ ngx_table_elt_t *last_modified;
+ ngx_table_elt_t *content_range;
+ ngx_table_elt_t *accept_ranges;
+ ngx_table_elt_t *expires;
+ ngx_table_elt_t *cache_control;
+ ngx_table_elt_t *etag;
+
+ ngx_str_t charset;
+ ngx_array_t ranges;
+
+ off_t content_length_n;
+ time_t date_time;
+ time_t last_modified_time;
+} ngx_http_headers_out_t;
+
+
+typedef struct {
+ ngx_temp_file_t *temp_file;
+ ngx_chain_t *bufs;
+ ngx_buf_t *buf;
+ size_t rest;
+ void (*handler) (void *data);
+ void *data;
+} ngx_http_request_body_t;
+
+
+struct ngx_http_cleanup_s {
+ union {
+ struct {
+ ngx_fd_t fd;
+ u_char *name;
+ } file;
+
+ struct {
+ ngx_http_cache_hash_t *hash;
+ ngx_http_cache_t *cache;
+ } cache;
+ } data;
+
+ unsigned valid:1;
+ unsigned cache:1;
+};
+
+
+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 ngx_int_t (*ngx_http_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_cache_t *cache;
+
+ ngx_file_t file;
+
+ 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;
+
+ ngx_uint_t method;
+ ngx_uint_t http_version;
+ ngx_uint_t http_major;
+ ngx_uint_t http_minor;
+
+ 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_http_request_t *main;
+
+ uint32_t in_addr;
+ ngx_uint_t port;
+ ngx_str_t *port_text; /* ":80" */
+ ngx_str_t *server_name;
+ ngx_array_t *virtual_names;
+
+ ngx_uint_t phase;
+ ngx_int_t phase_handler;
+ ngx_http_handler_pt content_handler;
+
+ ngx_array_t cleanup;
+
+ /* used to learn the Apache compatible response length without a header */
+ size_t header_size;
+
+ u_char *discarded_buffer;
+ void **err_ctx;
+ ngx_uint_t err_status;
+
+ ngx_http_connection_t *http_connection;
+
+ unsigned http_state:4;
+
+#if 0
+ /* URI is not started with '/' - "GET http://" */
+ unsigned unusual_uri:1;
+#endif
+ /* URI with "/.", "%" and on Win32 with "//" */
+ unsigned complex_uri:1;
+ unsigned header_timeout_set:1;
+
+ unsigned proxy:1;
+ unsigned bypass_cache:1;
+ unsigned no_cache:1;
+
+#if 0
+ unsigned cachable:1;
+#endif
+ unsigned pipeline:1;
+
+ /* can we use sendfile ? */
+ unsigned sendfile:1;
+
+ unsigned plain_http:1;
+ unsigned chunked:1;
+ unsigned header_only:1;
+ unsigned keepalive:1;
+ unsigned lingering_close:1;
+ unsigned closed:1;
+
+ unsigned filter_need_in_memory:1;
+ unsigned filter_ssi_need_in_memory:1;
+ unsigned filter_need_temporary:1;
+ unsigned filter_allow_ranges:1;
+
+#if (NGX_STAT_STUB)
+ unsigned stat_reading:1;
+ unsigned stat_writing:1;
+#endif
+
+ ngx_uint_t headers_n;
+
+ /* used to parse HTTP headers */
+ ngx_uint_t state;
+ 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;
+ u_char *header_name_start;
+ u_char *header_name_end;
+ u_char *header_start;
+ u_char *header_end;
+};
+
+
+extern ngx_http_header_t ngx_http_headers_in[];
+extern ngx_http_header_t ngx_http_headers_out[];
+
+
+
+#endif /* _NGX_HTTP_REQUEST_H_INCLUDED_ */
diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c
new file mode 100644
index 000000000..ee8e3685b
--- /dev/null
+++ b/src/http/ngx_http_request_body.c
@@ -0,0 +1,227 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_http.h>
+
+
+static void ngx_http_read_client_request_body_handler(ngx_event_t *rev);
+static ngx_int_t ngx_http_do_read_client_request_body(ngx_http_request_t *r);
+
+
+ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r)
+{
+ ssize_t size;
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+ ngx_http_core_loc_conf_t *clcf;
+
+ size = r->header_in->last - r->header_in->pos;
+
+ if (size) {
+
+ /* there is the pre-read part of the request body */
+
+ ngx_test_null(b, ngx_calloc_buf(r->pool),
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+ b->temporary = 1;
+ b->start = b->pos = r->header_in->pos;
+ b->end = b->last = r->header_in->last;
+
+ ngx_alloc_link_and_set_buf(r->request_body->bufs, b, r->pool,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+ if (size >= r->headers_in.content_length_n) {
+
+ /* the whole request body was pre-read */
+
+ r->header_in->pos += r->headers_in.content_length_n;
+
+ r->request_body->handler(r->request_body->data);
+
+ return NGX_OK;
+ }
+
+ r->header_in->pos = r->header_in->last;
+ }
+
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ r->request_body->rest = r->headers_in.content_length_n - size;
+
+ if (r->request_body->rest
+ < clcf->client_body_buffer_size
+ + (clcf->client_body_buffer_size >> 2))
+ {
+ size = r->request_body->rest;
+
+ } else {
+ size = clcf->client_body_buffer_size;
+ }
+
+ ngx_test_null(r->request_body->buf, ngx_create_temp_buf(r->pool, size),
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+ ngx_alloc_link_and_set_buf(cl, r->request_body->buf, r->pool,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+ if (r->request_body->bufs) {
+ r->request_body->bufs->next = cl;
+
+ } else {
+ r->request_body->bufs = cl;
+ }
+
+ r->connection->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_event_t *rev)
+{
+ ngx_int_t rc;
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+
+ c = rev->data;
+ r = c->data;
+
+ if (rev->timedout) {
+ 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_core_loc_conf_t *clcf;
+
+ c = r->connection;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http read client request body");
+
+ for ( ;; ) {
+ if (r->request_body->buf->last == r->request_body->buf->end) {
+ n = ngx_write_chain_to_temp_file(r->request_body->temp_file,
+ r->request_body->bufs->next ? r->request_body->bufs->next:
+ r->request_body->bufs);
+
+ /* TODO: n == 0 or not complete and level event */
+
+ if (n == NGX_ERROR) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ r->request_body->temp_file->offset += n;
+
+ r->request_body->buf->pos = r->request_body->buf->start;
+ r->request_body->buf->last = r->request_body->buf->start;
+ }
+
+ size = r->request_body->buf->end - r->request_body->buf->last;
+
+ if (size > r->request_body->rest) {
+ size = r->request_body->rest;
+ }
+
+ n = c->recv(c, r->request_body->buf->last, size);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http client request body recv " SIZE_T_FMT, n);
+
+ if (n == NGX_AGAIN) {
+ 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_ERROR) {
+ return NGX_HTTP_INTERNAL_SERVER_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) {
+ r->closed = 1;
+ return NGX_HTTP_BAD_REQUEST;
+ }
+
+ r->request_body->buf->last += n;
+ r->request_body->rest -= n;
+
+ if (r->request_body->rest == 0) {
+ break;
+ }
+
+ if (r->request_body->buf->last < r->request_body->buf->end) {
+ break;
+ }
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http client request body rest " SIZE_T_FMT,
+ r->request_body->rest);
+
+ if (r->request_body->rest) {
+ return NGX_AGAIN;
+ }
+
+ if (r->request_body->temp_file->file.fd != NGX_INVALID_FILE) {
+
+ /* save the last part */
+ n = ngx_write_chain_to_temp_file(r->request_body->temp_file,
+ r->request_body->bufs->next ? r->request_body->bufs->next:
+ r->request_body->bufs);
+
+ /* TODO: n == 0 or not complete and level event */
+
+ if (n == NGX_ERROR) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (!(b = ngx_calloc_buf(r->pool))) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ b->in_file = 1;
+ b->file_pos = 0;
+ b->file_last = r->request_body->temp_file->file.offset;
+ b->file = &r->request_body->temp_file->file;
+
+ if (r->request_body->bufs->next) {
+ r->request_body->bufs->next->buf = b;
+
+ } else {
+ r->request_body->bufs->buf = b;
+ }
+ }
+
+ r->request_body->handler(r->request_body->data);
+
+ return NGX_OK;
+}
diff --git a/src/http/ngx_http_special_response.c b/src/http/ngx_http_special_response.c
new file mode 100644
index 000000000..1af8746e5
--- /dev/null
+++ b/src/http/ngx_http_special_response.c
@@ -0,0 +1,358 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+
+static u_char error_tail[] =
+"<hr><center>" NGINX_VER "</center>" CRLF
+"</body>" CRLF
+"</html>" CRLF
+;
+
+
+static u_char msie_stub[] =
+"<!-- The padding to disable MSIE's friendly error page -->" CRLF
+"<!-- The padding to disable MSIE's friendly error page -->" CRLF
+"<!-- The padding to disable MSIE's friendly error page -->" CRLF
+"<!-- The padding to disable MSIE's friendly error page -->" CRLF
+"<!-- The padding to disable MSIE's friendly error page -->" CRLF
+"<!-- The padding to disable MSIE's friendly error page -->" CRLF
+;
+
+
+static char 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 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 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 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 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 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 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 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 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 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 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 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 error_501_page[] =
+"<html>" CRLF
+"<head><title>501 Method Not Implemented</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>500 Method Not Implemented</h1></center>" CRLF
+;
+
+
+static char 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 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 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 ngx_str_t error_pages[] = {
+ /* ngx_null_string, */ /* 300 */
+ ngx_string(error_301_page),
+ ngx_string(error_302_page),
+ ngx_null_string, /* 303 */
+
+ ngx_string(error_400_page),
+ ngx_null_string, /* 401 */
+ ngx_null_string, /* 402 */
+ ngx_string(error_403_page),
+ ngx_string(error_404_page),
+ ngx_string(error_405_page),
+ ngx_null_string, /* 406 */
+ ngx_null_string, /* 407 */
+ ngx_string(error_408_page),
+ ngx_null_string, /* 409 */
+ ngx_null_string, /* 410 */
+ ngx_null_string, /* 411 */
+ ngx_null_string, /* 412 */
+ ngx_string(error_413_page),
+ ngx_string(error_414_page),
+ ngx_null_string, /* 415 */
+ ngx_string(error_416_page),
+
+ ngx_string(error_497_page), /* 497, http to https */
+ ngx_string(error_404_page), /* 498, invalid host name */
+ ngx_null_string, /* 499, client closed connection */
+
+ ngx_string(error_500_page),
+ ngx_string(error_501_page),
+ ngx_string(error_502_page),
+ ngx_string(error_503_page),
+ ngx_string(error_504_page)
+};
+
+
+ngx_int_t ngx_http_special_response_handler(ngx_http_request_t *r, int error)
+{
+ ngx_int_t rc;
+ ngx_uint_t err, i, msie_padding;
+ ngx_buf_t *b;
+ ngx_chain_t *out, **ll, *cl;
+ ngx_http_err_page_t *err_page;
+ ngx_http_core_loc_conf_t *clcf;
+
+ rc = ngx_http_discard_body(r);
+
+ if (rc == NGX_HTTP_INTERNAL_SERVER_ERROR) {
+ error = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ r->headers_out.status = error;
+
+ if (r->keepalive != 0) {
+ 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_HTTP_INTERNAL_SERVER_ERROR:
+ r->keepalive = 0;
+ }
+ }
+
+ if (r->lingering_close == 1) {
+ switch (error) {
+ case NGX_HTTP_BAD_REQUEST:
+ case NGX_HTTP_TO_HTTPS:
+ r->lingering_close = 0;
+ }
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (r->err_ctx == NULL && clcf->error_pages) {
+ err_page = clcf->error_pages->elts;
+ for (i = 0; i < clcf->error_pages->nelts; i++) {
+ if (err_page[i].status == error) {
+ if (err_page[i].overwrite) {
+ r->err_status = err_page[i].overwrite;
+ } else {
+ r->err_status = error;
+ }
+ r->err_ctx = r->ctx;
+ return ngx_http_internal_redirect(r, &err_page[i].uri, NULL);
+ }
+ }
+ }
+
+ if (error < NGX_HTTP_BAD_REQUEST) {
+ /* 3XX */
+ err = error - NGX_HTTP_MOVED_PERMANENTLY;
+
+ } else if (error < NGX_HTTP_NGX_CODES) {
+ /* 4XX */
+ err = error - NGX_HTTP_BAD_REQUEST + 3;
+
+ } else {
+ /* 49X, 5XX */
+ err = error - NGX_HTTP_NGX_CODES + 3 + 17;
+
+ switch (error) {
+ case NGX_HTTP_TO_HTTPS:
+ r->headers_out.status = NGX_HTTP_BAD_REQUEST;
+ error = NGX_HTTP_BAD_REQUEST;
+ break;
+
+ case NGX_HTTP_INVALID_HOST:
+ r->headers_out.status = NGX_HTTP_NOT_FOUND;
+ error = NGX_HTTP_NOT_FOUND;
+ break;
+ }
+ }
+
+ msie_padding = 0;
+
+ if (error_pages[err].len) {
+ r->headers_out.content_length_n = error_pages[err].len
+ + sizeof(error_tail) - 1;
+
+ if (clcf->msie_padding
+ && r->headers_in.msie
+ && r->http_version >= NGX_HTTP_VERSION_10
+ && error >= NGX_HTTP_BAD_REQUEST
+ && error != NGX_HTTP_REQUEST_URI_TOO_LARGE)
+ {
+ r->headers_out.content_length_n += sizeof(msie_stub) - 1;
+ msie_padding = 1;
+ }
+
+ r->headers_out.content_type = ngx_list_push(&r->headers_out.headers);
+ if (r->headers_out.content_type == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->headers_out.content_type->key.len = sizeof("Content-Type") - 1;
+ r->headers_out.content_type->key.data = (u_char *) "Content-Type";
+ r->headers_out.content_type->value.len = sizeof("text/html") - 1;
+ r->headers_out.content_type->value.data = (u_char *) "text/html";
+
+ } else {
+ r->headers_out.content_length_n = -1;
+ }
+
+ if (r->headers_out.content_length) {
+ r->headers_out.content_length->key.len = 0;
+ r->headers_out.content_length = NULL;
+ }
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || r->header_only) {
+ return rc;
+ }
+
+ if (error_pages[err].len == 0) {
+ return NGX_OK;
+ }
+
+ out = NULL;
+ ll = NULL;
+
+ if (!(b = ngx_calloc_buf(r->pool))) {
+ return NGX_ERROR;
+ }
+ b->memory = 1;
+ b->pos = error_pages[err].data;
+ b->last = error_pages[err].data + error_pages[err].len;
+
+ ngx_alloc_link_and_set_buf(cl, b, r->pool, NGX_ERROR);
+ ngx_chain_add_link(out, ll, cl);
+
+
+ if (!(b = ngx_calloc_buf(r->pool))) {
+ return NGX_ERROR;
+ }
+ b->memory = 1;
+ b->pos = error_tail;
+ b->last = error_tail + sizeof(error_tail) - 1;
+
+ ngx_alloc_link_and_set_buf(cl, b, r->pool, NGX_ERROR);
+ ngx_chain_add_link(out, ll, cl);
+
+ if (msie_padding) {
+ if (!(b = ngx_calloc_buf(r->pool))) {
+ return NGX_ERROR;
+ }
+ b->memory = 1;
+ b->pos = msie_stub;
+ b->last = msie_stub + sizeof(msie_stub) - 1;
+
+ ngx_alloc_link_and_set_buf(cl, b, r->pool, NGX_ERROR);
+ ngx_chain_add_link(out, ll, cl);
+ }
+
+ b->last_buf = 1;
+
+ return ngx_http_output_filter(r, out);
+}
diff --git a/src/http/ngx_http_write_filter.c b/src/http/ngx_http_write_filter.c
new file mode 100644
index 000000000..a8c68947f
--- /dev/null
+++ b/src/http/ngx_http_write_filter.c
@@ -0,0 +1,166 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_chain_t *out;
+} ngx_http_write_filter_ctx_t;
+
+
+static ngx_int_t ngx_http_write_filter_init(ngx_cycle_t *cycle);
+
+
+ngx_http_module_t ngx_http_write_filter_module_ctx = {
+ NULL, /* pre conf */
+
+ 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,
+ &ngx_http_write_filter_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ ngx_http_write_filter_init, /* init module */
+ NULL /* init process */
+};
+
+
+ngx_int_t ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ int last;
+ off_t size, flush, sent;
+ ngx_chain_t *cl, *ln, **ll, *chain;
+ ngx_connection_t *c;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_write_filter_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r->main ? r->main : r,
+ ngx_http_write_filter_module);
+
+ if (ctx == NULL) {
+ ngx_http_create_ctx(r, ctx, ngx_http_write_filter_module,
+ sizeof(ngx_http_write_filter_ctx_t), NGX_ERROR);
+ }
+
+ size = 0;
+ flush = 0;
+ last = 0;
+ ll = &ctx->out;
+
+ /* find the size, the flush point and the last link of the saved chain */
+
+ for (cl = ctx->out; cl; cl = cl->next) {
+ ll = &cl->next;
+
+ size += ngx_buf_size(cl->buf);
+
+ if (cl->buf->flush || cl->buf->recycled) {
+ flush = size;
+ }
+
+ if (cl->buf->last_buf) {
+ last = 1;
+ }
+ }
+
+ /* add the new chain to the existent one */
+
+ for (ln = in; ln; ln = ln->next) {
+ ngx_alloc_link_and_set_buf(cl, ln->buf, r->pool, NGX_ERROR);
+ *ll = cl;
+ ll = &cl->next;
+
+ size += ngx_buf_size(cl->buf);
+
+ if (cl->buf->flush || cl->buf->recycled) {
+ flush = size;
+ }
+
+ if (cl->buf->last_buf) {
+ last = 1;
+ }
+ }
+
+ c = r->connection;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http write filter: l:%d f:" OFF_T_FMT " s:" OFF_T_FMT,
+ last, flush, size);
+
+ clcf = ngx_http_get_module_loc_conf(r->main ? r->main : r,
+ ngx_http_core_module);
+
+ /*
+ * avoid the output if there is 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 == 0 && in && size < (off_t) clcf->postpone_output) {
+ return NGX_OK;
+ }
+
+ if (c->write->delayed) {
+ return NGX_AGAIN;
+ }
+
+ if (size == 0 && !c->buffered) {
+ if (!last) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "the http output chain is empty");
+ }
+ return NGX_OK;
+ }
+
+ sent = c->sent;
+
+ chain = c->send_chain(c, ctx->out,
+ clcf->limit_rate ? clcf->limit_rate: OFF_T_MAX_VALUE);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http write filter %X", chain);
+
+ if (clcf->limit_rate) {
+ sent = c->sent - sent;
+ c->write->delayed = 1;
+ ngx_add_timer(r->connection->write,
+ (ngx_msec_t) (sent * 1000 / clcf->limit_rate));
+ }
+
+ if (chain == NGX_CHAIN_ERROR) {
+ return NGX_ERROR;
+ }
+
+ ctx->out = chain;
+
+ if (chain || c->buffered) {
+ return NGX_AGAIN;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t ngx_http_write_filter_init(ngx_cycle_t *cycle)
+{
+ ngx_http_top_body_filter = ngx_http_write_filter;
+
+ return NGX_OK;
+}