diff options
| author | Igor Sysoev <igor@sysoev.ru> | 2004-10-05 15:39:18 +0000 |
|---|---|---|
| committer | Jonathan Kolb <jon@b0g.us> | 2004-10-05 15:39:18 +0000 |
| commit | 7d66b1f69bb81e48c7efb79884771ea66b3685ca (patch) | |
| tree | e0cf023d6eebb4e422e62c5403e74e8218dc3192 /src/http | |
| download | nginx-7d66b1f69bb81e48c7efb79884771ea66b3685ca.tar.gz | |
The first public versionv0.1.0
Diffstat (limited to 'src/http')
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; +} |
