aboutsummaryrefslogtreecommitdiffstats
path: root/app/nginx/src/http/modules
diff options
context:
space:
mode:
Diffstat (limited to 'app/nginx/src/http/modules')
-rw-r--r--app/nginx/src/http/modules/ngx_http_access_module.c469
-rw-r--r--app/nginx/src/http/modules/ngx_http_addition_filter_module.c252
-rw-r--r--app/nginx/src/http/modules/ngx_http_auth_basic_module.c467
-rw-r--r--app/nginx/src/http/modules/ngx_http_auth_request_module.c444
-rw-r--r--app/nginx/src/http/modules/ngx_http_autoindex_module.c1050
-rw-r--r--app/nginx/src/http/modules/ngx_http_browser_module.c715
-rw-r--r--app/nginx/src/http/modules/ngx_http_charset_filter_module.c1685
-rw-r--r--app/nginx/src/http/modules/ngx_http_chunked_filter_module.c243
-rw-r--r--app/nginx/src/http/modules/ngx_http_dav_module.c1159
-rw-r--r--app/nginx/src/http/modules/ngx_http_degradation_module.c243
-rw-r--r--app/nginx/src/http/modules/ngx_http_empty_gif_module.c140
-rw-r--r--app/nginx/src/http/modules/ngx_http_fastcgi_module.c3788
-rw-r--r--app/nginx/src/http/modules/ngx_http_flv_module.c266
-rw-r--r--app/nginx/src/http/modules/ngx_http_geo_module.c1658
-rw-r--r--app/nginx/src/http/modules/ngx_http_geoip_module.c925
-rw-r--r--app/nginx/src/http/modules/ngx_http_gunzip_filter_module.c687
-rw-r--r--app/nginx/src/http/modules/ngx_http_gzip_filter_module.c1245
-rw-r--r--app/nginx/src/http/modules/ngx_http_gzip_static_module.c331
-rw-r--r--app/nginx/src/http/modules/ngx_http_headers_filter_module.c741
-rw-r--r--app/nginx/src/http/modules/ngx_http_image_filter_module.c1675
-rw-r--r--app/nginx/src/http/modules/ngx_http_index_module.c540
-rw-r--r--app/nginx/src/http/modules/ngx_http_limit_conn_module.c670
-rw-r--r--app/nginx/src/http/modules/ngx_http_limit_req_module.c960
-rw-r--r--app/nginx/src/http/modules/ngx_http_log_module.c1857
-rw-r--r--app/nginx/src/http/modules/ngx_http_map_module.c589
-rw-r--r--app/nginx/src/http/modules/ngx_http_memcached_module.c723
-rw-r--r--app/nginx/src/http/modules/ngx_http_mp4_module.c3548
-rw-r--r--app/nginx/src/http/modules/ngx_http_not_modified_filter_module.c266
-rw-r--r--app/nginx/src/http/modules/ngx_http_proxy_module.c4424
-rw-r--r--app/nginx/src/http/modules/ngx_http_random_index_module.c317
-rw-r--r--app/nginx/src/http/modules/ngx_http_range_filter_module.c920
-rw-r--r--app/nginx/src/http/modules/ngx_http_realip_module.c570
-rw-r--r--app/nginx/src/http/modules/ngx_http_referer_module.c671
-rw-r--r--app/nginx/src/http/modules/ngx_http_rewrite_module.c1022
-rw-r--r--app/nginx/src/http/modules/ngx_http_scgi_module.c2005
-rw-r--r--app/nginx/src/http/modules/ngx_http_secure_link_module.c368
-rw-r--r--app/nginx/src/http/modules/ngx_http_slice_filter_module.c543
-rw-r--r--app/nginx/src/http/modules/ngx_http_split_clients_module.c246
-rw-r--r--app/nginx/src/http/modules/ngx_http_ssi_filter_module.c2930
-rw-r--r--app/nginx/src/http/modules/ngx_http_ssi_filter_module.h114
-rw-r--r--app/nginx/src/http/modules/ngx_http_ssl_module.c992
-rw-r--r--app/nginx/src/http/modules/ngx_http_ssl_module.h66
-rw-r--r--app/nginx/src/http/modules/ngx_http_static_module.c287
-rw-r--r--app/nginx/src/http/modules/ngx_http_stub_status_module.c236
-rw-r--r--app/nginx/src/http/modules/ngx_http_sub_filter_module.c1018
-rw-r--r--app/nginx/src/http/modules/ngx_http_upstream_hash_module.c676
-rw-r--r--app/nginx/src/http/modules/ngx_http_upstream_ip_hash_module.c272
-rw-r--r--app/nginx/src/http/modules/ngx_http_upstream_keepalive_module.c529
-rw-r--r--app/nginx/src/http/modules/ngx_http_upstream_least_conn_module.c314
-rw-r--r--app/nginx/src/http/modules/ngx_http_upstream_zone_module.c246
-rw-r--r--app/nginx/src/http/modules/ngx_http_userid_filter_module.c842
-rw-r--r--app/nginx/src/http/modules/ngx_http_uwsgi_module.c2393
-rw-r--r--app/nginx/src/http/modules/ngx_http_xslt_filter_module.c1147
-rw-r--r--app/nginx/src/http/modules/perl/Makefile.PL35
-rw-r--r--app/nginx/src/http/modules/perl/nginx.pm138
-rw-r--r--app/nginx/src/http/modules/perl/nginx.xs1039
-rw-r--r--app/nginx/src/http/modules/perl/ngx_http_perl_module.c1086
-rw-r--r--app/nginx/src/http/modules/perl/ngx_http_perl_module.h67
-rw-r--r--app/nginx/src/http/modules/perl/typemap3
59 files changed, 52852 insertions, 0 deletions
diff --git a/app/nginx/src/http/modules/ngx_http_access_module.c b/app/nginx/src/http/modules/ngx_http_access_module.c
new file mode 100644
index 0000000..c553e46
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_access_module.c
@@ -0,0 +1,469 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ in_addr_t mask;
+ in_addr_t addr;
+ ngx_uint_t deny; /* unsigned deny:1; */
+} ngx_http_access_rule_t;
+
+#if (NGX_HAVE_INET6)
+
+typedef struct {
+ struct in6_addr addr;
+ struct in6_addr mask;
+ ngx_uint_t deny; /* unsigned deny:1; */
+} ngx_http_access_rule6_t;
+
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+typedef struct {
+ ngx_uint_t deny; /* unsigned deny:1; */
+} ngx_http_access_rule_un_t;
+
+#endif
+
+typedef struct {
+ ngx_array_t *rules; /* array of ngx_http_access_rule_t */
+#if (NGX_HAVE_INET6)
+ ngx_array_t *rules6; /* array of ngx_http_access_rule6_t */
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+ ngx_array_t *rules_un; /* array of ngx_http_access_rule_un_t */
+#endif
+} ngx_http_access_loc_conf_t;
+
+
+static ngx_int_t ngx_http_access_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_access_inet(ngx_http_request_t *r,
+ ngx_http_access_loc_conf_t *alcf, in_addr_t addr);
+#if (NGX_HAVE_INET6)
+static ngx_int_t ngx_http_access_inet6(ngx_http_request_t *r,
+ ngx_http_access_loc_conf_t *alcf, u_char *p);
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+static ngx_int_t ngx_http_access_unix(ngx_http_request_t *r,
+ ngx_http_access_loc_conf_t *alcf);
+#endif
+static ngx_int_t ngx_http_access_found(ngx_http_request_t *r, ngx_uint_t deny);
+static char *ngx_http_access_rule(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static void *ngx_http_access_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_access_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_access_init(ngx_conf_t *cf);
+
+
+static ngx_command_t ngx_http_access_commands[] = {
+
+ { ngx_string("allow"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF
+ |NGX_CONF_TAKE1,
+ ngx_http_access_rule,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("deny"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF
+ |NGX_CONF_TAKE1,
+ ngx_http_access_rule,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+
+static ngx_http_module_t ngx_http_access_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_access_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_access_create_loc_conf, /* create location configuration */
+ ngx_http_access_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_access_module = {
+ NGX_MODULE_V1,
+ &ngx_http_access_module_ctx, /* module context */
+ ngx_http_access_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_access_handler(ngx_http_request_t *r)
+{
+ struct sockaddr_in *sin;
+ ngx_http_access_loc_conf_t *alcf;
+#if (NGX_HAVE_INET6)
+ u_char *p;
+ in_addr_t addr;
+ struct sockaddr_in6 *sin6;
+#endif
+
+ alcf = ngx_http_get_module_loc_conf(r, ngx_http_access_module);
+
+ switch (r->connection->sockaddr->sa_family) {
+
+ case AF_INET:
+ if (alcf->rules) {
+ sin = (struct sockaddr_in *) r->connection->sockaddr;
+ return ngx_http_access_inet(r, alcf, sin->sin_addr.s_addr);
+ }
+ break;
+
+#if (NGX_HAVE_INET6)
+
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) r->connection->sockaddr;
+ p = sin6->sin6_addr.s6_addr;
+
+ if (alcf->rules && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ addr = p[12] << 24;
+ addr += p[13] << 16;
+ addr += p[14] << 8;
+ addr += p[15];
+ return ngx_http_access_inet(r, alcf, htonl(addr));
+ }
+
+ if (alcf->rules6) {
+ return ngx_http_access_inet6(r, alcf, p);
+ }
+
+ break;
+
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+ case AF_UNIX:
+ if (alcf->rules_un) {
+ return ngx_http_access_unix(r, alcf);
+ }
+
+ break;
+
+#endif
+ }
+
+ return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_access_inet(ngx_http_request_t *r, ngx_http_access_loc_conf_t *alcf,
+ in_addr_t addr)
+{
+ ngx_uint_t i;
+ ngx_http_access_rule_t *rule;
+
+ rule = alcf->rules->elts;
+ for (i = 0; i < alcf->rules->nelts; i++) {
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "access: %08XD %08XD %08XD",
+ addr, rule[i].mask, rule[i].addr);
+
+ if ((addr & rule[i].mask) == rule[i].addr) {
+ return ngx_http_access_found(r, rule[i].deny);
+ }
+ }
+
+ return NGX_DECLINED;
+}
+
+
+#if (NGX_HAVE_INET6)
+
+static ngx_int_t
+ngx_http_access_inet6(ngx_http_request_t *r, ngx_http_access_loc_conf_t *alcf,
+ u_char *p)
+{
+ ngx_uint_t n;
+ ngx_uint_t i;
+ ngx_http_access_rule6_t *rule6;
+
+ rule6 = alcf->rules6->elts;
+ for (i = 0; i < alcf->rules6->nelts; i++) {
+
+#if (NGX_DEBUG)
+ {
+ size_t cl, ml, al;
+ u_char ct[NGX_INET6_ADDRSTRLEN];
+ u_char mt[NGX_INET6_ADDRSTRLEN];
+ u_char at[NGX_INET6_ADDRSTRLEN];
+
+ cl = ngx_inet6_ntop(p, ct, NGX_INET6_ADDRSTRLEN);
+ ml = ngx_inet6_ntop(rule6[i].mask.s6_addr, mt, NGX_INET6_ADDRSTRLEN);
+ al = ngx_inet6_ntop(rule6[i].addr.s6_addr, at, NGX_INET6_ADDRSTRLEN);
+
+ ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "access: %*s %*s %*s", cl, ct, ml, mt, al, at);
+ }
+#endif
+
+ for (n = 0; n < 16; n++) {
+ if ((p[n] & rule6[i].mask.s6_addr[n]) != rule6[i].addr.s6_addr[n]) {
+ goto next;
+ }
+ }
+
+ return ngx_http_access_found(r, rule6[i].deny);
+
+ next:
+ continue;
+ }
+
+ return NGX_DECLINED;
+}
+
+#endif
+
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+static ngx_int_t
+ngx_http_access_unix(ngx_http_request_t *r, ngx_http_access_loc_conf_t *alcf)
+{
+ ngx_uint_t i;
+ ngx_http_access_rule_un_t *rule_un;
+
+ rule_un = alcf->rules_un->elts;
+ for (i = 0; i < alcf->rules_un->nelts; i++) {
+
+ /* TODO: check path */
+ if (1) {
+ return ngx_http_access_found(r, rule_un[i].deny);
+ }
+ }
+
+ return NGX_DECLINED;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_access_found(ngx_http_request_t *r, ngx_uint_t deny)
+{
+ ngx_http_core_loc_conf_t *clcf;
+
+ if (deny) {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->satisfy == NGX_HTTP_SATISFY_ALL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "access forbidden by rule");
+ }
+
+ return NGX_HTTP_FORBIDDEN;
+ }
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_http_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_access_loc_conf_t *alcf = conf;
+
+ ngx_int_t rc;
+ ngx_uint_t all;
+ ngx_str_t *value;
+ ngx_cidr_t cidr;
+ ngx_http_access_rule_t *rule;
+#if (NGX_HAVE_INET6)
+ ngx_http_access_rule6_t *rule6;
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+ ngx_http_access_rule_un_t *rule_un;
+#endif
+
+ ngx_memzero(&cidr, sizeof(ngx_cidr_t));
+
+ value = cf->args->elts;
+
+ all = (value[1].len == 3 && ngx_strcmp(value[1].data, "all") == 0);
+
+ if (!all) {
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+ if (value[1].len == 5 && ngx_strcmp(value[1].data, "unix:") == 0) {
+ cidr.family = AF_UNIX;
+ rc = NGX_OK;
+
+ } else {
+ rc = ngx_ptocidr(&value[1], &cidr);
+ }
+
+#else
+ rc = ngx_ptocidr(&value[1], &cidr);
+#endif
+
+ if (rc == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "low address bits of %V are meaningless", &value[1]);
+ }
+ }
+
+ if (cidr.family == AF_INET || all) {
+
+ if (alcf->rules == NULL) {
+ alcf->rules = ngx_array_create(cf->pool, 4,
+ sizeof(ngx_http_access_rule_t));
+ if (alcf->rules == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ rule = ngx_array_push(alcf->rules);
+ if (rule == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ rule->mask = cidr.u.in.mask;
+ rule->addr = cidr.u.in.addr;
+ rule->deny = (value[0].data[0] == 'd') ? 1 : 0;
+ }
+
+#if (NGX_HAVE_INET6)
+ if (cidr.family == AF_INET6 || all) {
+
+ if (alcf->rules6 == NULL) {
+ alcf->rules6 = ngx_array_create(cf->pool, 4,
+ sizeof(ngx_http_access_rule6_t));
+ if (alcf->rules6 == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ rule6 = ngx_array_push(alcf->rules6);
+ if (rule6 == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ rule6->mask = cidr.u.in6.mask;
+ rule6->addr = cidr.u.in6.addr;
+ rule6->deny = (value[0].data[0] == 'd') ? 1 : 0;
+ }
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+ if (cidr.family == AF_UNIX || all) {
+
+ if (alcf->rules_un == NULL) {
+ alcf->rules_un = ngx_array_create(cf->pool, 1,
+ sizeof(ngx_http_access_rule_un_t));
+ if (alcf->rules_un == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ rule_un = ngx_array_push(alcf->rules_un);
+ if (rule_un == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ rule_un->deny = (value[0].data[0] == 'd') ? 1 : 0;
+ }
+#endif
+
+ return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_access_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_access_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_access_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ return conf;
+}
+
+
+static char *
+ngx_http_access_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_access_loc_conf_t *prev = parent;
+ ngx_http_access_loc_conf_t *conf = child;
+
+ if (conf->rules == NULL
+#if (NGX_HAVE_INET6)
+ && conf->rules6 == NULL
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+ && conf->rules_un == NULL
+#endif
+ ) {
+ conf->rules = prev->rules;
+#if (NGX_HAVE_INET6)
+ conf->rules6 = prev->rules6;
+#endif
+#if (NGX_HAVE_UNIX_DOMAIN)
+ conf->rules_un = prev->rules_un;
+#endif
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_access_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_access_handler;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_addition_filter_module.c b/app/nginx/src/http/modules/ngx_http_addition_filter_module.c
new file mode 100644
index 0000000..2fad0e5
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_addition_filter_module.c
@@ -0,0 +1,252 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_str_t before_body;
+ ngx_str_t after_body;
+
+ ngx_hash_t types;
+ ngx_array_t *types_keys;
+} ngx_http_addition_conf_t;
+
+
+typedef struct {
+ ngx_uint_t before_body_sent;
+} ngx_http_addition_ctx_t;
+
+
+static void *ngx_http_addition_create_conf(ngx_conf_t *cf);
+static char *ngx_http_addition_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static ngx_int_t ngx_http_addition_filter_init(ngx_conf_t *cf);
+
+
+static ngx_command_t ngx_http_addition_commands[] = {
+
+ { ngx_string("add_before_body"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_addition_conf_t, before_body),
+ NULL },
+
+ { ngx_string("add_after_body"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_addition_conf_t, after_body),
+ NULL },
+
+ { ngx_string("addition_types"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_types_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_addition_conf_t, types_keys),
+ &ngx_http_html_default_types[0] },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_addition_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_addition_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_addition_create_conf, /* create location configuration */
+ ngx_http_addition_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_addition_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_addition_filter_module_ctx, /* module context */
+ ngx_http_addition_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_addition_header_filter(ngx_http_request_t *r)
+{
+ ngx_http_addition_ctx_t *ctx;
+ ngx_http_addition_conf_t *conf;
+
+ if (r->headers_out.status != NGX_HTTP_OK || r != r->main) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_addition_filter_module);
+
+ if (conf->before_body.len == 0 && conf->after_body.len == 0) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ if (ngx_http_test_content_type(r, &conf->types) == NULL) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_addition_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_addition_filter_module);
+
+ ngx_http_clear_content_length(r);
+ ngx_http_clear_accept_ranges(r);
+ ngx_http_weak_etag(r);
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_addition_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ ngx_int_t rc;
+ ngx_uint_t last;
+ ngx_chain_t *cl;
+ ngx_http_request_t *sr;
+ ngx_http_addition_ctx_t *ctx;
+ ngx_http_addition_conf_t *conf;
+
+ if (in == NULL || r->header_only) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_addition_filter_module);
+
+ if (ctx == NULL) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_addition_filter_module);
+
+ if (!ctx->before_body_sent) {
+ ctx->before_body_sent = 1;
+
+ if (conf->before_body.len) {
+ if (ngx_http_subrequest(r, &conf->before_body, NULL, &sr, NULL, 0)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+ }
+ }
+
+ if (conf->after_body.len == 0) {
+ ngx_http_set_ctx(r, NULL, ngx_http_addition_filter_module);
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ last = 0;
+
+ for (cl = in; cl; cl = cl->next) {
+ if (cl->buf->last_buf) {
+ cl->buf->last_buf = 0;
+ cl->buf->last_in_chain = 1;
+ cl->buf->sync = 1;
+ last = 1;
+ }
+ }
+
+ rc = ngx_http_next_body_filter(r, in);
+
+ if (rc == NGX_ERROR || !last || conf->after_body.len == 0) {
+ return rc;
+ }
+
+ if (ngx_http_subrequest(r, &conf->after_body, NULL, &sr, NULL, 0)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, NULL, ngx_http_addition_filter_module);
+
+ return ngx_http_send_special(r, NGX_HTTP_LAST);
+}
+
+
+static ngx_int_t
+ngx_http_addition_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_addition_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_addition_body_filter;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_addition_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_addition_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_addition_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->before_body = { 0, NULL };
+ * conf->after_body = { 0, NULL };
+ * conf->types = { NULL };
+ * conf->types_keys = NULL;
+ */
+
+ return conf;
+}
+
+
+static char *
+ngx_http_addition_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_addition_conf_t *prev = parent;
+ ngx_http_addition_conf_t *conf = child;
+
+ ngx_conf_merge_str_value(conf->before_body, prev->before_body, "");
+ ngx_conf_merge_str_value(conf->after_body, prev->after_body, "");
+
+ if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
+ &prev->types_keys, &prev->types,
+ ngx_http_html_default_types)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_auth_basic_module.c b/app/nginx/src/http/modules/ngx_http_auth_basic_module.c
new file mode 100644
index 0000000..1e7a0c2
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_auth_basic_module.c
@@ -0,0 +1,467 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_crypt.h>
+
+
+#define NGX_HTTP_AUTH_BUF_SIZE 2048
+
+
+typedef struct {
+ ngx_str_t passwd;
+} ngx_http_auth_basic_ctx_t;
+
+
+typedef struct {
+ ngx_http_complex_value_t *realm;
+ ngx_http_complex_value_t user_file;
+} ngx_http_auth_basic_loc_conf_t;
+
+
+static ngx_int_t ngx_http_auth_basic_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_auth_basic_crypt_handler(ngx_http_request_t *r,
+ ngx_http_auth_basic_ctx_t *ctx, ngx_str_t *passwd, ngx_str_t *realm);
+static ngx_int_t ngx_http_auth_basic_set_realm(ngx_http_request_t *r,
+ ngx_str_t *realm);
+static void ngx_http_auth_basic_close(ngx_file_t *file);
+static void *ngx_http_auth_basic_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_auth_basic_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_auth_basic_init(ngx_conf_t *cf);
+static char *ngx_http_auth_basic_user_file(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_command_t ngx_http_auth_basic_commands[] = {
+
+ { ngx_string("auth_basic"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF
+ |NGX_CONF_TAKE1,
+ ngx_http_set_complex_value_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_auth_basic_loc_conf_t, realm),
+ NULL },
+
+ { ngx_string("auth_basic_user_file"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF
+ |NGX_CONF_TAKE1,
+ ngx_http_auth_basic_user_file,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_auth_basic_loc_conf_t, user_file),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_auth_basic_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_auth_basic_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_auth_basic_create_loc_conf, /* create location configuration */
+ ngx_http_auth_basic_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_auth_basic_module = {
+ NGX_MODULE_V1,
+ &ngx_http_auth_basic_module_ctx, /* module context */
+ ngx_http_auth_basic_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_auth_basic_handler(ngx_http_request_t *r)
+{
+ off_t offset;
+ ssize_t n;
+ ngx_fd_t fd;
+ ngx_int_t rc;
+ ngx_err_t err;
+ ngx_str_t pwd, realm, user_file;
+ ngx_uint_t i, level, login, left, passwd;
+ ngx_file_t file;
+ ngx_http_auth_basic_ctx_t *ctx;
+ ngx_http_auth_basic_loc_conf_t *alcf;
+ u_char buf[NGX_HTTP_AUTH_BUF_SIZE];
+ enum {
+ sw_login,
+ sw_passwd,
+ sw_skip
+ } state;
+
+ alcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_basic_module);
+
+ if (alcf->realm == NULL || alcf->user_file.value.data == NULL) {
+ return NGX_DECLINED;
+ }
+
+ if (ngx_http_complex_value(r, alcf->realm, &realm) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (realm.len == 3 && ngx_strncmp(realm.data, "off", 3) == 0) {
+ return NGX_DECLINED;
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_auth_basic_module);
+
+ if (ctx) {
+ return ngx_http_auth_basic_crypt_handler(r, ctx, &ctx->passwd,
+ &realm);
+ }
+
+ rc = ngx_http_auth_basic_user(r);
+
+ if (rc == NGX_DECLINED) {
+
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "no user/password was provided for basic authentication");
+
+ return ngx_http_auth_basic_set_realm(r, &realm);
+ }
+
+ if (rc == NGX_ERROR) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_http_complex_value(r, &alcf->user_file, &user_file) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ fd = ngx_open_file(user_file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
+
+ if (fd == NGX_INVALID_FILE) {
+ err = ngx_errno;
+
+ if (err == NGX_ENOENT) {
+ level = NGX_LOG_ERR;
+ rc = NGX_HTTP_FORBIDDEN;
+
+ } else {
+ level = NGX_LOG_CRIT;
+ rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_log_error(level, r->connection->log, err,
+ ngx_open_file_n " \"%s\" failed", user_file.data);
+
+ return rc;
+ }
+
+ ngx_memzero(&file, sizeof(ngx_file_t));
+
+ file.fd = fd;
+ file.name = user_file;
+ file.log = r->connection->log;
+
+ state = sw_login;
+ passwd = 0;
+ login = 0;
+ left = 0;
+ offset = 0;
+
+ for ( ;; ) {
+ i = left;
+
+ n = ngx_read_file(&file, buf + left, NGX_HTTP_AUTH_BUF_SIZE - left,
+ offset);
+
+ if (n == NGX_ERROR) {
+ ngx_http_auth_basic_close(&file);
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (n == 0) {
+ break;
+ }
+
+ for (i = left; i < left + n; i++) {
+ switch (state) {
+
+ case sw_login:
+ if (login == 0) {
+
+ if (buf[i] == '#' || buf[i] == CR) {
+ state = sw_skip;
+ break;
+ }
+
+ if (buf[i] == LF) {
+ break;
+ }
+ }
+
+ if (buf[i] != r->headers_in.user.data[login]) {
+ state = sw_skip;
+ break;
+ }
+
+ if (login == r->headers_in.user.len) {
+ state = sw_passwd;
+ passwd = i + 1;
+ }
+
+ login++;
+
+ break;
+
+ case sw_passwd:
+ if (buf[i] == LF || buf[i] == CR || buf[i] == ':') {
+ buf[i] = '\0';
+
+ ngx_http_auth_basic_close(&file);
+
+ pwd.len = i - passwd;
+ pwd.data = &buf[passwd];
+
+ return ngx_http_auth_basic_crypt_handler(r, NULL, &pwd,
+ &realm);
+ }
+
+ break;
+
+ case sw_skip:
+ if (buf[i] == LF) {
+ state = sw_login;
+ login = 0;
+ }
+
+ break;
+ }
+ }
+
+ if (state == sw_passwd) {
+ left = left + n - passwd;
+ ngx_memmove(buf, &buf[passwd], left);
+ passwd = 0;
+
+ } else {
+ left = 0;
+ }
+
+ offset += n;
+ }
+
+ ngx_http_auth_basic_close(&file);
+
+ if (state == sw_passwd) {
+ pwd.len = i - passwd;
+ pwd.data = ngx_pnalloc(r->pool, pwd.len + 1);
+ if (pwd.data == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_cpystrn(pwd.data, &buf[passwd], pwd.len + 1);
+
+ return ngx_http_auth_basic_crypt_handler(r, NULL, &pwd, &realm);
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "user \"%V\" was not found in \"%V\"",
+ &r->headers_in.user, &user_file);
+
+ return ngx_http_auth_basic_set_realm(r, &realm);
+}
+
+
+static ngx_int_t
+ngx_http_auth_basic_crypt_handler(ngx_http_request_t *r,
+ ngx_http_auth_basic_ctx_t *ctx, ngx_str_t *passwd, ngx_str_t *realm)
+{
+ ngx_int_t rc;
+ u_char *encrypted;
+
+ rc = ngx_crypt(r->pool, r->headers_in.passwd.data, passwd->data,
+ &encrypted);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "rc: %i user: \"%V\" salt: \"%s\"",
+ rc, &r->headers_in.user, passwd->data);
+
+ if (rc == NGX_OK) {
+ if (ngx_strcmp(encrypted, passwd->data) == 0) {
+ return NGX_OK;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "encrypted: \"%s\"", encrypted);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "user \"%V\": password mismatch",
+ &r->headers_in.user);
+
+ return ngx_http_auth_basic_set_realm(r, realm);
+ }
+
+ if (rc == NGX_ERROR) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* rc == NGX_AGAIN */
+
+ if (ctx == NULL) {
+ ctx = ngx_palloc(r->pool, sizeof(ngx_http_auth_basic_ctx_t));
+ if (ctx == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_auth_basic_module);
+
+ ctx->passwd.len = passwd->len;
+ passwd->len++;
+
+ ctx->passwd.data = ngx_pstrdup(r->pool, passwd);
+ if (ctx->passwd.data == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ }
+
+ /* TODO: add mutex event */
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_auth_basic_set_realm(ngx_http_request_t *r, ngx_str_t *realm)
+{
+ size_t len;
+ u_char *basic, *p;
+
+ r->headers_out.www_authenticate = ngx_list_push(&r->headers_out.headers);
+ if (r->headers_out.www_authenticate == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ len = sizeof("Basic realm=\"\"") - 1 + realm->len;
+
+ basic = ngx_pnalloc(r->pool, len);
+ if (basic == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ p = ngx_cpymem(basic, "Basic realm=\"", sizeof("Basic realm=\"") - 1);
+ p = ngx_cpymem(p, realm->data, realm->len);
+ *p = '"';
+
+ r->headers_out.www_authenticate->hash = 1;
+ ngx_str_set(&r->headers_out.www_authenticate->key, "WWW-Authenticate");
+ r->headers_out.www_authenticate->value.data = basic;
+ r->headers_out.www_authenticate->value.len = len;
+
+ return NGX_HTTP_UNAUTHORIZED;
+}
+
+static void
+ngx_http_auth_basic_close(ngx_file_t *file)
+{
+ if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, file->log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", file->name.data);
+ }
+}
+
+
+static void *
+ngx_http_auth_basic_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_auth_basic_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_basic_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ return conf;
+}
+
+
+static char *
+ngx_http_auth_basic_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_auth_basic_loc_conf_t *prev = parent;
+ ngx_http_auth_basic_loc_conf_t *conf = child;
+
+ if (conf->realm == NULL) {
+ conf->realm = prev->realm;
+ }
+
+ if (conf->user_file.value.data == NULL) {
+ conf->user_file = prev->user_file;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_auth_basic_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_auth_basic_handler;
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_http_auth_basic_user_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_auth_basic_loc_conf_t *alcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_compile_complex_value_t ccv;
+
+ if (alcf->user_file.value.data) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &alcf->user_file;
+ ccv.zero = 1;
+ ccv.conf_prefix = 1;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_auth_request_module.c b/app/nginx/src/http/modules/ngx_http_auth_request_module.c
new file mode 100644
index 0000000..bab79e4
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_auth_request_module.c
@@ -0,0 +1,444 @@
+
+/*
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_str_t uri;
+ ngx_array_t *vars;
+} ngx_http_auth_request_conf_t;
+
+
+typedef struct {
+ ngx_uint_t done;
+ ngx_uint_t status;
+ ngx_http_request_t *subrequest;
+} ngx_http_auth_request_ctx_t;
+
+
+typedef struct {
+ ngx_int_t index;
+ ngx_http_complex_value_t value;
+ ngx_http_set_variable_pt set_handler;
+} ngx_http_auth_request_variable_t;
+
+
+static ngx_int_t ngx_http_auth_request_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_auth_request_done(ngx_http_request_t *r,
+ void *data, ngx_int_t rc);
+static ngx_int_t ngx_http_auth_request_set_variables(ngx_http_request_t *r,
+ ngx_http_auth_request_conf_t *arcf, ngx_http_auth_request_ctx_t *ctx);
+static ngx_int_t ngx_http_auth_request_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static void *ngx_http_auth_request_create_conf(ngx_conf_t *cf);
+static char *ngx_http_auth_request_merge_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_auth_request_init(ngx_conf_t *cf);
+static char *ngx_http_auth_request(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_auth_request_set(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_command_t ngx_http_auth_request_commands[] = {
+
+ { ngx_string("auth_request"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_auth_request,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("auth_request_set"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_http_auth_request_set,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_auth_request_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_auth_request_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_auth_request_create_conf, /* create location configuration */
+ ngx_http_auth_request_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_auth_request_module = {
+ NGX_MODULE_V1,
+ &ngx_http_auth_request_module_ctx, /* module context */
+ ngx_http_auth_request_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_auth_request_handler(ngx_http_request_t *r)
+{
+ ngx_table_elt_t *h, *ho;
+ ngx_http_request_t *sr;
+ ngx_http_post_subrequest_t *ps;
+ ngx_http_auth_request_ctx_t *ctx;
+ ngx_http_auth_request_conf_t *arcf;
+
+ arcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_request_module);
+
+ if (arcf->uri.len == 0) {
+ return NGX_DECLINED;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "auth request handler");
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_auth_request_module);
+
+ if (ctx != NULL) {
+ if (!ctx->done) {
+ return NGX_AGAIN;
+ }
+
+ /*
+ * as soon as we are done - explicitly set variables to make
+ * sure they will be available after internal redirects
+ */
+
+ if (ngx_http_auth_request_set_variables(r, arcf, ctx) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ /* return appropriate status */
+
+ if (ctx->status == NGX_HTTP_FORBIDDEN) {
+ return ctx->status;
+ }
+
+ if (ctx->status == NGX_HTTP_UNAUTHORIZED) {
+ sr = ctx->subrequest;
+
+ h = sr->headers_out.www_authenticate;
+
+ if (!h && sr->upstream) {
+ h = sr->upstream->headers_in.www_authenticate;
+ }
+
+ if (h) {
+ ho = ngx_list_push(&r->headers_out.headers);
+ if (ho == NULL) {
+ return NGX_ERROR;
+ }
+
+ *ho = *h;
+
+ r->headers_out.www_authenticate = ho;
+ }
+
+ return ctx->status;
+ }
+
+ if (ctx->status >= NGX_HTTP_OK
+ && ctx->status < NGX_HTTP_SPECIAL_RESPONSE)
+ {
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "auth request unexpected status: %ui", ctx->status);
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_auth_request_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
+ if (ps == NULL) {
+ return NGX_ERROR;
+ }
+
+ ps->handler = ngx_http_auth_request_done;
+ ps->data = ctx;
+
+ if (ngx_http_subrequest(r, &arcf->uri, NULL, &sr, ps,
+ NGX_HTTP_SUBREQUEST_WAITED)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ /*
+ * allocate fake request body to avoid attempts to read it and to make
+ * sure real body file (if already read) won't be closed by upstream
+ */
+
+ sr->request_body = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
+ if (sr->request_body == NULL) {
+ return NGX_ERROR;
+ }
+
+ sr->header_only = 1;
+
+ ctx->subrequest = sr;
+
+ ngx_http_set_ctx(r, ctx, ngx_http_auth_request_module);
+
+ return NGX_AGAIN;
+}
+
+
+static ngx_int_t
+ngx_http_auth_request_done(ngx_http_request_t *r, void *data, ngx_int_t rc)
+{
+ ngx_http_auth_request_ctx_t *ctx = data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "auth request done s:%ui", r->headers_out.status);
+
+ ctx->done = 1;
+ ctx->status = r->headers_out.status;
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_auth_request_set_variables(ngx_http_request_t *r,
+ ngx_http_auth_request_conf_t *arcf, ngx_http_auth_request_ctx_t *ctx)
+{
+ ngx_str_t val;
+ ngx_http_variable_t *v;
+ ngx_http_variable_value_t *vv;
+ ngx_http_auth_request_variable_t *av, *last;
+ ngx_http_core_main_conf_t *cmcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "auth request set variables");
+
+ if (arcf->vars == NULL) {
+ return NGX_OK;
+ }
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+ v = cmcf->variables.elts;
+
+ av = arcf->vars->elts;
+ last = av + arcf->vars->nelts;
+
+ while (av < last) {
+ /*
+ * explicitly set new value to make sure it will be available after
+ * internal redirects
+ */
+
+ vv = &r->variables[av->index];
+
+ if (ngx_http_complex_value(ctx->subrequest, &av->value, &val)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ vv->valid = 1;
+ vv->not_found = 0;
+ vv->data = val.data;
+ vv->len = val.len;
+
+ if (av->set_handler) {
+ /*
+ * set_handler only available in cmcf->variables_keys, so we store
+ * it explicitly
+ */
+
+ av->set_handler(r, vv, v[av->index].data);
+ }
+
+ av++;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_auth_request_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "auth request variable");
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_auth_request_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_auth_request_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_request_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->uri = { 0, NULL };
+ */
+
+ conf->vars = NGX_CONF_UNSET_PTR;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_auth_request_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_auth_request_conf_t *prev = parent;
+ ngx_http_auth_request_conf_t *conf = child;
+
+ ngx_conf_merge_str_value(conf->uri, prev->uri, "");
+ ngx_conf_merge_ptr_value(conf->vars, prev->vars, NULL);
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_auth_request_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_auth_request_handler;
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_http_auth_request(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_auth_request_conf_t *arcf = conf;
+
+ ngx_str_t *value;
+
+ if (arcf->uri.data != NULL) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ arcf->uri.len = 0;
+ arcf->uri.data = (u_char *) "";
+
+ return NGX_CONF_OK;
+ }
+
+ arcf->uri = value[1];
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_auth_request_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_auth_request_conf_t *arcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_variable_t *v;
+ ngx_http_auth_request_variable_t *av;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ if (value[1].data[0] != '$') {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid variable name \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ value[1].len--;
+ value[1].data++;
+
+ if (arcf->vars == NGX_CONF_UNSET_PTR) {
+ arcf->vars = ngx_array_create(cf->pool, 1,
+ sizeof(ngx_http_auth_request_variable_t));
+ if (arcf->vars == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ av = ngx_array_push(arcf->vars);
+ if (av == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE);
+ if (v == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ av->index = ngx_http_get_variable_index(cf, &value[1]);
+ if (av->index == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (v->get_handler == NULL) {
+ v->get_handler = ngx_http_auth_request_variable;
+ v->data = (uintptr_t) av;
+ }
+
+ av->set_handler = v->set_handler;
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[2];
+ ccv.complex_value = &av->value;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_autoindex_module.c b/app/nginx/src/http/modules/ngx_http_autoindex_module.c
new file mode 100644
index 0000000..b3bf652
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_autoindex_module.c
@@ -0,0 +1,1050 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#if 0
+
+typedef struct {
+ ngx_buf_t *buf;
+ size_t size;
+ ngx_pool_t *pool;
+ size_t alloc_size;
+ ngx_chain_t **last_out;
+} ngx_http_autoindex_ctx_t;
+
+#endif
+
+
+typedef struct {
+ ngx_str_t name;
+ size_t utf_len;
+ size_t escape;
+ size_t escape_html;
+
+ unsigned dir:1;
+ unsigned file:1;
+
+ time_t mtime;
+ off_t size;
+} ngx_http_autoindex_entry_t;
+
+
+typedef struct {
+ ngx_flag_t enable;
+ ngx_uint_t format;
+ ngx_flag_t localtime;
+ ngx_flag_t exact_size;
+} ngx_http_autoindex_loc_conf_t;
+
+
+#define NGX_HTTP_AUTOINDEX_HTML 0
+#define NGX_HTTP_AUTOINDEX_JSON 1
+#define NGX_HTTP_AUTOINDEX_JSONP 2
+#define NGX_HTTP_AUTOINDEX_XML 3
+
+#define NGX_HTTP_AUTOINDEX_PREALLOCATE 50
+
+#define NGX_HTTP_AUTOINDEX_NAME_LEN 50
+
+
+static ngx_buf_t *ngx_http_autoindex_html(ngx_http_request_t *r,
+ ngx_array_t *entries);
+static ngx_buf_t *ngx_http_autoindex_json(ngx_http_request_t *r,
+ ngx_array_t *entries, ngx_str_t *callback);
+static ngx_int_t ngx_http_autoindex_jsonp_callback(ngx_http_request_t *r,
+ ngx_str_t *callback);
+static ngx_buf_t *ngx_http_autoindex_xml(ngx_http_request_t *r,
+ ngx_array_t *entries);
+
+static int ngx_libc_cdecl ngx_http_autoindex_cmp_entries(const void *one,
+ const void *two);
+static ngx_int_t ngx_http_autoindex_error(ngx_http_request_t *r,
+ ngx_dir_t *dir, ngx_str_t *name);
+
+static ngx_int_t ngx_http_autoindex_init(ngx_conf_t *cf);
+static void *ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+
+
+static ngx_conf_enum_t ngx_http_autoindex_format[] = {
+ { ngx_string("html"), NGX_HTTP_AUTOINDEX_HTML },
+ { ngx_string("json"), NGX_HTTP_AUTOINDEX_JSON },
+ { ngx_string("jsonp"), NGX_HTTP_AUTOINDEX_JSONP },
+ { ngx_string("xml"), NGX_HTTP_AUTOINDEX_XML },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t ngx_http_autoindex_commands[] = {
+
+ { ngx_string("autoindex"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_autoindex_loc_conf_t, enable),
+ NULL },
+
+ { ngx_string("autoindex_format"),
+ 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_autoindex_loc_conf_t, format),
+ &ngx_http_autoindex_format },
+
+ { ngx_string("autoindex_localtime"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_autoindex_loc_conf_t, localtime),
+ NULL },
+
+ { ngx_string("autoindex_exact_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_autoindex_loc_conf_t, exact_size),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_autoindex_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_autoindex_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_autoindex_create_loc_conf, /* create location configuration */
+ ngx_http_autoindex_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_autoindex_module = {
+ NGX_MODULE_V1,
+ &ngx_http_autoindex_module_ctx, /* module context */
+ ngx_http_autoindex_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_autoindex_handler(ngx_http_request_t *r)
+{
+ u_char *last, *filename;
+ size_t len, allocated, root;
+ ngx_err_t err;
+ ngx_buf_t *b;
+ ngx_int_t rc;
+ ngx_str_t path, callback;
+ ngx_dir_t dir;
+ ngx_uint_t level, format;
+ ngx_pool_t *pool;
+ ngx_chain_t out;
+ ngx_array_t entries;
+ ngx_http_autoindex_entry_t *entry;
+ ngx_http_autoindex_loc_conf_t *alcf;
+
+ if (r->uri.data[r->uri.len - 1] != '/') {
+ return NGX_DECLINED;
+ }
+
+ if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
+ return NGX_DECLINED;
+ }
+
+ alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);
+
+ if (!alcf->enable) {
+ return NGX_DECLINED;
+ }
+
+ /* NGX_DIR_MASK_LEN is lesser than NGX_HTTP_AUTOINDEX_PREALLOCATE */
+
+ last = ngx_http_map_uri_to_path(r, &path, &root,
+ NGX_HTTP_AUTOINDEX_PREALLOCATE);
+ if (last == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ allocated = path.len;
+ path.len = last - path.data;
+ if (path.len > 1) {
+ path.len--;
+ }
+ path.data[path.len] = '\0';
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http autoindex: \"%s\"", path.data);
+
+ format = alcf->format;
+
+ if (format == NGX_HTTP_AUTOINDEX_JSONP) {
+ if (ngx_http_autoindex_jsonp_callback(r, &callback) != NGX_OK) {
+ return NGX_HTTP_BAD_REQUEST;
+ }
+
+ if (callback.len == 0) {
+ format = NGX_HTTP_AUTOINDEX_JSON;
+ }
+ }
+
+ if (ngx_open_dir(&path, &dir) == NGX_ERROR) {
+ err = ngx_errno;
+
+ if (err == NGX_ENOENT
+ || err == NGX_ENOTDIR
+ || err == NGX_ENAMETOOLONG)
+ {
+ level = NGX_LOG_ERR;
+ rc = NGX_HTTP_NOT_FOUND;
+
+ } else if (err == NGX_EACCES) {
+ level = NGX_LOG_ERR;
+ rc = NGX_HTTP_FORBIDDEN;
+
+ } else {
+ level = NGX_LOG_CRIT;
+ rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_log_error(level, r->connection->log, err,
+ ngx_open_dir_n " \"%s\" failed", path.data);
+
+ return rc;
+ }
+
+#if (NGX_SUPPRESS_WARN)
+
+ /* MSVC thinks 'entries' may be used without having been initialized */
+ ngx_memzero(&entries, sizeof(ngx_array_t));
+
+#endif
+
+ /* TODO: pool should be temporary pool */
+ pool = r->pool;
+
+ if (ngx_array_init(&entries, pool, 40, sizeof(ngx_http_autoindex_entry_t))
+ != NGX_OK)
+ {
+ return ngx_http_autoindex_error(r, &dir, &path);
+ }
+
+ r->headers_out.status = NGX_HTTP_OK;
+
+ switch (format) {
+
+ case NGX_HTTP_AUTOINDEX_JSON:
+ ngx_str_set(&r->headers_out.content_type, "application/json");
+ break;
+
+ case NGX_HTTP_AUTOINDEX_JSONP:
+ ngx_str_set(&r->headers_out.content_type, "application/javascript");
+ break;
+
+ case NGX_HTTP_AUTOINDEX_XML:
+ ngx_str_set(&r->headers_out.content_type, "text/xml");
+ ngx_str_set(&r->headers_out.charset, "utf-8");
+ break;
+
+ default: /* NGX_HTTP_AUTOINDEX_HTML */
+ ngx_str_set(&r->headers_out.content_type, "text/html");
+ break;
+ }
+
+ r->headers_out.content_type_len = r->headers_out.content_type.len;
+ r->headers_out.content_type_lowcase = NULL;
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ if (ngx_close_dir(&dir) == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
+ ngx_close_dir_n " \"%V\" failed", &path);
+ }
+
+ return rc;
+ }
+
+ filename = path.data;
+ filename[path.len] = '/';
+
+ for ( ;; ) {
+ ngx_set_errno(0);
+
+ if (ngx_read_dir(&dir) == NGX_ERROR) {
+ err = ngx_errno;
+
+ if (err != NGX_ENOMOREFILES) {
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
+ ngx_read_dir_n " \"%V\" failed", &path);
+ return ngx_http_autoindex_error(r, &dir, &path);
+ }
+
+ break;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http autoindex file: \"%s\"", ngx_de_name(&dir));
+
+ len = ngx_de_namelen(&dir);
+
+ if (ngx_de_name(&dir)[0] == '.') {
+ continue;
+ }
+
+ if (!dir.valid_info) {
+
+ /* 1 byte for '/' and 1 byte for terminating '\0' */
+
+ if (path.len + 1 + len + 1 > allocated) {
+ allocated = path.len + 1 + len + 1
+ + NGX_HTTP_AUTOINDEX_PREALLOCATE;
+
+ filename = ngx_pnalloc(pool, allocated);
+ if (filename == NULL) {
+ return ngx_http_autoindex_error(r, &dir, &path);
+ }
+
+ last = ngx_cpystrn(filename, path.data, path.len + 1);
+ *last++ = '/';
+ }
+
+ ngx_cpystrn(last, ngx_de_name(&dir), len + 1);
+
+ if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) {
+ err = ngx_errno;
+
+ if (err != NGX_ENOENT && err != NGX_ELOOP) {
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
+ ngx_de_info_n " \"%s\" failed", filename);
+
+ if (err == NGX_EACCES) {
+ continue;
+ }
+
+ return ngx_http_autoindex_error(r, &dir, &path);
+ }
+
+ if (ngx_de_link_info(filename, &dir) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+ ngx_de_link_info_n " \"%s\" failed",
+ filename);
+ return ngx_http_autoindex_error(r, &dir, &path);
+ }
+ }
+ }
+
+ entry = ngx_array_push(&entries);
+ if (entry == NULL) {
+ return ngx_http_autoindex_error(r, &dir, &path);
+ }
+
+ entry->name.len = len;
+
+ entry->name.data = ngx_pnalloc(pool, len + 1);
+ if (entry->name.data == NULL) {
+ return ngx_http_autoindex_error(r, &dir, &path);
+ }
+
+ ngx_cpystrn(entry->name.data, ngx_de_name(&dir), len + 1);
+
+ entry->dir = ngx_de_is_dir(&dir);
+ entry->file = ngx_de_is_file(&dir);
+ entry->mtime = ngx_de_mtime(&dir);
+ entry->size = ngx_de_size(&dir);
+ }
+
+ if (ngx_close_dir(&dir) == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
+ ngx_close_dir_n " \"%V\" failed", &path);
+ }
+
+ if (entries.nelts > 1) {
+ ngx_qsort(entries.elts, (size_t) entries.nelts,
+ sizeof(ngx_http_autoindex_entry_t),
+ ngx_http_autoindex_cmp_entries);
+ }
+
+ switch (format) {
+
+ case NGX_HTTP_AUTOINDEX_JSON:
+ b = ngx_http_autoindex_json(r, &entries, NULL);
+ break;
+
+ case NGX_HTTP_AUTOINDEX_JSONP:
+ b = ngx_http_autoindex_json(r, &entries, &callback);
+ break;
+
+ case NGX_HTTP_AUTOINDEX_XML:
+ b = ngx_http_autoindex_xml(r, &entries);
+ break;
+
+ default: /* NGX_HTTP_AUTOINDEX_HTML */
+ b = ngx_http_autoindex_html(r, &entries);
+ break;
+ }
+
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ /* TODO: free temporary pool */
+
+ if (r == r->main) {
+ b->last_buf = 1;
+ }
+
+ b->last_in_chain = 1;
+
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_output_filter(r, &out);
+}
+
+
+static ngx_buf_t *
+ngx_http_autoindex_html(ngx_http_request_t *r, ngx_array_t *entries)
+{
+ u_char *last, scale;
+ off_t length;
+ size_t len, char_len, escape_html;
+ ngx_tm_t tm;
+ ngx_buf_t *b;
+ ngx_int_t size;
+ ngx_uint_t i, utf8;
+ ngx_time_t *tp;
+ ngx_http_autoindex_entry_t *entry;
+ ngx_http_autoindex_loc_conf_t *alcf;
+
+ static u_char title[] =
+ "<html>" CRLF
+ "<head><title>Index of "
+ ;
+
+ static u_char header[] =
+ "</title></head>" CRLF
+ "<body bgcolor=\"white\">" CRLF
+ "<h1>Index of "
+ ;
+
+ static u_char tail[] =
+ "</body>" CRLF
+ "</html>" CRLF
+ ;
+
+ static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+ if (r->headers_out.charset.len == 5
+ && ngx_strncasecmp(r->headers_out.charset.data, (u_char *) "utf-8", 5)
+ == 0)
+ {
+ utf8 = 1;
+
+ } else {
+ utf8 = 0;
+ }
+
+ escape_html = ngx_escape_html(NULL, r->uri.data, r->uri.len);
+
+ len = sizeof(title) - 1
+ + r->uri.len + escape_html
+ + sizeof(header) - 1
+ + r->uri.len + escape_html
+ + sizeof("</h1>") - 1
+ + sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1
+ + sizeof("</pre><hr>") - 1
+ + sizeof(tail) - 1;
+
+ entry = entries->elts;
+ for (i = 0; i < entries->nelts; i++) {
+ entry[i].escape = 2 * ngx_escape_uri(NULL, entry[i].name.data,
+ entry[i].name.len,
+ NGX_ESCAPE_URI_COMPONENT);
+
+ entry[i].escape_html = ngx_escape_html(NULL, entry[i].name.data,
+ entry[i].name.len);
+
+ if (utf8) {
+ entry[i].utf_len = ngx_utf8_length(entry[i].name.data,
+ entry[i].name.len);
+ } else {
+ entry[i].utf_len = entry[i].name.len;
+ }
+
+ len += sizeof("<a href=\"") - 1
+ + entry[i].name.len + entry[i].escape
+ + 1 /* 1 is for "/" */
+ + sizeof("\">") - 1
+ + entry[i].name.len - entry[i].utf_len
+ + entry[i].escape_html
+ + NGX_HTTP_AUTOINDEX_NAME_LEN + sizeof("&gt;") - 2
+ + sizeof("</a>") - 1
+ + sizeof(" 28-Sep-1970 12:00 ") - 1
+ + 20 /* the file size */
+ + 2;
+ }
+
+ b = ngx_create_temp_buf(r->pool, len);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ b->last = ngx_cpymem(b->last, title, sizeof(title) - 1);
+
+ if (escape_html) {
+ b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
+ b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
+ b->last = (u_char *) ngx_escape_html(b->last, r->uri.data, r->uri.len);
+
+ } else {
+ b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
+ b->last = ngx_cpymem(b->last, header, sizeof(header) - 1);
+ b->last = ngx_cpymem(b->last, r->uri.data, r->uri.len);
+ }
+
+ b->last = ngx_cpymem(b->last, "</h1>", sizeof("</h1>") - 1);
+
+ b->last = ngx_cpymem(b->last, "<hr><pre><a href=\"../\">../</a>" CRLF,
+ sizeof("<hr><pre><a href=\"../\">../</a>" CRLF) - 1);
+
+ alcf = ngx_http_get_module_loc_conf(r, ngx_http_autoindex_module);
+ tp = ngx_timeofday();
+
+ for (i = 0; i < entries->nelts; i++) {
+ b->last = ngx_cpymem(b->last, "<a href=\"", sizeof("<a href=\"") - 1);
+
+ if (entry[i].escape) {
+ ngx_escape_uri(b->last, entry[i].name.data, entry[i].name.len,
+ NGX_ESCAPE_URI_COMPONENT);
+
+ b->last += entry[i].name.len + entry[i].escape;
+
+ } else {
+ b->last = ngx_cpymem(b->last, entry[i].name.data,
+ entry[i].name.len);
+ }
+
+ if (entry[i].dir) {
+ *b->last++ = '/';
+ }
+
+ *b->last++ = '"';
+ *b->last++ = '>';
+
+ len = entry[i].utf_len;
+
+ if (entry[i].name.len != len) {
+ if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
+ char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3 + 1;
+
+ } else {
+ char_len = NGX_HTTP_AUTOINDEX_NAME_LEN + 1;
+ }
+
+ last = b->last;
+ b->last = ngx_utf8_cpystrn(b->last, entry[i].name.data,
+ char_len, entry[i].name.len + 1);
+
+ if (entry[i].escape_html) {
+ b->last = (u_char *) ngx_escape_html(last, entry[i].name.data,
+ b->last - last);
+ }
+
+ last = b->last;
+
+ } else {
+ if (entry[i].escape_html) {
+ if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
+ char_len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3;
+
+ } else {
+ char_len = len;
+ }
+
+ b->last = (u_char *) ngx_escape_html(b->last,
+ entry[i].name.data, char_len);
+ last = b->last;
+
+ } else {
+ b->last = ngx_cpystrn(b->last, entry[i].name.data,
+ NGX_HTTP_AUTOINDEX_NAME_LEN + 1);
+ last = b->last - 3;
+ }
+ }
+
+ if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
+ b->last = ngx_cpymem(last, "..&gt;</a>", sizeof("..&gt;</a>") - 1);
+
+ } else {
+ if (entry[i].dir && NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
+ *b->last++ = '/';
+ len++;
+ }
+
+ b->last = ngx_cpymem(b->last, "</a>", sizeof("</a>") - 1);
+
+ if (NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
+ ngx_memset(b->last, ' ', NGX_HTTP_AUTOINDEX_NAME_LEN - len);
+ b->last += NGX_HTTP_AUTOINDEX_NAME_LEN - len;
+ }
+ }
+
+ *b->last++ = ' ';
+
+ ngx_gmtime(entry[i].mtime + tp->gmtoff * 60 * alcf->localtime, &tm);
+
+ b->last = ngx_sprintf(b->last, "%02d-%s-%d %02d:%02d ",
+ tm.ngx_tm_mday,
+ months[tm.ngx_tm_mon - 1],
+ tm.ngx_tm_year,
+ tm.ngx_tm_hour,
+ tm.ngx_tm_min);
+
+ if (alcf->exact_size) {
+ if (entry[i].dir) {
+ b->last = ngx_cpymem(b->last, " -",
+ sizeof(" -") - 1);
+ } else {
+ b->last = ngx_sprintf(b->last, "%19O", entry[i].size);
+ }
+
+ } else {
+ if (entry[i].dir) {
+ b->last = ngx_cpymem(b->last, " -",
+ sizeof(" -") - 1);
+
+ } else {
+ length = entry[i].size;
+
+ if (length > 1024 * 1024 * 1024 - 1) {
+ size = (ngx_int_t) (length / (1024 * 1024 * 1024));
+ if ((length % (1024 * 1024 * 1024))
+ > (1024 * 1024 * 1024 / 2 - 1))
+ {
+ size++;
+ }
+ scale = 'G';
+
+ } else if (length > 1024 * 1024 - 1) {
+ size = (ngx_int_t) (length / (1024 * 1024));
+ if ((length % (1024 * 1024)) > (1024 * 1024 / 2 - 1)) {
+ size++;
+ }
+ scale = 'M';
+
+ } else if (length > 9999) {
+ size = (ngx_int_t) (length / 1024);
+ if (length % 1024 > 511) {
+ size++;
+ }
+ scale = 'K';
+
+ } else {
+ size = (ngx_int_t) length;
+ scale = '\0';
+ }
+
+ if (scale) {
+ b->last = ngx_sprintf(b->last, "%6i%c", size, scale);
+
+ } else {
+ b->last = ngx_sprintf(b->last, " %6i", size);
+ }
+ }
+ }
+
+ *b->last++ = CR;
+ *b->last++ = LF;
+ }
+
+ b->last = ngx_cpymem(b->last, "</pre><hr>", sizeof("</pre><hr>") - 1);
+
+ b->last = ngx_cpymem(b->last, tail, sizeof(tail) - 1);
+
+ return b;
+}
+
+
+static ngx_buf_t *
+ngx_http_autoindex_json(ngx_http_request_t *r, ngx_array_t *entries,
+ ngx_str_t *callback)
+{
+ size_t len;
+ ngx_buf_t *b;
+ ngx_uint_t i;
+ ngx_http_autoindex_entry_t *entry;
+
+ len = sizeof("[" CRLF CRLF "]") - 1;
+
+ if (callback) {
+ len += sizeof("/* callback */" CRLF "();") - 1 + callback->len;
+ }
+
+ entry = entries->elts;
+
+ for (i = 0; i < entries->nelts; i++) {
+ entry[i].escape = ngx_escape_json(NULL, entry[i].name.data,
+ entry[i].name.len);
+
+ len += sizeof("{ }," CRLF) - 1
+ + sizeof("\"name\":\"\"") - 1
+ + entry[i].name.len + entry[i].escape
+ + sizeof(", \"type\":\"directory\"") - 1
+ + sizeof(", \"mtime\":\"Wed, 31 Dec 1986 10:00:00 GMT\"") - 1;
+
+ if (entry[i].file) {
+ len += sizeof(", \"size\":") - 1 + NGX_OFF_T_LEN;
+ }
+ }
+
+ b = ngx_create_temp_buf(r->pool, len);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ if (callback) {
+ b->last = ngx_cpymem(b->last, "/* callback */" CRLF,
+ sizeof("/* callback */" CRLF) - 1);
+
+ b->last = ngx_cpymem(b->last, callback->data, callback->len);
+
+ *b->last++ = '(';
+ }
+
+ *b->last++ = '[';
+
+ for (i = 0; i < entries->nelts; i++) {
+ b->last = ngx_cpymem(b->last, CRLF "{ \"name\":\"",
+ sizeof(CRLF "{ \"name\":\"") - 1);
+
+ if (entry[i].escape) {
+ b->last = (u_char *) ngx_escape_json(b->last, entry[i].name.data,
+ entry[i].name.len);
+ } else {
+ b->last = ngx_cpymem(b->last, entry[i].name.data,
+ entry[i].name.len);
+ }
+
+ b->last = ngx_cpymem(b->last, "\", \"type\":\"",
+ sizeof("\", \"type\":\"") - 1);
+
+ if (entry[i].dir) {
+ b->last = ngx_cpymem(b->last, "directory", sizeof("directory") - 1);
+
+ } else if (entry[i].file) {
+ b->last = ngx_cpymem(b->last, "file", sizeof("file") - 1);
+
+ } else {
+ b->last = ngx_cpymem(b->last, "other", sizeof("other") - 1);
+ }
+
+ b->last = ngx_cpymem(b->last, "\", \"mtime\":\"",
+ sizeof("\", \"mtime\":\"") - 1);
+
+ b->last = ngx_http_time(b->last, entry[i].mtime);
+
+ if (entry[i].file) {
+ b->last = ngx_cpymem(b->last, "\", \"size\":",
+ sizeof("\", \"size\":") - 1);
+ b->last = ngx_sprintf(b->last, "%O", entry[i].size);
+
+ } else {
+ *b->last++ = '"';
+ }
+
+ b->last = ngx_cpymem(b->last, " },", sizeof(" },") - 1);
+ }
+
+ if (i > 0) {
+ b->last--; /* strip last comma */
+ }
+
+ b->last = ngx_cpymem(b->last, CRLF "]", sizeof(CRLF "]") - 1);
+
+ if (callback) {
+ *b->last++ = ')'; *b->last++ = ';';
+ }
+
+ return b;
+}
+
+
+static ngx_int_t
+ngx_http_autoindex_jsonp_callback(ngx_http_request_t *r, ngx_str_t *callback)
+{
+ u_char *p, c, ch;
+ ngx_uint_t i;
+
+ if (ngx_http_arg(r, (u_char *) "callback", 8, callback) != NGX_OK) {
+ callback->len = 0;
+ return NGX_OK;
+ }
+
+ if (callback->len > 128) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent too long callback name: \"%V\"", callback);
+ return NGX_DECLINED;
+ }
+
+ p = callback->data;
+
+ for (i = 0; i < callback->len; i++) {
+ ch = p[i];
+
+ c = (u_char) (ch | 0x20);
+ if (c >= 'a' && c <= 'z') {
+ continue;
+ }
+
+ if ((ch >= '0' && ch <= '9') || ch == '_' || ch == '.') {
+ continue;
+ }
+
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent invalid callback name: \"%V\"", callback);
+
+ return NGX_DECLINED;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_buf_t *
+ngx_http_autoindex_xml(ngx_http_request_t *r, ngx_array_t *entries)
+{
+ size_t len;
+ ngx_tm_t tm;
+ ngx_buf_t *b;
+ ngx_str_t type;
+ ngx_uint_t i;
+ ngx_http_autoindex_entry_t *entry;
+
+ static u_char head[] = "<?xml version=\"1.0\"?>" CRLF "<list>" CRLF;
+ static u_char tail[] = "</list>" CRLF;
+
+ len = sizeof(head) - 1 + sizeof(tail) - 1;
+
+ entry = entries->elts;
+
+ for (i = 0; i < entries->nelts; i++) {
+ entry[i].escape = ngx_escape_html(NULL, entry[i].name.data,
+ entry[i].name.len);
+
+ len += sizeof("<directory></directory>" CRLF) - 1
+ + entry[i].name.len + entry[i].escape
+ + sizeof(" mtime=\"1986-12-31T10:00:00Z\"") - 1;
+
+ if (entry[i].file) {
+ len += sizeof(" size=\"\"") - 1 + NGX_OFF_T_LEN;
+ }
+ }
+
+ b = ngx_create_temp_buf(r->pool, len);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ b->last = ngx_cpymem(b->last, head, sizeof(head) - 1);
+
+ for (i = 0; i < entries->nelts; i++) {
+ *b->last++ = '<';
+
+ if (entry[i].dir) {
+ ngx_str_set(&type, "directory");
+
+ } else if (entry[i].file) {
+ ngx_str_set(&type, "file");
+
+ } else {
+ ngx_str_set(&type, "other");
+ }
+
+ b->last = ngx_cpymem(b->last, type.data, type.len);
+
+ b->last = ngx_cpymem(b->last, " mtime=\"", sizeof(" mtime=\"") - 1);
+
+ ngx_gmtime(entry[i].mtime, &tm);
+
+ b->last = ngx_sprintf(b->last, "%4d-%02d-%02dT%02d:%02d:%02dZ",
+ tm.ngx_tm_year, tm.ngx_tm_mon,
+ tm.ngx_tm_mday, tm.ngx_tm_hour,
+ tm.ngx_tm_min, tm.ngx_tm_sec);
+
+ if (entry[i].file) {
+ b->last = ngx_cpymem(b->last, "\" size=\"",
+ sizeof("\" size=\"") - 1);
+ b->last = ngx_sprintf(b->last, "%O", entry[i].size);
+ }
+
+ *b->last++ = '"'; *b->last++ = '>';
+
+ if (entry[i].escape) {
+ b->last = (u_char *) ngx_escape_html(b->last, entry[i].name.data,
+ entry[i].name.len);
+ } else {
+ b->last = ngx_cpymem(b->last, entry[i].name.data,
+ entry[i].name.len);
+ }
+
+ *b->last++ = '<'; *b->last++ = '/';
+
+ b->last = ngx_cpymem(b->last, type.data, type.len);
+
+ *b->last++ = '>';
+
+ *b->last++ = CR; *b->last++ = LF;
+ }
+
+ b->last = ngx_cpymem(b->last, tail, sizeof(tail) - 1);
+
+ return b;
+}
+
+
+static int ngx_libc_cdecl
+ngx_http_autoindex_cmp_entries(const void *one, const void *two)
+{
+ ngx_http_autoindex_entry_t *first = (ngx_http_autoindex_entry_t *) one;
+ ngx_http_autoindex_entry_t *second = (ngx_http_autoindex_entry_t *) two;
+
+ if (first->dir && !second->dir) {
+ /* move the directories to the start */
+ return -1;
+ }
+
+ if (!first->dir && second->dir) {
+ /* move the directories to the start */
+ return 1;
+ }
+
+ return (int) ngx_strcmp(first->name.data, second->name.data);
+}
+
+
+#if 0
+
+static ngx_buf_t *
+ngx_http_autoindex_alloc(ngx_http_autoindex_ctx_t *ctx, size_t size)
+{
+ ngx_chain_t *cl;
+
+ if (ctx->buf) {
+
+ if ((size_t) (ctx->buf->end - ctx->buf->last) >= size) {
+ return ctx->buf;
+ }
+
+ ctx->size += ctx->buf->last - ctx->buf->pos;
+ }
+
+ ctx->buf = ngx_create_temp_buf(ctx->pool, ctx->alloc_size);
+ if (ctx->buf == NULL) {
+ return NULL;
+ }
+
+ cl = ngx_alloc_chain_link(ctx->pool);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ cl->buf = ctx->buf;
+ cl->next = NULL;
+
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ return ctx->buf;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_autoindex_error(ngx_http_request_t *r, ngx_dir_t *dir, ngx_str_t *name)
+{
+ if (ngx_close_dir(dir) == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
+ ngx_close_dir_n " \"%V\" failed", name);
+ }
+
+ return r->header_sent ? NGX_ERROR : NGX_HTTP_INTERNAL_SERVER_ERROR;
+}
+
+
+static void *
+ngx_http_autoindex_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_autoindex_loc_conf_t *conf;
+
+ conf = ngx_palloc(cf->pool, sizeof(ngx_http_autoindex_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->enable = NGX_CONF_UNSET;
+ conf->format = NGX_CONF_UNSET_UINT;
+ conf->localtime = NGX_CONF_UNSET;
+ conf->exact_size = NGX_CONF_UNSET;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_autoindex_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_autoindex_loc_conf_t *prev = parent;
+ ngx_http_autoindex_loc_conf_t *conf = child;
+
+ ngx_conf_merge_value(conf->enable, prev->enable, 0);
+ ngx_conf_merge_uint_value(conf->format, prev->format,
+ NGX_HTTP_AUTOINDEX_HTML);
+ ngx_conf_merge_value(conf->localtime, prev->localtime, 0);
+ ngx_conf_merge_value(conf->exact_size, prev->exact_size, 1);
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_autoindex_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_autoindex_handler;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_browser_module.c b/app/nginx/src/http/modules/ngx_http_browser_module.c
new file mode 100644
index 0000000..80da0d8
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_browser_module.c
@@ -0,0 +1,715 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+/*
+ * The module can check browser versions conforming to the following formats:
+ * X, X.X, X.X.X, and X.X.X.X. The maximum values of each format may be
+ * 4000, 4000.99, 4000.99.99, and 4000.99.99.99.
+ */
+
+
+#define NGX_HTTP_MODERN_BROWSER 0
+#define NGX_HTTP_ANCIENT_BROWSER 1
+
+
+typedef struct {
+ u_char browser[12];
+ size_t skip;
+ size_t add;
+ u_char name[12];
+} ngx_http_modern_browser_mask_t;
+
+
+typedef struct {
+ ngx_uint_t version;
+ size_t skip;
+ size_t add;
+ u_char name[12];
+} ngx_http_modern_browser_t;
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_http_get_variable_pt handler;
+ uintptr_t data;
+} ngx_http_browser_variable_t;
+
+
+typedef struct {
+ ngx_array_t *modern_browsers;
+ ngx_array_t *ancient_browsers;
+ ngx_http_variable_value_t *modern_browser_value;
+ ngx_http_variable_value_t *ancient_browser_value;
+
+ unsigned modern_unlisted_browsers:1;
+ unsigned netscape4:1;
+} ngx_http_browser_conf_t;
+
+
+static ngx_int_t ngx_http_msie_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_browser_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+
+static ngx_uint_t ngx_http_browser(ngx_http_request_t *r,
+ ngx_http_browser_conf_t *cf);
+
+static ngx_int_t ngx_http_browser_add_variable(ngx_conf_t *cf);
+static void *ngx_http_browser_create_conf(ngx_conf_t *cf);
+static char *ngx_http_browser_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static int ngx_libc_cdecl ngx_http_modern_browser_sort(const void *one,
+ const void *two);
+static char *ngx_http_modern_browser(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_ancient_browser(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_modern_browser_value(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_ancient_browser_value(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_command_t ngx_http_browser_commands[] = {
+
+ { ngx_string("modern_browser"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+ ngx_http_modern_browser,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("ancient_browser"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_ancient_browser,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("modern_browser_value"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_modern_browser_value,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("ancient_browser_value"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_ancient_browser_value,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_browser_module_ctx = {
+ ngx_http_browser_add_variable, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_browser_create_conf, /* create location configuration */
+ ngx_http_browser_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_browser_module = {
+ NGX_MODULE_V1,
+ &ngx_http_browser_module_ctx, /* module context */
+ ngx_http_browser_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_modern_browser_mask_t ngx_http_modern_browser_masks[] = {
+
+ /* Opera must be the first browser to check */
+
+ /*
+ * "Opera/7.50 (X11; FreeBSD i386; U) [en]"
+ * "Mozilla/5.0 (X11; FreeBSD i386; U) Opera 7.50 [en]"
+ * "Mozilla/4.0 (compatible; MSIE 6.0; X11; FreeBSD i386) Opera 7.50 [en]"
+ * "Opera/8.0 (Windows NT 5.1; U; ru)"
+ * "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; en) Opera 8.0"
+ * "Opera/9.01 (X11; FreeBSD 6 i386; U; en)"
+ */
+
+ { "opera",
+ 0,
+ sizeof("Opera ") - 1,
+ "Opera"},
+
+ /* "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)" */
+
+ { "msie",
+ sizeof("Mozilla/4.0 (compatible; ") - 1,
+ sizeof("MSIE ") - 1,
+ "MSIE "},
+
+ /*
+ * "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.0.0) Gecko/20020610"
+ * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.5) Gecko/20031006"
+ * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.6) Gecko/20040206
+ * Firefox/0.8"
+ * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.7.8)
+ * Gecko/20050511 Firefox/1.0.4"
+ * "Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.8.0.5) Gecko/20060729
+ * Firefox/1.5.0.5"
+ */
+
+ { "gecko",
+ sizeof("Mozilla/5.0 (") - 1,
+ sizeof("rv:") - 1,
+ "rv:"},
+
+ /*
+ * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ru-ru) AppleWebKit/125.2
+ * (KHTML, like Gecko) Safari/125.7"
+ * "Mozilla/5.0 (SymbianOS/9.1; U; en-us) AppleWebKit/413
+ * (KHTML, like Gecko) Safari/413"
+ * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418
+ * (KHTML, like Gecko) Safari/417.9.3"
+ * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ru-ru) AppleWebKit/418.8
+ * (KHTML, like Gecko) Safari/419.3"
+ */
+
+ { "safari",
+ sizeof("Mozilla/5.0 (") - 1,
+ sizeof("Safari/") - 1,
+ "Safari/"},
+
+ /*
+ * "Mozilla/5.0 (compatible; Konqueror/3.1; Linux)"
+ * "Mozilla/5.0 (compatible; Konqueror/3.4; Linux) KHTML/3.4.2 (like Gecko)"
+ * "Mozilla/5.0 (compatible; Konqueror/3.5; FreeBSD) KHTML/3.5.1
+ * (like Gecko)"
+ */
+
+ { "konqueror",
+ sizeof("Mozilla/5.0 (compatible; ") - 1,
+ sizeof("Konqueror/") - 1,
+ "Konqueror/"},
+
+ { "", 0, 0, "" }
+
+};
+
+
+static ngx_http_browser_variable_t ngx_http_browsers[] = {
+ { ngx_string("msie"), ngx_http_msie_variable, 0 },
+ { ngx_string("modern_browser"), ngx_http_browser_variable,
+ NGX_HTTP_MODERN_BROWSER },
+ { ngx_string("ancient_browser"), ngx_http_browser_variable,
+ NGX_HTTP_ANCIENT_BROWSER },
+ { ngx_null_string, NULL, 0 }
+};
+
+
+static ngx_int_t
+ngx_http_browser_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_uint_t rc;
+ ngx_http_browser_conf_t *cf;
+
+ cf = ngx_http_get_module_loc_conf(r, ngx_http_browser_module);
+
+ rc = ngx_http_browser(r, cf);
+
+ if (data == NGX_HTTP_MODERN_BROWSER && rc == NGX_HTTP_MODERN_BROWSER) {
+ *v = *cf->modern_browser_value;
+ return NGX_OK;
+ }
+
+ if (data == NGX_HTTP_ANCIENT_BROWSER && rc == NGX_HTTP_ANCIENT_BROWSER) {
+ *v = *cf->ancient_browser_value;
+ return NGX_OK;
+ }
+
+ *v = ngx_http_variable_null_value;
+ return NGX_OK;
+}
+
+
+static ngx_uint_t
+ngx_http_browser(ngx_http_request_t *r, ngx_http_browser_conf_t *cf)
+{
+ size_t len;
+ u_char *name, *ua, *last, c;
+ ngx_str_t *ancient;
+ ngx_uint_t i, version, ver, scale;
+ ngx_http_modern_browser_t *modern;
+
+ if (r->headers_in.user_agent == NULL) {
+ if (cf->modern_unlisted_browsers) {
+ return NGX_HTTP_MODERN_BROWSER;
+ }
+
+ return NGX_HTTP_ANCIENT_BROWSER;
+ }
+
+ ua = r->headers_in.user_agent->value.data;
+ len = r->headers_in.user_agent->value.len;
+ last = ua + len;
+
+ if (cf->modern_browsers) {
+ modern = cf->modern_browsers->elts;
+
+ for (i = 0; i < cf->modern_browsers->nelts; i++) {
+ name = ua + modern[i].skip;
+
+ if (name >= last) {
+ continue;
+ }
+
+ name = (u_char *) ngx_strstr(name, modern[i].name);
+
+ if (name == NULL) {
+ continue;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "browser: \"%s\"", name);
+
+ name += modern[i].add;
+
+ if (name >= last) {
+ continue;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "version: \"%ui\" \"%s\"", modern[i].version, name);
+
+ version = 0;
+ ver = 0;
+ scale = 1000000;
+
+ while (name < last) {
+
+ c = *name++;
+
+ if (c >= '0' && c <= '9') {
+ ver = ver * 10 + (c - '0');
+ continue;
+ }
+
+ if (c == '.') {
+ version += ver * scale;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "version: \"%ui\" \"%ui\"",
+ modern[i].version, version);
+
+ if (version > modern[i].version) {
+ return NGX_HTTP_MODERN_BROWSER;
+ }
+
+ ver = 0;
+ scale /= 100;
+ continue;
+ }
+
+ break;
+ }
+
+ version += ver * scale;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "version: \"%ui\" \"%ui\"",
+ modern[i].version, version);
+
+ if (version >= modern[i].version) {
+ return NGX_HTTP_MODERN_BROWSER;
+ }
+
+ return NGX_HTTP_ANCIENT_BROWSER;
+ }
+
+ if (!cf->modern_unlisted_browsers) {
+ return NGX_HTTP_ANCIENT_BROWSER;
+ }
+ }
+
+ if (cf->netscape4) {
+ if (len > sizeof("Mozilla/4.72 ") - 1
+ && ngx_strncmp(ua, "Mozilla/", sizeof("Mozilla/") - 1) == 0
+ && ua[8] > '0' && ua[8] < '5')
+ {
+ return NGX_HTTP_ANCIENT_BROWSER;
+ }
+ }
+
+ if (cf->ancient_browsers) {
+ ancient = cf->ancient_browsers->elts;
+
+ for (i = 0; i < cf->ancient_browsers->nelts; i++) {
+ if (len >= ancient[i].len
+ && ngx_strstr(ua, ancient[i].data) != NULL)
+ {
+ return NGX_HTTP_ANCIENT_BROWSER;
+ }
+ }
+ }
+
+ if (cf->modern_unlisted_browsers) {
+ return NGX_HTTP_MODERN_BROWSER;
+ }
+
+ return NGX_HTTP_ANCIENT_BROWSER;
+}
+
+
+static ngx_int_t
+ngx_http_msie_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ if (r->headers_in.msie) {
+ *v = ngx_http_variable_true_value;
+ return NGX_OK;
+ }
+
+ *v = ngx_http_variable_null_value;
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_browser_add_variable(ngx_conf_t *cf)
+{
+ ngx_http_browser_variable_t *var;
+ ngx_http_variable_t *v;
+
+ for (var = ngx_http_browsers; var->name.len; var++) {
+
+ v = ngx_http_add_variable(cf, &var->name, NGX_HTTP_VAR_CHANGEABLE);
+ if (v == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->get_handler = var->handler;
+ v->data = var->data;
+ }
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_browser_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_browser_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_browser_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->modern_browsers = NULL;
+ * conf->ancient_browsers = NULL;
+ * conf->modern_browser_value = NULL;
+ * conf->ancient_browser_value = NULL;
+ *
+ * conf->modern_unlisted_browsers = 0;
+ * conf->netscape4 = 0;
+ */
+
+ return conf;
+}
+
+
+static char *
+ngx_http_browser_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_browser_conf_t *prev = parent;
+ ngx_http_browser_conf_t *conf = child;
+
+ ngx_uint_t i, n;
+ ngx_http_modern_browser_t *browsers, *opera;
+
+ /*
+ * At the merge the skip field is used to store the browser slot,
+ * it will be used in sorting and then will overwritten
+ * with a real skip value. The zero value means Opera.
+ */
+
+ if (conf->modern_browsers == NULL && conf->modern_unlisted_browsers == 0) {
+ conf->modern_browsers = prev->modern_browsers;
+ conf->modern_unlisted_browsers = prev->modern_unlisted_browsers;
+
+ } else if (conf->modern_browsers != NULL) {
+ browsers = conf->modern_browsers->elts;
+
+ for (i = 0; i < conf->modern_browsers->nelts; i++) {
+ if (browsers[i].skip == 0) {
+ goto found;
+ }
+ }
+
+ /*
+ * Opera may contain MSIE string, so if Opera was not enumerated
+ * as modern browsers, then add it and set a unreachable version
+ */
+
+ opera = ngx_array_push(conf->modern_browsers);
+ if (opera == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ opera->skip = 0;
+ opera->version = 4001000000U;
+
+ browsers = conf->modern_browsers->elts;
+
+found:
+
+ ngx_qsort(browsers, (size_t) conf->modern_browsers->nelts,
+ sizeof(ngx_http_modern_browser_t),
+ ngx_http_modern_browser_sort);
+
+ for (i = 0; i < conf->modern_browsers->nelts; i++) {
+ n = browsers[i].skip;
+
+ browsers[i].skip = ngx_http_modern_browser_masks[n].skip;
+ browsers[i].add = ngx_http_modern_browser_masks[n].add;
+ (void) ngx_cpystrn(browsers[i].name,
+ ngx_http_modern_browser_masks[n].name, 12);
+ }
+ }
+
+ if (conf->ancient_browsers == NULL && conf->netscape4 == 0) {
+ conf->ancient_browsers = prev->ancient_browsers;
+ conf->netscape4 = prev->netscape4;
+ }
+
+ if (conf->modern_browser_value == NULL) {
+ conf->modern_browser_value = prev->modern_browser_value;
+ }
+
+ if (conf->modern_browser_value == NULL) {
+ conf->modern_browser_value = &ngx_http_variable_true_value;
+ }
+
+ if (conf->ancient_browser_value == NULL) {
+ conf->ancient_browser_value = prev->ancient_browser_value;
+ }
+
+ if (conf->ancient_browser_value == NULL) {
+ conf->ancient_browser_value = &ngx_http_variable_true_value;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static int ngx_libc_cdecl
+ngx_http_modern_browser_sort(const void *one, const void *two)
+{
+ ngx_http_modern_browser_t *first = (ngx_http_modern_browser_t *) one;
+ ngx_http_modern_browser_t *second = (ngx_http_modern_browser_t *) two;
+
+ return (first->skip - second->skip);
+}
+
+
+static char *
+ngx_http_modern_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_browser_conf_t *bcf = conf;
+
+ u_char c;
+ ngx_str_t *value;
+ ngx_uint_t i, n, version, ver, scale;
+ ngx_http_modern_browser_t *browser;
+ ngx_http_modern_browser_mask_t *mask;
+
+ value = cf->args->elts;
+
+ if (cf->args->nelts == 2) {
+ if (ngx_strcmp(value[1].data, "unlisted") == 0) {
+ bcf->modern_unlisted_browsers = 1;
+ return NGX_CONF_OK;
+ }
+
+ return NGX_CONF_ERROR;
+ }
+
+ if (bcf->modern_browsers == NULL) {
+ bcf->modern_browsers = ngx_array_create(cf->pool, 5,
+ sizeof(ngx_http_modern_browser_t));
+ if (bcf->modern_browsers == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ browser = ngx_array_push(bcf->modern_browsers);
+ if (browser == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ mask = ngx_http_modern_browser_masks;
+
+ for (n = 0; mask[n].browser[0] != '\0'; n++) {
+ if (ngx_strcasecmp(mask[n].browser, value[1].data) == 0) {
+ goto found;
+ }
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "unknown browser name \"%V\"", &value[1]);
+
+ return NGX_CONF_ERROR;
+
+found:
+
+ /*
+ * at this stage the skip field is used to store the browser slot,
+ * it will be used in sorting in merge stage and then will overwritten
+ * with a real value
+ */
+
+ browser->skip = n;
+
+ version = 0;
+ ver = 0;
+ scale = 1000000;
+
+ for (i = 0; i < value[2].len; i++) {
+
+ c = value[2].data[i];
+
+ if (c >= '0' && c <= '9') {
+ ver = ver * 10 + (c - '0');
+ continue;
+ }
+
+ if (c == '.') {
+ version += ver * scale;
+ ver = 0;
+ scale /= 100;
+ continue;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid browser version \"%V\"", &value[2]);
+
+ return NGX_CONF_ERROR;
+ }
+
+ version += ver * scale;
+
+ browser->version = version;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_ancient_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_browser_conf_t *bcf = conf;
+
+ ngx_str_t *value, *browser;
+ ngx_uint_t i;
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+ if (ngx_strcmp(value[i].data, "netscape4") == 0) {
+ bcf->netscape4 = 1;
+ continue;
+ }
+
+ if (bcf->ancient_browsers == NULL) {
+ bcf->ancient_browsers = ngx_array_create(cf->pool, 4,
+ sizeof(ngx_str_t));
+ if (bcf->ancient_browsers == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ browser = ngx_array_push(bcf->ancient_browsers);
+ if (browser == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *browser = value[i];
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_modern_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_browser_conf_t *bcf = conf;
+
+ ngx_str_t *value;
+
+ bcf->modern_browser_value = ngx_palloc(cf->pool,
+ sizeof(ngx_http_variable_value_t));
+ if (bcf->modern_browser_value == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ bcf->modern_browser_value->len = value[1].len;
+ bcf->modern_browser_value->valid = 1;
+ bcf->modern_browser_value->no_cacheable = 0;
+ bcf->modern_browser_value->not_found = 0;
+ bcf->modern_browser_value->data = value[1].data;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_ancient_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_browser_conf_t *bcf = conf;
+
+ ngx_str_t *value;
+
+ bcf->ancient_browser_value = ngx_palloc(cf->pool,
+ sizeof(ngx_http_variable_value_t));
+ if (bcf->ancient_browser_value == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ bcf->ancient_browser_value->len = value[1].len;
+ bcf->ancient_browser_value->valid = 1;
+ bcf->ancient_browser_value->no_cacheable = 0;
+ bcf->ancient_browser_value->not_found = 0;
+ bcf->ancient_browser_value->data = value[1].data;
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_charset_filter_module.c b/app/nginx/src/http/modules/ngx_http_charset_filter_module.c
new file mode 100644
index 0000000..e52b96e
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_charset_filter_module.c
@@ -0,0 +1,1685 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_CHARSET_OFF -2
+#define NGX_HTTP_NO_CHARSET -3
+#define NGX_HTTP_CHARSET_VAR 0x10000
+
+/* 1 byte length and up to 3 bytes for the UTF-8 encoding of the UCS-2 */
+#define NGX_UTF_LEN 4
+
+#define NGX_HTML_ENTITY_LEN (sizeof("&#1114111;") - 1)
+
+
+typedef struct {
+ u_char **tables;
+ ngx_str_t name;
+
+ unsigned length:16;
+ unsigned utf8:1;
+} ngx_http_charset_t;
+
+
+typedef struct {
+ ngx_int_t src;
+ ngx_int_t dst;
+} ngx_http_charset_recode_t;
+
+
+typedef struct {
+ ngx_int_t src;
+ ngx_int_t dst;
+ u_char *src2dst;
+ u_char *dst2src;
+} ngx_http_charset_tables_t;
+
+
+typedef struct {
+ ngx_array_t charsets; /* ngx_http_charset_t */
+ ngx_array_t tables; /* ngx_http_charset_tables_t */
+ ngx_array_t recodes; /* ngx_http_charset_recode_t */
+} ngx_http_charset_main_conf_t;
+
+
+typedef struct {
+ ngx_int_t charset;
+ ngx_int_t source_charset;
+ ngx_flag_t override_charset;
+
+ ngx_hash_t types;
+ ngx_array_t *types_keys;
+} ngx_http_charset_loc_conf_t;
+
+
+typedef struct {
+ u_char *table;
+ ngx_int_t charset;
+ ngx_str_t charset_name;
+
+ ngx_chain_t *busy;
+ ngx_chain_t *free_bufs;
+ ngx_chain_t *free_buffers;
+
+ size_t saved_len;
+ u_char saved[NGX_UTF_LEN];
+
+ unsigned length:16;
+ unsigned from_utf8:1;
+ unsigned to_utf8:1;
+} ngx_http_charset_ctx_t;
+
+
+typedef struct {
+ ngx_http_charset_tables_t *table;
+ ngx_http_charset_t *charset;
+ ngx_uint_t characters;
+} ngx_http_charset_conf_ctx_t;
+
+
+static ngx_int_t ngx_http_destination_charset(ngx_http_request_t *r,
+ ngx_str_t *name);
+static ngx_int_t ngx_http_main_request_charset(ngx_http_request_t *r,
+ ngx_str_t *name);
+static ngx_int_t ngx_http_source_charset(ngx_http_request_t *r,
+ ngx_str_t *name);
+static ngx_int_t ngx_http_get_charset(ngx_http_request_t *r, ngx_str_t *name);
+static ngx_inline void ngx_http_set_charset(ngx_http_request_t *r,
+ ngx_str_t *charset);
+static ngx_int_t ngx_http_charset_ctx(ngx_http_request_t *r,
+ ngx_http_charset_t *charsets, ngx_int_t charset, ngx_int_t source_charset);
+static ngx_uint_t ngx_http_charset_recode(ngx_buf_t *b, u_char *table);
+static ngx_chain_t *ngx_http_charset_recode_from_utf8(ngx_pool_t *pool,
+ ngx_buf_t *buf, ngx_http_charset_ctx_t *ctx);
+static ngx_chain_t *ngx_http_charset_recode_to_utf8(ngx_pool_t *pool,
+ ngx_buf_t *buf, ngx_http_charset_ctx_t *ctx);
+
+static ngx_chain_t *ngx_http_charset_get_buf(ngx_pool_t *pool,
+ ngx_http_charset_ctx_t *ctx);
+static ngx_chain_t *ngx_http_charset_get_buffer(ngx_pool_t *pool,
+ ngx_http_charset_ctx_t *ctx, size_t size);
+
+static char *ngx_http_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_charset_map(ngx_conf_t *cf, ngx_command_t *dummy,
+ void *conf);
+
+static char *ngx_http_set_charset_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static ngx_int_t ngx_http_add_charset(ngx_array_t *charsets, ngx_str_t *name);
+
+static void *ngx_http_charset_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_charset_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_charset_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_charset_postconfiguration(ngx_conf_t *cf);
+
+
+static ngx_str_t ngx_http_charset_default_types[] = {
+ ngx_string("text/html"),
+ ngx_string("text/xml"),
+ ngx_string("text/plain"),
+ ngx_string("text/vnd.wap.wml"),
+ ngx_string("application/javascript"),
+ ngx_string("application/rss+xml"),
+ ngx_null_string
+};
+
+
+static ngx_command_t ngx_http_charset_filter_commands[] = {
+
+ { ngx_string("charset"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
+ |NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
+ ngx_http_set_charset_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_charset_loc_conf_t, charset),
+ NULL },
+
+ { ngx_string("source_charset"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
+ |NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
+ ngx_http_set_charset_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_charset_loc_conf_t, source_charset),
+ NULL },
+
+ { ngx_string("override_charset"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
+ |NGX_HTTP_LIF_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_charset_loc_conf_t, override_charset),
+ NULL },
+
+ { ngx_string("charset_types"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_types_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_charset_loc_conf_t, types_keys),
+ &ngx_http_charset_default_types[0] },
+
+ { ngx_string("charset_map"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2,
+ ngx_http_charset_map_block,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_charset_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_charset_postconfiguration, /* postconfiguration */
+
+ ngx_http_charset_create_main_conf, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_charset_create_loc_conf, /* create location configuration */
+ ngx_http_charset_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_charset_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_charset_filter_module_ctx, /* module context */
+ ngx_http_charset_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_charset_header_filter(ngx_http_request_t *r)
+{
+ ngx_int_t charset, source_charset;
+ ngx_str_t dst, src;
+ ngx_http_charset_t *charsets;
+ ngx_http_charset_main_conf_t *mcf;
+
+ if (r == r->main) {
+ charset = ngx_http_destination_charset(r, &dst);
+
+ } else {
+ charset = ngx_http_main_request_charset(r, &dst);
+ }
+
+ if (charset == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (charset == NGX_DECLINED) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ /* charset: charset index or NGX_HTTP_NO_CHARSET */
+
+ source_charset = ngx_http_source_charset(r, &src);
+
+ if (source_charset == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ /*
+ * source_charset: charset index, NGX_HTTP_NO_CHARSET,
+ * or NGX_HTTP_CHARSET_OFF
+ */
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "charset: \"%V\" > \"%V\"", &src, &dst);
+
+ if (source_charset == NGX_HTTP_CHARSET_OFF) {
+ ngx_http_set_charset(r, &dst);
+
+ return ngx_http_next_header_filter(r);
+ }
+
+ if (charset == NGX_HTTP_NO_CHARSET
+ || source_charset == NGX_HTTP_NO_CHARSET)
+ {
+ if (source_charset != charset
+ || ngx_strncasecmp(dst.data, src.data, dst.len) != 0)
+ {
+ goto no_charset_map;
+ }
+
+ ngx_http_set_charset(r, &dst);
+
+ return ngx_http_next_header_filter(r);
+ }
+
+ if (source_charset == charset) {
+ r->headers_out.content_type.len = r->headers_out.content_type_len;
+
+ ngx_http_set_charset(r, &dst);
+
+ return ngx_http_next_header_filter(r);
+ }
+
+ /* source_charset != charset */
+
+ if (r->headers_out.content_encoding
+ && r->headers_out.content_encoding->value.len)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module);
+ charsets = mcf->charsets.elts;
+
+ if (charsets[source_charset].tables == NULL
+ || charsets[source_charset].tables[charset] == NULL)
+ {
+ goto no_charset_map;
+ }
+
+ r->headers_out.content_type.len = r->headers_out.content_type_len;
+
+ ngx_http_set_charset(r, &dst);
+
+ return ngx_http_charset_ctx(r, charsets, charset, source_charset);
+
+no_charset_map:
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "no \"charset_map\" between the charsets \"%V\" and \"%V\"",
+ &src, &dst);
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_destination_charset(ngx_http_request_t *r, ngx_str_t *name)
+{
+ ngx_int_t charset;
+ ngx_http_charset_t *charsets;
+ ngx_http_variable_value_t *vv;
+ ngx_http_charset_loc_conf_t *mlcf;
+ ngx_http_charset_main_conf_t *mcf;
+
+ if (r->headers_out.content_type.len == 0) {
+ return NGX_DECLINED;
+ }
+
+ if (r->headers_out.override_charset
+ && r->headers_out.override_charset->len)
+ {
+ *name = *r->headers_out.override_charset;
+
+ charset = ngx_http_get_charset(r, name);
+
+ if (charset != NGX_HTTP_NO_CHARSET) {
+ return charset;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "unknown charset \"%V\" to override", name);
+
+ return NGX_DECLINED;
+ }
+
+ mlcf = ngx_http_get_module_loc_conf(r, ngx_http_charset_filter_module);
+ charset = mlcf->charset;
+
+ if (charset == NGX_HTTP_CHARSET_OFF) {
+ return NGX_DECLINED;
+ }
+
+ if (r->headers_out.charset.len) {
+ if (mlcf->override_charset == 0) {
+ return NGX_DECLINED;
+ }
+
+ } else {
+ if (ngx_http_test_content_type(r, &mlcf->types) == NULL) {
+ return NGX_DECLINED;
+ }
+ }
+
+ if (charset < NGX_HTTP_CHARSET_VAR) {
+ mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module);
+ charsets = mcf->charsets.elts;
+ *name = charsets[charset].name;
+ return charset;
+ }
+
+ vv = ngx_http_get_indexed_variable(r, charset - NGX_HTTP_CHARSET_VAR);
+
+ if (vv == NULL || vv->not_found) {
+ return NGX_ERROR;
+ }
+
+ name->len = vv->len;
+ name->data = vv->data;
+
+ return ngx_http_get_charset(r, name);
+}
+
+
+static ngx_int_t
+ngx_http_main_request_charset(ngx_http_request_t *r, ngx_str_t *src)
+{
+ ngx_int_t charset;
+ ngx_str_t *main_charset;
+ ngx_http_charset_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r->main, ngx_http_charset_filter_module);
+
+ if (ctx) {
+ *src = ctx->charset_name;
+ return ctx->charset;
+ }
+
+ main_charset = &r->main->headers_out.charset;
+
+ if (main_charset->len == 0) {
+ return NGX_DECLINED;
+ }
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_charset_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r->main, ctx, ngx_http_charset_filter_module);
+
+ charset = ngx_http_get_charset(r, main_charset);
+
+ ctx->charset = charset;
+ ctx->charset_name = *main_charset;
+ *src = *main_charset;
+
+ return charset;
+}
+
+
+static ngx_int_t
+ngx_http_source_charset(ngx_http_request_t *r, ngx_str_t *name)
+{
+ ngx_int_t charset;
+ ngx_http_charset_t *charsets;
+ ngx_http_variable_value_t *vv;
+ ngx_http_charset_loc_conf_t *lcf;
+ ngx_http_charset_main_conf_t *mcf;
+
+ if (r->headers_out.charset.len) {
+ *name = r->headers_out.charset;
+ return ngx_http_get_charset(r, name);
+ }
+
+ lcf = ngx_http_get_module_loc_conf(r, ngx_http_charset_filter_module);
+
+ charset = lcf->source_charset;
+
+ if (charset == NGX_HTTP_CHARSET_OFF) {
+ name->len = 0;
+ return charset;
+ }
+
+ if (charset < NGX_HTTP_CHARSET_VAR) {
+ mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module);
+ charsets = mcf->charsets.elts;
+ *name = charsets[charset].name;
+ return charset;
+ }
+
+ vv = ngx_http_get_indexed_variable(r, charset - NGX_HTTP_CHARSET_VAR);
+
+ if (vv == NULL || vv->not_found) {
+ return NGX_ERROR;
+ }
+
+ name->len = vv->len;
+ name->data = vv->data;
+
+ return ngx_http_get_charset(r, name);
+}
+
+
+static ngx_int_t
+ngx_http_get_charset(ngx_http_request_t *r, ngx_str_t *name)
+{
+ ngx_uint_t i, n;
+ ngx_http_charset_t *charset;
+ ngx_http_charset_main_conf_t *mcf;
+
+ mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module);
+
+ charset = mcf->charsets.elts;
+ n = mcf->charsets.nelts;
+
+ for (i = 0; i < n; i++) {
+ if (charset[i].name.len != name->len) {
+ continue;
+ }
+
+ if (ngx_strncasecmp(charset[i].name.data, name->data, name->len) == 0) {
+ return i;
+ }
+ }
+
+ return NGX_HTTP_NO_CHARSET;
+}
+
+
+static ngx_inline void
+ngx_http_set_charset(ngx_http_request_t *r, ngx_str_t *charset)
+{
+ if (r != r->main) {
+ return;
+ }
+
+ if (r->headers_out.status == NGX_HTTP_MOVED_PERMANENTLY
+ || r->headers_out.status == NGX_HTTP_MOVED_TEMPORARILY)
+ {
+ /*
+ * do not set charset for the redirect because NN 4.x
+ * use this charset instead of the next page charset
+ */
+
+ r->headers_out.charset.len = 0;
+ return;
+ }
+
+ r->headers_out.charset = *charset;
+}
+
+
+static ngx_int_t
+ngx_http_charset_ctx(ngx_http_request_t *r, ngx_http_charset_t *charsets,
+ ngx_int_t charset, ngx_int_t source_charset)
+{
+ ngx_http_charset_ctx_t *ctx;
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_charset_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_charset_filter_module);
+
+ ctx->table = charsets[source_charset].tables[charset];
+ ctx->charset = charset;
+ ctx->charset_name = charsets[charset].name;
+ ctx->length = charsets[charset].length;
+ ctx->from_utf8 = charsets[source_charset].utf8;
+ ctx->to_utf8 = charsets[charset].utf8;
+
+ r->filter_need_in_memory = 1;
+
+ if ((ctx->to_utf8 || ctx->from_utf8) && r == r->main) {
+ ngx_http_clear_content_length(r);
+
+ } else {
+ r->filter_need_temporary = 1;
+ }
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_charset_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_chain_t *cl, *out, **ll;
+ ngx_http_charset_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_charset_filter_module);
+
+ if (ctx == NULL || ctx->table == NULL) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ if ((ctx->to_utf8 || ctx->from_utf8) || ctx->busy) {
+
+ out = NULL;
+ ll = &out;
+
+ for (cl = in; cl; cl = cl->next) {
+ b = cl->buf;
+
+ if (ngx_buf_size(b) == 0) {
+
+ *ll = ngx_alloc_chain_link(r->pool);
+ if (*ll == NULL) {
+ return NGX_ERROR;
+ }
+
+ (*ll)->buf = b;
+ (*ll)->next = NULL;
+
+ ll = &(*ll)->next;
+
+ continue;
+ }
+
+ if (ctx->to_utf8) {
+ *ll = ngx_http_charset_recode_to_utf8(r->pool, b, ctx);
+
+ } else {
+ *ll = ngx_http_charset_recode_from_utf8(r->pool, b, ctx);
+ }
+
+ if (*ll == NULL) {
+ return NGX_ERROR;
+ }
+
+ while (*ll) {
+ ll = &(*ll)->next;
+ }
+ }
+
+ rc = ngx_http_next_body_filter(r, out);
+
+ if (out) {
+ if (ctx->busy == NULL) {
+ ctx->busy = out;
+
+ } else {
+ for (cl = ctx->busy; cl->next; cl = cl->next) { /* void */ }
+ cl->next = out;
+ }
+ }
+
+ while (ctx->busy) {
+
+ cl = ctx->busy;
+ b = cl->buf;
+
+ if (ngx_buf_size(b) != 0) {
+ break;
+ }
+
+ ctx->busy = cl->next;
+
+ if (b->tag != (ngx_buf_tag_t) &ngx_http_charset_filter_module) {
+ continue;
+ }
+
+ if (b->shadow) {
+ b->shadow->pos = b->shadow->last;
+ }
+
+ if (b->pos) {
+ cl->next = ctx->free_buffers;
+ ctx->free_buffers = cl;
+ continue;
+ }
+
+ cl->next = ctx->free_bufs;
+ ctx->free_bufs = cl;
+ }
+
+ return rc;
+ }
+
+ for (cl = in; cl; cl = cl->next) {
+ (void) ngx_http_charset_recode(cl->buf, ctx->table);
+ }
+
+ return ngx_http_next_body_filter(r, in);
+}
+
+
+static ngx_uint_t
+ngx_http_charset_recode(ngx_buf_t *b, u_char *table)
+{
+ u_char *p, *last;
+
+ last = b->last;
+
+ for (p = b->pos; p < last; p++) {
+
+ if (*p != table[*p]) {
+ goto recode;
+ }
+ }
+
+ return 0;
+
+recode:
+
+ do {
+ if (*p != table[*p]) {
+ *p = table[*p];
+ }
+
+ p++;
+
+ } while (p < last);
+
+ b->in_file = 0;
+
+ return 1;
+}
+
+
+static ngx_chain_t *
+ngx_http_charset_recode_from_utf8(ngx_pool_t *pool, ngx_buf_t *buf,
+ ngx_http_charset_ctx_t *ctx)
+{
+ size_t len, size;
+ u_char c, *p, *src, *dst, *saved, **table;
+ uint32_t n;
+ ngx_buf_t *b;
+ ngx_uint_t i;
+ ngx_chain_t *out, *cl, **ll;
+
+ src = buf->pos;
+
+ if (ctx->saved_len == 0) {
+
+ for ( /* void */ ; src < buf->last; src++) {
+
+ if (*src < 0x80) {
+ continue;
+ }
+
+ len = src - buf->pos;
+
+ if (len > 512) {
+ out = ngx_http_charset_get_buf(pool, ctx);
+ if (out == NULL) {
+ return NULL;
+ }
+
+ b = out->buf;
+
+ b->temporary = buf->temporary;
+ b->memory = buf->memory;
+ b->mmap = buf->mmap;
+ b->flush = buf->flush;
+
+ b->pos = buf->pos;
+ b->last = src;
+
+ out->buf = b;
+ out->next = NULL;
+
+ size = buf->last - src;
+
+ saved = src;
+ n = ngx_utf8_decode(&saved, size);
+
+ if (n == 0xfffffffe) {
+ /* incomplete UTF-8 symbol */
+
+ ngx_memcpy(ctx->saved, src, size);
+ ctx->saved_len = size;
+
+ b->shadow = buf;
+
+ return out;
+ }
+
+ } else {
+ out = NULL;
+ size = len + buf->last - src;
+ src = buf->pos;
+ }
+
+ if (size < NGX_HTML_ENTITY_LEN) {
+ size += NGX_HTML_ENTITY_LEN;
+ }
+
+ cl = ngx_http_charset_get_buffer(pool, ctx, size);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ if (out) {
+ out->next = cl;
+
+ } else {
+ out = cl;
+ }
+
+ b = cl->buf;
+ dst = b->pos;
+
+ goto recode;
+ }
+
+ out = ngx_alloc_chain_link(pool);
+ if (out == NULL) {
+ return NULL;
+ }
+
+ out->buf = buf;
+ out->next = NULL;
+
+ return out;
+ }
+
+ /* process incomplete UTF sequence from previous buffer */
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pool->log, 0,
+ "http charset utf saved: %z", ctx->saved_len);
+
+ p = src;
+
+ for (i = ctx->saved_len; i < NGX_UTF_LEN; i++) {
+ ctx->saved[i] = *p++;
+
+ if (p == buf->last) {
+ break;
+ }
+ }
+
+ saved = ctx->saved;
+ n = ngx_utf8_decode(&saved, i);
+
+ c = '\0';
+
+ if (n < 0x10000) {
+ table = (u_char **) ctx->table;
+ p = table[n >> 8];
+
+ if (p) {
+ c = p[n & 0xff];
+ }
+
+ } else if (n == 0xfffffffe) {
+
+ /* incomplete UTF-8 symbol */
+
+ if (i < NGX_UTF_LEN) {
+ out = ngx_http_charset_get_buf(pool, ctx);
+ if (out == NULL) {
+ return NULL;
+ }
+
+ b = out->buf;
+
+ b->pos = buf->pos;
+ b->last = buf->last;
+ b->sync = 1;
+ b->shadow = buf;
+
+ ngx_memcpy(&ctx->saved[ctx->saved_len], src, i);
+ ctx->saved_len += i;
+
+ return out;
+ }
+ }
+
+ size = buf->last - buf->pos;
+
+ if (size < NGX_HTML_ENTITY_LEN) {
+ size += NGX_HTML_ENTITY_LEN;
+ }
+
+ cl = ngx_http_charset_get_buffer(pool, ctx, size);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ out = cl;
+
+ b = cl->buf;
+ dst = b->pos;
+
+ if (c) {
+ *dst++ = c;
+
+ } else if (n == 0xfffffffe) {
+ *dst++ = '?';
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pool->log, 0,
+ "http charset invalid utf 0");
+
+ saved = &ctx->saved[NGX_UTF_LEN];
+
+ } else if (n > 0x10ffff) {
+ *dst++ = '?';
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pool->log, 0,
+ "http charset invalid utf 1");
+
+ } else {
+ dst = ngx_sprintf(dst, "&#%uD;", n);
+ }
+
+ src += (saved - ctx->saved) - ctx->saved_len;
+ ctx->saved_len = 0;
+
+recode:
+
+ ll = &cl->next;
+
+ table = (u_char **) ctx->table;
+
+ while (src < buf->last) {
+
+ if ((size_t) (b->end - dst) < NGX_HTML_ENTITY_LEN) {
+ b->last = dst;
+
+ size = buf->last - src + NGX_HTML_ENTITY_LEN;
+
+ cl = ngx_http_charset_get_buffer(pool, ctx, size);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ *ll = cl;
+ ll = &cl->next;
+
+ b = cl->buf;
+ dst = b->pos;
+ }
+
+ if (*src < 0x80) {
+ *dst++ = *src++;
+ continue;
+ }
+
+ len = buf->last - src;
+
+ n = ngx_utf8_decode(&src, len);
+
+ if (n < 0x10000) {
+
+ p = table[n >> 8];
+
+ if (p) {
+ c = p[n & 0xff];
+
+ if (c) {
+ *dst++ = c;
+ continue;
+ }
+ }
+
+ dst = ngx_sprintf(dst, "&#%uD;", n);
+
+ continue;
+ }
+
+ if (n == 0xfffffffe) {
+ /* incomplete UTF-8 symbol */
+
+ ngx_memcpy(ctx->saved, src, len);
+ ctx->saved_len = len;
+
+ if (b->pos == dst) {
+ b->sync = 1;
+ b->temporary = 0;
+ }
+
+ break;
+ }
+
+ if (n > 0x10ffff) {
+ *dst++ = '?';
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pool->log, 0,
+ "http charset invalid utf 2");
+
+ continue;
+ }
+
+ /* n > 0xffff */
+
+ dst = ngx_sprintf(dst, "&#%uD;", n);
+ }
+
+ b->last = dst;
+
+ b->last_buf = buf->last_buf;
+ b->last_in_chain = buf->last_in_chain;
+ b->flush = buf->flush;
+
+ b->shadow = buf;
+
+ return out;
+}
+
+
+static ngx_chain_t *
+ngx_http_charset_recode_to_utf8(ngx_pool_t *pool, ngx_buf_t *buf,
+ ngx_http_charset_ctx_t *ctx)
+{
+ size_t len, size;
+ u_char *p, *src, *dst, *table;
+ ngx_buf_t *b;
+ ngx_chain_t *out, *cl, **ll;
+
+ table = ctx->table;
+
+ for (src = buf->pos; src < buf->last; src++) {
+ if (table[*src * NGX_UTF_LEN] == '\1') {
+ continue;
+ }
+
+ goto recode;
+ }
+
+ out = ngx_alloc_chain_link(pool);
+ if (out == NULL) {
+ return NULL;
+ }
+
+ out->buf = buf;
+ out->next = NULL;
+
+ return out;
+
+recode:
+
+ /*
+ * we assume that there are about half of characters to be recoded,
+ * so we preallocate "size / 2 + size / 2 * ctx->length"
+ */
+
+ len = src - buf->pos;
+
+ if (len > 512) {
+ out = ngx_http_charset_get_buf(pool, ctx);
+ if (out == NULL) {
+ return NULL;
+ }
+
+ b = out->buf;
+
+ b->temporary = buf->temporary;
+ b->memory = buf->memory;
+ b->mmap = buf->mmap;
+ b->flush = buf->flush;
+
+ b->pos = buf->pos;
+ b->last = src;
+
+ out->buf = b;
+ out->next = NULL;
+
+ size = buf->last - src;
+ size = size / 2 + size / 2 * ctx->length;
+
+ } else {
+ out = NULL;
+
+ size = buf->last - src;
+ size = len + size / 2 + size / 2 * ctx->length;
+
+ src = buf->pos;
+ }
+
+ cl = ngx_http_charset_get_buffer(pool, ctx, size);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ if (out) {
+ out->next = cl;
+
+ } else {
+ out = cl;
+ }
+
+ ll = &cl->next;
+
+ b = cl->buf;
+ dst = b->pos;
+
+ while (src < buf->last) {
+
+ p = &table[*src++ * NGX_UTF_LEN];
+ len = *p++;
+
+ if ((size_t) (b->end - dst) < len) {
+ b->last = dst;
+
+ size = buf->last - src;
+ size = len + size / 2 + size / 2 * ctx->length;
+
+ cl = ngx_http_charset_get_buffer(pool, ctx, size);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ *ll = cl;
+ ll = &cl->next;
+
+ b = cl->buf;
+ dst = b->pos;
+ }
+
+ while (len) {
+ *dst++ = *p++;
+ len--;
+ }
+ }
+
+ b->last = dst;
+
+ b->last_buf = buf->last_buf;
+ b->last_in_chain = buf->last_in_chain;
+ b->flush = buf->flush;
+
+ b->shadow = buf;
+
+ return out;
+}
+
+
+static ngx_chain_t *
+ngx_http_charset_get_buf(ngx_pool_t *pool, ngx_http_charset_ctx_t *ctx)
+{
+ ngx_chain_t *cl;
+
+ cl = ctx->free_bufs;
+
+ if (cl) {
+ ctx->free_bufs = cl->next;
+
+ cl->buf->shadow = NULL;
+ cl->next = NULL;
+
+ return cl;
+ }
+
+ cl = ngx_alloc_chain_link(pool);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ cl->buf = ngx_calloc_buf(pool);
+ if (cl->buf == NULL) {
+ return NULL;
+ }
+
+ cl->next = NULL;
+
+ cl->buf->tag = (ngx_buf_tag_t) &ngx_http_charset_filter_module;
+
+ return cl;
+}
+
+
+static ngx_chain_t *
+ngx_http_charset_get_buffer(ngx_pool_t *pool, ngx_http_charset_ctx_t *ctx,
+ size_t size)
+{
+ ngx_buf_t *b;
+ ngx_chain_t *cl, **ll;
+
+ for (ll = &ctx->free_buffers, cl = ctx->free_buffers;
+ cl;
+ ll = &cl->next, cl = cl->next)
+ {
+ b = cl->buf;
+
+ if ((size_t) (b->end - b->start) >= size) {
+ *ll = cl->next;
+ cl->next = NULL;
+
+ b->pos = b->start;
+ b->temporary = 1;
+ b->shadow = NULL;
+
+ return cl;
+ }
+ }
+
+ cl = ngx_alloc_chain_link(pool);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ cl->buf = ngx_create_temp_buf(pool, size);
+ if (cl->buf == NULL) {
+ return NULL;
+ }
+
+ cl->next = NULL;
+
+ cl->buf->temporary = 1;
+ cl->buf->tag = (ngx_buf_tag_t) &ngx_http_charset_filter_module;
+
+ return cl;
+}
+
+
+static char *
+ngx_http_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_charset_main_conf_t *mcf = conf;
+
+ char *rv;
+ u_char *p, *dst2src, **pp;
+ ngx_int_t src, dst;
+ ngx_uint_t i, n;
+ ngx_str_t *value;
+ ngx_conf_t pvcf;
+ ngx_http_charset_t *charset;
+ ngx_http_charset_tables_t *table;
+ ngx_http_charset_conf_ctx_t ctx;
+
+ value = cf->args->elts;
+
+ src = ngx_http_add_charset(&mcf->charsets, &value[1]);
+ if (src == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ dst = ngx_http_add_charset(&mcf->charsets, &value[2]);
+ if (dst == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (src == dst) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"charset_map\" between the same charsets "
+ "\"%V\" and \"%V\"", &value[1], &value[2]);
+ return NGX_CONF_ERROR;
+ }
+
+ table = mcf->tables.elts;
+ for (i = 0; i < mcf->tables.nelts; i++) {
+ if ((src == table->src && dst == table->dst)
+ || (src == table->dst && dst == table->src))
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "duplicate \"charset_map\" between "
+ "\"%V\" and \"%V\"", &value[1], &value[2]);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ table = ngx_array_push(&mcf->tables);
+ if (table == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ table->src = src;
+ table->dst = dst;
+
+ if (ngx_strcasecmp(value[2].data, (u_char *) "utf-8") == 0) {
+ table->src2dst = ngx_pcalloc(cf->pool, 256 * NGX_UTF_LEN);
+ if (table->src2dst == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ table->dst2src = ngx_pcalloc(cf->pool, 256 * sizeof(void *));
+ if (table->dst2src == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ dst2src = ngx_pcalloc(cf->pool, 256);
+ if (dst2src == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pp = (u_char **) &table->dst2src[0];
+ pp[0] = dst2src;
+
+ for (i = 0; i < 128; i++) {
+ p = &table->src2dst[i * NGX_UTF_LEN];
+ p[0] = '\1';
+ p[1] = (u_char) i;
+ dst2src[i] = (u_char) i;
+ }
+
+ for (/* void */; i < 256; i++) {
+ p = &table->src2dst[i * NGX_UTF_LEN];
+ p[0] = '\1';
+ p[1] = '?';
+ }
+
+ } else {
+ table->src2dst = ngx_palloc(cf->pool, 256);
+ if (table->src2dst == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ table->dst2src = ngx_palloc(cf->pool, 256);
+ if (table->dst2src == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 0; i < 128; i++) {
+ table->src2dst[i] = (u_char) i;
+ table->dst2src[i] = (u_char) i;
+ }
+
+ for (/* void */; i < 256; i++) {
+ table->src2dst[i] = '?';
+ table->dst2src[i] = '?';
+ }
+ }
+
+ charset = mcf->charsets.elts;
+
+ ctx.table = table;
+ ctx.charset = &charset[dst];
+ ctx.characters = 0;
+
+ pvcf = *cf;
+ cf->ctx = &ctx;
+ cf->handler = ngx_http_charset_map;
+ cf->handler_conf = conf;
+
+ rv = ngx_conf_parse(cf, NULL);
+
+ *cf = pvcf;
+
+ if (ctx.characters) {
+ n = ctx.charset->length;
+ ctx.charset->length /= ctx.characters;
+
+ if (((n * 10) / ctx.characters) % 10 > 4) {
+ ctx.charset->length++;
+ }
+ }
+
+ return rv;
+}
+
+
+static char *
+ngx_http_charset_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+ u_char *p, *dst2src, **pp;
+ uint32_t n;
+ ngx_int_t src, dst;
+ ngx_str_t *value;
+ ngx_uint_t i;
+ ngx_http_charset_tables_t *table;
+ ngx_http_charset_conf_ctx_t *ctx;
+
+ if (cf->args->nelts != 2) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameters number");
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ src = ngx_hextoi(value[0].data, value[0].len);
+ if (src == NGX_ERROR || src > 255) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid value \"%V\"", &value[0]);
+ return NGX_CONF_ERROR;
+ }
+
+ ctx = cf->ctx;
+ table = ctx->table;
+
+ if (ctx->charset->utf8) {
+ p = &table->src2dst[src * NGX_UTF_LEN];
+
+ *p++ = (u_char) (value[1].len / 2);
+
+ for (i = 0; i < value[1].len; i += 2) {
+ dst = ngx_hextoi(&value[1].data[i], 2);
+ if (dst == NGX_ERROR || dst > 255) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid value \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ *p++ = (u_char) dst;
+ }
+
+ i /= 2;
+
+ ctx->charset->length += i;
+ ctx->characters++;
+
+ p = &table->src2dst[src * NGX_UTF_LEN] + 1;
+
+ n = ngx_utf8_decode(&p, i);
+
+ if (n > 0xffff) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid value \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ pp = (u_char **) &table->dst2src[0];
+
+ dst2src = pp[n >> 8];
+
+ if (dst2src == NULL) {
+ dst2src = ngx_pcalloc(cf->pool, 256);
+ if (dst2src == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pp[n >> 8] = dst2src;
+ }
+
+ dst2src[n & 0xff] = (u_char) src;
+
+ } else {
+ dst = ngx_hextoi(value[1].data, value[1].len);
+ if (dst == NGX_ERROR || dst > 255) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid value \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ table->src2dst[src] = (u_char) dst;
+ table->dst2src[dst] = (u_char) src;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_set_charset_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *p = conf;
+
+ ngx_int_t *cp;
+ ngx_str_t *value, var;
+ ngx_http_charset_main_conf_t *mcf;
+
+ cp = (ngx_int_t *) (p + cmd->offset);
+
+ if (*cp != NGX_CONF_UNSET) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (cmd->offset == offsetof(ngx_http_charset_loc_conf_t, charset)
+ && ngx_strcmp(value[1].data, "off") == 0)
+ {
+ *cp = NGX_HTTP_CHARSET_OFF;
+ return NGX_CONF_OK;
+ }
+
+
+ if (value[1].data[0] == '$') {
+ var.len = value[1].len - 1;
+ var.data = value[1].data + 1;
+
+ *cp = ngx_http_get_variable_index(cf, &var);
+
+ if (*cp == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ *cp += NGX_HTTP_CHARSET_VAR;
+
+ return NGX_CONF_OK;
+ }
+
+ mcf = ngx_http_conf_get_module_main_conf(cf,
+ ngx_http_charset_filter_module);
+
+ *cp = ngx_http_add_charset(&mcf->charsets, &value[1]);
+ if (*cp == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_add_charset(ngx_array_t *charsets, ngx_str_t *name)
+{
+ ngx_uint_t i;
+ ngx_http_charset_t *c;
+
+ c = charsets->elts;
+ for (i = 0; i < charsets->nelts; i++) {
+ if (name->len != c[i].name.len) {
+ continue;
+ }
+
+ if (ngx_strcasecmp(name->data, c[i].name.data) == 0) {
+ break;
+ }
+ }
+
+ if (i < charsets->nelts) {
+ return i;
+ }
+
+ c = ngx_array_push(charsets);
+ if (c == NULL) {
+ return NGX_ERROR;
+ }
+
+ c->tables = NULL;
+ c->name = *name;
+ c->length = 0;
+
+ if (ngx_strcasecmp(name->data, (u_char *) "utf-8") == 0) {
+ c->utf8 = 1;
+
+ } else {
+ c->utf8 = 0;
+ }
+
+ return i;
+}
+
+
+static void *
+ngx_http_charset_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_charset_main_conf_t *mcf;
+
+ mcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_charset_main_conf_t));
+ if (mcf == NULL) {
+ return NULL;
+ }
+
+ if (ngx_array_init(&mcf->charsets, cf->pool, 2, sizeof(ngx_http_charset_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ if (ngx_array_init(&mcf->tables, cf->pool, 1,
+ sizeof(ngx_http_charset_tables_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ if (ngx_array_init(&mcf->recodes, cf->pool, 2,
+ sizeof(ngx_http_charset_recode_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ return mcf;
+}
+
+
+static void *
+ngx_http_charset_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_charset_loc_conf_t *lcf;
+
+ lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_charset_loc_conf_t));
+ if (lcf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * lcf->types = { NULL };
+ * lcf->types_keys = NULL;
+ */
+
+ lcf->charset = NGX_CONF_UNSET;
+ lcf->source_charset = NGX_CONF_UNSET;
+ lcf->override_charset = NGX_CONF_UNSET;
+
+ return lcf;
+}
+
+
+static char *
+ngx_http_charset_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_charset_loc_conf_t *prev = parent;
+ ngx_http_charset_loc_conf_t *conf = child;
+
+ ngx_uint_t i;
+ ngx_http_charset_recode_t *recode;
+ ngx_http_charset_main_conf_t *mcf;
+
+ if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
+ &prev->types_keys, &prev->types,
+ ngx_http_charset_default_types)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_value(conf->override_charset, prev->override_charset, 0);
+ ngx_conf_merge_value(conf->charset, prev->charset, NGX_HTTP_CHARSET_OFF);
+ ngx_conf_merge_value(conf->source_charset, prev->source_charset,
+ NGX_HTTP_CHARSET_OFF);
+
+ if (conf->charset == NGX_HTTP_CHARSET_OFF
+ || conf->source_charset == NGX_HTTP_CHARSET_OFF
+ || conf->charset == conf->source_charset)
+ {
+ return NGX_CONF_OK;
+ }
+
+ if (conf->source_charset >= NGX_HTTP_CHARSET_VAR
+ || conf->charset >= NGX_HTTP_CHARSET_VAR)
+ {
+ return NGX_CONF_OK;
+ }
+
+ mcf = ngx_http_conf_get_module_main_conf(cf,
+ ngx_http_charset_filter_module);
+ recode = mcf->recodes.elts;
+ for (i = 0; i < mcf->recodes.nelts; i++) {
+ if (conf->source_charset == recode[i].src
+ && conf->charset == recode[i].dst)
+ {
+ return NGX_CONF_OK;
+ }
+ }
+
+ recode = ngx_array_push(&mcf->recodes);
+ if (recode == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ recode->src = conf->source_charset;
+ recode->dst = conf->charset;
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_charset_postconfiguration(ngx_conf_t *cf)
+{
+ u_char **src, **dst;
+ ngx_int_t c;
+ ngx_uint_t i, t;
+ ngx_http_charset_t *charset;
+ ngx_http_charset_recode_t *recode;
+ ngx_http_charset_tables_t *tables;
+ ngx_http_charset_main_conf_t *mcf;
+
+ mcf = ngx_http_conf_get_module_main_conf(cf,
+ ngx_http_charset_filter_module);
+
+ recode = mcf->recodes.elts;
+ tables = mcf->tables.elts;
+ charset = mcf->charsets.elts;
+
+ for (i = 0; i < mcf->recodes.nelts; i++) {
+
+ c = recode[i].src;
+
+ for (t = 0; t < mcf->tables.nelts; t++) {
+
+ if (c == tables[t].src && recode[i].dst == tables[t].dst) {
+ goto next;
+ }
+
+ if (c == tables[t].dst && recode[i].dst == tables[t].src) {
+ goto next;
+ }
+ }
+
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"charset_map\" between the charsets \"%V\" and \"%V\"",
+ &charset[c].name, &charset[recode[i].dst].name);
+ return NGX_ERROR;
+
+ next:
+ continue;
+ }
+
+
+ for (t = 0; t < mcf->tables.nelts; t++) {
+
+ src = charset[tables[t].src].tables;
+
+ if (src == NULL) {
+ src = ngx_pcalloc(cf->pool, sizeof(u_char *) * mcf->charsets.nelts);
+ if (src == NULL) {
+ return NGX_ERROR;
+ }
+
+ charset[tables[t].src].tables = src;
+ }
+
+ dst = charset[tables[t].dst].tables;
+
+ if (dst == NULL) {
+ dst = ngx_pcalloc(cf->pool, sizeof(u_char *) * mcf->charsets.nelts);
+ if (dst == NULL) {
+ return NGX_ERROR;
+ }
+
+ charset[tables[t].dst].tables = dst;
+ }
+
+ src[tables[t].dst] = tables[t].src2dst;
+ dst[tables[t].src] = tables[t].dst2src;
+ }
+
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_charset_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_charset_body_filter;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_chunked_filter_module.c b/app/nginx/src/http/modules/ngx_http_chunked_filter_module.c
new file mode 100644
index 0000000..ac2e3e8
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_chunked_filter_module.c
@@ -0,0 +1,243 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_chain_t *free;
+ ngx_chain_t *busy;
+} ngx_http_chunked_filter_ctx_t;
+
+
+static ngx_int_t ngx_http_chunked_filter_init(ngx_conf_t *cf);
+
+
+static ngx_http_module_t ngx_http_chunked_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_chunked_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_chunked_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_chunked_filter_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_chunked_header_filter(ngx_http_request_t *r)
+{
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_chunked_filter_ctx_t *ctx;
+
+ if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED
+ || r->headers_out.status == NGX_HTTP_NO_CONTENT
+ || r->headers_out.status < NGX_HTTP_OK
+ || r != r->main
+ || r->method == NGX_HTTP_HEAD)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ if (r->headers_out.content_length_n == -1) {
+ if (r->http_version < NGX_HTTP_VERSION_11) {
+ r->keepalive = 0;
+
+ } else {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->chunked_transfer_encoding) {
+ r->chunked = 1;
+
+ ctx = ngx_pcalloc(r->pool,
+ sizeof(ngx_http_chunked_filter_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_chunked_filter_module);
+
+ } else {
+ r->keepalive = 0;
+ }
+ }
+ }
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ u_char *chunk;
+ off_t size;
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_chain_t *out, *cl, *tl, **ll;
+ ngx_http_chunked_filter_ctx_t *ctx;
+
+ if (in == NULL || !r->chunked || r->header_only) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_chunked_filter_module);
+
+ out = NULL;
+ ll = &out;
+
+ size = 0;
+ cl = in;
+
+ for ( ;; ) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http chunk: %O", ngx_buf_size(cl->buf));
+
+ size += ngx_buf_size(cl->buf);
+
+ if (cl->buf->flush
+ || cl->buf->sync
+ || ngx_buf_in_memory(cl->buf)
+ || cl->buf->in_file)
+ {
+ tl = ngx_alloc_chain_link(r->pool);
+ if (tl == NULL) {
+ return NGX_ERROR;
+ }
+
+ tl->buf = cl->buf;
+ *ll = tl;
+ ll = &tl->next;
+ }
+
+ if (cl->next == NULL) {
+ break;
+ }
+
+ cl = cl->next;
+ }
+
+ if (size) {
+ tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+ if (tl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = tl->buf;
+ chunk = b->start;
+
+ if (chunk == NULL) {
+ /* the "0000000000000000" is 64-bit hexadecimal string */
+
+ chunk = ngx_palloc(r->pool, sizeof("0000000000000000" CRLF) - 1);
+ if (chunk == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->start = chunk;
+ b->end = chunk + sizeof("0000000000000000" CRLF) - 1;
+ }
+
+ b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module;
+ b->memory = 0;
+ b->temporary = 1;
+ b->pos = chunk;
+ b->last = ngx_sprintf(chunk, "%xO" CRLF, size);
+
+ tl->next = out;
+ out = tl;
+ }
+
+ if (cl->buf->last_buf) {
+ tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+ if (tl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = tl->buf;
+
+ b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module;
+ b->temporary = 0;
+ b->memory = 1;
+ b->last_buf = 1;
+ b->pos = (u_char *) CRLF "0" CRLF CRLF;
+ b->last = b->pos + 7;
+
+ cl->buf->last_buf = 0;
+
+ *ll = tl;
+
+ if (size == 0) {
+ b->pos += 2;
+ }
+
+ } else if (size > 0) {
+ tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+ if (tl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = tl->buf;
+
+ b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module;
+ b->temporary = 0;
+ b->memory = 1;
+ b->pos = (u_char *) CRLF;
+ b->last = b->pos + 2;
+
+ *ll = tl;
+
+ } else {
+ *ll = NULL;
+ }
+
+ rc = ngx_http_next_body_filter(r, out);
+
+ ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out,
+ (ngx_buf_tag_t) &ngx_http_chunked_filter_module);
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_chunked_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_chunked_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_chunked_body_filter;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_dav_module.c b/app/nginx/src/http/modules/ngx_http_dav_module.c
new file mode 100644
index 0000000..895a52d
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_dav_module.c
@@ -0,0 +1,1159 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_DAV_OFF 2
+
+
+#define NGX_HTTP_DAV_NO_DEPTH -3
+#define NGX_HTTP_DAV_INVALID_DEPTH -2
+#define NGX_HTTP_DAV_INFINITY_DEPTH -1
+
+
+typedef struct {
+ ngx_uint_t methods;
+ ngx_uint_t access;
+ ngx_uint_t min_delete_depth;
+ ngx_flag_t create_full_put_path;
+} ngx_http_dav_loc_conf_t;
+
+
+typedef struct {
+ ngx_str_t path;
+ size_t len;
+} ngx_http_dav_copy_ctx_t;
+
+
+static ngx_int_t ngx_http_dav_handler(ngx_http_request_t *r);
+
+static void ngx_http_dav_put_handler(ngx_http_request_t *r);
+
+static ngx_int_t ngx_http_dav_delete_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_dav_delete_path(ngx_http_request_t *r,
+ ngx_str_t *path, ngx_uint_t dir);
+static ngx_int_t ngx_http_dav_delete_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path);
+static ngx_int_t ngx_http_dav_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path);
+static ngx_int_t ngx_http_dav_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path);
+
+static ngx_int_t ngx_http_dav_mkcol_handler(ngx_http_request_t *r,
+ ngx_http_dav_loc_conf_t *dlcf);
+
+static ngx_int_t ngx_http_dav_copy_move_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_dav_copy_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path);
+static ngx_int_t ngx_http_dav_copy_dir_time(ngx_tree_ctx_t *ctx,
+ ngx_str_t *path);
+static ngx_int_t ngx_http_dav_copy_tree_file(ngx_tree_ctx_t *ctx,
+ ngx_str_t *path);
+
+static ngx_int_t ngx_http_dav_depth(ngx_http_request_t *r, ngx_int_t dflt);
+static ngx_int_t ngx_http_dav_error(ngx_log_t *log, ngx_err_t err,
+ ngx_int_t not_found, char *failed, u_char *path);
+static ngx_int_t ngx_http_dav_location(ngx_http_request_t *r, u_char *path);
+static void *ngx_http_dav_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_dav_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_dav_init(ngx_conf_t *cf);
+
+
+static ngx_conf_bitmask_t ngx_http_dav_methods_mask[] = {
+ { ngx_string("off"), NGX_HTTP_DAV_OFF },
+ { ngx_string("put"), NGX_HTTP_PUT },
+ { ngx_string("delete"), NGX_HTTP_DELETE },
+ { ngx_string("mkcol"), NGX_HTTP_MKCOL },
+ { ngx_string("copy"), NGX_HTTP_COPY },
+ { ngx_string("move"), NGX_HTTP_MOVE },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t ngx_http_dav_commands[] = {
+
+ { ngx_string("dav_methods"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_dav_loc_conf_t, methods),
+ &ngx_http_dav_methods_mask },
+
+ { ngx_string("create_full_put_path"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_dav_loc_conf_t, create_full_put_path),
+ NULL },
+
+ { ngx_string("min_delete_depth"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_dav_loc_conf_t, min_delete_depth),
+ NULL },
+
+ { ngx_string("dav_access"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
+ ngx_conf_set_access_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_dav_loc_conf_t, access),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_dav_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_dav_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_dav_create_loc_conf, /* create location configuration */
+ ngx_http_dav_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_dav_module = {
+ NGX_MODULE_V1,
+ &ngx_http_dav_module_ctx, /* module context */
+ ngx_http_dav_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_dav_handler(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_http_dav_loc_conf_t *dlcf;
+
+ dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
+
+ if (!(r->method & dlcf->methods)) {
+ return NGX_DECLINED;
+ }
+
+ switch (r->method) {
+
+ case NGX_HTTP_PUT:
+
+ if (r->uri.data[r->uri.len - 1] == '/') {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "cannot PUT to a collection");
+ return NGX_HTTP_CONFLICT;
+ }
+
+ if (r->headers_in.content_range) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "PUT with range is unsupported");
+ return NGX_HTTP_NOT_IMPLEMENTED;
+ }
+
+ r->request_body_in_file_only = 1;
+ r->request_body_in_persistent_file = 1;
+ r->request_body_in_clean_file = 1;
+ r->request_body_file_group_access = 1;
+ r->request_body_file_log_level = 0;
+
+ rc = ngx_http_read_client_request_body(r, ngx_http_dav_put_handler);
+
+ if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+ return rc;
+ }
+
+ return NGX_DONE;
+
+ case NGX_HTTP_DELETE:
+
+ return ngx_http_dav_delete_handler(r);
+
+ case NGX_HTTP_MKCOL:
+
+ return ngx_http_dav_mkcol_handler(r, dlcf);
+
+ case NGX_HTTP_COPY:
+
+ return ngx_http_dav_copy_move_handler(r);
+
+ case NGX_HTTP_MOVE:
+
+ return ngx_http_dav_copy_move_handler(r);
+ }
+
+ return NGX_DECLINED;
+}
+
+
+static void
+ngx_http_dav_put_handler(ngx_http_request_t *r)
+{
+ size_t root;
+ time_t date;
+ ngx_str_t *temp, path;
+ ngx_uint_t status;
+ ngx_file_info_t fi;
+ ngx_ext_rename_file_t ext;
+ ngx_http_dav_loc_conf_t *dlcf;
+
+ if (r->request_body == NULL || r->request_body->temp_file == NULL) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ path.len--;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http put filename: \"%s\"", path.data);
+
+ temp = &r->request_body->temp_file->file.name;
+
+ if (ngx_file_info(path.data, &fi) == NGX_FILE_ERROR) {
+ status = NGX_HTTP_CREATED;
+
+ } else {
+ status = NGX_HTTP_NO_CONTENT;
+
+ if (ngx_is_dir(&fi)) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_EISDIR,
+ "\"%s\" could not be created", path.data);
+
+ if (ngx_delete_file(temp->data) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+ ngx_delete_file_n " \"%s\" failed",
+ temp->data);
+ }
+
+ ngx_http_finalize_request(r, NGX_HTTP_CONFLICT);
+ return;
+ }
+ }
+
+ dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
+
+ ext.access = dlcf->access;
+ ext.path_access = dlcf->access;
+ ext.time = -1;
+ ext.create_path = dlcf->create_full_put_path;
+ ext.delete_file = 1;
+ ext.log = r->connection->log;
+
+ if (r->headers_in.date) {
+ date = ngx_parse_http_time(r->headers_in.date->value.data,
+ r->headers_in.date->value.len);
+
+ if (date != NGX_ERROR) {
+ ext.time = date;
+ ext.fd = r->request_body->temp_file->file.fd;
+ }
+ }
+
+ if (ngx_ext_rename_file(temp, &path, &ext) != NGX_OK) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ if (status == NGX_HTTP_CREATED) {
+ if (ngx_http_dav_location(r, path.data) != NGX_OK) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ r->headers_out.content_length_n = 0;
+ }
+
+ r->headers_out.status = status;
+ r->header_only = 1;
+
+ ngx_http_finalize_request(r, ngx_http_send_header(r));
+ return;
+}
+
+
+static ngx_int_t
+ngx_http_dav_delete_handler(ngx_http_request_t *r)
+{
+ size_t root;
+ ngx_err_t err;
+ ngx_int_t rc, depth;
+ ngx_uint_t i, d, dir;
+ ngx_str_t path;
+ ngx_file_info_t fi;
+ ngx_http_dav_loc_conf_t *dlcf;
+
+ if (r->headers_in.content_length_n > 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "DELETE with body is unsupported");
+ return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
+ }
+
+ dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
+
+ if (dlcf->min_delete_depth) {
+ d = 0;
+
+ for (i = 0; i < r->uri.len; /* void */) {
+ if (r->uri.data[i++] == '/') {
+ if (++d >= dlcf->min_delete_depth && i < r->uri.len) {
+ goto ok;
+ }
+ }
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "insufficient URI depth:%i to DELETE", d);
+ return NGX_HTTP_CONFLICT;
+ }
+
+ok:
+
+ if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http delete filename: \"%s\"", path.data);
+
+ if (ngx_link_info(path.data, &fi) == NGX_FILE_ERROR) {
+ err = ngx_errno;
+
+ rc = (err == NGX_ENOTDIR) ? NGX_HTTP_CONFLICT : NGX_HTTP_NOT_FOUND;
+
+ return ngx_http_dav_error(r->connection->log, err,
+ rc, ngx_link_info_n, path.data);
+ }
+
+ if (ngx_is_dir(&fi)) {
+
+ if (r->uri.data[r->uri.len - 1] != '/') {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_EISDIR,
+ "DELETE \"%s\" failed", path.data);
+ return NGX_HTTP_CONFLICT;
+ }
+
+ depth = ngx_http_dav_depth(r, NGX_HTTP_DAV_INFINITY_DEPTH);
+
+ if (depth != NGX_HTTP_DAV_INFINITY_DEPTH) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "\"Depth\" header must be infinity");
+ return NGX_HTTP_BAD_REQUEST;
+ }
+
+ path.len -= 2; /* omit "/\0" */
+
+ dir = 1;
+
+ } else {
+
+ /*
+ * we do not need to test (r->uri.data[r->uri.len - 1] == '/')
+ * because ngx_link_info("/file/") returned NGX_ENOTDIR above
+ */
+
+ depth = ngx_http_dav_depth(r, 0);
+
+ if (depth != 0 && depth != NGX_HTTP_DAV_INFINITY_DEPTH) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "\"Depth\" header must be 0 or infinity");
+ return NGX_HTTP_BAD_REQUEST;
+ }
+
+ dir = 0;
+ }
+
+ rc = ngx_http_dav_delete_path(r, &path, dir);
+
+ if (rc == NGX_OK) {
+ return NGX_HTTP_NO_CONTENT;
+ }
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_dav_delete_path(ngx_http_request_t *r, ngx_str_t *path, ngx_uint_t dir)
+{
+ char *failed;
+ ngx_tree_ctx_t tree;
+
+ if (dir) {
+
+ tree.init_handler = NULL;
+ tree.file_handler = ngx_http_dav_delete_file;
+ tree.pre_tree_handler = ngx_http_dav_noop;
+ tree.post_tree_handler = ngx_http_dav_delete_dir;
+ tree.spec_handler = ngx_http_dav_delete_file;
+ tree.data = NULL;
+ tree.alloc = 0;
+ tree.log = r->connection->log;
+
+ /* TODO: 207 */
+
+ if (ngx_walk_tree(&tree, path) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_delete_dir(path->data) != NGX_FILE_ERROR) {
+ return NGX_OK;
+ }
+
+ failed = ngx_delete_dir_n;
+
+ } else {
+
+ if (ngx_delete_file(path->data) != NGX_FILE_ERROR) {
+ return NGX_OK;
+ }
+
+ failed = ngx_delete_file_n;
+ }
+
+ return ngx_http_dav_error(r->connection->log, ngx_errno,
+ NGX_HTTP_NOT_FOUND, failed, path->data);
+}
+
+
+static ngx_int_t
+ngx_http_dav_delete_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "http delete dir: \"%s\"", path->data);
+
+ if (ngx_delete_dir(path->data) == NGX_FILE_ERROR) {
+
+ /* TODO: add to 207 */
+
+ (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_delete_dir_n,
+ path->data);
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "http delete file: \"%s\"", path->data);
+
+ if (ngx_delete_file(path->data) == NGX_FILE_ERROR) {
+
+ /* TODO: add to 207 */
+
+ (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_delete_file_n,
+ path->data);
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_mkcol_handler(ngx_http_request_t *r, ngx_http_dav_loc_conf_t *dlcf)
+{
+ u_char *p;
+ size_t root;
+ ngx_str_t path;
+
+ if (r->headers_in.content_length_n > 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "MKCOL with body is unsupported");
+ return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
+ }
+
+ if (r->uri.data[r->uri.len - 1] != '/') {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "MKCOL can create a collection only");
+ return NGX_HTTP_CONFLICT;
+ }
+
+ p = ngx_http_map_uri_to_path(r, &path, &root, 0);
+ if (p == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ *(p - 1) = '\0';
+ r->uri.len--;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http mkcol path: \"%s\"", path.data);
+
+ if (ngx_create_dir(path.data, ngx_dir_access(dlcf->access))
+ != NGX_FILE_ERROR)
+ {
+ if (ngx_http_dav_location(r, path.data) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ return NGX_HTTP_CREATED;
+ }
+
+ return ngx_http_dav_error(r->connection->log, ngx_errno,
+ NGX_HTTP_CONFLICT, ngx_create_dir_n, path.data);
+}
+
+
+static ngx_int_t
+ngx_http_dav_copy_move_handler(ngx_http_request_t *r)
+{
+ u_char *p, *host, *last, ch;
+ size_t len, root;
+ ngx_err_t err;
+ ngx_int_t rc, depth;
+ ngx_uint_t overwrite, slash, dir, flags;
+ ngx_str_t path, uri, duri, args;
+ ngx_tree_ctx_t tree;
+ ngx_copy_file_t cf;
+ ngx_file_info_t fi;
+ ngx_table_elt_t *dest, *over;
+ ngx_ext_rename_file_t ext;
+ ngx_http_dav_copy_ctx_t copy;
+ ngx_http_dav_loc_conf_t *dlcf;
+
+ if (r->headers_in.content_length_n > 0) {
+ return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
+ }
+
+ dest = r->headers_in.destination;
+
+ if (dest == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client sent no \"Destination\" header");
+ return NGX_HTTP_BAD_REQUEST;
+ }
+
+ p = dest->value.data;
+ /* there is always '\0' even after empty header value */
+ if (p[0] == '/') {
+ last = p + dest->value.len;
+ goto destination_done;
+ }
+
+ len = r->headers_in.server.len;
+
+ if (len == 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client sent no \"Host\" header");
+ return NGX_HTTP_BAD_REQUEST;
+ }
+
+#if (NGX_HTTP_SSL)
+
+ if (r->connection->ssl) {
+ if (ngx_strncmp(dest->value.data, "https://", sizeof("https://") - 1)
+ != 0)
+ {
+ goto invalid_destination;
+ }
+
+ host = dest->value.data + sizeof("https://") - 1;
+
+ } else
+#endif
+ {
+ if (ngx_strncmp(dest->value.data, "http://", sizeof("http://") - 1)
+ != 0)
+ {
+ goto invalid_destination;
+ }
+
+ host = dest->value.data + sizeof("http://") - 1;
+ }
+
+ if (ngx_strncmp(host, r->headers_in.server.data, len) != 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "\"Destination\" URI \"%V\" is handled by "
+ "different repository than the source URI",
+ &dest->value);
+ return NGX_HTTP_BAD_REQUEST;
+ }
+
+ last = dest->value.data + dest->value.len;
+
+ for (p = host + len; p < last; p++) {
+ if (*p == '/') {
+ goto destination_done;
+ }
+ }
+
+invalid_destination:
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client sent invalid \"Destination\" header: \"%V\"",
+ &dest->value);
+ return NGX_HTTP_BAD_REQUEST;
+
+destination_done:
+
+ duri.len = last - p;
+ duri.data = p;
+ flags = NGX_HTTP_LOG_UNSAFE;
+
+ if (ngx_http_parse_unsafe_uri(r, &duri, &args, &flags) != NGX_OK) {
+ goto invalid_destination;
+ }
+
+ if ((r->uri.data[r->uri.len - 1] == '/' && *(last - 1) != '/')
+ || (r->uri.data[r->uri.len - 1] != '/' && *(last - 1) == '/'))
+ {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "both URI \"%V\" and \"Destination\" URI \"%V\" "
+ "should be either collections or non-collections",
+ &r->uri, &dest->value);
+ return NGX_HTTP_CONFLICT;
+ }
+
+ depth = ngx_http_dav_depth(r, NGX_HTTP_DAV_INFINITY_DEPTH);
+
+ if (depth != NGX_HTTP_DAV_INFINITY_DEPTH) {
+
+ if (r->method == NGX_HTTP_COPY) {
+ if (depth != 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "\"Depth\" header must be 0 or infinity");
+ return NGX_HTTP_BAD_REQUEST;
+ }
+
+ } else {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "\"Depth\" header must be infinity");
+ return NGX_HTTP_BAD_REQUEST;
+ }
+ }
+
+ over = r->headers_in.overwrite;
+
+ if (over) {
+ if (over->value.len == 1) {
+ ch = over->value.data[0];
+
+ if (ch == 'T' || ch == 't') {
+ overwrite = 1;
+ goto overwrite_done;
+ }
+
+ if (ch == 'F' || ch == 'f') {
+ overwrite = 0;
+ goto overwrite_done;
+ }
+
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client sent invalid \"Overwrite\" header: \"%V\"",
+ &over->value);
+ return NGX_HTTP_BAD_REQUEST;
+ }
+
+ overwrite = 1;
+
+overwrite_done:
+
+ if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http copy from: \"%s\"", path.data);
+
+ uri = r->uri;
+ r->uri = duri;
+
+ if (ngx_http_map_uri_to_path(r, &copy.path, &root, 0) == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ r->uri = uri;
+
+ copy.path.len--; /* omit "\0" */
+
+ if (copy.path.data[copy.path.len - 1] == '/') {
+ slash = 1;
+ copy.path.len--;
+ copy.path.data[copy.path.len] = '\0';
+
+ } else {
+ slash = 0;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http copy to: \"%s\"", copy.path.data);
+
+ if (ngx_link_info(copy.path.data, &fi) == NGX_FILE_ERROR) {
+ err = ngx_errno;
+
+ if (err != NGX_ENOENT) {
+ return ngx_http_dav_error(r->connection->log, err,
+ NGX_HTTP_NOT_FOUND, ngx_link_info_n,
+ copy.path.data);
+ }
+
+ /* destination does not exist */
+
+ overwrite = 0;
+ dir = 0;
+
+ } else {
+
+ /* destination exists */
+
+ if (ngx_is_dir(&fi) && !slash) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "\"%V\" could not be %Ved to collection \"%V\"",
+ &r->uri, &r->method_name, &dest->value);
+ return NGX_HTTP_CONFLICT;
+ }
+
+ if (!overwrite) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_EEXIST,
+ "\"%s\" could not be created", copy.path.data);
+ return NGX_HTTP_PRECONDITION_FAILED;
+ }
+
+ dir = ngx_is_dir(&fi);
+ }
+
+ if (ngx_link_info(path.data, &fi) == NGX_FILE_ERROR) {
+ return ngx_http_dav_error(r->connection->log, ngx_errno,
+ NGX_HTTP_NOT_FOUND, ngx_link_info_n,
+ path.data);
+ }
+
+ if (ngx_is_dir(&fi)) {
+
+ if (r->uri.data[r->uri.len - 1] != '/') {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "\"%V\" is collection", &r->uri);
+ return NGX_HTTP_BAD_REQUEST;
+ }
+
+ if (overwrite) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http delete: \"%s\"", copy.path.data);
+
+ rc = ngx_http_dav_delete_path(r, &copy.path, dir);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+ }
+ }
+
+ if (ngx_is_dir(&fi)) {
+
+ path.len -= 2; /* omit "/\0" */
+
+ if (r->method == NGX_HTTP_MOVE) {
+ if (ngx_rename_file(path.data, copy.path.data) != NGX_FILE_ERROR) {
+ return NGX_HTTP_CREATED;
+ }
+ }
+
+ if (ngx_create_dir(copy.path.data, ngx_file_access(&fi))
+ == NGX_FILE_ERROR)
+ {
+ return ngx_http_dav_error(r->connection->log, ngx_errno,
+ NGX_HTTP_NOT_FOUND,
+ ngx_create_dir_n, copy.path.data);
+ }
+
+ copy.len = path.len;
+
+ tree.init_handler = NULL;
+ tree.file_handler = ngx_http_dav_copy_tree_file;
+ tree.pre_tree_handler = ngx_http_dav_copy_dir;
+ tree.post_tree_handler = ngx_http_dav_copy_dir_time;
+ tree.spec_handler = ngx_http_dav_noop;
+ tree.data = &copy;
+ tree.alloc = 0;
+ tree.log = r->connection->log;
+
+ if (ngx_walk_tree(&tree, &path) == NGX_OK) {
+
+ if (r->method == NGX_HTTP_MOVE) {
+ rc = ngx_http_dav_delete_path(r, &path, 1);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+ }
+
+ return NGX_HTTP_CREATED;
+ }
+
+ } else {
+
+ if (r->method == NGX_HTTP_MOVE) {
+
+ dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
+
+ ext.access = 0;
+ ext.path_access = dlcf->access;
+ ext.time = -1;
+ ext.create_path = 1;
+ ext.delete_file = 0;
+ ext.log = r->connection->log;
+
+ if (ngx_ext_rename_file(&path, &copy.path, &ext) == NGX_OK) {
+ return NGX_HTTP_NO_CONTENT;
+ }
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module);
+
+ cf.size = ngx_file_size(&fi);
+ cf.buf_size = 0;
+ cf.access = dlcf->access;
+ cf.time = ngx_file_mtime(&fi);
+ cf.log = r->connection->log;
+
+ if (ngx_copy_file(path.data, copy.path.data, &cf) == NGX_OK) {
+ return NGX_HTTP_NO_CONTENT;
+ }
+ }
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_dav_copy_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+ u_char *p, *dir;
+ size_t len;
+ ngx_http_dav_copy_ctx_t *copy;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "http copy dir: \"%s\"", path->data);
+
+ copy = ctx->data;
+
+ len = copy->path.len + path->len;
+
+ dir = ngx_alloc(len + 1, ctx->log);
+ if (dir == NULL) {
+ return NGX_ABORT;
+ }
+
+ p = ngx_cpymem(dir, copy->path.data, copy->path.len);
+ (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "http copy dir to: \"%s\"", dir);
+
+ if (ngx_create_dir(dir, ngx_dir_access(ctx->access)) == NGX_FILE_ERROR) {
+ (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_create_dir_n,
+ dir);
+ }
+
+ ngx_free(dir);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_copy_dir_time(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+ u_char *p, *dir;
+ size_t len;
+ ngx_http_dav_copy_ctx_t *copy;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "http copy dir time: \"%s\"", path->data);
+
+ copy = ctx->data;
+
+ len = copy->path.len + path->len;
+
+ dir = ngx_alloc(len + 1, ctx->log);
+ if (dir == NULL) {
+ return NGX_ABORT;
+ }
+
+ p = ngx_cpymem(dir, copy->path.data, copy->path.len);
+ (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "http copy dir time to: \"%s\"", dir);
+
+#if (NGX_WIN32)
+ {
+ ngx_fd_t fd;
+
+ fd = ngx_open_file(dir, NGX_FILE_RDWR, NGX_FILE_OPEN, 0);
+
+ if (fd == NGX_INVALID_FILE) {
+ (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_open_file_n, dir);
+ goto failed;
+ }
+
+ if (ngx_set_file_time(NULL, fd, ctx->mtime) != NGX_OK) {
+ ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno,
+ ngx_set_file_time_n " \"%s\" failed", dir);
+ }
+
+ if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", dir);
+ }
+ }
+
+failed:
+
+#else
+
+ if (ngx_set_file_time(dir, 0, ctx->mtime) != NGX_OK) {
+ ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno,
+ ngx_set_file_time_n " \"%s\" failed", dir);
+ }
+
+#endif
+
+ ngx_free(dir);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_copy_tree_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+ u_char *p, *file;
+ size_t len;
+ ngx_copy_file_t cf;
+ ngx_http_dav_copy_ctx_t *copy;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "http copy file: \"%s\"", path->data);
+
+ copy = ctx->data;
+
+ len = copy->path.len + path->len;
+
+ file = ngx_alloc(len + 1, ctx->log);
+ if (file == NULL) {
+ return NGX_ABORT;
+ }
+
+ p = ngx_cpymem(file, copy->path.data, copy->path.len);
+ (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "http copy file to: \"%s\"", file);
+
+ cf.size = ctx->size;
+ cf.buf_size = 0;
+ cf.access = ctx->access;
+ cf.time = ctx->mtime;
+ cf.log = ctx->log;
+
+ (void) ngx_copy_file(path->data, file, &cf);
+
+ ngx_free(file);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_depth(ngx_http_request_t *r, ngx_int_t dflt)
+{
+ ngx_table_elt_t *depth;
+
+ depth = r->headers_in.depth;
+
+ if (depth == NULL) {
+ return dflt;
+ }
+
+ if (depth->value.len == 1) {
+
+ if (depth->value.data[0] == '0') {
+ return 0;
+ }
+
+ if (depth->value.data[0] == '1') {
+ return 1;
+ }
+
+ } else {
+
+ if (depth->value.len == sizeof("infinity") - 1
+ && ngx_strcmp(depth->value.data, "infinity") == 0)
+ {
+ return NGX_HTTP_DAV_INFINITY_DEPTH;
+ }
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client sent invalid \"Depth\" header: \"%V\"",
+ &depth->value);
+
+ return NGX_HTTP_DAV_INVALID_DEPTH;
+}
+
+
+static ngx_int_t
+ngx_http_dav_error(ngx_log_t *log, ngx_err_t err, ngx_int_t not_found,
+ char *failed, u_char *path)
+{
+ ngx_int_t rc;
+ ngx_uint_t level;
+
+ if (err == NGX_ENOENT || err == NGX_ENOTDIR || err == NGX_ENAMETOOLONG) {
+ level = NGX_LOG_ERR;
+ rc = not_found;
+
+ } else if (err == NGX_EACCES || err == NGX_EPERM) {
+ level = NGX_LOG_ERR;
+ rc = NGX_HTTP_FORBIDDEN;
+
+ } else if (err == NGX_EEXIST) {
+ level = NGX_LOG_ERR;
+ rc = NGX_HTTP_NOT_ALLOWED;
+
+ } else if (err == NGX_ENOSPC) {
+ level = NGX_LOG_CRIT;
+ rc = NGX_HTTP_INSUFFICIENT_STORAGE;
+
+ } else {
+ level = NGX_LOG_CRIT;
+ rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_log_error(level, log, err, "%s \"%s\" failed", failed, path);
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_dav_location(ngx_http_request_t *r, u_char *path)
+{
+ u_char *location;
+ ngx_http_core_loc_conf_t *clcf;
+
+ r->headers_out.location = ngx_list_push(&r->headers_out.headers);
+ if (r->headers_out.location == NULL) {
+ return NGX_ERROR;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (!clcf->alias && clcf->root_lengths == NULL) {
+ location = path + clcf->root.len;
+
+ } else {
+ location = ngx_pnalloc(r->pool, r->uri.len);
+ if (location == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(location, r->uri.data, r->uri.len);
+ }
+
+ r->headers_out.location->hash = 1;
+ ngx_str_set(&r->headers_out.location->key, "Location");
+ r->headers_out.location->value.len = r->uri.len;
+ r->headers_out.location->value.data = location;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_dav_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_dav_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_dav_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->methods = 0;
+ */
+
+ conf->min_delete_depth = NGX_CONF_UNSET_UINT;
+ conf->access = NGX_CONF_UNSET_UINT;
+ conf->create_full_put_path = NGX_CONF_UNSET;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_dav_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_dav_loc_conf_t *prev = parent;
+ ngx_http_dav_loc_conf_t *conf = child;
+
+ ngx_conf_merge_bitmask_value(conf->methods, prev->methods,
+ (NGX_CONF_BITMASK_SET|NGX_HTTP_DAV_OFF));
+
+ ngx_conf_merge_uint_value(conf->min_delete_depth,
+ prev->min_delete_depth, 0);
+
+ ngx_conf_merge_uint_value(conf->access, prev->access, 0600);
+
+ ngx_conf_merge_value(conf->create_full_put_path,
+ prev->create_full_put_path, 0);
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_dav_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_dav_handler;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_degradation_module.c b/app/nginx/src/http/modules/ngx_http_degradation_module.c
new file mode 100644
index 0000000..b9c65cd
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_degradation_module.c
@@ -0,0 +1,243 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ size_t sbrk_size;
+} ngx_http_degradation_main_conf_t;
+
+
+typedef struct {
+ ngx_uint_t degrade;
+} ngx_http_degradation_loc_conf_t;
+
+
+static ngx_conf_enum_t ngx_http_degrade[] = {
+ { ngx_string("204"), 204 },
+ { ngx_string("444"), 444 },
+ { ngx_null_string, 0 }
+};
+
+
+static void *ngx_http_degradation_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_degradation_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_degradation_merge_loc_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_http_degradation(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static ngx_int_t ngx_http_degradation_init(ngx_conf_t *cf);
+
+
+static ngx_command_t ngx_http_degradation_commands[] = {
+
+ { ngx_string("degradation"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+ ngx_http_degradation,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("degrade"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_degradation_loc_conf_t, degrade),
+ &ngx_http_degrade },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_degradation_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_degradation_init, /* postconfiguration */
+
+ ngx_http_degradation_create_main_conf, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_degradation_create_loc_conf, /* create location configuration */
+ ngx_http_degradation_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_degradation_module = {
+ NGX_MODULE_V1,
+ &ngx_http_degradation_module_ctx, /* module context */
+ ngx_http_degradation_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_degradation_handler(ngx_http_request_t *r)
+{
+ ngx_http_degradation_loc_conf_t *dlcf;
+
+ dlcf = ngx_http_get_module_loc_conf(r, ngx_http_degradation_module);
+
+ if (dlcf->degrade && ngx_http_degraded(r)) {
+ return dlcf->degrade;
+ }
+
+ return NGX_DECLINED;
+}
+
+
+ngx_uint_t
+ngx_http_degraded(ngx_http_request_t *r)
+{
+ time_t now;
+ ngx_uint_t log;
+ static size_t sbrk_size;
+ static time_t sbrk_time;
+ ngx_http_degradation_main_conf_t *dmcf;
+
+ dmcf = ngx_http_get_module_main_conf(r, ngx_http_degradation_module);
+
+ if (dmcf->sbrk_size) {
+
+ log = 0;
+ now = ngx_time();
+
+ /* lock mutex */
+
+ if (now != sbrk_time) {
+
+ /*
+ * ELF/i386 is loaded at 0x08000000, 128M
+ * ELF/amd64 is loaded at 0x00400000, 4M
+ *
+ * use a function address to subtract the loading address
+ */
+
+ sbrk_size = (size_t) sbrk(0) - ((uintptr_t) ngx_palloc & ~0x3FFFFF);
+ sbrk_time = now;
+ log = 1;
+ }
+
+ /* unlock mutex */
+
+ if (sbrk_size >= dmcf->sbrk_size) {
+ if (log) {
+ ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+ "degradation sbrk:%uzM",
+ sbrk_size / (1024 * 1024));
+ }
+
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+static void *
+ngx_http_degradation_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_degradation_main_conf_t *dmcf;
+
+ dmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_degradation_main_conf_t));
+ if (dmcf == NULL) {
+ return NULL;
+ }
+
+ return dmcf;
+}
+
+
+static void *
+ngx_http_degradation_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_degradation_loc_conf_t *conf;
+
+ conf = ngx_palloc(cf->pool, sizeof(ngx_http_degradation_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->degrade = NGX_CONF_UNSET_UINT;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_degradation_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_degradation_loc_conf_t *prev = parent;
+ ngx_http_degradation_loc_conf_t *conf = child;
+
+ ngx_conf_merge_uint_value(conf->degrade, prev->degrade, 0);
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_degradation(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_degradation_main_conf_t *dmcf = conf;
+
+ ngx_str_t *value, s;
+
+ value = cf->args->elts;
+
+ if (ngx_strncmp(value[1].data, "sbrk=", 5) == 0) {
+
+ s.len = value[1].len - 5;
+ s.data = value[1].data + 5;
+
+ dmcf->sbrk_size = ngx_parse_size(&s);
+ if (dmcf->sbrk_size == (size_t) NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid sbrk size \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[1]);
+
+ return NGX_CONF_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_degradation_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_degradation_handler;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_empty_gif_module.c b/app/nginx/src/http/modules/ngx_http_empty_gif_module.c
new file mode 100644
index 0000000..04114dc
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_empty_gif_module.c
@@ -0,0 +1,140 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static char *ngx_http_empty_gif(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+static ngx_command_t ngx_http_empty_gif_commands[] = {
+
+ { ngx_string("empty_gif"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
+ ngx_http_empty_gif,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+/* the minimal single pixel transparent GIF, 43 bytes */
+
+static u_char ngx_empty_gif[] = {
+
+ 'G', 'I', 'F', '8', '9', 'a', /* header */
+
+ /* logical screen descriptor */
+ 0x01, 0x00, /* logical screen width */
+ 0x01, 0x00, /* logical screen height */
+ 0x80, /* global 1-bit color table */
+ 0x01, /* background color #1 */
+ 0x00, /* no aspect ratio */
+
+ /* global color table */
+ 0x00, 0x00, 0x00, /* #0: black */
+ 0xff, 0xff, 0xff, /* #1: white */
+
+ /* graphic control extension */
+ 0x21, /* extension introducer */
+ 0xf9, /* graphic control label */
+ 0x04, /* block size */
+ 0x01, /* transparent color is given, */
+ /* no disposal specified, */
+ /* user input is not expected */
+ 0x00, 0x00, /* delay time */
+ 0x01, /* transparent color #1 */
+ 0x00, /* block terminator */
+
+ /* image descriptor */
+ 0x2c, /* image separator */
+ 0x00, 0x00, /* image left position */
+ 0x00, 0x00, /* image top position */
+ 0x01, 0x00, /* image width */
+ 0x01, 0x00, /* image height */
+ 0x00, /* no local color table, no interlaced */
+
+ /* table based image data */
+ 0x02, /* LZW minimum code size, */
+ /* must be at least 2-bit */
+ 0x02, /* block size */
+ 0x4c, 0x01, /* compressed bytes 01_001_100, 0000000_1 */
+ /* 100: clear code */
+ /* 001: 1 */
+ /* 101: end of information code */
+ 0x00, /* block terminator */
+
+ 0x3B /* trailer */
+};
+
+
+static ngx_http_module_t ngx_http_empty_gif_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_empty_gif_module = {
+ NGX_MODULE_V1,
+ &ngx_http_empty_gif_module_ctx, /* module context */
+ ngx_http_empty_gif_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_http_gif_type = ngx_string("image/gif");
+
+
+static ngx_int_t
+ngx_http_empty_gif_handler(ngx_http_request_t *r)
+{
+ ngx_http_complex_value_t cv;
+
+ if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
+ return NGX_HTTP_NOT_ALLOWED;
+ }
+
+ ngx_memzero(&cv, sizeof(ngx_http_complex_value_t));
+
+ cv.value.len = sizeof(ngx_empty_gif);
+ cv.value.data = ngx_empty_gif;
+ r->headers_out.last_modified_time = 23349600;
+
+ return ngx_http_send_response(r, NGX_HTTP_OK, &ngx_http_gif_type, &cv);
+}
+
+
+static char *
+ngx_http_empty_gif(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf;
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+ clcf->handler = ngx_http_empty_gif_handler;
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_fastcgi_module.c b/app/nginx/src/http/modules/ngx_http_fastcgi_module.c
new file mode 100644
index 0000000..06c1973
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_fastcgi_module.c
@@ -0,0 +1,3788 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_array_t caches; /* ngx_http_file_cache_t * */
+} ngx_http_fastcgi_main_conf_t;
+
+
+typedef struct {
+ ngx_array_t *flushes;
+ ngx_array_t *lengths;
+ ngx_array_t *values;
+ ngx_uint_t number;
+ ngx_hash_t hash;
+} ngx_http_fastcgi_params_t;
+
+
+typedef struct {
+ ngx_http_upstream_conf_t upstream;
+
+ ngx_str_t index;
+
+ ngx_http_fastcgi_params_t params;
+#if (NGX_HTTP_CACHE)
+ ngx_http_fastcgi_params_t params_cache;
+#endif
+
+ ngx_array_t *params_source;
+ ngx_array_t *catch_stderr;
+
+ ngx_array_t *fastcgi_lengths;
+ ngx_array_t *fastcgi_values;
+
+ ngx_flag_t keep_conn;
+
+#if (NGX_HTTP_CACHE)
+ ngx_http_complex_value_t cache_key;
+#endif
+
+#if (NGX_PCRE)
+ ngx_regex_t *split_regex;
+ ngx_str_t split_name;
+#endif
+} ngx_http_fastcgi_loc_conf_t;
+
+
+typedef enum {
+ ngx_http_fastcgi_st_version = 0,
+ ngx_http_fastcgi_st_type,
+ ngx_http_fastcgi_st_request_id_hi,
+ ngx_http_fastcgi_st_request_id_lo,
+ ngx_http_fastcgi_st_content_length_hi,
+ ngx_http_fastcgi_st_content_length_lo,
+ ngx_http_fastcgi_st_padding_length,
+ ngx_http_fastcgi_st_reserved,
+ ngx_http_fastcgi_st_data,
+ ngx_http_fastcgi_st_padding
+} ngx_http_fastcgi_state_e;
+
+
+typedef struct {
+ u_char *start;
+ u_char *end;
+} ngx_http_fastcgi_split_part_t;
+
+
+typedef struct {
+ ngx_http_fastcgi_state_e state;
+ u_char *pos;
+ u_char *last;
+ ngx_uint_t type;
+ size_t length;
+ size_t padding;
+
+ ngx_chain_t *free;
+ ngx_chain_t *busy;
+
+ unsigned fastcgi_stdout:1;
+ unsigned large_stderr:1;
+ unsigned header_sent:1;
+
+ ngx_array_t *split_parts;
+
+ ngx_str_t script_name;
+ ngx_str_t path_info;
+} ngx_http_fastcgi_ctx_t;
+
+
+#define NGX_HTTP_FASTCGI_RESPONDER 1
+
+#define NGX_HTTP_FASTCGI_KEEP_CONN 1
+
+#define NGX_HTTP_FASTCGI_BEGIN_REQUEST 1
+#define NGX_HTTP_FASTCGI_ABORT_REQUEST 2
+#define NGX_HTTP_FASTCGI_END_REQUEST 3
+#define NGX_HTTP_FASTCGI_PARAMS 4
+#define NGX_HTTP_FASTCGI_STDIN 5
+#define NGX_HTTP_FASTCGI_STDOUT 6
+#define NGX_HTTP_FASTCGI_STDERR 7
+#define NGX_HTTP_FASTCGI_DATA 8
+
+
+typedef struct {
+ u_char version;
+ u_char type;
+ u_char request_id_hi;
+ u_char request_id_lo;
+ u_char content_length_hi;
+ u_char content_length_lo;
+ u_char padding_length;
+ u_char reserved;
+} ngx_http_fastcgi_header_t;
+
+
+typedef struct {
+ u_char role_hi;
+ u_char role_lo;
+ u_char flags;
+ u_char reserved[5];
+} ngx_http_fastcgi_begin_request_t;
+
+
+typedef struct {
+ u_char version;
+ u_char type;
+ u_char request_id_hi;
+ u_char request_id_lo;
+} ngx_http_fastcgi_header_small_t;
+
+
+typedef struct {
+ ngx_http_fastcgi_header_t h0;
+ ngx_http_fastcgi_begin_request_t br;
+ ngx_http_fastcgi_header_small_t h1;
+} ngx_http_fastcgi_request_start_t;
+
+
+static ngx_int_t ngx_http_fastcgi_eval(ngx_http_request_t *r,
+ ngx_http_fastcgi_loc_conf_t *flcf);
+#if (NGX_HTTP_CACHE)
+static ngx_int_t ngx_http_fastcgi_create_key(ngx_http_request_t *r);
+#endif
+static ngx_int_t ngx_http_fastcgi_create_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_fastcgi_reinit_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_fastcgi_body_output_filter(void *data,
+ ngx_chain_t *in);
+static ngx_int_t ngx_http_fastcgi_process_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_fastcgi_input_filter_init(void *data);
+static ngx_int_t ngx_http_fastcgi_input_filter(ngx_event_pipe_t *p,
+ ngx_buf_t *buf);
+static ngx_int_t ngx_http_fastcgi_non_buffered_filter(void *data,
+ ssize_t bytes);
+static ngx_int_t ngx_http_fastcgi_process_record(ngx_http_request_t *r,
+ ngx_http_fastcgi_ctx_t *f);
+static void ngx_http_fastcgi_abort_request(ngx_http_request_t *r);
+static void ngx_http_fastcgi_finalize_request(ngx_http_request_t *r,
+ ngx_int_t rc);
+
+static ngx_int_t ngx_http_fastcgi_add_variables(ngx_conf_t *cf);
+static void *ngx_http_fastcgi_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_fastcgi_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_fastcgi_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_fastcgi_init_params(ngx_conf_t *cf,
+ ngx_http_fastcgi_loc_conf_t *conf, ngx_http_fastcgi_params_t *params,
+ ngx_keyval_t *default_params);
+
+static ngx_int_t ngx_http_fastcgi_script_name_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_fastcgi_path_info_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_http_fastcgi_ctx_t *ngx_http_fastcgi_split(ngx_http_request_t *r,
+ ngx_http_fastcgi_loc_conf_t *flcf);
+
+static char *ngx_http_fastcgi_pass(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_fastcgi_split_path_info(ngx_conf_t *cf,
+ ngx_command_t *cmd, void *conf);
+static char *ngx_http_fastcgi_store(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+#if (NGX_HTTP_CACHE)
+static char *ngx_http_fastcgi_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_fastcgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+#endif
+
+static char *ngx_http_fastcgi_lowat_check(ngx_conf_t *cf, void *post,
+ void *data);
+
+
+static ngx_conf_post_t ngx_http_fastcgi_lowat_post =
+ { ngx_http_fastcgi_lowat_check };
+
+
+static ngx_conf_bitmask_t ngx_http_fastcgi_next_upstream_masks[] = {
+ { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
+ { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT },
+ { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER },
+ { ngx_string("non_idempotent"), NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT },
+ { ngx_string("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 },
+ { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 },
+ { ngx_string("http_403"), NGX_HTTP_UPSTREAM_FT_HTTP_403 },
+ { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 },
+ { ngx_string("http_429"), NGX_HTTP_UPSTREAM_FT_HTTP_429 },
+ { ngx_string("updating"), NGX_HTTP_UPSTREAM_FT_UPDATING },
+ { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF },
+ { ngx_null_string, 0 }
+};
+
+
+ngx_module_t ngx_http_fastcgi_module;
+
+
+static ngx_command_t ngx_http_fastcgi_commands[] = {
+
+ { ngx_string("fastcgi_pass"),
+ NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
+ ngx_http_fastcgi_pass,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("fastcgi_index"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, index),
+ NULL },
+
+ { ngx_string("fastcgi_split_path_info"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_fastcgi_split_path_info,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("fastcgi_store"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_fastcgi_store,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("fastcgi_store_access"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
+ ngx_conf_set_access_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.store_access),
+ NULL },
+
+ { ngx_string("fastcgi_buffering"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.buffering),
+ NULL },
+
+ { ngx_string("fastcgi_request_buffering"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.request_buffering),
+ NULL },
+
+ { ngx_string("fastcgi_ignore_client_abort"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.ignore_client_abort),
+ NULL },
+
+ { ngx_string("fastcgi_bind"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+ ngx_http_upstream_bind_set_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.local),
+ NULL },
+
+ { ngx_string("fastcgi_connect_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.connect_timeout),
+ NULL },
+
+ { ngx_string("fastcgi_send_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.send_timeout),
+ NULL },
+
+ { ngx_string("fastcgi_send_lowat"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.send_lowat),
+ &ngx_http_fastcgi_lowat_post },
+
+ { ngx_string("fastcgi_buffer_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.buffer_size),
+ NULL },
+
+ { ngx_string("fastcgi_pass_request_headers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.pass_request_headers),
+ NULL },
+
+ { ngx_string("fastcgi_pass_request_body"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.pass_request_body),
+ NULL },
+
+ { ngx_string("fastcgi_intercept_errors"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.intercept_errors),
+ NULL },
+
+ { ngx_string("fastcgi_read_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.read_timeout),
+ NULL },
+
+ { ngx_string("fastcgi_buffers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_conf_set_bufs_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.bufs),
+ NULL },
+
+ { ngx_string("fastcgi_busy_buffers_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.busy_buffers_size_conf),
+ NULL },
+
+ { ngx_string("fastcgi_force_ranges"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.force_ranges),
+ NULL },
+
+ { ngx_string("fastcgi_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_fastcgi_loc_conf_t, upstream.limit_rate),
+ NULL },
+
+#if (NGX_HTTP_CACHE)
+
+ { ngx_string("fastcgi_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_fastcgi_cache,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("fastcgi_cache_key"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_fastcgi_cache_key,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("fastcgi_cache_path"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE,
+ ngx_http_file_cache_set_slot,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_main_conf_t, caches),
+ &ngx_http_fastcgi_module },
+
+ { ngx_string("fastcgi_cache_bypass"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_set_predicate_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_bypass),
+ NULL },
+
+ { ngx_string("fastcgi_no_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_set_predicate_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.no_cache),
+ NULL },
+
+ { ngx_string("fastcgi_cache_valid"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_file_cache_valid_set_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_valid),
+ NULL },
+
+ { ngx_string("fastcgi_cache_min_uses"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_min_uses),
+ NULL },
+
+ { ngx_string("fastcgi_cache_max_range_offset"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_off_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_max_range_offset),
+ NULL },
+
+ { ngx_string("fastcgi_cache_use_stale"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_use_stale),
+ &ngx_http_fastcgi_next_upstream_masks },
+
+ { ngx_string("fastcgi_cache_methods"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_methods),
+ &ngx_http_upstream_cache_method_mask },
+
+ { ngx_string("fastcgi_cache_lock"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_lock),
+ NULL },
+
+ { ngx_string("fastcgi_cache_lock_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_lock_timeout),
+ NULL },
+
+ { ngx_string("fastcgi_cache_lock_age"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_lock_age),
+ NULL },
+
+ { ngx_string("fastcgi_cache_revalidate"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_revalidate),
+ NULL },
+
+ { ngx_string("fastcgi_cache_background_update"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_background_update),
+ NULL },
+
+#endif
+
+ { ngx_string("fastcgi_temp_path"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
+ ngx_conf_set_path_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.temp_path),
+ NULL },
+
+ { ngx_string("fastcgi_max_temp_file_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.max_temp_file_size_conf),
+ NULL },
+
+ { ngx_string("fastcgi_temp_file_write_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.temp_file_write_size_conf),
+ NULL },
+
+ { ngx_string("fastcgi_next_upstream"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.next_upstream),
+ &ngx_http_fastcgi_next_upstream_masks },
+
+ { ngx_string("fastcgi_next_upstream_tries"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.next_upstream_tries),
+ NULL },
+
+ { ngx_string("fastcgi_next_upstream_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.next_upstream_timeout),
+ NULL },
+
+ { ngx_string("fastcgi_param"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE23,
+ ngx_http_upstream_param_set_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, params_source),
+ NULL },
+
+ { ngx_string("fastcgi_pass_header"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.pass_headers),
+ NULL },
+
+ { ngx_string("fastcgi_hide_header"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.hide_headers),
+ NULL },
+
+ { ngx_string("fastcgi_ignore_headers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.ignore_headers),
+ &ngx_http_upstream_ignore_headers_masks },
+
+ { ngx_string("fastcgi_catch_stderr"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, catch_stderr),
+ NULL },
+
+ { ngx_string("fastcgi_keep_conn"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, keep_conn),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_fastcgi_module_ctx = {
+ ngx_http_fastcgi_add_variables, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ ngx_http_fastcgi_create_main_conf, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_fastcgi_create_loc_conf, /* create location configuration */
+ ngx_http_fastcgi_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_fastcgi_module = {
+ NGX_MODULE_V1,
+ &ngx_http_fastcgi_module_ctx, /* module context */
+ ngx_http_fastcgi_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_fastcgi_request_start_t ngx_http_fastcgi_request_start = {
+ { 1, /* version */
+ NGX_HTTP_FASTCGI_BEGIN_REQUEST, /* type */
+ 0, /* request_id_hi */
+ 1, /* request_id_lo */
+ 0, /* content_length_hi */
+ sizeof(ngx_http_fastcgi_begin_request_t), /* content_length_lo */
+ 0, /* padding_length */
+ 0 }, /* reserved */
+
+ { 0, /* role_hi */
+ NGX_HTTP_FASTCGI_RESPONDER, /* role_lo */
+ 0, /* NGX_HTTP_FASTCGI_KEEP_CONN */ /* flags */
+ { 0, 0, 0, 0, 0 } }, /* reserved[5] */
+
+ { 1, /* version */
+ NGX_HTTP_FASTCGI_PARAMS, /* type */
+ 0, /* request_id_hi */
+ 1 }, /* request_id_lo */
+
+};
+
+
+static ngx_http_variable_t ngx_http_fastcgi_vars[] = {
+
+ { ngx_string("fastcgi_script_name"), NULL,
+ ngx_http_fastcgi_script_name_variable, 0,
+ NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+ { ngx_string("fastcgi_path_info"), NULL,
+ ngx_http_fastcgi_path_info_variable, 0,
+ NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+ { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+
+static ngx_str_t ngx_http_fastcgi_hide_headers[] = {
+ ngx_string("Status"),
+ ngx_string("X-Accel-Expires"),
+ ngx_string("X-Accel-Redirect"),
+ ngx_string("X-Accel-Limit-Rate"),
+ ngx_string("X-Accel-Buffering"),
+ ngx_string("X-Accel-Charset"),
+ ngx_null_string
+};
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_keyval_t ngx_http_fastcgi_cache_headers[] = {
+ { ngx_string("HTTP_IF_MODIFIED_SINCE"),
+ ngx_string("$upstream_cache_last_modified") },
+ { ngx_string("HTTP_IF_UNMODIFIED_SINCE"), ngx_string("") },
+ { ngx_string("HTTP_IF_NONE_MATCH"), ngx_string("$upstream_cache_etag") },
+ { ngx_string("HTTP_IF_MATCH"), ngx_string("") },
+ { ngx_string("HTTP_RANGE"), ngx_string("") },
+ { ngx_string("HTTP_IF_RANGE"), ngx_string("") },
+ { ngx_null_string, ngx_null_string }
+};
+
+#endif
+
+
+static ngx_path_init_t ngx_http_fastcgi_temp_path = {
+ ngx_string(NGX_HTTP_FASTCGI_TEMP_PATH), { 1, 2, 0 }
+};
+
+
+static ngx_int_t
+ngx_http_fastcgi_handler(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_http_upstream_t *u;
+ ngx_http_fastcgi_ctx_t *f;
+ ngx_http_fastcgi_loc_conf_t *flcf;
+#if (NGX_HTTP_CACHE)
+ ngx_http_fastcgi_main_conf_t *fmcf;
+#endif
+
+ if (ngx_http_upstream_create(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ f = ngx_pcalloc(r->pool, sizeof(ngx_http_fastcgi_ctx_t));
+ if (f == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_http_set_ctx(r, f, ngx_http_fastcgi_module);
+
+ flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
+
+ if (flcf->fastcgi_lengths) {
+ if (ngx_http_fastcgi_eval(r, flcf) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ u = r->upstream;
+
+ ngx_str_set(&u->schema, "fastcgi://");
+ u->output.tag = (ngx_buf_tag_t) &ngx_http_fastcgi_module;
+
+ u->conf = &flcf->upstream;
+
+#if (NGX_HTTP_CACHE)
+ fmcf = ngx_http_get_module_main_conf(r, ngx_http_fastcgi_module);
+
+ u->caches = &fmcf->caches;
+ u->create_key = ngx_http_fastcgi_create_key;
+#endif
+
+ u->create_request = ngx_http_fastcgi_create_request;
+ u->reinit_request = ngx_http_fastcgi_reinit_request;
+ u->process_header = ngx_http_fastcgi_process_header;
+ u->abort_request = ngx_http_fastcgi_abort_request;
+ u->finalize_request = ngx_http_fastcgi_finalize_request;
+ r->state = 0;
+
+ u->buffering = flcf->upstream.buffering;
+
+ u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t));
+ if (u->pipe == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ u->pipe->input_filter = ngx_http_fastcgi_input_filter;
+ u->pipe->input_ctx = r;
+
+ u->input_filter_init = ngx_http_fastcgi_input_filter_init;
+ u->input_filter = ngx_http_fastcgi_non_buffered_filter;
+ u->input_filter_ctx = r;
+
+ if (!flcf->upstream.request_buffering
+ && flcf->upstream.pass_request_body)
+ {
+ r->request_body_no_buffering = 1;
+ }
+
+ rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);
+
+ if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+ return rc;
+ }
+
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_eval(ngx_http_request_t *r, ngx_http_fastcgi_loc_conf_t *flcf)
+{
+ ngx_url_t url;
+ ngx_http_upstream_t *u;
+
+ ngx_memzero(&url, sizeof(ngx_url_t));
+
+ if (ngx_http_script_run(r, &url.url, flcf->fastcgi_lengths->elts, 0,
+ flcf->fastcgi_values->elts)
+ == NULL)
+ {
+ return NGX_ERROR;
+ }
+
+ url.no_resolve = 1;
+
+ if (ngx_parse_url(r->pool, &url) != NGX_OK) {
+ if (url.err) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "%s in upstream \"%V\"", url.err, &url.url);
+ }
+
+ return NGX_ERROR;
+ }
+
+ u = r->upstream;
+
+ u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t));
+ if (u->resolved == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (url.addrs) {
+ u->resolved->sockaddr = url.addrs[0].sockaddr;
+ u->resolved->socklen = url.addrs[0].socklen;
+ u->resolved->name = url.addrs[0].name;
+ u->resolved->naddrs = 1;
+ }
+
+ u->resolved->host = url.host;
+ u->resolved->port = url.port;
+ u->resolved->no_port = url.no_port;
+
+ return NGX_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_int_t
+ngx_http_fastcgi_create_key(ngx_http_request_t *r)
+{
+ ngx_str_t *key;
+ ngx_http_fastcgi_loc_conf_t *flcf;
+
+ key = ngx_array_push(&r->cache->keys);
+ if (key == NULL) {
+ return NGX_ERROR;
+ }
+
+ flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
+
+ if (ngx_http_complex_value(r, &flcf->cache_key, key) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_fastcgi_create_request(ngx_http_request_t *r)
+{
+ off_t file_pos;
+ u_char ch, *pos, *lowcase_key;
+ size_t size, len, key_len, val_len, padding,
+ allocated;
+ ngx_uint_t i, n, next, hash, skip_empty, header_params;
+ ngx_buf_t *b;
+ ngx_chain_t *cl, *body;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header, **ignored;
+ ngx_http_upstream_t *u;
+ ngx_http_script_code_pt code;
+ ngx_http_script_engine_t e, le;
+ ngx_http_fastcgi_header_t *h;
+ ngx_http_fastcgi_params_t *params;
+ ngx_http_fastcgi_loc_conf_t *flcf;
+ ngx_http_script_len_code_pt lcode;
+
+ len = 0;
+ header_params = 0;
+ ignored = NULL;
+
+ u = r->upstream;
+
+ flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
+
+#if (NGX_HTTP_CACHE)
+ params = u->cacheable ? &flcf->params_cache : &flcf->params;
+#else
+ params = &flcf->params;
+#endif
+
+ if (params->lengths) {
+ ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
+
+ ngx_http_script_flush_no_cacheable_variables(r, params->flushes);
+ le.flushed = 1;
+
+ le.ip = params->lengths->elts;
+ le.request = r;
+
+ while (*(uintptr_t *) le.ip) {
+
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ key_len = lcode(&le);
+
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ skip_empty = lcode(&le);
+
+ for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) {
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ }
+ le.ip += sizeof(uintptr_t);
+
+ if (skip_empty && val_len == 0) {
+ continue;
+ }
+
+ len += 1 + key_len + ((val_len > 127) ? 4 : 1) + val_len;
+ }
+ }
+
+ if (flcf->upstream.pass_request_headers) {
+
+ allocated = 0;
+ lowcase_key = NULL;
+
+ if (params->number) {
+ n = 0;
+ part = &r->headers_in.headers.part;
+
+ while (part) {
+ n += part->nelts;
+ part = part->next;
+ }
+
+ ignored = ngx_palloc(r->pool, n * sizeof(void *));
+ if (ignored == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ part = &r->headers_in.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (params->number) {
+ if (allocated < header[i].key.len) {
+ allocated = header[i].key.len + 16;
+ lowcase_key = ngx_pnalloc(r->pool, allocated);
+ if (lowcase_key == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ hash = 0;
+
+ for (n = 0; n < header[i].key.len; n++) {
+ ch = header[i].key.data[n];
+
+ if (ch >= 'A' && ch <= 'Z') {
+ ch |= 0x20;
+
+ } else if (ch == '-') {
+ ch = '_';
+ }
+
+ hash = ngx_hash(hash, ch);
+ lowcase_key[n] = ch;
+ }
+
+ if (ngx_hash_find(&params->hash, hash, lowcase_key, n)) {
+ ignored[header_params++] = &header[i];
+ continue;
+ }
+
+ n += sizeof("HTTP_") - 1;
+
+ } else {
+ n = sizeof("HTTP_") - 1 + header[i].key.len;
+ }
+
+ len += ((n > 127) ? 4 : 1) + ((header[i].value.len > 127) ? 4 : 1)
+ + n + header[i].value.len;
+ }
+ }
+
+
+ if (len > 65535) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "fastcgi request record is too big: %uz", len);
+ return NGX_ERROR;
+ }
+
+
+ padding = 8 - len % 8;
+ padding = (padding == 8) ? 0 : padding;
+
+
+ size = sizeof(ngx_http_fastcgi_header_t)
+ + sizeof(ngx_http_fastcgi_begin_request_t)
+
+ + sizeof(ngx_http_fastcgi_header_t) /* NGX_HTTP_FASTCGI_PARAMS */
+ + len + padding
+ + sizeof(ngx_http_fastcgi_header_t) /* NGX_HTTP_FASTCGI_PARAMS */
+
+ + sizeof(ngx_http_fastcgi_header_t); /* NGX_HTTP_FASTCGI_STDIN */
+
+
+ b = ngx_create_temp_buf(r->pool, size);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+
+ ngx_http_fastcgi_request_start.br.flags =
+ flcf->keep_conn ? NGX_HTTP_FASTCGI_KEEP_CONN : 0;
+
+ ngx_memcpy(b->pos, &ngx_http_fastcgi_request_start,
+ sizeof(ngx_http_fastcgi_request_start_t));
+
+ h = (ngx_http_fastcgi_header_t *)
+ (b->pos + sizeof(ngx_http_fastcgi_header_t)
+ + sizeof(ngx_http_fastcgi_begin_request_t));
+
+ h->content_length_hi = (u_char) ((len >> 8) & 0xff);
+ h->content_length_lo = (u_char) (len & 0xff);
+ h->padding_length = (u_char) padding;
+ h->reserved = 0;
+
+ b->last = b->pos + sizeof(ngx_http_fastcgi_header_t)
+ + sizeof(ngx_http_fastcgi_begin_request_t)
+ + sizeof(ngx_http_fastcgi_header_t);
+
+
+ if (params->lengths) {
+ ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+ e.ip = params->values->elts;
+ e.pos = b->last;
+ e.request = r;
+ e.flushed = 1;
+
+ le.ip = params->lengths->elts;
+
+ while (*(uintptr_t *) le.ip) {
+
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ key_len = (u_char) lcode(&le);
+
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ skip_empty = lcode(&le);
+
+ for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) {
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ }
+ le.ip += sizeof(uintptr_t);
+
+ if (skip_empty && val_len == 0) {
+ e.skip = 1;
+
+ while (*(uintptr_t *) e.ip) {
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code((ngx_http_script_engine_t *) &e);
+ }
+ e.ip += sizeof(uintptr_t);
+
+ e.skip = 0;
+
+ continue;
+ }
+
+ *e.pos++ = (u_char) key_len;
+
+ if (val_len > 127) {
+ *e.pos++ = (u_char) (((val_len >> 24) & 0x7f) | 0x80);
+ *e.pos++ = (u_char) ((val_len >> 16) & 0xff);
+ *e.pos++ = (u_char) ((val_len >> 8) & 0xff);
+ *e.pos++ = (u_char) (val_len & 0xff);
+
+ } else {
+ *e.pos++ = (u_char) val_len;
+ }
+
+ while (*(uintptr_t *) e.ip) {
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code((ngx_http_script_engine_t *) &e);
+ }
+ e.ip += sizeof(uintptr_t);
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "fastcgi param: \"%*s: %*s\"",
+ key_len, e.pos - (key_len + val_len),
+ val_len, e.pos - val_len);
+ }
+
+ b->last = e.pos;
+ }
+
+
+ if (flcf->upstream.pass_request_headers) {
+
+ part = &r->headers_in.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ for (n = 0; n < header_params; n++) {
+ if (&header[i] == ignored[n]) {
+ goto next;
+ }
+ }
+
+ key_len = sizeof("HTTP_") - 1 + header[i].key.len;
+ if (key_len > 127) {
+ *b->last++ = (u_char) (((key_len >> 24) & 0x7f) | 0x80);
+ *b->last++ = (u_char) ((key_len >> 16) & 0xff);
+ *b->last++ = (u_char) ((key_len >> 8) & 0xff);
+ *b->last++ = (u_char) (key_len & 0xff);
+
+ } else {
+ *b->last++ = (u_char) key_len;
+ }
+
+ val_len = header[i].value.len;
+ if (val_len > 127) {
+ *b->last++ = (u_char) (((val_len >> 24) & 0x7f) | 0x80);
+ *b->last++ = (u_char) ((val_len >> 16) & 0xff);
+ *b->last++ = (u_char) ((val_len >> 8) & 0xff);
+ *b->last++ = (u_char) (val_len & 0xff);
+
+ } else {
+ *b->last++ = (u_char) val_len;
+ }
+
+ b->last = ngx_cpymem(b->last, "HTTP_", sizeof("HTTP_") - 1);
+
+ for (n = 0; n < header[i].key.len; n++) {
+ ch = header[i].key.data[n];
+
+ if (ch >= 'a' && ch <= 'z') {
+ ch &= ~0x20;
+
+ } else if (ch == '-') {
+ ch = '_';
+ }
+
+ *b->last++ = ch;
+ }
+
+ b->last = ngx_copy(b->last, header[i].value.data, val_len);
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "fastcgi param: \"%*s: %*s\"",
+ key_len, b->last - (key_len + val_len),
+ val_len, b->last - val_len);
+ next:
+
+ continue;
+ }
+ }
+
+
+ if (padding) {
+ ngx_memzero(b->last, padding);
+ b->last += padding;
+ }
+
+
+ h = (ngx_http_fastcgi_header_t *) b->last;
+ b->last += sizeof(ngx_http_fastcgi_header_t);
+
+ h->version = 1;
+ h->type = NGX_HTTP_FASTCGI_PARAMS;
+ h->request_id_hi = 0;
+ h->request_id_lo = 1;
+ h->content_length_hi = 0;
+ h->content_length_lo = 0;
+ h->padding_length = 0;
+ h->reserved = 0;
+
+ if (r->request_body_no_buffering) {
+
+ u->request_bufs = cl;
+
+ u->output.output_filter = ngx_http_fastcgi_body_output_filter;
+ u->output.filter_ctx = r;
+
+ } else if (flcf->upstream.pass_request_body) {
+
+ body = u->request_bufs;
+ u->request_bufs = cl;
+
+#if (NGX_SUPPRESS_WARN)
+ file_pos = 0;
+ pos = NULL;
+#endif
+
+ while (body) {
+
+ if (ngx_buf_special(body->buf)) {
+ body = body->next;
+ continue;
+ }
+
+ if (body->buf->in_file) {
+ file_pos = body->buf->file_pos;
+
+ } else {
+ pos = body->buf->pos;
+ }
+
+ next = 0;
+
+ do {
+ b = ngx_alloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(b, body->buf, sizeof(ngx_buf_t));
+
+ if (body->buf->in_file) {
+ b->file_pos = file_pos;
+ file_pos += 32 * 1024;
+
+ if (file_pos >= body->buf->file_last) {
+ file_pos = body->buf->file_last;
+ next = 1;
+ }
+
+ b->file_last = file_pos;
+ len = (ngx_uint_t) (file_pos - b->file_pos);
+
+ } else {
+ b->pos = pos;
+ b->start = pos;
+ pos += 32 * 1024;
+
+ if (pos >= body->buf->last) {
+ pos = body->buf->last;
+ next = 1;
+ }
+
+ b->last = pos;
+ len = (ngx_uint_t) (pos - b->pos);
+ }
+
+ padding = 8 - len % 8;
+ padding = (padding == 8) ? 0 : padding;
+
+ h = (ngx_http_fastcgi_header_t *) cl->buf->last;
+ cl->buf->last += sizeof(ngx_http_fastcgi_header_t);
+
+ h->version = 1;
+ h->type = NGX_HTTP_FASTCGI_STDIN;
+ h->request_id_hi = 0;
+ h->request_id_lo = 1;
+ h->content_length_hi = (u_char) ((len >> 8) & 0xff);
+ h->content_length_lo = (u_char) (len & 0xff);
+ h->padding_length = (u_char) padding;
+ h->reserved = 0;
+
+ cl->next = ngx_alloc_chain_link(r->pool);
+ if (cl->next == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = cl->next;
+ cl->buf = b;
+
+ b = ngx_create_temp_buf(r->pool,
+ sizeof(ngx_http_fastcgi_header_t)
+ + padding);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (padding) {
+ ngx_memzero(b->last, padding);
+ b->last += padding;
+ }
+
+ cl->next = ngx_alloc_chain_link(r->pool);
+ if (cl->next == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = cl->next;
+ cl->buf = b;
+
+ } while (!next);
+
+ body = body->next;
+ }
+
+ } else {
+ u->request_bufs = cl;
+ }
+
+ if (!r->request_body_no_buffering) {
+ h = (ngx_http_fastcgi_header_t *) cl->buf->last;
+ cl->buf->last += sizeof(ngx_http_fastcgi_header_t);
+
+ h->version = 1;
+ h->type = NGX_HTTP_FASTCGI_STDIN;
+ h->request_id_hi = 0;
+ h->request_id_lo = 1;
+ h->content_length_hi = 0;
+ h->content_length_lo = 0;
+ h->padding_length = 0;
+ h->reserved = 0;
+ }
+
+ cl->next = NULL;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_reinit_request(ngx_http_request_t *r)
+{
+ ngx_http_fastcgi_ctx_t *f;
+
+ f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module);
+
+ if (f == NULL) {
+ return NGX_OK;
+ }
+
+ f->state = ngx_http_fastcgi_st_version;
+ f->fastcgi_stdout = 0;
+ f->large_stderr = 0;
+
+ if (f->split_parts) {
+ f->split_parts->nelts = 0;
+ }
+
+ r->state = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_body_output_filter(void *data, ngx_chain_t *in)
+{
+ ngx_http_request_t *r = data;
+
+ off_t file_pos;
+ u_char *pos, *start;
+ size_t len, padding;
+ ngx_buf_t *b;
+ ngx_int_t rc;
+ ngx_uint_t next, last;
+ ngx_chain_t *cl, *tl, *out, **ll;
+ ngx_http_fastcgi_ctx_t *f;
+ ngx_http_fastcgi_header_t *h;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "fastcgi output filter");
+
+ f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module);
+
+ if (in == NULL) {
+ out = in;
+ goto out;
+ }
+
+ out = NULL;
+ ll = &out;
+
+ if (!f->header_sent) {
+ /* first buffer contains headers, pass it unmodified */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "fastcgi output header");
+
+ f->header_sent = 1;
+
+ tl = ngx_alloc_chain_link(r->pool);
+ if (tl == NULL) {
+ return NGX_ERROR;
+ }
+
+ tl->buf = in->buf;
+ *ll = tl;
+ ll = &tl->next;
+
+ in = in->next;
+
+ if (in == NULL) {
+ tl->next = NULL;
+ goto out;
+ }
+ }
+
+ cl = ngx_chain_get_free_buf(r->pool, &f->free);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = cl->buf;
+
+ b->tag = (ngx_buf_tag_t) &ngx_http_fastcgi_body_output_filter;
+ b->temporary = 1;
+
+ if (b->start == NULL) {
+ /* reserve space for maximum possible padding, 7 bytes */
+
+ b->start = ngx_palloc(r->pool,
+ sizeof(ngx_http_fastcgi_header_t) + 7);
+ if (b->start == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->pos = b->start;
+ b->last = b->start;
+
+ b->end = b->start + sizeof(ngx_http_fastcgi_header_t) + 7;
+ }
+
+ *ll = cl;
+
+ last = 0;
+ padding = 0;
+
+#if (NGX_SUPPRESS_WARN)
+ file_pos = 0;
+ pos = NULL;
+#endif
+
+ while (in) {
+
+ ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0,
+ "fastcgi output in l:%d f:%d %p, pos %p, size: %z "
+ "file: %O, size: %O",
+ in->buf->last_buf,
+ in->buf->in_file,
+ in->buf->start, in->buf->pos,
+ in->buf->last - in->buf->pos,
+ in->buf->file_pos,
+ in->buf->file_last - in->buf->file_pos);
+
+ if (in->buf->last_buf) {
+ last = 1;
+ }
+
+ if (ngx_buf_special(in->buf)) {
+ in = in->next;
+ continue;
+ }
+
+ if (in->buf->in_file) {
+ file_pos = in->buf->file_pos;
+
+ } else {
+ pos = in->buf->pos;
+ }
+
+ next = 0;
+
+ do {
+ tl = ngx_chain_get_free_buf(r->pool, &f->free);
+ if (tl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = tl->buf;
+ start = b->start;
+
+ ngx_memcpy(b, in->buf, sizeof(ngx_buf_t));
+
+ /*
+ * restore b->start to preserve memory allocated in the buffer,
+ * to reuse it later for headers and padding
+ */
+
+ b->start = start;
+
+ if (in->buf->in_file) {
+ b->file_pos = file_pos;
+ file_pos += 32 * 1024;
+
+ if (file_pos >= in->buf->file_last) {
+ file_pos = in->buf->file_last;
+ next = 1;
+ }
+
+ b->file_last = file_pos;
+ len = (ngx_uint_t) (file_pos - b->file_pos);
+
+ } else {
+ b->pos = pos;
+ pos += 32 * 1024;
+
+ if (pos >= in->buf->last) {
+ pos = in->buf->last;
+ next = 1;
+ }
+
+ b->last = pos;
+ len = (ngx_uint_t) (pos - b->pos);
+ }
+
+ b->tag = (ngx_buf_tag_t) &ngx_http_fastcgi_body_output_filter;
+ b->shadow = in->buf;
+ b->last_shadow = next;
+
+ b->last_buf = 0;
+ b->last_in_chain = 0;
+
+ padding = 8 - len % 8;
+ padding = (padding == 8) ? 0 : padding;
+
+ h = (ngx_http_fastcgi_header_t *) cl->buf->last;
+ cl->buf->last += sizeof(ngx_http_fastcgi_header_t);
+
+ h->version = 1;
+ h->type = NGX_HTTP_FASTCGI_STDIN;
+ h->request_id_hi = 0;
+ h->request_id_lo = 1;
+ h->content_length_hi = (u_char) ((len >> 8) & 0xff);
+ h->content_length_lo = (u_char) (len & 0xff);
+ h->padding_length = (u_char) padding;
+ h->reserved = 0;
+
+ cl->next = tl;
+ cl = tl;
+
+ tl = ngx_chain_get_free_buf(r->pool, &f->free);
+ if (tl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = tl->buf;
+
+ b->tag = (ngx_buf_tag_t) &ngx_http_fastcgi_body_output_filter;
+ b->temporary = 1;
+
+ if (b->start == NULL) {
+ /* reserve space for maximum possible padding, 7 bytes */
+
+ b->start = ngx_palloc(r->pool,
+ sizeof(ngx_http_fastcgi_header_t) + 7);
+ if (b->start == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->pos = b->start;
+ b->last = b->start;
+
+ b->end = b->start + sizeof(ngx_http_fastcgi_header_t) + 7;
+ }
+
+ if (padding) {
+ ngx_memzero(b->last, padding);
+ b->last += padding;
+ }
+
+ cl->next = tl;
+ cl = tl;
+
+ } while (!next);
+
+ in = in->next;
+ }
+
+ if (last) {
+ h = (ngx_http_fastcgi_header_t *) cl->buf->last;
+ cl->buf->last += sizeof(ngx_http_fastcgi_header_t);
+
+ h->version = 1;
+ h->type = NGX_HTTP_FASTCGI_STDIN;
+ h->request_id_hi = 0;
+ h->request_id_lo = 1;
+ h->content_length_hi = 0;
+ h->content_length_lo = 0;
+ h->padding_length = 0;
+ h->reserved = 0;
+
+ cl->buf->last_buf = 1;
+
+ } else if (padding == 0) {
+ /* TODO: do not allocate buffers instead */
+ cl->buf->temporary = 0;
+ cl->buf->sync = 1;
+ }
+
+ cl->next = NULL;
+
+out:
+
+#if (NGX_DEBUG)
+
+ for (cl = out; cl; cl = cl->next) {
+ ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0,
+ "fastcgi output out l:%d f:%d %p, pos %p, size: %z "
+ "file: %O, size: %O",
+ cl->buf->last_buf,
+ cl->buf->in_file,
+ cl->buf->start, cl->buf->pos,
+ cl->buf->last - cl->buf->pos,
+ cl->buf->file_pos,
+ cl->buf->file_last - cl->buf->file_pos);
+ }
+
+#endif
+
+ rc = ngx_chain_writer(&r->upstream->writer, out);
+
+ ngx_chain_update_chains(r->pool, &f->free, &f->busy, &out,
+ (ngx_buf_tag_t) &ngx_http_fastcgi_body_output_filter);
+
+ for (cl = f->free; cl; cl = cl->next) {
+
+ /* mark original buffers as sent */
+
+ if (cl->buf->shadow) {
+ if (cl->buf->last_shadow) {
+ b = cl->buf->shadow;
+ b->pos = b->last;
+ }
+
+ cl->buf->shadow = NULL;
+ }
+ }
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_process_header(ngx_http_request_t *r)
+{
+ u_char *p, *msg, *start, *last,
+ *part_start, *part_end;
+ size_t size;
+ ngx_str_t *status_line, *pattern;
+ ngx_int_t rc, status;
+ ngx_buf_t buf;
+ ngx_uint_t i;
+ ngx_table_elt_t *h;
+ ngx_http_upstream_t *u;
+ ngx_http_fastcgi_ctx_t *f;
+ ngx_http_upstream_header_t *hh;
+ ngx_http_fastcgi_loc_conf_t *flcf;
+ ngx_http_fastcgi_split_part_t *part;
+ ngx_http_upstream_main_conf_t *umcf;
+
+ f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module);
+
+ umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
+
+ u = r->upstream;
+
+ for ( ;; ) {
+
+ if (f->state < ngx_http_fastcgi_st_data) {
+
+ f->pos = u->buffer.pos;
+ f->last = u->buffer.last;
+
+ rc = ngx_http_fastcgi_process_record(r, f);
+
+ u->buffer.pos = f->pos;
+ u->buffer.last = f->last;
+
+ if (rc == NGX_AGAIN) {
+ return NGX_AGAIN;
+ }
+
+ if (rc == NGX_ERROR) {
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+ }
+
+ if (f->type != NGX_HTTP_FASTCGI_STDOUT
+ && f->type != NGX_HTTP_FASTCGI_STDERR)
+ {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent unexpected FastCGI record: %ui",
+ f->type);
+
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+ }
+
+ if (f->type == NGX_HTTP_FASTCGI_STDOUT && f->length == 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream prematurely closed FastCGI stdout");
+
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+ }
+ }
+
+ if (f->state == ngx_http_fastcgi_st_padding) {
+
+ if (u->buffer.pos + f->padding < u->buffer.last) {
+ f->state = ngx_http_fastcgi_st_version;
+ u->buffer.pos += f->padding;
+
+ continue;
+ }
+
+ if (u->buffer.pos + f->padding == u->buffer.last) {
+ f->state = ngx_http_fastcgi_st_version;
+ u->buffer.pos = u->buffer.last;
+
+ return NGX_AGAIN;
+ }
+
+ f->padding -= u->buffer.last - u->buffer.pos;
+ u->buffer.pos = u->buffer.last;
+
+ return NGX_AGAIN;
+ }
+
+
+ /* f->state == ngx_http_fastcgi_st_data */
+
+ if (f->type == NGX_HTTP_FASTCGI_STDERR) {
+
+ if (f->length) {
+ msg = u->buffer.pos;
+
+ if (u->buffer.pos + f->length <= u->buffer.last) {
+ u->buffer.pos += f->length;
+ f->length = 0;
+ f->state = ngx_http_fastcgi_st_padding;
+
+ } else {
+ f->length -= u->buffer.last - u->buffer.pos;
+ u->buffer.pos = u->buffer.last;
+ }
+
+ for (p = u->buffer.pos - 1; msg < p; p--) {
+ if (*p != LF && *p != CR && *p != '.' && *p != ' ') {
+ break;
+ }
+ }
+
+ p++;
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "FastCGI sent in stderr: \"%*s\"", p - msg, msg);
+
+ flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
+
+ if (flcf->catch_stderr) {
+ pattern = flcf->catch_stderr->elts;
+
+ for (i = 0; i < flcf->catch_stderr->nelts; i++) {
+ if (ngx_strnstr(msg, (char *) pattern[i].data,
+ p - msg)
+ != NULL)
+ {
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+ }
+ }
+ }
+
+ if (u->buffer.pos == u->buffer.last) {
+
+ if (!f->fastcgi_stdout) {
+
+ /*
+ * the special handling the large number
+ * of the PHP warnings to not allocate memory
+ */
+
+#if (NGX_HTTP_CACHE)
+ if (r->cache) {
+ u->buffer.pos = u->buffer.start
+ + r->cache->header_start;
+ } else {
+ u->buffer.pos = u->buffer.start;
+ }
+#else
+ u->buffer.pos = u->buffer.start;
+#endif
+ u->buffer.last = u->buffer.pos;
+ f->large_stderr = 1;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ } else {
+ f->state = ngx_http_fastcgi_st_padding;
+ }
+
+ continue;
+ }
+
+
+ /* f->type == NGX_HTTP_FASTCGI_STDOUT */
+
+#if (NGX_HTTP_CACHE)
+
+ if (f->large_stderr && r->cache) {
+ ssize_t len;
+ ngx_http_fastcgi_header_t *fh;
+
+ start = u->buffer.start + r->cache->header_start;
+
+ len = u->buffer.pos - start - 2 * sizeof(ngx_http_fastcgi_header_t);
+
+ /*
+ * A tail of large stderr output before HTTP header is placed
+ * in a cache file without a FastCGI record header.
+ * To workaround it we put a dummy FastCGI record header at the
+ * start of the stderr output or update r->cache_header_start,
+ * if there is no enough place for the record header.
+ */
+
+ if (len >= 0) {
+ fh = (ngx_http_fastcgi_header_t *) start;
+ fh->version = 1;
+ fh->type = NGX_HTTP_FASTCGI_STDERR;
+ fh->request_id_hi = 0;
+ fh->request_id_lo = 1;
+ fh->content_length_hi = (u_char) ((len >> 8) & 0xff);
+ fh->content_length_lo = (u_char) (len & 0xff);
+ fh->padding_length = 0;
+ fh->reserved = 0;
+
+ } else {
+ r->cache->header_start += u->buffer.pos - start
+ - sizeof(ngx_http_fastcgi_header_t);
+ }
+
+ f->large_stderr = 0;
+ }
+
+#endif
+
+ f->fastcgi_stdout = 1;
+
+ start = u->buffer.pos;
+
+ if (u->buffer.pos + f->length < u->buffer.last) {
+
+ /*
+ * set u->buffer.last to the end of the FastCGI record data
+ * for ngx_http_parse_header_line()
+ */
+
+ last = u->buffer.last;
+ u->buffer.last = u->buffer.pos + f->length;
+
+ } else {
+ last = NULL;
+ }
+
+ for ( ;; ) {
+
+ part_start = u->buffer.pos;
+ part_end = u->buffer.last;
+
+ rc = ngx_http_parse_header_line(r, &u->buffer, 1);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http fastcgi parser: %i", rc);
+
+ if (rc == NGX_AGAIN) {
+ break;
+ }
+
+ if (rc == NGX_OK) {
+
+ /* a header line has been parsed successfully */
+
+ h = ngx_list_push(&u->headers_in.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (f->split_parts && f->split_parts->nelts) {
+
+ part = f->split_parts->elts;
+ size = u->buffer.pos - part_start;
+
+ for (i = 0; i < f->split_parts->nelts; i++) {
+ size += part[i].end - part[i].start;
+ }
+
+ p = ngx_pnalloc(r->pool, size);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ buf.pos = p;
+
+ for (i = 0; i < f->split_parts->nelts; i++) {
+ p = ngx_cpymem(p, part[i].start,
+ part[i].end - part[i].start);
+ }
+
+ p = ngx_cpymem(p, part_start, u->buffer.pos - part_start);
+
+ buf.last = p;
+
+ f->split_parts->nelts = 0;
+
+ rc = ngx_http_parse_header_line(r, &buf, 1);
+
+ if (rc != NGX_OK) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "invalid header after joining "
+ "FastCGI records");
+ return NGX_ERROR;
+ }
+
+ h->key.len = r->header_name_end - r->header_name_start;
+ h->key.data = r->header_name_start;
+ h->key.data[h->key.len] = '\0';
+
+ h->value.len = r->header_end - r->header_start;
+ h->value.data = r->header_start;
+ h->value.data[h->value.len] = '\0';
+
+ h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
+ if (h->lowcase_key == NULL) {
+ return NGX_ERROR;
+ }
+
+ } else {
+
+ h->key.len = r->header_name_end - r->header_name_start;
+ h->value.len = r->header_end - r->header_start;
+
+ h->key.data = ngx_pnalloc(r->pool,
+ h->key.len + 1 + h->value.len + 1
+ + h->key.len);
+ if (h->key.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->value.data = h->key.data + h->key.len + 1;
+ h->lowcase_key = h->key.data + h->key.len + 1
+ + h->value.len + 1;
+
+ ngx_memcpy(h->key.data, r->header_name_start, h->key.len);
+ h->key.data[h->key.len] = '\0';
+ ngx_memcpy(h->value.data, r->header_start, h->value.len);
+ h->value.data[h->value.len] = '\0';
+ }
+
+ h->hash = r->header_hash;
+
+ if (h->key.len == r->lowcase_index) {
+ ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
+
+ } else {
+ ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
+ }
+
+ hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
+ h->lowcase_key, h->key.len);
+
+ if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http fastcgi header: \"%V: %V\"",
+ &h->key, &h->value);
+
+ if (u->buffer.pos < u->buffer.last) {
+ continue;
+ }
+
+ /* the end of the FastCGI record */
+
+ break;
+ }
+
+ if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+ /* a whole header has been parsed successfully */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http fastcgi header done");
+
+ if (u->headers_in.status) {
+ status_line = &u->headers_in.status->value;
+
+ status = ngx_atoi(status_line->data, 3);
+
+ if (status == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent invalid status \"%V\"",
+ status_line);
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+ }
+
+ u->headers_in.status_n = status;
+ u->headers_in.status_line = *status_line;
+
+ } else if (u->headers_in.location) {
+ u->headers_in.status_n = 302;
+ ngx_str_set(&u->headers_in.status_line,
+ "302 Moved Temporarily");
+
+ } else {
+ u->headers_in.status_n = 200;
+ ngx_str_set(&u->headers_in.status_line, "200 OK");
+ }
+
+ if (u->state && u->state->status == 0) {
+ u->state->status = u->headers_in.status_n;
+ }
+
+ break;
+ }
+
+ /* there was error while a header line parsing */
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent invalid header");
+
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+ }
+
+ if (last) {
+ u->buffer.last = last;
+ }
+
+ f->length -= u->buffer.pos - start;
+
+ if (f->length == 0) {
+ f->state = ngx_http_fastcgi_st_padding;
+ }
+
+ if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+ return NGX_OK;
+ }
+
+ if (rc == NGX_OK) {
+ continue;
+ }
+
+ /* rc == NGX_AGAIN */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "upstream split a header line in FastCGI records");
+
+ if (f->split_parts == NULL) {
+ f->split_parts = ngx_array_create(r->pool, 1,
+ sizeof(ngx_http_fastcgi_split_part_t));
+ if (f->split_parts == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ part = ngx_array_push(f->split_parts);
+ if (part == NULL) {
+ return NGX_ERROR;
+ }
+
+ part->start = part_start;
+ part->end = part_end;
+
+ if (u->buffer.pos < u->buffer.last) {
+ continue;
+ }
+
+ return NGX_AGAIN;
+ }
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_input_filter_init(void *data)
+{
+ ngx_http_request_t *r = data;
+ ngx_http_fastcgi_loc_conf_t *flcf;
+
+ flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
+
+ r->upstream->pipe->length = flcf->keep_conn ?
+ (off_t) sizeof(ngx_http_fastcgi_header_t) : -1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf)
+{
+ u_char *m, *msg;
+ ngx_int_t rc;
+ ngx_buf_t *b, **prev;
+ ngx_chain_t *cl;
+ ngx_http_request_t *r;
+ ngx_http_fastcgi_ctx_t *f;
+ ngx_http_fastcgi_loc_conf_t *flcf;
+
+ if (buf->pos == buf->last) {
+ return NGX_OK;
+ }
+
+ r = p->input_ctx;
+ f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module);
+ flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
+
+ b = NULL;
+ prev = &buf->shadow;
+
+ f->pos = buf->pos;
+ f->last = buf->last;
+
+ for ( ;; ) {
+ if (f->state < ngx_http_fastcgi_st_data) {
+
+ rc = ngx_http_fastcgi_process_record(r, f);
+
+ if (rc == NGX_AGAIN) {
+ break;
+ }
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (f->type == NGX_HTTP_FASTCGI_STDOUT && f->length == 0) {
+ f->state = ngx_http_fastcgi_st_padding;
+
+ if (!flcf->keep_conn) {
+ p->upstream_done = 1;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0,
+ "http fastcgi closed stdout");
+
+ continue;
+ }
+
+ if (f->type == NGX_HTTP_FASTCGI_END_REQUEST) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0,
+ "http fastcgi sent end request");
+
+ if (!flcf->keep_conn) {
+ p->upstream_done = 1;
+ break;
+ }
+
+ continue;
+ }
+ }
+
+
+ if (f->state == ngx_http_fastcgi_st_padding) {
+
+ if (f->type == NGX_HTTP_FASTCGI_END_REQUEST) {
+
+ if (f->pos + f->padding < f->last) {
+ p->upstream_done = 1;
+ break;
+ }
+
+ if (f->pos + f->padding == f->last) {
+ p->upstream_done = 1;
+ r->upstream->keepalive = 1;
+ break;
+ }
+
+ f->padding -= f->last - f->pos;
+
+ break;
+ }
+
+ if (f->pos + f->padding < f->last) {
+ f->state = ngx_http_fastcgi_st_version;
+ f->pos += f->padding;
+
+ continue;
+ }
+
+ if (f->pos + f->padding == f->last) {
+ f->state = ngx_http_fastcgi_st_version;
+
+ break;
+ }
+
+ f->padding -= f->last - f->pos;
+
+ break;
+ }
+
+
+ /* f->state == ngx_http_fastcgi_st_data */
+
+ if (f->type == NGX_HTTP_FASTCGI_STDERR) {
+
+ if (f->length) {
+
+ if (f->pos == f->last) {
+ break;
+ }
+
+ msg = f->pos;
+
+ if (f->pos + f->length <= f->last) {
+ f->pos += f->length;
+ f->length = 0;
+ f->state = ngx_http_fastcgi_st_padding;
+
+ } else {
+ f->length -= f->last - f->pos;
+ f->pos = f->last;
+ }
+
+ for (m = f->pos - 1; msg < m; m--) {
+ if (*m != LF && *m != CR && *m != '.' && *m != ' ') {
+ break;
+ }
+ }
+
+ ngx_log_error(NGX_LOG_ERR, p->log, 0,
+ "FastCGI sent in stderr: \"%*s\"",
+ m + 1 - msg, msg);
+
+ } else {
+ f->state = ngx_http_fastcgi_st_padding;
+ }
+
+ continue;
+ }
+
+ if (f->type == NGX_HTTP_FASTCGI_END_REQUEST) {
+
+ if (f->pos + f->length <= f->last) {
+ f->state = ngx_http_fastcgi_st_padding;
+ f->pos += f->length;
+
+ continue;
+ }
+
+ f->length -= f->last - f->pos;
+
+ break;
+ }
+
+
+ /* f->type == NGX_HTTP_FASTCGI_STDOUT */
+
+ if (f->pos == f->last) {
+ break;
+ }
+
+ cl = ngx_chain_get_free_buf(p->pool, &p->free);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = cl->buf;
+
+ ngx_memzero(b, sizeof(ngx_buf_t));
+
+ b->pos = f->pos;
+ b->start = buf->start;
+ b->end = buf->end;
+ b->tag = p->tag;
+ b->temporary = 1;
+ b->recycled = 1;
+
+ *prev = b;
+ prev = &b->shadow;
+
+ if (p->in) {
+ *p->last_in = cl;
+ } else {
+ p->in = cl;
+ }
+ p->last_in = &cl->next;
+
+
+ /* STUB */ b->num = buf->num;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "input buf #%d %p", b->num, b->pos);
+
+ if (f->pos + f->length <= f->last) {
+ f->state = ngx_http_fastcgi_st_padding;
+ f->pos += f->length;
+ b->last = f->pos;
+
+ continue;
+ }
+
+ f->length -= f->last - f->pos;
+
+ b->last = f->last;
+
+ break;
+
+ }
+
+ if (flcf->keep_conn) {
+
+ /* set p->length, minimal amount of data we want to see */
+
+ if (f->state < ngx_http_fastcgi_st_data) {
+ p->length = 1;
+
+ } else if (f->state == ngx_http_fastcgi_st_padding) {
+ p->length = f->padding;
+
+ } else {
+ /* ngx_http_fastcgi_st_data */
+
+ p->length = f->length;
+ }
+ }
+
+ if (b) {
+ b->shadow = buf;
+ b->last_shadow = 1;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "input buf %p %z", b->pos, b->last - b->pos);
+
+ return NGX_OK;
+ }
+
+ /* there is no data record in the buf, add it to free chain */
+
+ if (ngx_event_pipe_add_free_buf(p, buf) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_non_buffered_filter(void *data, ssize_t bytes)
+{
+ u_char *m, *msg;
+ ngx_int_t rc;
+ ngx_buf_t *b, *buf;
+ ngx_chain_t *cl, **ll;
+ ngx_http_request_t *r;
+ ngx_http_upstream_t *u;
+ ngx_http_fastcgi_ctx_t *f;
+
+ r = data;
+ f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module);
+
+ u = r->upstream;
+ buf = &u->buffer;
+
+ buf->pos = buf->last;
+ buf->last += bytes;
+
+ for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) {
+ ll = &cl->next;
+ }
+
+ f->pos = buf->pos;
+ f->last = buf->last;
+
+ for ( ;; ) {
+ if (f->state < ngx_http_fastcgi_st_data) {
+
+ rc = ngx_http_fastcgi_process_record(r, f);
+
+ if (rc == NGX_AGAIN) {
+ break;
+ }
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (f->type == NGX_HTTP_FASTCGI_STDOUT && f->length == 0) {
+ f->state = ngx_http_fastcgi_st_padding;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http fastcgi closed stdout");
+
+ continue;
+ }
+ }
+
+ if (f->state == ngx_http_fastcgi_st_padding) {
+
+ if (f->type == NGX_HTTP_FASTCGI_END_REQUEST) {
+
+ if (f->pos + f->padding < f->last) {
+ u->length = 0;
+ break;
+ }
+
+ if (f->pos + f->padding == f->last) {
+ u->length = 0;
+ u->keepalive = 1;
+ break;
+ }
+
+ f->padding -= f->last - f->pos;
+
+ break;
+ }
+
+ if (f->pos + f->padding < f->last) {
+ f->state = ngx_http_fastcgi_st_version;
+ f->pos += f->padding;
+
+ continue;
+ }
+
+ if (f->pos + f->padding == f->last) {
+ f->state = ngx_http_fastcgi_st_version;
+
+ break;
+ }
+
+ f->padding -= f->last - f->pos;
+
+ break;
+ }
+
+
+ /* f->state == ngx_http_fastcgi_st_data */
+
+ if (f->type == NGX_HTTP_FASTCGI_STDERR) {
+
+ if (f->length) {
+
+ if (f->pos == f->last) {
+ break;
+ }
+
+ msg = f->pos;
+
+ if (f->pos + f->length <= f->last) {
+ f->pos += f->length;
+ f->length = 0;
+ f->state = ngx_http_fastcgi_st_padding;
+
+ } else {
+ f->length -= f->last - f->pos;
+ f->pos = f->last;
+ }
+
+ for (m = f->pos - 1; msg < m; m--) {
+ if (*m != LF && *m != CR && *m != '.' && *m != ' ') {
+ break;
+ }
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "FastCGI sent in stderr: \"%*s\"",
+ m + 1 - msg, msg);
+
+ } else {
+ f->state = ngx_http_fastcgi_st_padding;
+ }
+
+ continue;
+ }
+
+ if (f->type == NGX_HTTP_FASTCGI_END_REQUEST) {
+
+ if (f->pos + f->length <= f->last) {
+ f->state = ngx_http_fastcgi_st_padding;
+ f->pos += f->length;
+
+ continue;
+ }
+
+ f->length -= f->last - f->pos;
+
+ break;
+ }
+
+
+ /* f->type == NGX_HTTP_FASTCGI_STDOUT */
+
+ if (f->pos == f->last) {
+ break;
+ }
+
+ cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ *ll = cl;
+ ll = &cl->next;
+
+ b = cl->buf;
+
+ b->flush = 1;
+ b->memory = 1;
+
+ b->pos = f->pos;
+ b->tag = u->output.tag;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http fastcgi output buf %p", b->pos);
+
+ if (f->pos + f->length <= f->last) {
+ f->state = ngx_http_fastcgi_st_padding;
+ f->pos += f->length;
+ b->last = f->pos;
+
+ continue;
+ }
+
+ f->length -= f->last - f->pos;
+ b->last = f->last;
+
+ break;
+ }
+
+ /* provide continuous buffer for subrequests in memory */
+
+ if (r->subrequest_in_memory) {
+
+ cl = u->out_bufs;
+
+ if (cl) {
+ buf->pos = cl->buf->pos;
+ }
+
+ buf->last = buf->pos;
+
+ for (cl = u->out_bufs; cl; cl = cl->next) {
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http fastcgi in memory %p-%p %O",
+ cl->buf->pos, cl->buf->last, ngx_buf_size(cl->buf));
+
+ if (buf->last == cl->buf->pos) {
+ buf->last = cl->buf->last;
+ continue;
+ }
+
+ buf->last = ngx_movemem(buf->last, cl->buf->pos,
+ cl->buf->last - cl->buf->pos);
+
+ cl->buf->pos = buf->last - (cl->buf->last - cl->buf->pos);
+ cl->buf->last = buf->last;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_process_record(ngx_http_request_t *r,
+ ngx_http_fastcgi_ctx_t *f)
+{
+ u_char ch, *p;
+ ngx_http_fastcgi_state_e state;
+
+ state = f->state;
+
+ for (p = f->pos; p < f->last; p++) {
+
+ ch = *p;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http fastcgi record byte: %02Xd", ch);
+
+ switch (state) {
+
+ case ngx_http_fastcgi_st_version:
+ if (ch != 1) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent unsupported FastCGI "
+ "protocol version: %d", ch);
+ return NGX_ERROR;
+ }
+ state = ngx_http_fastcgi_st_type;
+ break;
+
+ case ngx_http_fastcgi_st_type:
+ switch (ch) {
+ case NGX_HTTP_FASTCGI_STDOUT:
+ case NGX_HTTP_FASTCGI_STDERR:
+ case NGX_HTTP_FASTCGI_END_REQUEST:
+ f->type = (ngx_uint_t) ch;
+ break;
+ default:
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent invalid FastCGI "
+ "record type: %d", ch);
+ return NGX_ERROR;
+
+ }
+ state = ngx_http_fastcgi_st_request_id_hi;
+ break;
+
+ /* we support the single request per connection */
+
+ case ngx_http_fastcgi_st_request_id_hi:
+ if (ch != 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent unexpected FastCGI "
+ "request id high byte: %d", ch);
+ return NGX_ERROR;
+ }
+ state = ngx_http_fastcgi_st_request_id_lo;
+ break;
+
+ case ngx_http_fastcgi_st_request_id_lo:
+ if (ch != 1) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent unexpected FastCGI "
+ "request id low byte: %d", ch);
+ return NGX_ERROR;
+ }
+ state = ngx_http_fastcgi_st_content_length_hi;
+ break;
+
+ case ngx_http_fastcgi_st_content_length_hi:
+ f->length = ch << 8;
+ state = ngx_http_fastcgi_st_content_length_lo;
+ break;
+
+ case ngx_http_fastcgi_st_content_length_lo:
+ f->length |= (size_t) ch;
+ state = ngx_http_fastcgi_st_padding_length;
+ break;
+
+ case ngx_http_fastcgi_st_padding_length:
+ f->padding = (size_t) ch;
+ state = ngx_http_fastcgi_st_reserved;
+ break;
+
+ case ngx_http_fastcgi_st_reserved:
+ state = ngx_http_fastcgi_st_data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http fastcgi record length: %z", f->length);
+
+ f->pos = p + 1;
+ f->state = state;
+
+ return NGX_OK;
+
+ /* suppress warning */
+ case ngx_http_fastcgi_st_data:
+ case ngx_http_fastcgi_st_padding:
+ break;
+ }
+ }
+
+ f->state = state;
+
+ return NGX_AGAIN;
+}
+
+
+static void
+ngx_http_fastcgi_abort_request(ngx_http_request_t *r)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "abort http fastcgi request");
+
+ return;
+}
+
+
+static void
+ngx_http_fastcgi_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "finalize http fastcgi request");
+
+ return;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var, *v;
+
+ for (v = ngx_http_fastcgi_vars; v->name.len; v++) {
+ var = ngx_http_add_variable(cf, &v->name, v->flags);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = v->get_handler;
+ var->data = v->data;
+ }
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_fastcgi_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_fastcgi_main_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_fastcgi_main_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+#if (NGX_HTTP_CACHE)
+ if (ngx_array_init(&conf->caches, cf->pool, 4,
+ sizeof(ngx_http_file_cache_t *))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+#endif
+
+ return conf;
+}
+
+
+static void *
+ngx_http_fastcgi_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_fastcgi_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_fastcgi_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->upstream.bufs.num = 0;
+ * conf->upstream.ignore_headers = 0;
+ * conf->upstream.next_upstream = 0;
+ * conf->upstream.cache_zone = NULL;
+ * conf->upstream.cache_use_stale = 0;
+ * conf->upstream.cache_methods = 0;
+ * conf->upstream.temp_path = NULL;
+ * conf->upstream.hide_headers_hash = { NULL, 0 };
+ * conf->upstream.uri = { 0, NULL };
+ * conf->upstream.location = NULL;
+ * conf->upstream.store_lengths = NULL;
+ * conf->upstream.store_values = NULL;
+ *
+ * conf->index.len = { 0, NULL };
+ */
+
+ conf->upstream.store = NGX_CONF_UNSET;
+ conf->upstream.store_access = NGX_CONF_UNSET_UINT;
+ conf->upstream.next_upstream_tries = NGX_CONF_UNSET_UINT;
+ conf->upstream.buffering = NGX_CONF_UNSET;
+ conf->upstream.request_buffering = NGX_CONF_UNSET;
+ conf->upstream.ignore_client_abort = NGX_CONF_UNSET;
+ conf->upstream.force_ranges = NGX_CONF_UNSET;
+
+ conf->upstream.local = NGX_CONF_UNSET_PTR;
+
+ conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC;
+
+ conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE;
+ conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE;
+ conf->upstream.limit_rate = NGX_CONF_UNSET_SIZE;
+
+ conf->upstream.busy_buffers_size_conf = NGX_CONF_UNSET_SIZE;
+ conf->upstream.max_temp_file_size_conf = NGX_CONF_UNSET_SIZE;
+ conf->upstream.temp_file_write_size_conf = NGX_CONF_UNSET_SIZE;
+
+ conf->upstream.pass_request_headers = NGX_CONF_UNSET;
+ conf->upstream.pass_request_body = NGX_CONF_UNSET;
+
+#if (NGX_HTTP_CACHE)
+ conf->upstream.cache = NGX_CONF_UNSET;
+ conf->upstream.cache_min_uses = NGX_CONF_UNSET_UINT;
+ conf->upstream.cache_max_range_offset = NGX_CONF_UNSET;
+ conf->upstream.cache_bypass = NGX_CONF_UNSET_PTR;
+ conf->upstream.no_cache = NGX_CONF_UNSET_PTR;
+ conf->upstream.cache_valid = NGX_CONF_UNSET_PTR;
+ conf->upstream.cache_lock = NGX_CONF_UNSET;
+ conf->upstream.cache_lock_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.cache_lock_age = NGX_CONF_UNSET_MSEC;
+ conf->upstream.cache_revalidate = NGX_CONF_UNSET;
+ conf->upstream.cache_background_update = NGX_CONF_UNSET;
+#endif
+
+ conf->upstream.hide_headers = NGX_CONF_UNSET_PTR;
+ conf->upstream.pass_headers = NGX_CONF_UNSET_PTR;
+
+ conf->upstream.intercept_errors = NGX_CONF_UNSET;
+
+ /* "fastcgi_cyclic_temp_file" is disabled */
+ conf->upstream.cyclic_temp_file = 0;
+
+ conf->upstream.change_buffering = 1;
+
+ conf->catch_stderr = NGX_CONF_UNSET_PTR;
+
+ conf->keep_conn = NGX_CONF_UNSET;
+
+ ngx_str_set(&conf->upstream.module, "fastcgi");
+
+ return conf;
+}
+
+
+static char *
+ngx_http_fastcgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_fastcgi_loc_conf_t *prev = parent;
+ ngx_http_fastcgi_loc_conf_t *conf = child;
+
+ size_t size;
+ ngx_int_t rc;
+ ngx_hash_init_t hash;
+ ngx_http_core_loc_conf_t *clcf;
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->upstream.store > 0) {
+ conf->upstream.cache = 0;
+ }
+
+ if (conf->upstream.cache > 0) {
+ conf->upstream.store = 0;
+ }
+
+#endif
+
+ if (conf->upstream.store == NGX_CONF_UNSET) {
+ ngx_conf_merge_value(conf->upstream.store,
+ prev->upstream.store, 0);
+
+ conf->upstream.store_lengths = prev->upstream.store_lengths;
+ conf->upstream.store_values = prev->upstream.store_values;
+ }
+
+ ngx_conf_merge_uint_value(conf->upstream.store_access,
+ prev->upstream.store_access, 0600);
+
+ ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries,
+ prev->upstream.next_upstream_tries, 0);
+
+ ngx_conf_merge_value(conf->upstream.buffering,
+ prev->upstream.buffering, 1);
+
+ ngx_conf_merge_value(conf->upstream.request_buffering,
+ prev->upstream.request_buffering, 1);
+
+ ngx_conf_merge_value(conf->upstream.ignore_client_abort,
+ prev->upstream.ignore_client_abort, 0);
+
+ ngx_conf_merge_value(conf->upstream.force_ranges,
+ prev->upstream.force_ranges, 0);
+
+ ngx_conf_merge_ptr_value(conf->upstream.local,
+ prev->upstream.local, NULL);
+
+ ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
+ prev->upstream.connect_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->upstream.send_timeout,
+ prev->upstream.send_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->upstream.read_timeout,
+ prev->upstream.read_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->upstream.next_upstream_timeout,
+ prev->upstream.next_upstream_timeout, 0);
+
+ ngx_conf_merge_size_value(conf->upstream.send_lowat,
+ prev->upstream.send_lowat, 0);
+
+ ngx_conf_merge_size_value(conf->upstream.buffer_size,
+ prev->upstream.buffer_size,
+ (size_t) ngx_pagesize);
+
+ ngx_conf_merge_size_value(conf->upstream.limit_rate,
+ prev->upstream.limit_rate, 0);
+
+
+ ngx_conf_merge_bufs_value(conf->upstream.bufs, prev->upstream.bufs,
+ 8, ngx_pagesize);
+
+ if (conf->upstream.bufs.num < 2) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "there must be at least 2 \"fastcgi_buffers\"");
+ return NGX_CONF_ERROR;
+ }
+
+
+ size = conf->upstream.buffer_size;
+ if (size < conf->upstream.bufs.size) {
+ size = conf->upstream.bufs.size;
+ }
+
+
+ ngx_conf_merge_size_value(conf->upstream.busy_buffers_size_conf,
+ prev->upstream.busy_buffers_size_conf,
+ NGX_CONF_UNSET_SIZE);
+
+ if (conf->upstream.busy_buffers_size_conf == NGX_CONF_UNSET_SIZE) {
+ conf->upstream.busy_buffers_size = 2 * size;
+ } else {
+ conf->upstream.busy_buffers_size =
+ conf->upstream.busy_buffers_size_conf;
+ }
+
+ if (conf->upstream.busy_buffers_size < size) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"fastcgi_busy_buffers_size\" must be equal to or greater than "
+ "the maximum of the value of \"fastcgi_buffer_size\" and "
+ "one of the \"fastcgi_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+ if (conf->upstream.busy_buffers_size
+ > (conf->upstream.bufs.num - 1) * conf->upstream.bufs.size)
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"fastcgi_busy_buffers_size\" must be less than "
+ "the size of all \"fastcgi_buffers\" minus one buffer");
+
+ return NGX_CONF_ERROR;
+ }
+
+
+ ngx_conf_merge_size_value(conf->upstream.temp_file_write_size_conf,
+ prev->upstream.temp_file_write_size_conf,
+ NGX_CONF_UNSET_SIZE);
+
+ if (conf->upstream.temp_file_write_size_conf == NGX_CONF_UNSET_SIZE) {
+ conf->upstream.temp_file_write_size = 2 * size;
+ } else {
+ conf->upstream.temp_file_write_size =
+ conf->upstream.temp_file_write_size_conf;
+ }
+
+ if (conf->upstream.temp_file_write_size < size) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"fastcgi_temp_file_write_size\" must be equal to or greater "
+ "than the maximum of the value of \"fastcgi_buffer_size\" and "
+ "one of the \"fastcgi_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+
+ ngx_conf_merge_size_value(conf->upstream.max_temp_file_size_conf,
+ prev->upstream.max_temp_file_size_conf,
+ NGX_CONF_UNSET_SIZE);
+
+ if (conf->upstream.max_temp_file_size_conf == NGX_CONF_UNSET_SIZE) {
+ conf->upstream.max_temp_file_size = 1024 * 1024 * 1024;
+ } else {
+ conf->upstream.max_temp_file_size =
+ conf->upstream.max_temp_file_size_conf;
+ }
+
+ if (conf->upstream.max_temp_file_size != 0
+ && conf->upstream.max_temp_file_size < size)
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"fastcgi_max_temp_file_size\" must be equal to zero to disable "
+ "temporary files usage or must be equal to or greater than "
+ "the maximum of the value of \"fastcgi_buffer_size\" and "
+ "one of the \"fastcgi_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+
+ ngx_conf_merge_bitmask_value(conf->upstream.ignore_headers,
+ prev->upstream.ignore_headers,
+ NGX_CONF_BITMASK_SET);
+
+
+ ngx_conf_merge_bitmask_value(conf->upstream.next_upstream,
+ prev->upstream.next_upstream,
+ (NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_ERROR
+ |NGX_HTTP_UPSTREAM_FT_TIMEOUT));
+
+ if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) {
+ conf->upstream.next_upstream = NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_OFF;
+ }
+
+ if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path,
+ prev->upstream.temp_path,
+ &ngx_http_fastcgi_temp_path)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->upstream.cache == NGX_CONF_UNSET) {
+ ngx_conf_merge_value(conf->upstream.cache,
+ prev->upstream.cache, 0);
+
+ conf->upstream.cache_zone = prev->upstream.cache_zone;
+ conf->upstream.cache_value = prev->upstream.cache_value;
+ }
+
+ if (conf->upstream.cache_zone && conf->upstream.cache_zone->data == NULL) {
+ ngx_shm_zone_t *shm_zone;
+
+ shm_zone = conf->upstream.cache_zone;
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"fastcgi_cache\" zone \"%V\" is unknown",
+ &shm_zone->shm.name);
+
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_uint_value(conf->upstream.cache_min_uses,
+ prev->upstream.cache_min_uses, 1);
+
+ ngx_conf_merge_off_value(conf->upstream.cache_max_range_offset,
+ prev->upstream.cache_max_range_offset,
+ NGX_MAX_OFF_T_VALUE);
+
+ ngx_conf_merge_bitmask_value(conf->upstream.cache_use_stale,
+ prev->upstream.cache_use_stale,
+ (NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_OFF));
+
+ if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_OFF) {
+ conf->upstream.cache_use_stale = NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_OFF;
+ }
+
+ if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_ERROR) {
+ conf->upstream.cache_use_stale |= NGX_HTTP_UPSTREAM_FT_NOLIVE;
+ }
+
+ if (conf->upstream.cache_methods == 0) {
+ conf->upstream.cache_methods = prev->upstream.cache_methods;
+ }
+
+ conf->upstream.cache_methods |= NGX_HTTP_GET|NGX_HTTP_HEAD;
+
+ ngx_conf_merge_ptr_value(conf->upstream.cache_bypass,
+ prev->upstream.cache_bypass, NULL);
+
+ ngx_conf_merge_ptr_value(conf->upstream.no_cache,
+ prev->upstream.no_cache, NULL);
+
+ ngx_conf_merge_ptr_value(conf->upstream.cache_valid,
+ prev->upstream.cache_valid, NULL);
+
+ if (conf->cache_key.value.data == NULL) {
+ conf->cache_key = prev->cache_key;
+ }
+
+ if (conf->upstream.cache && conf->cache_key.value.data == NULL) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "no \"fastcgi_cache_key\" for \"fastcgi_cache\"");
+ }
+
+ ngx_conf_merge_value(conf->upstream.cache_lock,
+ prev->upstream.cache_lock, 0);
+
+ ngx_conf_merge_msec_value(conf->upstream.cache_lock_timeout,
+ prev->upstream.cache_lock_timeout, 5000);
+
+ ngx_conf_merge_msec_value(conf->upstream.cache_lock_age,
+ prev->upstream.cache_lock_age, 5000);
+
+ ngx_conf_merge_value(conf->upstream.cache_revalidate,
+ prev->upstream.cache_revalidate, 0);
+
+ ngx_conf_merge_value(conf->upstream.cache_background_update,
+ prev->upstream.cache_background_update, 0);
+
+#endif
+
+ ngx_conf_merge_value(conf->upstream.pass_request_headers,
+ prev->upstream.pass_request_headers, 1);
+ ngx_conf_merge_value(conf->upstream.pass_request_body,
+ prev->upstream.pass_request_body, 1);
+
+ ngx_conf_merge_value(conf->upstream.intercept_errors,
+ prev->upstream.intercept_errors, 0);
+
+ ngx_conf_merge_ptr_value(conf->catch_stderr, prev->catch_stderr, NULL);
+
+ ngx_conf_merge_value(conf->keep_conn, prev->keep_conn, 0);
+
+
+ ngx_conf_merge_str_value(conf->index, prev->index, "");
+
+ hash.max_size = 512;
+ hash.bucket_size = ngx_align(64, ngx_cacheline_size);
+ hash.name = "fastcgi_hide_headers_hash";
+
+ if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream,
+ &prev->upstream, ngx_http_fastcgi_hide_headers, &hash)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+ if (clcf->noname
+ && conf->upstream.upstream == NULL && conf->fastcgi_lengths == NULL)
+ {
+ conf->upstream.upstream = prev->upstream.upstream;
+ conf->fastcgi_lengths = prev->fastcgi_lengths;
+ conf->fastcgi_values = prev->fastcgi_values;
+ }
+
+ if (clcf->lmt_excpt && clcf->handler == NULL
+ && (conf->upstream.upstream || conf->fastcgi_lengths))
+ {
+ clcf->handler = ngx_http_fastcgi_handler;
+ }
+
+#if (NGX_PCRE)
+ if (conf->split_regex == NULL) {
+ conf->split_regex = prev->split_regex;
+ conf->split_name = prev->split_name;
+ }
+#endif
+
+ if (conf->params_source == NULL) {
+ conf->params = prev->params;
+#if (NGX_HTTP_CACHE)
+ conf->params_cache = prev->params_cache;
+#endif
+ conf->params_source = prev->params_source;
+ }
+
+ rc = ngx_http_fastcgi_init_params(cf, conf, &conf->params, NULL);
+ if (rc != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->upstream.cache) {
+ rc = ngx_http_fastcgi_init_params(cf, conf, &conf->params_cache,
+ ngx_http_fastcgi_cache_headers);
+ if (rc != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+#endif
+
+ /*
+ * special handling to preserve conf->params in the "http" section
+ * to inherit it to all servers
+ */
+
+ if (prev->params.hash.buckets == NULL
+ && conf->params_source == prev->params_source)
+ {
+ prev->params = conf->params;
+#if (NGX_HTTP_CACHE)
+ prev->params_cache = conf->params_cache;
+#endif
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_init_params(ngx_conf_t *cf, ngx_http_fastcgi_loc_conf_t *conf,
+ ngx_http_fastcgi_params_t *params, ngx_keyval_t *default_params)
+{
+ u_char *p;
+ size_t size;
+ uintptr_t *code;
+ ngx_uint_t i, nsrc;
+ ngx_array_t headers_names, params_merged;
+ ngx_keyval_t *h;
+ ngx_hash_key_t *hk;
+ ngx_hash_init_t hash;
+ ngx_http_upstream_param_t *src, *s;
+ ngx_http_script_compile_t sc;
+ ngx_http_script_copy_code_t *copy;
+
+ if (params->hash.buckets) {
+ return NGX_OK;
+ }
+
+ if (conf->params_source == NULL && default_params == NULL) {
+ params->hash.buckets = (void *) 1;
+ return NGX_OK;
+ }
+
+ params->lengths = ngx_array_create(cf->pool, 64, 1);
+ if (params->lengths == NULL) {
+ return NGX_ERROR;
+ }
+
+ params->values = ngx_array_create(cf->pool, 512, 1);
+ if (params->values == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ if (conf->params_source) {
+ src = conf->params_source->elts;
+ nsrc = conf->params_source->nelts;
+
+ } else {
+ src = NULL;
+ nsrc = 0;
+ }
+
+ if (default_params) {
+ if (ngx_array_init(&params_merged, cf->temp_pool, 4,
+ sizeof(ngx_http_upstream_param_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ for (i = 0; i < nsrc; i++) {
+
+ s = ngx_array_push(&params_merged);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ *s = src[i];
+ }
+
+ h = default_params;
+
+ while (h->key.len) {
+
+ src = params_merged.elts;
+ nsrc = params_merged.nelts;
+
+ for (i = 0; i < nsrc; i++) {
+ if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) {
+ goto next;
+ }
+ }
+
+ s = ngx_array_push(&params_merged);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ s->key = h->key;
+ s->value = h->value;
+ s->skip_empty = 1;
+
+ next:
+
+ h++;
+ }
+
+ src = params_merged.elts;
+ nsrc = params_merged.nelts;
+ }
+
+ for (i = 0; i < nsrc; i++) {
+
+ if (src[i].key.len > sizeof("HTTP_") - 1
+ && ngx_strncmp(src[i].key.data, "HTTP_", sizeof("HTTP_") - 1) == 0)
+ {
+ hk = ngx_array_push(&headers_names);
+ if (hk == NULL) {
+ return NGX_ERROR;
+ }
+
+ hk->key.len = src[i].key.len - 5;
+ hk->key.data = src[i].key.data + 5;
+ hk->key_hash = ngx_hash_key_lc(hk->key.data, hk->key.len);
+ hk->value = (void *) 1;
+
+ if (src[i].value.len == 0) {
+ continue;
+ }
+ }
+
+ copy = ngx_array_push_n(params->lengths,
+ sizeof(ngx_http_script_copy_code_t));
+ if (copy == NULL) {
+ return NGX_ERROR;
+ }
+
+ copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code;
+ copy->len = src[i].key.len;
+
+ copy = ngx_array_push_n(params->lengths,
+ sizeof(ngx_http_script_copy_code_t));
+ if (copy == NULL) {
+ return NGX_ERROR;
+ }
+
+ copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code;
+ copy->len = src[i].skip_empty;
+
+
+ size = (sizeof(ngx_http_script_copy_code_t)
+ + src[i].key.len + sizeof(uintptr_t) - 1)
+ & ~(sizeof(uintptr_t) - 1);
+
+ copy = ngx_array_push_n(params->values, size);
+ if (copy == NULL) {
+ return NGX_ERROR;
+ }
+
+ copy->code = ngx_http_script_copy_code;
+ copy->len = src[i].key.len;
+
+ p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t);
+ ngx_memcpy(p, src[i].key.data, src[i].key.len);
+
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = &src[i].value;
+ sc.flushes = &params->flushes;
+ sc.lengths = &params->lengths;
+ sc.values = &params->values;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ code = ngx_array_push_n(params->lengths, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+
+
+ code = ngx_array_push_n(params->values, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+ }
+
+ code = ngx_array_push_n(params->lengths, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+
+ params->number = headers_names.nelts;
+
+ hash.hash = &params->hash;
+ hash.key = ngx_hash_key_lc;
+ hash.max_size = 512;
+ hash.bucket_size = 64;
+ hash.name = "fastcgi_params_hash";
+ hash.pool = cf->pool;
+ hash.temp_pool = NULL;
+
+ return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts);
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_script_name_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ u_char *p;
+ ngx_http_fastcgi_ctx_t *f;
+ ngx_http_fastcgi_loc_conf_t *flcf;
+
+ flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
+
+ f = ngx_http_fastcgi_split(r, flcf);
+
+ if (f == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (f->script_name.len == 0
+ || f->script_name.data[f->script_name.len - 1] != '/')
+ {
+ v->len = f->script_name.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = f->script_name.data;
+
+ return NGX_OK;
+ }
+
+ v->len = f->script_name.len + flcf->index.len;
+
+ v->data = ngx_pnalloc(r->pool, v->len);
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_copy(v->data, f->script_name.data, f->script_name.len);
+ ngx_memcpy(p, flcf->index.data, flcf->index.len);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_fastcgi_path_info_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_http_fastcgi_ctx_t *f;
+ ngx_http_fastcgi_loc_conf_t *flcf;
+
+ flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module);
+
+ f = ngx_http_fastcgi_split(r, flcf);
+
+ if (f == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = f->path_info.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = f->path_info.data;
+
+ return NGX_OK;
+}
+
+
+static ngx_http_fastcgi_ctx_t *
+ngx_http_fastcgi_split(ngx_http_request_t *r, ngx_http_fastcgi_loc_conf_t *flcf)
+{
+ ngx_http_fastcgi_ctx_t *f;
+#if (NGX_PCRE)
+ ngx_int_t n;
+ int captures[(1 + 2) * 3];
+
+ f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module);
+
+ if (f == NULL) {
+ f = ngx_pcalloc(r->pool, sizeof(ngx_http_fastcgi_ctx_t));
+ if (f == NULL) {
+ return NULL;
+ }
+
+ ngx_http_set_ctx(r, f, ngx_http_fastcgi_module);
+ }
+
+ if (f->script_name.len) {
+ return f;
+ }
+
+ if (flcf->split_regex == NULL) {
+ f->script_name = r->uri;
+ return f;
+ }
+
+ n = ngx_regex_exec(flcf->split_regex, &r->uri, captures, (1 + 2) * 3);
+
+ if (n >= 0) { /* match */
+ f->script_name.len = captures[3] - captures[2];
+ f->script_name.data = r->uri.data + captures[2];
+
+ f->path_info.len = captures[5] - captures[4];
+ f->path_info.data = r->uri.data + captures[4];
+
+ return f;
+ }
+
+ if (n == NGX_REGEX_NO_MATCHED) {
+ f->script_name = r->uri;
+ return f;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ ngx_regex_exec_n " failed: %i on \"%V\" using \"%V\"",
+ n, &r->uri, &flcf->split_name);
+ return NULL;
+
+#else
+
+ f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module);
+
+ if (f == NULL) {
+ f = ngx_pcalloc(r->pool, sizeof(ngx_http_fastcgi_ctx_t));
+ if (f == NULL) {
+ return NULL;
+ }
+
+ ngx_http_set_ctx(r, f, ngx_http_fastcgi_module);
+ }
+
+ f->script_name = r->uri;
+
+ return f;
+
+#endif
+}
+
+
+static char *
+ngx_http_fastcgi_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_fastcgi_loc_conf_t *flcf = conf;
+
+ ngx_url_t u;
+ ngx_str_t *value, *url;
+ ngx_uint_t n;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_script_compile_t sc;
+
+ if (flcf->upstream.upstream || flcf->fastcgi_lengths) {
+ return "is duplicate";
+ }
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+ clcf->handler = ngx_http_fastcgi_handler;
+
+ if (clcf->name.data[clcf->name.len - 1] == '/') {
+ clcf->auto_redirect = 1;
+ }
+
+ value = cf->args->elts;
+
+ url = &value[1];
+
+ n = ngx_http_script_variables_count(url);
+
+ if (n) {
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = url;
+ sc.lengths = &flcf->fastcgi_lengths;
+ sc.values = &flcf->fastcgi_values;
+ sc.variables = n;
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+ }
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.url = value[1];
+ u.no_resolve = 1;
+
+ flcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
+ if (flcf->upstream.upstream == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_fastcgi_split_path_info(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+#if (NGX_PCRE)
+ ngx_http_fastcgi_loc_conf_t *flcf = conf;
+
+ ngx_str_t *value;
+ ngx_regex_compile_t rc;
+ u_char errstr[NGX_MAX_CONF_ERRSTR];
+
+ value = cf->args->elts;
+
+ flcf->split_name = value[1];
+
+ ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+ rc.pattern = value[1];
+ rc.pool = cf->pool;
+ rc.err.len = NGX_MAX_CONF_ERRSTR;
+ rc.err.data = errstr;
+
+ if (ngx_regex_compile(&rc) != NGX_OK) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
+ return NGX_CONF_ERROR;
+ }
+
+ if (rc.captures != 2) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "pattern \"%V\" must have 2 captures", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ flcf->split_regex = rc.regex;
+
+ return NGX_CONF_OK;
+
+#else
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"%V\" requires PCRE library", &cmd->name);
+ return NGX_CONF_ERROR;
+
+#endif
+}
+
+
+static char *
+ngx_http_fastcgi_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_fastcgi_loc_conf_t *flcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_script_compile_t sc;
+
+ if (flcf->upstream.store != NGX_CONF_UNSET) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ flcf->upstream.store = 0;
+ return NGX_CONF_OK;
+ }
+
+#if (NGX_HTTP_CACHE)
+ if (flcf->upstream.cache > 0) {
+ return "is incompatible with \"fastcgi_cache\"";
+ }
+#endif
+
+ flcf->upstream.store = 1;
+
+ if (ngx_strcmp(value[1].data, "on") == 0) {
+ return NGX_CONF_OK;
+ }
+
+ /* include the terminating '\0' into script */
+ value[1].len++;
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = &value[1];
+ sc.lengths = &flcf->upstream.store_lengths;
+ sc.values = &flcf->upstream.store_values;
+ sc.variables = ngx_http_script_variables_count(&value[1]);
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static char *
+ngx_http_fastcgi_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_fastcgi_loc_conf_t *flcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_complex_value_t cv;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ if (flcf->upstream.cache != NGX_CONF_UNSET) {
+ return "is duplicate";
+ }
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ flcf->upstream.cache = 0;
+ return NGX_CONF_OK;
+ }
+
+ if (flcf->upstream.store > 0) {
+ return "is incompatible with \"fastcgi_store\"";
+ }
+
+ flcf->upstream.cache = 1;
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cv.lengths != NULL) {
+
+ flcf->upstream.cache_value = ngx_palloc(cf->pool,
+ sizeof(ngx_http_complex_value_t));
+ if (flcf->upstream.cache_value == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *flcf->upstream.cache_value = cv;
+
+ return NGX_CONF_OK;
+ }
+
+ flcf->upstream.cache_zone = ngx_shared_memory_add(cf, &value[1], 0,
+ &ngx_http_fastcgi_module);
+ if (flcf->upstream.cache_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_fastcgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_fastcgi_loc_conf_t *flcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ if (flcf->cache_key.value.data) {
+ return "is duplicate";
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &flcf->cache_key;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+#endif
+
+
+static char *
+ngx_http_fastcgi_lowat_check(ngx_conf_t *cf, void *post, void *data)
+{
+#if (NGX_FREEBSD)
+ ssize_t *np = data;
+
+ if ((u_long) *np >= ngx_freebsd_net_inet_tcp_sendspace) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"fastcgi_send_lowat\" must be less than %d "
+ "(sysctl net.inet.tcp.sendspace)",
+ ngx_freebsd_net_inet_tcp_sendspace);
+
+ return NGX_CONF_ERROR;
+ }
+
+#elif !(NGX_HAVE_SO_SNDLOWAT)
+ ssize_t *np = data;
+
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "\"fastcgi_send_lowat\" is not supported, ignored");
+
+ *np = 0;
+
+#endif
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_flv_module.c b/app/nginx/src/http/modules/ngx_http_flv_module.c
new file mode 100644
index 0000000..cc25320
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_flv_module.c
@@ -0,0 +1,266 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static char *ngx_http_flv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+static ngx_command_t ngx_http_flv_commands[] = {
+
+ { ngx_string("flv"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
+ ngx_http_flv,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static u_char ngx_flv_header[] = "FLV\x1\x5\0\0\0\x9\0\0\0\0";
+
+
+static ngx_http_module_t ngx_http_flv_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_flv_module = {
+ NGX_MODULE_V1,
+ &ngx_http_flv_module_ctx, /* module context */
+ ngx_http_flv_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_flv_handler(ngx_http_request_t *r)
+{
+ u_char *last;
+ off_t start, len;
+ size_t root;
+ ngx_int_t rc;
+ ngx_uint_t level, i;
+ ngx_str_t path, value;
+ ngx_log_t *log;
+ ngx_buf_t *b;
+ ngx_chain_t out[2];
+ ngx_open_file_info_t of;
+ ngx_http_core_loc_conf_t *clcf;
+
+ if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
+ return NGX_HTTP_NOT_ALLOWED;
+ }
+
+ if (r->uri.data[r->uri.len - 1] == '/') {
+ return NGX_DECLINED;
+ }
+
+ rc = ngx_http_discard_request_body(r);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ last = ngx_http_map_uri_to_path(r, &path, &root, 0);
+ if (last == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ log = r->connection->log;
+
+ path.len = last - path.data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "http flv filename: \"%V\"", &path);
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+ of.read_ahead = clcf->read_ahead;
+ of.directio = clcf->directio;
+ of.valid = clcf->open_file_cache_valid;
+ of.min_uses = clcf->open_file_cache_min_uses;
+ of.errors = clcf->open_file_cache_errors;
+ of.events = clcf->open_file_cache_events;
+
+ if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+ != NGX_OK)
+ {
+ switch (of.err) {
+
+ case 0:
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+
+ case NGX_ENOENT:
+ case NGX_ENOTDIR:
+ case NGX_ENAMETOOLONG:
+
+ level = NGX_LOG_ERR;
+ rc = NGX_HTTP_NOT_FOUND;
+ break;
+
+ case NGX_EACCES:
+#if (NGX_HAVE_OPENAT)
+ case NGX_EMLINK:
+ case NGX_ELOOP:
+#endif
+
+ level = NGX_LOG_ERR;
+ rc = NGX_HTTP_FORBIDDEN;
+ break;
+
+ default:
+
+ level = NGX_LOG_CRIT;
+ rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ }
+
+ if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
+ ngx_log_error(level, log, of.err,
+ "%s \"%s\" failed", of.failed, path.data);
+ }
+
+ return rc;
+ }
+
+ if (!of.is_file) {
+
+ if (ngx_close_file(of.fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", path.data);
+ }
+
+ return NGX_DECLINED;
+ }
+
+ r->root_tested = !r->error_page;
+
+ start = 0;
+ len = of.size;
+ i = 1;
+
+ if (r->args.len) {
+
+ if (ngx_http_arg(r, (u_char *) "start", 5, &value) == NGX_OK) {
+
+ start = ngx_atoof(value.data, value.len);
+
+ if (start == NGX_ERROR || start >= len) {
+ start = 0;
+ }
+
+ if (start) {
+ len = sizeof(ngx_flv_header) - 1 + len - start;
+ i = 0;
+ }
+ }
+ }
+
+ log->action = "sending flv to client";
+
+ r->headers_out.status = NGX_HTTP_OK;
+ r->headers_out.content_length_n = len;
+ r->headers_out.last_modified_time = of.mtime;
+
+ if (ngx_http_set_etag(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_http_set_content_type(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (i == 0) {
+ b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
+ if (b == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ b->pos = ngx_flv_header;
+ b->last = ngx_flv_header + sizeof(ngx_flv_header) - 1;
+ b->memory = 1;
+
+ out[0].buf = b;
+ out[0].next = &out[1];
+ }
+
+
+ b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
+ if (b == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
+ if (b->file == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ r->allow_ranges = 1;
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ return rc;
+ }
+
+ b->file_pos = start;
+ b->file_last = of.size;
+
+ b->in_file = b->file_last ? 1: 0;
+ b->last_buf = (r == r->main) ? 1 : 0;
+ b->last_in_chain = 1;
+
+ b->file->fd = of.fd;
+ b->file->name = path;
+ b->file->log = log;
+ b->file->directio = of.is_directio;
+
+ out[1].buf = b;
+ out[1].next = NULL;
+
+ return ngx_http_output_filter(r, &out[i]);
+}
+
+
+static char *
+ngx_http_flv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf;
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+ clcf->handler = ngx_http_flv_handler;
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_geo_module.c b/app/nginx/src/http/modules/ngx_http_geo_module.c
new file mode 100644
index 0000000..46a8d7c
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_geo_module.c
@@ -0,0 +1,1658 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_http_variable_value_t *value;
+ u_short start;
+ u_short end;
+} ngx_http_geo_range_t;
+
+
+typedef struct {
+ ngx_radix_tree_t *tree;
+#if (NGX_HAVE_INET6)
+ ngx_radix_tree_t *tree6;
+#endif
+} ngx_http_geo_trees_t;
+
+
+typedef struct {
+ ngx_http_geo_range_t **low;
+ ngx_http_variable_value_t *default_value;
+} ngx_http_geo_high_ranges_t;
+
+
+typedef struct {
+ ngx_str_node_t sn;
+ ngx_http_variable_value_t *value;
+ size_t offset;
+} ngx_http_geo_variable_value_node_t;
+
+
+typedef struct {
+ ngx_http_variable_value_t *value;
+ ngx_str_t *net;
+ ngx_http_geo_high_ranges_t high;
+ ngx_radix_tree_t *tree;
+#if (NGX_HAVE_INET6)
+ ngx_radix_tree_t *tree6;
+#endif
+ ngx_rbtree_t rbtree;
+ ngx_rbtree_node_t sentinel;
+ ngx_array_t *proxies;
+ ngx_pool_t *pool;
+ ngx_pool_t *temp_pool;
+
+ size_t data_size;
+
+ ngx_str_t include_name;
+ ngx_uint_t includes;
+ ngx_uint_t entries;
+
+ unsigned ranges:1;
+ unsigned outside_entries:1;
+ unsigned allow_binary_include:1;
+ unsigned binary_include:1;
+ unsigned proxy_recursive:1;
+} ngx_http_geo_conf_ctx_t;
+
+
+typedef struct {
+ union {
+ ngx_http_geo_trees_t trees;
+ ngx_http_geo_high_ranges_t high;
+ } u;
+
+ ngx_array_t *proxies;
+ unsigned proxy_recursive:1;
+
+ ngx_int_t index;
+} ngx_http_geo_ctx_t;
+
+
+static ngx_int_t ngx_http_geo_addr(ngx_http_request_t *r,
+ ngx_http_geo_ctx_t *ctx, ngx_addr_t *addr);
+static ngx_int_t ngx_http_geo_real_addr(ngx_http_request_t *r,
+ ngx_http_geo_ctx_t *ctx, ngx_addr_t *addr);
+static char *ngx_http_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
+static char *ngx_http_geo_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+ ngx_str_t *value);
+static char *ngx_http_geo_add_range(ngx_conf_t *cf,
+ ngx_http_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end);
+static ngx_uint_t ngx_http_geo_delete_range(ngx_conf_t *cf,
+ ngx_http_geo_conf_ctx_t *ctx, in_addr_t start, in_addr_t end);
+static char *ngx_http_geo_cidr(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+ ngx_str_t *value);
+static char *ngx_http_geo_cidr_add(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+ ngx_cidr_t *cidr, ngx_str_t *value, ngx_str_t *net);
+static ngx_http_variable_value_t *ngx_http_geo_value(ngx_conf_t *cf,
+ ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *value);
+static char *ngx_http_geo_add_proxy(ngx_conf_t *cf,
+ ngx_http_geo_conf_ctx_t *ctx, ngx_cidr_t *cidr);
+static ngx_int_t ngx_http_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net,
+ ngx_cidr_t *cidr);
+static char *ngx_http_geo_include(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+ ngx_str_t *name);
+static ngx_int_t ngx_http_geo_include_binary_base(ngx_conf_t *cf,
+ ngx_http_geo_conf_ctx_t *ctx, ngx_str_t *name);
+static void ngx_http_geo_create_binary_base(ngx_http_geo_conf_ctx_t *ctx);
+static u_char *ngx_http_geo_copy_values(u_char *base, u_char *p,
+ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+
+
+static ngx_command_t ngx_http_geo_commands[] = {
+
+ { ngx_string("geo"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
+ ngx_http_geo_block,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_geo_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_geo_module = {
+ NGX_MODULE_V1,
+ &ngx_http_geo_module_ctx, /* module context */
+ ngx_http_geo_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+typedef struct {
+ u_char GEORNG[6];
+ u_char version;
+ u_char ptr_size;
+ uint32_t endianness;
+ uint32_t crc32;
+} ngx_http_geo_header_t;
+
+
+static ngx_http_geo_header_t ngx_http_geo_header = {
+ { 'G', 'E', 'O', 'R', 'N', 'G' }, 0, sizeof(void *), 0x12345678, 0
+};
+
+
+/* geo range is AF_INET only */
+
+static ngx_int_t
+ngx_http_geo_cidr_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_http_geo_ctx_t *ctx = (ngx_http_geo_ctx_t *) data;
+
+ in_addr_t inaddr;
+ ngx_addr_t addr;
+ struct sockaddr_in *sin;
+ ngx_http_variable_value_t *vv;
+#if (NGX_HAVE_INET6)
+ u_char *p;
+ struct in6_addr *inaddr6;
+#endif
+
+ if (ngx_http_geo_addr(r, ctx, &addr) != NGX_OK) {
+ vv = (ngx_http_variable_value_t *)
+ ngx_radix32tree_find(ctx->u.trees.tree, INADDR_NONE);
+ goto done;
+ }
+
+ switch (addr.sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr;
+ p = inaddr6->s6_addr;
+
+ if (IN6_IS_ADDR_V4MAPPED(inaddr6)) {
+ inaddr = p[12] << 24;
+ inaddr += p[13] << 16;
+ inaddr += p[14] << 8;
+ inaddr += p[15];
+
+ vv = (ngx_http_variable_value_t *)
+ ngx_radix32tree_find(ctx->u.trees.tree, inaddr);
+
+ } else {
+ vv = (ngx_http_variable_value_t *)
+ ngx_radix128tree_find(ctx->u.trees.tree6, p);
+ }
+
+ break;
+#endif
+
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) addr.sockaddr;
+ inaddr = ntohl(sin->sin_addr.s_addr);
+
+ vv = (ngx_http_variable_value_t *)
+ ngx_radix32tree_find(ctx->u.trees.tree, inaddr);
+
+ break;
+ }
+
+done:
+
+ *v = *vv;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http geo: %v", v);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geo_range_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_http_geo_ctx_t *ctx = (ngx_http_geo_ctx_t *) data;
+
+ in_addr_t inaddr;
+ ngx_addr_t addr;
+ ngx_uint_t n;
+ struct sockaddr_in *sin;
+ ngx_http_geo_range_t *range;
+#if (NGX_HAVE_INET6)
+ u_char *p;
+ struct in6_addr *inaddr6;
+#endif
+
+ *v = *ctx->u.high.default_value;
+
+ if (ngx_http_geo_addr(r, ctx, &addr) == NGX_OK) {
+
+ switch (addr.sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr;
+
+ if (IN6_IS_ADDR_V4MAPPED(inaddr6)) {
+ p = inaddr6->s6_addr;
+
+ inaddr = p[12] << 24;
+ inaddr += p[13] << 16;
+ inaddr += p[14] << 8;
+ inaddr += p[15];
+
+ } else {
+ inaddr = INADDR_NONE;
+ }
+
+ break;
+#endif
+
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) addr.sockaddr;
+ inaddr = ntohl(sin->sin_addr.s_addr);
+ break;
+ }
+
+ } else {
+ inaddr = INADDR_NONE;
+ }
+
+ if (ctx->u.high.low) {
+ range = ctx->u.high.low[inaddr >> 16];
+
+ if (range) {
+ n = inaddr & 0xffff;
+ do {
+ if (n >= (ngx_uint_t) range->start
+ && n <= (ngx_uint_t) range->end)
+ {
+ *v = *range->value;
+ break;
+ }
+ } while ((++range)->value);
+ }
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http geo: %v", v);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geo_addr(ngx_http_request_t *r, ngx_http_geo_ctx_t *ctx,
+ ngx_addr_t *addr)
+{
+ ngx_array_t *xfwd;
+
+ if (ngx_http_geo_real_addr(r, ctx, addr) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ xfwd = &r->headers_in.x_forwarded_for;
+
+ if (xfwd->nelts > 0 && ctx->proxies != NULL) {
+ (void) ngx_http_get_forwarded_addr(r, addr, xfwd, NULL,
+ ctx->proxies, ctx->proxy_recursive);
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geo_real_addr(ngx_http_request_t *r, ngx_http_geo_ctx_t *ctx,
+ ngx_addr_t *addr)
+{
+ ngx_http_variable_value_t *v;
+
+ if (ctx->index == -1) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http geo started: %V", &r->connection->addr_text);
+
+ addr->sockaddr = r->connection->sockaddr;
+ addr->socklen = r->connection->socklen;
+ /* addr->name = r->connection->addr_text; */
+
+ return NGX_OK;
+ }
+
+ v = ngx_http_get_flushed_variable(r, ctx->index);
+
+ if (v == NULL || v->not_found) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http geo not found");
+
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http geo started: %v", v);
+
+ if (ngx_parse_addr(r->pool, addr, v->data, v->len) == NGX_OK) {
+ return NGX_OK;
+ }
+
+ return NGX_ERROR;
+}
+
+
+static char *
+ngx_http_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *rv;
+ size_t len;
+ ngx_str_t *value, name;
+ ngx_uint_t i;
+ ngx_conf_t save;
+ ngx_pool_t *pool;
+ ngx_array_t *a;
+ ngx_http_variable_t *var;
+ ngx_http_geo_ctx_t *geo;
+ ngx_http_geo_conf_ctx_t ctx;
+#if (NGX_HAVE_INET6)
+ static struct in6_addr zero;
+#endif
+
+ value = cf->args->elts;
+
+ geo = ngx_palloc(cf->pool, sizeof(ngx_http_geo_ctx_t));
+ if (geo == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ name = value[1];
+
+ if (name.data[0] != '$') {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid variable name \"%V\"", &name);
+ return NGX_CONF_ERROR;
+ }
+
+ name.len--;
+ name.data++;
+
+ if (cf->args->nelts == 3) {
+
+ geo->index = ngx_http_get_variable_index(cf, &name);
+ if (geo->index == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ name = value[2];
+
+ if (name.data[0] != '$') {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid variable name \"%V\"", &name);
+ return NGX_CONF_ERROR;
+ }
+
+ name.len--;
+ name.data++;
+
+ } else {
+ geo->index = -1;
+ }
+
+ var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
+ if (var == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
+ if (pool == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&ctx, sizeof(ngx_http_geo_conf_ctx_t));
+
+ ctx.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
+ if (ctx.temp_pool == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_rbtree_init(&ctx.rbtree, &ctx.sentinel, ngx_str_rbtree_insert_value);
+
+ ctx.pool = cf->pool;
+ ctx.data_size = sizeof(ngx_http_geo_header_t)
+ + sizeof(ngx_http_variable_value_t)
+ + 0x10000 * sizeof(ngx_http_geo_range_t *);
+ ctx.allow_binary_include = 1;
+
+ save = *cf;
+ cf->pool = pool;
+ cf->ctx = &ctx;
+ cf->handler = ngx_http_geo;
+ cf->handler_conf = conf;
+
+ rv = ngx_conf_parse(cf, NULL);
+
+ *cf = save;
+
+ geo->proxies = ctx.proxies;
+ geo->proxy_recursive = ctx.proxy_recursive;
+
+ if (ctx.ranges) {
+
+ if (ctx.high.low && !ctx.binary_include) {
+ for (i = 0; i < 0x10000; i++) {
+ a = (ngx_array_t *) ctx.high.low[i];
+
+ if (a == NULL) {
+ continue;
+ }
+
+ if (a->nelts == 0) {
+ ctx.high.low[i] = NULL;
+ continue;
+ }
+
+ len = a->nelts * sizeof(ngx_http_geo_range_t);
+
+ ctx.high.low[i] = ngx_palloc(cf->pool, len + sizeof(void *));
+ if (ctx.high.low[i] == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memcpy(ctx.high.low[i], a->elts, len);
+ ctx.high.low[i][a->nelts].value = NULL;
+ ctx.data_size += len + sizeof(void *);
+ }
+
+ if (ctx.allow_binary_include
+ && !ctx.outside_entries
+ && ctx.entries > 100000
+ && ctx.includes == 1)
+ {
+ ngx_http_geo_create_binary_base(&ctx);
+ }
+ }
+
+ if (ctx.high.default_value == NULL) {
+ ctx.high.default_value = &ngx_http_variable_null_value;
+ }
+
+ geo->u.high = ctx.high;
+
+ var->get_handler = ngx_http_geo_range_variable;
+ var->data = (uintptr_t) geo;
+
+ ngx_destroy_pool(ctx.temp_pool);
+ ngx_destroy_pool(pool);
+
+ } else {
+ if (ctx.tree == NULL) {
+ ctx.tree = ngx_radix_tree_create(cf->pool, -1);
+ if (ctx.tree == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ geo->u.trees.tree = ctx.tree;
+
+#if (NGX_HAVE_INET6)
+ if (ctx.tree6 == NULL) {
+ ctx.tree6 = ngx_radix_tree_create(cf->pool, -1);
+ if (ctx.tree6 == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ geo->u.trees.tree6 = ctx.tree6;
+#endif
+
+ var->get_handler = ngx_http_geo_cidr_variable;
+ var->data = (uintptr_t) geo;
+
+ ngx_destroy_pool(ctx.temp_pool);
+ ngx_destroy_pool(pool);
+
+ if (ngx_radix32tree_insert(ctx.tree, 0, 0,
+ (uintptr_t) &ngx_http_variable_null_value)
+ == NGX_ERROR)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ /* NGX_BUSY is okay (default was set explicitly) */
+
+#if (NGX_HAVE_INET6)
+ if (ngx_radix128tree_insert(ctx.tree6, zero.s6_addr, zero.s6_addr,
+ (uintptr_t) &ngx_http_variable_null_value)
+ == NGX_ERROR)
+ {
+ return NGX_CONF_ERROR;
+ }
+#endif
+ }
+
+ return rv;
+}
+
+
+static char *
+ngx_http_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+ char *rv;
+ ngx_str_t *value;
+ ngx_cidr_t cidr;
+ ngx_http_geo_conf_ctx_t *ctx;
+
+ ctx = cf->ctx;
+
+ value = cf->args->elts;
+
+ if (cf->args->nelts == 1) {
+
+ if (ngx_strcmp(value[0].data, "ranges") == 0) {
+
+ if (ctx->tree
+#if (NGX_HAVE_INET6)
+ || ctx->tree6
+#endif
+ )
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the \"ranges\" directive must be "
+ "the first directive inside \"geo\" block");
+ goto failed;
+ }
+
+ ctx->ranges = 1;
+
+ rv = NGX_CONF_OK;
+
+ goto done;
+ }
+
+ else if (ngx_strcmp(value[0].data, "proxy_recursive") == 0) {
+ ctx->proxy_recursive = 1;
+ rv = NGX_CONF_OK;
+ goto done;
+ }
+ }
+
+ if (cf->args->nelts != 2) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid number of the geo parameters");
+ goto failed;
+ }
+
+ if (ngx_strcmp(value[0].data, "include") == 0) {
+
+ rv = ngx_http_geo_include(cf, ctx, &value[1]);
+
+ goto done;
+
+ } else if (ngx_strcmp(value[0].data, "proxy") == 0) {
+
+ if (ngx_http_geo_cidr_value(cf, &value[1], &cidr) != NGX_OK) {
+ goto failed;
+ }
+
+ rv = ngx_http_geo_add_proxy(cf, ctx, &cidr);
+
+ goto done;
+ }
+
+ if (ctx->ranges) {
+ rv = ngx_http_geo_range(cf, ctx, value);
+
+ } else {
+ rv = ngx_http_geo_cidr(cf, ctx, value);
+ }
+
+done:
+
+ ngx_reset_pool(cf->pool);
+
+ return rv;
+
+failed:
+
+ ngx_reset_pool(cf->pool);
+
+ return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_http_geo_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+ ngx_str_t *value)
+{
+ u_char *p, *last;
+ in_addr_t start, end;
+ ngx_str_t *net;
+ ngx_uint_t del;
+
+ if (ngx_strcmp(value[0].data, "default") == 0) {
+
+ if (ctx->high.default_value) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "duplicate default geo range value: \"%V\", old value: \"%v\"",
+ &value[1], ctx->high.default_value);
+ }
+
+ ctx->high.default_value = ngx_http_geo_value(cf, ctx, &value[1]);
+ if (ctx->high.default_value == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+ }
+
+ if (ctx->binary_include) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "binary geo range base \"%s\" cannot be mixed with usual entries",
+ ctx->include_name.data);
+ return NGX_CONF_ERROR;
+ }
+
+ if (ctx->high.low == NULL) {
+ ctx->high.low = ngx_pcalloc(ctx->pool,
+ 0x10000 * sizeof(ngx_http_geo_range_t *));
+ if (ctx->high.low == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ ctx->entries++;
+ ctx->outside_entries = 1;
+
+ if (ngx_strcmp(value[0].data, "delete") == 0) {
+ net = &value[1];
+ del = 1;
+
+ } else {
+ net = &value[0];
+ del = 0;
+ }
+
+ last = net->data + net->len;
+
+ p = ngx_strlchr(net->data, last, '-');
+
+ if (p == NULL) {
+ goto invalid;
+ }
+
+ start = ngx_inet_addr(net->data, p - net->data);
+
+ if (start == INADDR_NONE) {
+ goto invalid;
+ }
+
+ start = ntohl(start);
+
+ p++;
+
+ end = ngx_inet_addr(p, last - p);
+
+ if (end == INADDR_NONE) {
+ goto invalid;
+ }
+
+ end = ntohl(end);
+
+ if (start > end) {
+ goto invalid;
+ }
+
+ if (del) {
+ if (ngx_http_geo_delete_range(cf, ctx, start, end)) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "no address range \"%V\" to delete", net);
+ }
+
+ return NGX_CONF_OK;
+ }
+
+ ctx->value = ngx_http_geo_value(cf, ctx, &value[1]);
+
+ if (ctx->value == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ctx->net = net;
+
+ return ngx_http_geo_add_range(cf, ctx, start, end);
+
+invalid:
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid range \"%V\"", net);
+
+ return NGX_CONF_ERROR;
+}
+
+
+/* the add procedure is optimized to add a growing up sequence */
+
+static char *
+ngx_http_geo_add_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+ in_addr_t start, in_addr_t end)
+{
+ in_addr_t n;
+ ngx_uint_t h, i, s, e;
+ ngx_array_t *a;
+ ngx_http_geo_range_t *range;
+
+ for (n = start; n <= end; n = (n + 0x10000) & 0xffff0000) {
+
+ h = n >> 16;
+
+ if (n == start) {
+ s = n & 0xffff;
+ } else {
+ s = 0;
+ }
+
+ if ((n | 0xffff) > end) {
+ e = end & 0xffff;
+
+ } else {
+ e = 0xffff;
+ }
+
+ a = (ngx_array_t *) ctx->high.low[h];
+
+ if (a == NULL) {
+ a = ngx_array_create(ctx->temp_pool, 64,
+ sizeof(ngx_http_geo_range_t));
+ if (a == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ctx->high.low[h] = (ngx_http_geo_range_t *) a;
+ }
+
+ i = a->nelts;
+ range = a->elts;
+
+ while (i) {
+
+ i--;
+
+ if (e < (ngx_uint_t) range[i].start) {
+ continue;
+ }
+
+ if (s > (ngx_uint_t) range[i].end) {
+
+ /* add after the range */
+
+ range = ngx_array_push(a);
+ if (range == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ range = a->elts;
+
+ ngx_memmove(&range[i + 2], &range[i + 1],
+ (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t));
+
+ range[i + 1].start = (u_short) s;
+ range[i + 1].end = (u_short) e;
+ range[i + 1].value = ctx->value;
+
+ goto next;
+ }
+
+ if (s == (ngx_uint_t) range[i].start
+ && e == (ngx_uint_t) range[i].end)
+ {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "duplicate range \"%V\", value: \"%v\", old value: \"%v\"",
+ ctx->net, ctx->value, range[i].value);
+
+ range[i].value = ctx->value;
+
+ goto next;
+ }
+
+ if (s > (ngx_uint_t) range[i].start
+ && e < (ngx_uint_t) range[i].end)
+ {
+ /* split the range and insert the new one */
+
+ range = ngx_array_push(a);
+ if (range == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ range = ngx_array_push(a);
+ if (range == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ range = a->elts;
+
+ ngx_memmove(&range[i + 3], &range[i + 1],
+ (a->nelts - 3 - i) * sizeof(ngx_http_geo_range_t));
+
+ range[i + 2].start = (u_short) (e + 1);
+ range[i + 2].end = range[i].end;
+ range[i + 2].value = range[i].value;
+
+ range[i + 1].start = (u_short) s;
+ range[i + 1].end = (u_short) e;
+ range[i + 1].value = ctx->value;
+
+ range[i].end = (u_short) (s - 1);
+
+ goto next;
+ }
+
+ if (s == (ngx_uint_t) range[i].start
+ && e < (ngx_uint_t) range[i].end)
+ {
+ /* shift the range start and insert the new range */
+
+ range = ngx_array_push(a);
+ if (range == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ range = a->elts;
+
+ ngx_memmove(&range[i + 1], &range[i],
+ (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t));
+
+ range[i + 1].start = (u_short) (e + 1);
+
+ range[i].start = (u_short) s;
+ range[i].end = (u_short) e;
+ range[i].value = ctx->value;
+
+ goto next;
+ }
+
+ if (s > (ngx_uint_t) range[i].start
+ && e == (ngx_uint_t) range[i].end)
+ {
+ /* shift the range end and insert the new range */
+
+ range = ngx_array_push(a);
+ if (range == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ range = a->elts;
+
+ ngx_memmove(&range[i + 2], &range[i + 1],
+ (a->nelts - 2 - i) * sizeof(ngx_http_geo_range_t));
+
+ range[i + 1].start = (u_short) s;
+ range[i + 1].end = (u_short) e;
+ range[i + 1].value = ctx->value;
+
+ range[i].end = (u_short) (s - 1);
+
+ goto next;
+ }
+
+ s = (ngx_uint_t) range[i].start;
+ e = (ngx_uint_t) range[i].end;
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "range \"%V\" overlaps \"%d.%d.%d.%d-%d.%d.%d.%d\"",
+ ctx->net,
+ h >> 8, h & 0xff, s >> 8, s & 0xff,
+ h >> 8, h & 0xff, e >> 8, e & 0xff);
+
+ return NGX_CONF_ERROR;
+ }
+
+ /* add the first range */
+
+ range = ngx_array_push(a);
+ if (range == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ range = a->elts;
+
+ ngx_memmove(&range[1], &range[0],
+ (a->nelts - 1) * sizeof(ngx_http_geo_range_t));
+
+ range[0].start = (u_short) s;
+ range[0].end = (u_short) e;
+ range[0].value = ctx->value;
+
+ next:
+
+ if (h == 0xffff) {
+ break;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_uint_t
+ngx_http_geo_delete_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+ in_addr_t start, in_addr_t end)
+{
+ in_addr_t n;
+ ngx_uint_t h, i, s, e, warn;
+ ngx_array_t *a;
+ ngx_http_geo_range_t *range;
+
+ warn = 0;
+
+ for (n = start; n <= end; n = (n + 0x10000) & 0xffff0000) {
+
+ h = n >> 16;
+
+ if (n == start) {
+ s = n & 0xffff;
+ } else {
+ s = 0;
+ }
+
+ if ((n | 0xffff) > end) {
+ e = end & 0xffff;
+
+ } else {
+ e = 0xffff;
+ }
+
+ a = (ngx_array_t *) ctx->high.low[h];
+
+ if (a == NULL || a->nelts == 0) {
+ warn = 1;
+ goto next;
+ }
+
+ range = a->elts;
+ for (i = 0; i < a->nelts; i++) {
+
+ if (s == (ngx_uint_t) range[i].start
+ && e == (ngx_uint_t) range[i].end)
+ {
+ ngx_memmove(&range[i], &range[i + 1],
+ (a->nelts - 1 - i) * sizeof(ngx_http_geo_range_t));
+
+ a->nelts--;
+
+ break;
+ }
+
+ if (i == a->nelts - 1) {
+ warn = 1;
+ }
+ }
+
+ next:
+
+ if (h == 0xffff) {
+ break;
+ }
+ }
+
+ return warn;
+}
+
+
+static char *
+ngx_http_geo_cidr(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+ ngx_str_t *value)
+{
+ char *rv;
+ ngx_int_t rc, del;
+ ngx_str_t *net;
+ ngx_cidr_t cidr;
+
+ if (ctx->tree == NULL) {
+ ctx->tree = ngx_radix_tree_create(ctx->pool, -1);
+ if (ctx->tree == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+#if (NGX_HAVE_INET6)
+ if (ctx->tree6 == NULL) {
+ ctx->tree6 = ngx_radix_tree_create(ctx->pool, -1);
+ if (ctx->tree6 == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+#endif
+
+ if (ngx_strcmp(value[0].data, "default") == 0) {
+ cidr.family = AF_INET;
+ cidr.u.in.addr = 0;
+ cidr.u.in.mask = 0;
+
+ rv = ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]);
+
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+
+#if (NGX_HAVE_INET6)
+ cidr.family = AF_INET6;
+ ngx_memzero(&cidr.u.in6, sizeof(ngx_in6_cidr_t));
+
+ rv = ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]);
+
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+#endif
+
+ return NGX_CONF_OK;
+ }
+
+ if (ngx_strcmp(value[0].data, "delete") == 0) {
+ net = &value[1];
+ del = 1;
+
+ } else {
+ net = &value[0];
+ del = 0;
+ }
+
+ if (ngx_http_geo_cidr_value(cf, net, &cidr) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cidr.family == AF_INET) {
+ cidr.u.in.addr = ntohl(cidr.u.in.addr);
+ cidr.u.in.mask = ntohl(cidr.u.in.mask);
+ }
+
+ if (del) {
+ switch (cidr.family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ rc = ngx_radix128tree_delete(ctx->tree6,
+ cidr.u.in6.addr.s6_addr,
+ cidr.u.in6.mask.s6_addr);
+ break;
+#endif
+
+ default: /* AF_INET */
+ rc = ngx_radix32tree_delete(ctx->tree, cidr.u.in.addr,
+ cidr.u.in.mask);
+ break;
+ }
+
+ if (rc != NGX_OK) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "no network \"%V\" to delete", net);
+ }
+
+ return NGX_CONF_OK;
+ }
+
+ return ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], net);
+}
+
+
+static char *
+ngx_http_geo_cidr_add(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+ ngx_cidr_t *cidr, ngx_str_t *value, ngx_str_t *net)
+{
+ ngx_int_t rc;
+ ngx_http_variable_value_t *val, *old;
+
+ val = ngx_http_geo_value(cf, ctx, value);
+
+ if (val == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ switch (cidr->family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr,
+ cidr->u.in6.mask.s6_addr,
+ (uintptr_t) val);
+
+ if (rc == NGX_OK) {
+ return NGX_CONF_OK;
+ }
+
+ if (rc == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ /* rc == NGX_BUSY */
+
+ old = (ngx_http_variable_value_t *)
+ ngx_radix128tree_find(ctx->tree6,
+ cidr->u.in6.addr.s6_addr);
+
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "duplicate network \"%V\", value: \"%v\", old value: \"%v\"",
+ net, val, old);
+
+ rc = ngx_radix128tree_delete(ctx->tree6,
+ cidr->u.in6.addr.s6_addr,
+ cidr->u.in6.mask.s6_addr);
+
+ if (rc == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree");
+ return NGX_CONF_ERROR;
+ }
+
+ rc = ngx_radix128tree_insert(ctx->tree6, cidr->u.in6.addr.s6_addr,
+ cidr->u.in6.mask.s6_addr,
+ (uintptr_t) val);
+
+ break;
+#endif
+
+ default: /* AF_INET */
+ rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr,
+ cidr->u.in.mask, (uintptr_t) val);
+
+ if (rc == NGX_OK) {
+ return NGX_CONF_OK;
+ }
+
+ if (rc == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ /* rc == NGX_BUSY */
+
+ old = (ngx_http_variable_value_t *)
+ ngx_radix32tree_find(ctx->tree, cidr->u.in.addr);
+
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "duplicate network \"%V\", value: \"%v\", old value: \"%v\"",
+ net, val, old);
+
+ rc = ngx_radix32tree_delete(ctx->tree,
+ cidr->u.in.addr, cidr->u.in.mask);
+
+ if (rc == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree");
+ return NGX_CONF_ERROR;
+ }
+
+ rc = ngx_radix32tree_insert(ctx->tree, cidr->u.in.addr,
+ cidr->u.in.mask, (uintptr_t) val);
+
+ break;
+ }
+
+ if (rc == NGX_OK) {
+ return NGX_CONF_OK;
+ }
+
+ return NGX_CONF_ERROR;
+}
+
+
+static ngx_http_variable_value_t *
+ngx_http_geo_value(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+ ngx_str_t *value)
+{
+ uint32_t hash;
+ ngx_http_variable_value_t *val;
+ ngx_http_geo_variable_value_node_t *gvvn;
+
+ hash = ngx_crc32_long(value->data, value->len);
+
+ gvvn = (ngx_http_geo_variable_value_node_t *)
+ ngx_str_rbtree_lookup(&ctx->rbtree, value, hash);
+
+ if (gvvn) {
+ return gvvn->value;
+ }
+
+ val = ngx_palloc(ctx->pool, sizeof(ngx_http_variable_value_t));
+ if (val == NULL) {
+ return NULL;
+ }
+
+ val->len = value->len;
+ val->data = ngx_pstrdup(ctx->pool, value);
+ if (val->data == NULL) {
+ return NULL;
+ }
+
+ val->valid = 1;
+ val->no_cacheable = 0;
+ val->not_found = 0;
+
+ gvvn = ngx_palloc(ctx->temp_pool,
+ sizeof(ngx_http_geo_variable_value_node_t));
+ if (gvvn == NULL) {
+ return NULL;
+ }
+
+ gvvn->sn.node.key = hash;
+ gvvn->sn.str.len = val->len;
+ gvvn->sn.str.data = val->data;
+ gvvn->value = val;
+ gvvn->offset = 0;
+
+ ngx_rbtree_insert(&ctx->rbtree, &gvvn->sn.node);
+
+ ctx->data_size += ngx_align(sizeof(ngx_http_variable_value_t) + value->len,
+ sizeof(void *));
+
+ return val;
+}
+
+
+static char *
+ngx_http_geo_add_proxy(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+ ngx_cidr_t *cidr)
+{
+ ngx_cidr_t *c;
+
+ if (ctx->proxies == NULL) {
+ ctx->proxies = ngx_array_create(ctx->pool, 4, sizeof(ngx_cidr_t));
+ if (ctx->proxies == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ c = ngx_array_push(ctx->proxies);
+ if (c == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *c = *cidr;
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geo_cidr_value(ngx_conf_t *cf, ngx_str_t *net, ngx_cidr_t *cidr)
+{
+ ngx_int_t rc;
+
+ if (ngx_strcmp(net->data, "255.255.255.255") == 0) {
+ cidr->family = AF_INET;
+ cidr->u.in.addr = 0xffffffff;
+ cidr->u.in.mask = 0xffffffff;
+
+ return NGX_OK;
+ }
+
+ rc = ngx_ptocidr(net, cidr);
+
+ if (rc == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid network \"%V\"", net);
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "low address bits of %V are meaningless", net);
+ }
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_http_geo_include(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+ ngx_str_t *name)
+{
+ char *rv;
+ ngx_str_t file;
+
+ file.len = name->len + 4;
+ file.data = ngx_pnalloc(ctx->temp_pool, name->len + 5);
+ if (file.data == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_sprintf(file.data, "%V.bin%Z", name);
+
+ if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ctx->ranges) {
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
+
+ switch (ngx_http_geo_include_binary_base(cf, ctx, &file)) {
+ case NGX_OK:
+ return NGX_CONF_OK;
+ case NGX_ERROR:
+ return NGX_CONF_ERROR;
+ default:
+ break;
+ }
+ }
+
+ file.len -= 4;
+ file.data[file.len] = '\0';
+
+ ctx->include_name = file;
+
+ if (ctx->outside_entries) {
+ ctx->allow_binary_include = 0;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
+
+ rv = ngx_conf_parse(cf, &file);
+
+ ctx->includes++;
+ ctx->outside_entries = 0;
+
+ return rv;
+}
+
+
+static ngx_int_t
+ngx_http_geo_include_binary_base(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
+ ngx_str_t *name)
+{
+ u_char *base, ch;
+ time_t mtime;
+ size_t size, len;
+ ssize_t n;
+ uint32_t crc32;
+ ngx_err_t err;
+ ngx_int_t rc;
+ ngx_uint_t i;
+ ngx_file_t file;
+ ngx_file_info_t fi;
+ ngx_http_geo_range_t *range, **ranges;
+ ngx_http_geo_header_t *header;
+ ngx_http_variable_value_t *vv;
+
+ ngx_memzero(&file, sizeof(ngx_file_t));
+ file.name = *name;
+ file.log = cf->log;
+
+ file.fd = ngx_open_file(name->data, NGX_FILE_RDONLY, 0, 0);
+ if (file.fd == NGX_INVALID_FILE) {
+ err = ngx_errno;
+ if (err != NGX_ENOENT) {
+ ngx_conf_log_error(NGX_LOG_CRIT, cf, err,
+ ngx_open_file_n " \"%s\" failed", name->data);
+ }
+ return NGX_DECLINED;
+ }
+
+ if (ctx->outside_entries) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "binary geo range base \"%s\" cannot be mixed with usual entries",
+ name->data);
+ rc = NGX_ERROR;
+ goto done;
+ }
+
+ if (ctx->binary_include) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "second binary geo range base \"%s\" cannot be mixed with \"%s\"",
+ name->data, ctx->include_name.data);
+ rc = NGX_ERROR;
+ goto done;
+ }
+
+ if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
+ ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+ ngx_fd_info_n " \"%s\" failed", name->data);
+ goto failed;
+ }
+
+ size = (size_t) ngx_file_size(&fi);
+ mtime = ngx_file_mtime(&fi);
+
+ ch = name->data[name->len - 4];
+ name->data[name->len - 4] = '\0';
+
+ if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) {
+ ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+ ngx_file_info_n " \"%s\" failed", name->data);
+ goto failed;
+ }
+
+ name->data[name->len - 4] = ch;
+
+ if (mtime < ngx_file_mtime(&fi)) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "stale binary geo range base \"%s\"", name->data);
+ goto failed;
+ }
+
+ base = ngx_palloc(ctx->pool, size);
+ if (base == NULL) {
+ goto failed;
+ }
+
+ n = ngx_read_file(&file, base, size, 0);
+
+ if (n == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+ ngx_read_file_n " \"%s\" failed", name->data);
+ goto failed;
+ }
+
+ if ((size_t) n != size) {
+ ngx_conf_log_error(NGX_LOG_CRIT, cf, 0,
+ ngx_read_file_n " \"%s\" returned only %z bytes instead of %z",
+ name->data, n, size);
+ goto failed;
+ }
+
+ header = (ngx_http_geo_header_t *) base;
+
+ if (size < 16 || ngx_memcmp(&ngx_http_geo_header, header, 12) != 0) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "incompatible binary geo range base \"%s\"", name->data);
+ goto failed;
+ }
+
+ ngx_crc32_init(crc32);
+
+ vv = (ngx_http_variable_value_t *) (base + sizeof(ngx_http_geo_header_t));
+
+ while (vv->data) {
+ len = ngx_align(sizeof(ngx_http_variable_value_t) + vv->len,
+ sizeof(void *));
+ ngx_crc32_update(&crc32, (u_char *) vv, len);
+ vv->data += (size_t) base;
+ vv = (ngx_http_variable_value_t *) ((u_char *) vv + len);
+ }
+ ngx_crc32_update(&crc32, (u_char *) vv, sizeof(ngx_http_variable_value_t));
+ vv++;
+
+ ranges = (ngx_http_geo_range_t **) vv;
+
+ for (i = 0; i < 0x10000; i++) {
+ ngx_crc32_update(&crc32, (u_char *) &ranges[i], sizeof(void *));
+ if (ranges[i]) {
+ ranges[i] = (ngx_http_geo_range_t *)
+ ((u_char *) ranges[i] + (size_t) base);
+ }
+ }
+
+ range = (ngx_http_geo_range_t *) &ranges[0x10000];
+
+ while ((u_char *) range < base + size) {
+ while (range->value) {
+ ngx_crc32_update(&crc32, (u_char *) range,
+ sizeof(ngx_http_geo_range_t));
+ range->value = (ngx_http_variable_value_t *)
+ ((u_char *) range->value + (size_t) base);
+ range++;
+ }
+ ngx_crc32_update(&crc32, (u_char *) range, sizeof(void *));
+ range = (ngx_http_geo_range_t *) ((u_char *) range + sizeof(void *));
+ }
+
+ ngx_crc32_final(crc32);
+
+ if (crc32 != header->crc32) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "CRC32 mismatch in binary geo range base \"%s\"", name->data);
+ goto failed;
+ }
+
+ ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0,
+ "using binary geo range base \"%s\"", name->data);
+
+ ctx->include_name = *name;
+ ctx->binary_include = 1;
+ ctx->high.low = ranges;
+ rc = NGX_OK;
+
+ goto done;
+
+failed:
+
+ rc = NGX_DECLINED;
+
+done:
+
+ if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", name->data);
+ }
+
+ return rc;
+}
+
+
+static void
+ngx_http_geo_create_binary_base(ngx_http_geo_conf_ctx_t *ctx)
+{
+ u_char *p;
+ uint32_t hash;
+ ngx_str_t s;
+ ngx_uint_t i;
+ ngx_file_mapping_t fm;
+ ngx_http_geo_range_t *r, *range, **ranges;
+ ngx_http_geo_header_t *header;
+ ngx_http_geo_variable_value_node_t *gvvn;
+
+ fm.name = ngx_pnalloc(ctx->temp_pool, ctx->include_name.len + 5);
+ if (fm.name == NULL) {
+ return;
+ }
+
+ ngx_sprintf(fm.name, "%V.bin%Z", &ctx->include_name);
+
+ fm.size = ctx->data_size;
+ fm.log = ctx->pool->log;
+
+ ngx_log_error(NGX_LOG_NOTICE, fm.log, 0,
+ "creating binary geo range base \"%s\"", fm.name);
+
+ if (ngx_create_file_mapping(&fm) != NGX_OK) {
+ return;
+ }
+
+ p = ngx_cpymem(fm.addr, &ngx_http_geo_header,
+ sizeof(ngx_http_geo_header_t));
+
+ p = ngx_http_geo_copy_values(fm.addr, p, ctx->rbtree.root,
+ ctx->rbtree.sentinel);
+
+ p += sizeof(ngx_http_variable_value_t);
+
+ ranges = (ngx_http_geo_range_t **) p;
+
+ p += 0x10000 * sizeof(ngx_http_geo_range_t *);
+
+ for (i = 0; i < 0x10000; i++) {
+ r = ctx->high.low[i];
+ if (r == NULL) {
+ continue;
+ }
+
+ range = (ngx_http_geo_range_t *) p;
+ ranges[i] = (ngx_http_geo_range_t *) (p - (u_char *) fm.addr);
+
+ do {
+ s.len = r->value->len;
+ s.data = r->value->data;
+ hash = ngx_crc32_long(s.data, s.len);
+ gvvn = (ngx_http_geo_variable_value_node_t *)
+ ngx_str_rbtree_lookup(&ctx->rbtree, &s, hash);
+
+ range->value = (ngx_http_variable_value_t *) gvvn->offset;
+ range->start = r->start;
+ range->end = r->end;
+ range++;
+
+ } while ((++r)->value);
+
+ range->value = NULL;
+
+ p = (u_char *) range + sizeof(void *);
+ }
+
+ header = fm.addr;
+ header->crc32 = ngx_crc32_long((u_char *) fm.addr
+ + sizeof(ngx_http_geo_header_t),
+ fm.size - sizeof(ngx_http_geo_header_t));
+
+ ngx_close_file_mapping(&fm);
+}
+
+
+static u_char *
+ngx_http_geo_copy_values(u_char *base, u_char *p, ngx_rbtree_node_t *node,
+ ngx_rbtree_node_t *sentinel)
+{
+ ngx_http_variable_value_t *vv;
+ ngx_http_geo_variable_value_node_t *gvvn;
+
+ if (node == sentinel) {
+ return p;
+ }
+
+ gvvn = (ngx_http_geo_variable_value_node_t *) node;
+ gvvn->offset = p - base;
+
+ vv = (ngx_http_variable_value_t *) p;
+ *vv = *gvvn->value;
+ p += sizeof(ngx_http_variable_value_t);
+ vv->data = (u_char *) (p - base);
+
+ p = ngx_cpymem(p, gvvn->sn.str.data, gvvn->sn.str.len);
+
+ p = ngx_align_ptr(p, sizeof(void *));
+
+ p = ngx_http_geo_copy_values(base, p, node->left, sentinel);
+
+ return ngx_http_geo_copy_values(base, p, node->right, sentinel);
+}
diff --git a/app/nginx/src/http/modules/ngx_http_geoip_module.c b/app/nginx/src/http/modules/ngx_http_geoip_module.c
new file mode 100644
index 0000000..8e151aa
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_geoip_module.c
@@ -0,0 +1,925 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <GeoIP.h>
+#include <GeoIPCity.h>
+
+
+#define NGX_GEOIP_COUNTRY_CODE 0
+#define NGX_GEOIP_COUNTRY_CODE3 1
+#define NGX_GEOIP_COUNTRY_NAME 2
+
+
+typedef struct {
+ GeoIP *country;
+ GeoIP *org;
+ GeoIP *city;
+ ngx_array_t *proxies; /* array of ngx_cidr_t */
+ ngx_flag_t proxy_recursive;
+#if (NGX_HAVE_GEOIP_V6)
+ unsigned country_v6:1;
+ unsigned org_v6:1;
+ unsigned city_v6:1;
+#endif
+} ngx_http_geoip_conf_t;
+
+
+typedef struct {
+ ngx_str_t *name;
+ uintptr_t data;
+} ngx_http_geoip_var_t;
+
+
+typedef const char *(*ngx_http_geoip_variable_handler_pt)(GeoIP *,
+ u_long addr);
+
+
+ngx_http_geoip_variable_handler_pt ngx_http_geoip_country_functions[] = {
+ GeoIP_country_code_by_ipnum,
+ GeoIP_country_code3_by_ipnum,
+ GeoIP_country_name_by_ipnum,
+};
+
+
+#if (NGX_HAVE_GEOIP_V6)
+
+typedef const char *(*ngx_http_geoip_variable_handler_v6_pt)(GeoIP *,
+ geoipv6_t addr);
+
+
+ngx_http_geoip_variable_handler_v6_pt ngx_http_geoip_country_v6_functions[] = {
+ GeoIP_country_code_by_ipnum_v6,
+ GeoIP_country_code3_by_ipnum_v6,
+ GeoIP_country_name_by_ipnum_v6,
+};
+
+#endif
+
+
+static ngx_int_t ngx_http_geoip_country_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_geoip_org_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_geoip_city_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_geoip_region_name_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_geoip_city_float_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_geoip_city_int_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static GeoIPRecord *ngx_http_geoip_get_city_record(ngx_http_request_t *r);
+
+static ngx_int_t ngx_http_geoip_add_variables(ngx_conf_t *cf);
+static void *ngx_http_geoip_create_conf(ngx_conf_t *cf);
+static char *ngx_http_geoip_init_conf(ngx_conf_t *cf, void *conf);
+static char *ngx_http_geoip_country(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_geoip_org(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_geoip_proxy(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static ngx_int_t ngx_http_geoip_cidr_value(ngx_conf_t *cf, ngx_str_t *net,
+ ngx_cidr_t *cidr);
+static void ngx_http_geoip_cleanup(void *data);
+
+
+static ngx_command_t ngx_http_geoip_commands[] = {
+
+ { ngx_string("geoip_country"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE12,
+ ngx_http_geoip_country,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("geoip_org"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE12,
+ ngx_http_geoip_org,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("geoip_city"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE12,
+ ngx_http_geoip_city,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("geoip_proxy"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+ ngx_http_geoip_proxy,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("geoip_proxy_recursive"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ offsetof(ngx_http_geoip_conf_t, proxy_recursive),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_geoip_module_ctx = {
+ ngx_http_geoip_add_variables, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ ngx_http_geoip_create_conf, /* create main configuration */
+ ngx_http_geoip_init_conf, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_geoip_module = {
+ NGX_MODULE_V1,
+ &ngx_http_geoip_module_ctx, /* module context */
+ ngx_http_geoip_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_variable_t ngx_http_geoip_vars[] = {
+
+ { ngx_string("geoip_country_code"), NULL,
+ ngx_http_geoip_country_variable,
+ NGX_GEOIP_COUNTRY_CODE, 0, 0 },
+
+ { ngx_string("geoip_country_code3"), NULL,
+ ngx_http_geoip_country_variable,
+ NGX_GEOIP_COUNTRY_CODE3, 0, 0 },
+
+ { ngx_string("geoip_country_name"), NULL,
+ ngx_http_geoip_country_variable,
+ NGX_GEOIP_COUNTRY_NAME, 0, 0 },
+
+ { ngx_string("geoip_org"), NULL,
+ ngx_http_geoip_org_variable,
+ 0, 0, 0 },
+
+ { ngx_string("geoip_city_continent_code"), NULL,
+ ngx_http_geoip_city_variable,
+ offsetof(GeoIPRecord, continent_code), 0, 0 },
+
+ { ngx_string("geoip_city_country_code"), NULL,
+ ngx_http_geoip_city_variable,
+ offsetof(GeoIPRecord, country_code), 0, 0 },
+
+ { ngx_string("geoip_city_country_code3"), NULL,
+ ngx_http_geoip_city_variable,
+ offsetof(GeoIPRecord, country_code3), 0, 0 },
+
+ { ngx_string("geoip_city_country_name"), NULL,
+ ngx_http_geoip_city_variable,
+ offsetof(GeoIPRecord, country_name), 0, 0 },
+
+ { ngx_string("geoip_region"), NULL,
+ ngx_http_geoip_city_variable,
+ offsetof(GeoIPRecord, region), 0, 0 },
+
+ { ngx_string("geoip_region_name"), NULL,
+ ngx_http_geoip_region_name_variable,
+ 0, 0, 0 },
+
+ { ngx_string("geoip_city"), NULL,
+ ngx_http_geoip_city_variable,
+ offsetof(GeoIPRecord, city), 0, 0 },
+
+ { ngx_string("geoip_postal_code"), NULL,
+ ngx_http_geoip_city_variable,
+ offsetof(GeoIPRecord, postal_code), 0, 0 },
+
+ { ngx_string("geoip_latitude"), NULL,
+ ngx_http_geoip_city_float_variable,
+ offsetof(GeoIPRecord, latitude), 0, 0 },
+
+ { ngx_string("geoip_longitude"), NULL,
+ ngx_http_geoip_city_float_variable,
+ offsetof(GeoIPRecord, longitude), 0, 0 },
+
+ { ngx_string("geoip_dma_code"), NULL,
+ ngx_http_geoip_city_int_variable,
+ offsetof(GeoIPRecord, dma_code), 0, 0 },
+
+ { ngx_string("geoip_area_code"), NULL,
+ ngx_http_geoip_city_int_variable,
+ offsetof(GeoIPRecord, area_code), 0, 0 },
+
+ { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+
+static u_long
+ngx_http_geoip_addr(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf)
+{
+ ngx_addr_t addr;
+ ngx_array_t *xfwd;
+ struct sockaddr_in *sin;
+
+ addr.sockaddr = r->connection->sockaddr;
+ addr.socklen = r->connection->socklen;
+ /* addr.name = r->connection->addr_text; */
+
+ xfwd = &r->headers_in.x_forwarded_for;
+
+ if (xfwd->nelts > 0 && gcf->proxies != NULL) {
+ (void) ngx_http_get_forwarded_addr(r, &addr, xfwd, NULL,
+ gcf->proxies, gcf->proxy_recursive);
+ }
+
+#if (NGX_HAVE_INET6)
+
+ if (addr.sockaddr->sa_family == AF_INET6) {
+ u_char *p;
+ in_addr_t inaddr;
+ struct in6_addr *inaddr6;
+
+ inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr;
+
+ if (IN6_IS_ADDR_V4MAPPED(inaddr6)) {
+ p = inaddr6->s6_addr;
+
+ inaddr = p[12] << 24;
+ inaddr += p[13] << 16;
+ inaddr += p[14] << 8;
+ inaddr += p[15];
+
+ return inaddr;
+ }
+ }
+
+#endif
+
+ if (addr.sockaddr->sa_family != AF_INET) {
+ return INADDR_NONE;
+ }
+
+ sin = (struct sockaddr_in *) addr.sockaddr;
+ return ntohl(sin->sin_addr.s_addr);
+}
+
+
+#if (NGX_HAVE_GEOIP_V6)
+
+static geoipv6_t
+ngx_http_geoip_addr_v6(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf)
+{
+ ngx_addr_t addr;
+ ngx_array_t *xfwd;
+ in_addr_t addr4;
+ struct in6_addr addr6;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+
+ addr.sockaddr = r->connection->sockaddr;
+ addr.socklen = r->connection->socklen;
+ /* addr.name = r->connection->addr_text; */
+
+ xfwd = &r->headers_in.x_forwarded_for;
+
+ if (xfwd->nelts > 0 && gcf->proxies != NULL) {
+ (void) ngx_http_get_forwarded_addr(r, &addr, xfwd, NULL,
+ gcf->proxies, gcf->proxy_recursive);
+ }
+
+ switch (addr.sockaddr->sa_family) {
+
+ case AF_INET:
+ /* Produce IPv4-mapped IPv6 address. */
+ sin = (struct sockaddr_in *) addr.sockaddr;
+ addr4 = ntohl(sin->sin_addr.s_addr);
+
+ ngx_memzero(&addr6, sizeof(struct in6_addr));
+ addr6.s6_addr[10] = 0xff;
+ addr6.s6_addr[11] = 0xff;
+ addr6.s6_addr[12] = addr4 >> 24;
+ addr6.s6_addr[13] = addr4 >> 16;
+ addr6.s6_addr[14] = addr4 >> 8;
+ addr6.s6_addr[15] = addr4;
+ return addr6;
+
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) addr.sockaddr;
+ return sin6->sin6_addr;
+
+ default:
+ return in6addr_any;
+ }
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_geoip_country_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_http_geoip_variable_handler_pt handler =
+ ngx_http_geoip_country_functions[data];
+#if (NGX_HAVE_GEOIP_V6)
+ ngx_http_geoip_variable_handler_v6_pt handler_v6 =
+ ngx_http_geoip_country_v6_functions[data];
+#endif
+
+ const char *val;
+ ngx_http_geoip_conf_t *gcf;
+
+ gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module);
+
+ if (gcf->country == NULL) {
+ goto not_found;
+ }
+
+#if (NGX_HAVE_GEOIP_V6)
+ val = gcf->country_v6
+ ? handler_v6(gcf->country, ngx_http_geoip_addr_v6(r, gcf))
+ : handler(gcf->country, ngx_http_geoip_addr(r, gcf));
+#else
+ val = handler(gcf->country, ngx_http_geoip_addr(r, gcf));
+#endif
+
+ if (val == NULL) {
+ goto not_found;
+ }
+
+ v->len = ngx_strlen(val);
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = (u_char *) val;
+
+ return NGX_OK;
+
+not_found:
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geoip_org_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ size_t len;
+ char *val;
+ ngx_http_geoip_conf_t *gcf;
+
+ gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module);
+
+ if (gcf->org == NULL) {
+ goto not_found;
+ }
+
+#if (NGX_HAVE_GEOIP_V6)
+ val = gcf->org_v6
+ ? GeoIP_name_by_ipnum_v6(gcf->org,
+ ngx_http_geoip_addr_v6(r, gcf))
+ : GeoIP_name_by_ipnum(gcf->org,
+ ngx_http_geoip_addr(r, gcf));
+#else
+ val = GeoIP_name_by_ipnum(gcf->org, ngx_http_geoip_addr(r, gcf));
+#endif
+
+ if (val == NULL) {
+ goto not_found;
+ }
+
+ len = ngx_strlen(val);
+ v->data = ngx_pnalloc(r->pool, len);
+ if (v->data == NULL) {
+ ngx_free(val);
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(v->data, val, len);
+
+ v->len = len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ ngx_free(val);
+
+ return NGX_OK;
+
+not_found:
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geoip_city_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ char *val;
+ size_t len;
+ GeoIPRecord *gr;
+
+ gr = ngx_http_geoip_get_city_record(r);
+ if (gr == NULL) {
+ goto not_found;
+ }
+
+ val = *(char **) ((char *) gr + data);
+ if (val == NULL) {
+ goto no_value;
+ }
+
+ len = ngx_strlen(val);
+ v->data = ngx_pnalloc(r->pool, len);
+ if (v->data == NULL) {
+ GeoIPRecord_delete(gr);
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(v->data, val, len);
+
+ v->len = len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ GeoIPRecord_delete(gr);
+
+ return NGX_OK;
+
+no_value:
+
+ GeoIPRecord_delete(gr);
+
+not_found:
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geoip_region_name_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ size_t len;
+ const char *val;
+ GeoIPRecord *gr;
+
+ gr = ngx_http_geoip_get_city_record(r);
+ if (gr == NULL) {
+ goto not_found;
+ }
+
+ val = GeoIP_region_name_by_code(gr->country_code, gr->region);
+
+ GeoIPRecord_delete(gr);
+
+ if (val == NULL) {
+ goto not_found;
+ }
+
+ len = ngx_strlen(val);
+ v->data = ngx_pnalloc(r->pool, len);
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(v->data, val, len);
+
+ v->len = len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ return NGX_OK;
+
+not_found:
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geoip_city_float_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ float val;
+ GeoIPRecord *gr;
+
+ gr = ngx_http_geoip_get_city_record(r);
+ if (gr == NULL) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ v->data = ngx_pnalloc(r->pool, NGX_INT64_LEN + 5);
+ if (v->data == NULL) {
+ GeoIPRecord_delete(gr);
+ return NGX_ERROR;
+ }
+
+ val = *(float *) ((char *) gr + data);
+
+ v->len = ngx_sprintf(v->data, "%.4f", val) - v->data;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ GeoIPRecord_delete(gr);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_geoip_city_int_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ int val;
+ GeoIPRecord *gr;
+
+ gr = ngx_http_geoip_get_city_record(r);
+ if (gr == NULL) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ v->data = ngx_pnalloc(r->pool, NGX_INT64_LEN);
+ if (v->data == NULL) {
+ GeoIPRecord_delete(gr);
+ return NGX_ERROR;
+ }
+
+ val = *(int *) ((char *) gr + data);
+
+ v->len = ngx_sprintf(v->data, "%d", val) - v->data;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ GeoIPRecord_delete(gr);
+
+ return NGX_OK;
+}
+
+
+static GeoIPRecord *
+ngx_http_geoip_get_city_record(ngx_http_request_t *r)
+{
+ ngx_http_geoip_conf_t *gcf;
+
+ gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module);
+
+ if (gcf->city) {
+#if (NGX_HAVE_GEOIP_V6)
+ return gcf->city_v6
+ ? GeoIP_record_by_ipnum_v6(gcf->city,
+ ngx_http_geoip_addr_v6(r, gcf))
+ : GeoIP_record_by_ipnum(gcf->city,
+ ngx_http_geoip_addr(r, gcf));
+#else
+ return GeoIP_record_by_ipnum(gcf->city, ngx_http_geoip_addr(r, gcf));
+#endif
+ }
+
+ return NULL;
+}
+
+
+static ngx_int_t
+ngx_http_geoip_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var, *v;
+
+ for (v = ngx_http_geoip_vars; v->name.len; v++) {
+ var = ngx_http_add_variable(cf, &v->name, v->flags);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = v->get_handler;
+ var->data = v->data;
+ }
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_geoip_create_conf(ngx_conf_t *cf)
+{
+ ngx_pool_cleanup_t *cln;
+ ngx_http_geoip_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_geoip_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->proxy_recursive = NGX_CONF_UNSET;
+
+ cln = ngx_pool_cleanup_add(cf->pool, 0);
+ if (cln == NULL) {
+ return NULL;
+ }
+
+ cln->handler = ngx_http_geoip_cleanup;
+ cln->data = conf;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_geoip_init_conf(ngx_conf_t *cf, void *conf)
+{
+ ngx_http_geoip_conf_t *gcf = conf;
+
+ ngx_conf_init_value(gcf->proxy_recursive, 0);
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_geoip_country(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_geoip_conf_t *gcf = conf;
+
+ ngx_str_t *value;
+
+ if (gcf->country) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ gcf->country = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE);
+
+ if (gcf->country == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "GeoIP_open(\"%V\") failed", &value[1]);
+
+ return NGX_CONF_ERROR;
+ }
+
+ if (cf->args->nelts == 3) {
+ if (ngx_strcmp(value[2].data, "utf8") == 0) {
+ GeoIP_set_charset(gcf->country, GEOIP_CHARSET_UTF8);
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[2]);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ switch (gcf->country->databaseType) {
+
+ case GEOIP_COUNTRY_EDITION:
+
+ return NGX_CONF_OK;
+
+#if (NGX_HAVE_GEOIP_V6)
+ case GEOIP_COUNTRY_EDITION_V6:
+
+ gcf->country_v6 = 1;
+ return NGX_CONF_OK;
+#endif
+
+ default:
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid GeoIP database \"%V\" type:%d",
+ &value[1], gcf->country->databaseType);
+ return NGX_CONF_ERROR;
+ }
+}
+
+
+static char *
+ngx_http_geoip_org(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_geoip_conf_t *gcf = conf;
+
+ ngx_str_t *value;
+
+ if (gcf->org) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ gcf->org = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE);
+
+ if (gcf->org == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "GeoIP_open(\"%V\") failed", &value[1]);
+
+ return NGX_CONF_ERROR;
+ }
+
+ if (cf->args->nelts == 3) {
+ if (ngx_strcmp(value[2].data, "utf8") == 0) {
+ GeoIP_set_charset(gcf->org, GEOIP_CHARSET_UTF8);
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[2]);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ switch (gcf->org->databaseType) {
+
+ case GEOIP_ISP_EDITION:
+ case GEOIP_ORG_EDITION:
+ case GEOIP_DOMAIN_EDITION:
+ case GEOIP_ASNUM_EDITION:
+
+ return NGX_CONF_OK;
+
+#if (NGX_HAVE_GEOIP_V6)
+ case GEOIP_ISP_EDITION_V6:
+ case GEOIP_ORG_EDITION_V6:
+ case GEOIP_DOMAIN_EDITION_V6:
+ case GEOIP_ASNUM_EDITION_V6:
+
+ gcf->org_v6 = 1;
+ return NGX_CONF_OK;
+#endif
+
+ default:
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid GeoIP database \"%V\" type:%d",
+ &value[1], gcf->org->databaseType);
+ return NGX_CONF_ERROR;
+ }
+}
+
+
+static char *
+ngx_http_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_geoip_conf_t *gcf = conf;
+
+ ngx_str_t *value;
+
+ if (gcf->city) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ gcf->city = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE);
+
+ if (gcf->city == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "GeoIP_open(\"%V\") failed", &value[1]);
+
+ return NGX_CONF_ERROR;
+ }
+
+ if (cf->args->nelts == 3) {
+ if (ngx_strcmp(value[2].data, "utf8") == 0) {
+ GeoIP_set_charset(gcf->city, GEOIP_CHARSET_UTF8);
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[2]);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ switch (gcf->city->databaseType) {
+
+ case GEOIP_CITY_EDITION_REV0:
+ case GEOIP_CITY_EDITION_REV1:
+
+ return NGX_CONF_OK;
+
+#if (NGX_HAVE_GEOIP_V6)
+ case GEOIP_CITY_EDITION_REV0_V6:
+ case GEOIP_CITY_EDITION_REV1_V6:
+
+ gcf->city_v6 = 1;
+ return NGX_CONF_OK;
+#endif
+
+ default:
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid GeoIP City database \"%V\" type:%d",
+ &value[1], gcf->city->databaseType);
+ return NGX_CONF_ERROR;
+ }
+}
+
+
+static char *
+ngx_http_geoip_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_geoip_conf_t *gcf = conf;
+
+ ngx_str_t *value;
+ ngx_cidr_t cidr, *c;
+
+ value = cf->args->elts;
+
+ if (ngx_http_geoip_cidr_value(cf, &value[1], &cidr) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (gcf->proxies == NULL) {
+ gcf->proxies = ngx_array_create(cf->pool, 4, sizeof(ngx_cidr_t));
+ if (gcf->proxies == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ c = ngx_array_push(gcf->proxies);
+ if (c == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *c = cidr;
+
+ return NGX_CONF_OK;
+}
+
+static ngx_int_t
+ngx_http_geoip_cidr_value(ngx_conf_t *cf, ngx_str_t *net, ngx_cidr_t *cidr)
+{
+ ngx_int_t rc;
+
+ if (ngx_strcmp(net->data, "255.255.255.255") == 0) {
+ cidr->family = AF_INET;
+ cidr->u.in.addr = 0xffffffff;
+ cidr->u.in.mask = 0xffffffff;
+
+ return NGX_OK;
+ }
+
+ rc = ngx_ptocidr(net, cidr);
+
+ if (rc == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid network \"%V\"", net);
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "low address bits of %V are meaningless", net);
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_geoip_cleanup(void *data)
+{
+ ngx_http_geoip_conf_t *gcf = data;
+
+ if (gcf->country) {
+ GeoIP_delete(gcf->country);
+ }
+
+ if (gcf->org) {
+ GeoIP_delete(gcf->org);
+ }
+
+ if (gcf->city) {
+ GeoIP_delete(gcf->city);
+ }
+}
diff --git a/app/nginx/src/http/modules/ngx_http_gunzip_filter_module.c b/app/nginx/src/http/modules/ngx_http_gunzip_filter_module.c
new file mode 100644
index 0000000..c1341f5
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_gunzip_filter_module.c
@@ -0,0 +1,687 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <zlib.h>
+
+
+typedef struct {
+ ngx_flag_t enable;
+ ngx_bufs_t bufs;
+} ngx_http_gunzip_conf_t;
+
+
+typedef struct {
+ ngx_chain_t *in;
+ ngx_chain_t *free;
+ ngx_chain_t *busy;
+ ngx_chain_t *out;
+ ngx_chain_t **last_out;
+
+ ngx_buf_t *in_buf;
+ ngx_buf_t *out_buf;
+ ngx_int_t bufs;
+
+ unsigned started:1;
+ unsigned flush:4;
+ unsigned redo:1;
+ unsigned done:1;
+ unsigned nomem:1;
+
+ z_stream zstream;
+ ngx_http_request_t *request;
+} ngx_http_gunzip_ctx_t;
+
+
+static ngx_int_t ngx_http_gunzip_filter_inflate_start(ngx_http_request_t *r,
+ ngx_http_gunzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gunzip_filter_add_data(ngx_http_request_t *r,
+ ngx_http_gunzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gunzip_filter_get_buf(ngx_http_request_t *r,
+ ngx_http_gunzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gunzip_filter_inflate(ngx_http_request_t *r,
+ ngx_http_gunzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gunzip_filter_inflate_end(ngx_http_request_t *r,
+ ngx_http_gunzip_ctx_t *ctx);
+
+static void *ngx_http_gunzip_filter_alloc(void *opaque, u_int items,
+ u_int size);
+static void ngx_http_gunzip_filter_free(void *opaque, void *address);
+
+static ngx_int_t ngx_http_gunzip_filter_init(ngx_conf_t *cf);
+static void *ngx_http_gunzip_create_conf(ngx_conf_t *cf);
+static char *ngx_http_gunzip_merge_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+
+
+static ngx_command_t ngx_http_gunzip_filter_commands[] = {
+
+ { ngx_string("gunzip"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_gunzip_conf_t, enable),
+ NULL },
+
+ { ngx_string("gunzip_buffers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_conf_set_bufs_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_gunzip_conf_t, bufs),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_gunzip_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_gunzip_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_gunzip_create_conf, /* create location configuration */
+ ngx_http_gunzip_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_gunzip_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_gunzip_filter_module_ctx, /* module context */
+ ngx_http_gunzip_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_gunzip_header_filter(ngx_http_request_t *r)
+{
+ ngx_http_gunzip_ctx_t *ctx;
+ ngx_http_gunzip_conf_t *conf;
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_gunzip_filter_module);
+
+ /* TODO support multiple content-codings */
+ /* TODO always gunzip - due to configuration or module request */
+ /* TODO ignore content encoding? */
+
+ if (!conf->enable
+ || r->headers_out.content_encoding == NULL
+ || r->headers_out.content_encoding->value.len != 4
+ || ngx_strncasecmp(r->headers_out.content_encoding->value.data,
+ (u_char *) "gzip", 4) != 0)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ r->gzip_vary = 1;
+
+ if (!r->gzip_tested) {
+ if (ngx_http_gzip_ok(r) == NGX_OK) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ } else if (r->gzip_ok) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_gunzip_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_gunzip_filter_module);
+
+ ctx->request = r;
+
+ r->filter_need_in_memory = 1;
+
+ r->headers_out.content_encoding->hash = 0;
+ r->headers_out.content_encoding = NULL;
+
+ ngx_http_clear_content_length(r);
+ ngx_http_clear_accept_ranges(r);
+ ngx_http_weak_etag(r);
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_gunzip_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ int rc;
+ ngx_uint_t flush;
+ ngx_chain_t *cl;
+ ngx_http_gunzip_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_gunzip_filter_module);
+
+ if (ctx == NULL || ctx->done) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http gunzip filter");
+
+ if (!ctx->started) {
+ if (ngx_http_gunzip_filter_inflate_start(r, ctx) != NGX_OK) {
+ goto failed;
+ }
+ }
+
+ if (in) {
+ if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
+ goto failed;
+ }
+ }
+
+ if (ctx->nomem) {
+
+ /* flush busy buffers */
+
+ if (ngx_http_next_body_filter(r, NULL) == NGX_ERROR) {
+ goto failed;
+ }
+
+ cl = NULL;
+
+ ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &cl,
+ (ngx_buf_tag_t) &ngx_http_gunzip_filter_module);
+ ctx->nomem = 0;
+ flush = 0;
+
+ } else {
+ flush = ctx->busy ? 1 : 0;
+ }
+
+ for ( ;; ) {
+
+ /* cycle while we can write to a client */
+
+ for ( ;; ) {
+
+ /* cycle while there is data to feed zlib and ... */
+
+ rc = ngx_http_gunzip_filter_add_data(r, ctx);
+
+ if (rc == NGX_DECLINED) {
+ break;
+ }
+
+ if (rc == NGX_AGAIN) {
+ continue;
+ }
+
+
+ /* ... there are buffers to write zlib output */
+
+ rc = ngx_http_gunzip_filter_get_buf(r, ctx);
+
+ if (rc == NGX_DECLINED) {
+ break;
+ }
+
+ if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+ rc = ngx_http_gunzip_filter_inflate(r, ctx);
+
+ if (rc == NGX_OK) {
+ break;
+ }
+
+ if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+ /* rc == NGX_AGAIN */
+ }
+
+ if (ctx->out == NULL && !flush) {
+ return ctx->busy ? NGX_AGAIN : NGX_OK;
+ }
+
+ rc = ngx_http_next_body_filter(r, ctx->out);
+
+ if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+ ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &ctx->out,
+ (ngx_buf_tag_t) &ngx_http_gunzip_filter_module);
+ ctx->last_out = &ctx->out;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "gunzip out: %p", ctx->out);
+
+ ctx->nomem = 0;
+ flush = 0;
+
+ if (ctx->done) {
+ return rc;
+ }
+ }
+
+ /* unreachable */
+
+failed:
+
+ ctx->done = 1;
+
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_gunzip_filter_inflate_start(ngx_http_request_t *r,
+ ngx_http_gunzip_ctx_t *ctx)
+{
+ int rc;
+
+ ctx->zstream.next_in = Z_NULL;
+ ctx->zstream.avail_in = 0;
+
+ ctx->zstream.zalloc = ngx_http_gunzip_filter_alloc;
+ ctx->zstream.zfree = ngx_http_gunzip_filter_free;
+ ctx->zstream.opaque = ctx;
+
+ /* windowBits +16 to decode gzip, zlib 1.2.0.4+ */
+ rc = inflateInit2(&ctx->zstream, MAX_WBITS + 16);
+
+ if (rc != Z_OK) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "inflateInit2() failed: %d", rc);
+ return NGX_ERROR;
+ }
+
+ ctx->started = 1;
+
+ ctx->last_out = &ctx->out;
+ ctx->flush = Z_NO_FLUSH;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gunzip_filter_add_data(ngx_http_request_t *r,
+ ngx_http_gunzip_ctx_t *ctx)
+{
+ if (ctx->zstream.avail_in || ctx->flush != Z_NO_FLUSH || ctx->redo) {
+ return NGX_OK;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "gunzip in: %p", ctx->in);
+
+ if (ctx->in == NULL) {
+ return NGX_DECLINED;
+ }
+
+ ctx->in_buf = ctx->in->buf;
+ ctx->in = ctx->in->next;
+
+ ctx->zstream.next_in = ctx->in_buf->pos;
+ ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "gunzip in_buf:%p ni:%p ai:%ud",
+ ctx->in_buf,
+ ctx->zstream.next_in, ctx->zstream.avail_in);
+
+ if (ctx->in_buf->last_buf || ctx->in_buf->last_in_chain) {
+ ctx->flush = Z_FINISH;
+
+ } else if (ctx->in_buf->flush) {
+ ctx->flush = Z_SYNC_FLUSH;
+
+ } else if (ctx->zstream.avail_in == 0) {
+ /* ctx->flush == Z_NO_FLUSH */
+ return NGX_AGAIN;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gunzip_filter_get_buf(ngx_http_request_t *r,
+ ngx_http_gunzip_ctx_t *ctx)
+{
+ ngx_http_gunzip_conf_t *conf;
+
+ if (ctx->zstream.avail_out) {
+ return NGX_OK;
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_gunzip_filter_module);
+
+ if (ctx->free) {
+ ctx->out_buf = ctx->free->buf;
+ ctx->free = ctx->free->next;
+
+ ctx->out_buf->flush = 0;
+
+ } else if (ctx->bufs < conf->bufs.num) {
+
+ ctx->out_buf = ngx_create_temp_buf(r->pool, conf->bufs.size);
+ if (ctx->out_buf == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_gunzip_filter_module;
+ ctx->out_buf->recycled = 1;
+ ctx->bufs++;
+
+ } else {
+ ctx->nomem = 1;
+ return NGX_DECLINED;
+ }
+
+ ctx->zstream.next_out = ctx->out_buf->pos;
+ ctx->zstream.avail_out = conf->bufs.size;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gunzip_filter_inflate(ngx_http_request_t *r,
+ ngx_http_gunzip_ctx_t *ctx)
+{
+ int rc;
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+
+ ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "inflate in: ni:%p no:%p ai:%ud ao:%ud fl:%d redo:%d",
+ ctx->zstream.next_in, ctx->zstream.next_out,
+ ctx->zstream.avail_in, ctx->zstream.avail_out,
+ ctx->flush, ctx->redo);
+
+ rc = inflate(&ctx->zstream, ctx->flush);
+
+ if (rc != Z_OK && rc != Z_STREAM_END && rc != Z_BUF_ERROR) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "inflate() failed: %d, %d", ctx->flush, rc);
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
+ ctx->zstream.next_in, ctx->zstream.next_out,
+ ctx->zstream.avail_in, ctx->zstream.avail_out,
+ rc);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "gunzip in_buf:%p pos:%p",
+ ctx->in_buf, ctx->in_buf->pos);
+
+ if (ctx->zstream.next_in) {
+ ctx->in_buf->pos = ctx->zstream.next_in;
+
+ if (ctx->zstream.avail_in == 0) {
+ ctx->zstream.next_in = NULL;
+ }
+ }
+
+ ctx->out_buf->last = ctx->zstream.next_out;
+
+ if (ctx->zstream.avail_out == 0) {
+
+ /* zlib wants to output some more data */
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = ctx->out_buf;
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ ctx->redo = 1;
+
+ return NGX_AGAIN;
+ }
+
+ ctx->redo = 0;
+
+ if (ctx->flush == Z_SYNC_FLUSH) {
+
+ ctx->flush = Z_NO_FLUSH;
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = ctx->out_buf;
+
+ if (ngx_buf_size(b) == 0) {
+
+ b = ngx_calloc_buf(ctx->request->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ } else {
+ ctx->zstream.avail_out = 0;
+ }
+
+ b->flush = 1;
+
+ cl->buf = b;
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ return NGX_OK;
+ }
+
+ if (ctx->flush == Z_FINISH && ctx->zstream.avail_in == 0) {
+
+ if (rc != Z_STREAM_END) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "inflate() returned %d on response end", rc);
+ return NGX_ERROR;
+ }
+
+ if (ngx_http_gunzip_filter_inflate_end(r, ctx) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+ }
+
+ if (rc == Z_STREAM_END && ctx->zstream.avail_in > 0) {
+
+ rc = inflateReset(&ctx->zstream);
+
+ if (rc != Z_OK) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "inflateReset() failed: %d", rc);
+ return NGX_ERROR;
+ }
+
+ ctx->redo = 1;
+
+ return NGX_AGAIN;
+ }
+
+ if (ctx->in == NULL) {
+
+ b = ctx->out_buf;
+
+ if (ngx_buf_size(b) == 0) {
+ return NGX_OK;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->zstream.avail_out = 0;
+
+ cl->buf = b;
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ return NGX_OK;
+ }
+
+ return NGX_AGAIN;
+}
+
+
+static ngx_int_t
+ngx_http_gunzip_filter_inflate_end(ngx_http_request_t *r,
+ ngx_http_gunzip_ctx_t *ctx)
+{
+ int rc;
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "gunzip inflate end");
+
+ rc = inflateEnd(&ctx->zstream);
+
+ if (rc != Z_OK) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "inflateEnd() failed: %d", rc);
+ return NGX_ERROR;
+ }
+
+ b = ctx->out_buf;
+
+ if (ngx_buf_size(b) == 0) {
+
+ b = ngx_calloc_buf(ctx->request->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ b->last_buf = (r == r->main) ? 1 : 0;
+ b->last_in_chain = 1;
+ b->sync = 1;
+
+ ctx->done = 1;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_gunzip_filter_alloc(void *opaque, u_int items, u_int size)
+{
+ ngx_http_gunzip_ctx_t *ctx = opaque;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
+ "gunzip alloc: n:%ud s:%ud",
+ items, size);
+
+ return ngx_palloc(ctx->request->pool, items * size);
+}
+
+
+static void
+ngx_http_gunzip_filter_free(void *opaque, void *address)
+{
+#if 0
+ ngx_http_gunzip_ctx_t *ctx = opaque;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
+ "gunzip free: %p", address);
+#endif
+}
+
+
+static void *
+ngx_http_gunzip_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_gunzip_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_gunzip_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->bufs.num = 0;
+ */
+
+ conf->enable = NGX_CONF_UNSET;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_gunzip_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_gunzip_conf_t *prev = parent;
+ ngx_http_gunzip_conf_t *conf = child;
+
+ ngx_conf_merge_value(conf->enable, prev->enable, 0);
+
+ ngx_conf_merge_bufs_value(conf->bufs, prev->bufs,
+ (128 * 1024) / ngx_pagesize, ngx_pagesize);
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gunzip_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_gunzip_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_gunzip_body_filter;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_gzip_filter_module.c b/app/nginx/src/http/modules/ngx_http_gzip_filter_module.c
new file mode 100644
index 0000000..f9652d0
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_gzip_filter_module.c
@@ -0,0 +1,1245 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <zlib.h>
+
+
+typedef struct {
+ ngx_flag_t enable;
+ ngx_flag_t no_buffer;
+
+ ngx_hash_t types;
+
+ ngx_bufs_t bufs;
+
+ size_t postpone_gzipping;
+ ngx_int_t level;
+ size_t wbits;
+ size_t memlevel;
+ ssize_t min_length;
+
+ ngx_array_t *types_keys;
+} ngx_http_gzip_conf_t;
+
+
+typedef struct {
+ ngx_chain_t *in;
+ ngx_chain_t *free;
+ ngx_chain_t *busy;
+ ngx_chain_t *out;
+ ngx_chain_t **last_out;
+
+ ngx_chain_t *copied;
+ ngx_chain_t *copy_buf;
+
+ ngx_buf_t *in_buf;
+ ngx_buf_t *out_buf;
+ ngx_int_t bufs;
+
+ void *preallocated;
+ char *free_mem;
+ ngx_uint_t allocated;
+
+ int wbits;
+ int memlevel;
+
+ unsigned flush:4;
+ unsigned redo:1;
+ unsigned done:1;
+ unsigned nomem:1;
+ unsigned gzheader:1;
+ unsigned buffering:1;
+
+ size_t zin;
+ size_t zout;
+
+ uint32_t crc32;
+ z_stream zstream;
+ ngx_http_request_t *request;
+} ngx_http_gzip_ctx_t;
+
+
+#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
+
+struct gztrailer {
+ uint32_t crc32;
+ uint32_t zlen;
+};
+
+#else /* NGX_HAVE_BIG_ENDIAN || !NGX_HAVE_NONALIGNED */
+
+struct gztrailer {
+ u_char crc32[4];
+ u_char zlen[4];
+};
+
+#endif
+
+
+static void ngx_http_gzip_filter_memory(ngx_http_request_t *r,
+ ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_buffer(ngx_http_gzip_ctx_t *ctx,
+ ngx_chain_t *in);
+static ngx_int_t ngx_http_gzip_filter_deflate_start(ngx_http_request_t *r,
+ ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_gzheader(ngx_http_request_t *r,
+ ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_add_data(ngx_http_request_t *r,
+ ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_get_buf(ngx_http_request_t *r,
+ ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_deflate(ngx_http_request_t *r,
+ ngx_http_gzip_ctx_t *ctx);
+static ngx_int_t ngx_http_gzip_filter_deflate_end(ngx_http_request_t *r,
+ ngx_http_gzip_ctx_t *ctx);
+
+static void *ngx_http_gzip_filter_alloc(void *opaque, u_int items,
+ u_int size);
+static void ngx_http_gzip_filter_free(void *opaque, void *address);
+static void ngx_http_gzip_filter_free_copy_buf(ngx_http_request_t *r,
+ ngx_http_gzip_ctx_t *ctx);
+
+static ngx_int_t ngx_http_gzip_add_variables(ngx_conf_t *cf);
+static ngx_int_t ngx_http_gzip_ratio_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+
+static ngx_int_t ngx_http_gzip_filter_init(ngx_conf_t *cf);
+static void *ngx_http_gzip_create_conf(ngx_conf_t *cf);
+static char *ngx_http_gzip_merge_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static char *ngx_http_gzip_window(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_gzip_hash(ngx_conf_t *cf, void *post, void *data);
+
+
+static ngx_conf_num_bounds_t ngx_http_gzip_comp_level_bounds = {
+ ngx_conf_check_num_bounds, 1, 9
+};
+
+static ngx_conf_post_handler_pt ngx_http_gzip_window_p = ngx_http_gzip_window;
+static ngx_conf_post_handler_pt ngx_http_gzip_hash_p = ngx_http_gzip_hash;
+
+
+static ngx_command_t ngx_http_gzip_filter_commands[] = {
+
+ { ngx_string("gzip"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_gzip_conf_t, enable),
+ NULL },
+
+ { ngx_string("gzip_buffers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_conf_set_bufs_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_gzip_conf_t, bufs),
+ NULL },
+
+ { ngx_string("gzip_types"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_types_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_gzip_conf_t, types_keys),
+ &ngx_http_html_default_types[0] },
+
+ { ngx_string("gzip_comp_level"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_gzip_conf_t, level),
+ &ngx_http_gzip_comp_level_bounds },
+
+ { ngx_string("gzip_window"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_gzip_conf_t, wbits),
+ &ngx_http_gzip_window_p },
+
+ { ngx_string("gzip_hash"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_gzip_conf_t, memlevel),
+ &ngx_http_gzip_hash_p },
+
+ { ngx_string("postpone_gzipping"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_gzip_conf_t, postpone_gzipping),
+ NULL },
+
+ { ngx_string("gzip_no_buffer"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_gzip_conf_t, no_buffer),
+ NULL },
+
+ { ngx_string("gzip_min_length"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_gzip_conf_t, min_length),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_gzip_filter_module_ctx = {
+ ngx_http_gzip_add_variables, /* preconfiguration */
+ ngx_http_gzip_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_gzip_create_conf, /* create location configuration */
+ ngx_http_gzip_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_gzip_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_gzip_filter_module_ctx, /* module context */
+ ngx_http_gzip_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_http_gzip_ratio = ngx_string("gzip_ratio");
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_gzip_header_filter(ngx_http_request_t *r)
+{
+ ngx_table_elt_t *h;
+ ngx_http_gzip_ctx_t *ctx;
+ ngx_http_gzip_conf_t *conf;
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+ if (!conf->enable
+ || (r->headers_out.status != NGX_HTTP_OK
+ && r->headers_out.status != NGX_HTTP_FORBIDDEN
+ && r->headers_out.status != NGX_HTTP_NOT_FOUND)
+ || (r->headers_out.content_encoding
+ && r->headers_out.content_encoding->value.len)
+ || (r->headers_out.content_length_n != -1
+ && r->headers_out.content_length_n < conf->min_length)
+ || ngx_http_test_content_type(r, &conf->types) == NULL
+ || r->header_only)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ r->gzip_vary = 1;
+
+#if (NGX_HTTP_DEGRADATION)
+ {
+ ngx_http_core_loc_conf_t *clcf;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->gzip_disable_degradation && ngx_http_degraded(r)) {
+ return ngx_http_next_header_filter(r);
+ }
+ }
+#endif
+
+ if (!r->gzip_tested) {
+ if (ngx_http_gzip_ok(r) != NGX_OK) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ } else if (!r->gzip_ok) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_gzip_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_gzip_filter_module);
+
+ ctx->request = r;
+ ctx->buffering = (conf->postpone_gzipping != 0);
+
+ ngx_http_gzip_filter_memory(r, ctx);
+
+ h = ngx_list_push(&r->headers_out.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->hash = 1;
+ ngx_str_set(&h->key, "Content-Encoding");
+ ngx_str_set(&h->value, "gzip");
+ r->headers_out.content_encoding = h;
+
+ r->main_filter_need_in_memory = 1;
+
+ ngx_http_clear_content_length(r);
+ ngx_http_clear_accept_ranges(r);
+ ngx_http_weak_etag(r);
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_gzip_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ int rc;
+ ngx_uint_t flush;
+ ngx_chain_t *cl;
+ ngx_http_gzip_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_gzip_filter_module);
+
+ if (ctx == NULL || ctx->done || r->header_only) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http gzip filter");
+
+ if (ctx->buffering) {
+
+ /*
+ * With default memory settings zlib starts to output gzipped data
+ * only after it has got about 90K, so it makes sense to allocate
+ * zlib memory (200-400K) only after we have enough data to compress.
+ * Although we copy buffers, nevertheless for not big responses
+ * this allows to allocate zlib memory, to compress and to output
+ * the response in one step using hot CPU cache.
+ */
+
+ if (in) {
+ switch (ngx_http_gzip_filter_buffer(ctx, in)) {
+
+ case NGX_OK:
+ return NGX_OK;
+
+ case NGX_DONE:
+ in = NULL;
+ break;
+
+ default: /* NGX_ERROR */
+ goto failed;
+ }
+
+ } else {
+ ctx->buffering = 0;
+ }
+ }
+
+ if (ctx->preallocated == NULL) {
+ if (ngx_http_gzip_filter_deflate_start(r, ctx) != NGX_OK) {
+ goto failed;
+ }
+ }
+
+ if (in) {
+ if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
+ goto failed;
+ }
+
+ r->connection->buffered |= NGX_HTTP_GZIP_BUFFERED;
+ }
+
+ if (ctx->nomem) {
+
+ /* flush busy buffers */
+
+ if (ngx_http_next_body_filter(r, NULL) == NGX_ERROR) {
+ goto failed;
+ }
+
+ cl = NULL;
+
+ ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &cl,
+ (ngx_buf_tag_t) &ngx_http_gzip_filter_module);
+ ctx->nomem = 0;
+ flush = 0;
+
+ } else {
+ flush = ctx->busy ? 1 : 0;
+ }
+
+ for ( ;; ) {
+
+ /* cycle while we can write to a client */
+
+ for ( ;; ) {
+
+ /* cycle while there is data to feed zlib and ... */
+
+ rc = ngx_http_gzip_filter_add_data(r, ctx);
+
+ if (rc == NGX_DECLINED) {
+ break;
+ }
+
+ if (rc == NGX_AGAIN) {
+ continue;
+ }
+
+
+ /* ... there are buffers to write zlib output */
+
+ rc = ngx_http_gzip_filter_get_buf(r, ctx);
+
+ if (rc == NGX_DECLINED) {
+ break;
+ }
+
+ if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+
+ rc = ngx_http_gzip_filter_deflate(r, ctx);
+
+ if (rc == NGX_OK) {
+ break;
+ }
+
+ if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+ /* rc == NGX_AGAIN */
+ }
+
+ if (ctx->out == NULL && !flush) {
+ ngx_http_gzip_filter_free_copy_buf(r, ctx);
+
+ return ctx->busy ? NGX_AGAIN : NGX_OK;
+ }
+
+ if (!ctx->gzheader) {
+ if (ngx_http_gzip_filter_gzheader(r, ctx) != NGX_OK) {
+ goto failed;
+ }
+ }
+
+ rc = ngx_http_next_body_filter(r, ctx->out);
+
+ if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+ ngx_http_gzip_filter_free_copy_buf(r, ctx);
+
+ ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &ctx->out,
+ (ngx_buf_tag_t) &ngx_http_gzip_filter_module);
+ ctx->last_out = &ctx->out;
+
+ ctx->nomem = 0;
+ flush = 0;
+
+ if (ctx->done) {
+ return rc;
+ }
+ }
+
+ /* unreachable */
+
+failed:
+
+ ctx->done = 1;
+
+ if (ctx->preallocated) {
+ deflateEnd(&ctx->zstream);
+
+ ngx_pfree(r->pool, ctx->preallocated);
+ }
+
+ ngx_http_gzip_filter_free_copy_buf(r, ctx);
+
+ return NGX_ERROR;
+}
+
+
+static void
+ngx_http_gzip_filter_memory(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+ int wbits, memlevel;
+ ngx_http_gzip_conf_t *conf;
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+ wbits = conf->wbits;
+ memlevel = conf->memlevel;
+
+ if (r->headers_out.content_length_n > 0) {
+
+ /* the actual zlib window size is smaller by 262 bytes */
+
+ while (r->headers_out.content_length_n < ((1 << (wbits - 1)) - 262)) {
+ wbits--;
+ memlevel--;
+ }
+
+ if (memlevel < 1) {
+ memlevel = 1;
+ }
+ }
+
+ ctx->wbits = wbits;
+ ctx->memlevel = memlevel;
+
+ /*
+ * We preallocate a memory for zlib in one buffer (200K-400K), this
+ * decreases a number of malloc() and free() calls and also probably
+ * decreases a number of syscalls (sbrk()/mmap() and so on).
+ * Besides we free the memory as soon as a gzipping will complete
+ * and do not wait while a whole response will be sent to a client.
+ *
+ * 8K is for zlib deflate_state, it takes
+ * *) 5816 bytes on i386 and sparc64 (32-bit mode)
+ * *) 5920 bytes on amd64 and sparc64
+ */
+
+ ctx->allocated = 8192 + (1 << (wbits + 2)) + (1 << (memlevel + 9));
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_buffer(ngx_http_gzip_ctx_t *ctx, ngx_chain_t *in)
+{
+ size_t size, buffered;
+ ngx_buf_t *b, *buf;
+ ngx_chain_t *cl, **ll;
+ ngx_http_request_t *r;
+ ngx_http_gzip_conf_t *conf;
+
+ r = ctx->request;
+
+ r->connection->buffered |= NGX_HTTP_GZIP_BUFFERED;
+
+ buffered = 0;
+ ll = &ctx->in;
+
+ for (cl = ctx->in; cl; cl = cl->next) {
+ buffered += cl->buf->last - cl->buf->pos;
+ ll = &cl->next;
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+ while (in) {
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = in->buf;
+
+ size = b->last - b->pos;
+ buffered += size;
+
+ if (b->flush || b->last_buf || buffered > conf->postpone_gzipping) {
+ ctx->buffering = 0;
+ }
+
+ if (ctx->buffering && size) {
+
+ buf = ngx_create_temp_buf(r->pool, size);
+ if (buf == NULL) {
+ return NGX_ERROR;
+ }
+
+ buf->last = ngx_cpymem(buf->pos, b->pos, size);
+ b->pos = b->last;
+
+ buf->last_buf = b->last_buf;
+ buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module;
+
+ cl->buf = buf;
+
+ } else {
+ cl->buf = b;
+ }
+
+ *ll = cl;
+ ll = &cl->next;
+ in = in->next;
+ }
+
+ *ll = NULL;
+
+ return ctx->buffering ? NGX_OK : NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_deflate_start(ngx_http_request_t *r,
+ ngx_http_gzip_ctx_t *ctx)
+{
+ int rc;
+ ngx_http_gzip_conf_t *conf;
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+ ctx->preallocated = ngx_palloc(r->pool, ctx->allocated);
+ if (ctx->preallocated == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->free_mem = ctx->preallocated;
+
+ ctx->zstream.zalloc = ngx_http_gzip_filter_alloc;
+ ctx->zstream.zfree = ngx_http_gzip_filter_free;
+ ctx->zstream.opaque = ctx;
+
+ rc = deflateInit2(&ctx->zstream, (int) conf->level, Z_DEFLATED,
+ - ctx->wbits, ctx->memlevel, Z_DEFAULT_STRATEGY);
+
+ if (rc != Z_OK) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "deflateInit2() failed: %d", rc);
+ return NGX_ERROR;
+ }
+
+ ctx->last_out = &ctx->out;
+ ctx->crc32 = crc32(0L, Z_NULL, 0);
+ ctx->flush = Z_NO_FLUSH;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_gzheader(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+ static u_char gzheader[10] =
+ { 0x1f, 0x8b, Z_DEFLATED, 0, 0, 0, 0, 0, 0, 3 };
+
+ b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->memory = 1;
+ b->pos = gzheader;
+ b->last = b->pos + 10;
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ cl->next = ctx->out;
+ ctx->out = cl;
+
+ ctx->gzheader = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_add_data(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+ ngx_chain_t *cl;
+
+ if (ctx->zstream.avail_in || ctx->flush != Z_NO_FLUSH || ctx->redo) {
+ return NGX_OK;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "gzip in: %p", ctx->in);
+
+ if (ctx->in == NULL) {
+ return NGX_DECLINED;
+ }
+
+ if (ctx->copy_buf) {
+
+ /*
+ * to avoid CPU cache trashing we do not free() just quit buf,
+ * but postpone free()ing after zlib compressing and data output
+ */
+
+ ctx->copy_buf->next = ctx->copied;
+ ctx->copied = ctx->copy_buf;
+ ctx->copy_buf = NULL;
+ }
+
+ cl = ctx->in;
+ ctx->in_buf = cl->buf;
+ ctx->in = cl->next;
+
+ if (ctx->in_buf->tag == (ngx_buf_tag_t) &ngx_http_gzip_filter_module) {
+ ctx->copy_buf = cl;
+
+ } else {
+ ngx_free_chain(r->pool, cl);
+ }
+
+ ctx->zstream.next_in = ctx->in_buf->pos;
+ ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "gzip in_buf:%p ni:%p ai:%ud",
+ ctx->in_buf,
+ ctx->zstream.next_in, ctx->zstream.avail_in);
+
+ if (ctx->in_buf->last_buf) {
+ ctx->flush = Z_FINISH;
+
+ } else if (ctx->in_buf->flush) {
+ ctx->flush = Z_SYNC_FLUSH;
+ }
+
+ if (ctx->zstream.avail_in) {
+
+ ctx->crc32 = crc32(ctx->crc32, ctx->zstream.next_in,
+ ctx->zstream.avail_in);
+
+ } else if (ctx->flush == Z_NO_FLUSH) {
+ return NGX_AGAIN;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_get_buf(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+ ngx_chain_t *cl;
+ ngx_http_gzip_conf_t *conf;
+
+ if (ctx->zstream.avail_out) {
+ return NGX_OK;
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+ if (ctx->free) {
+
+ cl = ctx->free;
+ ctx->out_buf = cl->buf;
+ ctx->free = cl->next;
+
+ ngx_free_chain(r->pool, cl);
+
+ } else if (ctx->bufs < conf->bufs.num) {
+
+ ctx->out_buf = ngx_create_temp_buf(r->pool, conf->bufs.size);
+ if (ctx->out_buf == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->out_buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module;
+ ctx->out_buf->recycled = 1;
+ ctx->bufs++;
+
+ } else {
+ ctx->nomem = 1;
+ return NGX_DECLINED;
+ }
+
+ ctx->zstream.next_out = ctx->out_buf->pos;
+ ctx->zstream.avail_out = conf->bufs.size;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_deflate(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx)
+{
+ int rc;
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+ ngx_http_gzip_conf_t *conf;
+
+ ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "deflate in: ni:%p no:%p ai:%ud ao:%ud fl:%d redo:%d",
+ ctx->zstream.next_in, ctx->zstream.next_out,
+ ctx->zstream.avail_in, ctx->zstream.avail_out,
+ ctx->flush, ctx->redo);
+
+ rc = deflate(&ctx->zstream, ctx->flush);
+
+ if (rc != Z_OK && rc != Z_STREAM_END && rc != Z_BUF_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "deflate() failed: %d, %d", ctx->flush, rc);
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
+ ctx->zstream.next_in, ctx->zstream.next_out,
+ ctx->zstream.avail_in, ctx->zstream.avail_out,
+ rc);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "gzip in_buf:%p pos:%p",
+ ctx->in_buf, ctx->in_buf->pos);
+
+ if (ctx->zstream.next_in) {
+ ctx->in_buf->pos = ctx->zstream.next_in;
+
+ if (ctx->zstream.avail_in == 0) {
+ ctx->zstream.next_in = NULL;
+ }
+ }
+
+ ctx->out_buf->last = ctx->zstream.next_out;
+
+ if (ctx->zstream.avail_out == 0) {
+
+ /* zlib wants to output some more gzipped data */
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = ctx->out_buf;
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ ctx->redo = 1;
+
+ return NGX_AGAIN;
+ }
+
+ ctx->redo = 0;
+
+ if (ctx->flush == Z_SYNC_FLUSH) {
+
+ ctx->flush = Z_NO_FLUSH;
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = ctx->out_buf;
+
+ if (ngx_buf_size(b) == 0) {
+
+ b = ngx_calloc_buf(ctx->request->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ } else {
+ ctx->zstream.avail_out = 0;
+ }
+
+ b->flush = 1;
+
+ cl->buf = b;
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
+
+ return NGX_OK;
+ }
+
+ if (rc == Z_STREAM_END) {
+
+ if (ngx_http_gzip_filter_deflate_end(r, ctx) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+ if (conf->no_buffer && ctx->in == NULL) {
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = ctx->out_buf;
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ return NGX_OK;
+ }
+
+ return NGX_AGAIN;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_deflate_end(ngx_http_request_t *r,
+ ngx_http_gzip_ctx_t *ctx)
+{
+ int rc;
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+ struct gztrailer *trailer;
+
+ ctx->zin = ctx->zstream.total_in;
+ ctx->zout = 10 + ctx->zstream.total_out + 8;
+
+ rc = deflateEnd(&ctx->zstream);
+
+ if (rc != Z_OK) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "deflateEnd() failed: %d", rc);
+ return NGX_ERROR;
+ }
+
+ ngx_pfree(r->pool, ctx->preallocated);
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = ctx->out_buf;
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ if (ctx->zstream.avail_out >= 8) {
+ trailer = (struct gztrailer *) ctx->out_buf->last;
+ ctx->out_buf->last += 8;
+ ctx->out_buf->last_buf = 1;
+
+ } else {
+ b = ngx_create_temp_buf(r->pool, 8);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->last_buf = 1;
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+ trailer = (struct gztrailer *) b->pos;
+ b->last += 8;
+ }
+
+#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED)
+
+ trailer->crc32 = ctx->crc32;
+ trailer->zlen = ctx->zin;
+
+#else
+
+ trailer->crc32[0] = (u_char) (ctx->crc32 & 0xff);
+ trailer->crc32[1] = (u_char) ((ctx->crc32 >> 8) & 0xff);
+ trailer->crc32[2] = (u_char) ((ctx->crc32 >> 16) & 0xff);
+ trailer->crc32[3] = (u_char) ((ctx->crc32 >> 24) & 0xff);
+
+ trailer->zlen[0] = (u_char) (ctx->zin & 0xff);
+ trailer->zlen[1] = (u_char) ((ctx->zin >> 8) & 0xff);
+ trailer->zlen[2] = (u_char) ((ctx->zin >> 16) & 0xff);
+ trailer->zlen[3] = (u_char) ((ctx->zin >> 24) & 0xff);
+
+#endif
+
+ ctx->zstream.avail_in = 0;
+ ctx->zstream.avail_out = 0;
+
+ ctx->done = 1;
+
+ r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_gzip_filter_alloc(void *opaque, u_int items, u_int size)
+{
+ ngx_http_gzip_ctx_t *ctx = opaque;
+
+ void *p;
+ ngx_uint_t alloc;
+
+ alloc = items * size;
+
+ if (alloc % 512 != 0 && alloc < 8192) {
+
+ /*
+ * The zlib deflate_state allocation, it takes about 6K,
+ * we allocate 8K. Other allocations are divisible by 512.
+ */
+
+ alloc = 8192;
+ }
+
+ if (alloc <= ctx->allocated) {
+ p = ctx->free_mem;
+ ctx->free_mem += alloc;
+ ctx->allocated -= alloc;
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
+ "gzip alloc: n:%ud s:%ud a:%ui p:%p",
+ items, size, alloc, p);
+
+ return p;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, ctx->request->connection->log, 0,
+ "gzip filter failed to use preallocated memory: %ud of %ui",
+ items * size, ctx->allocated);
+
+ p = ngx_palloc(ctx->request->pool, items * size);
+
+ return p;
+}
+
+
+static void
+ngx_http_gzip_filter_free(void *opaque, void *address)
+{
+#if 0
+ ngx_http_gzip_ctx_t *ctx = opaque;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
+ "gzip free: %p", address);
+#endif
+}
+
+
+static void
+ngx_http_gzip_filter_free_copy_buf(ngx_http_request_t *r,
+ ngx_http_gzip_ctx_t *ctx)
+{
+ ngx_chain_t *cl;
+
+ for (cl = ctx->copied; cl; cl = cl->next) {
+ ngx_pfree(r->pool, cl->buf->start);
+ }
+
+ ctx->copied = NULL;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var;
+
+ var = ngx_http_add_variable(cf, &ngx_http_gzip_ratio, NGX_HTTP_VAR_NOHASH);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = ngx_http_gzip_ratio_variable;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_ratio_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_uint_t zint, zfrac;
+ ngx_http_gzip_ctx_t *ctx;
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_gzip_filter_module);
+
+ if (ctx == NULL || ctx->zout == 0) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ v->data = ngx_pnalloc(r->pool, NGX_INT32_LEN + 3);
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ zint = (ngx_uint_t) (ctx->zin / ctx->zout);
+ zfrac = (ngx_uint_t) ((ctx->zin * 100 / ctx->zout) % 100);
+
+ if ((ctx->zin * 1000 / ctx->zout) % 10 > 4) {
+
+ /* the rounding, e.g., 2.125 to 2.13 */
+
+ zfrac++;
+
+ if (zfrac > 99) {
+ zint++;
+ zfrac = 0;
+ }
+ }
+
+ v->len = ngx_sprintf(v->data, "%ui.%02ui", zint, zfrac) - v->data;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_gzip_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_gzip_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_gzip_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->bufs.num = 0;
+ * conf->types = { NULL };
+ * conf->types_keys = NULL;
+ */
+
+ conf->enable = NGX_CONF_UNSET;
+ conf->no_buffer = NGX_CONF_UNSET;
+
+ conf->postpone_gzipping = NGX_CONF_UNSET_SIZE;
+ conf->level = NGX_CONF_UNSET;
+ conf->wbits = NGX_CONF_UNSET_SIZE;
+ conf->memlevel = NGX_CONF_UNSET_SIZE;
+ conf->min_length = NGX_CONF_UNSET;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_gzip_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_gzip_conf_t *prev = parent;
+ ngx_http_gzip_conf_t *conf = child;
+
+ ngx_conf_merge_value(conf->enable, prev->enable, 0);
+ ngx_conf_merge_value(conf->no_buffer, prev->no_buffer, 0);
+
+ ngx_conf_merge_bufs_value(conf->bufs, prev->bufs,
+ (128 * 1024) / ngx_pagesize, ngx_pagesize);
+
+ ngx_conf_merge_size_value(conf->postpone_gzipping, prev->postpone_gzipping,
+ 0);
+ ngx_conf_merge_value(conf->level, prev->level, 1);
+ ngx_conf_merge_size_value(conf->wbits, prev->wbits, MAX_WBITS);
+ ngx_conf_merge_size_value(conf->memlevel, prev->memlevel,
+ MAX_MEM_LEVEL - 1);
+ ngx_conf_merge_value(conf->min_length, prev->min_length, 20);
+
+ if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
+ &prev->types_keys, &prev->types,
+ ngx_http_html_default_types)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_gzip_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_gzip_body_filter;
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_http_gzip_window(ngx_conf_t *cf, void *post, void *data)
+{
+ size_t *np = data;
+
+ size_t wbits, wsize;
+
+ wbits = 15;
+
+ for (wsize = 32 * 1024; wsize > 256; wsize >>= 1) {
+
+ if (wsize == *np) {
+ *np = wbits;
+
+ return NGX_CONF_OK;
+ }
+
+ wbits--;
+ }
+
+ return "must be 512, 1k, 2k, 4k, 8k, 16k, or 32k";
+}
+
+
+static char *
+ngx_http_gzip_hash(ngx_conf_t *cf, void *post, void *data)
+{
+ size_t *np = data;
+
+ size_t memlevel, hsize;
+
+ memlevel = 9;
+
+ for (hsize = 128 * 1024; hsize > 256; hsize >>= 1) {
+
+ if (hsize == *np) {
+ *np = memlevel;
+
+ return NGX_CONF_OK;
+ }
+
+ memlevel--;
+ }
+
+ return "must be 512, 1k, 2k, 4k, 8k, 16k, 32k, 64k, or 128k";
+}
diff --git a/app/nginx/src/http/modules/ngx_http_gzip_static_module.c b/app/nginx/src/http/modules/ngx_http_gzip_static_module.c
new file mode 100644
index 0000000..b9294dd
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_gzip_static_module.c
@@ -0,0 +1,331 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_GZIP_STATIC_OFF 0
+#define NGX_HTTP_GZIP_STATIC_ON 1
+#define NGX_HTTP_GZIP_STATIC_ALWAYS 2
+
+
+typedef struct {
+ ngx_uint_t enable;
+} ngx_http_gzip_static_conf_t;
+
+
+static ngx_int_t ngx_http_gzip_static_handler(ngx_http_request_t *r);
+static void *ngx_http_gzip_static_create_conf(ngx_conf_t *cf);
+static char *ngx_http_gzip_static_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static ngx_int_t ngx_http_gzip_static_init(ngx_conf_t *cf);
+
+
+static ngx_conf_enum_t ngx_http_gzip_static[] = {
+ { ngx_string("off"), NGX_HTTP_GZIP_STATIC_OFF },
+ { ngx_string("on"), NGX_HTTP_GZIP_STATIC_ON },
+ { ngx_string("always"), NGX_HTTP_GZIP_STATIC_ALWAYS },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t ngx_http_gzip_static_commands[] = {
+
+ { ngx_string("gzip_static"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_gzip_static_conf_t, enable),
+ &ngx_http_gzip_static },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_gzip_static_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_gzip_static_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_gzip_static_create_conf, /* create location configuration */
+ ngx_http_gzip_static_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_gzip_static_module = {
+ NGX_MODULE_V1,
+ &ngx_http_gzip_static_module_ctx, /* module context */
+ ngx_http_gzip_static_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_gzip_static_handler(ngx_http_request_t *r)
+{
+ u_char *p;
+ size_t root;
+ ngx_str_t path;
+ ngx_int_t rc;
+ ngx_uint_t level;
+ ngx_log_t *log;
+ ngx_buf_t *b;
+ ngx_chain_t out;
+ ngx_table_elt_t *h;
+ ngx_open_file_info_t of;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_gzip_static_conf_t *gzcf;
+
+ if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
+ return NGX_DECLINED;
+ }
+
+ if (r->uri.data[r->uri.len - 1] == '/') {
+ return NGX_DECLINED;
+ }
+
+ gzcf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_static_module);
+
+ if (gzcf->enable == NGX_HTTP_GZIP_STATIC_OFF) {
+ return NGX_DECLINED;
+ }
+
+ if (gzcf->enable == NGX_HTTP_GZIP_STATIC_ON) {
+ rc = ngx_http_gzip_ok(r);
+
+ } else {
+ /* always */
+ rc = NGX_OK;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (!clcf->gzip_vary && rc != NGX_OK) {
+ return NGX_DECLINED;
+ }
+
+ log = r->connection->log;
+
+ p = ngx_http_map_uri_to_path(r, &path, &root, sizeof(".gz") - 1);
+ if (p == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ *p++ = '.';
+ *p++ = 'g';
+ *p++ = 'z';
+ *p = '\0';
+
+ path.len = p - path.data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "http filename: \"%s\"", path.data);
+
+ ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+ of.read_ahead = clcf->read_ahead;
+ of.directio = clcf->directio;
+ of.valid = clcf->open_file_cache_valid;
+ of.min_uses = clcf->open_file_cache_min_uses;
+ of.errors = clcf->open_file_cache_errors;
+ of.events = clcf->open_file_cache_events;
+
+ if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+ != NGX_OK)
+ {
+ switch (of.err) {
+
+ case 0:
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+
+ case NGX_ENOENT:
+ case NGX_ENOTDIR:
+ case NGX_ENAMETOOLONG:
+
+ return NGX_DECLINED;
+
+ case NGX_EACCES:
+#if (NGX_HAVE_OPENAT)
+ case NGX_EMLINK:
+ case NGX_ELOOP:
+#endif
+
+ level = NGX_LOG_ERR;
+ break;
+
+ default:
+
+ level = NGX_LOG_CRIT;
+ break;
+ }
+
+ ngx_log_error(level, log, of.err,
+ "%s \"%s\" failed", of.failed, path.data);
+
+ return NGX_DECLINED;
+ }
+
+ if (gzcf->enable == NGX_HTTP_GZIP_STATIC_ON) {
+ r->gzip_vary = 1;
+
+ if (rc != NGX_OK) {
+ return NGX_DECLINED;
+ }
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", of.fd);
+
+ if (of.is_dir) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir");
+ return NGX_DECLINED;
+ }
+
+#if !(NGX_WIN32) /* the not regular files are probably Unix specific */
+
+ if (!of.is_file) {
+ ngx_log_error(NGX_LOG_CRIT, log, 0,
+ "\"%s\" is not a regular file", path.data);
+
+ return NGX_HTTP_NOT_FOUND;
+ }
+
+#endif
+
+ r->root_tested = !r->error_page;
+
+ rc = ngx_http_discard_request_body(r);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ log->action = "sending response to client";
+
+ r->headers_out.status = NGX_HTTP_OK;
+ r->headers_out.content_length_n = of.size;
+ r->headers_out.last_modified_time = of.mtime;
+
+ if (ngx_http_set_etag(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_http_set_content_type(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ h = ngx_list_push(&r->headers_out.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->hash = 1;
+ ngx_str_set(&h->key, "Content-Encoding");
+ ngx_str_set(&h->value, "gzip");
+ r->headers_out.content_encoding = h;
+
+ /* we need to allocate all before the header would be sent */
+
+ b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
+ if (b == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
+ if (b->file == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ return rc;
+ }
+
+ b->file_pos = 0;
+ b->file_last = of.size;
+
+ b->in_file = b->file_last ? 1 : 0;
+ b->last_buf = (r == r->main) ? 1 : 0;
+ b->last_in_chain = 1;
+
+ b->file->fd = of.fd;
+ b->file->name = path;
+ b->file->log = log;
+ b->file->directio = of.is_directio;
+
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_output_filter(r, &out);
+}
+
+
+static void *
+ngx_http_gzip_static_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_gzip_static_conf_t *conf;
+
+ conf = ngx_palloc(cf->pool, sizeof(ngx_http_gzip_static_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->enable = NGX_CONF_UNSET_UINT;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_gzip_static_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_gzip_static_conf_t *prev = parent;
+ ngx_http_gzip_static_conf_t *conf = child;
+
+ ngx_conf_merge_uint_value(conf->enable, prev->enable,
+ NGX_HTTP_GZIP_STATIC_OFF);
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_gzip_static_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_gzip_static_handler;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_headers_filter_module.c b/app/nginx/src/http/modules/ngx_http_headers_filter_module.c
new file mode 100644
index 0000000..6738afe
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_headers_filter_module.c
@@ -0,0 +1,741 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct ngx_http_header_val_s ngx_http_header_val_t;
+
+typedef ngx_int_t (*ngx_http_set_header_pt)(ngx_http_request_t *r,
+ ngx_http_header_val_t *hv, ngx_str_t *value);
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_uint_t offset;
+ ngx_http_set_header_pt handler;
+} ngx_http_set_header_t;
+
+
+struct ngx_http_header_val_s {
+ ngx_http_complex_value_t value;
+ ngx_str_t key;
+ ngx_http_set_header_pt handler;
+ ngx_uint_t offset;
+ ngx_uint_t always; /* unsigned always:1 */
+};
+
+
+typedef enum {
+ NGX_HTTP_EXPIRES_OFF,
+ NGX_HTTP_EXPIRES_EPOCH,
+ NGX_HTTP_EXPIRES_MAX,
+ NGX_HTTP_EXPIRES_ACCESS,
+ NGX_HTTP_EXPIRES_MODIFIED,
+ NGX_HTTP_EXPIRES_DAILY,
+ NGX_HTTP_EXPIRES_UNSET
+} ngx_http_expires_t;
+
+
+typedef struct {
+ ngx_http_expires_t expires;
+ time_t expires_time;
+ ngx_http_complex_value_t *expires_value;
+ ngx_array_t *headers;
+} ngx_http_headers_conf_t;
+
+
+static ngx_int_t ngx_http_set_expires(ngx_http_request_t *r,
+ ngx_http_headers_conf_t *conf);
+static ngx_int_t ngx_http_parse_expires(ngx_str_t *value,
+ ngx_http_expires_t *expires, time_t *expires_time, char **err);
+static ngx_int_t ngx_http_add_cache_control(ngx_http_request_t *r,
+ ngx_http_header_val_t *hv, ngx_str_t *value);
+static ngx_int_t ngx_http_add_header(ngx_http_request_t *r,
+ ngx_http_header_val_t *hv, ngx_str_t *value);
+static ngx_int_t ngx_http_set_last_modified(ngx_http_request_t *r,
+ ngx_http_header_val_t *hv, ngx_str_t *value);
+static ngx_int_t ngx_http_set_response_header(ngx_http_request_t *r,
+ ngx_http_header_val_t *hv, ngx_str_t *value);
+
+static void *ngx_http_headers_create_conf(ngx_conf_t *cf);
+static char *ngx_http_headers_merge_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_headers_filter_init(ngx_conf_t *cf);
+static char *ngx_http_headers_expires(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_headers_add(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_http_set_header_t ngx_http_set_headers[] = {
+
+ { ngx_string("Cache-Control"), 0, ngx_http_add_cache_control },
+
+ { ngx_string("Last-Modified"),
+ offsetof(ngx_http_headers_out_t, last_modified),
+ ngx_http_set_last_modified },
+
+ { ngx_string("ETag"),
+ offsetof(ngx_http_headers_out_t, etag),
+ ngx_http_set_response_header },
+
+ { ngx_null_string, 0, NULL }
+};
+
+
+static ngx_command_t ngx_http_headers_filter_commands[] = {
+
+ { ngx_string("expires"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_TAKE12,
+ ngx_http_headers_expires,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL},
+
+ { ngx_string("add_header"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_TAKE23,
+ ngx_http_headers_add,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL},
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_headers_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_headers_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_headers_create_conf, /* create location configuration */
+ ngx_http_headers_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_headers_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_headers_filter_module_ctx, /* module context */
+ ngx_http_headers_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+
+
+static ngx_int_t
+ngx_http_headers_filter(ngx_http_request_t *r)
+{
+ ngx_str_t value;
+ ngx_uint_t i, safe_status;
+ ngx_http_header_val_t *h;
+ ngx_http_headers_conf_t *conf;
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module);
+
+ if ((conf->expires == NGX_HTTP_EXPIRES_OFF && conf->headers == NULL)
+ || r != r->main)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ switch (r->headers_out.status) {
+
+ case NGX_HTTP_OK:
+ case NGX_HTTP_CREATED:
+ case NGX_HTTP_NO_CONTENT:
+ case NGX_HTTP_PARTIAL_CONTENT:
+ case NGX_HTTP_MOVED_PERMANENTLY:
+ case NGX_HTTP_MOVED_TEMPORARILY:
+ case NGX_HTTP_SEE_OTHER:
+ case NGX_HTTP_NOT_MODIFIED:
+ case NGX_HTTP_TEMPORARY_REDIRECT:
+ safe_status = 1;
+ break;
+
+ default:
+ safe_status = 0;
+ break;
+ }
+
+ if (conf->expires != NGX_HTTP_EXPIRES_OFF && safe_status) {
+ if (ngx_http_set_expires(r, conf) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ if (conf->headers) {
+ h = conf->headers->elts;
+ for (i = 0; i < conf->headers->nelts; i++) {
+
+ if (!safe_status && !h[i].always) {
+ continue;
+ }
+
+ if (ngx_http_complex_value(r, &h[i].value, &value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (h[i].handler(r, &h[i], &value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+ }
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_set_expires(ngx_http_request_t *r, ngx_http_headers_conf_t *conf)
+{
+ char *err;
+ size_t len;
+ time_t now, expires_time, max_age;
+ ngx_str_t value;
+ ngx_int_t rc;
+ ngx_uint_t i;
+ ngx_table_elt_t *e, *cc, **ccp;
+ ngx_http_expires_t expires;
+
+ expires = conf->expires;
+ expires_time = conf->expires_time;
+
+ if (conf->expires_value != NULL) {
+
+ if (ngx_http_complex_value(r, conf->expires_value, &value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ rc = ngx_http_parse_expires(&value, &expires, &expires_time, &err);
+
+ if (rc != NGX_OK) {
+ return NGX_OK;
+ }
+
+ if (expires == NGX_HTTP_EXPIRES_OFF) {
+ return NGX_OK;
+ }
+ }
+
+ e = r->headers_out.expires;
+
+ if (e == NULL) {
+
+ e = ngx_list_push(&r->headers_out.headers);
+ if (e == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->headers_out.expires = e;
+
+ e->hash = 1;
+ ngx_str_set(&e->key, "Expires");
+ }
+
+ len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT");
+ e->value.len = len - 1;
+
+ ccp = r->headers_out.cache_control.elts;
+
+ if (ccp == NULL) {
+
+ if (ngx_array_init(&r->headers_out.cache_control, r->pool,
+ 1, sizeof(ngx_table_elt_t *))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ ccp = ngx_array_push(&r->headers_out.cache_control);
+ if (ccp == NULL) {
+ return NGX_ERROR;
+ }
+
+ cc = ngx_list_push(&r->headers_out.headers);
+ if (cc == NULL) {
+ return NGX_ERROR;
+ }
+
+ cc->hash = 1;
+ ngx_str_set(&cc->key, "Cache-Control");
+ *ccp = cc;
+
+ } else {
+ for (i = 1; i < r->headers_out.cache_control.nelts; i++) {
+ ccp[i]->hash = 0;
+ }
+
+ cc = ccp[0];
+ }
+
+ if (expires == NGX_HTTP_EXPIRES_EPOCH) {
+ e->value.data = (u_char *) "Thu, 01 Jan 1970 00:00:01 GMT";
+ ngx_str_set(&cc->value, "no-cache");
+ return NGX_OK;
+ }
+
+ if (expires == NGX_HTTP_EXPIRES_MAX) {
+ e->value.data = (u_char *) "Thu, 31 Dec 2037 23:55:55 GMT";
+ /* 10 years */
+ ngx_str_set(&cc->value, "max-age=315360000");
+ return NGX_OK;
+ }
+
+ e->value.data = ngx_pnalloc(r->pool, len);
+ if (e->value.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (expires_time == 0 && expires != NGX_HTTP_EXPIRES_DAILY) {
+ ngx_memcpy(e->value.data, ngx_cached_http_time.data,
+ ngx_cached_http_time.len + 1);
+ ngx_str_set(&cc->value, "max-age=0");
+ return NGX_OK;
+ }
+
+ now = ngx_time();
+
+ if (expires == NGX_HTTP_EXPIRES_DAILY) {
+ expires_time = ngx_next_time(expires_time);
+ max_age = expires_time - now;
+
+ } else if (expires == NGX_HTTP_EXPIRES_ACCESS
+ || r->headers_out.last_modified_time == -1)
+ {
+ max_age = expires_time;
+ expires_time += now;
+
+ } else {
+ expires_time += r->headers_out.last_modified_time;
+ max_age = expires_time - now;
+ }
+
+ ngx_http_time(e->value.data, expires_time);
+
+ if (conf->expires_time < 0 || max_age < 0) {
+ ngx_str_set(&cc->value, "no-cache");
+ return NGX_OK;
+ }
+
+ cc->value.data = ngx_pnalloc(r->pool,
+ sizeof("max-age=") + NGX_TIME_T_LEN + 1);
+ if (cc->value.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ cc->value.len = ngx_sprintf(cc->value.data, "max-age=%T", max_age)
+ - cc->value.data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_parse_expires(ngx_str_t *value, ngx_http_expires_t *expires,
+ time_t *expires_time, char **err)
+{
+ ngx_uint_t minus;
+
+ if (*expires != NGX_HTTP_EXPIRES_MODIFIED) {
+
+ if (value->len == 5 && ngx_strncmp(value->data, "epoch", 5) == 0) {
+ *expires = NGX_HTTP_EXPIRES_EPOCH;
+ return NGX_OK;
+ }
+
+ if (value->len == 3 && ngx_strncmp(value->data, "max", 3) == 0) {
+ *expires = NGX_HTTP_EXPIRES_MAX;
+ return NGX_OK;
+ }
+
+ if (value->len == 3 && ngx_strncmp(value->data, "off", 3) == 0) {
+ *expires = NGX_HTTP_EXPIRES_OFF;
+ return NGX_OK;
+ }
+ }
+
+ if (value->len && value->data[0] == '@') {
+ value->data++;
+ value->len--;
+ minus = 0;
+
+ if (*expires == NGX_HTTP_EXPIRES_MODIFIED) {
+ *err = "daily time cannot be used with \"modified\" parameter";
+ return NGX_ERROR;
+ }
+
+ *expires = NGX_HTTP_EXPIRES_DAILY;
+
+ } else if (value->len && value->data[0] == '+') {
+ value->data++;
+ value->len--;
+ minus = 0;
+
+ } else if (value->len && value->data[0] == '-') {
+ value->data++;
+ value->len--;
+ minus = 1;
+
+ } else {
+ minus = 0;
+ }
+
+ *expires_time = ngx_parse_time(value, 1);
+
+ if (*expires_time == (time_t) NGX_ERROR) {
+ *err = "invalid value";
+ return NGX_ERROR;
+ }
+
+ if (*expires == NGX_HTTP_EXPIRES_DAILY
+ && *expires_time > 24 * 60 * 60)
+ {
+ *err = "daily time value must be less than 24 hours";
+ return NGX_ERROR;
+ }
+
+ if (minus) {
+ *expires_time = - *expires_time;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_add_header(ngx_http_request_t *r, ngx_http_header_val_t *hv,
+ ngx_str_t *value)
+{
+ ngx_table_elt_t *h;
+
+ if (value->len) {
+ h = ngx_list_push(&r->headers_out.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->hash = 1;
+ h->key = hv->key;
+ h->value = *value;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_add_cache_control(ngx_http_request_t *r, ngx_http_header_val_t *hv,
+ ngx_str_t *value)
+{
+ ngx_table_elt_t *cc, **ccp;
+
+ if (value->len == 0) {
+ return NGX_OK;
+ }
+
+ ccp = r->headers_out.cache_control.elts;
+
+ if (ccp == NULL) {
+
+ if (ngx_array_init(&r->headers_out.cache_control, r->pool,
+ 1, sizeof(ngx_table_elt_t *))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ ccp = ngx_array_push(&r->headers_out.cache_control);
+ if (ccp == NULL) {
+ return NGX_ERROR;
+ }
+
+ cc = ngx_list_push(&r->headers_out.headers);
+ if (cc == NULL) {
+ return NGX_ERROR;
+ }
+
+ cc->hash = 1;
+ ngx_str_set(&cc->key, "Cache-Control");
+ cc->value = *value;
+
+ *ccp = cc;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_set_last_modified(ngx_http_request_t *r, ngx_http_header_val_t *hv,
+ ngx_str_t *value)
+{
+ if (ngx_http_set_response_header(r, hv, value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ r->headers_out.last_modified_time =
+ (value->len) ? ngx_parse_http_time(value->data, value->len) : -1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_set_response_header(ngx_http_request_t *r, ngx_http_header_val_t *hv,
+ ngx_str_t *value)
+{
+ ngx_table_elt_t *h, **old;
+
+ old = (ngx_table_elt_t **) ((char *) &r->headers_out + hv->offset);
+
+ if (value->len == 0) {
+ if (*old) {
+ (*old)->hash = 0;
+ *old = NULL;
+ }
+
+ return NGX_OK;
+ }
+
+ if (*old) {
+ h = *old;
+
+ } else {
+ h = ngx_list_push(&r->headers_out.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *old = h;
+ }
+
+ h->hash = 1;
+ h->key = hv->key;
+ h->value = *value;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_headers_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_headers_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_headers_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->headers = NULL;
+ * conf->expires_time = 0;
+ * conf->expires_value = NULL;
+ */
+
+ conf->expires = NGX_HTTP_EXPIRES_UNSET;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_headers_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_headers_conf_t *prev = parent;
+ ngx_http_headers_conf_t *conf = child;
+
+ if (conf->expires == NGX_HTTP_EXPIRES_UNSET) {
+ conf->expires = prev->expires;
+ conf->expires_time = prev->expires_time;
+ conf->expires_value = prev->expires_value;
+
+ if (conf->expires == NGX_HTTP_EXPIRES_UNSET) {
+ conf->expires = NGX_HTTP_EXPIRES_OFF;
+ }
+ }
+
+ if (conf->headers == NULL) {
+ conf->headers = prev->headers;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_headers_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_headers_filter;
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_http_headers_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_headers_conf_t *hcf = conf;
+
+ char *err;
+ ngx_str_t *value;
+ ngx_int_t rc;
+ ngx_uint_t n;
+ ngx_http_complex_value_t cv;
+ ngx_http_compile_complex_value_t ccv;
+
+ if (hcf->expires != NGX_HTTP_EXPIRES_UNSET) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (cf->args->nelts == 2) {
+
+ hcf->expires = NGX_HTTP_EXPIRES_ACCESS;
+
+ n = 1;
+
+ } else { /* cf->args->nelts == 3 */
+
+ if (ngx_strcmp(value[1].data, "modified") != 0) {
+ return "invalid value";
+ }
+
+ hcf->expires = NGX_HTTP_EXPIRES_MODIFIED;
+
+ n = 2;
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[n];
+ ccv.complex_value = &cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cv.lengths != NULL) {
+
+ hcf->expires_value = ngx_palloc(cf->pool,
+ sizeof(ngx_http_complex_value_t));
+ if (hcf->expires_value == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *hcf->expires_value = cv;
+
+ return NGX_CONF_OK;
+ }
+
+ rc = ngx_http_parse_expires(&value[n], &hcf->expires, &hcf->expires_time,
+ &err);
+ if (rc != NGX_OK) {
+ return err;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_headers_add(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_headers_conf_t *hcf = conf;
+
+ ngx_str_t *value;
+ ngx_uint_t i;
+ ngx_http_header_val_t *hv;
+ ngx_http_set_header_t *set;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ if (hcf->headers == NULL) {
+ hcf->headers = ngx_array_create(cf->pool, 1,
+ sizeof(ngx_http_header_val_t));
+ if (hcf->headers == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ hv = ngx_array_push(hcf->headers);
+ if (hv == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ hv->key = value[1];
+ hv->handler = ngx_http_add_header;
+ hv->offset = 0;
+ hv->always = 0;
+
+ set = ngx_http_set_headers;
+ for (i = 0; set[i].name.len; i++) {
+ if (ngx_strcasecmp(value[1].data, set[i].name.data) != 0) {
+ continue;
+ }
+
+ hv->offset = set[i].offset;
+ hv->handler = set[i].handler;
+
+ break;
+ }
+
+ if (value[2].len == 0) {
+ ngx_memzero(&hv->value, sizeof(ngx_http_complex_value_t));
+
+ } else {
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[2];
+ ccv.complex_value = &hv->value;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (cf->args->nelts == 3) {
+ return NGX_CONF_OK;
+ }
+
+ if (ngx_strcmp(value[3].data, "always") != 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[3]);
+ return NGX_CONF_ERROR;
+ }
+
+ hv->always = 1;
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_image_filter_module.c b/app/nginx/src/http/modules/ngx_http_image_filter_module.c
new file mode 100644
index 0000000..dbec5d8
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_image_filter_module.c
@@ -0,0 +1,1675 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <gd.h>
+
+
+#define NGX_HTTP_IMAGE_OFF 0
+#define NGX_HTTP_IMAGE_TEST 1
+#define NGX_HTTP_IMAGE_SIZE 2
+#define NGX_HTTP_IMAGE_RESIZE 3
+#define NGX_HTTP_IMAGE_CROP 4
+#define NGX_HTTP_IMAGE_ROTATE 5
+
+
+#define NGX_HTTP_IMAGE_START 0
+#define NGX_HTTP_IMAGE_READ 1
+#define NGX_HTTP_IMAGE_PROCESS 2
+#define NGX_HTTP_IMAGE_PASS 3
+#define NGX_HTTP_IMAGE_DONE 4
+
+
+#define NGX_HTTP_IMAGE_NONE 0
+#define NGX_HTTP_IMAGE_JPEG 1
+#define NGX_HTTP_IMAGE_GIF 2
+#define NGX_HTTP_IMAGE_PNG 3
+#define NGX_HTTP_IMAGE_WEBP 4
+
+
+#define NGX_HTTP_IMAGE_BUFFERED 0x08
+
+
+typedef struct {
+ ngx_uint_t filter;
+ ngx_uint_t width;
+ ngx_uint_t height;
+ ngx_uint_t angle;
+ ngx_uint_t jpeg_quality;
+ ngx_uint_t webp_quality;
+ ngx_uint_t sharpen;
+
+ ngx_flag_t transparency;
+ ngx_flag_t interlace;
+
+ ngx_http_complex_value_t *wcv;
+ ngx_http_complex_value_t *hcv;
+ ngx_http_complex_value_t *acv;
+ ngx_http_complex_value_t *jqcv;
+ ngx_http_complex_value_t *wqcv;
+ ngx_http_complex_value_t *shcv;
+
+ size_t buffer_size;
+} ngx_http_image_filter_conf_t;
+
+
+typedef struct {
+ u_char *image;
+ u_char *last;
+
+ size_t length;
+
+ ngx_uint_t width;
+ ngx_uint_t height;
+ ngx_uint_t max_width;
+ ngx_uint_t max_height;
+ ngx_uint_t angle;
+
+ ngx_uint_t phase;
+ ngx_uint_t type;
+ ngx_uint_t force;
+} ngx_http_image_filter_ctx_t;
+
+
+static ngx_int_t ngx_http_image_send(ngx_http_request_t *r,
+ ngx_http_image_filter_ctx_t *ctx, ngx_chain_t *in);
+static ngx_uint_t ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in);
+static ngx_int_t ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in);
+static ngx_buf_t *ngx_http_image_process(ngx_http_request_t *r);
+static ngx_buf_t *ngx_http_image_json(ngx_http_request_t *r,
+ ngx_http_image_filter_ctx_t *ctx);
+static ngx_buf_t *ngx_http_image_asis(ngx_http_request_t *r,
+ ngx_http_image_filter_ctx_t *ctx);
+static void ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b);
+static ngx_int_t ngx_http_image_size(ngx_http_request_t *r,
+ ngx_http_image_filter_ctx_t *ctx);
+
+static ngx_buf_t *ngx_http_image_resize(ngx_http_request_t *r,
+ ngx_http_image_filter_ctx_t *ctx);
+static gdImagePtr ngx_http_image_source(ngx_http_request_t *r,
+ ngx_http_image_filter_ctx_t *ctx);
+static gdImagePtr ngx_http_image_new(ngx_http_request_t *r, int w, int h,
+ int colors);
+static u_char *ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type,
+ gdImagePtr img, int *size);
+static void ngx_http_image_cleanup(void *data);
+static ngx_uint_t ngx_http_image_filter_get_value(ngx_http_request_t *r,
+ ngx_http_complex_value_t *cv, ngx_uint_t v);
+static ngx_uint_t ngx_http_image_filter_value(ngx_str_t *value);
+
+
+static void *ngx_http_image_filter_create_conf(ngx_conf_t *cf);
+static char *ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf,
+ ngx_command_t *cmd, void *conf);
+static char *ngx_http_image_filter_webp_quality(ngx_conf_t *cf,
+ ngx_command_t *cmd, void *conf);
+static char *ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf);
+
+
+static ngx_command_t ngx_http_image_filter_commands[] = {
+
+ { ngx_string("image_filter"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
+ ngx_http_image_filter,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("image_filter_jpeg_quality"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_image_filter_jpeg_quality,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("image_filter_webp_quality"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_image_filter_webp_quality,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("image_filter_sharpen"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_image_filter_sharpen,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("image_filter_transparency"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_image_filter_conf_t, transparency),
+ NULL },
+
+ { ngx_string("image_filter_interlace"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_image_filter_conf_t, interlace),
+ NULL },
+
+ { ngx_string("image_filter_buffer"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_image_filter_conf_t, buffer_size),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_image_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_image_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_image_filter_create_conf, /* create location configuration */
+ ngx_http_image_filter_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_image_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_image_filter_module_ctx, /* module context */
+ ngx_http_image_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
+
+
+static ngx_str_t ngx_http_image_types[] = {
+ ngx_string("image/jpeg"),
+ ngx_string("image/gif"),
+ ngx_string("image/png"),
+ ngx_string("image/webp")
+};
+
+
+static ngx_int_t
+ngx_http_image_header_filter(ngx_http_request_t *r)
+{
+ off_t len;
+ ngx_http_image_filter_ctx_t *ctx;
+ ngx_http_image_filter_conf_t *conf;
+
+ if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
+
+ if (ctx) {
+ ngx_http_set_ctx(r, NULL, ngx_http_image_filter_module);
+ return ngx_http_next_header_filter(r);
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
+
+ if (conf->filter == NGX_HTTP_IMAGE_OFF) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ if (r->headers_out.content_type.len
+ >= sizeof("multipart/x-mixed-replace") - 1
+ && ngx_strncasecmp(r->headers_out.content_type.data,
+ (u_char *) "multipart/x-mixed-replace",
+ sizeof("multipart/x-mixed-replace") - 1)
+ == 0)
+ {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "image filter: multipart/x-mixed-replace response");
+
+ return NGX_ERROR;
+ }
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_image_filter_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_image_filter_module);
+
+ len = r->headers_out.content_length_n;
+
+ if (len != -1 && len > (off_t) conf->buffer_size) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "image filter: too big response: %O", len);
+
+ return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
+ }
+
+ if (len == -1) {
+ ctx->length = conf->buffer_size;
+
+ } else {
+ ctx->length = (size_t) len;
+ }
+
+ if (r->headers_out.refresh) {
+ r->headers_out.refresh->hash = 0;
+ }
+
+ r->main_filter_need_in_memory = 1;
+ r->allow_ranges = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_image_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ ngx_int_t rc;
+ ngx_str_t *ct;
+ ngx_chain_t out;
+ ngx_http_image_filter_ctx_t *ctx;
+ ngx_http_image_filter_conf_t *conf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image filter");
+
+ if (in == NULL) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
+
+ if (ctx == NULL) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ switch (ctx->phase) {
+
+ case NGX_HTTP_IMAGE_START:
+
+ ctx->type = ngx_http_image_test(r, in);
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
+
+ if (ctx->type == NGX_HTTP_IMAGE_NONE) {
+
+ if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
+ out.buf = ngx_http_image_json(r, NULL);
+
+ if (out.buf) {
+ out.next = NULL;
+ ctx->phase = NGX_HTTP_IMAGE_DONE;
+
+ return ngx_http_image_send(r, ctx, &out);
+ }
+ }
+
+ return ngx_http_filter_finalize_request(r,
+ &ngx_http_image_filter_module,
+ NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
+ }
+
+ /* override content type */
+
+ ct = &ngx_http_image_types[ctx->type - 1];
+ r->headers_out.content_type_len = ct->len;
+ r->headers_out.content_type = *ct;
+ r->headers_out.content_type_lowcase = NULL;
+
+ if (conf->filter == NGX_HTTP_IMAGE_TEST) {
+ ctx->phase = NGX_HTTP_IMAGE_PASS;
+
+ return ngx_http_image_send(r, ctx, in);
+ }
+
+ ctx->phase = NGX_HTTP_IMAGE_READ;
+
+ /* fall through */
+
+ case NGX_HTTP_IMAGE_READ:
+
+ rc = ngx_http_image_read(r, in);
+
+ if (rc == NGX_AGAIN) {
+ return NGX_OK;
+ }
+
+ if (rc == NGX_ERROR) {
+ return ngx_http_filter_finalize_request(r,
+ &ngx_http_image_filter_module,
+ NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
+ }
+
+ /* fall through */
+
+ case NGX_HTTP_IMAGE_PROCESS:
+
+ out.buf = ngx_http_image_process(r);
+
+ if (out.buf == NULL) {
+ return ngx_http_filter_finalize_request(r,
+ &ngx_http_image_filter_module,
+ NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
+ }
+
+ out.next = NULL;
+ ctx->phase = NGX_HTTP_IMAGE_PASS;
+
+ return ngx_http_image_send(r, ctx, &out);
+
+ case NGX_HTTP_IMAGE_PASS:
+
+ return ngx_http_next_body_filter(r, in);
+
+ default: /* NGX_HTTP_IMAGE_DONE */
+
+ rc = ngx_http_next_body_filter(r, NULL);
+
+ /* NGX_ERROR resets any pending data */
+ return (rc == NGX_OK) ? NGX_ERROR : rc;
+ }
+}
+
+
+static ngx_int_t
+ngx_http_image_send(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx,
+ ngx_chain_t *in)
+{
+ ngx_int_t rc;
+
+ rc = ngx_http_next_header_filter(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ return NGX_ERROR;
+ }
+
+ rc = ngx_http_next_body_filter(r, in);
+
+ if (ctx->phase == NGX_HTTP_IMAGE_DONE) {
+ /* NGX_ERROR resets any pending data */
+ return (rc == NGX_OK) ? NGX_ERROR : rc;
+ }
+
+ return rc;
+}
+
+
+static ngx_uint_t
+ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ u_char *p;
+
+ p = in->buf->pos;
+
+ if (in->buf->last - p < 16) {
+ return NGX_HTTP_IMAGE_NONE;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "image filter: \"%c%c\"", p[0], p[1]);
+
+ if (p[0] == 0xff && p[1] == 0xd8) {
+
+ /* JPEG */
+
+ return NGX_HTTP_IMAGE_JPEG;
+
+ } else if (p[0] == 'G' && p[1] == 'I' && p[2] == 'F' && p[3] == '8'
+ && p[5] == 'a')
+ {
+ if (p[4] == '9' || p[4] == '7') {
+ /* GIF */
+ return NGX_HTTP_IMAGE_GIF;
+ }
+
+ } else if (p[0] == 0x89 && p[1] == 'P' && p[2] == 'N' && p[3] == 'G'
+ && p[4] == 0x0d && p[5] == 0x0a && p[6] == 0x1a && p[7] == 0x0a)
+ {
+ /* PNG */
+
+ return NGX_HTTP_IMAGE_PNG;
+
+ } else if (p[0] == 'R' && p[1] == 'I' && p[2] == 'F' && p[3] == 'F'
+ && p[8] == 'W' && p[9] == 'E' && p[10] == 'B' && p[11] == 'P')
+ {
+ /* WebP */
+
+ return NGX_HTTP_IMAGE_WEBP;
+ }
+
+ return NGX_HTTP_IMAGE_NONE;
+}
+
+
+static ngx_int_t
+ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ u_char *p;
+ size_t size, rest;
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+ ngx_http_image_filter_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
+
+ if (ctx->image == NULL) {
+ ctx->image = ngx_palloc(r->pool, ctx->length);
+ if (ctx->image == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->last = ctx->image;
+ }
+
+ p = ctx->last;
+
+ for (cl = in; cl; cl = cl->next) {
+
+ b = cl->buf;
+ size = b->last - b->pos;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "image buf: %uz", size);
+
+ rest = ctx->image + ctx->length - p;
+
+ if (size > rest) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "image filter: too big response");
+ return NGX_ERROR;
+ }
+
+ p = ngx_cpymem(p, b->pos, size);
+ b->pos += size;
+
+ if (b->last_buf) {
+ ctx->last = p;
+ return NGX_OK;
+ }
+ }
+
+ ctx->last = p;
+ r->connection->buffered |= NGX_HTTP_IMAGE_BUFFERED;
+
+ return NGX_AGAIN;
+}
+
+
+static ngx_buf_t *
+ngx_http_image_process(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_http_image_filter_ctx_t *ctx;
+ ngx_http_image_filter_conf_t *conf;
+
+ r->connection->buffered &= ~NGX_HTTP_IMAGE_BUFFERED;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
+
+ rc = ngx_http_image_size(r, ctx);
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
+
+ if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
+ return ngx_http_image_json(r, rc == NGX_OK ? ctx : NULL);
+ }
+
+ ctx->angle = ngx_http_image_filter_get_value(r, conf->acv, conf->angle);
+
+ if (conf->filter == NGX_HTTP_IMAGE_ROTATE) {
+
+ if (ctx->angle != 90 && ctx->angle != 180 && ctx->angle != 270) {
+ return NULL;
+ }
+
+ return ngx_http_image_resize(r, ctx);
+ }
+
+ ctx->max_width = ngx_http_image_filter_get_value(r, conf->wcv, conf->width);
+ if (ctx->max_width == 0) {
+ return NULL;
+ }
+
+ ctx->max_height = ngx_http_image_filter_get_value(r, conf->hcv,
+ conf->height);
+ if (ctx->max_height == 0) {
+ return NULL;
+ }
+
+ if (rc == NGX_OK
+ && ctx->width <= ctx->max_width
+ && ctx->height <= ctx->max_height
+ && ctx->angle == 0
+ && !ctx->force)
+ {
+ return ngx_http_image_asis(r, ctx);
+ }
+
+ return ngx_http_image_resize(r, ctx);
+}
+
+
+static ngx_buf_t *
+ngx_http_image_json(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
+{
+ size_t len;
+ ngx_buf_t *b;
+
+ b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
+ if (b == NULL) {
+ return NULL;
+ }
+
+ b->memory = 1;
+ b->last_buf = 1;
+
+ ngx_http_clean_header(r);
+
+ r->headers_out.status = NGX_HTTP_OK;
+ r->headers_out.content_type_len = sizeof("application/json") - 1;
+ ngx_str_set(&r->headers_out.content_type, "application/json");
+ r->headers_out.content_type_lowcase = NULL;
+
+ if (ctx == NULL) {
+ b->pos = (u_char *) "{}" CRLF;
+ b->last = b->pos + sizeof("{}" CRLF) - 1;
+
+ ngx_http_image_length(r, b);
+
+ return b;
+ }
+
+ len = sizeof("{ \"img\" : "
+ "{ \"width\": , \"height\": , \"type\": \"jpeg\" } }" CRLF) - 1
+ + 2 * NGX_SIZE_T_LEN;
+
+ b->pos = ngx_pnalloc(r->pool, len);
+ if (b->pos == NULL) {
+ return NULL;
+ }
+
+ b->last = ngx_sprintf(b->pos,
+ "{ \"img\" : "
+ "{ \"width\": %uz,"
+ " \"height\": %uz,"
+ " \"type\": \"%s\" } }" CRLF,
+ ctx->width, ctx->height,
+ ngx_http_image_types[ctx->type - 1].data + 6);
+
+ ngx_http_image_length(r, b);
+
+ return b;
+}
+
+
+static ngx_buf_t *
+ngx_http_image_asis(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
+{
+ ngx_buf_t *b;
+
+ b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
+ if (b == NULL) {
+ return NULL;
+ }
+
+ b->pos = ctx->image;
+ b->last = ctx->last;
+ b->memory = 1;
+ b->last_buf = 1;
+
+ ngx_http_image_length(r, b);
+
+ return b;
+}
+
+
+static void
+ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b)
+{
+ r->headers_out.content_length_n = b->last - b->pos;
+
+ if (r->headers_out.content_length) {
+ r->headers_out.content_length->hash = 0;
+ }
+
+ r->headers_out.content_length = NULL;
+}
+
+
+static ngx_int_t
+ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
+{
+ u_char *p, *last;
+ size_t len, app;
+ ngx_uint_t width, height;
+
+ p = ctx->image;
+
+ switch (ctx->type) {
+
+ case NGX_HTTP_IMAGE_JPEG:
+
+ p += 2;
+ last = ctx->image + ctx->length - 10;
+ width = 0;
+ height = 0;
+ app = 0;
+
+ while (p < last) {
+
+ if (p[0] == 0xff && p[1] != 0xff) {
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "JPEG: %02xd %02xd", p[0], p[1]);
+
+ p++;
+
+ if ((*p == 0xc0 || *p == 0xc1 || *p == 0xc2 || *p == 0xc3
+ || *p == 0xc9 || *p == 0xca || *p == 0xcb)
+ && (width == 0 || height == 0))
+ {
+ width = p[6] * 256 + p[7];
+ height = p[4] * 256 + p[5];
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "JPEG: %02xd %02xd", p[1], p[2]);
+
+ len = p[1] * 256 + p[2];
+
+ if (*p >= 0xe1 && *p <= 0xef) {
+ /* application data, e.g., EXIF, Adobe XMP, etc. */
+ app += len;
+ }
+
+ p += len;
+
+ continue;
+ }
+
+ p++;
+ }
+
+ if (width == 0 || height == 0) {
+ return NGX_DECLINED;
+ }
+
+ if (ctx->length / 20 < app) {
+ /* force conversion if application data consume more than 5% */
+ ctx->force = 1;
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "app data size: %uz", app);
+ }
+
+ break;
+
+ case NGX_HTTP_IMAGE_GIF:
+
+ if (ctx->length < 10) {
+ return NGX_DECLINED;
+ }
+
+ width = p[7] * 256 + p[6];
+ height = p[9] * 256 + p[8];
+
+ break;
+
+ case NGX_HTTP_IMAGE_PNG:
+
+ if (ctx->length < 24) {
+ return NGX_DECLINED;
+ }
+
+ width = p[18] * 256 + p[19];
+ height = p[22] * 256 + p[23];
+
+ break;
+
+ case NGX_HTTP_IMAGE_WEBP:
+
+ if (ctx->length < 30) {
+ return NGX_DECLINED;
+ }
+
+ if (p[12] != 'V' || p[13] != 'P' || p[14] != '8') {
+ return NGX_DECLINED;
+ }
+
+ switch (p[15]) {
+
+ case ' ':
+ if (p[20] & 1) {
+ /* not a key frame */
+ return NGX_DECLINED;
+ }
+
+ if (p[23] != 0x9d || p[24] != 0x01 || p[25] != 0x2a) {
+ /* invalid start code */
+ return NGX_DECLINED;
+ }
+
+ width = (p[26] | p[27] << 8) & 0x3fff;
+ height = (p[28] | p[29] << 8) & 0x3fff;
+
+ break;
+
+ case 'L':
+ if (p[20] != 0x2f) {
+ /* invalid signature */
+ return NGX_DECLINED;
+ }
+
+ width = ((p[21] | p[22] << 8) & 0x3fff) + 1;
+ height = ((p[22] >> 6 | p[23] << 2 | p[24] << 10) & 0x3fff) + 1;
+
+ break;
+
+ case 'X':
+ width = (p[24] | p[25] << 8 | p[26] << 16) + 1;
+ height = (p[27] | p[28] << 8 | p[29] << 16) + 1;
+ break;
+
+ default:
+ return NGX_DECLINED;
+ }
+
+ break;
+
+ default:
+
+ return NGX_DECLINED;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "image size: %d x %d", (int) width, (int) height);
+
+ ctx->width = width;
+ ctx->height = height;
+
+ return NGX_OK;
+}
+
+
+static ngx_buf_t *
+ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
+{
+ int sx, sy, dx, dy, ox, oy, ax, ay, size,
+ colors, palette, transparent, sharpen,
+ red, green, blue, t;
+ u_char *out;
+ ngx_buf_t *b;
+ ngx_uint_t resize;
+ gdImagePtr src, dst;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_image_filter_conf_t *conf;
+
+ src = ngx_http_image_source(r, ctx);
+
+ if (src == NULL) {
+ return NULL;
+ }
+
+ sx = gdImageSX(src);
+ sy = gdImageSY(src);
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
+
+ if (!ctx->force
+ && ctx->angle == 0
+ && (ngx_uint_t) sx <= ctx->max_width
+ && (ngx_uint_t) sy <= ctx->max_height)
+ {
+ gdImageDestroy(src);
+ return ngx_http_image_asis(r, ctx);
+ }
+
+ colors = gdImageColorsTotal(src);
+
+ if (colors && conf->transparency) {
+ transparent = gdImageGetTransparent(src);
+
+ if (transparent != -1) {
+ palette = colors;
+ red = gdImageRed(src, transparent);
+ green = gdImageGreen(src, transparent);
+ blue = gdImageBlue(src, transparent);
+
+ goto transparent;
+ }
+ }
+
+ palette = 0;
+ transparent = -1;
+ red = 0;
+ green = 0;
+ blue = 0;
+
+transparent:
+
+ gdImageColorTransparent(src, -1);
+
+ dx = sx;
+ dy = sy;
+
+ if (conf->filter == NGX_HTTP_IMAGE_RESIZE) {
+
+ if ((ngx_uint_t) dx > ctx->max_width) {
+ dy = dy * ctx->max_width / dx;
+ dy = dy ? dy : 1;
+ dx = ctx->max_width;
+ }
+
+ if ((ngx_uint_t) dy > ctx->max_height) {
+ dx = dx * ctx->max_height / dy;
+ dx = dx ? dx : 1;
+ dy = ctx->max_height;
+ }
+
+ resize = 1;
+
+ } else if (conf->filter == NGX_HTTP_IMAGE_ROTATE) {
+
+ resize = 0;
+
+ } else { /* NGX_HTTP_IMAGE_CROP */
+
+ resize = 0;
+
+ if ((double) dx / dy < (double) ctx->max_width / ctx->max_height) {
+ if ((ngx_uint_t) dx > ctx->max_width) {
+ dy = dy * ctx->max_width / dx;
+ dy = dy ? dy : 1;
+ dx = ctx->max_width;
+ resize = 1;
+ }
+
+ } else {
+ if ((ngx_uint_t) dy > ctx->max_height) {
+ dx = dx * ctx->max_height / dy;
+ dx = dx ? dx : 1;
+ dy = ctx->max_height;
+ resize = 1;
+ }
+ }
+ }
+
+ if (resize) {
+ dst = ngx_http_image_new(r, dx, dy, palette);
+ if (dst == NULL) {
+ gdImageDestroy(src);
+ return NULL;
+ }
+
+ if (colors == 0) {
+ gdImageSaveAlpha(dst, 1);
+ gdImageAlphaBlending(dst, 0);
+ }
+
+ gdImageCopyResampled(dst, src, 0, 0, 0, 0, dx, dy, sx, sy);
+
+ if (colors) {
+ gdImageTrueColorToPalette(dst, 1, 256);
+ }
+
+ gdImageDestroy(src);
+
+ } else {
+ dst = src;
+ }
+
+ if (ctx->angle) {
+ src = dst;
+
+ ax = (dx % 2 == 0) ? 1 : 0;
+ ay = (dy % 2 == 0) ? 1 : 0;
+
+ switch (ctx->angle) {
+
+ case 90:
+ case 270:
+ dst = ngx_http_image_new(r, dy, dx, palette);
+ if (dst == NULL) {
+ gdImageDestroy(src);
+ return NULL;
+ }
+ if (ctx->angle == 90) {
+ ox = dy / 2 + ay;
+ oy = dx / 2 - ax;
+
+ } else {
+ ox = dy / 2 - ay;
+ oy = dx / 2 + ax;
+ }
+
+ gdImageCopyRotated(dst, src, ox, oy, 0, 0,
+ dx + ax, dy + ay, ctx->angle);
+ gdImageDestroy(src);
+
+ t = dx;
+ dx = dy;
+ dy = t;
+ break;
+
+ case 180:
+ dst = ngx_http_image_new(r, dx, dy, palette);
+ if (dst == NULL) {
+ gdImageDestroy(src);
+ return NULL;
+ }
+ gdImageCopyRotated(dst, src, dx / 2 - ax, dy / 2 - ay, 0, 0,
+ dx + ax, dy + ay, ctx->angle);
+ gdImageDestroy(src);
+ break;
+ }
+ }
+
+ if (conf->filter == NGX_HTTP_IMAGE_CROP) {
+
+ src = dst;
+
+ if ((ngx_uint_t) dx > ctx->max_width) {
+ ox = dx - ctx->max_width;
+
+ } else {
+ ox = 0;
+ }
+
+ if ((ngx_uint_t) dy > ctx->max_height) {
+ oy = dy - ctx->max_height;
+
+ } else {
+ oy = 0;
+ }
+
+ if (ox || oy) {
+
+ dst = ngx_http_image_new(r, dx - ox, dy - oy, colors);
+
+ if (dst == NULL) {
+ gdImageDestroy(src);
+ return NULL;
+ }
+
+ ox /= 2;
+ oy /= 2;
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "image crop: %d x %d @ %d x %d",
+ dx, dy, ox, oy);
+
+ if (colors == 0) {
+ gdImageSaveAlpha(dst, 1);
+ gdImageAlphaBlending(dst, 0);
+ }
+
+ gdImageCopy(dst, src, 0, 0, ox, oy, dx - ox, dy - oy);
+
+ if (colors) {
+ gdImageTrueColorToPalette(dst, 1, 256);
+ }
+
+ gdImageDestroy(src);
+ }
+ }
+
+ if (transparent != -1 && colors) {
+ gdImageColorTransparent(dst, gdImageColorExact(dst, red, green, blue));
+ }
+
+ sharpen = ngx_http_image_filter_get_value(r, conf->shcv, conf->sharpen);
+ if (sharpen > 0) {
+ gdImageSharpen(dst, sharpen);
+ }
+
+ gdImageInterlace(dst, (int) conf->interlace);
+
+ out = ngx_http_image_out(r, ctx->type, dst, &size);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "image: %d x %d %d", sx, sy, colors);
+
+ gdImageDestroy(dst);
+ ngx_pfree(r->pool, ctx->image);
+
+ if (out == NULL) {
+ return NULL;
+ }
+
+ cln = ngx_pool_cleanup_add(r->pool, 0);
+ if (cln == NULL) {
+ gdFree(out);
+ return NULL;
+ }
+
+ b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
+ if (b == NULL) {
+ gdFree(out);
+ return NULL;
+ }
+
+ cln->handler = ngx_http_image_cleanup;
+ cln->data = out;
+
+ b->pos = out;
+ b->last = out + size;
+ b->memory = 1;
+ b->last_buf = 1;
+
+ ngx_http_image_length(r, b);
+ ngx_http_weak_etag(r);
+
+ return b;
+}
+
+
+static gdImagePtr
+ngx_http_image_source(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
+{
+ char *failed;
+ gdImagePtr img;
+
+ img = NULL;
+
+ switch (ctx->type) {
+
+ case NGX_HTTP_IMAGE_JPEG:
+ img = gdImageCreateFromJpegPtr(ctx->length, ctx->image);
+ failed = "gdImageCreateFromJpegPtr() failed";
+ break;
+
+ case NGX_HTTP_IMAGE_GIF:
+ img = gdImageCreateFromGifPtr(ctx->length, ctx->image);
+ failed = "gdImageCreateFromGifPtr() failed";
+ break;
+
+ case NGX_HTTP_IMAGE_PNG:
+ img = gdImageCreateFromPngPtr(ctx->length, ctx->image);
+ failed = "gdImageCreateFromPngPtr() failed";
+ break;
+
+ case NGX_HTTP_IMAGE_WEBP:
+#if (NGX_HAVE_GD_WEBP)
+ img = gdImageCreateFromWebpPtr(ctx->length, ctx->image);
+ failed = "gdImageCreateFromWebpPtr() failed";
+#else
+ failed = "nginx was built without GD WebP support";
+#endif
+ break;
+
+ default:
+ failed = "unknown image type";
+ break;
+ }
+
+ if (img == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed);
+ }
+
+ return img;
+}
+
+
+static gdImagePtr
+ngx_http_image_new(ngx_http_request_t *r, int w, int h, int colors)
+{
+ gdImagePtr img;
+
+ if (colors == 0) {
+ img = gdImageCreateTrueColor(w, h);
+
+ if (img == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "gdImageCreateTrueColor() failed");
+ return NULL;
+ }
+
+ } else {
+ img = gdImageCreate(w, h);
+
+ if (img == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "gdImageCreate() failed");
+ return NULL;
+ }
+ }
+
+ return img;
+}
+
+
+static u_char *
+ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img,
+ int *size)
+{
+ char *failed;
+ u_char *out;
+ ngx_int_t q;
+ ngx_http_image_filter_conf_t *conf;
+
+ out = NULL;
+
+ switch (type) {
+
+ case NGX_HTTP_IMAGE_JPEG:
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
+
+ q = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality);
+ if (q <= 0) {
+ return NULL;
+ }
+
+ out = gdImageJpegPtr(img, size, q);
+ failed = "gdImageJpegPtr() failed";
+ break;
+
+ case NGX_HTTP_IMAGE_GIF:
+ out = gdImageGifPtr(img, size);
+ failed = "gdImageGifPtr() failed";
+ break;
+
+ case NGX_HTTP_IMAGE_PNG:
+ out = gdImagePngPtr(img, size);
+ failed = "gdImagePngPtr() failed";
+ break;
+
+ case NGX_HTTP_IMAGE_WEBP:
+#if (NGX_HAVE_GD_WEBP)
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
+
+ q = ngx_http_image_filter_get_value(r, conf->wqcv, conf->webp_quality);
+ if (q <= 0) {
+ return NULL;
+ }
+
+ out = gdImageWebpPtrEx(img, size, q);
+ failed = "gdImageWebpPtrEx() failed";
+#else
+ failed = "nginx was built without GD WebP support";
+#endif
+ break;
+
+ default:
+ failed = "unknown image type";
+ break;
+ }
+
+ if (out == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed);
+ }
+
+ return out;
+}
+
+
+static void
+ngx_http_image_cleanup(void *data)
+{
+ gdFree(data);
+}
+
+
+static ngx_uint_t
+ngx_http_image_filter_get_value(ngx_http_request_t *r,
+ ngx_http_complex_value_t *cv, ngx_uint_t v)
+{
+ ngx_str_t val;
+
+ if (cv == NULL) {
+ return v;
+ }
+
+ if (ngx_http_complex_value(r, cv, &val) != NGX_OK) {
+ return 0;
+ }
+
+ return ngx_http_image_filter_value(&val);
+}
+
+
+static ngx_uint_t
+ngx_http_image_filter_value(ngx_str_t *value)
+{
+ ngx_int_t n;
+
+ if (value->len == 1 && value->data[0] == '-') {
+ return (ngx_uint_t) -1;
+ }
+
+ n = ngx_atoi(value->data, value->len);
+
+ if (n > 0) {
+ return (ngx_uint_t) n;
+ }
+
+ return 0;
+}
+
+
+static void *
+ngx_http_image_filter_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_image_filter_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_image_filter_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->width = 0;
+ * conf->height = 0;
+ * conf->angle = 0;
+ * conf->wcv = NULL;
+ * conf->hcv = NULL;
+ * conf->acv = NULL;
+ * conf->jqcv = NULL;
+ * conf->wqcv = NULL;
+ * conf->shcv = NULL;
+ */
+
+ conf->filter = NGX_CONF_UNSET_UINT;
+ conf->jpeg_quality = NGX_CONF_UNSET_UINT;
+ conf->webp_quality = NGX_CONF_UNSET_UINT;
+ conf->sharpen = NGX_CONF_UNSET_UINT;
+ conf->transparency = NGX_CONF_UNSET;
+ conf->interlace = NGX_CONF_UNSET;
+ conf->buffer_size = NGX_CONF_UNSET_SIZE;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_image_filter_conf_t *prev = parent;
+ ngx_http_image_filter_conf_t *conf = child;
+
+ if (conf->filter == NGX_CONF_UNSET_UINT) {
+
+ if (prev->filter == NGX_CONF_UNSET_UINT) {
+ conf->filter = NGX_HTTP_IMAGE_OFF;
+
+ } else {
+ conf->filter = prev->filter;
+ conf->width = prev->width;
+ conf->height = prev->height;
+ conf->angle = prev->angle;
+ conf->wcv = prev->wcv;
+ conf->hcv = prev->hcv;
+ conf->acv = prev->acv;
+ }
+ }
+
+ if (conf->jpeg_quality == NGX_CONF_UNSET_UINT) {
+
+ /* 75 is libjpeg default quality */
+ ngx_conf_merge_uint_value(conf->jpeg_quality, prev->jpeg_quality, 75);
+
+ if (conf->jqcv == NULL) {
+ conf->jqcv = prev->jqcv;
+ }
+ }
+
+ if (conf->webp_quality == NGX_CONF_UNSET_UINT) {
+
+ /* 80 is libwebp default quality */
+ ngx_conf_merge_uint_value(conf->webp_quality, prev->webp_quality, 80);
+
+ if (conf->wqcv == NULL) {
+ conf->wqcv = prev->wqcv;
+ }
+ }
+
+ if (conf->sharpen == NGX_CONF_UNSET_UINT) {
+ ngx_conf_merge_uint_value(conf->sharpen, prev->sharpen, 0);
+
+ if (conf->shcv == NULL) {
+ conf->shcv = prev->shcv;
+ }
+ }
+
+ ngx_conf_merge_value(conf->transparency, prev->transparency, 1);
+
+ ngx_conf_merge_value(conf->interlace, prev->interlace, 0);
+
+ ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size,
+ 1 * 1024 * 1024);
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_image_filter_conf_t *imcf = conf;
+
+ ngx_str_t *value;
+ ngx_int_t n;
+ ngx_uint_t i;
+ ngx_http_complex_value_t cv;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ i = 1;
+
+ if (cf->args->nelts == 2) {
+ if (ngx_strcmp(value[i].data, "off") == 0) {
+ imcf->filter = NGX_HTTP_IMAGE_OFF;
+
+ } else if (ngx_strcmp(value[i].data, "test") == 0) {
+ imcf->filter = NGX_HTTP_IMAGE_TEST;
+
+ } else if (ngx_strcmp(value[i].data, "size") == 0) {
+ imcf->filter = NGX_HTTP_IMAGE_SIZE;
+
+ } else {
+ goto failed;
+ }
+
+ return NGX_CONF_OK;
+
+ } else if (cf->args->nelts == 3) {
+
+ if (ngx_strcmp(value[i].data, "rotate") == 0) {
+ if (imcf->filter != NGX_HTTP_IMAGE_RESIZE
+ && imcf->filter != NGX_HTTP_IMAGE_CROP)
+ {
+ imcf->filter = NGX_HTTP_IMAGE_ROTATE;
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[++i];
+ ccv.complex_value = &cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cv.lengths == NULL) {
+ n = ngx_http_image_filter_value(&value[i]);
+
+ if (n != 90 && n != 180 && n != 270) {
+ goto failed;
+ }
+
+ imcf->angle = (ngx_uint_t) n;
+
+ } else {
+ imcf->acv = ngx_palloc(cf->pool,
+ sizeof(ngx_http_complex_value_t));
+ if (imcf->acv == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *imcf->acv = cv;
+ }
+
+ return NGX_CONF_OK;
+
+ } else {
+ goto failed;
+ }
+ }
+
+ if (ngx_strcmp(value[i].data, "resize") == 0) {
+ imcf->filter = NGX_HTTP_IMAGE_RESIZE;
+
+ } else if (ngx_strcmp(value[i].data, "crop") == 0) {
+ imcf->filter = NGX_HTTP_IMAGE_CROP;
+
+ } else {
+ goto failed;
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[++i];
+ ccv.complex_value = &cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cv.lengths == NULL) {
+ n = ngx_http_image_filter_value(&value[i]);
+
+ if (n == 0) {
+ goto failed;
+ }
+
+ imcf->width = (ngx_uint_t) n;
+
+ } else {
+ imcf->wcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
+ if (imcf->wcv == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *imcf->wcv = cv;
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[++i];
+ ccv.complex_value = &cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cv.lengths == NULL) {
+ n = ngx_http_image_filter_value(&value[i]);
+
+ if (n == 0) {
+ goto failed;
+ }
+
+ imcf->height = (ngx_uint_t) n;
+
+ } else {
+ imcf->hcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
+ if (imcf->hcv == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *imcf->hcv = cv;
+ }
+
+ return NGX_CONF_OK;
+
+failed:
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"",
+ &value[i]);
+
+ return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf)
+{
+ ngx_http_image_filter_conf_t *imcf = conf;
+
+ ngx_str_t *value;
+ ngx_int_t n;
+ ngx_http_complex_value_t cv;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cv.lengths == NULL) {
+ n = ngx_http_image_filter_value(&value[1]);
+
+ if (n <= 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid value \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ imcf->jpeg_quality = (ngx_uint_t) n;
+
+ } else {
+ imcf->jqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
+ if (imcf->jqcv == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *imcf->jqcv = cv;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_image_filter_webp_quality(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf)
+{
+ ngx_http_image_filter_conf_t *imcf = conf;
+
+ ngx_str_t *value;
+ ngx_int_t n;
+ ngx_http_complex_value_t cv;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cv.lengths == NULL) {
+ n = ngx_http_image_filter_value(&value[1]);
+
+ if (n <= 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid value \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ imcf->webp_quality = (ngx_uint_t) n;
+
+ } else {
+ imcf->wqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
+ if (imcf->wqcv == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *imcf->wqcv = cv;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf)
+{
+ ngx_http_image_filter_conf_t *imcf = conf;
+
+ ngx_str_t *value;
+ ngx_int_t n;
+ ngx_http_complex_value_t cv;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cv.lengths == NULL) {
+ n = ngx_http_image_filter_value(&value[1]);
+
+ if (n < 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid value \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ imcf->sharpen = (ngx_uint_t) n;
+
+ } else {
+ imcf->shcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
+ if (imcf->shcv == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *imcf->shcv = cv;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_image_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_image_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_image_body_filter;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_index_module.c b/app/nginx/src/http/modules/ngx_http_index_module.c
new file mode 100644
index 0000000..c144b31
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_index_module.c
@@ -0,0 +1,540 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_array_t *lengths;
+ ngx_array_t *values;
+} ngx_http_index_t;
+
+
+typedef struct {
+ ngx_array_t *indices; /* array of ngx_http_index_t */
+ size_t max_index_len;
+} ngx_http_index_loc_conf_t;
+
+
+#define NGX_HTTP_DEFAULT_INDEX "index.html"
+
+
+static ngx_int_t ngx_http_index_test_dir(ngx_http_request_t *r,
+ ngx_http_core_loc_conf_t *clcf, u_char *path, u_char *last);
+static ngx_int_t ngx_http_index_error(ngx_http_request_t *r,
+ ngx_http_core_loc_conf_t *clcf, u_char *file, ngx_err_t err);
+
+static ngx_int_t ngx_http_index_init(ngx_conf_t *cf);
+static void *ngx_http_index_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_index_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static char *ngx_http_index_set_index(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_command_t ngx_http_index_commands[] = {
+
+ { ngx_string("index"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_index_set_index,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_index_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_index_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_index_create_loc_conf, /* create location configuration */
+ ngx_http_index_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_index_module = {
+ NGX_MODULE_V1,
+ &ngx_http_index_module_ctx, /* module context */
+ ngx_http_index_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+/*
+ * Try to open/test the first index file before the test of directory
+ * existence because valid requests should prevail over invalid ones.
+ * If open()/stat() of a file will fail then stat() of a directory
+ * should be faster because kernel may have already cached some data.
+ * Besides, Win32 may return ERROR_PATH_NOT_FOUND (NGX_ENOTDIR) at once.
+ * Unix has ENOTDIR error; however, it's less helpful than Win32's one:
+ * it only indicates that path points to a regular file, not a directory.
+ */
+
+static ngx_int_t
+ngx_http_index_handler(ngx_http_request_t *r)
+{
+ u_char *p, *name;
+ size_t len, root, reserve, allocated;
+ ngx_int_t rc;
+ ngx_str_t path, uri;
+ ngx_uint_t i, dir_tested;
+ ngx_http_index_t *index;
+ ngx_open_file_info_t of;
+ ngx_http_script_code_pt code;
+ ngx_http_script_engine_t e;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_index_loc_conf_t *ilcf;
+ ngx_http_script_len_code_pt lcode;
+
+ if (r->uri.data[r->uri.len - 1] != '/') {
+ return NGX_DECLINED;
+ }
+
+ if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))) {
+ return NGX_DECLINED;
+ }
+
+ ilcf = ngx_http_get_module_loc_conf(r, ngx_http_index_module);
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ allocated = 0;
+ root = 0;
+ dir_tested = 0;
+ name = NULL;
+ /* suppress MSVC warning */
+ path.data = NULL;
+
+ index = ilcf->indices->elts;
+ for (i = 0; i < ilcf->indices->nelts; i++) {
+
+ if (index[i].lengths == NULL) {
+
+ if (index[i].name.data[0] == '/') {
+ return ngx_http_internal_redirect(r, &index[i].name, &r->args);
+ }
+
+ reserve = ilcf->max_index_len;
+ len = index[i].name.len;
+
+ } else {
+ ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+ e.ip = index[i].lengths->elts;
+ e.request = r;
+ e.flushed = 1;
+
+ /* 1 is for terminating '\0' as in static names */
+ len = 1;
+
+ while (*(uintptr_t *) e.ip) {
+ lcode = *(ngx_http_script_len_code_pt *) e.ip;
+ len += lcode(&e);
+ }
+
+ /* 16 bytes are preallocation */
+
+ reserve = len + 16;
+ }
+
+ if (reserve > allocated) {
+
+ name = ngx_http_map_uri_to_path(r, &path, &root, reserve);
+ if (name == NULL) {
+ return NGX_ERROR;
+ }
+
+ allocated = path.data + path.len - name;
+ }
+
+ if (index[i].values == NULL) {
+
+ /* index[i].name.len includes the terminating '\0' */
+
+ ngx_memcpy(name, index[i].name.data, index[i].name.len);
+
+ path.len = (name + index[i].name.len - 1) - path.data;
+
+ } else {
+ e.ip = index[i].values->elts;
+ e.pos = name;
+
+ while (*(uintptr_t *) e.ip) {
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code((ngx_http_script_engine_t *) &e);
+ }
+
+ if (*name == '/') {
+ uri.len = len - 1;
+ uri.data = name;
+ return ngx_http_internal_redirect(r, &uri, &r->args);
+ }
+
+ path.len = e.pos - path.data;
+
+ *e.pos = '\0';
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "open index \"%V\"", &path);
+
+ ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+ of.read_ahead = clcf->read_ahead;
+ of.directio = clcf->directio;
+ of.valid = clcf->open_file_cache_valid;
+ of.min_uses = clcf->open_file_cache_min_uses;
+ of.test_only = 1;
+ of.errors = clcf->open_file_cache_errors;
+ of.events = clcf->open_file_cache_events;
+
+ if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+ != NGX_OK)
+ {
+ if (of.err == 0) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, of.err,
+ "%s \"%s\" failed", of.failed, path.data);
+
+#if (NGX_HAVE_OPENAT)
+ if (of.err == NGX_EMLINK
+ || of.err == NGX_ELOOP)
+ {
+ return NGX_HTTP_FORBIDDEN;
+ }
+#endif
+
+ if (of.err == NGX_ENOTDIR
+ || of.err == NGX_ENAMETOOLONG
+ || of.err == NGX_EACCES)
+ {
+ return ngx_http_index_error(r, clcf, path.data, of.err);
+ }
+
+ if (!dir_tested) {
+ rc = ngx_http_index_test_dir(r, clcf, path.data, name - 1);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ dir_tested = 1;
+ }
+
+ if (of.err == NGX_ENOENT) {
+ continue;
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err,
+ "%s \"%s\" failed", of.failed, path.data);
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ uri.len = r->uri.len + len - 1;
+
+ if (!clcf->alias) {
+ uri.data = path.data + root;
+
+ } else {
+ uri.data = ngx_pnalloc(r->pool, uri.len);
+ if (uri.data == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ p = ngx_copy(uri.data, r->uri.data, r->uri.len);
+ ngx_memcpy(p, name, len - 1);
+ }
+
+ return ngx_http_internal_redirect(r, &uri, &r->args);
+ }
+
+ return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_index_test_dir(ngx_http_request_t *r, ngx_http_core_loc_conf_t *clcf,
+ u_char *path, u_char *last)
+{
+ u_char c;
+ ngx_str_t dir;
+ ngx_open_file_info_t of;
+
+ c = *last;
+ if (c != '/' || path == last) {
+ /* "alias" without trailing slash */
+ c = *(++last);
+ }
+ *last = '\0';
+
+ dir.len = last - path;
+ dir.data = path;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http index check dir: \"%V\"", &dir);
+
+ ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+ of.test_dir = 1;
+ of.test_only = 1;
+ of.valid = clcf->open_file_cache_valid;
+ of.errors = clcf->open_file_cache_errors;
+
+ if (ngx_http_set_disable_symlinks(r, clcf, &dir, &of) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_open_cached_file(clcf->open_file_cache, &dir, &of, r->pool)
+ != NGX_OK)
+ {
+ if (of.err) {
+
+#if (NGX_HAVE_OPENAT)
+ if (of.err == NGX_EMLINK
+ || of.err == NGX_ELOOP)
+ {
+ return NGX_HTTP_FORBIDDEN;
+ }
+#endif
+
+ if (of.err == NGX_ENOENT) {
+ *last = c;
+ return ngx_http_index_error(r, clcf, dir.data, NGX_ENOENT);
+ }
+
+ if (of.err == NGX_EACCES) {
+
+ *last = c;
+
+ /*
+ * ngx_http_index_test_dir() is called after the first index
+ * file testing has returned an error distinct from NGX_EACCES.
+ * This means that directory searching is allowed.
+ */
+
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err,
+ "%s \"%s\" failed", of.failed, dir.data);
+ }
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ *last = c;
+
+ if (of.is_dir) {
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "\"%s\" is not a directory", dir.data);
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_index_error(ngx_http_request_t *r, ngx_http_core_loc_conf_t *clcf,
+ u_char *file, ngx_err_t err)
+{
+ if (err == NGX_EACCES) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, err,
+ "\"%s\" is forbidden", file);
+
+ return NGX_HTTP_FORBIDDEN;
+ }
+
+ if (clcf->log_not_found) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, err,
+ "\"%s\" is not found", file);
+ }
+
+ return NGX_HTTP_NOT_FOUND;
+}
+
+
+static void *
+ngx_http_index_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_index_loc_conf_t *conf;
+
+ conf = ngx_palloc(cf->pool, sizeof(ngx_http_index_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->indices = NULL;
+ conf->max_index_len = 0;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_index_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_index_loc_conf_t *prev = parent;
+ ngx_http_index_loc_conf_t *conf = child;
+
+ ngx_http_index_t *index;
+
+ if (conf->indices == NULL) {
+ conf->indices = prev->indices;
+ conf->max_index_len = prev->max_index_len;
+ }
+
+ if (conf->indices == NULL) {
+ conf->indices = ngx_array_create(cf->pool, 1, sizeof(ngx_http_index_t));
+ if (conf->indices == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ index = ngx_array_push(conf->indices);
+ if (index == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ index->name.len = sizeof(NGX_HTTP_DEFAULT_INDEX);
+ index->name.data = (u_char *) NGX_HTTP_DEFAULT_INDEX;
+ index->lengths = NULL;
+ index->values = NULL;
+
+ conf->max_index_len = sizeof(NGX_HTTP_DEFAULT_INDEX);
+
+ return NGX_CONF_OK;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_index_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_index_handler;
+
+ return NGX_OK;
+}
+
+
+/* TODO: warn about duplicate indices */
+
+static char *
+ngx_http_index_set_index(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_index_loc_conf_t *ilcf = conf;
+
+ ngx_str_t *value;
+ ngx_uint_t i, n;
+ ngx_http_index_t *index;
+ ngx_http_script_compile_t sc;
+
+ if (ilcf->indices == NULL) {
+ ilcf->indices = ngx_array_create(cf->pool, 2, sizeof(ngx_http_index_t));
+ if (ilcf->indices == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+
+ if (value[i].data[0] == '/' && i != cf->args->nelts - 1) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "only the last index in \"index\" directive "
+ "should be absolute");
+ }
+
+ if (value[i].len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "index \"%V\" in \"index\" directive is invalid",
+ &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ index = ngx_array_push(ilcf->indices);
+ if (index == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ index->name.len = value[i].len;
+ index->name.data = value[i].data;
+ index->lengths = NULL;
+ index->values = NULL;
+
+ n = ngx_http_script_variables_count(&value[i]);
+
+ if (n == 0) {
+ if (ilcf->max_index_len < index->name.len) {
+ ilcf->max_index_len = index->name.len;
+ }
+
+ if (index->name.data[0] == '/') {
+ continue;
+ }
+
+ /* include the terminating '\0' to the length to use ngx_memcpy() */
+ index->name.len++;
+
+ continue;
+ }
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = &value[i];
+ sc.lengths = &index->lengths;
+ sc.values = &index->values;
+ sc.variables = n;
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_limit_conn_module.c b/app/nginx/src/http/modules/ngx_http_limit_conn_module.c
new file mode 100644
index 0000000..913d599
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_limit_conn_module.c
@@ -0,0 +1,670 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ u_char color;
+ u_char len;
+ u_short conn;
+ u_char data[1];
+} ngx_http_limit_conn_node_t;
+
+
+typedef struct {
+ ngx_shm_zone_t *shm_zone;
+ ngx_rbtree_node_t *node;
+} ngx_http_limit_conn_cleanup_t;
+
+
+typedef struct {
+ ngx_rbtree_t *rbtree;
+ ngx_http_complex_value_t key;
+} ngx_http_limit_conn_ctx_t;
+
+
+typedef struct {
+ ngx_shm_zone_t *shm_zone;
+ ngx_uint_t conn;
+} ngx_http_limit_conn_limit_t;
+
+
+typedef struct {
+ ngx_array_t limits;
+ ngx_uint_t log_level;
+ ngx_uint_t status_code;
+} ngx_http_limit_conn_conf_t;
+
+
+static ngx_rbtree_node_t *ngx_http_limit_conn_lookup(ngx_rbtree_t *rbtree,
+ ngx_str_t *key, uint32_t hash);
+static void ngx_http_limit_conn_cleanup(void *data);
+static ngx_inline void ngx_http_limit_conn_cleanup_all(ngx_pool_t *pool);
+
+static void *ngx_http_limit_conn_create_conf(ngx_conf_t *cf);
+static char *ngx_http_limit_conn_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_http_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static ngx_int_t ngx_http_limit_conn_init(ngx_conf_t *cf);
+
+
+static ngx_conf_enum_t ngx_http_limit_conn_log_levels[] = {
+ { ngx_string("info"), NGX_LOG_INFO },
+ { ngx_string("notice"), NGX_LOG_NOTICE },
+ { ngx_string("warn"), NGX_LOG_WARN },
+ { ngx_string("error"), NGX_LOG_ERR },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_num_bounds_t ngx_http_limit_conn_status_bounds = {
+ ngx_conf_check_num_bounds, 400, 599
+};
+
+
+static ngx_command_t ngx_http_limit_conn_commands[] = {
+
+ { ngx_string("limit_conn_zone"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE2,
+ ngx_http_limit_conn_zone,
+ 0,
+ 0,
+ NULL },
+
+ { ngx_string("limit_conn"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_http_limit_conn,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("limit_conn_log_level"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_limit_conn_conf_t, log_level),
+ &ngx_http_limit_conn_log_levels },
+
+ { ngx_string("limit_conn_status"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_limit_conn_conf_t, status_code),
+ &ngx_http_limit_conn_status_bounds },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_limit_conn_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_limit_conn_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_limit_conn_create_conf, /* create location configuration */
+ ngx_http_limit_conn_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_limit_conn_module = {
+ NGX_MODULE_V1,
+ &ngx_http_limit_conn_module_ctx, /* module context */
+ ngx_http_limit_conn_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_limit_conn_handler(ngx_http_request_t *r)
+{
+ size_t n;
+ uint32_t hash;
+ ngx_str_t key;
+ ngx_uint_t i;
+ ngx_slab_pool_t *shpool;
+ ngx_rbtree_node_t *node;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_limit_conn_ctx_t *ctx;
+ ngx_http_limit_conn_node_t *lc;
+ ngx_http_limit_conn_conf_t *lccf;
+ ngx_http_limit_conn_limit_t *limits;
+ ngx_http_limit_conn_cleanup_t *lccln;
+
+ if (r->main->limit_conn_set) {
+ return NGX_DECLINED;
+ }
+
+ lccf = ngx_http_get_module_loc_conf(r, ngx_http_limit_conn_module);
+ limits = lccf->limits.elts;
+
+ for (i = 0; i < lccf->limits.nelts; i++) {
+ ctx = limits[i].shm_zone->data;
+
+ if (ngx_http_complex_value(r, &ctx->key, &key) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (key.len == 0) {
+ continue;
+ }
+
+ if (key.len > 255) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "the value of the \"%V\" key "
+ "is more than 255 bytes: \"%V\"",
+ &ctx->key.value, &key);
+ continue;
+ }
+
+ r->main->limit_conn_set = 1;
+
+ hash = ngx_crc32_short(key.data, key.len);
+
+ shpool = (ngx_slab_pool_t *) limits[i].shm_zone->shm.addr;
+
+ ngx_shmtx_lock(&shpool->mutex);
+
+ node = ngx_http_limit_conn_lookup(ctx->rbtree, &key, hash);
+
+ if (node == NULL) {
+
+ n = offsetof(ngx_rbtree_node_t, color)
+ + offsetof(ngx_http_limit_conn_node_t, data)
+ + key.len;
+
+ node = ngx_slab_alloc_locked(shpool, n);
+
+ if (node == NULL) {
+ ngx_shmtx_unlock(&shpool->mutex);
+ ngx_http_limit_conn_cleanup_all(r->pool);
+ return lccf->status_code;
+ }
+
+ lc = (ngx_http_limit_conn_node_t *) &node->color;
+
+ node->key = hash;
+ lc->len = (u_char) key.len;
+ lc->conn = 1;
+ ngx_memcpy(lc->data, key.data, key.len);
+
+ ngx_rbtree_insert(ctx->rbtree, node);
+
+ } else {
+
+ lc = (ngx_http_limit_conn_node_t *) &node->color;
+
+ if ((ngx_uint_t) lc->conn >= limits[i].conn) {
+
+ ngx_shmtx_unlock(&shpool->mutex);
+
+ ngx_log_error(lccf->log_level, r->connection->log, 0,
+ "limiting connections by zone \"%V\"",
+ &limits[i].shm_zone->shm.name);
+
+ ngx_http_limit_conn_cleanup_all(r->pool);
+ return lccf->status_code;
+ }
+
+ lc->conn++;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "limit conn: %08Xi %d", node->key, lc->conn);
+
+ ngx_shmtx_unlock(&shpool->mutex);
+
+ cln = ngx_pool_cleanup_add(r->pool,
+ sizeof(ngx_http_limit_conn_cleanup_t));
+ if (cln == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ cln->handler = ngx_http_limit_conn_cleanup;
+ lccln = cln->data;
+
+ lccln->shm_zone = limits[i].shm_zone;
+ lccln->node = node;
+ }
+
+ return NGX_DECLINED;
+}
+
+
+static void
+ngx_http_limit_conn_rbtree_insert_value(ngx_rbtree_node_t *temp,
+ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+ ngx_rbtree_node_t **p;
+ ngx_http_limit_conn_node_t *lcn, *lcnt;
+
+ for ( ;; ) {
+
+ if (node->key < temp->key) {
+
+ p = &temp->left;
+
+ } else if (node->key > temp->key) {
+
+ p = &temp->right;
+
+ } else { /* node->key == temp->key */
+
+ lcn = (ngx_http_limit_conn_node_t *) &node->color;
+ lcnt = (ngx_http_limit_conn_node_t *) &temp->color;
+
+ p = (ngx_memn2cmp(lcn->data, lcnt->data, lcn->len, lcnt->len) < 0)
+ ? &temp->left : &temp->right;
+ }
+
+ if (*p == sentinel) {
+ break;
+ }
+
+ temp = *p;
+ }
+
+ *p = node;
+ node->parent = temp;
+ node->left = sentinel;
+ node->right = sentinel;
+ ngx_rbt_red(node);
+}
+
+
+static ngx_rbtree_node_t *
+ngx_http_limit_conn_lookup(ngx_rbtree_t *rbtree, ngx_str_t *key, uint32_t hash)
+{
+ ngx_int_t rc;
+ ngx_rbtree_node_t *node, *sentinel;
+ ngx_http_limit_conn_node_t *lcn;
+
+ node = rbtree->root;
+ sentinel = rbtree->sentinel;
+
+ while (node != sentinel) {
+
+ if (hash < node->key) {
+ node = node->left;
+ continue;
+ }
+
+ if (hash > node->key) {
+ node = node->right;
+ continue;
+ }
+
+ /* hash == node->key */
+
+ lcn = (ngx_http_limit_conn_node_t *) &node->color;
+
+ rc = ngx_memn2cmp(key->data, lcn->data, key->len, (size_t) lcn->len);
+
+ if (rc == 0) {
+ return node;
+ }
+
+ node = (rc < 0) ? node->left : node->right;
+ }
+
+ return NULL;
+}
+
+
+static void
+ngx_http_limit_conn_cleanup(void *data)
+{
+ ngx_http_limit_conn_cleanup_t *lccln = data;
+
+ ngx_slab_pool_t *shpool;
+ ngx_rbtree_node_t *node;
+ ngx_http_limit_conn_ctx_t *ctx;
+ ngx_http_limit_conn_node_t *lc;
+
+ ctx = lccln->shm_zone->data;
+ shpool = (ngx_slab_pool_t *) lccln->shm_zone->shm.addr;
+ node = lccln->node;
+ lc = (ngx_http_limit_conn_node_t *) &node->color;
+
+ ngx_shmtx_lock(&shpool->mutex);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, lccln->shm_zone->shm.log, 0,
+ "limit conn cleanup: %08Xi %d", node->key, lc->conn);
+
+ lc->conn--;
+
+ if (lc->conn == 0) {
+ ngx_rbtree_delete(ctx->rbtree, node);
+ ngx_slab_free_locked(shpool, node);
+ }
+
+ ngx_shmtx_unlock(&shpool->mutex);
+}
+
+
+static ngx_inline void
+ngx_http_limit_conn_cleanup_all(ngx_pool_t *pool)
+{
+ ngx_pool_cleanup_t *cln;
+
+ cln = pool->cleanup;
+
+ while (cln && cln->handler == ngx_http_limit_conn_cleanup) {
+ ngx_http_limit_conn_cleanup(cln->data);
+ cln = cln->next;
+ }
+
+ pool->cleanup = cln;
+}
+
+
+static ngx_int_t
+ngx_http_limit_conn_init_zone(ngx_shm_zone_t *shm_zone, void *data)
+{
+ ngx_http_limit_conn_ctx_t *octx = data;
+
+ size_t len;
+ ngx_slab_pool_t *shpool;
+ ngx_rbtree_node_t *sentinel;
+ ngx_http_limit_conn_ctx_t *ctx;
+
+ ctx = shm_zone->data;
+
+ if (octx) {
+ if (ctx->key.value.len != octx->key.value.len
+ || ngx_strncmp(ctx->key.value.data, octx->key.value.data,
+ ctx->key.value.len)
+ != 0)
+ {
+ ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
+ "limit_conn_zone \"%V\" uses the \"%V\" key "
+ "while previously it used the \"%V\" key",
+ &shm_zone->shm.name, &ctx->key.value,
+ &octx->key.value);
+ return NGX_ERROR;
+ }
+
+ ctx->rbtree = octx->rbtree;
+
+ return NGX_OK;
+ }
+
+ shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+ if (shm_zone->shm.exists) {
+ ctx->rbtree = shpool->data;
+
+ return NGX_OK;
+ }
+
+ ctx->rbtree = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_t));
+ if (ctx->rbtree == NULL) {
+ return NGX_ERROR;
+ }
+
+ shpool->data = ctx->rbtree;
+
+ sentinel = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_node_t));
+ if (sentinel == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_rbtree_init(ctx->rbtree, sentinel,
+ ngx_http_limit_conn_rbtree_insert_value);
+
+ len = sizeof(" in limit_conn_zone \"\"") + shm_zone->shm.name.len;
+
+ shpool->log_ctx = ngx_slab_alloc(shpool, len);
+ if (shpool->log_ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_sprintf(shpool->log_ctx, " in limit_conn_zone \"%V\"%Z",
+ &shm_zone->shm.name);
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_limit_conn_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_limit_conn_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->limits.elts = NULL;
+ */
+
+ conf->log_level = NGX_CONF_UNSET_UINT;
+ conf->status_code = NGX_CONF_UNSET_UINT;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_limit_conn_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_limit_conn_conf_t *prev = parent;
+ ngx_http_limit_conn_conf_t *conf = child;
+
+ if (conf->limits.elts == NULL) {
+ conf->limits = prev->limits;
+ }
+
+ ngx_conf_merge_uint_value(conf->log_level, prev->log_level, NGX_LOG_ERR);
+ ngx_conf_merge_uint_value(conf->status_code, prev->status_code,
+ NGX_HTTP_SERVICE_UNAVAILABLE);
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ u_char *p;
+ ssize_t size;
+ ngx_str_t *value, name, s;
+ ngx_uint_t i;
+ ngx_shm_zone_t *shm_zone;
+ ngx_http_limit_conn_ctx_t *ctx;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_ctx_t));
+ if (ctx == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &ctx->key;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ size = 0;
+ name.len = 0;
+
+ for (i = 2; i < cf->args->nelts; i++) {
+
+ if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
+
+ name.data = value[i].data + 5;
+
+ p = (u_char *) ngx_strchr(name.data, ':');
+
+ if (p == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid zone size \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ name.len = p - name.data;
+
+ s.data = p + 1;
+ s.len = value[i].data + value[i].len - s.data;
+
+ size = ngx_parse_size(&s);
+
+ if (size == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid zone size \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (size < (ssize_t) (8 * ngx_pagesize)) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "zone \"%V\" is too small", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (name.len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"%V\" must have \"zone\" parameter",
+ &cmd->name);
+ return NGX_CONF_ERROR;
+ }
+
+ shm_zone = ngx_shared_memory_add(cf, &name, size,
+ &ngx_http_limit_conn_module);
+ if (shm_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (shm_zone->data) {
+ ctx = shm_zone->data;
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "%V \"%V\" is already bound to key \"%V\"",
+ &cmd->name, &name, &ctx->key.value);
+ return NGX_CONF_ERROR;
+ }
+
+ shm_zone->init = ngx_http_limit_conn_init_zone;
+ shm_zone->data = ctx;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_shm_zone_t *shm_zone;
+ ngx_http_limit_conn_conf_t *lccf = conf;
+ ngx_http_limit_conn_limit_t *limit, *limits;
+
+ ngx_str_t *value;
+ ngx_int_t n;
+ ngx_uint_t i;
+
+ value = cf->args->elts;
+
+ shm_zone = ngx_shared_memory_add(cf, &value[1], 0,
+ &ngx_http_limit_conn_module);
+ if (shm_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ limits = lccf->limits.elts;
+
+ if (limits == NULL) {
+ if (ngx_array_init(&lccf->limits, cf->pool, 1,
+ sizeof(ngx_http_limit_conn_limit_t))
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ for (i = 0; i < lccf->limits.nelts; i++) {
+ if (shm_zone == limits[i].shm_zone) {
+ return "is duplicate";
+ }
+ }
+
+ n = ngx_atoi(value[2].data, value[2].len);
+ if (n <= 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid number of connections \"%V\"", &value[2]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (n > 65535) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "connection limit must be less 65536");
+ return NGX_CONF_ERROR;
+ }
+
+ limit = ngx_array_push(&lccf->limits);
+ if (limit == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ limit->conn = n;
+ limit->shm_zone = shm_zone;
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_limit_conn_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_limit_conn_handler;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_limit_req_module.c b/app/nginx/src/http/modules/ngx_http_limit_req_module.c
new file mode 100644
index 0000000..579b13c
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_limit_req_module.c
@@ -0,0 +1,960 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ u_char color;
+ u_char dummy;
+ u_short len;
+ ngx_queue_t queue;
+ ngx_msec_t last;
+ /* integer value, 1 corresponds to 0.001 r/s */
+ ngx_uint_t excess;
+ ngx_uint_t count;
+ u_char data[1];
+} ngx_http_limit_req_node_t;
+
+
+typedef struct {
+ ngx_rbtree_t rbtree;
+ ngx_rbtree_node_t sentinel;
+ ngx_queue_t queue;
+} ngx_http_limit_req_shctx_t;
+
+
+typedef struct {
+ ngx_http_limit_req_shctx_t *sh;
+ ngx_slab_pool_t *shpool;
+ /* integer value, 1 corresponds to 0.001 r/s */
+ ngx_uint_t rate;
+ ngx_http_complex_value_t key;
+ ngx_http_limit_req_node_t *node;
+} ngx_http_limit_req_ctx_t;
+
+
+typedef struct {
+ ngx_shm_zone_t *shm_zone;
+ /* integer value, 1 corresponds to 0.001 r/s */
+ ngx_uint_t burst;
+ ngx_uint_t nodelay; /* unsigned nodelay:1 */
+} ngx_http_limit_req_limit_t;
+
+
+typedef struct {
+ ngx_array_t limits;
+ ngx_uint_t limit_log_level;
+ ngx_uint_t delay_log_level;
+ ngx_uint_t status_code;
+} ngx_http_limit_req_conf_t;
+
+
+static void ngx_http_limit_req_delay(ngx_http_request_t *r);
+static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit,
+ ngx_uint_t hash, ngx_str_t *key, ngx_uint_t *ep, ngx_uint_t account);
+static ngx_msec_t ngx_http_limit_req_account(ngx_http_limit_req_limit_t *limits,
+ ngx_uint_t n, ngx_uint_t *ep, ngx_http_limit_req_limit_t **limit);
+static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx,
+ ngx_uint_t n);
+
+static void *ngx_http_limit_req_create_conf(ngx_conf_t *cf);
+static char *ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static ngx_int_t ngx_http_limit_req_init(ngx_conf_t *cf);
+
+
+static ngx_conf_enum_t ngx_http_limit_req_log_levels[] = {
+ { ngx_string("info"), NGX_LOG_INFO },
+ { ngx_string("notice"), NGX_LOG_NOTICE },
+ { ngx_string("warn"), NGX_LOG_WARN },
+ { ngx_string("error"), NGX_LOG_ERR },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_num_bounds_t ngx_http_limit_req_status_bounds = {
+ ngx_conf_check_num_bounds, 400, 599
+};
+
+
+static ngx_command_t ngx_http_limit_req_commands[] = {
+
+ { ngx_string("limit_req_zone"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE3,
+ ngx_http_limit_req_zone,
+ 0,
+ 0,
+ NULL },
+
+ { ngx_string("limit_req"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
+ ngx_http_limit_req,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("limit_req_log_level"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_limit_req_conf_t, limit_log_level),
+ &ngx_http_limit_req_log_levels },
+
+ { ngx_string("limit_req_status"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_limit_req_conf_t, status_code),
+ &ngx_http_limit_req_status_bounds },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_limit_req_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_limit_req_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_limit_req_create_conf, /* create location configuration */
+ ngx_http_limit_req_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_limit_req_module = {
+ NGX_MODULE_V1,
+ &ngx_http_limit_req_module_ctx, /* module context */
+ ngx_http_limit_req_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_limit_req_handler(ngx_http_request_t *r)
+{
+ uint32_t hash;
+ ngx_str_t key;
+ ngx_int_t rc;
+ ngx_uint_t n, excess;
+ ngx_msec_t delay;
+ ngx_http_limit_req_ctx_t *ctx;
+ ngx_http_limit_req_conf_t *lrcf;
+ ngx_http_limit_req_limit_t *limit, *limits;
+
+ if (r->main->limit_req_set) {
+ return NGX_DECLINED;
+ }
+
+ lrcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module);
+ limits = lrcf->limits.elts;
+
+ excess = 0;
+
+ rc = NGX_DECLINED;
+
+#if (NGX_SUPPRESS_WARN)
+ limit = NULL;
+#endif
+
+ for (n = 0; n < lrcf->limits.nelts; n++) {
+
+ limit = &limits[n];
+
+ ctx = limit->shm_zone->data;
+
+ if (ngx_http_complex_value(r, &ctx->key, &key) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (key.len == 0) {
+ continue;
+ }
+
+ if (key.len > 65535) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "the value of the \"%V\" key "
+ "is more than 65535 bytes: \"%V\"",
+ &ctx->key.value, &key);
+ continue;
+ }
+
+ hash = ngx_crc32_short(key.data, key.len);
+
+ ngx_shmtx_lock(&ctx->shpool->mutex);
+
+ rc = ngx_http_limit_req_lookup(limit, hash, &key, &excess,
+ (n == lrcf->limits.nelts - 1));
+
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "limit_req[%ui]: %i %ui.%03ui",
+ n, rc, excess / 1000, excess % 1000);
+
+ if (rc != NGX_AGAIN) {
+ break;
+ }
+ }
+
+ if (rc == NGX_DECLINED) {
+ return NGX_DECLINED;
+ }
+
+ r->main->limit_req_set = 1;
+
+ if (rc == NGX_BUSY || rc == NGX_ERROR) {
+
+ if (rc == NGX_BUSY) {
+ ngx_log_error(lrcf->limit_log_level, r->connection->log, 0,
+ "limiting requests, excess: %ui.%03ui by zone \"%V\"",
+ excess / 1000, excess % 1000,
+ &limit->shm_zone->shm.name);
+ }
+
+ while (n--) {
+ ctx = limits[n].shm_zone->data;
+
+ if (ctx->node == NULL) {
+ continue;
+ }
+
+ ngx_shmtx_lock(&ctx->shpool->mutex);
+
+ ctx->node->count--;
+
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+
+ ctx->node = NULL;
+ }
+
+ return lrcf->status_code;
+ }
+
+ /* rc == NGX_AGAIN || rc == NGX_OK */
+
+ if (rc == NGX_AGAIN) {
+ excess = 0;
+ }
+
+ delay = ngx_http_limit_req_account(limits, n, &excess, &limit);
+
+ if (!delay) {
+ return NGX_DECLINED;
+ }
+
+ ngx_log_error(lrcf->delay_log_level, r->connection->log, 0,
+ "delaying request, excess: %ui.%03ui, by zone \"%V\"",
+ excess / 1000, excess % 1000, &limit->shm_zone->shm.name);
+
+ if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ r->read_event_handler = ngx_http_test_reading;
+ r->write_event_handler = ngx_http_limit_req_delay;
+
+ r->connection->write->delayed = 1;
+ ngx_add_timer(r->connection->write, delay);
+
+ return NGX_AGAIN;
+}
+
+
+static void
+ngx_http_limit_req_delay(ngx_http_request_t *r)
+{
+ ngx_event_t *wev;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "limit_req delay");
+
+ wev = r->connection->write;
+
+ if (wev->delayed) {
+
+ if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ }
+
+ return;
+ }
+
+ if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ r->read_event_handler = ngx_http_block_reading;
+ r->write_event_handler = ngx_http_core_run_phases;
+
+ ngx_http_core_run_phases(r);
+}
+
+
+static void
+ngx_http_limit_req_rbtree_insert_value(ngx_rbtree_node_t *temp,
+ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+ ngx_rbtree_node_t **p;
+ ngx_http_limit_req_node_t *lrn, *lrnt;
+
+ for ( ;; ) {
+
+ if (node->key < temp->key) {
+
+ p = &temp->left;
+
+ } else if (node->key > temp->key) {
+
+ p = &temp->right;
+
+ } else { /* node->key == temp->key */
+
+ lrn = (ngx_http_limit_req_node_t *) &node->color;
+ lrnt = (ngx_http_limit_req_node_t *) &temp->color;
+
+ p = (ngx_memn2cmp(lrn->data, lrnt->data, lrn->len, lrnt->len) < 0)
+ ? &temp->left : &temp->right;
+ }
+
+ if (*p == sentinel) {
+ break;
+ }
+
+ temp = *p;
+ }
+
+ *p = node;
+ node->parent = temp;
+ node->left = sentinel;
+ node->right = sentinel;
+ ngx_rbt_red(node);
+}
+
+
+static ngx_int_t
+ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit, ngx_uint_t hash,
+ ngx_str_t *key, ngx_uint_t *ep, ngx_uint_t account)
+{
+ size_t size;
+ ngx_int_t rc, excess;
+ ngx_msec_t now;
+ ngx_msec_int_t ms;
+ ngx_rbtree_node_t *node, *sentinel;
+ ngx_http_limit_req_ctx_t *ctx;
+ ngx_http_limit_req_node_t *lr;
+
+ now = ngx_current_msec;
+
+ ctx = limit->shm_zone->data;
+
+ node = ctx->sh->rbtree.root;
+ sentinel = ctx->sh->rbtree.sentinel;
+
+ while (node != sentinel) {
+
+ if (hash < node->key) {
+ node = node->left;
+ continue;
+ }
+
+ if (hash > node->key) {
+ node = node->right;
+ continue;
+ }
+
+ /* hash == node->key */
+
+ lr = (ngx_http_limit_req_node_t *) &node->color;
+
+ rc = ngx_memn2cmp(key->data, lr->data, key->len, (size_t) lr->len);
+
+ if (rc == 0) {
+ ngx_queue_remove(&lr->queue);
+ ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
+
+ ms = (ngx_msec_int_t) (now - lr->last);
+
+ excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;
+
+ if (excess < 0) {
+ excess = 0;
+ }
+
+ *ep = excess;
+
+ if ((ngx_uint_t) excess > limit->burst) {
+ return NGX_BUSY;
+ }
+
+ if (account) {
+ lr->excess = excess;
+ lr->last = now;
+ return NGX_OK;
+ }
+
+ lr->count++;
+
+ ctx->node = lr;
+
+ return NGX_AGAIN;
+ }
+
+ node = (rc < 0) ? node->left : node->right;
+ }
+
+ *ep = 0;
+
+ size = offsetof(ngx_rbtree_node_t, color)
+ + offsetof(ngx_http_limit_req_node_t, data)
+ + key->len;
+
+ ngx_http_limit_req_expire(ctx, 1);
+
+ node = ngx_slab_alloc_locked(ctx->shpool, size);
+
+ if (node == NULL) {
+ ngx_http_limit_req_expire(ctx, 0);
+
+ node = ngx_slab_alloc_locked(ctx->shpool, size);
+ if (node == NULL) {
+ ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+ "could not allocate node%s", ctx->shpool->log_ctx);
+ return NGX_ERROR;
+ }
+ }
+
+ node->key = hash;
+
+ lr = (ngx_http_limit_req_node_t *) &node->color;
+
+ lr->len = (u_short) key->len;
+ lr->excess = 0;
+
+ ngx_memcpy(lr->data, key->data, key->len);
+
+ ngx_rbtree_insert(&ctx->sh->rbtree, node);
+
+ ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
+
+ if (account) {
+ lr->last = now;
+ lr->count = 0;
+ return NGX_OK;
+ }
+
+ lr->last = 0;
+ lr->count = 1;
+
+ ctx->node = lr;
+
+ return NGX_AGAIN;
+}
+
+
+static ngx_msec_t
+ngx_http_limit_req_account(ngx_http_limit_req_limit_t *limits, ngx_uint_t n,
+ ngx_uint_t *ep, ngx_http_limit_req_limit_t **limit)
+{
+ ngx_int_t excess;
+ ngx_msec_t now, delay, max_delay;
+ ngx_msec_int_t ms;
+ ngx_http_limit_req_ctx_t *ctx;
+ ngx_http_limit_req_node_t *lr;
+
+ excess = *ep;
+
+ if (excess == 0 || (*limit)->nodelay) {
+ max_delay = 0;
+
+ } else {
+ ctx = (*limit)->shm_zone->data;
+ max_delay = excess * 1000 / ctx->rate;
+ }
+
+ while (n--) {
+ ctx = limits[n].shm_zone->data;
+ lr = ctx->node;
+
+ if (lr == NULL) {
+ continue;
+ }
+
+ ngx_shmtx_lock(&ctx->shpool->mutex);
+
+ now = ngx_current_msec;
+ ms = (ngx_msec_int_t) (now - lr->last);
+
+ excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;
+
+ if (excess < 0) {
+ excess = 0;
+ }
+
+ lr->last = now;
+ lr->excess = excess;
+ lr->count--;
+
+ ngx_shmtx_unlock(&ctx->shpool->mutex);
+
+ ctx->node = NULL;
+
+ if (limits[n].nodelay) {
+ continue;
+ }
+
+ delay = excess * 1000 / ctx->rate;
+
+ if (delay > max_delay) {
+ max_delay = delay;
+ *ep = excess;
+ *limit = &limits[n];
+ }
+ }
+
+ return max_delay;
+}
+
+
+static void
+ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n)
+{
+ ngx_int_t excess;
+ ngx_msec_t now;
+ ngx_queue_t *q;
+ ngx_msec_int_t ms;
+ ngx_rbtree_node_t *node;
+ ngx_http_limit_req_node_t *lr;
+
+ now = ngx_current_msec;
+
+ /*
+ * n == 1 deletes one or two zero rate entries
+ * n == 0 deletes oldest entry by force
+ * and one or two zero rate entries
+ */
+
+ while (n < 3) {
+
+ if (ngx_queue_empty(&ctx->sh->queue)) {
+ return;
+ }
+
+ q = ngx_queue_last(&ctx->sh->queue);
+
+ lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);
+
+ if (lr->count) {
+
+ /*
+ * There is not much sense in looking further,
+ * because we bump nodes on the lookup stage.
+ */
+
+ return;
+ }
+
+ if (n++ != 0) {
+
+ ms = (ngx_msec_int_t) (now - lr->last);
+ ms = ngx_abs(ms);
+
+ if (ms < 60000) {
+ return;
+ }
+
+ excess = lr->excess - ctx->rate * ms / 1000;
+
+ if (excess > 0) {
+ return;
+ }
+ }
+
+ ngx_queue_remove(q);
+
+ node = (ngx_rbtree_node_t *)
+ ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));
+
+ ngx_rbtree_delete(&ctx->sh->rbtree, node);
+
+ ngx_slab_free_locked(ctx->shpool, node);
+ }
+}
+
+
+static ngx_int_t
+ngx_http_limit_req_init_zone(ngx_shm_zone_t *shm_zone, void *data)
+{
+ ngx_http_limit_req_ctx_t *octx = data;
+
+ size_t len;
+ ngx_http_limit_req_ctx_t *ctx;
+
+ ctx = shm_zone->data;
+
+ if (octx) {
+ if (ctx->key.value.len != octx->key.value.len
+ || ngx_strncmp(ctx->key.value.data, octx->key.value.data,
+ ctx->key.value.len)
+ != 0)
+ {
+ ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
+ "limit_req \"%V\" uses the \"%V\" key "
+ "while previously it used the \"%V\" key",
+ &shm_zone->shm.name, &ctx->key.value,
+ &octx->key.value);
+ return NGX_ERROR;
+ }
+
+ ctx->sh = octx->sh;
+ ctx->shpool = octx->shpool;
+
+ return NGX_OK;
+ }
+
+ ctx->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+ if (shm_zone->shm.exists) {
+ ctx->sh = ctx->shpool->data;
+
+ return NGX_OK;
+ }
+
+ ctx->sh = ngx_slab_alloc(ctx->shpool, sizeof(ngx_http_limit_req_shctx_t));
+ if (ctx->sh == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->shpool->data = ctx->sh;
+
+ ngx_rbtree_init(&ctx->sh->rbtree, &ctx->sh->sentinel,
+ ngx_http_limit_req_rbtree_insert_value);
+
+ ngx_queue_init(&ctx->sh->queue);
+
+ len = sizeof(" in limit_req zone \"\"") + shm_zone->shm.name.len;
+
+ ctx->shpool->log_ctx = ngx_slab_alloc(ctx->shpool, len);
+ if (ctx->shpool->log_ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_sprintf(ctx->shpool->log_ctx, " in limit_req zone \"%V\"%Z",
+ &shm_zone->shm.name);
+
+ ctx->shpool->log_nomem = 0;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_limit_req_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_limit_req_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->limits.elts = NULL;
+ */
+
+ conf->limit_log_level = NGX_CONF_UNSET_UINT;
+ conf->status_code = NGX_CONF_UNSET_UINT;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_limit_req_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_limit_req_conf_t *prev = parent;
+ ngx_http_limit_req_conf_t *conf = child;
+
+ if (conf->limits.elts == NULL) {
+ conf->limits = prev->limits;
+ }
+
+ ngx_conf_merge_uint_value(conf->limit_log_level, prev->limit_log_level,
+ NGX_LOG_ERR);
+
+ conf->delay_log_level = (conf->limit_log_level == NGX_LOG_INFO) ?
+ NGX_LOG_INFO : conf->limit_log_level + 1;
+
+ ngx_conf_merge_uint_value(conf->status_code, prev->status_code,
+ NGX_HTTP_SERVICE_UNAVAILABLE);
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ u_char *p;
+ size_t len;
+ ssize_t size;
+ ngx_str_t *value, name, s;
+ ngx_int_t rate, scale;
+ ngx_uint_t i;
+ ngx_shm_zone_t *shm_zone;
+ ngx_http_limit_req_ctx_t *ctx;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_ctx_t));
+ if (ctx == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &ctx->key;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ size = 0;
+ rate = 1;
+ scale = 1;
+ name.len = 0;
+
+ for (i = 2; i < cf->args->nelts; i++) {
+
+ if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
+
+ name.data = value[i].data + 5;
+
+ p = (u_char *) ngx_strchr(name.data, ':');
+
+ if (p == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid zone size \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ name.len = p - name.data;
+
+ s.data = p + 1;
+ s.len = value[i].data + value[i].len - s.data;
+
+ size = ngx_parse_size(&s);
+
+ if (size == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid zone size \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (size < (ssize_t) (8 * ngx_pagesize)) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "zone \"%V\" is too small", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "rate=", 5) == 0) {
+
+ len = value[i].len;
+ p = value[i].data + len - 3;
+
+ if (ngx_strncmp(p, "r/s", 3) == 0) {
+ scale = 1;
+ len -= 3;
+
+ } else if (ngx_strncmp(p, "r/m", 3) == 0) {
+ scale = 60;
+ len -= 3;
+ }
+
+ rate = ngx_atoi(value[i].data + 5, len - 5);
+ if (rate <= 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid rate \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (name.len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"%V\" must have \"zone\" parameter",
+ &cmd->name);
+ return NGX_CONF_ERROR;
+ }
+
+ ctx->rate = rate * 1000 / scale;
+
+ shm_zone = ngx_shared_memory_add(cf, &name, size,
+ &ngx_http_limit_req_module);
+ if (shm_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (shm_zone->data) {
+ ctx = shm_zone->data;
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "%V \"%V\" is already bound to key \"%V\"",
+ &cmd->name, &name, &ctx->key.value);
+ return NGX_CONF_ERROR;
+ }
+
+ shm_zone->init = ngx_http_limit_req_init_zone;
+ shm_zone->data = ctx;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_limit_req_conf_t *lrcf = conf;
+
+ ngx_int_t burst;
+ ngx_str_t *value, s;
+ ngx_uint_t i, nodelay;
+ ngx_shm_zone_t *shm_zone;
+ ngx_http_limit_req_limit_t *limit, *limits;
+
+ value = cf->args->elts;
+
+ shm_zone = NULL;
+ burst = 0;
+ nodelay = 0;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+
+ if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
+
+ s.len = value[i].len - 5;
+ s.data = value[i].data + 5;
+
+ shm_zone = ngx_shared_memory_add(cf, &s, 0,
+ &ngx_http_limit_req_module);
+ if (shm_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "burst=", 6) == 0) {
+
+ burst = ngx_atoi(value[i].data + 6, value[i].len - 6);
+ if (burst <= 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid burst rate \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ if (ngx_strcmp(value[i].data, "nodelay") == 0) {
+ nodelay = 1;
+ continue;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (shm_zone == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"%V\" must have \"zone\" parameter",
+ &cmd->name);
+ return NGX_CONF_ERROR;
+ }
+
+ limits = lrcf->limits.elts;
+
+ if (limits == NULL) {
+ if (ngx_array_init(&lrcf->limits, cf->pool, 1,
+ sizeof(ngx_http_limit_req_limit_t))
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ for (i = 0; i < lrcf->limits.nelts; i++) {
+ if (shm_zone == limits[i].shm_zone) {
+ return "is duplicate";
+ }
+ }
+
+ limit = ngx_array_push(&lrcf->limits);
+ if (limit == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ limit->shm_zone = shm_zone;
+ limit->burst = burst * 1000;
+ limit->nodelay = nodelay;
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_limit_req_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_limit_req_handler;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_log_module.c b/app/nginx/src/http/modules/ngx_http_log_module.c
new file mode 100644
index 0000000..917ed55
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_log_module.c
@@ -0,0 +1,1857 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#if (NGX_ZLIB)
+#include <zlib.h>
+#endif
+
+
+typedef struct ngx_http_log_op_s ngx_http_log_op_t;
+
+typedef u_char *(*ngx_http_log_op_run_pt) (ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op);
+
+typedef size_t (*ngx_http_log_op_getlen_pt) (ngx_http_request_t *r,
+ uintptr_t data);
+
+
+struct ngx_http_log_op_s {
+ size_t len;
+ ngx_http_log_op_getlen_pt getlen;
+ ngx_http_log_op_run_pt run;
+ uintptr_t data;
+};
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_array_t *flushes;
+ ngx_array_t *ops; /* array of ngx_http_log_op_t */
+} ngx_http_log_fmt_t;
+
+
+typedef struct {
+ ngx_array_t formats; /* array of ngx_http_log_fmt_t */
+ ngx_uint_t combined_used; /* unsigned combined_used:1 */
+} ngx_http_log_main_conf_t;
+
+
+typedef struct {
+ u_char *start;
+ u_char *pos;
+ u_char *last;
+
+ ngx_event_t *event;
+ ngx_msec_t flush;
+ ngx_int_t gzip;
+} ngx_http_log_buf_t;
+
+
+typedef struct {
+ ngx_array_t *lengths;
+ ngx_array_t *values;
+} ngx_http_log_script_t;
+
+
+typedef struct {
+ ngx_open_file_t *file;
+ ngx_http_log_script_t *script;
+ time_t disk_full_time;
+ time_t error_log_time;
+ ngx_syslog_peer_t *syslog_peer;
+ ngx_http_log_fmt_t *format;
+ ngx_http_complex_value_t *filter;
+} ngx_http_log_t;
+
+
+typedef struct {
+ ngx_array_t *logs; /* array of ngx_http_log_t */
+
+ ngx_open_file_cache_t *open_file_cache;
+ time_t open_file_cache_valid;
+ ngx_uint_t open_file_cache_min_uses;
+
+ ngx_uint_t off; /* unsigned off:1 */
+} ngx_http_log_loc_conf_t;
+
+
+typedef struct {
+ ngx_str_t name;
+ size_t len;
+ ngx_http_log_op_run_pt run;
+} ngx_http_log_var_t;
+
+
+static void ngx_http_log_write(ngx_http_request_t *r, ngx_http_log_t *log,
+ u_char *buf, size_t len);
+static ssize_t ngx_http_log_script_write(ngx_http_request_t *r,
+ ngx_http_log_script_t *script, u_char **name, u_char *buf, size_t len);
+
+#if (NGX_ZLIB)
+static ssize_t ngx_http_log_gzip(ngx_fd_t fd, u_char *buf, size_t len,
+ ngx_int_t level, ngx_log_t *log);
+
+static void *ngx_http_log_gzip_alloc(void *opaque, u_int items, u_int size);
+static void ngx_http_log_gzip_free(void *opaque, void *address);
+#endif
+
+static void ngx_http_log_flush(ngx_open_file_t *file, ngx_log_t *log);
+static void ngx_http_log_flush_handler(ngx_event_t *ev);
+
+static u_char *ngx_http_log_pipe(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op);
+static u_char *ngx_http_log_time(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op);
+static u_char *ngx_http_log_iso8601(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op);
+static u_char *ngx_http_log_msec(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op);
+static u_char *ngx_http_log_request_time(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op);
+static u_char *ngx_http_log_status(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op);
+static u_char *ngx_http_log_bytes_sent(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op);
+static u_char *ngx_http_log_body_bytes_sent(ngx_http_request_t *r,
+ u_char *buf, ngx_http_log_op_t *op);
+static u_char *ngx_http_log_request_length(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op);
+
+static ngx_int_t ngx_http_log_variable_compile(ngx_conf_t *cf,
+ ngx_http_log_op_t *op, ngx_str_t *value, ngx_uint_t json);
+static size_t ngx_http_log_variable_getlen(ngx_http_request_t *r,
+ uintptr_t data);
+static u_char *ngx_http_log_variable(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op);
+static uintptr_t ngx_http_log_escape(u_char *dst, u_char *src, size_t size);
+static size_t ngx_http_log_json_variable_getlen(ngx_http_request_t *r,
+ uintptr_t data);
+static u_char *ngx_http_log_json_variable(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op);
+
+
+static void *ngx_http_log_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_log_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_log_merge_loc_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_http_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_log_compile_format(ngx_conf_t *cf,
+ ngx_array_t *flushes, ngx_array_t *ops, ngx_array_t *args, ngx_uint_t s);
+static char *ngx_http_log_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static ngx_int_t ngx_http_log_init(ngx_conf_t *cf);
+
+
+static ngx_command_t ngx_http_log_commands[] = {
+
+ { ngx_string("log_format"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE,
+ ngx_http_log_set_format,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("access_log"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_HTTP_LMT_CONF|NGX_CONF_1MORE,
+ ngx_http_log_set_log,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("open_log_file_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
+ ngx_http_log_open_file_cache,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_log_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_log_init, /* postconfiguration */
+
+ ngx_http_log_create_main_conf, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_log_create_loc_conf, /* create location configuration */
+ ngx_http_log_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_log_module = {
+ NGX_MODULE_V1,
+ &ngx_http_log_module_ctx, /* module context */
+ ngx_http_log_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_http_access_log = ngx_string(NGX_HTTP_LOG_PATH);
+
+
+static ngx_str_t ngx_http_combined_fmt =
+ ngx_string("$remote_addr - $remote_user [$time_local] "
+ "\"$request\" $status $body_bytes_sent "
+ "\"$http_referer\" \"$http_user_agent\"");
+
+
+static ngx_http_log_var_t ngx_http_log_vars[] = {
+ { ngx_string("pipe"), 1, ngx_http_log_pipe },
+ { ngx_string("time_local"), sizeof("28/Sep/1970:12:00:00 +0600") - 1,
+ ngx_http_log_time },
+ { ngx_string("time_iso8601"), sizeof("1970-09-28T12:00:00+06:00") - 1,
+ ngx_http_log_iso8601 },
+ { ngx_string("msec"), NGX_TIME_T_LEN + 4, ngx_http_log_msec },
+ { ngx_string("request_time"), NGX_TIME_T_LEN + 4,
+ ngx_http_log_request_time },
+ { ngx_string("status"), NGX_INT_T_LEN, ngx_http_log_status },
+ { ngx_string("bytes_sent"), NGX_OFF_T_LEN, ngx_http_log_bytes_sent },
+ { ngx_string("body_bytes_sent"), NGX_OFF_T_LEN,
+ ngx_http_log_body_bytes_sent },
+ { ngx_string("request_length"), NGX_SIZE_T_LEN,
+ ngx_http_log_request_length },
+
+ { ngx_null_string, 0, NULL }
+};
+
+
+static ngx_int_t
+ngx_http_log_handler(ngx_http_request_t *r)
+{
+ u_char *line, *p;
+ size_t len, size;
+ ssize_t n;
+ ngx_str_t val;
+ ngx_uint_t i, l;
+ ngx_http_log_t *log;
+ ngx_http_log_op_t *op;
+ ngx_http_log_buf_t *buffer;
+ ngx_http_log_loc_conf_t *lcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http log handler");
+
+ lcf = ngx_http_get_module_loc_conf(r, ngx_http_log_module);
+
+ if (lcf->off) {
+ return NGX_OK;
+ }
+
+ log = lcf->logs->elts;
+ for (l = 0; l < lcf->logs->nelts; l++) {
+
+ if (log[l].filter) {
+ if (ngx_http_complex_value(r, log[l].filter, &val) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (val.len == 0 || (val.len == 1 && val.data[0] == '0')) {
+ continue;
+ }
+ }
+
+ if (ngx_time() == log[l].disk_full_time) {
+
+ /*
+ * on FreeBSD writing to a full filesystem with enabled softupdates
+ * may block process for much longer time than writing to non-full
+ * filesystem, so we skip writing to a log for one second
+ */
+
+ continue;
+ }
+
+ ngx_http_script_flush_no_cacheable_variables(r, log[l].format->flushes);
+
+ len = 0;
+ op = log[l].format->ops->elts;
+ for (i = 0; i < log[l].format->ops->nelts; i++) {
+ if (op[i].len == 0) {
+ len += op[i].getlen(r, op[i].data);
+
+ } else {
+ len += op[i].len;
+ }
+ }
+
+ if (log[l].syslog_peer) {
+
+ /* length of syslog's PRI and HEADER message parts */
+ len += sizeof("<255>Jan 01 00:00:00 ") - 1
+ + ngx_cycle->hostname.len + 1
+ + log[l].syslog_peer->tag.len + 2;
+
+ goto alloc_line;
+ }
+
+ len += NGX_LINEFEED_SIZE;
+
+ buffer = log[l].file ? log[l].file->data : NULL;
+
+ if (buffer) {
+
+ if (len > (size_t) (buffer->last - buffer->pos)) {
+
+ ngx_http_log_write(r, &log[l], buffer->start,
+ buffer->pos - buffer->start);
+
+ buffer->pos = buffer->start;
+ }
+
+ if (len <= (size_t) (buffer->last - buffer->pos)) {
+
+ p = buffer->pos;
+
+ if (buffer->event && p == buffer->start) {
+ ngx_add_timer(buffer->event, buffer->flush);
+ }
+
+ for (i = 0; i < log[l].format->ops->nelts; i++) {
+ p = op[i].run(r, p, &op[i]);
+ }
+
+ ngx_linefeed(p);
+
+ buffer->pos = p;
+
+ continue;
+ }
+
+ if (buffer->event && buffer->event->timer_set) {
+ ngx_del_timer(buffer->event);
+ }
+ }
+
+ alloc_line:
+
+ line = ngx_pnalloc(r->pool, len);
+ if (line == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = line;
+
+ if (log[l].syslog_peer) {
+ p = ngx_syslog_add_header(log[l].syslog_peer, line);
+ }
+
+ for (i = 0; i < log[l].format->ops->nelts; i++) {
+ p = op[i].run(r, p, &op[i]);
+ }
+
+ if (log[l].syslog_peer) {
+
+ size = p - line;
+
+ n = ngx_syslog_send(log[l].syslog_peer, line, size);
+
+ if (n < 0) {
+ ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+ "send() to syslog failed");
+
+ } else if ((size_t) n != size) {
+ ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+ "send() to syslog has written only %z of %uz",
+ n, size);
+ }
+
+ continue;
+ }
+
+ ngx_linefeed(p);
+
+ ngx_http_log_write(r, &log[l], line, p - line);
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_log_write(ngx_http_request_t *r, ngx_http_log_t *log, u_char *buf,
+ size_t len)
+{
+ u_char *name;
+ time_t now;
+ ssize_t n;
+ ngx_err_t err;
+#if (NGX_ZLIB)
+ ngx_http_log_buf_t *buffer;
+#endif
+
+ if (log->script == NULL) {
+ name = log->file->name.data;
+
+#if (NGX_ZLIB)
+ buffer = log->file->data;
+
+ if (buffer && buffer->gzip) {
+ n = ngx_http_log_gzip(log->file->fd, buf, len, buffer->gzip,
+ r->connection->log);
+ } else {
+ n = ngx_write_fd(log->file->fd, buf, len);
+ }
+#else
+ n = ngx_write_fd(log->file->fd, buf, len);
+#endif
+
+ } else {
+ name = NULL;
+ n = ngx_http_log_script_write(r, log->script, &name, buf, len);
+ }
+
+ if (n == (ssize_t) len) {
+ return;
+ }
+
+ now = ngx_time();
+
+ if (n == -1) {
+ err = ngx_errno;
+
+ if (err == NGX_ENOSPC) {
+ log->disk_full_time = now;
+ }
+
+ if (now - log->error_log_time > 59) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, err,
+ ngx_write_fd_n " to \"%s\" failed", name);
+
+ log->error_log_time = now;
+ }
+
+ return;
+ }
+
+ if (now - log->error_log_time > 59) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ ngx_write_fd_n " to \"%s\" was incomplete: %z of %uz",
+ name, n, len);
+
+ log->error_log_time = now;
+ }
+}
+
+
+static ssize_t
+ngx_http_log_script_write(ngx_http_request_t *r, ngx_http_log_script_t *script,
+ u_char **name, u_char *buf, size_t len)
+{
+ size_t root;
+ ssize_t n;
+ ngx_str_t log, path;
+ ngx_open_file_info_t of;
+ ngx_http_log_loc_conf_t *llcf;
+ ngx_http_core_loc_conf_t *clcf;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (!r->root_tested) {
+
+ /* test root directory existence */
+
+ if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) {
+ /* simulate successful logging */
+ return len;
+ }
+
+ path.data[root] = '\0';
+
+ ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+ of.valid = clcf->open_file_cache_valid;
+ of.min_uses = clcf->open_file_cache_min_uses;
+ of.test_dir = 1;
+ of.test_only = 1;
+ of.errors = clcf->open_file_cache_errors;
+ of.events = clcf->open_file_cache_events;
+
+ if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+ /* simulate successful logging */
+ return len;
+ }
+
+ if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+ != NGX_OK)
+ {
+ if (of.err == 0) {
+ /* simulate successful logging */
+ return len;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, of.err,
+ "testing \"%s\" existence failed", path.data);
+
+ /* simulate successful logging */
+ return len;
+ }
+
+ if (!of.is_dir) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_ENOTDIR,
+ "testing \"%s\" existence failed", path.data);
+
+ /* simulate successful logging */
+ return len;
+ }
+ }
+
+ if (ngx_http_script_run(r, &log, script->lengths->elts, 1,
+ script->values->elts)
+ == NULL)
+ {
+ /* simulate successful logging */
+ return len;
+ }
+
+ log.data[log.len - 1] = '\0';
+ *name = log.data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http log \"%s\"", log.data);
+
+ llcf = ngx_http_get_module_loc_conf(r, ngx_http_log_module);
+
+ ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+ of.log = 1;
+ of.valid = llcf->open_file_cache_valid;
+ of.min_uses = llcf->open_file_cache_min_uses;
+ of.directio = NGX_OPEN_FILE_DIRECTIO_OFF;
+
+ if (ngx_http_set_disable_symlinks(r, clcf, &log, &of) != NGX_OK) {
+ /* simulate successful logging */
+ return len;
+ }
+
+ if (ngx_open_cached_file(llcf->open_file_cache, &log, &of, r->pool)
+ != NGX_OK)
+ {
+ if (of.err == 0) {
+ /* simulate successful logging */
+ return len;
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+ "%s \"%s\" failed", of.failed, log.data);
+ /* simulate successful logging */
+ return len;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http log #%d", of.fd);
+
+ n = ngx_write_fd(of.fd, buf, len);
+
+ return n;
+}
+
+
+#if (NGX_ZLIB)
+
+static ssize_t
+ngx_http_log_gzip(ngx_fd_t fd, u_char *buf, size_t len, ngx_int_t level,
+ ngx_log_t *log)
+{
+ int rc, wbits, memlevel;
+ u_char *out;
+ size_t size;
+ ssize_t n;
+ z_stream zstream;
+ ngx_err_t err;
+ ngx_pool_t *pool;
+
+ wbits = MAX_WBITS;
+ memlevel = MAX_MEM_LEVEL - 1;
+
+ while ((ssize_t) len < ((1 << (wbits - 1)) - 262)) {
+ wbits--;
+ memlevel--;
+ }
+
+ /*
+ * This is a formula from deflateBound() for conservative upper bound of
+ * compressed data plus 18 bytes of gzip wrapper.
+ */
+
+ size = len + ((len + 7) >> 3) + ((len + 63) >> 6) + 5 + 18;
+
+ ngx_memzero(&zstream, sizeof(z_stream));
+
+ pool = ngx_create_pool(256, log);
+ if (pool == NULL) {
+ /* simulate successful logging */
+ return len;
+ }
+
+ pool->log = log;
+
+ zstream.zalloc = ngx_http_log_gzip_alloc;
+ zstream.zfree = ngx_http_log_gzip_free;
+ zstream.opaque = pool;
+
+ out = ngx_pnalloc(pool, size);
+ if (out == NULL) {
+ goto done;
+ }
+
+ zstream.next_in = buf;
+ zstream.avail_in = len;
+ zstream.next_out = out;
+ zstream.avail_out = size;
+
+ rc = deflateInit2(&zstream, (int) level, Z_DEFLATED, wbits + 16, memlevel,
+ Z_DEFAULT_STRATEGY);
+
+ if (rc != Z_OK) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0, "deflateInit2() failed: %d", rc);
+ goto done;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, log, 0,
+ "deflate in: ni:%p no:%p ai:%ud ao:%ud",
+ zstream.next_in, zstream.next_out,
+ zstream.avail_in, zstream.avail_out);
+
+ rc = deflate(&zstream, Z_FINISH);
+
+ if (rc != Z_STREAM_END) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "deflate(Z_FINISH) failed: %d", rc);
+ goto done;
+ }
+
+ ngx_log_debug5(NGX_LOG_DEBUG_HTTP, log, 0,
+ "deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d",
+ zstream.next_in, zstream.next_out,
+ zstream.avail_in, zstream.avail_out,
+ rc);
+
+ size -= zstream.avail_out;
+
+ rc = deflateEnd(&zstream);
+
+ if (rc != Z_OK) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0, "deflateEnd() failed: %d", rc);
+ goto done;
+ }
+
+ n = ngx_write_fd(fd, out, size);
+
+ if (n != (ssize_t) size) {
+ err = (n == -1) ? ngx_errno : 0;
+
+ ngx_destroy_pool(pool);
+
+ ngx_set_errno(err);
+ return -1;
+ }
+
+done:
+
+ ngx_destroy_pool(pool);
+
+ /* simulate successful logging */
+ return len;
+}
+
+
+static void *
+ngx_http_log_gzip_alloc(void *opaque, u_int items, u_int size)
+{
+ ngx_pool_t *pool = opaque;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pool->log, 0,
+ "gzip alloc: n:%ud s:%ud", items, size);
+
+ return ngx_palloc(pool, items * size);
+}
+
+
+static void
+ngx_http_log_gzip_free(void *opaque, void *address)
+{
+#if 0
+ ngx_pool_t *pool = opaque;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pool->log, 0, "gzip free: %p", address);
+#endif
+}
+
+#endif
+
+
+static void
+ngx_http_log_flush(ngx_open_file_t *file, ngx_log_t *log)
+{
+ size_t len;
+ ssize_t n;
+ ngx_http_log_buf_t *buffer;
+
+ buffer = file->data;
+
+ len = buffer->pos - buffer->start;
+
+ if (len == 0) {
+ return;
+ }
+
+#if (NGX_ZLIB)
+ if (buffer->gzip) {
+ n = ngx_http_log_gzip(file->fd, buffer->start, len, buffer->gzip, log);
+ } else {
+ n = ngx_write_fd(file->fd, buffer->start, len);
+ }
+#else
+ n = ngx_write_fd(file->fd, buffer->start, len);
+#endif
+
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ ngx_write_fd_n " to \"%s\" failed",
+ file->name.data);
+
+ } else if ((size_t) n != len) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ ngx_write_fd_n " to \"%s\" was incomplete: %z of %uz",
+ file->name.data, n, len);
+ }
+
+ buffer->pos = buffer->start;
+
+ if (buffer->event && buffer->event->timer_set) {
+ ngx_del_timer(buffer->event);
+ }
+}
+
+
+static void
+ngx_http_log_flush_handler(ngx_event_t *ev)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "http log buffer flush handler");
+
+ ngx_http_log_flush(ev->data, ev->log);
+}
+
+
+static u_char *
+ngx_http_log_copy_short(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op)
+{
+ size_t len;
+ uintptr_t data;
+
+ len = op->len;
+ data = op->data;
+
+ while (len--) {
+ *buf++ = (u_char) (data & 0xff);
+ data >>= 8;
+ }
+
+ return buf;
+}
+
+
+static u_char *
+ngx_http_log_copy_long(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op)
+{
+ return ngx_cpymem(buf, (u_char *) op->data, op->len);
+}
+
+
+static u_char *
+ngx_http_log_pipe(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op)
+{
+ if (r->pipeline) {
+ *buf = 'p';
+ } else {
+ *buf = '.';
+ }
+
+ return buf + 1;
+}
+
+
+static u_char *
+ngx_http_log_time(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op)
+{
+ return ngx_cpymem(buf, ngx_cached_http_log_time.data,
+ ngx_cached_http_log_time.len);
+}
+
+static u_char *
+ngx_http_log_iso8601(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op)
+{
+ return ngx_cpymem(buf, ngx_cached_http_log_iso8601.data,
+ ngx_cached_http_log_iso8601.len);
+}
+
+static u_char *
+ngx_http_log_msec(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op)
+{
+ ngx_time_t *tp;
+
+ tp = ngx_timeofday();
+
+ return ngx_sprintf(buf, "%T.%03M", tp->sec, tp->msec);
+}
+
+
+static u_char *
+ngx_http_log_request_time(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op)
+{
+ ngx_time_t *tp;
+ ngx_msec_int_t ms;
+
+ tp = ngx_timeofday();
+
+ ms = (ngx_msec_int_t)
+ ((tp->sec - r->start_sec) * 1000 + (tp->msec - r->start_msec));
+ ms = ngx_max(ms, 0);
+
+ return ngx_sprintf(buf, "%T.%03M", (time_t) ms / 1000, ms % 1000);
+}
+
+
+static u_char *
+ngx_http_log_status(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op)
+{
+ ngx_uint_t status;
+
+ if (r->err_status) {
+ status = r->err_status;
+
+ } else if (r->headers_out.status) {
+ status = r->headers_out.status;
+
+ } else if (r->http_version == NGX_HTTP_VERSION_9) {
+ status = 9;
+
+ } else {
+ status = 0;
+ }
+
+ return ngx_sprintf(buf, "%03ui", status);
+}
+
+
+static u_char *
+ngx_http_log_bytes_sent(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op)
+{
+ return ngx_sprintf(buf, "%O", r->connection->sent);
+}
+
+
+/*
+ * although there is a real $body_bytes_sent variable,
+ * this log operation code function is more optimized for logging
+ */
+
+static u_char *
+ngx_http_log_body_bytes_sent(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op)
+{
+ off_t length;
+
+ length = r->connection->sent - r->header_size;
+
+ if (length > 0) {
+ return ngx_sprintf(buf, "%O", length);
+ }
+
+ *buf = '0';
+
+ return buf + 1;
+}
+
+
+static u_char *
+ngx_http_log_request_length(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op)
+{
+ return ngx_sprintf(buf, "%O", r->request_length);
+}
+
+
+static ngx_int_t
+ngx_http_log_variable_compile(ngx_conf_t *cf, ngx_http_log_op_t *op,
+ ngx_str_t *value, ngx_uint_t json)
+{
+ ngx_int_t index;
+
+ index = ngx_http_get_variable_index(cf, value);
+ if (index == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ op->len = 0;
+
+ if (json) {
+ op->getlen = ngx_http_log_json_variable_getlen;
+ op->run = ngx_http_log_json_variable;
+
+ } else {
+ op->getlen = ngx_http_log_variable_getlen;
+ op->run = ngx_http_log_variable;
+ }
+
+ op->data = index;
+
+ return NGX_OK;
+}
+
+
+static size_t
+ngx_http_log_variable_getlen(ngx_http_request_t *r, uintptr_t data)
+{
+ uintptr_t len;
+ ngx_http_variable_value_t *value;
+
+ value = ngx_http_get_indexed_variable(r, data);
+
+ if (value == NULL || value->not_found) {
+ return 1;
+ }
+
+ len = ngx_http_log_escape(NULL, value->data, value->len);
+
+ value->escape = len ? 1 : 0;
+
+ return value->len + len * 3;
+}
+
+
+static u_char *
+ngx_http_log_variable(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op)
+{
+ ngx_http_variable_value_t *value;
+
+ value = ngx_http_get_indexed_variable(r, op->data);
+
+ if (value == NULL || value->not_found) {
+ *buf = '-';
+ return buf + 1;
+ }
+
+ if (value->escape == 0) {
+ return ngx_cpymem(buf, value->data, value->len);
+
+ } else {
+ return (u_char *) ngx_http_log_escape(buf, value->data, value->len);
+ }
+}
+
+
+static uintptr_t
+ngx_http_log_escape(u_char *dst, u_char *src, size_t size)
+{
+ ngx_uint_t n;
+ static u_char hex[] = "0123456789ABCDEF";
+
+ static uint32_t escape[] = {
+ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
+
+ /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
+ 0x00000004, /* 0000 0000 0000 0000 0000 0000 0000 0100 */
+
+ /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
+ 0x10000000, /* 0001 0000 0000 0000 0000 0000 0000 0000 */
+
+ /* ~}| {zyx wvut srqp onml kjih gfed cba` */
+ 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */
+
+ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
+ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
+ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
+ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
+ };
+
+
+ if (dst == NULL) {
+
+ /* find the number of the characters to be escaped */
+
+ n = 0;
+
+ while (size) {
+ if (escape[*src >> 5] & (1U << (*src & 0x1f))) {
+ n++;
+ }
+ src++;
+ size--;
+ }
+
+ return (uintptr_t) n;
+ }
+
+ while (size) {
+ if (escape[*src >> 5] & (1U << (*src & 0x1f))) {
+ *dst++ = '\\';
+ *dst++ = 'x';
+ *dst++ = hex[*src >> 4];
+ *dst++ = hex[*src & 0xf];
+ src++;
+
+ } else {
+ *dst++ = *src++;
+ }
+ size--;
+ }
+
+ return (uintptr_t) dst;
+}
+
+
+static size_t
+ngx_http_log_json_variable_getlen(ngx_http_request_t *r, uintptr_t data)
+{
+ uintptr_t len;
+ ngx_http_variable_value_t *value;
+
+ value = ngx_http_get_indexed_variable(r, data);
+
+ if (value == NULL || value->not_found) {
+ return 0;
+ }
+
+ len = ngx_escape_json(NULL, value->data, value->len);
+
+ value->escape = len ? 1 : 0;
+
+ return value->len + len;
+}
+
+
+static u_char *
+ngx_http_log_json_variable(ngx_http_request_t *r, u_char *buf,
+ ngx_http_log_op_t *op)
+{
+ ngx_http_variable_value_t *value;
+
+ value = ngx_http_get_indexed_variable(r, op->data);
+
+ if (value == NULL || value->not_found) {
+ return buf;
+ }
+
+ if (value->escape == 0) {
+ return ngx_cpymem(buf, value->data, value->len);
+
+ } else {
+ return (u_char *) ngx_escape_json(buf, value->data, value->len);
+ }
+}
+
+
+static void *
+ngx_http_log_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_log_main_conf_t *conf;
+
+ ngx_http_log_fmt_t *fmt;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_main_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ if (ngx_array_init(&conf->formats, cf->pool, 4, sizeof(ngx_http_log_fmt_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ fmt = ngx_array_push(&conf->formats);
+ if (fmt == NULL) {
+ return NULL;
+ }
+
+ ngx_str_set(&fmt->name, "combined");
+
+ fmt->flushes = NULL;
+
+ fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_http_log_op_t));
+ if (fmt->ops == NULL) {
+ return NULL;
+ }
+
+ return conf;
+}
+
+
+static void *
+ngx_http_log_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_log_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->open_file_cache = NGX_CONF_UNSET_PTR;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_log_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_log_loc_conf_t *prev = parent;
+ ngx_http_log_loc_conf_t *conf = child;
+
+ ngx_http_log_t *log;
+ ngx_http_log_fmt_t *fmt;
+ ngx_http_log_main_conf_t *lmcf;
+
+ if (conf->open_file_cache == NGX_CONF_UNSET_PTR) {
+
+ conf->open_file_cache = prev->open_file_cache;
+ conf->open_file_cache_valid = prev->open_file_cache_valid;
+ conf->open_file_cache_min_uses = prev->open_file_cache_min_uses;
+
+ if (conf->open_file_cache == NGX_CONF_UNSET_PTR) {
+ conf->open_file_cache = NULL;
+ }
+ }
+
+ if (conf->logs || conf->off) {
+ return NGX_CONF_OK;
+ }
+
+ conf->logs = prev->logs;
+ conf->off = prev->off;
+
+ if (conf->logs || conf->off) {
+ return NGX_CONF_OK;
+ }
+
+ conf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_http_log_t));
+ if (conf->logs == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ log = ngx_array_push(conf->logs);
+ if (log == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(log, sizeof(ngx_http_log_t));
+
+ log->file = ngx_conf_open_file(cf->cycle, &ngx_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->format = &fmt[0];
+ lmcf->combined_used = 1;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_log_loc_conf_t *llcf = conf;
+
+ ssize_t size;
+ ngx_int_t gzip;
+ ngx_uint_t i, n;
+ ngx_msec_t flush;
+ ngx_str_t *value, name, s;
+ ngx_http_log_t *log;
+ ngx_syslog_peer_t *peer;
+ ngx_http_log_buf_t *buffer;
+ ngx_http_log_fmt_t *fmt;
+ ngx_http_log_main_conf_t *lmcf;
+ ngx_http_script_compile_t sc;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ llcf->off = 1;
+ if (cf->args->nelts == 2) {
+ return NGX_CONF_OK;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[2]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (llcf->logs == NULL) {
+ llcf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_http_log_t));
+ if (llcf->logs == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_log_module);
+
+ log = ngx_array_push(llcf->logs);
+ if (log == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(log, sizeof(ngx_http_log_t));
+
+
+ if (ngx_strncmp(value[1].data, "syslog:", 7) == 0) {
+
+ peer = ngx_pcalloc(cf->pool, sizeof(ngx_syslog_peer_t));
+ if (peer == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_syslog_process_conf(cf, peer) != NGX_CONF_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ log->syslog_peer = peer;
+
+ goto process_formats;
+ }
+
+ n = ngx_http_script_variables_count(&value[1]);
+
+ if (n == 0) {
+ log->file = ngx_conf_open_file(cf->cycle, &value[1]);
+ if (log->file == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+ if (ngx_conf_full_name(cf->cycle, &value[1], 0) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ log->script = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_script_t));
+ if (log->script == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = &value[1];
+ sc.lengths = &log->script->lengths;
+ sc.values = &log->script->values;
+ sc.variables = n;
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+process_formats:
+
+ if (cf->args->nelts >= 3) {
+ name = value[2];
+
+ if (ngx_strcmp(name.data, "combined") == 0) {
+ lmcf->combined_used = 1;
+ }
+
+ } else {
+ ngx_str_set(&name, "combined");
+ lmcf->combined_used = 1;
+ }
+
+ fmt = lmcf->formats.elts;
+ for (i = 0; i < lmcf->formats.nelts; i++) {
+ if (fmt[i].name.len == name.len
+ && ngx_strcasecmp(fmt[i].name.data, name.data) == 0)
+ {
+ log->format = &fmt[i];
+ break;
+ }
+ }
+
+ if (log->format == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "unknown log format \"%V\"", &name);
+ return NGX_CONF_ERROR;
+ }
+
+ size = 0;
+ flush = 0;
+ gzip = 0;
+
+ for (i = 3; i < cf->args->nelts; i++) {
+
+ if (ngx_strncmp(value[i].data, "buffer=", 7) == 0) {
+ s.len = value[i].len - 7;
+ s.data = value[i].data + 7;
+
+ size = ngx_parse_size(&s);
+
+ if (size == NGX_ERROR || size == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid buffer size \"%V\"", &s);
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "flush=", 6) == 0) {
+ s.len = value[i].len - 6;
+ s.data = value[i].data + 6;
+
+ flush = ngx_parse_time(&s, 0);
+
+ if (flush == (ngx_msec_t) NGX_ERROR || flush == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid flush time \"%V\"", &s);
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "gzip", 4) == 0
+ && (value[i].len == 4 || value[i].data[4] == '='))
+ {
+#if (NGX_ZLIB)
+ if (size == 0) {
+ size = 64 * 1024;
+ }
+
+ if (value[i].len == 4) {
+ gzip = Z_BEST_SPEED;
+ continue;
+ }
+
+ s.len = value[i].len - 5;
+ s.data = value[i].data + 5;
+
+ gzip = ngx_atoi(s.data, s.len);
+
+ if (gzip < 1 || gzip > 9) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid compression level \"%V\"", &s);
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "nginx was built without zlib support");
+ return NGX_CONF_ERROR;
+#endif
+ }
+
+ if (ngx_strncmp(value[i].data, "if=", 3) == 0) {
+ s.len = value[i].len - 3;
+ s.data = value[i].data + 3;
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &s;
+ ccv.complex_value = ngx_palloc(cf->pool,
+ sizeof(ngx_http_complex_value_t));
+ if (ccv.complex_value == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ log->filter = ccv.complex_value;
+
+ continue;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (flush && size == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "no buffer is defined for access_log \"%V\"",
+ &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (size) {
+
+ if (log->script) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "buffered logs cannot have variables in name");
+ return NGX_CONF_ERROR;
+ }
+
+ if (log->syslog_peer) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "logs to syslog cannot be buffered");
+ return NGX_CONF_ERROR;
+ }
+
+ if (log->file->data) {
+ buffer = log->file->data;
+
+ if (buffer->last - buffer->start != size
+ || buffer->flush != flush
+ || buffer->gzip != gzip)
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "access_log \"%V\" already defined "
+ "with conflicting parameters",
+ &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+ }
+
+ buffer = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_buf_t));
+ if (buffer == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ buffer->start = ngx_pnalloc(cf->pool, size);
+ if (buffer->start == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ buffer->pos = buffer->start;
+ buffer->last = buffer->start + size;
+
+ if (flush) {
+ buffer->event = ngx_pcalloc(cf->pool, sizeof(ngx_event_t));
+ if (buffer->event == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ buffer->event->data = log->file;
+ buffer->event->handler = ngx_http_log_flush_handler;
+ buffer->event->log = &cf->cycle->new_log;
+ buffer->event->cancelable = 1;
+
+ buffer->flush = flush;
+ }
+
+ buffer->gzip = gzip;
+
+ log->file->flush = ngx_http_log_flush;
+ log->file->data = buffer;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_log_main_conf_t *lmcf = conf;
+
+ ngx_str_t *value;
+ ngx_uint_t i;
+ ngx_http_log_fmt_t *fmt;
+
+ value = cf->args->elts;
+
+ fmt = lmcf->formats.elts;
+ for (i = 0; i < lmcf->formats.nelts; i++) {
+ if (fmt[i].name.len == value[1].len
+ && ngx_strcmp(fmt[i].name.data, value[1].data) == 0)
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "duplicate \"log_format\" name \"%V\"",
+ &value[1]);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ fmt = ngx_array_push(&lmcf->formats);
+ if (fmt == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ fmt->name = value[1];
+
+ fmt->flushes = ngx_array_create(cf->pool, 4, sizeof(ngx_int_t));
+ if (fmt->flushes == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_http_log_op_t));
+ if (fmt->ops == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ return ngx_http_log_compile_format(cf, fmt->flushes, fmt->ops, cf->args, 2);
+}
+
+
+static char *
+ngx_http_log_compile_format(ngx_conf_t *cf, ngx_array_t *flushes,
+ ngx_array_t *ops, ngx_array_t *args, ngx_uint_t s)
+{
+ u_char *data, *p, ch;
+ size_t i, len;
+ ngx_str_t *value, var;
+ ngx_int_t *flush;
+ ngx_uint_t bracket, json;
+ ngx_http_log_op_t *op;
+ ngx_http_log_var_t *v;
+
+ json = 0;
+ value = args->elts;
+
+ if (s < args->nelts && ngx_strncmp(value[s].data, "escape=", 7) == 0) {
+ data = value[s].data + 7;
+
+ if (ngx_strcmp(data, "json") == 0) {
+ json = 1;
+
+ } else if (ngx_strcmp(data, "default") != 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "unknown log format escaping \"%s\"", data);
+ return NGX_CONF_ERROR;
+ }
+
+ s++;
+ }
+
+ for ( /* void */ ; s < args->nelts; s++) {
+
+ i = 0;
+
+ while (i < value[s].len) {
+
+ op = ngx_array_push(ops);
+ if (op == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ data = &value[s].data[i];
+
+ if (value[s].data[i] == '$') {
+
+ if (++i == value[s].len) {
+ goto invalid;
+ }
+
+ if (value[s].data[i] == '{') {
+ bracket = 1;
+
+ if (++i == value[s].len) {
+ goto invalid;
+ }
+
+ var.data = &value[s].data[i];
+
+ } else {
+ bracket = 0;
+ var.data = &value[s].data[i];
+ }
+
+ for (var.len = 0; i < value[s].len; i++, var.len++) {
+ ch = value[s].data[i];
+
+ if (ch == '}' && bracket) {
+ i++;
+ bracket = 0;
+ break;
+ }
+
+ if ((ch >= 'A' && ch <= 'Z')
+ || (ch >= 'a' && ch <= 'z')
+ || (ch >= '0' && ch <= '9')
+ || ch == '_')
+ {
+ continue;
+ }
+
+ break;
+ }
+
+ if (bracket) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the closing bracket in \"%V\" "
+ "variable is missing", &var);
+ return NGX_CONF_ERROR;
+ }
+
+ if (var.len == 0) {
+ goto invalid;
+ }
+
+ for (v = ngx_http_log_vars; v->name.len; v++) {
+
+ if (v->name.len == var.len
+ && ngx_strncmp(v->name.data, var.data, var.len) == 0)
+ {
+ op->len = v->len;
+ op->getlen = NULL;
+ op->run = v->run;
+ op->data = 0;
+
+ goto found;
+ }
+ }
+
+ if (ngx_http_log_variable_compile(cf, op, &var, json)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ if (flushes) {
+
+ flush = ngx_array_push(flushes);
+ if (flush == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *flush = op->data; /* variable index */
+ }
+
+ found:
+
+ continue;
+ }
+
+ i++;
+
+ while (i < value[s].len && value[s].data[i] != '$') {
+ i++;
+ }
+
+ len = &value[s].data[i] - data;
+
+ if (len) {
+
+ op->len = len;
+ op->getlen = NULL;
+
+ if (len <= sizeof(uintptr_t)) {
+ op->run = ngx_http_log_copy_short;
+ op->data = 0;
+
+ while (len--) {
+ op->data <<= 8;
+ op->data |= data[len];
+ }
+
+ } else {
+ op->run = ngx_http_log_copy_long;
+
+ p = ngx_pnalloc(cf->pool, len);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memcpy(p, data, len);
+ op->data = (uintptr_t) p;
+ }
+ }
+ }
+ }
+
+ return NGX_CONF_OK;
+
+invalid:
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%s\"", data);
+
+ return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_http_log_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_log_loc_conf_t *llcf = conf;
+
+ time_t inactive, valid;
+ ngx_str_t *value, s;
+ ngx_int_t max, min_uses;
+ ngx_uint_t i;
+
+ if (llcf->open_file_cache != NGX_CONF_UNSET_PTR) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ max = 0;
+ inactive = 10;
+ valid = 60;
+ min_uses = 1;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+
+ if (ngx_strncmp(value[i].data, "max=", 4) == 0) {
+
+ max = ngx_atoi(value[i].data + 4, value[i].len - 4);
+ if (max == NGX_ERROR) {
+ goto failed;
+ }
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) {
+
+ s.len = value[i].len - 9;
+ s.data = value[i].data + 9;
+
+ inactive = ngx_parse_time(&s, 1);
+ if (inactive == (time_t) NGX_ERROR) {
+ goto failed;
+ }
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "min_uses=", 9) == 0) {
+
+ min_uses = ngx_atoi(value[i].data + 9, value[i].len - 9);
+ if (min_uses == NGX_ERROR) {
+ goto failed;
+ }
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "valid=", 6) == 0) {
+
+ s.len = value[i].len - 6;
+ s.data = value[i].data + 6;
+
+ valid = ngx_parse_time(&s, 1);
+ if (valid == (time_t) NGX_ERROR) {
+ goto failed;
+ }
+
+ continue;
+ }
+
+ if (ngx_strcmp(value[i].data, "off") == 0) {
+
+ llcf->open_file_cache = NULL;
+
+ continue;
+ }
+
+ failed:
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid \"open_log_file_cache\" parameter \"%V\"",
+ &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (llcf->open_file_cache == NULL) {
+ return NGX_CONF_OK;
+ }
+
+ if (max == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"open_log_file_cache\" must have \"max\" parameter");
+ return NGX_CONF_ERROR;
+ }
+
+ llcf->open_file_cache = ngx_open_file_cache_init(cf->pool, max, inactive);
+
+ if (llcf->open_file_cache) {
+
+ llcf->open_file_cache_valid = valid;
+ llcf->open_file_cache_min_uses = min_uses;
+
+ return NGX_CONF_OK;
+ }
+
+ return NGX_CONF_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_log_init(ngx_conf_t *cf)
+{
+ ngx_str_t *value;
+ ngx_array_t a;
+ ngx_http_handler_pt *h;
+ ngx_http_log_fmt_t *fmt;
+ ngx_http_log_main_conf_t *lmcf;
+ ngx_http_core_main_conf_t *cmcf;
+
+ lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_log_module);
+
+ if (lmcf->combined_used) {
+ if (ngx_array_init(&a, cf->pool, 1, sizeof(ngx_str_t)) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ value = ngx_array_push(&a);
+ if (value == NULL) {
+ return NGX_ERROR;
+ }
+
+ *value = ngx_http_combined_fmt;
+ fmt = lmcf->formats.elts;
+
+ if (ngx_http_log_compile_format(cf, NULL, fmt->ops, &a, 0)
+ != NGX_CONF_OK)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_LOG_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_log_handler;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_map_module.c b/app/nginx/src/http/modules/ngx_http_map_module.c
new file mode 100644
index 0000000..2fc9be9
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_map_module.c
@@ -0,0 +1,589 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_uint_t hash_max_size;
+ ngx_uint_t hash_bucket_size;
+} ngx_http_map_conf_t;
+
+
+typedef struct {
+ ngx_hash_keys_arrays_t keys;
+
+ ngx_array_t *values_hash;
+#if (NGX_PCRE)
+ ngx_array_t regexes;
+#endif
+
+ ngx_http_variable_value_t *default_value;
+ ngx_conf_t *cf;
+ unsigned hostnames:1;
+ unsigned no_cacheable:1;
+} ngx_http_map_conf_ctx_t;
+
+
+typedef struct {
+ ngx_http_map_t map;
+ ngx_http_complex_value_t value;
+ ngx_http_variable_value_t *default_value;
+ ngx_uint_t hostnames; /* unsigned hostnames:1 */
+} ngx_http_map_ctx_t;
+
+
+static int ngx_libc_cdecl ngx_http_map_cmp_dns_wildcards(const void *one,
+ const void *two);
+static void *ngx_http_map_create_conf(ngx_conf_t *cf);
+static char *ngx_http_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
+
+
+static ngx_command_t ngx_http_map_commands[] = {
+
+ { ngx_string("map"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2,
+ ngx_http_map_block,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("map_hash_max_size"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ offsetof(ngx_http_map_conf_t, hash_max_size),
+ NULL },
+
+ { ngx_string("map_hash_bucket_size"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ offsetof(ngx_http_map_conf_t, hash_bucket_size),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_map_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ ngx_http_map_create_conf, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_map_module = {
+ NGX_MODULE_V1,
+ &ngx_http_map_module_ctx, /* module context */
+ ngx_http_map_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_map_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_http_map_ctx_t *map = (ngx_http_map_ctx_t *) data;
+
+ ngx_str_t val, str;
+ ngx_http_complex_value_t *cv;
+ ngx_http_variable_value_t *value;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http map started");
+
+ if (ngx_http_complex_value(r, &map->value, &val) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (map->hostnames && val.len > 0 && val.data[val.len - 1] == '.') {
+ val.len--;
+ }
+
+ value = ngx_http_map_find(r, &map->map, &val);
+
+ if (value == NULL) {
+ value = map->default_value;
+ }
+
+ if (!value->valid) {
+ cv = (ngx_http_complex_value_t *) value->data;
+
+ if (ngx_http_complex_value(r, cv, &str) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->len = str.len;
+ v->data = str.data;
+
+ } else {
+ *v = *value;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http map: \"%V\" \"%v\"", &val, v);
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_map_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_map_conf_t *mcf;
+
+ mcf = ngx_palloc(cf->pool, sizeof(ngx_http_map_conf_t));
+ if (mcf == NULL) {
+ return NULL;
+ }
+
+ mcf->hash_max_size = NGX_CONF_UNSET_UINT;
+ mcf->hash_bucket_size = NGX_CONF_UNSET_UINT;
+
+ return mcf;
+}
+
+
+static char *
+ngx_http_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_map_conf_t *mcf = conf;
+
+ char *rv;
+ ngx_str_t *value, name;
+ ngx_conf_t save;
+ ngx_pool_t *pool;
+ ngx_hash_init_t hash;
+ ngx_http_map_ctx_t *map;
+ ngx_http_variable_t *var;
+ ngx_http_map_conf_ctx_t ctx;
+ ngx_http_compile_complex_value_t ccv;
+
+ if (mcf->hash_max_size == NGX_CONF_UNSET_UINT) {
+ mcf->hash_max_size = 2048;
+ }
+
+ if (mcf->hash_bucket_size == NGX_CONF_UNSET_UINT) {
+ mcf->hash_bucket_size = ngx_cacheline_size;
+
+ } else {
+ mcf->hash_bucket_size = ngx_align(mcf->hash_bucket_size,
+ ngx_cacheline_size);
+ }
+
+ map = ngx_pcalloc(cf->pool, sizeof(ngx_http_map_ctx_t));
+ if (map == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &map->value;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ name = value[2];
+
+ if (name.data[0] != '$') {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid variable name \"%V\"", &name);
+ return NGX_CONF_ERROR;
+ }
+
+ name.len--;
+ name.data++;
+
+ var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
+ if (var == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ var->get_handler = ngx_http_map_variable;
+ var->data = (uintptr_t) map;
+
+ pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
+ if (pool == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ctx.keys.pool = cf->pool;
+ ctx.keys.temp_pool = pool;
+
+ if (ngx_hash_keys_array_init(&ctx.keys, NGX_HASH_LARGE) != NGX_OK) {
+ ngx_destroy_pool(pool);
+ return NGX_CONF_ERROR;
+ }
+
+ ctx.values_hash = ngx_pcalloc(pool, sizeof(ngx_array_t) * ctx.keys.hsize);
+ if (ctx.values_hash == NULL) {
+ ngx_destroy_pool(pool);
+ return NGX_CONF_ERROR;
+ }
+
+#if (NGX_PCRE)
+ if (ngx_array_init(&ctx.regexes, cf->pool, 2, sizeof(ngx_http_map_regex_t))
+ != NGX_OK)
+ {
+ ngx_destroy_pool(pool);
+ return NGX_CONF_ERROR;
+ }
+#endif
+
+ ctx.default_value = NULL;
+ ctx.cf = &save;
+ ctx.hostnames = 0;
+ ctx.no_cacheable = 0;
+
+ save = *cf;
+ cf->pool = pool;
+ cf->ctx = &ctx;
+ cf->handler = ngx_http_map;
+ cf->handler_conf = conf;
+
+ rv = ngx_conf_parse(cf, NULL);
+
+ *cf = save;
+
+ if (rv != NGX_CONF_OK) {
+ ngx_destroy_pool(pool);
+ return rv;
+ }
+
+ if (ctx.no_cacheable) {
+ var->flags |= NGX_HTTP_VAR_NOCACHEABLE;
+ }
+
+ map->default_value = ctx.default_value ? ctx.default_value:
+ &ngx_http_variable_null_value;
+
+ map->hostnames = ctx.hostnames;
+
+ hash.key = ngx_hash_key_lc;
+ hash.max_size = mcf->hash_max_size;
+ hash.bucket_size = mcf->hash_bucket_size;
+ hash.name = "map_hash";
+ hash.pool = cf->pool;
+
+ if (ctx.keys.keys.nelts) {
+ hash.hash = &map->map.hash.hash;
+ hash.temp_pool = NULL;
+
+ if (ngx_hash_init(&hash, ctx.keys.keys.elts, ctx.keys.keys.nelts)
+ != NGX_OK)
+ {
+ ngx_destroy_pool(pool);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (ctx.keys.dns_wc_head.nelts) {
+
+ ngx_qsort(ctx.keys.dns_wc_head.elts,
+ (size_t) ctx.keys.dns_wc_head.nelts,
+ sizeof(ngx_hash_key_t), ngx_http_map_cmp_dns_wildcards);
+
+ hash.hash = NULL;
+ hash.temp_pool = pool;
+
+ if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_head.elts,
+ ctx.keys.dns_wc_head.nelts)
+ != NGX_OK)
+ {
+ ngx_destroy_pool(pool);
+ return NGX_CONF_ERROR;
+ }
+
+ map->map.hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
+ }
+
+ if (ctx.keys.dns_wc_tail.nelts) {
+
+ ngx_qsort(ctx.keys.dns_wc_tail.elts,
+ (size_t) ctx.keys.dns_wc_tail.nelts,
+ sizeof(ngx_hash_key_t), ngx_http_map_cmp_dns_wildcards);
+
+ hash.hash = NULL;
+ hash.temp_pool = pool;
+
+ if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_tail.elts,
+ ctx.keys.dns_wc_tail.nelts)
+ != NGX_OK)
+ {
+ ngx_destroy_pool(pool);
+ return NGX_CONF_ERROR;
+ }
+
+ map->map.hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash;
+ }
+
+#if (NGX_PCRE)
+
+ if (ctx.regexes.nelts) {
+ map->map.regex = ctx.regexes.elts;
+ map->map.nregex = ctx.regexes.nelts;
+ }
+
+#endif
+
+ ngx_destroy_pool(pool);
+
+ return rv;
+}
+
+
+static int ngx_libc_cdecl
+ngx_http_map_cmp_dns_wildcards(const void *one, const void *two)
+{
+ ngx_hash_key_t *first, *second;
+
+ first = (ngx_hash_key_t *) one;
+ second = (ngx_hash_key_t *) two;
+
+ return ngx_dns_strcmp(first->key.data, second->key.data);
+}
+
+
+static char *
+ngx_http_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+ u_char *data;
+ size_t len;
+ ngx_int_t rv;
+ ngx_str_t *value, v;
+ ngx_uint_t i, key;
+ ngx_http_map_conf_ctx_t *ctx;
+ ngx_http_complex_value_t cv, *cvp;
+ ngx_http_variable_value_t *var, **vp;
+ ngx_http_compile_complex_value_t ccv;
+
+ ctx = cf->ctx;
+
+ value = cf->args->elts;
+
+ if (cf->args->nelts == 1
+ && ngx_strcmp(value[0].data, "hostnames") == 0)
+ {
+ ctx->hostnames = 1;
+ return NGX_CONF_OK;
+ }
+
+ if (cf->args->nelts == 1
+ && ngx_strcmp(value[0].data, "volatile") == 0)
+ {
+ ctx->no_cacheable = 1;
+ return NGX_CONF_OK;
+ }
+
+ if (cf->args->nelts != 2) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid number of the map parameters");
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_strcmp(value[0].data, "include") == 0) {
+ return ngx_conf_include(cf, dummy, conf);
+ }
+
+ key = 0;
+
+ for (i = 0; i < value[1].len; i++) {
+ key = ngx_hash(key, value[1].data[i]);
+ }
+
+ key %= ctx->keys.hsize;
+
+ vp = ctx->values_hash[key].elts;
+
+ if (vp) {
+ for (i = 0; i < ctx->values_hash[key].nelts; i++) {
+
+ if (vp[i]->valid) {
+ data = vp[i]->data;
+ len = vp[i]->len;
+
+ } else {
+ cvp = (ngx_http_complex_value_t *) vp[i]->data;
+ data = cvp->value.data;
+ len = cvp->value.len;
+ }
+
+ if (value[1].len != len) {
+ continue;
+ }
+
+ if (ngx_strncmp(value[1].data, data, len) == 0) {
+ var = vp[i];
+ goto found;
+ }
+ }
+
+ } else {
+ if (ngx_array_init(&ctx->values_hash[key], cf->pool, 4,
+ sizeof(ngx_http_variable_value_t *))
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ var = ngx_palloc(ctx->keys.pool, sizeof(ngx_http_variable_value_t));
+ if (var == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ v.len = value[1].len;
+ v.data = ngx_pstrdup(ctx->keys.pool, &value[1]);
+ if (v.data == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = ctx->cf;
+ ccv.value = &v;
+ ccv.complex_value = &cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cv.lengths != NULL) {
+ cvp = ngx_palloc(ctx->keys.pool, sizeof(ngx_http_complex_value_t));
+ if (cvp == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *cvp = cv;
+
+ var->len = 0;
+ var->data = (u_char *) cvp;
+ var->valid = 0;
+
+ } else {
+ var->len = v.len;
+ var->data = v.data;
+ var->valid = 1;
+ }
+
+ var->no_cacheable = 0;
+ var->not_found = 0;
+
+ vp = ngx_array_push(&ctx->values_hash[key]);
+ if (vp == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *vp = var;
+
+found:
+
+ if (ngx_strcmp(value[0].data, "default") == 0) {
+
+ if (ctx->default_value) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "duplicate default map parameter");
+ return NGX_CONF_ERROR;
+ }
+
+ ctx->default_value = var;
+
+ return NGX_CONF_OK;
+ }
+
+#if (NGX_PCRE)
+
+ if (value[0].len && value[0].data[0] == '~') {
+ ngx_regex_compile_t rc;
+ ngx_http_map_regex_t *regex;
+ u_char errstr[NGX_MAX_CONF_ERRSTR];
+
+ regex = ngx_array_push(&ctx->regexes);
+ if (regex == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ value[0].len--;
+ value[0].data++;
+
+ ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+ if (value[0].data[0] == '*') {
+ value[0].len--;
+ value[0].data++;
+ rc.options = NGX_REGEX_CASELESS;
+ }
+
+ rc.pattern = value[0];
+ rc.err.len = NGX_MAX_CONF_ERRSTR;
+ rc.err.data = errstr;
+
+ regex->regex = ngx_http_regex_compile(ctx->cf, &rc);
+ if (regex->regex == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ regex->value = var;
+
+ return NGX_CONF_OK;
+ }
+
+#endif
+
+ if (value[0].len && value[0].data[0] == '\\') {
+ value[0].len--;
+ value[0].data++;
+ }
+
+ rv = ngx_hash_add_key(&ctx->keys, &value[0], var,
+ (ctx->hostnames) ? NGX_HASH_WILDCARD_KEY : 0);
+
+ if (rv == NGX_OK) {
+ return NGX_CONF_OK;
+ }
+
+ if (rv == NGX_DECLINED) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid hostname or wildcard \"%V\"", &value[0]);
+ }
+
+ if (rv == NGX_BUSY) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "conflicting parameter \"%V\"", &value[0]);
+ }
+
+ return NGX_CONF_ERROR;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_memcached_module.c b/app/nginx/src/http/modules/ngx_http_memcached_module.c
new file mode 100644
index 0000000..69f28fa
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_memcached_module.c
@@ -0,0 +1,723 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_http_upstream_conf_t upstream;
+ ngx_int_t index;
+ ngx_uint_t gzip_flag;
+} ngx_http_memcached_loc_conf_t;
+
+
+typedef struct {
+ size_t rest;
+ ngx_http_request_t *request;
+ ngx_str_t key;
+} ngx_http_memcached_ctx_t;
+
+
+static ngx_int_t ngx_http_memcached_create_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_memcached_reinit_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_memcached_process_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_memcached_filter_init(void *data);
+static ngx_int_t ngx_http_memcached_filter(void *data, ssize_t bytes);
+static void ngx_http_memcached_abort_request(ngx_http_request_t *r);
+static void ngx_http_memcached_finalize_request(ngx_http_request_t *r,
+ ngx_int_t rc);
+
+static void *ngx_http_memcached_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_memcached_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+
+static char *ngx_http_memcached_pass(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_conf_bitmask_t ngx_http_memcached_next_upstream_masks[] = {
+ { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
+ { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT },
+ { ngx_string("invalid_response"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER },
+ { ngx_string("not_found"), NGX_HTTP_UPSTREAM_FT_HTTP_404 },
+ { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t ngx_http_memcached_commands[] = {
+
+ { ngx_string("memcached_pass"),
+ NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
+ ngx_http_memcached_pass,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("memcached_bind"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+ ngx_http_upstream_bind_set_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_memcached_loc_conf_t, upstream.local),
+ NULL },
+
+ { ngx_string("memcached_connect_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_memcached_loc_conf_t, upstream.connect_timeout),
+ NULL },
+
+ { ngx_string("memcached_send_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_memcached_loc_conf_t, upstream.send_timeout),
+ NULL },
+
+ { ngx_string("memcached_buffer_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_memcached_loc_conf_t, upstream.buffer_size),
+ NULL },
+
+ { ngx_string("memcached_read_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_memcached_loc_conf_t, upstream.read_timeout),
+ NULL },
+
+ { ngx_string("memcached_next_upstream"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_memcached_loc_conf_t, upstream.next_upstream),
+ &ngx_http_memcached_next_upstream_masks },
+
+ { ngx_string("memcached_next_upstream_tries"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_memcached_loc_conf_t, upstream.next_upstream_tries),
+ NULL },
+
+ { ngx_string("memcached_next_upstream_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_memcached_loc_conf_t, upstream.next_upstream_timeout),
+ NULL },
+
+ { ngx_string("memcached_gzip_flag"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_memcached_loc_conf_t, gzip_flag),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_memcached_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_memcached_create_loc_conf, /* create location configuration */
+ ngx_http_memcached_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_memcached_module = {
+ NGX_MODULE_V1,
+ &ngx_http_memcached_module_ctx, /* module context */
+ ngx_http_memcached_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_http_memcached_key = ngx_string("memcached_key");
+
+
+#define NGX_HTTP_MEMCACHED_END (sizeof(ngx_http_memcached_end) - 1)
+static u_char ngx_http_memcached_end[] = CRLF "END" CRLF;
+
+
+static ngx_int_t
+ngx_http_memcached_handler(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_http_upstream_t *u;
+ ngx_http_memcached_ctx_t *ctx;
+ ngx_http_memcached_loc_conf_t *mlcf;
+
+ if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
+ return NGX_HTTP_NOT_ALLOWED;
+ }
+
+ rc = ngx_http_discard_request_body(r);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ if (ngx_http_set_content_type(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_http_upstream_create(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ u = r->upstream;
+
+ ngx_str_set(&u->schema, "memcached://");
+ u->output.tag = (ngx_buf_tag_t) &ngx_http_memcached_module;
+
+ mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module);
+
+ u->conf = &mlcf->upstream;
+
+ u->create_request = ngx_http_memcached_create_request;
+ u->reinit_request = ngx_http_memcached_reinit_request;
+ u->process_header = ngx_http_memcached_process_header;
+ u->abort_request = ngx_http_memcached_abort_request;
+ u->finalize_request = ngx_http_memcached_finalize_request;
+
+ ctx = ngx_palloc(r->pool, sizeof(ngx_http_memcached_ctx_t));
+ if (ctx == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ctx->request = r;
+
+ ngx_http_set_ctx(r, ctx, ngx_http_memcached_module);
+
+ u->input_filter_init = ngx_http_memcached_filter_init;
+ u->input_filter = ngx_http_memcached_filter;
+ u->input_filter_ctx = ctx;
+
+ r->main->count++;
+
+ ngx_http_upstream_init(r);
+
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_memcached_create_request(ngx_http_request_t *r)
+{
+ size_t len;
+ uintptr_t escape;
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+ ngx_http_memcached_ctx_t *ctx;
+ ngx_http_variable_value_t *vv;
+ ngx_http_memcached_loc_conf_t *mlcf;
+
+ mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module);
+
+ vv = ngx_http_get_indexed_variable(r, mlcf->index);
+
+ if (vv == NULL || vv->not_found || vv->len == 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "the \"$memcached_key\" variable is not set");
+ return NGX_ERROR;
+ }
+
+ escape = 2 * ngx_escape_uri(NULL, vv->data, vv->len, NGX_ESCAPE_MEMCACHED);
+
+ len = sizeof("get ") - 1 + vv->len + escape + sizeof(CRLF) - 1;
+
+ b = ngx_create_temp_buf(r->pool, len);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+
+ r->upstream->request_bufs = cl;
+
+ *b->last++ = 'g'; *b->last++ = 'e'; *b->last++ = 't'; *b->last++ = ' ';
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_memcached_module);
+
+ ctx->key.data = b->last;
+
+ if (escape == 0) {
+ b->last = ngx_copy(b->last, vv->data, vv->len);
+
+ } else {
+ b->last = (u_char *) ngx_escape_uri(b->last, vv->data, vv->len,
+ NGX_ESCAPE_MEMCACHED);
+ }
+
+ ctx->key.len = b->last - ctx->key.data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http memcached request: \"%V\"", &ctx->key);
+
+ *b->last++ = CR; *b->last++ = LF;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_memcached_reinit_request(ngx_http_request_t *r)
+{
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_memcached_process_header(ngx_http_request_t *r)
+{
+ u_char *p, *start;
+ ngx_str_t line;
+ ngx_uint_t flags;
+ ngx_table_elt_t *h;
+ ngx_http_upstream_t *u;
+ ngx_http_memcached_ctx_t *ctx;
+ ngx_http_memcached_loc_conf_t *mlcf;
+
+ u = r->upstream;
+
+ for (p = u->buffer.pos; p < u->buffer.last; p++) {
+ if (*p == LF) {
+ goto found;
+ }
+ }
+
+ return NGX_AGAIN;
+
+found:
+
+ line.data = u->buffer.pos;
+ line.len = p - u->buffer.pos;
+
+ if (line.len == 0 || *(p - 1) != CR) {
+ goto no_valid;
+ }
+
+ *p = '\0';
+ line.len--;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "memcached: \"%V\"", &line);
+
+ p = u->buffer.pos;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_memcached_module);
+ mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module);
+
+ if (ngx_strncmp(p, "VALUE ", sizeof("VALUE ") - 1) == 0) {
+
+ p += sizeof("VALUE ") - 1;
+
+ if (ngx_strncmp(p, ctx->key.data, ctx->key.len) != 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "memcached sent invalid key in response \"%V\" "
+ "for key \"%V\"",
+ &line, &ctx->key);
+
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+ }
+
+ p += ctx->key.len;
+
+ if (*p++ != ' ') {
+ goto no_valid;
+ }
+
+ /* flags */
+
+ start = p;
+
+ while (*p) {
+ if (*p++ == ' ') {
+ if (mlcf->gzip_flag) {
+ goto flags;
+ } else {
+ goto length;
+ }
+ }
+ }
+
+ goto no_valid;
+
+ flags:
+
+ flags = ngx_atoi(start, p - start - 1);
+
+ if (flags == (ngx_uint_t) NGX_ERROR) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "memcached sent invalid flags in response \"%V\" "
+ "for key \"%V\"",
+ &line, &ctx->key);
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+ }
+
+ if (flags & mlcf->gzip_flag) {
+ h = ngx_list_push(&r->headers_out.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->hash = 1;
+ ngx_str_set(&h->key, "Content-Encoding");
+ ngx_str_set(&h->value, "gzip");
+ r->headers_out.content_encoding = h;
+ }
+
+ length:
+
+ start = p;
+ p = line.data + line.len;
+
+ u->headers_in.content_length_n = ngx_atoof(start, p - start);
+ if (u->headers_in.content_length_n == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "memcached sent invalid length in response \"%V\" "
+ "for key \"%V\"",
+ &line, &ctx->key);
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+ }
+
+ u->headers_in.status_n = 200;
+ u->state->status = 200;
+ u->buffer.pos = p + sizeof(CRLF) - 1;
+
+ return NGX_OK;
+ }
+
+ if (ngx_strcmp(p, "END\x0d") == 0) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "key: \"%V\" was not found by memcached", &ctx->key);
+
+ u->headers_in.content_length_n = 0;
+ u->headers_in.status_n = 404;
+ u->state->status = 404;
+ u->buffer.pos = p + sizeof("END" CRLF) - 1;
+ u->keepalive = 1;
+
+ return NGX_OK;
+ }
+
+no_valid:
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "memcached sent invalid response: \"%V\"", &line);
+
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+}
+
+
+static ngx_int_t
+ngx_http_memcached_filter_init(void *data)
+{
+ ngx_http_memcached_ctx_t *ctx = data;
+
+ ngx_http_upstream_t *u;
+
+ u = ctx->request->upstream;
+
+ if (u->headers_in.status_n != 404) {
+ u->length = u->headers_in.content_length_n + NGX_HTTP_MEMCACHED_END;
+ ctx->rest = NGX_HTTP_MEMCACHED_END;
+
+ } else {
+ u->length = 0;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_memcached_filter(void *data, ssize_t bytes)
+{
+ ngx_http_memcached_ctx_t *ctx = data;
+
+ u_char *last;
+ ngx_buf_t *b;
+ ngx_chain_t *cl, **ll;
+ ngx_http_upstream_t *u;
+
+ u = ctx->request->upstream;
+ b = &u->buffer;
+
+ if (u->length == (ssize_t) ctx->rest) {
+
+ if (ngx_strncmp(b->last,
+ ngx_http_memcached_end + NGX_HTTP_MEMCACHED_END - ctx->rest,
+ bytes)
+ != 0)
+ {
+ ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0,
+ "memcached sent invalid trailer");
+
+ u->length = 0;
+ ctx->rest = 0;
+
+ return NGX_OK;
+ }
+
+ u->length -= bytes;
+ ctx->rest -= bytes;
+
+ if (u->length == 0) {
+ u->keepalive = 1;
+ }
+
+ return NGX_OK;
+ }
+
+ for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) {
+ ll = &cl->next;
+ }
+
+ cl = ngx_chain_get_free_buf(ctx->request->pool, &u->free_bufs);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf->flush = 1;
+ cl->buf->memory = 1;
+
+ *ll = cl;
+
+ last = b->last;
+ cl->buf->pos = last;
+ b->last += bytes;
+ cl->buf->last = b->last;
+ cl->buf->tag = u->output.tag;
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
+ "memcached filter bytes:%z size:%z length:%O rest:%z",
+ bytes, b->last - b->pos, u->length, ctx->rest);
+
+ if (bytes <= (ssize_t) (u->length - NGX_HTTP_MEMCACHED_END)) {
+ u->length -= bytes;
+ return NGX_OK;
+ }
+
+ last += (size_t) (u->length - NGX_HTTP_MEMCACHED_END);
+
+ if (ngx_strncmp(last, ngx_http_memcached_end, b->last - last) != 0) {
+ ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0,
+ "memcached sent invalid trailer");
+
+ b->last = last;
+ cl->buf->last = last;
+ u->length = 0;
+ ctx->rest = 0;
+
+ return NGX_OK;
+ }
+
+ ctx->rest -= b->last - last;
+ b->last = last;
+ cl->buf->last = last;
+ u->length = ctx->rest;
+
+ if (u->length == 0) {
+ u->keepalive = 1;
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_memcached_abort_request(ngx_http_request_t *r)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "abort http memcached request");
+ return;
+}
+
+
+static void
+ngx_http_memcached_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "finalize http memcached request");
+ return;
+}
+
+
+static void *
+ngx_http_memcached_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_memcached_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_memcached_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->upstream.bufs.num = 0;
+ * conf->upstream.next_upstream = 0;
+ * conf->upstream.temp_path = NULL;
+ * conf->upstream.uri = { 0, NULL };
+ * conf->upstream.location = NULL;
+ */
+
+ conf->upstream.local = NGX_CONF_UNSET_PTR;
+ conf->upstream.next_upstream_tries = NGX_CONF_UNSET_UINT;
+ conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC;
+
+ conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE;
+
+ /* the hardcoded values */
+ conf->upstream.cyclic_temp_file = 0;
+ conf->upstream.buffering = 0;
+ conf->upstream.ignore_client_abort = 0;
+ conf->upstream.send_lowat = 0;
+ conf->upstream.bufs.num = 0;
+ conf->upstream.busy_buffers_size = 0;
+ conf->upstream.max_temp_file_size = 0;
+ conf->upstream.temp_file_write_size = 0;
+ conf->upstream.intercept_errors = 1;
+ conf->upstream.intercept_404 = 1;
+ conf->upstream.pass_request_headers = 0;
+ conf->upstream.pass_request_body = 0;
+ conf->upstream.force_ranges = 1;
+
+ conf->index = NGX_CONF_UNSET;
+ conf->gzip_flag = NGX_CONF_UNSET_UINT;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_memcached_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_memcached_loc_conf_t *prev = parent;
+ ngx_http_memcached_loc_conf_t *conf = child;
+
+ ngx_conf_merge_ptr_value(conf->upstream.local,
+ prev->upstream.local, NULL);
+
+ ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries,
+ prev->upstream.next_upstream_tries, 0);
+
+ ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
+ prev->upstream.connect_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->upstream.send_timeout,
+ prev->upstream.send_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->upstream.read_timeout,
+ prev->upstream.read_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->upstream.next_upstream_timeout,
+ prev->upstream.next_upstream_timeout, 0);
+
+ ngx_conf_merge_size_value(conf->upstream.buffer_size,
+ prev->upstream.buffer_size,
+ (size_t) ngx_pagesize);
+
+ ngx_conf_merge_bitmask_value(conf->upstream.next_upstream,
+ prev->upstream.next_upstream,
+ (NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_ERROR
+ |NGX_HTTP_UPSTREAM_FT_TIMEOUT));
+
+ if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) {
+ conf->upstream.next_upstream = NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_OFF;
+ }
+
+ if (conf->upstream.upstream == NULL) {
+ conf->upstream.upstream = prev->upstream.upstream;
+ }
+
+ if (conf->index == NGX_CONF_UNSET) {
+ conf->index = prev->index;
+ }
+
+ ngx_conf_merge_uint_value(conf->gzip_flag, prev->gzip_flag, 0);
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_memcached_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_memcached_loc_conf_t *mlcf = conf;
+
+ ngx_str_t *value;
+ ngx_url_t u;
+ ngx_http_core_loc_conf_t *clcf;
+
+ if (mlcf->upstream.upstream) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.url = value[1];
+ u.no_resolve = 1;
+
+ mlcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
+ if (mlcf->upstream.upstream == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+ clcf->handler = ngx_http_memcached_handler;
+
+ if (clcf->name.data[clcf->name.len - 1] == '/') {
+ clcf->auto_redirect = 1;
+ }
+
+ mlcf->index = ngx_http_get_variable_index(cf, &ngx_http_memcached_key);
+
+ if (mlcf->index == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_mp4_module.c b/app/nginx/src/http/modules/ngx_http_mp4_module.c
new file mode 100644
index 0000000..f3c0fdd
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_mp4_module.c
@@ -0,0 +1,3548 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_MP4_TRAK_ATOM 0
+#define NGX_HTTP_MP4_TKHD_ATOM 1
+#define NGX_HTTP_MP4_MDIA_ATOM 2
+#define NGX_HTTP_MP4_MDHD_ATOM 3
+#define NGX_HTTP_MP4_HDLR_ATOM 4
+#define NGX_HTTP_MP4_MINF_ATOM 5
+#define NGX_HTTP_MP4_VMHD_ATOM 6
+#define NGX_HTTP_MP4_SMHD_ATOM 7
+#define NGX_HTTP_MP4_DINF_ATOM 8
+#define NGX_HTTP_MP4_STBL_ATOM 9
+#define NGX_HTTP_MP4_STSD_ATOM 10
+#define NGX_HTTP_MP4_STTS_ATOM 11
+#define NGX_HTTP_MP4_STTS_DATA 12
+#define NGX_HTTP_MP4_STSS_ATOM 13
+#define NGX_HTTP_MP4_STSS_DATA 14
+#define NGX_HTTP_MP4_CTTS_ATOM 15
+#define NGX_HTTP_MP4_CTTS_DATA 16
+#define NGX_HTTP_MP4_STSC_ATOM 17
+#define NGX_HTTP_MP4_STSC_START 18
+#define NGX_HTTP_MP4_STSC_DATA 19
+#define NGX_HTTP_MP4_STSC_END 20
+#define NGX_HTTP_MP4_STSZ_ATOM 21
+#define NGX_HTTP_MP4_STSZ_DATA 22
+#define NGX_HTTP_MP4_STCO_ATOM 23
+#define NGX_HTTP_MP4_STCO_DATA 24
+#define NGX_HTTP_MP4_CO64_ATOM 25
+#define NGX_HTTP_MP4_CO64_DATA 26
+
+#define NGX_HTTP_MP4_LAST_ATOM NGX_HTTP_MP4_CO64_DATA
+
+
+typedef struct {
+ size_t buffer_size;
+ size_t max_buffer_size;
+} ngx_http_mp4_conf_t;
+
+
+typedef struct {
+ u_char chunk[4];
+ u_char samples[4];
+ u_char id[4];
+} ngx_mp4_stsc_entry_t;
+
+
+typedef struct {
+ uint32_t timescale;
+ uint32_t time_to_sample_entries;
+ uint32_t sample_to_chunk_entries;
+ uint32_t sync_samples_entries;
+ uint32_t composition_offset_entries;
+ uint32_t sample_sizes_entries;
+ uint32_t chunks;
+
+ ngx_uint_t start_sample;
+ ngx_uint_t end_sample;
+ ngx_uint_t start_chunk;
+ ngx_uint_t end_chunk;
+ ngx_uint_t start_chunk_samples;
+ ngx_uint_t end_chunk_samples;
+ uint64_t start_chunk_samples_size;
+ uint64_t end_chunk_samples_size;
+ off_t start_offset;
+ off_t end_offset;
+
+ size_t tkhd_size;
+ size_t mdhd_size;
+ size_t hdlr_size;
+ size_t vmhd_size;
+ size_t smhd_size;
+ size_t dinf_size;
+ size_t size;
+
+ ngx_chain_t out[NGX_HTTP_MP4_LAST_ATOM + 1];
+
+ ngx_buf_t trak_atom_buf;
+ ngx_buf_t tkhd_atom_buf;
+ ngx_buf_t mdia_atom_buf;
+ ngx_buf_t mdhd_atom_buf;
+ ngx_buf_t hdlr_atom_buf;
+ ngx_buf_t minf_atom_buf;
+ ngx_buf_t vmhd_atom_buf;
+ ngx_buf_t smhd_atom_buf;
+ ngx_buf_t dinf_atom_buf;
+ ngx_buf_t stbl_atom_buf;
+ ngx_buf_t stsd_atom_buf;
+ ngx_buf_t stts_atom_buf;
+ ngx_buf_t stts_data_buf;
+ ngx_buf_t stss_atom_buf;
+ ngx_buf_t stss_data_buf;
+ ngx_buf_t ctts_atom_buf;
+ ngx_buf_t ctts_data_buf;
+ ngx_buf_t stsc_atom_buf;
+ ngx_buf_t stsc_start_chunk_buf;
+ ngx_buf_t stsc_end_chunk_buf;
+ ngx_buf_t stsc_data_buf;
+ ngx_buf_t stsz_atom_buf;
+ ngx_buf_t stsz_data_buf;
+ ngx_buf_t stco_atom_buf;
+ ngx_buf_t stco_data_buf;
+ ngx_buf_t co64_atom_buf;
+ ngx_buf_t co64_data_buf;
+
+ ngx_mp4_stsc_entry_t stsc_start_chunk_entry;
+ ngx_mp4_stsc_entry_t stsc_end_chunk_entry;
+} ngx_http_mp4_trak_t;
+
+
+typedef struct {
+ ngx_file_t file;
+
+ u_char *buffer;
+ u_char *buffer_start;
+ u_char *buffer_pos;
+ u_char *buffer_end;
+ size_t buffer_size;
+
+ off_t offset;
+ off_t end;
+ off_t content_length;
+ ngx_uint_t start;
+ ngx_uint_t length;
+ uint32_t timescale;
+ ngx_http_request_t *request;
+ ngx_array_t trak;
+ ngx_http_mp4_trak_t traks[2];
+
+ size_t ftyp_size;
+ size_t moov_size;
+
+ ngx_chain_t *out;
+ ngx_chain_t ftyp_atom;
+ ngx_chain_t moov_atom;
+ ngx_chain_t mvhd_atom;
+ ngx_chain_t mdat_atom;
+ ngx_chain_t mdat_data;
+
+ ngx_buf_t ftyp_atom_buf;
+ ngx_buf_t moov_atom_buf;
+ ngx_buf_t mvhd_atom_buf;
+ ngx_buf_t mdat_atom_buf;
+ ngx_buf_t mdat_data_buf;
+
+ u_char moov_atom_header[8];
+ u_char mdat_atom_header[16];
+} ngx_http_mp4_file_t;
+
+
+typedef struct {
+ char *name;
+ ngx_int_t (*handler)(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+} ngx_http_mp4_atom_handler_t;
+
+
+#define ngx_mp4_atom_header(mp4) (mp4->buffer_pos - 8)
+#define ngx_mp4_atom_data(mp4) mp4->buffer_pos
+#define ngx_mp4_atom_data_size(t) (uint64_t) (sizeof(t) - 8)
+
+
+#define ngx_mp4_atom_next(mp4, n) \
+ mp4->buffer_pos += (size_t) n; \
+ mp4->offset += n
+
+
+#define ngx_mp4_set_atom_name(p, n1, n2, n3, n4) \
+ ((u_char *) (p))[4] = n1; \
+ ((u_char *) (p))[5] = n2; \
+ ((u_char *) (p))[6] = n3; \
+ ((u_char *) (p))[7] = n4
+
+#define ngx_mp4_get_32value(p) \
+ ( ((uint32_t) ((u_char *) (p))[0] << 24) \
+ + ( ((u_char *) (p))[1] << 16) \
+ + ( ((u_char *) (p))[2] << 8) \
+ + ( ((u_char *) (p))[3]) )
+
+#define ngx_mp4_set_32value(p, n) \
+ ((u_char *) (p))[0] = (u_char) ((n) >> 24); \
+ ((u_char *) (p))[1] = (u_char) ((n) >> 16); \
+ ((u_char *) (p))[2] = (u_char) ((n) >> 8); \
+ ((u_char *) (p))[3] = (u_char) (n)
+
+#define ngx_mp4_get_64value(p) \
+ ( ((uint64_t) ((u_char *) (p))[0] << 56) \
+ + ((uint64_t) ((u_char *) (p))[1] << 48) \
+ + ((uint64_t) ((u_char *) (p))[2] << 40) \
+ + ((uint64_t) ((u_char *) (p))[3] << 32) \
+ + ((uint64_t) ((u_char *) (p))[4] << 24) \
+ + ( ((u_char *) (p))[5] << 16) \
+ + ( ((u_char *) (p))[6] << 8) \
+ + ( ((u_char *) (p))[7]) )
+
+#define ngx_mp4_set_64value(p, n) \
+ ((u_char *) (p))[0] = (u_char) ((uint64_t) (n) >> 56); \
+ ((u_char *) (p))[1] = (u_char) ((uint64_t) (n) >> 48); \
+ ((u_char *) (p))[2] = (u_char) ((uint64_t) (n) >> 40); \
+ ((u_char *) (p))[3] = (u_char) ((uint64_t) (n) >> 32); \
+ ((u_char *) (p))[4] = (u_char) ( (n) >> 24); \
+ ((u_char *) (p))[5] = (u_char) ( (n) >> 16); \
+ ((u_char *) (p))[6] = (u_char) ( (n) >> 8); \
+ ((u_char *) (p))[7] = (u_char) (n)
+
+#define ngx_mp4_last_trak(mp4) \
+ &((ngx_http_mp4_trak_t *) mp4->trak.elts)[mp4->trak.nelts - 1]
+
+
+static ngx_int_t ngx_http_mp4_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_mp4_atofp(u_char *line, size_t n, size_t point);
+
+static ngx_int_t ngx_http_mp4_process(ngx_http_mp4_file_t *mp4);
+static ngx_int_t ngx_http_mp4_read_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_atom_handler_t *atom, uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read(ngx_http_mp4_file_t *mp4, size_t size);
+static ngx_int_t ngx_http_mp4_read_ftyp_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_moov_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_mdat_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static size_t ngx_http_mp4_update_mdat_atom(ngx_http_mp4_file_t *mp4,
+ off_t start_offset, off_t end_offset);
+static ngx_int_t ngx_http_mp4_read_mvhd_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_trak_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static void ngx_http_mp4_update_trak_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak);
+static ngx_int_t ngx_http_mp4_read_cmov_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_tkhd_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_mdia_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static void ngx_http_mp4_update_mdia_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak);
+static ngx_int_t ngx_http_mp4_read_mdhd_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_hdlr_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_minf_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static void ngx_http_mp4_update_minf_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak);
+static ngx_int_t ngx_http_mp4_read_dinf_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_vmhd_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_smhd_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_stbl_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static void ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak);
+static ngx_int_t ngx_http_mp4_read_stsd_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_read_stts_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_update_stts_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak);
+static ngx_int_t ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak, ngx_uint_t start);
+static ngx_int_t ngx_http_mp4_read_stss_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_update_stss_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak);
+static void ngx_http_mp4_crop_stss_data(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak, ngx_uint_t start);
+static ngx_int_t ngx_http_mp4_read_ctts_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static void ngx_http_mp4_update_ctts_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak);
+static void ngx_http_mp4_crop_ctts_data(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak, ngx_uint_t start);
+static ngx_int_t ngx_http_mp4_read_stsc_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_update_stsc_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak);
+static ngx_int_t ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak, ngx_uint_t start);
+static ngx_int_t ngx_http_mp4_read_stsz_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_update_stsz_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak);
+static ngx_int_t ngx_http_mp4_read_stco_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_update_stco_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak);
+static void ngx_http_mp4_adjust_stco_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak, int32_t adjustment);
+static ngx_int_t ngx_http_mp4_read_co64_atom(ngx_http_mp4_file_t *mp4,
+ uint64_t atom_data_size);
+static ngx_int_t ngx_http_mp4_update_co64_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak);
+static void ngx_http_mp4_adjust_co64_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak, off_t adjustment);
+
+static char *ngx_http_mp4(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static void *ngx_http_mp4_create_conf(ngx_conf_t *cf);
+static char *ngx_http_mp4_merge_conf(ngx_conf_t *cf, void *parent, void *child);
+
+
+static ngx_command_t ngx_http_mp4_commands[] = {
+
+ { ngx_string("mp4"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
+ ngx_http_mp4,
+ 0,
+ 0,
+ NULL },
+
+ { ngx_string("mp4_buffer_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_mp4_conf_t, buffer_size),
+ NULL },
+
+ { ngx_string("mp4_max_buffer_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_mp4_conf_t, max_buffer_size),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_mp4_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_mp4_create_conf, /* create location configuration */
+ ngx_http_mp4_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_mp4_module = {
+ NGX_MODULE_V1,
+ &ngx_http_mp4_module_ctx, /* module context */
+ ngx_http_mp4_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_mp4_atom_handler_t ngx_http_mp4_atoms[] = {
+ { "ftyp", ngx_http_mp4_read_ftyp_atom },
+ { "moov", ngx_http_mp4_read_moov_atom },
+ { "mdat", ngx_http_mp4_read_mdat_atom },
+ { NULL, NULL }
+};
+
+static ngx_http_mp4_atom_handler_t ngx_http_mp4_moov_atoms[] = {
+ { "mvhd", ngx_http_mp4_read_mvhd_atom },
+ { "trak", ngx_http_mp4_read_trak_atom },
+ { "cmov", ngx_http_mp4_read_cmov_atom },
+ { NULL, NULL }
+};
+
+static ngx_http_mp4_atom_handler_t ngx_http_mp4_trak_atoms[] = {
+ { "tkhd", ngx_http_mp4_read_tkhd_atom },
+ { "mdia", ngx_http_mp4_read_mdia_atom },
+ { NULL, NULL }
+};
+
+static ngx_http_mp4_atom_handler_t ngx_http_mp4_mdia_atoms[] = {
+ { "mdhd", ngx_http_mp4_read_mdhd_atom },
+ { "hdlr", ngx_http_mp4_read_hdlr_atom },
+ { "minf", ngx_http_mp4_read_minf_atom },
+ { NULL, NULL }
+};
+
+static ngx_http_mp4_atom_handler_t ngx_http_mp4_minf_atoms[] = {
+ { "vmhd", ngx_http_mp4_read_vmhd_atom },
+ { "smhd", ngx_http_mp4_read_smhd_atom },
+ { "dinf", ngx_http_mp4_read_dinf_atom },
+ { "stbl", ngx_http_mp4_read_stbl_atom },
+ { NULL, NULL }
+};
+
+static ngx_http_mp4_atom_handler_t ngx_http_mp4_stbl_atoms[] = {
+ { "stsd", ngx_http_mp4_read_stsd_atom },
+ { "stts", ngx_http_mp4_read_stts_atom },
+ { "stss", ngx_http_mp4_read_stss_atom },
+ { "ctts", ngx_http_mp4_read_ctts_atom },
+ { "stsc", ngx_http_mp4_read_stsc_atom },
+ { "stsz", ngx_http_mp4_read_stsz_atom },
+ { "stco", ngx_http_mp4_read_stco_atom },
+ { "co64", ngx_http_mp4_read_co64_atom },
+ { NULL, NULL }
+};
+
+
+static ngx_int_t
+ngx_http_mp4_handler(ngx_http_request_t *r)
+{
+ u_char *last;
+ size_t root;
+ ngx_int_t rc, start, end;
+ ngx_uint_t level, length;
+ ngx_str_t path, value;
+ ngx_log_t *log;
+ ngx_buf_t *b;
+ ngx_chain_t out;
+ ngx_http_mp4_file_t *mp4;
+ ngx_open_file_info_t of;
+ ngx_http_core_loc_conf_t *clcf;
+
+ if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
+ return NGX_HTTP_NOT_ALLOWED;
+ }
+
+ if (r->uri.data[r->uri.len - 1] == '/') {
+ return NGX_DECLINED;
+ }
+
+ rc = ngx_http_discard_request_body(r);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ last = ngx_http_map_uri_to_path(r, &path, &root, 0);
+ if (last == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ log = r->connection->log;
+
+ path.len = last - path.data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "http mp4 filename: \"%V\"", &path);
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+ of.read_ahead = clcf->read_ahead;
+ of.directio = NGX_MAX_OFF_T_VALUE;
+ of.valid = clcf->open_file_cache_valid;
+ of.min_uses = clcf->open_file_cache_min_uses;
+ of.errors = clcf->open_file_cache_errors;
+ of.events = clcf->open_file_cache_events;
+
+ if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+ != NGX_OK)
+ {
+ switch (of.err) {
+
+ case 0:
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+
+ case NGX_ENOENT:
+ case NGX_ENOTDIR:
+ case NGX_ENAMETOOLONG:
+
+ level = NGX_LOG_ERR;
+ rc = NGX_HTTP_NOT_FOUND;
+ break;
+
+ case NGX_EACCES:
+#if (NGX_HAVE_OPENAT)
+ case NGX_EMLINK:
+ case NGX_ELOOP:
+#endif
+
+ level = NGX_LOG_ERR;
+ rc = NGX_HTTP_FORBIDDEN;
+ break;
+
+ default:
+
+ level = NGX_LOG_CRIT;
+ rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ }
+
+ if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
+ ngx_log_error(level, log, of.err,
+ "%s \"%s\" failed", of.failed, path.data);
+ }
+
+ return rc;
+ }
+
+ if (!of.is_file) {
+
+ if (ngx_close_file(of.fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", path.data);
+ }
+
+ return NGX_DECLINED;
+ }
+
+ r->root_tested = !r->error_page;
+ r->allow_ranges = 1;
+
+ start = -1;
+ length = 0;
+ r->headers_out.content_length_n = of.size;
+ mp4 = NULL;
+ b = NULL;
+
+ if (r->args.len) {
+
+ if (ngx_http_arg(r, (u_char *) "start", 5, &value) == NGX_OK) {
+
+ /*
+ * A Flash player may send start value with a lot of digits
+ * after dot so a custom function is used instead of ngx_atofp().
+ */
+
+ start = ngx_http_mp4_atofp(value.data, value.len, 3);
+ }
+
+ if (ngx_http_arg(r, (u_char *) "end", 3, &value) == NGX_OK) {
+
+ end = ngx_http_mp4_atofp(value.data, value.len, 3);
+
+ if (end > 0) {
+ if (start < 0) {
+ start = 0;
+ }
+
+ if (end > start) {
+ length = end - start;
+ }
+ }
+ }
+ }
+
+ if (start >= 0) {
+ r->single_range = 1;
+
+ mp4 = ngx_pcalloc(r->pool, sizeof(ngx_http_mp4_file_t));
+ if (mp4 == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ mp4->file.fd = of.fd;
+ mp4->file.name = path;
+ mp4->file.log = r->connection->log;
+ mp4->end = of.size;
+ mp4->start = (ngx_uint_t) start;
+ mp4->length = length;
+ mp4->request = r;
+
+ switch (ngx_http_mp4_process(mp4)) {
+
+ case NGX_DECLINED:
+ if (mp4->buffer) {
+ ngx_pfree(r->pool, mp4->buffer);
+ }
+
+ ngx_pfree(r->pool, mp4);
+ mp4 = NULL;
+
+ break;
+
+ case NGX_OK:
+ r->headers_out.content_length_n = mp4->content_length;
+ break;
+
+ default: /* NGX_ERROR */
+ if (mp4->buffer) {
+ ngx_pfree(r->pool, mp4->buffer);
+ }
+
+ ngx_pfree(r->pool, mp4);
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ log->action = "sending mp4 to client";
+
+ if (clcf->directio <= of.size) {
+
+ /*
+ * DIRECTIO is set on transfer only
+ * to allow kernel to cache "moov" atom
+ */
+
+ if (ngx_directio_on(of.fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ ngx_directio_on_n " \"%s\" failed", path.data);
+ }
+
+ of.is_directio = 1;
+
+ if (mp4) {
+ mp4->file.directio = 1;
+ }
+ }
+
+ r->headers_out.status = NGX_HTTP_OK;
+ r->headers_out.last_modified_time = of.mtime;
+
+ if (ngx_http_set_etag(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_http_set_content_type(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (mp4 == NULL) {
+ b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
+ if (b == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
+ if (b->file == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ return rc;
+ }
+
+ if (mp4) {
+ return ngx_http_output_filter(r, mp4->out);
+ }
+
+ b->file_pos = 0;
+ b->file_last = of.size;
+
+ b->in_file = b->file_last ? 1 : 0;
+ b->last_buf = (r == r->main) ? 1 : 0;
+ b->last_in_chain = 1;
+
+ b->file->fd = of.fd;
+ b->file->name = path;
+ b->file->log = log;
+ b->file->directio = of.is_directio;
+
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_output_filter(r, &out);
+}
+
+
+static ngx_int_t
+ngx_http_mp4_atofp(u_char *line, size_t n, size_t point)
+{
+ ngx_int_t value, cutoff, cutlim;
+ ngx_uint_t dot;
+
+ /* same as ngx_atofp(), but allows additional digits */
+
+ if (n == 0) {
+ return NGX_ERROR;
+ }
+
+ cutoff = NGX_MAX_INT_T_VALUE / 10;
+ cutlim = NGX_MAX_INT_T_VALUE % 10;
+
+ dot = 0;
+
+ for (value = 0; n--; line++) {
+
+ if (*line == '.') {
+ if (dot) {
+ return NGX_ERROR;
+ }
+
+ dot = 1;
+ continue;
+ }
+
+ if (*line < '0' || *line > '9') {
+ return NGX_ERROR;
+ }
+
+ if (point == 0) {
+ continue;
+ }
+
+ if (value >= cutoff && (value > cutoff || *line - '0' > cutlim)) {
+ return NGX_ERROR;
+ }
+
+ value = value * 10 + (*line - '0');
+ point -= dot;
+ }
+
+ while (point--) {
+ if (value > cutoff) {
+ return NGX_ERROR;
+ }
+
+ value = value * 10;
+ }
+
+ return value;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_process(ngx_http_mp4_file_t *mp4)
+{
+ off_t start_offset, end_offset, adjustment;
+ ngx_int_t rc;
+ ngx_uint_t i, j;
+ ngx_chain_t **prev;
+ ngx_http_mp4_trak_t *trak;
+ ngx_http_mp4_conf_t *conf;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 start:%ui, length:%ui", mp4->start, mp4->length);
+
+ conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module);
+
+ mp4->buffer_size = conf->buffer_size;
+
+ rc = ngx_http_mp4_read_atom(mp4, ngx_http_mp4_atoms, mp4->end);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ if (mp4->trak.nelts == 0) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "no mp4 trak atoms were found in \"%s\"",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ if (mp4->mdat_atom.buf == NULL) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "no mp4 mdat atom was found in \"%s\"",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ prev = &mp4->out;
+
+ if (mp4->ftyp_atom.buf) {
+ *prev = &mp4->ftyp_atom;
+ prev = &mp4->ftyp_atom.next;
+ }
+
+ *prev = &mp4->moov_atom;
+ prev = &mp4->moov_atom.next;
+
+ if (mp4->mvhd_atom.buf) {
+ mp4->moov_size += mp4->mvhd_atom_buf.last - mp4->mvhd_atom_buf.pos;
+ *prev = &mp4->mvhd_atom;
+ prev = &mp4->mvhd_atom.next;
+ }
+
+ start_offset = mp4->end;
+ end_offset = 0;
+ trak = mp4->trak.elts;
+
+ for (i = 0; i < mp4->trak.nelts; i++) {
+
+ if (ngx_http_mp4_update_stts_atom(mp4, &trak[i]) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_http_mp4_update_stss_atom(mp4, &trak[i]) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_mp4_update_ctts_atom(mp4, &trak[i]);
+
+ if (ngx_http_mp4_update_stsc_atom(mp4, &trak[i]) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_http_mp4_update_stsz_atom(mp4, &trak[i]) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (trak[i].out[NGX_HTTP_MP4_CO64_DATA].buf) {
+ if (ngx_http_mp4_update_co64_atom(mp4, &trak[i]) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ } else {
+ if (ngx_http_mp4_update_stco_atom(mp4, &trak[i]) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ ngx_http_mp4_update_stbl_atom(mp4, &trak[i]);
+ ngx_http_mp4_update_minf_atom(mp4, &trak[i]);
+ trak[i].size += trak[i].mdhd_size;
+ trak[i].size += trak[i].hdlr_size;
+ ngx_http_mp4_update_mdia_atom(mp4, &trak[i]);
+ trak[i].size += trak[i].tkhd_size;
+ ngx_http_mp4_update_trak_atom(mp4, &trak[i]);
+
+ mp4->moov_size += trak[i].size;
+
+ if (start_offset > trak[i].start_offset) {
+ start_offset = trak[i].start_offset;
+ }
+
+ if (end_offset < trak[i].end_offset) {
+ end_offset = trak[i].end_offset;
+ }
+
+ *prev = &trak[i].out[NGX_HTTP_MP4_TRAK_ATOM];
+ prev = &trak[i].out[NGX_HTTP_MP4_TRAK_ATOM].next;
+
+ for (j = 0; j < NGX_HTTP_MP4_LAST_ATOM + 1; j++) {
+ if (trak[i].out[j].buf) {
+ *prev = &trak[i].out[j];
+ prev = &trak[i].out[j].next;
+ }
+ }
+ }
+
+ if (end_offset < start_offset) {
+ end_offset = start_offset;
+ }
+
+ mp4->moov_size += 8;
+
+ ngx_mp4_set_32value(mp4->moov_atom_header, mp4->moov_size);
+ ngx_mp4_set_atom_name(mp4->moov_atom_header, 'm', 'o', 'o', 'v');
+ mp4->content_length += mp4->moov_size;
+
+ *prev = &mp4->mdat_atom;
+
+ if (start_offset > mp4->mdat_data.buf->file_last) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "start time is out mp4 mdat atom in \"%s\"",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ adjustment = mp4->ftyp_size + mp4->moov_size
+ + ngx_http_mp4_update_mdat_atom(mp4, start_offset, end_offset)
+ - start_offset;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 adjustment:%O", adjustment);
+
+ for (i = 0; i < mp4->trak.nelts; i++) {
+ if (trak[i].out[NGX_HTTP_MP4_CO64_DATA].buf) {
+ ngx_http_mp4_adjust_co64_atom(mp4, &trak[i], adjustment);
+ } else {
+ ngx_http_mp4_adjust_stco_atom(mp4, &trak[i], (int32_t) adjustment);
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+typedef struct {
+ u_char size[4];
+ u_char name[4];
+} ngx_mp4_atom_header_t;
+
+typedef struct {
+ u_char size[4];
+ u_char name[4];
+ u_char size64[8];
+} ngx_mp4_atom_header64_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_atom_handler_t *atom, uint64_t atom_data_size)
+{
+ off_t end;
+ size_t atom_header_size;
+ u_char *atom_header, *atom_name;
+ uint64_t atom_size;
+ ngx_int_t rc;
+ ngx_uint_t n;
+
+ end = mp4->offset + atom_data_size;
+
+ while (mp4->offset < end) {
+
+ if (ngx_http_mp4_read(mp4, sizeof(uint32_t)) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ atom_header = mp4->buffer_pos;
+ atom_size = ngx_mp4_get_32value(atom_header);
+ atom_header_size = sizeof(ngx_mp4_atom_header_t);
+
+ if (atom_size == 0) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 atom end");
+ return NGX_OK;
+ }
+
+ if (atom_size < sizeof(ngx_mp4_atom_header_t)) {
+
+ if (atom_size == 1) {
+
+ if (ngx_http_mp4_read(mp4, sizeof(ngx_mp4_atom_header64_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ /* 64-bit atom size */
+ atom_header = mp4->buffer_pos;
+ atom_size = ngx_mp4_get_64value(atom_header + 8);
+ atom_header_size = sizeof(ngx_mp4_atom_header64_t);
+
+ } else {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 atom is too small:%uL",
+ mp4->file.name.data, atom_size);
+ return NGX_ERROR;
+ }
+ }
+
+ if (ngx_http_mp4_read(mp4, sizeof(ngx_mp4_atom_header_t)) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ atom_header = mp4->buffer_pos;
+ atom_name = atom_header + sizeof(uint32_t);
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 atom: %*s @%O:%uL",
+ (size_t) 4, atom_name, mp4->offset, atom_size);
+
+ if (atom_size > (uint64_t) (NGX_MAX_OFF_T_VALUE - mp4->offset)
+ || mp4->offset + (off_t) atom_size > end)
+ {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 atom too large:%uL",
+ mp4->file.name.data, atom_size);
+ return NGX_ERROR;
+ }
+
+ for (n = 0; atom[n].name; n++) {
+
+ if (ngx_strncmp(atom_name, atom[n].name, 4) == 0) {
+
+ ngx_mp4_atom_next(mp4, atom_header_size);
+
+ rc = atom[n].handler(mp4, atom_size - atom_header_size);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ goto next;
+ }
+ }
+
+ ngx_mp4_atom_next(mp4, atom_size);
+
+ next:
+ continue;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read(ngx_http_mp4_file_t *mp4, size_t size)
+{
+ ssize_t n;
+
+ if (mp4->buffer_pos + size <= mp4->buffer_end) {
+ return NGX_OK;
+ }
+
+ if (mp4->offset + (off_t) mp4->buffer_size > mp4->end) {
+ mp4->buffer_size = (size_t) (mp4->end - mp4->offset);
+ }
+
+ if (mp4->buffer_size < size) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 file truncated", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ if (mp4->buffer == NULL) {
+ mp4->buffer = ngx_palloc(mp4->request->pool, mp4->buffer_size);
+ if (mp4->buffer == NULL) {
+ return NGX_ERROR;
+ }
+
+ mp4->buffer_start = mp4->buffer;
+ }
+
+ n = ngx_read_file(&mp4->file, mp4->buffer_start, mp4->buffer_size,
+ mp4->offset);
+
+ if (n == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if ((size_t) n != mp4->buffer_size) {
+ ngx_log_error(NGX_LOG_CRIT, mp4->file.log, 0,
+ ngx_read_file_n " read only %z of %z from \"%s\"",
+ n, mp4->buffer_size, mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ mp4->buffer_pos = mp4->buffer_start;
+ mp4->buffer_end = mp4->buffer_start + mp4->buffer_size;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_ftyp_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *ftyp_atom;
+ size_t atom_size;
+ ngx_buf_t *atom;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 ftyp atom");
+
+ if (atom_data_size > 1024
+ || ngx_mp4_atom_data(mp4) + (size_t) atom_data_size > mp4->buffer_end)
+ {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 ftyp atom is too large:%uL",
+ mp4->file.name.data, atom_data_size);
+ return NGX_ERROR;
+ }
+
+ atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+
+ ftyp_atom = ngx_palloc(mp4->request->pool, atom_size);
+ if (ftyp_atom == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_mp4_set_32value(ftyp_atom, atom_size);
+ ngx_mp4_set_atom_name(ftyp_atom, 'f', 't', 'y', 'p');
+
+ /*
+ * only moov atom content is guaranteed to be in mp4->buffer
+ * during sending response, so ftyp atom content should be copied
+ */
+ ngx_memcpy(ftyp_atom + sizeof(ngx_mp4_atom_header_t),
+ ngx_mp4_atom_data(mp4), (size_t) atom_data_size);
+
+ atom = &mp4->ftyp_atom_buf;
+ atom->temporary = 1;
+ atom->pos = ftyp_atom;
+ atom->last = ftyp_atom + atom_size;
+
+ mp4->ftyp_atom.buf = atom;
+ mp4->ftyp_size = atom_size;
+ mp4->content_length = atom_size;
+
+ ngx_mp4_atom_next(mp4, atom_data_size);
+
+ return NGX_OK;
+}
+
+
+/*
+ * Small excess buffer to process atoms after moov atom, mp4->buffer_start
+ * will be set to this buffer part after moov atom processing.
+ */
+#define NGX_HTTP_MP4_MOOV_BUFFER_EXCESS (4 * 1024)
+
+static ngx_int_t
+ngx_http_mp4_read_moov_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ ngx_int_t rc;
+ ngx_uint_t no_mdat;
+ ngx_buf_t *atom;
+ ngx_http_mp4_conf_t *conf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 moov atom");
+
+ no_mdat = (mp4->mdat_atom.buf == NULL);
+
+ if (no_mdat && mp4->start == 0 && mp4->length == 0) {
+ /*
+ * send original file if moov atom resides before
+ * mdat atom and client requests integral file
+ */
+ return NGX_DECLINED;
+ }
+
+ conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module);
+
+ if (atom_data_size > mp4->buffer_size) {
+
+ if (atom_data_size > conf->max_buffer_size) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 moov atom is too large:%uL, "
+ "you may want to increase mp4_max_buffer_size",
+ mp4->file.name.data, atom_data_size);
+ return NGX_ERROR;
+ }
+
+ ngx_pfree(mp4->request->pool, mp4->buffer);
+ mp4->buffer = NULL;
+ mp4->buffer_pos = NULL;
+ mp4->buffer_end = NULL;
+
+ mp4->buffer_size = (size_t) atom_data_size
+ + NGX_HTTP_MP4_MOOV_BUFFER_EXCESS * no_mdat;
+ }
+
+ if (ngx_http_mp4_read(mp4, (size_t) atom_data_size) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ mp4->trak.elts = &mp4->traks;
+ mp4->trak.size = sizeof(ngx_http_mp4_trak_t);
+ mp4->trak.nalloc = 2;
+ mp4->trak.pool = mp4->request->pool;
+
+ atom = &mp4->moov_atom_buf;
+ atom->temporary = 1;
+ atom->pos = mp4->moov_atom_header;
+ atom->last = mp4->moov_atom_header + 8;
+
+ mp4->moov_atom.buf = &mp4->moov_atom_buf;
+
+ rc = ngx_http_mp4_read_atom(mp4, ngx_http_mp4_moov_atoms, atom_data_size);
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 moov atom done");
+
+ if (no_mdat) {
+ mp4->buffer_start = mp4->buffer_pos;
+ mp4->buffer_size = NGX_HTTP_MP4_MOOV_BUFFER_EXCESS;
+
+ if (mp4->buffer_start + mp4->buffer_size > mp4->buffer_end) {
+ mp4->buffer = NULL;
+ mp4->buffer_pos = NULL;
+ mp4->buffer_end = NULL;
+ }
+
+ } else {
+ /* skip atoms after moov atom */
+ mp4->offset = mp4->end;
+ }
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_mdat_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ ngx_buf_t *data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mdat atom");
+
+ data = &mp4->mdat_data_buf;
+ data->file = &mp4->file;
+ data->in_file = 1;
+ data->last_buf = (mp4->request == mp4->request->main) ? 1 : 0;
+ data->last_in_chain = 1;
+ data->file_last = mp4->offset + atom_data_size;
+
+ mp4->mdat_atom.buf = &mp4->mdat_atom_buf;
+ mp4->mdat_atom.next = &mp4->mdat_data;
+ mp4->mdat_data.buf = data;
+
+ if (mp4->trak.nelts) {
+ /* skip atoms after mdat atom */
+ mp4->offset = mp4->end;
+
+ } else {
+ ngx_mp4_atom_next(mp4, atom_data_size);
+ }
+
+ return NGX_OK;
+}
+
+
+static size_t
+ngx_http_mp4_update_mdat_atom(ngx_http_mp4_file_t *mp4, off_t start_offset,
+ off_t end_offset)
+{
+ off_t atom_data_size;
+ u_char *atom_header;
+ uint32_t atom_header_size;
+ uint64_t atom_size;
+ ngx_buf_t *atom;
+
+ atom_data_size = end_offset - start_offset;
+ mp4->mdat_data.buf->file_pos = start_offset;
+ mp4->mdat_data.buf->file_last = end_offset;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mdat new offset @%O:%O", start_offset, atom_data_size);
+
+ atom_header = mp4->mdat_atom_header;
+
+ if ((uint64_t) atom_data_size
+ > (uint64_t) 0xffffffff - sizeof(ngx_mp4_atom_header_t))
+ {
+ atom_size = 1;
+ atom_header_size = sizeof(ngx_mp4_atom_header64_t);
+ ngx_mp4_set_64value(atom_header + sizeof(ngx_mp4_atom_header_t),
+ sizeof(ngx_mp4_atom_header64_t) + atom_data_size);
+ } else {
+ atom_size = sizeof(ngx_mp4_atom_header_t) + atom_data_size;
+ atom_header_size = sizeof(ngx_mp4_atom_header_t);
+ }
+
+ mp4->content_length += atom_header_size + atom_data_size;
+
+ ngx_mp4_set_32value(atom_header, atom_size);
+ ngx_mp4_set_atom_name(atom_header, 'm', 'd', 'a', 't');
+
+ atom = &mp4->mdat_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_header + atom_header_size;
+
+ return atom_header_size;
+}
+
+
+typedef struct {
+ u_char size[4];
+ u_char name[4];
+ u_char version[1];
+ u_char flags[3];
+ u_char creation_time[4];
+ u_char modification_time[4];
+ u_char timescale[4];
+ u_char duration[4];
+ u_char rate[4];
+ u_char volume[2];
+ u_char reserved[10];
+ u_char matrix[36];
+ u_char preview_time[4];
+ u_char preview_duration[4];
+ u_char poster_time[4];
+ u_char selection_time[4];
+ u_char selection_duration[4];
+ u_char current_time[4];
+ u_char next_track_id[4];
+} ngx_mp4_mvhd_atom_t;
+
+typedef struct {
+ u_char size[4];
+ u_char name[4];
+ u_char version[1];
+ u_char flags[3];
+ u_char creation_time[8];
+ u_char modification_time[8];
+ u_char timescale[4];
+ u_char duration[8];
+ u_char rate[4];
+ u_char volume[2];
+ u_char reserved[10];
+ u_char matrix[36];
+ u_char preview_time[4];
+ u_char preview_duration[4];
+ u_char poster_time[4];
+ u_char selection_time[4];
+ u_char selection_duration[4];
+ u_char current_time[4];
+ u_char next_track_id[4];
+} ngx_mp4_mvhd64_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_mvhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header;
+ size_t atom_size;
+ uint32_t timescale;
+ uint64_t duration, start_time, length_time;
+ ngx_buf_t *atom;
+ ngx_mp4_mvhd_atom_t *mvhd_atom;
+ ngx_mp4_mvhd64_atom_t *mvhd64_atom;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mvhd atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ mvhd_atom = (ngx_mp4_mvhd_atom_t *) atom_header;
+ mvhd64_atom = (ngx_mp4_mvhd64_atom_t *) atom_header;
+ ngx_mp4_set_atom_name(atom_header, 'm', 'v', 'h', 'd');
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_mvhd_atom_t) > atom_data_size) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 mvhd atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ if (mvhd_atom->version[0] == 0) {
+ /* version 0: 32-bit duration */
+ timescale = ngx_mp4_get_32value(mvhd_atom->timescale);
+ duration = ngx_mp4_get_32value(mvhd_atom->duration);
+
+ } else {
+ /* version 1: 64-bit duration */
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_mvhd64_atom_t) > atom_data_size) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 mvhd atom too small",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ timescale = ngx_mp4_get_32value(mvhd64_atom->timescale);
+ duration = ngx_mp4_get_64value(mvhd64_atom->duration);
+ }
+
+ mp4->timescale = timescale;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mvhd timescale:%uD, duration:%uL, time:%.3fs",
+ timescale, duration, (double) duration / timescale);
+
+ start_time = (uint64_t) mp4->start * timescale / 1000;
+
+ if (duration < start_time) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 start time exceeds file duration",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ duration -= start_time;
+
+ if (mp4->length) {
+ length_time = (uint64_t) mp4->length * timescale / 1000;
+
+ if (duration > length_time) {
+ duration = length_time;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mvhd new duration:%uL, time:%.3fs",
+ duration, (double) duration / timescale);
+
+ atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+ ngx_mp4_set_32value(mvhd_atom->size, atom_size);
+
+ if (mvhd_atom->version[0] == 0) {
+ ngx_mp4_set_32value(mvhd_atom->duration, duration);
+
+ } else {
+ ngx_mp4_set_64value(mvhd64_atom->duration, duration);
+ }
+
+ atom = &mp4->mvhd_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_header + atom_size;
+
+ mp4->mvhd_atom.buf = atom;
+
+ ngx_mp4_atom_next(mp4, atom_data_size);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_trak_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header, *atom_end;
+ off_t atom_file_end;
+ ngx_int_t rc;
+ ngx_buf_t *atom;
+ ngx_http_mp4_trak_t *trak;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 trak atom");
+
+ trak = ngx_array_push(&mp4->trak);
+ if (trak == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memzero(trak, sizeof(ngx_http_mp4_trak_t));
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ ngx_mp4_set_atom_name(atom_header, 't', 'r', 'a', 'k');
+
+ atom = &trak->trak_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_header + sizeof(ngx_mp4_atom_header_t);
+
+ trak->out[NGX_HTTP_MP4_TRAK_ATOM].buf = atom;
+
+ atom_end = mp4->buffer_pos + (size_t) atom_data_size;
+ atom_file_end = mp4->offset + atom_data_size;
+
+ rc = ngx_http_mp4_read_atom(mp4, ngx_http_mp4_trak_atoms, atom_data_size);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 trak atom: %i", rc);
+
+ if (rc == NGX_DECLINED) {
+ /* skip this trak */
+ ngx_memzero(trak, sizeof(ngx_http_mp4_trak_t));
+ mp4->trak.nelts--;
+ mp4->buffer_pos = atom_end;
+ mp4->offset = atom_file_end;
+ return NGX_OK;
+ }
+
+ return rc;
+}
+
+
+static void
+ngx_http_mp4_update_trak_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak)
+{
+ ngx_buf_t *atom;
+
+ trak->size += sizeof(ngx_mp4_atom_header_t);
+ atom = &trak->trak_atom_buf;
+ ngx_mp4_set_32value(atom->pos, trak->size);
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_cmov_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 compressed moov atom (cmov) is not supported",
+ mp4->file.name.data);
+
+ return NGX_ERROR;
+}
+
+
+typedef struct {
+ u_char size[4];
+ u_char name[4];
+ u_char version[1];
+ u_char flags[3];
+ u_char creation_time[4];
+ u_char modification_time[4];
+ u_char track_id[4];
+ u_char reserved1[4];
+ u_char duration[4];
+ u_char reserved2[8];
+ u_char layer[2];
+ u_char group[2];
+ u_char volume[2];
+ u_char reserved3[2];
+ u_char matrix[36];
+ u_char width[4];
+ u_char height[4];
+} ngx_mp4_tkhd_atom_t;
+
+typedef struct {
+ u_char size[4];
+ u_char name[4];
+ u_char version[1];
+ u_char flags[3];
+ u_char creation_time[8];
+ u_char modification_time[8];
+ u_char track_id[4];
+ u_char reserved1[4];
+ u_char duration[8];
+ u_char reserved2[8];
+ u_char layer[2];
+ u_char group[2];
+ u_char volume[2];
+ u_char reserved3[2];
+ u_char matrix[36];
+ u_char width[4];
+ u_char height[4];
+} ngx_mp4_tkhd64_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_tkhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header;
+ size_t atom_size;
+ uint64_t duration, start_time, length_time;
+ ngx_buf_t *atom;
+ ngx_http_mp4_trak_t *trak;
+ ngx_mp4_tkhd_atom_t *tkhd_atom;
+ ngx_mp4_tkhd64_atom_t *tkhd64_atom;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 tkhd atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ tkhd_atom = (ngx_mp4_tkhd_atom_t *) atom_header;
+ tkhd64_atom = (ngx_mp4_tkhd64_atom_t *) atom_header;
+ ngx_mp4_set_atom_name(tkhd_atom, 't', 'k', 'h', 'd');
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_tkhd_atom_t) > atom_data_size) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 tkhd atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ if (tkhd_atom->version[0] == 0) {
+ /* version 0: 32-bit duration */
+ duration = ngx_mp4_get_32value(tkhd_atom->duration);
+
+ } else {
+ /* version 1: 64-bit duration */
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_tkhd64_atom_t) > atom_data_size) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 tkhd atom too small",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ duration = ngx_mp4_get_64value(tkhd64_atom->duration);
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "tkhd duration:%uL, time:%.3fs",
+ duration, (double) duration / mp4->timescale);
+
+ start_time = (uint64_t) mp4->start * mp4->timescale / 1000;
+
+ if (duration <= start_time) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "tkhd duration is less than start time");
+ return NGX_DECLINED;
+ }
+
+ duration -= start_time;
+
+ if (mp4->length) {
+ length_time = (uint64_t) mp4->length * mp4->timescale / 1000;
+
+ if (duration > length_time) {
+ duration = length_time;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "tkhd new duration:%uL, time:%.3fs",
+ duration, (double) duration / mp4->timescale);
+
+ atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+
+ trak = ngx_mp4_last_trak(mp4);
+ trak->tkhd_size = atom_size;
+
+ ngx_mp4_set_32value(tkhd_atom->size, atom_size);
+
+ if (tkhd_atom->version[0] == 0) {
+ ngx_mp4_set_32value(tkhd_atom->duration, duration);
+
+ } else {
+ ngx_mp4_set_64value(tkhd64_atom->duration, duration);
+ }
+
+ atom = &trak->tkhd_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_header + atom_size;
+
+ trak->out[NGX_HTTP_MP4_TKHD_ATOM].buf = atom;
+
+ ngx_mp4_atom_next(mp4, atom_data_size);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_mdia_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header;
+ ngx_buf_t *atom;
+ ngx_http_mp4_trak_t *trak;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "process mdia atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ ngx_mp4_set_atom_name(atom_header, 'm', 'd', 'i', 'a');
+
+ trak = ngx_mp4_last_trak(mp4);
+
+ atom = &trak->mdia_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_header + sizeof(ngx_mp4_atom_header_t);
+
+ trak->out[NGX_HTTP_MP4_MDIA_ATOM].buf = atom;
+
+ return ngx_http_mp4_read_atom(mp4, ngx_http_mp4_mdia_atoms, atom_data_size);
+}
+
+
+static void
+ngx_http_mp4_update_mdia_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak)
+{
+ ngx_buf_t *atom;
+
+ trak->size += sizeof(ngx_mp4_atom_header_t);
+ atom = &trak->mdia_atom_buf;
+ ngx_mp4_set_32value(atom->pos, trak->size);
+}
+
+
+typedef struct {
+ u_char size[4];
+ u_char name[4];
+ u_char version[1];
+ u_char flags[3];
+ u_char creation_time[4];
+ u_char modification_time[4];
+ u_char timescale[4];
+ u_char duration[4];
+ u_char language[2];
+ u_char quality[2];
+} ngx_mp4_mdhd_atom_t;
+
+typedef struct {
+ u_char size[4];
+ u_char name[4];
+ u_char version[1];
+ u_char flags[3];
+ u_char creation_time[8];
+ u_char modification_time[8];
+ u_char timescale[4];
+ u_char duration[8];
+ u_char language[2];
+ u_char quality[2];
+} ngx_mp4_mdhd64_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_mdhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header;
+ size_t atom_size;
+ uint32_t timescale;
+ uint64_t duration, start_time, length_time;
+ ngx_buf_t *atom;
+ ngx_http_mp4_trak_t *trak;
+ ngx_mp4_mdhd_atom_t *mdhd_atom;
+ ngx_mp4_mdhd64_atom_t *mdhd64_atom;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mdhd atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ mdhd_atom = (ngx_mp4_mdhd_atom_t *) atom_header;
+ mdhd64_atom = (ngx_mp4_mdhd64_atom_t *) atom_header;
+ ngx_mp4_set_atom_name(mdhd_atom, 'm', 'd', 'h', 'd');
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_mdhd_atom_t) > atom_data_size) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 mdhd atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ if (mdhd_atom->version[0] == 0) {
+ /* version 0: everything is 32-bit */
+ timescale = ngx_mp4_get_32value(mdhd_atom->timescale);
+ duration = ngx_mp4_get_32value(mdhd_atom->duration);
+
+ } else {
+ /* version 1: 64-bit duration and 32-bit timescale */
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_mdhd64_atom_t) > atom_data_size) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 mdhd atom too small",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ timescale = ngx_mp4_get_32value(mdhd64_atom->timescale);
+ duration = ngx_mp4_get_64value(mdhd64_atom->duration);
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mdhd timescale:%uD, duration:%uL, time:%.3fs",
+ timescale, duration, (double) duration / timescale);
+
+ start_time = (uint64_t) mp4->start * timescale / 1000;
+
+ if (duration <= start_time) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mdhd duration is less than start time");
+ return NGX_DECLINED;
+ }
+
+ duration -= start_time;
+
+ if (mp4->length) {
+ length_time = (uint64_t) mp4->length * timescale / 1000;
+
+ if (duration > length_time) {
+ duration = length_time;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mdhd new duration:%uL, time:%.3fs",
+ duration, (double) duration / timescale);
+
+ atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+
+ trak = ngx_mp4_last_trak(mp4);
+ trak->mdhd_size = atom_size;
+ trak->timescale = timescale;
+
+ ngx_mp4_set_32value(mdhd_atom->size, atom_size);
+
+ if (mdhd_atom->version[0] == 0) {
+ ngx_mp4_set_32value(mdhd_atom->duration, duration);
+
+ } else {
+ ngx_mp4_set_64value(mdhd64_atom->duration, duration);
+ }
+
+ atom = &trak->mdhd_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_header + atom_size;
+
+ trak->out[NGX_HTTP_MP4_MDHD_ATOM].buf = atom;
+
+ ngx_mp4_atom_next(mp4, atom_data_size);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_hdlr_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header;
+ size_t atom_size;
+ ngx_buf_t *atom;
+ ngx_http_mp4_trak_t *trak;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 hdlr atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+ ngx_mp4_set_32value(atom_header, atom_size);
+ ngx_mp4_set_atom_name(atom_header, 'h', 'd', 'l', 'r');
+
+ trak = ngx_mp4_last_trak(mp4);
+
+ atom = &trak->hdlr_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_header + atom_size;
+
+ trak->hdlr_size = atom_size;
+ trak->out[NGX_HTTP_MP4_HDLR_ATOM].buf = atom;
+
+ ngx_mp4_atom_next(mp4, atom_data_size);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_minf_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header;
+ ngx_buf_t *atom;
+ ngx_http_mp4_trak_t *trak;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "process minf atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ ngx_mp4_set_atom_name(atom_header, 'm', 'i', 'n', 'f');
+
+ trak = ngx_mp4_last_trak(mp4);
+
+ atom = &trak->minf_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_header + sizeof(ngx_mp4_atom_header_t);
+
+ trak->out[NGX_HTTP_MP4_MINF_ATOM].buf = atom;
+
+ return ngx_http_mp4_read_atom(mp4, ngx_http_mp4_minf_atoms, atom_data_size);
+}
+
+
+static void
+ngx_http_mp4_update_minf_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak)
+{
+ ngx_buf_t *atom;
+
+ trak->size += sizeof(ngx_mp4_atom_header_t)
+ + trak->vmhd_size
+ + trak->smhd_size
+ + trak->dinf_size;
+ atom = &trak->minf_atom_buf;
+ ngx_mp4_set_32value(atom->pos, trak->size);
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_vmhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header;
+ size_t atom_size;
+ ngx_buf_t *atom;
+ ngx_http_mp4_trak_t *trak;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 vmhd atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+ ngx_mp4_set_32value(atom_header, atom_size);
+ ngx_mp4_set_atom_name(atom_header, 'v', 'm', 'h', 'd');
+
+ trak = ngx_mp4_last_trak(mp4);
+
+ atom = &trak->vmhd_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_header + atom_size;
+
+ trak->vmhd_size += atom_size;
+ trak->out[NGX_HTTP_MP4_VMHD_ATOM].buf = atom;
+
+ ngx_mp4_atom_next(mp4, atom_data_size);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_smhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header;
+ size_t atom_size;
+ ngx_buf_t *atom;
+ ngx_http_mp4_trak_t *trak;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 smhd atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+ ngx_mp4_set_32value(atom_header, atom_size);
+ ngx_mp4_set_atom_name(atom_header, 's', 'm', 'h', 'd');
+
+ trak = ngx_mp4_last_trak(mp4);
+
+ atom = &trak->smhd_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_header + atom_size;
+
+ trak->smhd_size += atom_size;
+ trak->out[NGX_HTTP_MP4_SMHD_ATOM].buf = atom;
+
+ ngx_mp4_atom_next(mp4, atom_data_size);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_dinf_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header;
+ size_t atom_size;
+ ngx_buf_t *atom;
+ ngx_http_mp4_trak_t *trak;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 dinf atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+ ngx_mp4_set_32value(atom_header, atom_size);
+ ngx_mp4_set_atom_name(atom_header, 'd', 'i', 'n', 'f');
+
+ trak = ngx_mp4_last_trak(mp4);
+
+ atom = &trak->dinf_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_header + atom_size;
+
+ trak->dinf_size += atom_size;
+ trak->out[NGX_HTTP_MP4_DINF_ATOM].buf = atom;
+
+ ngx_mp4_atom_next(mp4, atom_data_size);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_read_stbl_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header;
+ ngx_buf_t *atom;
+ ngx_http_mp4_trak_t *trak;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "process stbl atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ ngx_mp4_set_atom_name(atom_header, 's', 't', 'b', 'l');
+
+ trak = ngx_mp4_last_trak(mp4);
+
+ atom = &trak->stbl_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_header + sizeof(ngx_mp4_atom_header_t);
+
+ trak->out[NGX_HTTP_MP4_STBL_ATOM].buf = atom;
+
+ return ngx_http_mp4_read_atom(mp4, ngx_http_mp4_stbl_atoms, atom_data_size);
+}
+
+
+static void
+ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak)
+{
+ ngx_buf_t *atom;
+
+ trak->size += sizeof(ngx_mp4_atom_header_t);
+ atom = &trak->stbl_atom_buf;
+ ngx_mp4_set_32value(atom->pos, trak->size);
+}
+
+
+typedef struct {
+ u_char size[4];
+ u_char name[4];
+ u_char version[1];
+ u_char flags[3];
+ u_char entries[4];
+
+ u_char media_size[4];
+ u_char media_name[4];
+} ngx_mp4_stsd_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_stsd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header, *atom_table;
+ size_t atom_size;
+ ngx_buf_t *atom;
+ ngx_mp4_stsd_atom_t *stsd_atom;
+ ngx_http_mp4_trak_t *trak;
+
+ /* sample description atom */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stsd atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ stsd_atom = (ngx_mp4_stsd_atom_t *) atom_header;
+ atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+ atom_table = atom_header + atom_size;
+ ngx_mp4_set_32value(stsd_atom->size, atom_size);
+ ngx_mp4_set_atom_name(stsd_atom, 's', 't', 's', 'd');
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_stsd_atom_t) > atom_data_size) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 stsd atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "stsd entries:%uD, media:%*s",
+ ngx_mp4_get_32value(stsd_atom->entries),
+ (size_t) 4, stsd_atom->media_name);
+
+ trak = ngx_mp4_last_trak(mp4);
+
+ atom = &trak->stsd_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_table;
+
+ trak->out[NGX_HTTP_MP4_STSD_ATOM].buf = atom;
+ trak->size += atom_size;
+
+ ngx_mp4_atom_next(mp4, atom_data_size);
+
+ return NGX_OK;
+}
+
+
+typedef struct {
+ u_char size[4];
+ u_char name[4];
+ u_char version[1];
+ u_char flags[3];
+ u_char entries[4];
+} ngx_mp4_stts_atom_t;
+
+typedef struct {
+ u_char count[4];
+ u_char duration[4];
+} ngx_mp4_stts_entry_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_stts_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header, *atom_table, *atom_end;
+ uint32_t entries;
+ ngx_buf_t *atom, *data;
+ ngx_mp4_stts_atom_t *stts_atom;
+ ngx_http_mp4_trak_t *trak;
+
+ /* time-to-sample atom */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stts atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ stts_atom = (ngx_mp4_stts_atom_t *) atom_header;
+ ngx_mp4_set_atom_name(stts_atom, 's', 't', 't', 's');
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_stts_atom_t) > atom_data_size) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 stts atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ entries = ngx_mp4_get_32value(stts_atom->entries);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 time-to-sample entries:%uD", entries);
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_stts_atom_t)
+ + entries * sizeof(ngx_mp4_stts_entry_t) > atom_data_size)
+ {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 stts atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ atom_table = atom_header + sizeof(ngx_mp4_stts_atom_t);
+ atom_end = atom_table + entries * sizeof(ngx_mp4_stts_entry_t);
+
+ trak = ngx_mp4_last_trak(mp4);
+ trak->time_to_sample_entries = entries;
+
+ atom = &trak->stts_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_table;
+
+ data = &trak->stts_data_buf;
+ data->temporary = 1;
+ data->pos = atom_table;
+ data->last = atom_end;
+
+ trak->out[NGX_HTTP_MP4_STTS_ATOM].buf = atom;
+ trak->out[NGX_HTTP_MP4_STTS_DATA].buf = data;
+
+ ngx_mp4_atom_next(mp4, atom_data_size);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_update_stts_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak)
+{
+ size_t atom_size;
+ ngx_buf_t *atom, *data;
+ ngx_mp4_stts_atom_t *stts_atom;
+
+ /*
+ * mdia.minf.stbl.stts updating requires trak->timescale
+ * from mdia.mdhd atom which may reside after mdia.minf
+ */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 stts atom update");
+
+ data = trak->out[NGX_HTTP_MP4_STTS_DATA].buf;
+
+ if (data == NULL) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "no mp4 stts atoms were found in \"%s\"",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ if (ngx_http_mp4_crop_stts_data(mp4, trak, 1) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_http_mp4_crop_stts_data(mp4, trak, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "time-to-sample entries:%uD", trak->time_to_sample_entries);
+
+ atom_size = sizeof(ngx_mp4_stts_atom_t) + (data->last - data->pos);
+ trak->size += atom_size;
+
+ atom = trak->out[NGX_HTTP_MP4_STTS_ATOM].buf;
+ stts_atom = (ngx_mp4_stts_atom_t *) atom->pos;
+ ngx_mp4_set_32value(stts_atom->size, atom_size);
+ ngx_mp4_set_32value(stts_atom->entries, trak->time_to_sample_entries);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak, ngx_uint_t start)
+{
+ uint32_t count, duration, rest;
+ uint64_t start_time;
+ ngx_buf_t *data;
+ ngx_uint_t start_sample, entries, start_sec;
+ ngx_mp4_stts_entry_t *entry, *end;
+
+ if (start) {
+ start_sec = mp4->start;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 stts crop start_time:%ui", start_sec);
+
+ } else if (mp4->length) {
+ start_sec = mp4->length;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 stts crop end_time:%ui", start_sec);
+
+ } else {
+ return NGX_OK;
+ }
+
+ data = trak->out[NGX_HTTP_MP4_STTS_DATA].buf;
+
+ start_time = (uint64_t) start_sec * trak->timescale / 1000;
+
+ entries = trak->time_to_sample_entries;
+ start_sample = 0;
+ entry = (ngx_mp4_stts_entry_t *) data->pos;
+ end = (ngx_mp4_stts_entry_t *) data->last;
+
+ while (entry < end) {
+ count = ngx_mp4_get_32value(entry->count);
+ duration = ngx_mp4_get_32value(entry->duration);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "time:%uL, count:%uD, duration:%uD",
+ start_time, count, duration);
+
+ if (start_time < (uint64_t) count * duration) {
+ start_sample += (ngx_uint_t) (start_time / duration);
+ rest = (uint32_t) (start_time / duration);
+ goto found;
+ }
+
+ start_sample += count;
+ start_time -= count * duration;
+ entries--;
+ entry++;
+ }
+
+ if (start) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "start time is out mp4 stts samples in \"%s\"",
+ mp4->file.name.data);
+
+ return NGX_ERROR;
+
+ } else {
+ trak->end_sample = trak->start_sample + start_sample;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "end_sample:%ui", trak->end_sample);
+
+ return NGX_OK;
+ }
+
+found:
+
+ if (start) {
+ ngx_mp4_set_32value(entry->count, count - rest);
+ data->pos = (u_char *) entry;
+ trak->time_to_sample_entries = entries;
+ trak->start_sample = start_sample;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "start_sample:%ui, new count:%uD",
+ trak->start_sample, count - rest);
+
+ } else {
+ ngx_mp4_set_32value(entry->count, rest);
+ data->last = (u_char *) (entry + 1);
+ trak->time_to_sample_entries -= entries - 1;
+ trak->end_sample = trak->start_sample + start_sample;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "end_sample:%ui, new count:%uD",
+ trak->end_sample, rest);
+ }
+
+ return NGX_OK;
+}
+
+
+typedef struct {
+ u_char size[4];
+ u_char name[4];
+ u_char version[1];
+ u_char flags[3];
+ u_char entries[4];
+} ngx_http_mp4_stss_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_stss_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header, *atom_table, *atom_end;
+ uint32_t entries;
+ ngx_buf_t *atom, *data;
+ ngx_http_mp4_trak_t *trak;
+ ngx_http_mp4_stss_atom_t *stss_atom;
+
+ /* sync samples atom */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stss atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ stss_atom = (ngx_http_mp4_stss_atom_t *) atom_header;
+ ngx_mp4_set_atom_name(stss_atom, 's', 't', 's', 's');
+
+ if (ngx_mp4_atom_data_size(ngx_http_mp4_stss_atom_t) > atom_data_size) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 stss atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ entries = ngx_mp4_get_32value(stss_atom->entries);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "sync sample entries:%uD", entries);
+
+ trak = ngx_mp4_last_trak(mp4);
+ trak->sync_samples_entries = entries;
+
+ atom_table = atom_header + sizeof(ngx_http_mp4_stss_atom_t);
+
+ atom = &trak->stss_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_table;
+
+ if (ngx_mp4_atom_data_size(ngx_http_mp4_stss_atom_t)
+ + entries * sizeof(uint32_t) > atom_data_size)
+ {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 stss atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ atom_end = atom_table + entries * sizeof(uint32_t);
+
+ data = &trak->stss_data_buf;
+ data->temporary = 1;
+ data->pos = atom_table;
+ data->last = atom_end;
+
+ trak->out[NGX_HTTP_MP4_STSS_ATOM].buf = atom;
+ trak->out[NGX_HTTP_MP4_STSS_DATA].buf = data;
+
+ ngx_mp4_atom_next(mp4, atom_data_size);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_update_stss_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak)
+{
+ size_t atom_size;
+ uint32_t sample, start_sample, *entry, *end;
+ ngx_buf_t *atom, *data;
+ ngx_http_mp4_stss_atom_t *stss_atom;
+
+ /*
+ * mdia.minf.stbl.stss updating requires trak->start_sample
+ * from mdia.minf.stbl.stts which depends on value from mdia.mdhd
+ * atom which may reside after mdia.minf
+ */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 stss atom update");
+
+ data = trak->out[NGX_HTTP_MP4_STSS_DATA].buf;
+
+ if (data == NULL) {
+ return NGX_OK;
+ }
+
+ ngx_http_mp4_crop_stss_data(mp4, trak, 1);
+ ngx_http_mp4_crop_stss_data(mp4, trak, 0);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "sync sample entries:%uD", trak->sync_samples_entries);
+
+ if (trak->sync_samples_entries) {
+ entry = (uint32_t *) data->pos;
+ end = (uint32_t *) data->last;
+
+ start_sample = trak->start_sample;
+
+ while (entry < end) {
+ sample = ngx_mp4_get_32value(entry);
+ sample -= start_sample;
+ ngx_mp4_set_32value(entry, sample);
+ entry++;
+ }
+
+ } else {
+ trak->out[NGX_HTTP_MP4_STSS_DATA].buf = NULL;
+ }
+
+ atom_size = sizeof(ngx_http_mp4_stss_atom_t) + (data->last - data->pos);
+ trak->size += atom_size;
+
+ atom = trak->out[NGX_HTTP_MP4_STSS_ATOM].buf;
+ stss_atom = (ngx_http_mp4_stss_atom_t *) atom->pos;
+
+ ngx_mp4_set_32value(stss_atom->size, atom_size);
+ ngx_mp4_set_32value(stss_atom->entries, trak->sync_samples_entries);
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_mp4_crop_stss_data(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak, ngx_uint_t start)
+{
+ uint32_t sample, start_sample, *entry, *end;
+ ngx_buf_t *data;
+ ngx_uint_t entries;
+
+ /* sync samples starts from 1 */
+
+ if (start) {
+ start_sample = trak->start_sample + 1;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 stss crop start_sample:%uD", start_sample);
+
+ } else if (mp4->length) {
+ start_sample = trak->end_sample + 1;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 stss crop end_sample:%uD", start_sample);
+
+ } else {
+ return;
+ }
+
+ data = trak->out[NGX_HTTP_MP4_STSS_DATA].buf;
+
+ entries = trak->sync_samples_entries;
+ entry = (uint32_t *) data->pos;
+ end = (uint32_t *) data->last;
+
+ while (entry < end) {
+ sample = ngx_mp4_get_32value(entry);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "sync:%uD", sample);
+
+ if (sample >= start_sample) {
+ goto found;
+ }
+
+ entries--;
+ entry++;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "sample is out of mp4 stss atom");
+
+found:
+
+ if (start) {
+ data->pos = (u_char *) entry;
+ trak->sync_samples_entries = entries;
+
+ } else {
+ data->last = (u_char *) entry;
+ trak->sync_samples_entries -= entries;
+ }
+}
+
+
+typedef struct {
+ u_char size[4];
+ u_char name[4];
+ u_char version[1];
+ u_char flags[3];
+ u_char entries[4];
+} ngx_mp4_ctts_atom_t;
+
+typedef struct {
+ u_char count[4];
+ u_char offset[4];
+} ngx_mp4_ctts_entry_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_ctts_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header, *atom_table, *atom_end;
+ uint32_t entries;
+ ngx_buf_t *atom, *data;
+ ngx_mp4_ctts_atom_t *ctts_atom;
+ ngx_http_mp4_trak_t *trak;
+
+ /* composition offsets atom */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 ctts atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ ctts_atom = (ngx_mp4_ctts_atom_t *) atom_header;
+ ngx_mp4_set_atom_name(ctts_atom, 'c', 't', 't', 's');
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_ctts_atom_t) > atom_data_size) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 ctts atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ entries = ngx_mp4_get_32value(ctts_atom->entries);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "composition offset entries:%uD", entries);
+
+ trak = ngx_mp4_last_trak(mp4);
+ trak->composition_offset_entries = entries;
+
+ atom_table = atom_header + sizeof(ngx_mp4_ctts_atom_t);
+
+ atom = &trak->ctts_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_table;
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_ctts_atom_t)
+ + entries * sizeof(ngx_mp4_ctts_entry_t) > atom_data_size)
+ {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 ctts atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ atom_end = atom_table + entries * sizeof(ngx_mp4_ctts_entry_t);
+
+ data = &trak->ctts_data_buf;
+ data->temporary = 1;
+ data->pos = atom_table;
+ data->last = atom_end;
+
+ trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf = atom;
+ trak->out[NGX_HTTP_MP4_CTTS_DATA].buf = data;
+
+ ngx_mp4_atom_next(mp4, atom_data_size);
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_mp4_update_ctts_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak)
+{
+ size_t atom_size;
+ ngx_buf_t *atom, *data;
+ ngx_mp4_ctts_atom_t *ctts_atom;
+
+ /*
+ * mdia.minf.stbl.ctts updating requires trak->start_sample
+ * from mdia.minf.stbl.stts which depends on value from mdia.mdhd
+ * atom which may reside after mdia.minf
+ */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 ctts atom update");
+
+ data = trak->out[NGX_HTTP_MP4_CTTS_DATA].buf;
+
+ if (data == NULL) {
+ return;
+ }
+
+ ngx_http_mp4_crop_ctts_data(mp4, trak, 1);
+ ngx_http_mp4_crop_ctts_data(mp4, trak, 0);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "composition offset entries:%uD",
+ trak->composition_offset_entries);
+
+ if (trak->composition_offset_entries == 0) {
+ trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf = NULL;
+ trak->out[NGX_HTTP_MP4_CTTS_DATA].buf = NULL;
+ return;
+ }
+
+ atom_size = sizeof(ngx_mp4_ctts_atom_t) + (data->last - data->pos);
+ trak->size += atom_size;
+
+ atom = trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf;
+ ctts_atom = (ngx_mp4_ctts_atom_t *) atom->pos;
+
+ ngx_mp4_set_32value(ctts_atom->size, atom_size);
+ ngx_mp4_set_32value(ctts_atom->entries, trak->composition_offset_entries);
+
+ return;
+}
+
+
+static void
+ngx_http_mp4_crop_ctts_data(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak, ngx_uint_t start)
+{
+ uint32_t count, start_sample, rest;
+ ngx_buf_t *data;
+ ngx_uint_t entries;
+ ngx_mp4_ctts_entry_t *entry, *end;
+
+ /* sync samples starts from 1 */
+
+ if (start) {
+ start_sample = trak->start_sample + 1;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 ctts crop start_sample:%uD", start_sample);
+
+ } else if (mp4->length) {
+ start_sample = trak->end_sample - trak->start_sample + 1;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 ctts crop end_sample:%uD", start_sample);
+
+ } else {
+ return;
+ }
+
+ data = trak->out[NGX_HTTP_MP4_CTTS_DATA].buf;
+
+ entries = trak->composition_offset_entries;
+ entry = (ngx_mp4_ctts_entry_t *) data->pos;
+ end = (ngx_mp4_ctts_entry_t *) data->last;
+
+ while (entry < end) {
+ count = ngx_mp4_get_32value(entry->count);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "sample:%uD, count:%uD, offset:%uD",
+ start_sample, count, ngx_mp4_get_32value(entry->offset));
+
+ if (start_sample <= count) {
+ rest = start_sample - 1;
+ goto found;
+ }
+
+ start_sample -= count;
+ entries--;
+ entry++;
+ }
+
+ if (start) {
+ data->pos = (u_char *) end;
+ trak->composition_offset_entries = 0;
+ }
+
+ return;
+
+found:
+
+ if (start) {
+ ngx_mp4_set_32value(entry->count, count - rest);
+ data->pos = (u_char *) entry;
+ trak->composition_offset_entries = entries;
+
+ } else {
+ ngx_mp4_set_32value(entry->count, rest);
+ data->last = (u_char *) (entry + 1);
+ trak->composition_offset_entries -= entries - 1;
+ }
+}
+
+
+typedef struct {
+ u_char size[4];
+ u_char name[4];
+ u_char version[1];
+ u_char flags[3];
+ u_char entries[4];
+} ngx_mp4_stsc_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_stsc_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header, *atom_table, *atom_end;
+ uint32_t entries;
+ ngx_buf_t *atom, *data;
+ ngx_mp4_stsc_atom_t *stsc_atom;
+ ngx_http_mp4_trak_t *trak;
+
+ /* sample-to-chunk atom */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stsc atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ stsc_atom = (ngx_mp4_stsc_atom_t *) atom_header;
+ ngx_mp4_set_atom_name(stsc_atom, 's', 't', 's', 'c');
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_stsc_atom_t) > atom_data_size) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 stsc atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ entries = ngx_mp4_get_32value(stsc_atom->entries);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "sample-to-chunk entries:%uD", entries);
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_stsc_atom_t)
+ + entries * sizeof(ngx_mp4_stsc_entry_t) > atom_data_size)
+ {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 stsc atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ atom_table = atom_header + sizeof(ngx_mp4_stsc_atom_t);
+ atom_end = atom_table + entries * sizeof(ngx_mp4_stsc_entry_t);
+
+ trak = ngx_mp4_last_trak(mp4);
+ trak->sample_to_chunk_entries = entries;
+
+ atom = &trak->stsc_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_table;
+
+ data = &trak->stsc_data_buf;
+ data->temporary = 1;
+ data->pos = atom_table;
+ data->last = atom_end;
+
+ trak->out[NGX_HTTP_MP4_STSC_ATOM].buf = atom;
+ trak->out[NGX_HTTP_MP4_STSC_DATA].buf = data;
+
+ ngx_mp4_atom_next(mp4, atom_data_size);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_update_stsc_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak)
+{
+ size_t atom_size;
+ uint32_t chunk;
+ ngx_buf_t *atom, *data;
+ ngx_mp4_stsc_atom_t *stsc_atom;
+ ngx_mp4_stsc_entry_t *entry, *end;
+
+ /*
+ * mdia.minf.stbl.stsc updating requires trak->start_sample
+ * from mdia.minf.stbl.stts which depends on value from mdia.mdhd
+ * atom which may reside after mdia.minf
+ */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 stsc atom update");
+
+ data = trak->out[NGX_HTTP_MP4_STSC_DATA].buf;
+
+ if (data == NULL) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "no mp4 stsc atoms were found in \"%s\"",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ if (trak->sample_to_chunk_entries == 0) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "zero number of entries in stsc atom in \"%s\"",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ if (ngx_http_mp4_crop_stsc_data(mp4, trak, 1) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_http_mp4_crop_stsc_data(mp4, trak, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "sample-to-chunk entries:%uD",
+ trak->sample_to_chunk_entries);
+
+ entry = (ngx_mp4_stsc_entry_t *) data->pos;
+ end = (ngx_mp4_stsc_entry_t *) data->last;
+
+ while (entry < end) {
+ chunk = ngx_mp4_get_32value(entry->chunk);
+ chunk -= trak->start_chunk;
+ ngx_mp4_set_32value(entry->chunk, chunk);
+ entry++;
+ }
+
+ atom_size = sizeof(ngx_mp4_stsc_atom_t)
+ + trak->sample_to_chunk_entries * sizeof(ngx_mp4_stsc_entry_t);
+
+ trak->size += atom_size;
+
+ atom = trak->out[NGX_HTTP_MP4_STSC_ATOM].buf;
+ stsc_atom = (ngx_mp4_stsc_atom_t *) atom->pos;
+
+ ngx_mp4_set_32value(stsc_atom->size, atom_size);
+ ngx_mp4_set_32value(stsc_atom->entries, trak->sample_to_chunk_entries);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak, ngx_uint_t start)
+{
+ uint32_t start_sample, chunk, samples, id, next_chunk, n,
+ prev_samples;
+ ngx_buf_t *data, *buf;
+ ngx_uint_t entries, target_chunk, chunk_samples;
+ ngx_mp4_stsc_entry_t *entry, *end, *first;
+
+ entries = trak->sample_to_chunk_entries - 1;
+
+ if (start) {
+ start_sample = (uint32_t) trak->start_sample;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 stsc crop start_sample:%uD", start_sample);
+
+ } else if (mp4->length) {
+ start_sample = (uint32_t) (trak->end_sample - trak->start_sample);
+ samples = 0;
+
+ data = trak->out[NGX_HTTP_MP4_STSC_START].buf;
+
+ if (data) {
+ entry = (ngx_mp4_stsc_entry_t *) data->pos;
+ samples = ngx_mp4_get_32value(entry->samples);
+ entries--;
+
+ if (samples > start_sample) {
+ samples = start_sample;
+ ngx_mp4_set_32value(entry->samples, samples);
+ }
+
+ start_sample -= samples;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 stsc crop end_sample:%uD, ext_samples:%uD",
+ start_sample, samples);
+
+ } else {
+ return NGX_OK;
+ }
+
+ data = trak->out[NGX_HTTP_MP4_STSC_DATA].buf;
+
+ entry = (ngx_mp4_stsc_entry_t *) data->pos;
+ end = (ngx_mp4_stsc_entry_t *) data->last;
+
+ chunk = ngx_mp4_get_32value(entry->chunk);
+ samples = ngx_mp4_get_32value(entry->samples);
+ id = ngx_mp4_get_32value(entry->id);
+ prev_samples = 0;
+ entry++;
+
+ while (entry < end) {
+
+ next_chunk = ngx_mp4_get_32value(entry->chunk);
+
+ ngx_log_debug5(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "sample:%uD, chunk:%uD, chunks:%uD, "
+ "samples:%uD, id:%uD",
+ start_sample, chunk, next_chunk - chunk, samples, id);
+
+ n = (next_chunk - chunk) * samples;
+
+ if (start_sample < n) {
+ goto found;
+ }
+
+ start_sample -= n;
+
+ prev_samples = samples;
+ chunk = next_chunk;
+ samples = ngx_mp4_get_32value(entry->samples);
+ id = ngx_mp4_get_32value(entry->id);
+ entries--;
+ entry++;
+ }
+
+ next_chunk = trak->chunks + 1;
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "sample:%uD, chunk:%uD, chunks:%uD, samples:%uD",
+ start_sample, chunk, next_chunk - chunk, samples);
+
+ n = (next_chunk - chunk) * samples;
+
+ if (start_sample > n) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "%s time is out mp4 stsc chunks in \"%s\"",
+ start ? "start" : "end", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+found:
+
+ entries++;
+ entry--;
+
+ if (samples == 0) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "zero number of samples in \"%s\"",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ target_chunk = chunk - 1;
+ target_chunk += start_sample / samples;
+ chunk_samples = start_sample % samples;
+
+ if (start) {
+ data->pos = (u_char *) entry;
+
+ trak->sample_to_chunk_entries = entries;
+ trak->start_chunk = target_chunk;
+ trak->start_chunk_samples = chunk_samples;
+
+ ngx_mp4_set_32value(entry->chunk, trak->start_chunk + 1);
+
+ samples -= chunk_samples;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "start_chunk:%ui, start_chunk_samples:%ui",
+ trak->start_chunk, trak->start_chunk_samples);
+
+ } else {
+ if (start_sample) {
+ data->last = (u_char *) (entry + 1);
+ trak->sample_to_chunk_entries -= entries - 1;
+ trak->end_chunk_samples = samples;
+
+ } else {
+ data->last = (u_char *) entry;
+ trak->sample_to_chunk_entries -= entries;
+ trak->end_chunk_samples = prev_samples;
+ }
+
+ if (chunk_samples) {
+ trak->end_chunk = target_chunk + 1;
+ trak->end_chunk_samples = chunk_samples;
+
+ } else {
+ trak->end_chunk = target_chunk;
+ }
+
+ samples = chunk_samples;
+ next_chunk = chunk + 1;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "end_chunk:%ui, end_chunk_samples:%ui",
+ trak->end_chunk, trak->end_chunk_samples);
+ }
+
+ if (chunk_samples && next_chunk - target_chunk == 2) {
+
+ ngx_mp4_set_32value(entry->samples, samples);
+
+ } else if (chunk_samples && start) {
+
+ first = &trak->stsc_start_chunk_entry;
+ ngx_mp4_set_32value(first->chunk, 1);
+ ngx_mp4_set_32value(first->samples, samples);
+ ngx_mp4_set_32value(first->id, id);
+
+ buf = &trak->stsc_start_chunk_buf;
+ buf->temporary = 1;
+ buf->pos = (u_char *) first;
+ buf->last = (u_char *) first + sizeof(ngx_mp4_stsc_entry_t);
+
+ trak->out[NGX_HTTP_MP4_STSC_START].buf = buf;
+
+ ngx_mp4_set_32value(entry->chunk, trak->start_chunk + 2);
+
+ trak->sample_to_chunk_entries++;
+
+ } else if (chunk_samples) {
+
+ first = &trak->stsc_end_chunk_entry;
+ ngx_mp4_set_32value(first->chunk, trak->end_chunk - trak->start_chunk);
+ ngx_mp4_set_32value(first->samples, samples);
+ ngx_mp4_set_32value(first->id, id);
+
+ buf = &trak->stsc_end_chunk_buf;
+ buf->temporary = 1;
+ buf->pos = (u_char *) first;
+ buf->last = (u_char *) first + sizeof(ngx_mp4_stsc_entry_t);
+
+ trak->out[NGX_HTTP_MP4_STSC_END].buf = buf;
+
+ trak->sample_to_chunk_entries++;
+ }
+
+ return NGX_OK;
+}
+
+
+typedef struct {
+ u_char size[4];
+ u_char name[4];
+ u_char version[1];
+ u_char flags[3];
+ u_char uniform_size[4];
+ u_char entries[4];
+} ngx_mp4_stsz_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_stsz_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header, *atom_table, *atom_end;
+ size_t atom_size;
+ uint32_t entries, size;
+ ngx_buf_t *atom, *data;
+ ngx_mp4_stsz_atom_t *stsz_atom;
+ ngx_http_mp4_trak_t *trak;
+
+ /* sample sizes atom */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stsz atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ stsz_atom = (ngx_mp4_stsz_atom_t *) atom_header;
+ ngx_mp4_set_atom_name(stsz_atom, 's', 't', 's', 'z');
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_stsz_atom_t) > atom_data_size) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 stsz atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ size = ngx_mp4_get_32value(stsz_atom->uniform_size);
+ entries = ngx_mp4_get_32value(stsz_atom->entries);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "sample uniform size:%uD, entries:%uD", size, entries);
+
+ trak = ngx_mp4_last_trak(mp4);
+ trak->sample_sizes_entries = entries;
+
+ atom_table = atom_header + sizeof(ngx_mp4_stsz_atom_t);
+
+ atom = &trak->stsz_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_table;
+
+ trak->out[NGX_HTTP_MP4_STSZ_ATOM].buf = atom;
+
+ if (size == 0) {
+ if (ngx_mp4_atom_data_size(ngx_mp4_stsz_atom_t)
+ + entries * sizeof(uint32_t) > atom_data_size)
+ {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 stsz atom too small",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ atom_end = atom_table + entries * sizeof(uint32_t);
+
+ data = &trak->stsz_data_buf;
+ data->temporary = 1;
+ data->pos = atom_table;
+ data->last = atom_end;
+
+ trak->out[NGX_HTTP_MP4_STSZ_DATA].buf = data;
+
+ } else {
+ /* if size != 0 then all samples are the same size */
+ /* TODO : chunk samples */
+ atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
+ ngx_mp4_set_32value(atom_header, atom_size);
+ trak->size += atom_size;
+ }
+
+ ngx_mp4_atom_next(mp4, atom_data_size);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_update_stsz_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak)
+{
+ size_t atom_size;
+ uint32_t *pos, *end, entries;
+ ngx_buf_t *atom, *data;
+ ngx_mp4_stsz_atom_t *stsz_atom;
+
+ /*
+ * mdia.minf.stbl.stsz updating requires trak->start_sample
+ * from mdia.minf.stbl.stts which depends on value from mdia.mdhd
+ * atom which may reside after mdia.minf
+ */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 stsz atom update");
+
+ data = trak->out[NGX_HTTP_MP4_STSZ_DATA].buf;
+
+ if (data) {
+ entries = trak->sample_sizes_entries;
+
+ if (trak->start_sample > entries) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "start time is out mp4 stsz samples in \"%s\"",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ entries -= trak->start_sample;
+ data->pos += trak->start_sample * sizeof(uint32_t);
+ end = (uint32_t *) data->pos;
+
+ for (pos = end - trak->start_chunk_samples; pos < end; pos++) {
+ trak->start_chunk_samples_size += ngx_mp4_get_32value(pos);
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "chunk samples sizes:%uL",
+ trak->start_chunk_samples_size);
+
+ if (mp4->length) {
+ if (trak->end_sample - trak->start_sample > entries) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "end time is out mp4 stsz samples in \"%s\"",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ entries = trak->end_sample - trak->start_sample;
+ data->last = data->pos + entries * sizeof(uint32_t);
+ end = (uint32_t *) data->last;
+
+ for (pos = end - trak->end_chunk_samples; pos < end; pos++) {
+ trak->end_chunk_samples_size += ngx_mp4_get_32value(pos);
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 stsz end_chunk_samples_size:%uL",
+ trak->end_chunk_samples_size);
+ }
+
+ atom_size = sizeof(ngx_mp4_stsz_atom_t) + (data->last - data->pos);
+ trak->size += atom_size;
+
+ atom = trak->out[NGX_HTTP_MP4_STSZ_ATOM].buf;
+ stsz_atom = (ngx_mp4_stsz_atom_t *) atom->pos;
+
+ ngx_mp4_set_32value(stsz_atom->size, atom_size);
+ ngx_mp4_set_32value(stsz_atom->entries, entries);
+ }
+
+ return NGX_OK;
+}
+
+
+typedef struct {
+ u_char size[4];
+ u_char name[4];
+ u_char version[1];
+ u_char flags[3];
+ u_char entries[4];
+} ngx_mp4_stco_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_stco_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header, *atom_table, *atom_end;
+ uint32_t entries;
+ ngx_buf_t *atom, *data;
+ ngx_mp4_stco_atom_t *stco_atom;
+ ngx_http_mp4_trak_t *trak;
+
+ /* chunk offsets atom */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stco atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ stco_atom = (ngx_mp4_stco_atom_t *) atom_header;
+ ngx_mp4_set_atom_name(stco_atom, 's', 't', 'c', 'o');
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_stco_atom_t) > atom_data_size) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 stco atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ entries = ngx_mp4_get_32value(stco_atom->entries);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "chunks:%uD", entries);
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_stco_atom_t)
+ + entries * sizeof(uint32_t) > atom_data_size)
+ {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 stco atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ atom_table = atom_header + sizeof(ngx_mp4_stco_atom_t);
+ atom_end = atom_table + entries * sizeof(uint32_t);
+
+ trak = ngx_mp4_last_trak(mp4);
+ trak->chunks = entries;
+
+ atom = &trak->stco_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_table;
+
+ data = &trak->stco_data_buf;
+ data->temporary = 1;
+ data->pos = atom_table;
+ data->last = atom_end;
+
+ trak->out[NGX_HTTP_MP4_STCO_ATOM].buf = atom;
+ trak->out[NGX_HTTP_MP4_STCO_DATA].buf = data;
+
+ ngx_mp4_atom_next(mp4, atom_data_size);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_update_stco_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak)
+{
+ size_t atom_size;
+ uint32_t entries;
+ ngx_buf_t *atom, *data;
+ ngx_mp4_stco_atom_t *stco_atom;
+
+ /*
+ * mdia.minf.stbl.stco updating requires trak->start_chunk
+ * from mdia.minf.stbl.stsc which depends on value from mdia.mdhd
+ * atom which may reside after mdia.minf
+ */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 stco atom update");
+
+ data = trak->out[NGX_HTTP_MP4_STCO_DATA].buf;
+
+ if (data == NULL) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "no mp4 stco atoms were found in \"%s\"",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ if (trak->start_chunk > trak->chunks) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "start time is out mp4 stco chunks in \"%s\"",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ data->pos += trak->start_chunk * sizeof(uint32_t);
+
+ trak->start_offset = ngx_mp4_get_32value(data->pos);
+ trak->start_offset += trak->start_chunk_samples_size;
+ ngx_mp4_set_32value(data->pos, trak->start_offset);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "start chunk offset:%O", trak->start_offset);
+
+ if (mp4->length) {
+
+ if (trak->end_chunk > trak->chunks) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "end time is out mp4 stco chunks in \"%s\"",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ entries = trak->end_chunk - trak->start_chunk;
+ data->last = data->pos + entries * sizeof(uint32_t);
+
+ if (entries) {
+ trak->end_offset =
+ ngx_mp4_get_32value(data->last - sizeof(uint32_t));
+ trak->end_offset += trak->end_chunk_samples_size;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "end chunk offset:%O", trak->end_offset);
+ }
+
+ } else {
+ entries = trak->chunks - trak->start_chunk;
+ trak->end_offset = mp4->mdat_data.buf->file_last;
+ }
+
+ if (entries == 0) {
+ trak->start_offset = mp4->end;
+ trak->end_offset = 0;
+ }
+
+ atom_size = sizeof(ngx_mp4_stco_atom_t) + (data->last - data->pos);
+ trak->size += atom_size;
+
+ atom = trak->out[NGX_HTTP_MP4_STCO_ATOM].buf;
+ stco_atom = (ngx_mp4_stco_atom_t *) atom->pos;
+
+ ngx_mp4_set_32value(stco_atom->size, atom_size);
+ ngx_mp4_set_32value(stco_atom->entries, entries);
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_mp4_adjust_stco_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak, int32_t adjustment)
+{
+ uint32_t offset, *entry, *end;
+ ngx_buf_t *data;
+
+ /*
+ * moov.trak.mdia.minf.stbl.stco adjustment requires
+ * minimal start offset of all traks and new moov atom size
+ */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 stco atom adjustment");
+
+ data = trak->out[NGX_HTTP_MP4_STCO_DATA].buf;
+ entry = (uint32_t *) data->pos;
+ end = (uint32_t *) data->last;
+
+ while (entry < end) {
+ offset = ngx_mp4_get_32value(entry);
+ offset += adjustment;
+ ngx_mp4_set_32value(entry, offset);
+ entry++;
+ }
+}
+
+
+typedef struct {
+ u_char size[4];
+ u_char name[4];
+ u_char version[1];
+ u_char flags[3];
+ u_char entries[4];
+} ngx_mp4_co64_atom_t;
+
+
+static ngx_int_t
+ngx_http_mp4_read_co64_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
+{
+ u_char *atom_header, *atom_table, *atom_end;
+ uint32_t entries;
+ ngx_buf_t *atom, *data;
+ ngx_mp4_co64_atom_t *co64_atom;
+ ngx_http_mp4_trak_t *trak;
+
+ /* chunk offsets atom */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 co64 atom");
+
+ atom_header = ngx_mp4_atom_header(mp4);
+ co64_atom = (ngx_mp4_co64_atom_t *) atom_header;
+ ngx_mp4_set_atom_name(co64_atom, 'c', 'o', '6', '4');
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_co64_atom_t) > atom_data_size) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 co64 atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ entries = ngx_mp4_get_32value(co64_atom->entries);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "chunks:%uD", entries);
+
+ if (ngx_mp4_atom_data_size(ngx_mp4_co64_atom_t)
+ + entries * sizeof(uint64_t) > atom_data_size)
+ {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "\"%s\" mp4 co64 atom too small", mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ atom_table = atom_header + sizeof(ngx_mp4_co64_atom_t);
+ atom_end = atom_table + entries * sizeof(uint64_t);
+
+ trak = ngx_mp4_last_trak(mp4);
+ trak->chunks = entries;
+
+ atom = &trak->co64_atom_buf;
+ atom->temporary = 1;
+ atom->pos = atom_header;
+ atom->last = atom_table;
+
+ data = &trak->co64_data_buf;
+ data->temporary = 1;
+ data->pos = atom_table;
+ data->last = atom_end;
+
+ trak->out[NGX_HTTP_MP4_CO64_ATOM].buf = atom;
+ trak->out[NGX_HTTP_MP4_CO64_DATA].buf = data;
+
+ ngx_mp4_atom_next(mp4, atom_data_size);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_mp4_update_co64_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak)
+{
+ size_t atom_size;
+ uint64_t entries;
+ ngx_buf_t *atom, *data;
+ ngx_mp4_co64_atom_t *co64_atom;
+
+ /*
+ * mdia.minf.stbl.co64 updating requires trak->start_chunk
+ * from mdia.minf.stbl.stsc which depends on value from mdia.mdhd
+ * atom which may reside after mdia.minf
+ */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 co64 atom update");
+
+ data = trak->out[NGX_HTTP_MP4_CO64_DATA].buf;
+
+ if (data == NULL) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "no mp4 co64 atoms were found in \"%s\"",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ if (trak->start_chunk > trak->chunks) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "start time is out mp4 co64 chunks in \"%s\"",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ data->pos += trak->start_chunk * sizeof(uint64_t);
+
+ trak->start_offset = ngx_mp4_get_64value(data->pos);
+ trak->start_offset += trak->start_chunk_samples_size;
+ ngx_mp4_set_64value(data->pos, trak->start_offset);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "start chunk offset:%O", trak->start_offset);
+
+ if (mp4->length) {
+
+ if (trak->end_chunk > trak->chunks) {
+ ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+ "end time is out mp4 co64 chunks in \"%s\"",
+ mp4->file.name.data);
+ return NGX_ERROR;
+ }
+
+ entries = trak->end_chunk - trak->start_chunk;
+ data->last = data->pos + entries * sizeof(uint64_t);
+
+ if (entries) {
+ trak->end_offset =
+ ngx_mp4_get_64value(data->last - sizeof(uint64_t));
+ trak->end_offset += trak->end_chunk_samples_size;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "end chunk offset:%O", trak->end_offset);
+ }
+
+ } else {
+ entries = trak->chunks - trak->start_chunk;
+ trak->end_offset = mp4->mdat_data.buf->file_last;
+ }
+
+ if (entries == 0) {
+ trak->start_offset = mp4->end;
+ trak->end_offset = 0;
+ }
+
+ atom_size = sizeof(ngx_mp4_co64_atom_t) + (data->last - data->pos);
+ trak->size += atom_size;
+
+ atom = trak->out[NGX_HTTP_MP4_CO64_ATOM].buf;
+ co64_atom = (ngx_mp4_co64_atom_t *) atom->pos;
+
+ ngx_mp4_set_32value(co64_atom->size, atom_size);
+ ngx_mp4_set_32value(co64_atom->entries, entries);
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_mp4_adjust_co64_atom(ngx_http_mp4_file_t *mp4,
+ ngx_http_mp4_trak_t *trak, off_t adjustment)
+{
+ uint64_t offset, *entry, *end;
+ ngx_buf_t *data;
+
+ /*
+ * moov.trak.mdia.minf.stbl.co64 adjustment requires
+ * minimal start offset of all traks and new moov atom size
+ */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+ "mp4 co64 atom adjustment");
+
+ data = trak->out[NGX_HTTP_MP4_CO64_DATA].buf;
+ entry = (uint64_t *) data->pos;
+ end = (uint64_t *) data->last;
+
+ while (entry < end) {
+ offset = ngx_mp4_get_64value(entry);
+ offset += adjustment;
+ ngx_mp4_set_64value(entry, offset);
+ entry++;
+ }
+}
+
+
+static char *
+ngx_http_mp4(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf;
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+ clcf->handler = ngx_http_mp4_handler;
+
+ return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_mp4_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_mp4_conf_t *conf;
+
+ conf = ngx_palloc(cf->pool, sizeof(ngx_http_mp4_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->buffer_size = NGX_CONF_UNSET_SIZE;
+ conf->max_buffer_size = NGX_CONF_UNSET_SIZE;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_mp4_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_mp4_conf_t *prev = parent;
+ ngx_http_mp4_conf_t *conf = child;
+
+ ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 512 * 1024);
+ ngx_conf_merge_size_value(conf->max_buffer_size, prev->max_buffer_size,
+ 10 * 1024 * 1024);
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_not_modified_filter_module.c b/app/nginx/src/http/modules/ngx_http_not_modified_filter_module.c
new file mode 100644
index 0000000..032ba96
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_not_modified_filter_module.c
@@ -0,0 +1,266 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_uint_t ngx_http_test_if_unmodified(ngx_http_request_t *r);
+static ngx_uint_t ngx_http_test_if_modified(ngx_http_request_t *r);
+static ngx_uint_t ngx_http_test_if_match(ngx_http_request_t *r,
+ ngx_table_elt_t *header, ngx_uint_t weak);
+static ngx_int_t ngx_http_not_modified_filter_init(ngx_conf_t *cf);
+
+
+static ngx_http_module_t ngx_http_not_modified_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_not_modified_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_not_modified_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_not_modified_filter_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+
+
+static ngx_int_t
+ngx_http_not_modified_header_filter(ngx_http_request_t *r)
+{
+ if (r->headers_out.status != NGX_HTTP_OK
+ || r != r->main
+ || r->disable_not_modified)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ if (r->headers_in.if_unmodified_since
+ && !ngx_http_test_if_unmodified(r))
+ {
+ return ngx_http_filter_finalize_request(r, NULL,
+ NGX_HTTP_PRECONDITION_FAILED);
+ }
+
+ if (r->headers_in.if_match
+ && !ngx_http_test_if_match(r, r->headers_in.if_match, 0))
+ {
+ return ngx_http_filter_finalize_request(r, NULL,
+ NGX_HTTP_PRECONDITION_FAILED);
+ }
+
+ if (r->headers_in.if_modified_since || r->headers_in.if_none_match) {
+
+ if (r->headers_in.if_modified_since
+ && ngx_http_test_if_modified(r))
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ if (r->headers_in.if_none_match
+ && !ngx_http_test_if_match(r, r->headers_in.if_none_match, 1))
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ /* not modified */
+
+ r->headers_out.status = NGX_HTTP_NOT_MODIFIED;
+ r->headers_out.status_line.len = 0;
+ r->headers_out.content_type.len = 0;
+ ngx_http_clear_content_length(r);
+ ngx_http_clear_accept_ranges(r);
+
+ if (r->headers_out.content_encoding) {
+ r->headers_out.content_encoding->hash = 0;
+ r->headers_out.content_encoding = NULL;
+ }
+
+ return ngx_http_next_header_filter(r);
+ }
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_uint_t
+ngx_http_test_if_unmodified(ngx_http_request_t *r)
+{
+ time_t iums;
+
+ if (r->headers_out.last_modified_time == (time_t) -1) {
+ return 0;
+ }
+
+ iums = ngx_parse_http_time(r->headers_in.if_unmodified_since->value.data,
+ r->headers_in.if_unmodified_since->value.len);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http iums:%T lm:%T", iums, r->headers_out.last_modified_time);
+
+ if (iums >= r->headers_out.last_modified_time) {
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static ngx_uint_t
+ngx_http_test_if_modified(ngx_http_request_t *r)
+{
+ time_t ims;
+ ngx_http_core_loc_conf_t *clcf;
+
+ if (r->headers_out.last_modified_time == (time_t) -1) {
+ return 1;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->if_modified_since == NGX_HTTP_IMS_OFF) {
+ return 1;
+ }
+
+ ims = ngx_parse_http_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:%T lm:%T", ims, r->headers_out.last_modified_time);
+
+ if (ims == r->headers_out.last_modified_time) {
+ return 0;
+ }
+
+ if (clcf->if_modified_since == NGX_HTTP_IMS_EXACT
+ || ims < r->headers_out.last_modified_time)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static ngx_uint_t
+ngx_http_test_if_match(ngx_http_request_t *r, ngx_table_elt_t *header,
+ ngx_uint_t weak)
+{
+ u_char *start, *end, ch;
+ ngx_str_t etag, *list;
+
+ list = &header->value;
+
+ if (list->len == 1 && list->data[0] == '*') {
+ return 1;
+ }
+
+ if (r->headers_out.etag == NULL) {
+ return 0;
+ }
+
+ etag = r->headers_out.etag->value;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http im:\"%V\" etag:%V", list, &etag);
+
+ if (weak
+ && etag.len > 2
+ && etag.data[0] == 'W'
+ && etag.data[1] == '/')
+ {
+ etag.len -= 2;
+ etag.data += 2;
+ }
+
+ start = list->data;
+ end = list->data + list->len;
+
+ while (start < end) {
+
+ if (weak
+ && end - start > 2
+ && start[0] == 'W'
+ && start[1] == '/')
+ {
+ start += 2;
+ }
+
+ if (etag.len > (size_t) (end - start)) {
+ return 0;
+ }
+
+ if (ngx_strncmp(start, etag.data, etag.len) != 0) {
+ goto skip;
+ }
+
+ start += etag.len;
+
+ while (start < end) {
+ ch = *start;
+
+ if (ch == ' ' || ch == '\t') {
+ start++;
+ continue;
+ }
+
+ break;
+ }
+
+ if (start == end || *start == ',') {
+ return 1;
+ }
+
+ skip:
+
+ while (start < end && *start != ',') { start++; }
+ while (start < end) {
+ ch = *start;
+
+ if (ch == ' ' || ch == '\t' || ch == ',') {
+ start++;
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+static ngx_int_t
+ngx_http_not_modified_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_not_modified_header_filter;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_proxy_module.c b/app/nginx/src/http/modules/ngx_http_proxy_module.c
new file mode 100644
index 0000000..e594d06
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_proxy_module.c
@@ -0,0 +1,4424 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_array_t caches; /* ngx_http_file_cache_t * */
+} ngx_http_proxy_main_conf_t;
+
+
+typedef struct ngx_http_proxy_rewrite_s ngx_http_proxy_rewrite_t;
+
+typedef ngx_int_t (*ngx_http_proxy_rewrite_pt)(ngx_http_request_t *r,
+ ngx_table_elt_t *h, size_t prefix, size_t len,
+ ngx_http_proxy_rewrite_t *pr);
+
+struct ngx_http_proxy_rewrite_s {
+ ngx_http_proxy_rewrite_pt handler;
+
+ union {
+ ngx_http_complex_value_t complex;
+#if (NGX_PCRE)
+ ngx_http_regex_t *regex;
+#endif
+ } pattern;
+
+ ngx_http_complex_value_t replacement;
+};
+
+
+typedef struct {
+ ngx_str_t key_start;
+ ngx_str_t schema;
+ ngx_str_t host_header;
+ ngx_str_t port;
+ ngx_str_t uri;
+} ngx_http_proxy_vars_t;
+
+
+typedef struct {
+ ngx_array_t *flushes;
+ ngx_array_t *lengths;
+ ngx_array_t *values;
+ ngx_hash_t hash;
+} ngx_http_proxy_headers_t;
+
+
+typedef struct {
+ ngx_http_upstream_conf_t upstream;
+
+ ngx_array_t *body_flushes;
+ ngx_array_t *body_lengths;
+ ngx_array_t *body_values;
+ ngx_str_t body_source;
+
+ ngx_http_proxy_headers_t headers;
+#if (NGX_HTTP_CACHE)
+ ngx_http_proxy_headers_t headers_cache;
+#endif
+ ngx_array_t *headers_source;
+
+ ngx_array_t *proxy_lengths;
+ ngx_array_t *proxy_values;
+
+ ngx_array_t *redirects;
+ ngx_array_t *cookie_domains;
+ ngx_array_t *cookie_paths;
+
+ ngx_http_complex_value_t *method;
+ ngx_str_t location;
+ ngx_str_t url;
+
+#if (NGX_HTTP_CACHE)
+ ngx_http_complex_value_t cache_key;
+#endif
+
+ ngx_http_proxy_vars_t vars;
+
+ ngx_flag_t redirect;
+
+ ngx_uint_t http_version;
+
+ ngx_uint_t headers_hash_max_size;
+ ngx_uint_t headers_hash_bucket_size;
+
+#if (NGX_HTTP_SSL)
+ ngx_uint_t ssl;
+ ngx_uint_t ssl_protocols;
+ ngx_str_t ssl_ciphers;
+ ngx_uint_t ssl_verify_depth;
+ ngx_str_t ssl_trusted_certificate;
+ ngx_str_t ssl_crl;
+ ngx_str_t ssl_certificate;
+ ngx_str_t ssl_certificate_key;
+ ngx_array_t *ssl_passwords;
+#endif
+} ngx_http_proxy_loc_conf_t;
+
+
+typedef struct {
+ ngx_http_status_t status;
+ ngx_http_chunked_t chunked;
+ ngx_http_proxy_vars_t vars;
+ off_t internal_body_length;
+
+ ngx_chain_t *free;
+ ngx_chain_t *busy;
+
+ unsigned head:1;
+ unsigned internal_chunked:1;
+ unsigned header_sent:1;
+} ngx_http_proxy_ctx_t;
+
+
+static ngx_int_t ngx_http_proxy_eval(ngx_http_request_t *r,
+ ngx_http_proxy_ctx_t *ctx, ngx_http_proxy_loc_conf_t *plcf);
+#if (NGX_HTTP_CACHE)
+static ngx_int_t ngx_http_proxy_create_key(ngx_http_request_t *r);
+#endif
+static ngx_int_t ngx_http_proxy_create_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_proxy_reinit_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_proxy_body_output_filter(void *data, ngx_chain_t *in);
+static ngx_int_t ngx_http_proxy_process_status_line(ngx_http_request_t *r);
+static ngx_int_t ngx_http_proxy_process_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_proxy_input_filter_init(void *data);
+static ngx_int_t ngx_http_proxy_copy_filter(ngx_event_pipe_t *p,
+ ngx_buf_t *buf);
+static ngx_int_t ngx_http_proxy_chunked_filter(ngx_event_pipe_t *p,
+ ngx_buf_t *buf);
+static ngx_int_t ngx_http_proxy_non_buffered_copy_filter(void *data,
+ ssize_t bytes);
+static ngx_int_t ngx_http_proxy_non_buffered_chunked_filter(void *data,
+ ssize_t bytes);
+static void ngx_http_proxy_abort_request(ngx_http_request_t *r);
+static void ngx_http_proxy_finalize_request(ngx_http_request_t *r,
+ ngx_int_t rc);
+
+static ngx_int_t ngx_http_proxy_host_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_proxy_port_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t
+ ngx_http_proxy_add_x_forwarded_for_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t
+ ngx_http_proxy_internal_body_length_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_proxy_internal_chunked_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_proxy_rewrite_redirect(ngx_http_request_t *r,
+ ngx_table_elt_t *h, size_t prefix);
+static ngx_int_t ngx_http_proxy_rewrite_cookie(ngx_http_request_t *r,
+ ngx_table_elt_t *h);
+static ngx_int_t ngx_http_proxy_rewrite_cookie_value(ngx_http_request_t *r,
+ ngx_table_elt_t *h, u_char *value, ngx_array_t *rewrites);
+static ngx_int_t ngx_http_proxy_rewrite(ngx_http_request_t *r,
+ ngx_table_elt_t *h, size_t prefix, size_t len, ngx_str_t *replacement);
+
+static ngx_int_t ngx_http_proxy_add_variables(ngx_conf_t *cf);
+static void *ngx_http_proxy_create_main_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 ngx_int_t ngx_http_proxy_init_headers(ngx_conf_t *cf,
+ ngx_http_proxy_loc_conf_t *conf, ngx_http_proxy_headers_t *headers,
+ ngx_keyval_t *default_headers);
+
+static char *ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_proxy_redirect(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_proxy_cookie_domain(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_proxy_cookie_path(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_proxy_store(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+#if (NGX_HTTP_CACHE)
+static char *ngx_http_proxy_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_proxy_cache_key(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+#endif
+#if (NGX_HTTP_SSL)
+static char *ngx_http_proxy_ssl_password_file(ngx_conf_t *cf,
+ ngx_command_t *cmd, void *conf);
+#endif
+
+static char *ngx_http_proxy_lowat_check(ngx_conf_t *cf, void *post, void *data);
+
+static ngx_int_t ngx_http_proxy_rewrite_regex(ngx_conf_t *cf,
+ ngx_http_proxy_rewrite_t *pr, ngx_str_t *regex, ngx_uint_t caseless);
+
+#if (NGX_HTTP_SSL)
+static ngx_int_t ngx_http_proxy_set_ssl(ngx_conf_t *cf,
+ ngx_http_proxy_loc_conf_t *plcf);
+#endif
+static void ngx_http_proxy_set_vars(ngx_url_t *u, ngx_http_proxy_vars_t *v);
+
+
+static ngx_conf_post_t ngx_http_proxy_lowat_post =
+ { ngx_http_proxy_lowat_check };
+
+
+static ngx_conf_bitmask_t ngx_http_proxy_next_upstream_masks[] = {
+ { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
+ { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT },
+ { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER },
+ { ngx_string("non_idempotent"), NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT },
+ { ngx_string("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 },
+ { ngx_string("http_502"), NGX_HTTP_UPSTREAM_FT_HTTP_502 },
+ { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 },
+ { ngx_string("http_504"), NGX_HTTP_UPSTREAM_FT_HTTP_504 },
+ { ngx_string("http_403"), NGX_HTTP_UPSTREAM_FT_HTTP_403 },
+ { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 },
+ { ngx_string("http_429"), NGX_HTTP_UPSTREAM_FT_HTTP_429 },
+ { ngx_string("updating"), NGX_HTTP_UPSTREAM_FT_UPDATING },
+ { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF },
+ { ngx_null_string, 0 }
+};
+
+
+#if (NGX_HTTP_SSL)
+
+static ngx_conf_bitmask_t ngx_http_proxy_ssl_protocols[] = {
+ { ngx_string("SSLv2"), NGX_SSL_SSLv2 },
+ { ngx_string("SSLv3"), NGX_SSL_SSLv3 },
+ { ngx_string("TLSv1"), NGX_SSL_TLSv1 },
+ { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 },
+ { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 },
+ { ngx_null_string, 0 }
+};
+
+#endif
+
+
+static ngx_conf_enum_t ngx_http_proxy_http_version[] = {
+ { ngx_string("1.0"), NGX_HTTP_VERSION_10 },
+ { ngx_string("1.1"), NGX_HTTP_VERSION_11 },
+ { ngx_null_string, 0 }
+};
+
+
+ngx_module_t ngx_http_proxy_module;
+
+
+static ngx_command_t ngx_http_proxy_commands[] = {
+
+ { ngx_string("proxy_pass"),
+ NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1,
+ ngx_http_proxy_pass,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("proxy_redirect"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+ ngx_http_proxy_redirect,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("proxy_cookie_domain"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+ ngx_http_proxy_cookie_domain,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("proxy_cookie_path"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+ ngx_http_proxy_cookie_path,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("proxy_store"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_proxy_store,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("proxy_store_access"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
+ ngx_conf_set_access_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.store_access),
+ NULL },
+
+ { ngx_string("proxy_buffering"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.buffering),
+ NULL },
+
+ { ngx_string("proxy_request_buffering"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.request_buffering),
+ NULL },
+
+ { ngx_string("proxy_ignore_client_abort"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.ignore_client_abort),
+ NULL },
+
+ { ngx_string("proxy_bind"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+ ngx_http_upstream_bind_set_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.local),
+ NULL },
+
+ { ngx_string("proxy_connect_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.connect_timeout),
+ NULL },
+
+ { ngx_string("proxy_send_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.send_timeout),
+ NULL },
+
+ { ngx_string("proxy_send_lowat"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.send_lowat),
+ &ngx_http_proxy_lowat_post },
+
+ { ngx_string("proxy_intercept_errors"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.intercept_errors),
+ NULL },
+
+ { ngx_string("proxy_set_header"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_conf_set_keyval_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, headers_source),
+ NULL },
+
+ { ngx_string("proxy_headers_hash_max_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, headers_hash_max_size),
+ NULL },
+
+ { ngx_string("proxy_headers_hash_bucket_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, headers_hash_bucket_size),
+ NULL },
+
+ { ngx_string("proxy_set_body"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, body_source),
+ NULL },
+
+ { ngx_string("proxy_method"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_set_complex_value_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, method),
+ NULL },
+
+ { ngx_string("proxy_pass_request_headers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.pass_request_headers),
+ NULL },
+
+ { ngx_string("proxy_pass_request_body"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.pass_request_body),
+ NULL },
+
+ { ngx_string("proxy_buffer_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.buffer_size),
+ NULL },
+
+ { ngx_string("proxy_read_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.read_timeout),
+ NULL },
+
+ { ngx_string("proxy_buffers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_conf_set_bufs_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.bufs),
+ NULL },
+
+ { ngx_string("proxy_busy_buffers_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.busy_buffers_size_conf),
+ NULL },
+
+ { ngx_string("proxy_force_ranges"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.force_ranges),
+ NULL },
+
+ { ngx_string("proxy_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_proxy_loc_conf_t, upstream.limit_rate),
+ NULL },
+
+#if (NGX_HTTP_CACHE)
+
+ { ngx_string("proxy_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_proxy_cache,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("proxy_cache_key"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_proxy_cache_key,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("proxy_cache_path"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE,
+ ngx_http_file_cache_set_slot,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ offsetof(ngx_http_proxy_main_conf_t, caches),
+ &ngx_http_proxy_module },
+
+ { ngx_string("proxy_cache_bypass"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_set_predicate_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_bypass),
+ NULL },
+
+ { ngx_string("proxy_no_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_set_predicate_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.no_cache),
+ NULL },
+
+ { ngx_string("proxy_cache_valid"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_file_cache_valid_set_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_valid),
+ NULL },
+
+ { ngx_string("proxy_cache_min_uses"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_min_uses),
+ NULL },
+
+ { ngx_string("proxy_cache_max_range_offset"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_off_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_max_range_offset),
+ NULL },
+
+ { ngx_string("proxy_cache_use_stale"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_use_stale),
+ &ngx_http_proxy_next_upstream_masks },
+
+ { ngx_string("proxy_cache_methods"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_methods),
+ &ngx_http_upstream_cache_method_mask },
+
+ { ngx_string("proxy_cache_lock"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_lock),
+ NULL },
+
+ { ngx_string("proxy_cache_lock_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_lock_timeout),
+ NULL },
+
+ { ngx_string("proxy_cache_lock_age"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_lock_age),
+ NULL },
+
+ { ngx_string("proxy_cache_revalidate"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_revalidate),
+ NULL },
+
+ { ngx_string("proxy_cache_convert_head"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_convert_head),
+ NULL },
+
+ { ngx_string("proxy_cache_background_update"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_background_update),
+ NULL },
+
+#endif
+
+ { ngx_string("proxy_temp_path"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
+ ngx_conf_set_path_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.temp_path),
+ NULL },
+
+ { ngx_string("proxy_max_temp_file_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.max_temp_file_size_conf),
+ NULL },
+
+ { ngx_string("proxy_temp_file_write_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.temp_file_write_size_conf),
+ NULL },
+
+ { ngx_string("proxy_next_upstream"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.next_upstream),
+ &ngx_http_proxy_next_upstream_masks },
+
+ { ngx_string("proxy_next_upstream_tries"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.next_upstream_tries),
+ NULL },
+
+ { ngx_string("proxy_next_upstream_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.next_upstream_timeout),
+ NULL },
+
+ { ngx_string("proxy_pass_header"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.pass_headers),
+ NULL },
+
+ { ngx_string("proxy_hide_header"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.hide_headers),
+ NULL },
+
+ { ngx_string("proxy_ignore_headers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.ignore_headers),
+ &ngx_http_upstream_ignore_headers_masks },
+
+ { ngx_string("proxy_http_version"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, http_version),
+ &ngx_http_proxy_http_version },
+
+#if (NGX_HTTP_SSL)
+
+ { ngx_string("proxy_ssl_session_reuse"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_session_reuse),
+ NULL },
+
+ { ngx_string("proxy_ssl_protocols"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, ssl_protocols),
+ &ngx_http_proxy_ssl_protocols },
+
+ { ngx_string("proxy_ssl_ciphers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, ssl_ciphers),
+ NULL },
+
+ { ngx_string("proxy_ssl_name"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_set_complex_value_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_name),
+ NULL },
+
+ { ngx_string("proxy_ssl_server_name"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_server_name),
+ NULL },
+
+ { ngx_string("proxy_ssl_verify"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_verify),
+ NULL },
+
+ { ngx_string("proxy_ssl_verify_depth"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, ssl_verify_depth),
+ NULL },
+
+ { ngx_string("proxy_ssl_trusted_certificate"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, ssl_trusted_certificate),
+ NULL },
+
+ { ngx_string("proxy_ssl_crl"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, ssl_crl),
+ NULL },
+
+ { ngx_string("proxy_ssl_certificate"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, ssl_certificate),
+ NULL },
+
+ { ngx_string("proxy_ssl_certificate_key"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, ssl_certificate_key),
+ NULL },
+
+ { ngx_string("proxy_ssl_password_file"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_proxy_ssl_password_file,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+#endif
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_proxy_module_ctx = {
+ ngx_http_proxy_add_variables, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ ngx_http_proxy_create_main_conf, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_proxy_create_loc_conf, /* create location configuration */
+ ngx_http_proxy_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_proxy_module = {
+ NGX_MODULE_V1,
+ &ngx_http_proxy_module_ctx, /* module context */
+ ngx_http_proxy_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static char ngx_http_proxy_version[] = " HTTP/1.0" CRLF;
+static char ngx_http_proxy_version_11[] = " HTTP/1.1" CRLF;
+
+
+static ngx_keyval_t ngx_http_proxy_headers[] = {
+ { ngx_string("Host"), ngx_string("$proxy_host") },
+ { ngx_string("Connection"), ngx_string("close") },
+ { ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") },
+ { ngx_string("Transfer-Encoding"), ngx_string("$proxy_internal_chunked") },
+ { ngx_string("TE"), ngx_string("") },
+ { ngx_string("Keep-Alive"), ngx_string("") },
+ { ngx_string("Expect"), ngx_string("") },
+ { ngx_string("Upgrade"), ngx_string("") },
+ { ngx_null_string, ngx_null_string }
+};
+
+
+static ngx_str_t ngx_http_proxy_hide_headers[] = {
+ ngx_string("Date"),
+ ngx_string("Server"),
+ ngx_string("X-Pad"),
+ ngx_string("X-Accel-Expires"),
+ ngx_string("X-Accel-Redirect"),
+ ngx_string("X-Accel-Limit-Rate"),
+ ngx_string("X-Accel-Buffering"),
+ ngx_string("X-Accel-Charset"),
+ ngx_null_string
+};
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_keyval_t ngx_http_proxy_cache_headers[] = {
+ { ngx_string("Host"), ngx_string("$proxy_host") },
+ { ngx_string("Connection"), ngx_string("close") },
+ { ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") },
+ { ngx_string("Transfer-Encoding"), ngx_string("$proxy_internal_chunked") },
+ { ngx_string("TE"), ngx_string("") },
+ { ngx_string("Keep-Alive"), ngx_string("") },
+ { ngx_string("Expect"), ngx_string("") },
+ { ngx_string("Upgrade"), ngx_string("") },
+ { ngx_string("If-Modified-Since"),
+ ngx_string("$upstream_cache_last_modified") },
+ { ngx_string("If-Unmodified-Since"), ngx_string("") },
+ { ngx_string("If-None-Match"), ngx_string("$upstream_cache_etag") },
+ { ngx_string("If-Match"), ngx_string("") },
+ { ngx_string("Range"), ngx_string("") },
+ { ngx_string("If-Range"), ngx_string("") },
+ { ngx_null_string, ngx_null_string }
+};
+
+#endif
+
+
+static ngx_http_variable_t ngx_http_proxy_vars[] = {
+
+ { ngx_string("proxy_host"), NULL, ngx_http_proxy_host_variable, 0,
+ NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+ { ngx_string("proxy_port"), NULL, ngx_http_proxy_port_variable, 0,
+ NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+ { ngx_string("proxy_add_x_forwarded_for"), NULL,
+ ngx_http_proxy_add_x_forwarded_for_variable, 0, NGX_HTTP_VAR_NOHASH, 0 },
+
+#if 0
+ { ngx_string("proxy_add_via"), NULL, NULL, 0, NGX_HTTP_VAR_NOHASH, 0 },
+#endif
+
+ { ngx_string("proxy_internal_body_length"), NULL,
+ ngx_http_proxy_internal_body_length_variable, 0,
+ NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+ { ngx_string("proxy_internal_chunked"), NULL,
+ ngx_http_proxy_internal_chunked_variable, 0,
+ NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+ { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+
+static ngx_path_init_t ngx_http_proxy_temp_path = {
+ ngx_string(NGX_HTTP_PROXY_TEMP_PATH), { 1, 2, 0 }
+};
+
+
+static ngx_int_t
+ngx_http_proxy_handler(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_http_upstream_t *u;
+ ngx_http_proxy_ctx_t *ctx;
+ ngx_http_proxy_loc_conf_t *plcf;
+#if (NGX_HTTP_CACHE)
+ ngx_http_proxy_main_conf_t *pmcf;
+#endif
+
+ if (ngx_http_upstream_create(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_proxy_ctx_t));
+ if (ctx == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_proxy_module);
+
+ plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);
+
+ u = r->upstream;
+
+ if (plcf->proxy_lengths == NULL) {
+ ctx->vars = plcf->vars;
+ u->schema = plcf->vars.schema;
+#if (NGX_HTTP_SSL)
+ u->ssl = (plcf->upstream.ssl != NULL);
+#endif
+
+ } else {
+ if (ngx_http_proxy_eval(r, ctx, plcf) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ u->output.tag = (ngx_buf_tag_t) &ngx_http_proxy_module;
+
+ u->conf = &plcf->upstream;
+
+#if (NGX_HTTP_CACHE)
+ pmcf = ngx_http_get_module_main_conf(r, ngx_http_proxy_module);
+
+ u->caches = &pmcf->caches;
+ u->create_key = ngx_http_proxy_create_key;
+#endif
+
+ u->create_request = ngx_http_proxy_create_request;
+ u->reinit_request = ngx_http_proxy_reinit_request;
+ u->process_header = ngx_http_proxy_process_status_line;
+ u->abort_request = ngx_http_proxy_abort_request;
+ u->finalize_request = ngx_http_proxy_finalize_request;
+ r->state = 0;
+
+ if (plcf->redirects) {
+ u->rewrite_redirect = ngx_http_proxy_rewrite_redirect;
+ }
+
+ if (plcf->cookie_domains || plcf->cookie_paths) {
+ u->rewrite_cookie = ngx_http_proxy_rewrite_cookie;
+ }
+
+ u->buffering = plcf->upstream.buffering;
+
+ u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t));
+ if (u->pipe == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ u->pipe->input_filter = ngx_http_proxy_copy_filter;
+ u->pipe->input_ctx = r;
+
+ u->input_filter_init = ngx_http_proxy_input_filter_init;
+ u->input_filter = ngx_http_proxy_non_buffered_copy_filter;
+ u->input_filter_ctx = r;
+
+ u->accel = 1;
+
+ if (!plcf->upstream.request_buffering
+ && plcf->body_values == NULL && plcf->upstream.pass_request_body
+ && (!r->headers_in.chunked
+ || plcf->http_version == NGX_HTTP_VERSION_11))
+ {
+ r->request_body_no_buffering = 1;
+ }
+
+ rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);
+
+ if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+ return rc;
+ }
+
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_eval(ngx_http_request_t *r, ngx_http_proxy_ctx_t *ctx,
+ ngx_http_proxy_loc_conf_t *plcf)
+{
+ u_char *p;
+ size_t add;
+ u_short port;
+ ngx_str_t proxy;
+ ngx_url_t url;
+ ngx_http_upstream_t *u;
+
+ if (ngx_http_script_run(r, &proxy, plcf->proxy_lengths->elts, 0,
+ plcf->proxy_values->elts)
+ == NULL)
+ {
+ return NGX_ERROR;
+ }
+
+ if (proxy.len > 7
+ && ngx_strncasecmp(proxy.data, (u_char *) "http://", 7) == 0)
+ {
+ add = 7;
+ port = 80;
+
+#if (NGX_HTTP_SSL)
+
+ } else if (proxy.len > 8
+ && ngx_strncasecmp(proxy.data, (u_char *) "https://", 8) == 0)
+ {
+ add = 8;
+ port = 443;
+ r->upstream->ssl = 1;
+
+#endif
+
+ } else {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid URL prefix in \"%V\"", &proxy);
+ return NGX_ERROR;
+ }
+
+ u = r->upstream;
+
+ u->schema.len = add;
+ u->schema.data = proxy.data;
+
+ ngx_memzero(&url, sizeof(ngx_url_t));
+
+ url.url.len = proxy.len - add;
+ url.url.data = proxy.data + add;
+ url.default_port = port;
+ url.uri_part = 1;
+ url.no_resolve = 1;
+
+ if (ngx_parse_url(r->pool, &url) != NGX_OK) {
+ if (url.err) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "%s in upstream \"%V\"", url.err, &url.url);
+ }
+
+ return NGX_ERROR;
+ }
+
+ if (url.uri.len) {
+ if (url.uri.data[0] == '?') {
+ p = ngx_pnalloc(r->pool, url.uri.len + 1);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ *p++ = '/';
+ ngx_memcpy(p, url.uri.data, url.uri.len);
+
+ url.uri.len++;
+ url.uri.data = p - 1;
+ }
+ }
+
+ ctx->vars.key_start = u->schema;
+
+ ngx_http_proxy_set_vars(&url, &ctx->vars);
+
+ u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t));
+ if (u->resolved == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (url.addrs) {
+ u->resolved->sockaddr = url.addrs[0].sockaddr;
+ u->resolved->socklen = url.addrs[0].socklen;
+ u->resolved->name = url.addrs[0].name;
+ u->resolved->naddrs = 1;
+ }
+
+ u->resolved->host = url.host;
+ u->resolved->port = (in_port_t) (url.no_port ? port : url.port);
+ u->resolved->no_port = url.no_port;
+
+ return NGX_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_int_t
+ngx_http_proxy_create_key(ngx_http_request_t *r)
+{
+ size_t len, loc_len;
+ u_char *p;
+ uintptr_t escape;
+ ngx_str_t *key;
+ ngx_http_upstream_t *u;
+ ngx_http_proxy_ctx_t *ctx;
+ ngx_http_proxy_loc_conf_t *plcf;
+
+ u = r->upstream;
+
+ plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+ key = ngx_array_push(&r->cache->keys);
+ if (key == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (plcf->cache_key.value.data) {
+
+ if (ngx_http_complex_value(r, &plcf->cache_key, key) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+ }
+
+ *key = ctx->vars.key_start;
+
+ key = ngx_array_push(&r->cache->keys);
+ if (key == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (plcf->proxy_lengths && ctx->vars.uri.len) {
+
+ *key = ctx->vars.uri;
+ u->uri = ctx->vars.uri;
+
+ return NGX_OK;
+
+ } else if (ctx->vars.uri.len == 0 && r->valid_unparsed_uri && r == r->main)
+ {
+ *key = r->unparsed_uri;
+ u->uri = r->unparsed_uri;
+
+ return NGX_OK;
+ }
+
+ loc_len = (r->valid_location && ctx->vars.uri.len) ? plcf->location.len : 0;
+
+ if (r->quoted_uri || r->internal) {
+ escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len,
+ r->uri.len - loc_len, NGX_ESCAPE_URI);
+ } else {
+ escape = 0;
+ }
+
+ len = ctx->vars.uri.len + r->uri.len - loc_len + escape
+ + sizeof("?") - 1 + r->args.len;
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ key->data = p;
+
+ if (r->valid_location) {
+ p = ngx_copy(p, ctx->vars.uri.data, ctx->vars.uri.len);
+ }
+
+ if (escape) {
+ ngx_escape_uri(p, r->uri.data + loc_len,
+ r->uri.len - loc_len, NGX_ESCAPE_URI);
+ p += r->uri.len - loc_len + escape;
+
+ } else {
+ p = ngx_copy(p, r->uri.data + loc_len, r->uri.len - loc_len);
+ }
+
+ if (r->args.len > 0) {
+ *p++ = '?';
+ p = ngx_copy(p, r->args.data, r->args.len);
+ }
+
+ key->len = p - key->data;
+ u->uri = *key;
+
+ return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_proxy_create_request(ngx_http_request_t *r)
+{
+ size_t len, uri_len, loc_len, body_len;
+ uintptr_t escape;
+ ngx_buf_t *b;
+ ngx_str_t method;
+ ngx_uint_t i, unparsed_uri;
+ ngx_chain_t *cl, *body;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header;
+ ngx_http_upstream_t *u;
+ ngx_http_proxy_ctx_t *ctx;
+ ngx_http_script_code_pt code;
+ ngx_http_proxy_headers_t *headers;
+ ngx_http_script_engine_t e, le;
+ ngx_http_proxy_loc_conf_t *plcf;
+ ngx_http_script_len_code_pt lcode;
+
+ u = r->upstream;
+
+ plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);
+
+#if (NGX_HTTP_CACHE)
+ headers = u->cacheable ? &plcf->headers_cache : &plcf->headers;
+#else
+ headers = &plcf->headers;
+#endif
+
+ if (u->method.len) {
+ /* HEAD was changed to GET to cache response */
+ method = u->method;
+
+ } else if (plcf->method) {
+ if (ngx_http_complex_value(r, plcf->method, &method) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ } else {
+ method = r->method_name;
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+ if (method.len == 4
+ && ngx_strncasecmp(method.data, (u_char *) "HEAD", 4) == 0)
+ {
+ ctx->head = 1;
+ }
+
+ len = method.len + 1 + sizeof(ngx_http_proxy_version) - 1
+ + sizeof(CRLF) - 1;
+
+ escape = 0;
+ loc_len = 0;
+ unparsed_uri = 0;
+
+ if (plcf->proxy_lengths && ctx->vars.uri.len) {
+ uri_len = ctx->vars.uri.len;
+
+ } else if (ctx->vars.uri.len == 0 && r->valid_unparsed_uri && r == r->main)
+ {
+ unparsed_uri = 1;
+ uri_len = r->unparsed_uri.len;
+
+ } else {
+ loc_len = (r->valid_location && ctx->vars.uri.len) ?
+ plcf->location.len : 0;
+
+ if (r->quoted_uri || r->space_in_uri || r->internal) {
+ escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len,
+ r->uri.len - loc_len, NGX_ESCAPE_URI);
+ }
+
+ uri_len = ctx->vars.uri.len + r->uri.len - loc_len + escape
+ + sizeof("?") - 1 + r->args.len;
+ }
+
+ if (uri_len == 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "zero length URI to proxy");
+ return NGX_ERROR;
+ }
+
+ len += uri_len;
+
+ ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
+
+ ngx_http_script_flush_no_cacheable_variables(r, plcf->body_flushes);
+ ngx_http_script_flush_no_cacheable_variables(r, headers->flushes);
+
+ if (plcf->body_lengths) {
+ le.ip = plcf->body_lengths->elts;
+ le.request = r;
+ le.flushed = 1;
+ body_len = 0;
+
+ while (*(uintptr_t *) le.ip) {
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ body_len += lcode(&le);
+ }
+
+ ctx->internal_body_length = body_len;
+ len += body_len;
+
+ } else if (r->headers_in.chunked && r->reading_body) {
+ ctx->internal_body_length = -1;
+ ctx->internal_chunked = 1;
+
+ } else {
+ ctx->internal_body_length = r->headers_in.content_length_n;
+ }
+
+ le.ip = headers->lengths->elts;
+ le.request = r;
+ le.flushed = 1;
+
+ while (*(uintptr_t *) le.ip) {
+ while (*(uintptr_t *) le.ip) {
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ len += lcode(&le);
+ }
+ le.ip += sizeof(uintptr_t);
+ }
+
+
+ if (plcf->upstream.pass_request_headers) {
+ part = &r->headers_in.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (ngx_hash_find(&headers->hash, header[i].hash,
+ header[i].lowcase_key, header[i].key.len))
+ {
+ continue;
+ }
+
+ len += header[i].key.len + sizeof(": ") - 1
+ + header[i].value.len + sizeof(CRLF) - 1;
+ }
+ }
+
+
+ b = ngx_create_temp_buf(r->pool, len);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+
+
+ /* the request line */
+
+ b->last = ngx_copy(b->last, method.data, method.len);
+ *b->last++ = ' ';
+
+ u->uri.data = b->last;
+
+ if (plcf->proxy_lengths && ctx->vars.uri.len) {
+ b->last = ngx_copy(b->last, ctx->vars.uri.data, ctx->vars.uri.len);
+
+ } else if (unparsed_uri) {
+ b->last = ngx_copy(b->last, r->unparsed_uri.data, r->unparsed_uri.len);
+
+ } else {
+ if (r->valid_location) {
+ b->last = ngx_copy(b->last, ctx->vars.uri.data, ctx->vars.uri.len);
+ }
+
+ if (escape) {
+ ngx_escape_uri(b->last, r->uri.data + loc_len,
+ r->uri.len - loc_len, NGX_ESCAPE_URI);
+ b->last += r->uri.len - loc_len + escape;
+
+ } else {
+ b->last = ngx_copy(b->last, r->uri.data + loc_len,
+ r->uri.len - loc_len);
+ }
+
+ if (r->args.len > 0) {
+ *b->last++ = '?';
+ b->last = ngx_copy(b->last, r->args.data, r->args.len);
+ }
+ }
+
+ u->uri.len = b->last - u->uri.data;
+
+ if (plcf->http_version == NGX_HTTP_VERSION_11) {
+ b->last = ngx_cpymem(b->last, ngx_http_proxy_version_11,
+ sizeof(ngx_http_proxy_version_11) - 1);
+
+ } else {
+ b->last = ngx_cpymem(b->last, ngx_http_proxy_version,
+ sizeof(ngx_http_proxy_version) - 1);
+ }
+
+ ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+ e.ip = headers->values->elts;
+ e.pos = b->last;
+ e.request = r;
+ e.flushed = 1;
+
+ le.ip = headers->lengths->elts;
+
+ while (*(uintptr_t *) le.ip) {
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+
+ /* skip the header line name length */
+ (void) lcode(&le);
+
+ if (*(ngx_http_script_len_code_pt *) le.ip) {
+
+ for (len = 0; *(uintptr_t *) le.ip; len += lcode(&le)) {
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ }
+
+ e.skip = (len == sizeof(CRLF) - 1) ? 1 : 0;
+
+ } else {
+ e.skip = 0;
+ }
+
+ le.ip += sizeof(uintptr_t);
+
+ while (*(uintptr_t *) e.ip) {
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code((ngx_http_script_engine_t *) &e);
+ }
+ e.ip += sizeof(uintptr_t);
+ }
+
+ b->last = e.pos;
+
+
+ if (plcf->upstream.pass_request_headers) {
+ part = &r->headers_in.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (ngx_hash_find(&headers->hash, header[i].hash,
+ header[i].lowcase_key, header[i].key.len))
+ {
+ continue;
+ }
+
+ b->last = ngx_copy(b->last, header[i].key.data, header[i].key.len);
+
+ *b->last++ = ':'; *b->last++ = ' ';
+
+ b->last = ngx_copy(b->last, header[i].value.data,
+ header[i].value.len);
+
+ *b->last++ = CR; *b->last++ = LF;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http proxy header: \"%V: %V\"",
+ &header[i].key, &header[i].value);
+ }
+ }
+
+
+ /* add "\r\n" at the header end */
+ *b->last++ = CR; *b->last++ = LF;
+
+ if (plcf->body_values) {
+ e.ip = plcf->body_values->elts;
+ e.pos = b->last;
+ e.skip = 0;
+
+ while (*(uintptr_t *) e.ip) {
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code((ngx_http_script_engine_t *) &e);
+ }
+
+ b->last = e.pos;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http proxy header:%N\"%*s\"",
+ (size_t) (b->last - b->pos), b->pos);
+
+ if (r->request_body_no_buffering) {
+
+ u->request_bufs = cl;
+
+ if (ctx->internal_chunked) {
+ u->output.output_filter = ngx_http_proxy_body_output_filter;
+ u->output.filter_ctx = r;
+ }
+
+ } else if (plcf->body_values == NULL && plcf->upstream.pass_request_body) {
+
+ body = u->request_bufs;
+ u->request_bufs = cl;
+
+ while (body) {
+ b = ngx_alloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(b, body->buf, sizeof(ngx_buf_t));
+
+ cl->next = ngx_alloc_chain_link(r->pool);
+ if (cl->next == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = cl->next;
+ cl->buf = b;
+
+ body = body->next;
+ }
+
+ } else {
+ u->request_bufs = cl;
+ }
+
+ b->flush = 1;
+ cl->next = NULL;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_reinit_request(ngx_http_request_t *r)
+{
+ ngx_http_proxy_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+ if (ctx == NULL) {
+ return NGX_OK;
+ }
+
+ ctx->status.code = 0;
+ ctx->status.count = 0;
+ ctx->status.start = NULL;
+ ctx->status.end = NULL;
+ ctx->chunked.state = 0;
+
+ r->upstream->process_header = ngx_http_proxy_process_status_line;
+ r->upstream->pipe->input_filter = ngx_http_proxy_copy_filter;
+ r->upstream->input_filter = ngx_http_proxy_non_buffered_copy_filter;
+ r->state = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_body_output_filter(void *data, ngx_chain_t *in)
+{
+ ngx_http_request_t *r = data;
+
+ off_t size;
+ u_char *chunk;
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_chain_t *out, *cl, *tl, **ll, **fl;
+ ngx_http_proxy_ctx_t *ctx;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "proxy output filter");
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+ if (in == NULL) {
+ out = in;
+ goto out;
+ }
+
+ out = NULL;
+ ll = &out;
+
+ if (!ctx->header_sent) {
+ /* first buffer contains headers, pass it unmodified */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "proxy output header");
+
+ ctx->header_sent = 1;
+
+ tl = ngx_alloc_chain_link(r->pool);
+ if (tl == NULL) {
+ return NGX_ERROR;
+ }
+
+ tl->buf = in->buf;
+ *ll = tl;
+ ll = &tl->next;
+
+ in = in->next;
+
+ if (in == NULL) {
+ tl->next = NULL;
+ goto out;
+ }
+ }
+
+ size = 0;
+ cl = in;
+ fl = ll;
+
+ for ( ;; ) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "proxy output chunk: %O", ngx_buf_size(cl->buf));
+
+ size += ngx_buf_size(cl->buf);
+
+ if (cl->buf->flush
+ || cl->buf->sync
+ || ngx_buf_in_memory(cl->buf)
+ || cl->buf->in_file)
+ {
+ tl = ngx_alloc_chain_link(r->pool);
+ if (tl == NULL) {
+ return NGX_ERROR;
+ }
+
+ tl->buf = cl->buf;
+ *ll = tl;
+ ll = &tl->next;
+ }
+
+ if (cl->next == NULL) {
+ break;
+ }
+
+ cl = cl->next;
+ }
+
+ if (size) {
+ tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+ if (tl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = tl->buf;
+ chunk = b->start;
+
+ if (chunk == NULL) {
+ /* the "0000000000000000" is 64-bit hexadecimal string */
+
+ chunk = ngx_palloc(r->pool, sizeof("0000000000000000" CRLF) - 1);
+ if (chunk == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->start = chunk;
+ b->end = chunk + sizeof("0000000000000000" CRLF) - 1;
+ }
+
+ b->tag = (ngx_buf_tag_t) &ngx_http_proxy_body_output_filter;
+ b->memory = 0;
+ b->temporary = 1;
+ b->pos = chunk;
+ b->last = ngx_sprintf(chunk, "%xO" CRLF, size);
+
+ tl->next = *fl;
+ *fl = tl;
+ }
+
+ if (cl->buf->last_buf) {
+ tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+ if (tl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = tl->buf;
+
+ b->tag = (ngx_buf_tag_t) &ngx_http_proxy_body_output_filter;
+ b->temporary = 0;
+ b->memory = 1;
+ b->last_buf = 1;
+ b->pos = (u_char *) CRLF "0" CRLF CRLF;
+ b->last = b->pos + 7;
+
+ cl->buf->last_buf = 0;
+
+ *ll = tl;
+
+ if (size == 0) {
+ b->pos += 2;
+ }
+
+ } else if (size > 0) {
+ tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+ if (tl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = tl->buf;
+
+ b->tag = (ngx_buf_tag_t) &ngx_http_proxy_body_output_filter;
+ b->temporary = 0;
+ b->memory = 1;
+ b->pos = (u_char *) CRLF;
+ b->last = b->pos + 2;
+
+ *ll = tl;
+
+ } else {
+ *ll = NULL;
+ }
+
+out:
+
+ rc = ngx_chain_writer(&r->upstream->writer, out);
+
+ ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out,
+ (ngx_buf_tag_t) &ngx_http_proxy_body_output_filter);
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_process_status_line(ngx_http_request_t *r)
+{
+ size_t len;
+ ngx_int_t rc;
+ ngx_http_upstream_t *u;
+ ngx_http_proxy_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ u = r->upstream;
+
+ rc = ngx_http_parse_status_line(r, &u->buffer, &ctx->status);
+
+ if (rc == NGX_AGAIN) {
+ return rc;
+ }
+
+ if (rc == NGX_ERROR) {
+
+#if (NGX_HTTP_CACHE)
+
+ if (r->cache) {
+ r->http_version = NGX_HTTP_VERSION_9;
+ return NGX_OK;
+ }
+
+#endif
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent no valid HTTP/1.0 header");
+
+#if 0
+ if (u->accel) {
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+ }
+#endif
+
+ r->http_version = NGX_HTTP_VERSION_9;
+ u->state->status = NGX_HTTP_OK;
+ u->headers_in.connection_close = 1;
+
+ return NGX_OK;
+ }
+
+ if (u->state && u->state->status == 0) {
+ u->state->status = ctx->status.code;
+ }
+
+ u->headers_in.status_n = ctx->status.code;
+
+ len = ctx->status.end - ctx->status.start;
+ u->headers_in.status_line.len = len;
+
+ u->headers_in.status_line.data = ngx_pnalloc(r->pool, len);
+ if (u->headers_in.status_line.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(u->headers_in.status_line.data, ctx->status.start, len);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http proxy status %ui \"%V\"",
+ u->headers_in.status_n, &u->headers_in.status_line);
+
+ if (ctx->status.http_version < NGX_HTTP_VERSION_11) {
+ u->headers_in.connection_close = 1;
+ }
+
+ u->process_header = ngx_http_proxy_process_header;
+
+ return ngx_http_proxy_process_header(r);
+}
+
+
+static ngx_int_t
+ngx_http_proxy_process_header(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_table_elt_t *h;
+ ngx_http_upstream_t *u;
+ ngx_http_proxy_ctx_t *ctx;
+ ngx_http_upstream_header_t *hh;
+ ngx_http_upstream_main_conf_t *umcf;
+
+ umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
+
+ for ( ;; ) {
+
+ rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1);
+
+ if (rc == NGX_OK) {
+
+ /* a header line has been parsed successfully */
+
+ h = ngx_list_push(&r->upstream->headers_in.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->hash = r->header_hash;
+
+ h->key.len = r->header_name_end - r->header_name_start;
+ h->value.len = r->header_end - r->header_start;
+
+ h->key.data = ngx_pnalloc(r->pool,
+ h->key.len + 1 + h->value.len + 1 + h->key.len);
+ if (h->key.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->value.data = h->key.data + h->key.len + 1;
+ h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1;
+
+ ngx_memcpy(h->key.data, r->header_name_start, h->key.len);
+ h->key.data[h->key.len] = '\0';
+ ngx_memcpy(h->value.data, r->header_start, h->value.len);
+ h->value.data[h->value.len] = '\0';
+
+ if (h->key.len == r->lowcase_index) {
+ ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
+
+ } else {
+ ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
+ }
+
+ hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
+ h->lowcase_key, h->key.len);
+
+ if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http proxy header: \"%V: %V\"",
+ &h->key, &h->value);
+
+ continue;
+ }
+
+ if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+ /* a whole header has been parsed successfully */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http proxy header done");
+
+ /*
+ * if no "Server" and "Date" in header line,
+ * then add the special empty headers
+ */
+
+ if (r->upstream->headers_in.server == NULL) {
+ h = ngx_list_push(&r->upstream->headers_in.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash(
+ ngx_hash('s', 'e'), 'r'), 'v'), 'e'), 'r');
+
+ ngx_str_set(&h->key, "Server");
+ ngx_str_null(&h->value);
+ h->lowcase_key = (u_char *) "server";
+ }
+
+ if (r->upstream->headers_in.date == NULL) {
+ h = ngx_list_push(&r->upstream->headers_in.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->hash = ngx_hash(ngx_hash(ngx_hash('d', 'a'), 't'), 'e');
+
+ ngx_str_set(&h->key, "Date");
+ ngx_str_null(&h->value);
+ h->lowcase_key = (u_char *) "date";
+ }
+
+ /* clear content length if response is chunked */
+
+ u = r->upstream;
+
+ if (u->headers_in.chunked) {
+ u->headers_in.content_length_n = -1;
+ }
+
+ /*
+ * set u->keepalive if response has no body; this allows to keep
+ * connections alive in case of r->header_only or X-Accel-Redirect
+ */
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+ if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT
+ || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED
+ || ctx->head
+ || (!u->headers_in.chunked
+ && u->headers_in.content_length_n == 0))
+ {
+ u->keepalive = !u->headers_in.connection_close;
+ }
+
+ if (u->headers_in.status_n == NGX_HTTP_SWITCHING_PROTOCOLS) {
+ u->keepalive = 0;
+
+ if (r->headers_in.upgrade) {
+ u->upgrade = 1;
+ }
+ }
+
+ return NGX_OK;
+ }
+
+ if (rc == NGX_AGAIN) {
+ return NGX_AGAIN;
+ }
+
+ /* there was error while a header line parsing */
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent invalid header");
+
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+ }
+}
+
+
+static ngx_int_t
+ngx_http_proxy_input_filter_init(void *data)
+{
+ ngx_http_request_t *r = data;
+ ngx_http_upstream_t *u;
+ ngx_http_proxy_ctx_t *ctx;
+
+ u = r->upstream;
+ ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http proxy filter init s:%ui h:%d c:%d l:%O",
+ u->headers_in.status_n, ctx->head, u->headers_in.chunked,
+ u->headers_in.content_length_n);
+
+ /* as per RFC2616, 4.4 Message Length */
+
+ if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT
+ || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED
+ || ctx->head)
+ {
+ /* 1xx, 204, and 304 and replies to HEAD requests */
+ /* no 1xx since we don't send Expect and Upgrade */
+
+ u->pipe->length = 0;
+ u->length = 0;
+ u->keepalive = !u->headers_in.connection_close;
+
+ } else if (u->headers_in.chunked) {
+ /* chunked */
+
+ u->pipe->input_filter = ngx_http_proxy_chunked_filter;
+ u->pipe->length = 3; /* "0" LF LF */
+
+ u->input_filter = ngx_http_proxy_non_buffered_chunked_filter;
+ u->length = 1;
+
+ } else if (u->headers_in.content_length_n == 0) {
+ /* empty body: special case as filter won't be called */
+
+ u->pipe->length = 0;
+ u->length = 0;
+ u->keepalive = !u->headers_in.connection_close;
+
+ } else {
+ /* content length or connection close */
+
+ u->pipe->length = u->headers_in.content_length_n;
+ u->length = u->headers_in.content_length_n;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_copy_filter(ngx_event_pipe_t *p, ngx_buf_t *buf)
+{
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+ ngx_http_request_t *r;
+
+ if (buf->pos == buf->last) {
+ return NGX_OK;
+ }
+
+ cl = ngx_chain_get_free_buf(p->pool, &p->free);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = cl->buf;
+
+ ngx_memcpy(b, buf, sizeof(ngx_buf_t));
+ b->shadow = buf;
+ b->tag = p->tag;
+ b->last_shadow = 1;
+ b->recycled = 1;
+ buf->shadow = b;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0, "input buf #%d", b->num);
+
+ if (p->in) {
+ *p->last_in = cl;
+ } else {
+ p->in = cl;
+ }
+ p->last_in = &cl->next;
+
+ if (p->length == -1) {
+ return NGX_OK;
+ }
+
+ p->length -= b->last - b->pos;
+
+ if (p->length == 0) {
+ r = p->input_ctx;
+ p->upstream_done = 1;
+ r->upstream->keepalive = !r->upstream->headers_in.connection_close;
+
+ } else if (p->length < 0) {
+ r = p->input_ctx;
+ p->upstream_done = 1;
+
+ ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+ "upstream sent more data than specified in "
+ "\"Content-Length\" header");
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_chunked_filter(ngx_event_pipe_t *p, ngx_buf_t *buf)
+{
+ ngx_int_t rc;
+ ngx_buf_t *b, **prev;
+ ngx_chain_t *cl;
+ ngx_http_request_t *r;
+ ngx_http_proxy_ctx_t *ctx;
+
+ if (buf->pos == buf->last) {
+ return NGX_OK;
+ }
+
+ r = p->input_ctx;
+ ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = NULL;
+ prev = &buf->shadow;
+
+ for ( ;; ) {
+
+ rc = ngx_http_parse_chunked(r, buf, &ctx->chunked);
+
+ if (rc == NGX_OK) {
+
+ /* a chunk has been parsed successfully */
+
+ cl = ngx_chain_get_free_buf(p->pool, &p->free);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = cl->buf;
+
+ ngx_memzero(b, sizeof(ngx_buf_t));
+
+ b->pos = buf->pos;
+ b->start = buf->start;
+ b->end = buf->end;
+ b->tag = p->tag;
+ b->temporary = 1;
+ b->recycled = 1;
+
+ *prev = b;
+ prev = &b->shadow;
+
+ if (p->in) {
+ *p->last_in = cl;
+ } else {
+ p->in = cl;
+ }
+ p->last_in = &cl->next;
+
+ /* STUB */ b->num = buf->num;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "input buf #%d %p", b->num, b->pos);
+
+ if (buf->last - buf->pos >= ctx->chunked.size) {
+
+ buf->pos += (size_t) ctx->chunked.size;
+ b->last = buf->pos;
+ ctx->chunked.size = 0;
+
+ continue;
+ }
+
+ ctx->chunked.size -= buf->last - buf->pos;
+ buf->pos = buf->last;
+ b->last = buf->last;
+
+ continue;
+ }
+
+ if (rc == NGX_DONE) {
+
+ /* a whole response has been parsed successfully */
+
+ p->upstream_done = 1;
+ r->upstream->keepalive = !r->upstream->headers_in.connection_close;
+
+ break;
+ }
+
+ if (rc == NGX_AGAIN) {
+
+ /* set p->length, minimal amount of data we want to see */
+
+ p->length = ctx->chunked.length;
+
+ break;
+ }
+
+ /* invalid response */
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent invalid chunked response");
+
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http proxy chunked state %ui, length %O",
+ ctx->chunked.state, p->length);
+
+ if (b) {
+ b->shadow = buf;
+ b->last_shadow = 1;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0,
+ "input buf %p %z", b->pos, b->last - b->pos);
+
+ return NGX_OK;
+ }
+
+ /* there is no data record in the buf, add it to free chain */
+
+ if (ngx_event_pipe_add_free_buf(p, buf) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_non_buffered_copy_filter(void *data, ssize_t bytes)
+{
+ ngx_http_request_t *r = data;
+
+ ngx_buf_t *b;
+ ngx_chain_t *cl, **ll;
+ ngx_http_upstream_t *u;
+
+ u = r->upstream;
+
+ for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) {
+ ll = &cl->next;
+ }
+
+ cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ *ll = cl;
+
+ cl->buf->flush = 1;
+ cl->buf->memory = 1;
+
+ b = &u->buffer;
+
+ cl->buf->pos = b->last;
+ b->last += bytes;
+ cl->buf->last = b->last;
+ cl->buf->tag = u->output.tag;
+
+ if (u->length == -1) {
+ return NGX_OK;
+ }
+
+ u->length -= bytes;
+
+ if (u->length == 0) {
+ u->keepalive = !u->headers_in.connection_close;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_non_buffered_chunked_filter(void *data, ssize_t bytes)
+{
+ ngx_http_request_t *r = data;
+
+ ngx_int_t rc;
+ ngx_buf_t *b, *buf;
+ ngx_chain_t *cl, **ll;
+ ngx_http_upstream_t *u;
+ ngx_http_proxy_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ u = r->upstream;
+ buf = &u->buffer;
+
+ buf->pos = buf->last;
+ buf->last += bytes;
+
+ for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) {
+ ll = &cl->next;
+ }
+
+ for ( ;; ) {
+
+ rc = ngx_http_parse_chunked(r, buf, &ctx->chunked);
+
+ if (rc == NGX_OK) {
+
+ /* a chunk has been parsed successfully */
+
+ cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ *ll = cl;
+ ll = &cl->next;
+
+ b = cl->buf;
+
+ b->flush = 1;
+ b->memory = 1;
+
+ b->pos = buf->pos;
+ b->tag = u->output.tag;
+
+ if (buf->last - buf->pos >= ctx->chunked.size) {
+ buf->pos += (size_t) ctx->chunked.size;
+ b->last = buf->pos;
+ ctx->chunked.size = 0;
+
+ } else {
+ ctx->chunked.size -= buf->last - buf->pos;
+ buf->pos = buf->last;
+ b->last = buf->last;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http proxy out buf %p %z",
+ b->pos, b->last - b->pos);
+
+ continue;
+ }
+
+ if (rc == NGX_DONE) {
+
+ /* a whole response has been parsed successfully */
+
+ u->keepalive = !u->headers_in.connection_close;
+ u->length = 0;
+
+ break;
+ }
+
+ if (rc == NGX_AGAIN) {
+ break;
+ }
+
+ /* invalid response */
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent invalid chunked response");
+
+ return NGX_ERROR;
+ }
+
+ /* provide continuous buffer for subrequests in memory */
+
+ if (r->subrequest_in_memory) {
+
+ cl = u->out_bufs;
+
+ if (cl) {
+ buf->pos = cl->buf->pos;
+ }
+
+ buf->last = buf->pos;
+
+ for (cl = u->out_bufs; cl; cl = cl->next) {
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http proxy in memory %p-%p %O",
+ cl->buf->pos, cl->buf->last, ngx_buf_size(cl->buf));
+
+ if (buf->last == cl->buf->pos) {
+ buf->last = cl->buf->last;
+ continue;
+ }
+
+ buf->last = ngx_movemem(buf->last, cl->buf->pos,
+ cl->buf->last - cl->buf->pos);
+
+ cl->buf->pos = buf->last - (cl->buf->last - cl->buf->pos);
+ cl->buf->last = buf->last;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_proxy_abort_request(ngx_http_request_t *r)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "abort http proxy request");
+
+ return;
+}
+
+
+static void
+ngx_http_proxy_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "finalize http proxy request");
+
+ return;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_host_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_http_proxy_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+ if (ctx == NULL) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ v->len = ctx->vars.host_header.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = ctx->vars.host_header.data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_port_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_http_proxy_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+ if (ctx == NULL) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ v->len = ctx->vars.port.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = ctx->vars.port.data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_add_x_forwarded_for_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ size_t len;
+ u_char *p;
+ ngx_uint_t i, n;
+ ngx_table_elt_t **h;
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ n = r->headers_in.x_forwarded_for.nelts;
+ h = r->headers_in.x_forwarded_for.elts;
+
+ len = 0;
+
+ for (i = 0; i < n; i++) {
+ len += h[i]->value.len + sizeof(", ") - 1;
+ }
+
+ if (len == 0) {
+ v->len = r->connection->addr_text.len;
+ v->data = r->connection->addr_text.data;
+ return NGX_OK;
+ }
+
+ len += r->connection->addr_text.len;
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = len;
+ v->data = p;
+
+ for (i = 0; i < n; i++) {
+ p = ngx_copy(p, h[i]->value.data, h[i]->value.len);
+ *p++ = ','; *p++ = ' ';
+ }
+
+ ngx_memcpy(p, r->connection->addr_text.data, r->connection->addr_text.len);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_internal_body_length_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_http_proxy_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+ if (ctx == NULL || ctx->internal_body_length < 0) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ v->data = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);
+
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = ngx_sprintf(v->data, "%O", ctx->internal_body_length) - v->data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_internal_chunked_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_http_proxy_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+ if (ctx == NULL || !ctx->internal_chunked) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ v->data = (u_char *) "chunked";
+ v->len = sizeof("chunked") - 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_rewrite_redirect(ngx_http_request_t *r, ngx_table_elt_t *h,
+ size_t prefix)
+{
+ size_t len;
+ ngx_int_t rc;
+ ngx_uint_t i;
+ ngx_http_proxy_rewrite_t *pr;
+ ngx_http_proxy_loc_conf_t *plcf;
+
+ plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);
+
+ pr = plcf->redirects->elts;
+
+ if (pr == NULL) {
+ return NGX_DECLINED;
+ }
+
+ len = h->value.len - prefix;
+
+ for (i = 0; i < plcf->redirects->nelts; i++) {
+ rc = pr[i].handler(r, h, prefix, len, &pr[i]);
+
+ if (rc != NGX_DECLINED) {
+ return rc;
+ }
+ }
+
+ return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_rewrite_cookie(ngx_http_request_t *r, ngx_table_elt_t *h)
+{
+ size_t prefix;
+ u_char *p;
+ ngx_int_t rc, rv;
+ ngx_http_proxy_loc_conf_t *plcf;
+
+ p = (u_char *) ngx_strchr(h->value.data, ';');
+ if (p == NULL) {
+ return NGX_DECLINED;
+ }
+
+ prefix = p + 1 - h->value.data;
+
+ rv = NGX_DECLINED;
+
+ plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);
+
+ if (plcf->cookie_domains) {
+ p = ngx_strcasestrn(h->value.data + prefix, "domain=", 7 - 1);
+
+ if (p) {
+ rc = ngx_http_proxy_rewrite_cookie_value(r, h, p + 7,
+ plcf->cookie_domains);
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc != NGX_DECLINED) {
+ rv = rc;
+ }
+ }
+ }
+
+ if (plcf->cookie_paths) {
+ p = ngx_strcasestrn(h->value.data + prefix, "path=", 5 - 1);
+
+ if (p) {
+ rc = ngx_http_proxy_rewrite_cookie_value(r, h, p + 5,
+ plcf->cookie_paths);
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc != NGX_DECLINED) {
+ rv = rc;
+ }
+ }
+ }
+
+ return rv;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_rewrite_cookie_value(ngx_http_request_t *r, ngx_table_elt_t *h,
+ u_char *value, ngx_array_t *rewrites)
+{
+ size_t len, prefix;
+ u_char *p;
+ ngx_int_t rc;
+ ngx_uint_t i;
+ ngx_http_proxy_rewrite_t *pr;
+
+ prefix = value - h->value.data;
+
+ p = (u_char *) ngx_strchr(value, ';');
+
+ len = p ? (size_t) (p - value) : (h->value.len - prefix);
+
+ pr = rewrites->elts;
+
+ for (i = 0; i < rewrites->nelts; i++) {
+ rc = pr[i].handler(r, h, prefix, len, &pr[i]);
+
+ if (rc != NGX_DECLINED) {
+ return rc;
+ }
+ }
+
+ return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_rewrite_complex_handler(ngx_http_request_t *r,
+ ngx_table_elt_t *h, size_t prefix, size_t len, ngx_http_proxy_rewrite_t *pr)
+{
+ ngx_str_t pattern, replacement;
+
+ if (ngx_http_complex_value(r, &pr->pattern.complex, &pattern) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (pattern.len > len
+ || ngx_rstrncmp(h->value.data + prefix, pattern.data,
+ pattern.len) != 0)
+ {
+ return NGX_DECLINED;
+ }
+
+ if (ngx_http_complex_value(r, &pr->replacement, &replacement) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return ngx_http_proxy_rewrite(r, h, prefix, pattern.len, &replacement);
+}
+
+
+#if (NGX_PCRE)
+
+static ngx_int_t
+ngx_http_proxy_rewrite_regex_handler(ngx_http_request_t *r, ngx_table_elt_t *h,
+ size_t prefix, size_t len, ngx_http_proxy_rewrite_t *pr)
+{
+ ngx_str_t pattern, replacement;
+
+ pattern.len = len;
+ pattern.data = h->value.data + prefix;
+
+ if (ngx_http_regex_exec(r, pr->pattern.regex, &pattern) != NGX_OK) {
+ return NGX_DECLINED;
+ }
+
+ if (ngx_http_complex_value(r, &pr->replacement, &replacement) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (prefix == 0 && h->value.len == len) {
+ h->value = replacement;
+ return NGX_OK;
+ }
+
+ return ngx_http_proxy_rewrite(r, h, prefix, len, &replacement);
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_proxy_rewrite_domain_handler(ngx_http_request_t *r,
+ ngx_table_elt_t *h, size_t prefix, size_t len, ngx_http_proxy_rewrite_t *pr)
+{
+ u_char *p;
+ ngx_str_t pattern, replacement;
+
+ if (ngx_http_complex_value(r, &pr->pattern.complex, &pattern) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ p = h->value.data + prefix;
+
+ if (p[0] == '.') {
+ p++;
+ prefix++;
+ len--;
+ }
+
+ if (pattern.len != len || ngx_rstrncasecmp(pattern.data, p, len) != 0) {
+ return NGX_DECLINED;
+ }
+
+ if (ngx_http_complex_value(r, &pr->replacement, &replacement) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return ngx_http_proxy_rewrite(r, h, prefix, len, &replacement);
+}
+
+
+static ngx_int_t
+ngx_http_proxy_rewrite(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix,
+ size_t len, ngx_str_t *replacement)
+{
+ u_char *p, *data;
+ size_t new_len;
+
+ new_len = replacement->len + h->value.len - len;
+
+ if (replacement->len > len) {
+
+ data = ngx_pnalloc(r->pool, new_len + 1);
+ if (data == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_copy(data, h->value.data, prefix);
+ p = ngx_copy(p, replacement->data, replacement->len);
+
+ ngx_memcpy(p, h->value.data + prefix + len,
+ h->value.len - len - prefix + 1);
+
+ h->value.data = data;
+
+ } else {
+ p = ngx_copy(h->value.data + prefix, replacement->data,
+ replacement->len);
+
+ ngx_memmove(p, h->value.data + prefix + len,
+ h->value.len - len - prefix + 1);
+ }
+
+ h->value.len = new_len;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var, *v;
+
+ for (v = ngx_http_proxy_vars; v->name.len; v++) {
+ var = ngx_http_add_variable(cf, &v->name, v->flags);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = v->get_handler;
+ var->data = v->data;
+ }
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_proxy_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_proxy_main_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_proxy_main_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+#if (NGX_HTTP_CACHE)
+ if (ngx_array_init(&conf->caches, cf->pool, 4,
+ sizeof(ngx_http_file_cache_t *))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+#endif
+
+ return conf;
+}
+
+
+static void *
+ngx_http_proxy_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_proxy_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_proxy_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->upstream.bufs.num = 0;
+ * conf->upstream.ignore_headers = 0;
+ * conf->upstream.next_upstream = 0;
+ * conf->upstream.cache_zone = NULL;
+ * conf->upstream.cache_use_stale = 0;
+ * conf->upstream.cache_methods = 0;
+ * conf->upstream.temp_path = NULL;
+ * conf->upstream.hide_headers_hash = { NULL, 0 };
+ * conf->upstream.uri = { 0, NULL };
+ * conf->upstream.location = NULL;
+ * conf->upstream.store_lengths = NULL;
+ * conf->upstream.store_values = NULL;
+ * conf->upstream.ssl_name = NULL;
+ *
+ * conf->method = NULL;
+ * conf->headers_source = NULL;
+ * conf->headers.lengths = NULL;
+ * conf->headers.values = NULL;
+ * conf->headers.hash = { NULL, 0 };
+ * conf->headers_cache.lengths = NULL;
+ * conf->headers_cache.values = NULL;
+ * conf->headers_cache.hash = { NULL, 0 };
+ * conf->body_lengths = NULL;
+ * conf->body_values = NULL;
+ * conf->body_source = { 0, NULL };
+ * conf->redirects = NULL;
+ * conf->ssl = 0;
+ * conf->ssl_protocols = 0;
+ * conf->ssl_ciphers = { 0, NULL };
+ * conf->ssl_trusted_certificate = { 0, NULL };
+ * conf->ssl_crl = { 0, NULL };
+ * conf->ssl_certificate = { 0, NULL };
+ * conf->ssl_certificate_key = { 0, NULL };
+ */
+
+ conf->upstream.store = NGX_CONF_UNSET;
+ conf->upstream.store_access = NGX_CONF_UNSET_UINT;
+ conf->upstream.next_upstream_tries = NGX_CONF_UNSET_UINT;
+ conf->upstream.buffering = NGX_CONF_UNSET;
+ conf->upstream.request_buffering = NGX_CONF_UNSET;
+ conf->upstream.ignore_client_abort = NGX_CONF_UNSET;
+ conf->upstream.force_ranges = NGX_CONF_UNSET;
+
+ conf->upstream.local = NGX_CONF_UNSET_PTR;
+
+ conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC;
+
+ conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE;
+ conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE;
+ conf->upstream.limit_rate = NGX_CONF_UNSET_SIZE;
+
+ conf->upstream.busy_buffers_size_conf = NGX_CONF_UNSET_SIZE;
+ conf->upstream.max_temp_file_size_conf = NGX_CONF_UNSET_SIZE;
+ conf->upstream.temp_file_write_size_conf = NGX_CONF_UNSET_SIZE;
+
+ conf->upstream.pass_request_headers = NGX_CONF_UNSET;
+ conf->upstream.pass_request_body = NGX_CONF_UNSET;
+
+#if (NGX_HTTP_CACHE)
+ conf->upstream.cache = NGX_CONF_UNSET;
+ conf->upstream.cache_min_uses = NGX_CONF_UNSET_UINT;
+ conf->upstream.cache_max_range_offset = NGX_CONF_UNSET;
+ conf->upstream.cache_bypass = NGX_CONF_UNSET_PTR;
+ conf->upstream.no_cache = NGX_CONF_UNSET_PTR;
+ conf->upstream.cache_valid = NGX_CONF_UNSET_PTR;
+ conf->upstream.cache_lock = NGX_CONF_UNSET;
+ conf->upstream.cache_lock_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.cache_lock_age = NGX_CONF_UNSET_MSEC;
+ conf->upstream.cache_revalidate = NGX_CONF_UNSET;
+ conf->upstream.cache_convert_head = NGX_CONF_UNSET;
+ conf->upstream.cache_background_update = NGX_CONF_UNSET;
+#endif
+
+ conf->upstream.hide_headers = NGX_CONF_UNSET_PTR;
+ conf->upstream.pass_headers = NGX_CONF_UNSET_PTR;
+
+ conf->upstream.intercept_errors = NGX_CONF_UNSET;
+
+#if (NGX_HTTP_SSL)
+ conf->upstream.ssl_session_reuse = NGX_CONF_UNSET;
+ conf->upstream.ssl_server_name = NGX_CONF_UNSET;
+ conf->upstream.ssl_verify = NGX_CONF_UNSET;
+ conf->ssl_verify_depth = NGX_CONF_UNSET_UINT;
+ conf->ssl_passwords = NGX_CONF_UNSET_PTR;
+#endif
+
+ /* "proxy_cyclic_temp_file" is disabled */
+ conf->upstream.cyclic_temp_file = 0;
+
+ conf->redirect = NGX_CONF_UNSET;
+ conf->upstream.change_buffering = 1;
+
+ conf->cookie_domains = NGX_CONF_UNSET_PTR;
+ conf->cookie_paths = NGX_CONF_UNSET_PTR;
+
+ conf->http_version = NGX_CONF_UNSET_UINT;
+
+ conf->headers_hash_max_size = NGX_CONF_UNSET_UINT;
+ conf->headers_hash_bucket_size = NGX_CONF_UNSET_UINT;
+
+ ngx_str_set(&conf->upstream.module, "proxy");
+
+ return conf;
+}
+
+
+static char *
+ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_proxy_loc_conf_t *prev = parent;
+ ngx_http_proxy_loc_conf_t *conf = child;
+
+ u_char *p;
+ size_t size;
+ ngx_int_t rc;
+ ngx_hash_init_t hash;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_proxy_rewrite_t *pr;
+ ngx_http_script_compile_t sc;
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->upstream.store > 0) {
+ conf->upstream.cache = 0;
+ }
+
+ if (conf->upstream.cache > 0) {
+ conf->upstream.store = 0;
+ }
+
+#endif
+
+ if (conf->upstream.store == NGX_CONF_UNSET) {
+ ngx_conf_merge_value(conf->upstream.store,
+ prev->upstream.store, 0);
+
+ conf->upstream.store_lengths = prev->upstream.store_lengths;
+ conf->upstream.store_values = prev->upstream.store_values;
+ }
+
+ ngx_conf_merge_uint_value(conf->upstream.store_access,
+ prev->upstream.store_access, 0600);
+
+ ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries,
+ prev->upstream.next_upstream_tries, 0);
+
+ ngx_conf_merge_value(conf->upstream.buffering,
+ prev->upstream.buffering, 1);
+
+ ngx_conf_merge_value(conf->upstream.request_buffering,
+ prev->upstream.request_buffering, 1);
+
+ ngx_conf_merge_value(conf->upstream.ignore_client_abort,
+ prev->upstream.ignore_client_abort, 0);
+
+ ngx_conf_merge_value(conf->upstream.force_ranges,
+ prev->upstream.force_ranges, 0);
+
+ ngx_conf_merge_ptr_value(conf->upstream.local,
+ prev->upstream.local, NULL);
+
+ ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
+ prev->upstream.connect_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->upstream.send_timeout,
+ prev->upstream.send_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->upstream.read_timeout,
+ prev->upstream.read_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->upstream.next_upstream_timeout,
+ prev->upstream.next_upstream_timeout, 0);
+
+ ngx_conf_merge_size_value(conf->upstream.send_lowat,
+ prev->upstream.send_lowat, 0);
+
+ ngx_conf_merge_size_value(conf->upstream.buffer_size,
+ prev->upstream.buffer_size,
+ (size_t) ngx_pagesize);
+
+ ngx_conf_merge_size_value(conf->upstream.limit_rate,
+ prev->upstream.limit_rate, 0);
+
+ ngx_conf_merge_bufs_value(conf->upstream.bufs, prev->upstream.bufs,
+ 8, ngx_pagesize);
+
+ if (conf->upstream.bufs.num < 2) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "there must be at least 2 \"proxy_buffers\"");
+ return NGX_CONF_ERROR;
+ }
+
+
+ size = conf->upstream.buffer_size;
+ if (size < conf->upstream.bufs.size) {
+ size = conf->upstream.bufs.size;
+ }
+
+
+ ngx_conf_merge_size_value(conf->upstream.busy_buffers_size_conf,
+ prev->upstream.busy_buffers_size_conf,
+ NGX_CONF_UNSET_SIZE);
+
+ if (conf->upstream.busy_buffers_size_conf == NGX_CONF_UNSET_SIZE) {
+ conf->upstream.busy_buffers_size = 2 * size;
+ } else {
+ conf->upstream.busy_buffers_size =
+ conf->upstream.busy_buffers_size_conf;
+ }
+
+ if (conf->upstream.busy_buffers_size < size) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"proxy_busy_buffers_size\" must be equal to or greater than "
+ "the maximum of the value of \"proxy_buffer_size\" and "
+ "one of the \"proxy_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+ if (conf->upstream.busy_buffers_size
+ > (conf->upstream.bufs.num - 1) * conf->upstream.bufs.size)
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"proxy_busy_buffers_size\" must be less than "
+ "the size of all \"proxy_buffers\" minus one buffer");
+
+ return NGX_CONF_ERROR;
+ }
+
+
+ ngx_conf_merge_size_value(conf->upstream.temp_file_write_size_conf,
+ prev->upstream.temp_file_write_size_conf,
+ NGX_CONF_UNSET_SIZE);
+
+ if (conf->upstream.temp_file_write_size_conf == NGX_CONF_UNSET_SIZE) {
+ conf->upstream.temp_file_write_size = 2 * size;
+ } else {
+ conf->upstream.temp_file_write_size =
+ conf->upstream.temp_file_write_size_conf;
+ }
+
+ if (conf->upstream.temp_file_write_size < size) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"proxy_temp_file_write_size\" must be equal to or greater "
+ "than the maximum of the value of \"proxy_buffer_size\" and "
+ "one of the \"proxy_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_size_value(conf->upstream.max_temp_file_size_conf,
+ prev->upstream.max_temp_file_size_conf,
+ NGX_CONF_UNSET_SIZE);
+
+ if (conf->upstream.max_temp_file_size_conf == NGX_CONF_UNSET_SIZE) {
+ conf->upstream.max_temp_file_size = 1024 * 1024 * 1024;
+ } else {
+ conf->upstream.max_temp_file_size =
+ conf->upstream.max_temp_file_size_conf;
+ }
+
+ if (conf->upstream.max_temp_file_size != 0
+ && conf->upstream.max_temp_file_size < size)
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"proxy_max_temp_file_size\" must be equal to zero to disable "
+ "temporary files usage or must be equal to or greater than "
+ "the maximum of the value of \"proxy_buffer_size\" and "
+ "one of the \"proxy_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+
+ ngx_conf_merge_bitmask_value(conf->upstream.ignore_headers,
+ prev->upstream.ignore_headers,
+ NGX_CONF_BITMASK_SET);
+
+
+ ngx_conf_merge_bitmask_value(conf->upstream.next_upstream,
+ prev->upstream.next_upstream,
+ (NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_ERROR
+ |NGX_HTTP_UPSTREAM_FT_TIMEOUT));
+
+ if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) {
+ conf->upstream.next_upstream = NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_OFF;
+ }
+
+ if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path,
+ prev->upstream.temp_path,
+ &ngx_http_proxy_temp_path)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->upstream.cache == NGX_CONF_UNSET) {
+ ngx_conf_merge_value(conf->upstream.cache,
+ prev->upstream.cache, 0);
+
+ conf->upstream.cache_zone = prev->upstream.cache_zone;
+ conf->upstream.cache_value = prev->upstream.cache_value;
+ }
+
+ if (conf->upstream.cache_zone && conf->upstream.cache_zone->data == NULL) {
+ ngx_shm_zone_t *shm_zone;
+
+ shm_zone = conf->upstream.cache_zone;
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"proxy_cache\" zone \"%V\" is unknown",
+ &shm_zone->shm.name);
+
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_uint_value(conf->upstream.cache_min_uses,
+ prev->upstream.cache_min_uses, 1);
+
+ ngx_conf_merge_off_value(conf->upstream.cache_max_range_offset,
+ prev->upstream.cache_max_range_offset,
+ NGX_MAX_OFF_T_VALUE);
+
+ ngx_conf_merge_bitmask_value(conf->upstream.cache_use_stale,
+ prev->upstream.cache_use_stale,
+ (NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_OFF));
+
+ if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_OFF) {
+ conf->upstream.cache_use_stale = NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_OFF;
+ }
+
+ if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_ERROR) {
+ conf->upstream.cache_use_stale |= NGX_HTTP_UPSTREAM_FT_NOLIVE;
+ }
+
+ if (conf->upstream.cache_methods == 0) {
+ conf->upstream.cache_methods = prev->upstream.cache_methods;
+ }
+
+ conf->upstream.cache_methods |= NGX_HTTP_GET|NGX_HTTP_HEAD;
+
+ ngx_conf_merge_ptr_value(conf->upstream.cache_bypass,
+ prev->upstream.cache_bypass, NULL);
+
+ ngx_conf_merge_ptr_value(conf->upstream.no_cache,
+ prev->upstream.no_cache, NULL);
+
+ ngx_conf_merge_ptr_value(conf->upstream.cache_valid,
+ prev->upstream.cache_valid, NULL);
+
+ if (conf->cache_key.value.data == NULL) {
+ conf->cache_key = prev->cache_key;
+ }
+
+ ngx_conf_merge_value(conf->upstream.cache_lock,
+ prev->upstream.cache_lock, 0);
+
+ ngx_conf_merge_msec_value(conf->upstream.cache_lock_timeout,
+ prev->upstream.cache_lock_timeout, 5000);
+
+ ngx_conf_merge_msec_value(conf->upstream.cache_lock_age,
+ prev->upstream.cache_lock_age, 5000);
+
+ ngx_conf_merge_value(conf->upstream.cache_revalidate,
+ prev->upstream.cache_revalidate, 0);
+
+ ngx_conf_merge_value(conf->upstream.cache_convert_head,
+ prev->upstream.cache_convert_head, 1);
+
+ ngx_conf_merge_value(conf->upstream.cache_background_update,
+ prev->upstream.cache_background_update, 0);
+
+#endif
+
+ if (conf->method == NULL) {
+ conf->method = prev->method;
+ }
+
+ ngx_conf_merge_value(conf->upstream.pass_request_headers,
+ prev->upstream.pass_request_headers, 1);
+ ngx_conf_merge_value(conf->upstream.pass_request_body,
+ prev->upstream.pass_request_body, 1);
+
+ ngx_conf_merge_value(conf->upstream.intercept_errors,
+ prev->upstream.intercept_errors, 0);
+
+#if (NGX_HTTP_SSL)
+
+ ngx_conf_merge_value(conf->upstream.ssl_session_reuse,
+ prev->upstream.ssl_session_reuse, 1);
+
+ ngx_conf_merge_bitmask_value(conf->ssl_protocols, prev->ssl_protocols,
+ (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1
+ |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2));
+
+ ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers,
+ "DEFAULT");
+
+ if (conf->upstream.ssl_name == NULL) {
+ conf->upstream.ssl_name = prev->upstream.ssl_name;
+ }
+
+ ngx_conf_merge_value(conf->upstream.ssl_server_name,
+ prev->upstream.ssl_server_name, 0);
+ ngx_conf_merge_value(conf->upstream.ssl_verify,
+ prev->upstream.ssl_verify, 0);
+ ngx_conf_merge_uint_value(conf->ssl_verify_depth,
+ prev->ssl_verify_depth, 1);
+ ngx_conf_merge_str_value(conf->ssl_trusted_certificate,
+ prev->ssl_trusted_certificate, "");
+ ngx_conf_merge_str_value(conf->ssl_crl, prev->ssl_crl, "");
+
+ ngx_conf_merge_str_value(conf->ssl_certificate,
+ prev->ssl_certificate, "");
+ ngx_conf_merge_str_value(conf->ssl_certificate_key,
+ prev->ssl_certificate_key, "");
+ ngx_conf_merge_ptr_value(conf->ssl_passwords, prev->ssl_passwords, NULL);
+
+ if (conf->ssl && ngx_http_proxy_set_ssl(cf, conf) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+#endif
+
+ ngx_conf_merge_value(conf->redirect, prev->redirect, 1);
+
+ if (conf->redirect) {
+
+ if (conf->redirects == NULL) {
+ conf->redirects = prev->redirects;
+ }
+
+ if (conf->redirects == NULL && conf->url.data) {
+
+ conf->redirects = ngx_array_create(cf->pool, 1,
+ sizeof(ngx_http_proxy_rewrite_t));
+ if (conf->redirects == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pr = ngx_array_push(conf->redirects);
+ if (pr == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&pr->pattern.complex,
+ sizeof(ngx_http_complex_value_t));
+
+ ngx_memzero(&pr->replacement, sizeof(ngx_http_complex_value_t));
+
+ pr->handler = ngx_http_proxy_rewrite_complex_handler;
+
+ if (conf->vars.uri.len) {
+ pr->pattern.complex.value = conf->url;
+ pr->replacement.value = conf->location;
+
+ } else {
+ pr->pattern.complex.value.len = conf->url.len
+ + sizeof("/") - 1;
+
+ p = ngx_pnalloc(cf->pool, pr->pattern.complex.value.len);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pr->pattern.complex.value.data = p;
+
+ p = ngx_cpymem(p, conf->url.data, conf->url.len);
+ *p = '/';
+
+ ngx_str_set(&pr->replacement.value, "/");
+ }
+ }
+ }
+
+ ngx_conf_merge_ptr_value(conf->cookie_domains, prev->cookie_domains, NULL);
+
+ ngx_conf_merge_ptr_value(conf->cookie_paths, prev->cookie_paths, NULL);
+
+ ngx_conf_merge_uint_value(conf->http_version, prev->http_version,
+ NGX_HTTP_VERSION_10);
+
+ ngx_conf_merge_uint_value(conf->headers_hash_max_size,
+ prev->headers_hash_max_size, 512);
+
+ ngx_conf_merge_uint_value(conf->headers_hash_bucket_size,
+ prev->headers_hash_bucket_size, 64);
+
+ conf->headers_hash_bucket_size = ngx_align(conf->headers_hash_bucket_size,
+ ngx_cacheline_size);
+
+ hash.max_size = conf->headers_hash_max_size;
+ hash.bucket_size = conf->headers_hash_bucket_size;
+ hash.name = "proxy_headers_hash";
+
+ if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream,
+ &prev->upstream, ngx_http_proxy_hide_headers, &hash)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+ if (clcf->noname
+ && conf->upstream.upstream == NULL && conf->proxy_lengths == NULL)
+ {
+ conf->upstream.upstream = prev->upstream.upstream;
+ conf->location = prev->location;
+ conf->vars = prev->vars;
+
+ conf->proxy_lengths = prev->proxy_lengths;
+ conf->proxy_values = prev->proxy_values;
+
+#if (NGX_HTTP_SSL)
+ conf->upstream.ssl = prev->upstream.ssl;
+#endif
+ }
+
+ if (clcf->lmt_excpt && clcf->handler == NULL
+ && (conf->upstream.upstream || conf->proxy_lengths))
+ {
+ clcf->handler = ngx_http_proxy_handler;
+ }
+
+ if (conf->body_source.data == NULL) {
+ conf->body_flushes = prev->body_flushes;
+ conf->body_source = prev->body_source;
+ conf->body_lengths = prev->body_lengths;
+ conf->body_values = prev->body_values;
+ }
+
+ if (conf->body_source.data && conf->body_lengths == NULL) {
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = &conf->body_source;
+ sc.flushes = &conf->body_flushes;
+ sc.lengths = &conf->body_lengths;
+ sc.values = &conf->body_values;
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (conf->headers_source == NULL) {
+ conf->headers = prev->headers;
+#if (NGX_HTTP_CACHE)
+ conf->headers_cache = prev->headers_cache;
+#endif
+ conf->headers_source = prev->headers_source;
+ }
+
+ rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers,
+ ngx_http_proxy_headers);
+ if (rc != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->upstream.cache) {
+ rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers_cache,
+ ngx_http_proxy_cache_headers);
+ if (rc != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+#endif
+
+ /*
+ * special handling to preserve conf->headers in the "http" section
+ * to inherit it to all servers
+ */
+
+ if (prev->headers.hash.buckets == NULL
+ && conf->headers_source == prev->headers_source)
+ {
+ prev->headers = conf->headers;
+#if (NGX_HTTP_CACHE)
+ prev->headers_cache = conf->headers_cache;
+#endif
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_init_headers(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf,
+ ngx_http_proxy_headers_t *headers, ngx_keyval_t *default_headers)
+{
+ u_char *p;
+ size_t size;
+ uintptr_t *code;
+ ngx_uint_t i;
+ ngx_array_t headers_names, headers_merged;
+ ngx_keyval_t *src, *s, *h;
+ ngx_hash_key_t *hk;
+ ngx_hash_init_t hash;
+ ngx_http_script_compile_t sc;
+ ngx_http_script_copy_code_t *copy;
+
+ if (headers->hash.buckets) {
+ return NGX_OK;
+ }
+
+ if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ if (ngx_array_init(&headers_merged, cf->temp_pool, 4, sizeof(ngx_keyval_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ headers->lengths = ngx_array_create(cf->pool, 64, 1);
+ if (headers->lengths == NULL) {
+ return NGX_ERROR;
+ }
+
+ headers->values = ngx_array_create(cf->pool, 512, 1);
+ if (headers->values == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (conf->headers_source) {
+
+ src = conf->headers_source->elts;
+ for (i = 0; i < conf->headers_source->nelts; i++) {
+
+ s = ngx_array_push(&headers_merged);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ *s = src[i];
+ }
+ }
+
+ h = default_headers;
+
+ while (h->key.len) {
+
+ src = headers_merged.elts;
+ for (i = 0; i < headers_merged.nelts; i++) {
+ if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) {
+ goto next;
+ }
+ }
+
+ s = ngx_array_push(&headers_merged);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ *s = *h;
+
+ next:
+
+ h++;
+ }
+
+
+ src = headers_merged.elts;
+ for (i = 0; i < headers_merged.nelts; i++) {
+
+ hk = ngx_array_push(&headers_names);
+ if (hk == NULL) {
+ return NGX_ERROR;
+ }
+
+ hk->key = src[i].key;
+ hk->key_hash = ngx_hash_key_lc(src[i].key.data, src[i].key.len);
+ hk->value = (void *) 1;
+
+ if (src[i].value.len == 0) {
+ continue;
+ }
+
+ if (ngx_http_script_variables_count(&src[i].value) == 0) {
+ copy = ngx_array_push_n(headers->lengths,
+ sizeof(ngx_http_script_copy_code_t));
+ if (copy == NULL) {
+ return NGX_ERROR;
+ }
+
+ copy->code = (ngx_http_script_code_pt)
+ ngx_http_script_copy_len_code;
+ copy->len = src[i].key.len + sizeof(": ") - 1
+ + src[i].value.len + sizeof(CRLF) - 1;
+
+
+ size = (sizeof(ngx_http_script_copy_code_t)
+ + src[i].key.len + sizeof(": ") - 1
+ + src[i].value.len + sizeof(CRLF) - 1
+ + sizeof(uintptr_t) - 1)
+ & ~(sizeof(uintptr_t) - 1);
+
+ copy = ngx_array_push_n(headers->values, size);
+ if (copy == NULL) {
+ return NGX_ERROR;
+ }
+
+ copy->code = ngx_http_script_copy_code;
+ copy->len = src[i].key.len + sizeof(": ") - 1
+ + src[i].value.len + sizeof(CRLF) - 1;
+
+ p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t);
+
+ p = ngx_cpymem(p, src[i].key.data, src[i].key.len);
+ *p++ = ':'; *p++ = ' ';
+ p = ngx_cpymem(p, src[i].value.data, src[i].value.len);
+ *p++ = CR; *p = LF;
+
+ } else {
+ copy = ngx_array_push_n(headers->lengths,
+ sizeof(ngx_http_script_copy_code_t));
+ if (copy == NULL) {
+ return NGX_ERROR;
+ }
+
+ copy->code = (ngx_http_script_code_pt)
+ ngx_http_script_copy_len_code;
+ copy->len = src[i].key.len + sizeof(": ") - 1;
+
+
+ size = (sizeof(ngx_http_script_copy_code_t)
+ + src[i].key.len + sizeof(": ") - 1 + sizeof(uintptr_t) - 1)
+ & ~(sizeof(uintptr_t) - 1);
+
+ copy = ngx_array_push_n(headers->values, size);
+ if (copy == NULL) {
+ return NGX_ERROR;
+ }
+
+ copy->code = ngx_http_script_copy_code;
+ copy->len = src[i].key.len + sizeof(": ") - 1;
+
+ p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t);
+ p = ngx_cpymem(p, src[i].key.data, src[i].key.len);
+ *p++ = ':'; *p = ' ';
+
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = &src[i].value;
+ sc.flushes = &headers->flushes;
+ sc.lengths = &headers->lengths;
+ sc.values = &headers->values;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+
+ copy = ngx_array_push_n(headers->lengths,
+ sizeof(ngx_http_script_copy_code_t));
+ if (copy == NULL) {
+ return NGX_ERROR;
+ }
+
+ copy->code = (ngx_http_script_code_pt)
+ ngx_http_script_copy_len_code;
+ copy->len = sizeof(CRLF) - 1;
+
+
+ size = (sizeof(ngx_http_script_copy_code_t)
+ + sizeof(CRLF) - 1 + sizeof(uintptr_t) - 1)
+ & ~(sizeof(uintptr_t) - 1);
+
+ copy = ngx_array_push_n(headers->values, size);
+ if (copy == NULL) {
+ return NGX_ERROR;
+ }
+
+ copy->code = ngx_http_script_copy_code;
+ copy->len = sizeof(CRLF) - 1;
+
+ p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t);
+ *p++ = CR; *p = LF;
+ }
+
+ code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+
+ code = ngx_array_push_n(headers->values, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+ }
+
+ code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+
+
+ hash.hash = &headers->hash;
+ hash.key = ngx_hash_key_lc;
+ hash.max_size = conf->headers_hash_max_size;
+ hash.bucket_size = conf->headers_hash_bucket_size;
+ hash.name = "proxy_headers_hash";
+ hash.pool = cf->pool;
+ hash.temp_pool = NULL;
+
+ return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts);
+}
+
+
+static char *
+ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_proxy_loc_conf_t *plcf = conf;
+
+ size_t add;
+ u_short port;
+ ngx_str_t *value, *url;
+ ngx_url_t u;
+ ngx_uint_t n;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_script_compile_t sc;
+
+ if (plcf->upstream.upstream || plcf->proxy_lengths) {
+ return "is duplicate";
+ }
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+ clcf->handler = ngx_http_proxy_handler;
+
+ if (clcf->name.data[clcf->name.len - 1] == '/') {
+ clcf->auto_redirect = 1;
+ }
+
+ value = cf->args->elts;
+
+ url = &value[1];
+
+ n = ngx_http_script_variables_count(url);
+
+ if (n) {
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = url;
+ sc.lengths = &plcf->proxy_lengths;
+ sc.values = &plcf->proxy_values;
+ sc.variables = n;
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+#if (NGX_HTTP_SSL)
+ plcf->ssl = 1;
+#endif
+
+ return NGX_CONF_OK;
+ }
+
+ if (ngx_strncasecmp(url->data, (u_char *) "http://", 7) == 0) {
+ add = 7;
+ port = 80;
+
+ } else if (ngx_strncasecmp(url->data, (u_char *) "https://", 8) == 0) {
+
+#if (NGX_HTTP_SSL)
+ plcf->ssl = 1;
+
+ add = 8;
+ port = 443;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "https protocol requires SSL support");
+ return NGX_CONF_ERROR;
+#endif
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid URL prefix");
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.url.len = url->len - add;
+ u.url.data = url->data + add;
+ u.default_port = port;
+ u.uri_part = 1;
+ u.no_resolve = 1;
+
+ plcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
+ if (plcf->upstream.upstream == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ plcf->vars.schema.len = add;
+ plcf->vars.schema.data = url->data;
+ plcf->vars.key_start = plcf->vars.schema;
+
+ ngx_http_proxy_set_vars(&u, &plcf->vars);
+
+ plcf->location = clcf->name;
+
+ if (clcf->named
+#if (NGX_PCRE)
+ || clcf->regex
+#endif
+ || clcf->noname)
+ {
+ if (plcf->vars.uri.len) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"proxy_pass\" cannot have URI part in "
+ "location given by regular expression, "
+ "or inside named location, "
+ "or inside \"if\" statement, "
+ "or inside \"limit_except\" block");
+ return NGX_CONF_ERROR;
+ }
+
+ plcf->location.len = 0;
+ }
+
+ plcf->url = *url;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_proxy_redirect(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_proxy_loc_conf_t *plcf = conf;
+
+ u_char *p;
+ ngx_str_t *value;
+ ngx_http_proxy_rewrite_t *pr;
+ ngx_http_compile_complex_value_t ccv;
+
+ if (plcf->redirect == 0) {
+ return NGX_CONF_OK;
+ }
+
+ plcf->redirect = 1;
+
+ value = cf->args->elts;
+
+ if (cf->args->nelts == 2) {
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ plcf->redirect = 0;
+ plcf->redirects = NULL;
+ return NGX_CONF_OK;
+ }
+
+ if (ngx_strcmp(value[1].data, "false") == 0) {
+ ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
+ "invalid parameter \"false\", use \"off\" instead");
+ plcf->redirect = 0;
+ plcf->redirects = NULL;
+ return NGX_CONF_OK;
+ }
+
+ if (ngx_strcmp(value[1].data, "default") != 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (plcf->redirects == NULL) {
+ plcf->redirects = ngx_array_create(cf->pool, 1,
+ sizeof(ngx_http_proxy_rewrite_t));
+ if (plcf->redirects == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ pr = ngx_array_push(plcf->redirects);
+ if (pr == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_strcmp(value[1].data, "default") == 0) {
+ if (plcf->proxy_lengths) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"proxy_redirect default\" cannot be used "
+ "with \"proxy_pass\" directive with variables");
+ return NGX_CONF_ERROR;
+ }
+
+ if (plcf->url.data == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"proxy_redirect default\" should be placed "
+ "after the \"proxy_pass\" directive");
+ return NGX_CONF_ERROR;
+ }
+
+ pr->handler = ngx_http_proxy_rewrite_complex_handler;
+
+ ngx_memzero(&pr->pattern.complex, sizeof(ngx_http_complex_value_t));
+
+ ngx_memzero(&pr->replacement, sizeof(ngx_http_complex_value_t));
+
+ if (plcf->vars.uri.len) {
+ pr->pattern.complex.value = plcf->url;
+ pr->replacement.value = plcf->location;
+
+ } else {
+ pr->pattern.complex.value.len = plcf->url.len + sizeof("/") - 1;
+
+ p = ngx_pnalloc(cf->pool, pr->pattern.complex.value.len);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pr->pattern.complex.value.data = p;
+
+ p = ngx_cpymem(p, plcf->url.data, plcf->url.len);
+ *p = '/';
+
+ ngx_str_set(&pr->replacement.value, "/");
+ }
+
+ return NGX_CONF_OK;
+ }
+
+
+ if (value[1].data[0] == '~') {
+ value[1].len--;
+ value[1].data++;
+
+ if (value[1].data[0] == '*') {
+ value[1].len--;
+ value[1].data++;
+
+ if (ngx_http_proxy_rewrite_regex(cf, pr, &value[1], 1) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+ if (ngx_http_proxy_rewrite_regex(cf, pr, &value[1], 0) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ } else {
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &pr->pattern.complex;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ pr->handler = ngx_http_proxy_rewrite_complex_handler;
+ }
+
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[2];
+ ccv.complex_value = &pr->replacement;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_proxy_cookie_domain(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_proxy_loc_conf_t *plcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_proxy_rewrite_t *pr;
+ ngx_http_compile_complex_value_t ccv;
+
+ if (plcf->cookie_domains == NULL) {
+ return NGX_CONF_OK;
+ }
+
+ value = cf->args->elts;
+
+ if (cf->args->nelts == 2) {
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ plcf->cookie_domains = NULL;
+ return NGX_CONF_OK;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (plcf->cookie_domains == NGX_CONF_UNSET_PTR) {
+ plcf->cookie_domains = ngx_array_create(cf->pool, 1,
+ sizeof(ngx_http_proxy_rewrite_t));
+ if (plcf->cookie_domains == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ pr = ngx_array_push(plcf->cookie_domains);
+ if (pr == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (value[1].data[0] == '~') {
+ value[1].len--;
+ value[1].data++;
+
+ if (ngx_http_proxy_rewrite_regex(cf, pr, &value[1], 1) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+
+ if (value[1].data[0] == '.') {
+ value[1].len--;
+ value[1].data++;
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &pr->pattern.complex;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ pr->handler = ngx_http_proxy_rewrite_domain_handler;
+
+ if (value[2].data[0] == '.') {
+ value[2].len--;
+ value[2].data++;
+ }
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[2];
+ ccv.complex_value = &pr->replacement;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_proxy_cookie_path(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_proxy_loc_conf_t *plcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_proxy_rewrite_t *pr;
+ ngx_http_compile_complex_value_t ccv;
+
+ if (plcf->cookie_paths == NULL) {
+ return NGX_CONF_OK;
+ }
+
+ value = cf->args->elts;
+
+ if (cf->args->nelts == 2) {
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ plcf->cookie_paths = NULL;
+ return NGX_CONF_OK;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (plcf->cookie_paths == NGX_CONF_UNSET_PTR) {
+ plcf->cookie_paths = ngx_array_create(cf->pool, 1,
+ sizeof(ngx_http_proxy_rewrite_t));
+ if (plcf->cookie_paths == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ pr = ngx_array_push(plcf->cookie_paths);
+ if (pr == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (value[1].data[0] == '~') {
+ value[1].len--;
+ value[1].data++;
+
+ if (value[1].data[0] == '*') {
+ value[1].len--;
+ value[1].data++;
+
+ if (ngx_http_proxy_rewrite_regex(cf, pr, &value[1], 1) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+ if (ngx_http_proxy_rewrite_regex(cf, pr, &value[1], 0) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ } else {
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &pr->pattern.complex;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ pr->handler = ngx_http_proxy_rewrite_complex_handler;
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[2];
+ ccv.complex_value = &pr->replacement;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_proxy_rewrite_regex(ngx_conf_t *cf, ngx_http_proxy_rewrite_t *pr,
+ ngx_str_t *regex, ngx_uint_t caseless)
+{
+#if (NGX_PCRE)
+ u_char errstr[NGX_MAX_CONF_ERRSTR];
+ ngx_regex_compile_t rc;
+
+ ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+ rc.pattern = *regex;
+ rc.err.len = NGX_MAX_CONF_ERRSTR;
+ rc.err.data = errstr;
+
+ if (caseless) {
+ rc.options = NGX_REGEX_CASELESS;
+ }
+
+ pr->pattern.regex = ngx_http_regex_compile(cf, &rc);
+ if (pr->pattern.regex == NULL) {
+ return NGX_ERROR;
+ }
+
+ pr->handler = ngx_http_proxy_rewrite_regex_handler;
+
+ return NGX_OK;
+
+#else
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "using regex \"%V\" requires PCRE library", regex);
+ return NGX_ERROR;
+
+#endif
+}
+
+
+static char *
+ngx_http_proxy_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_proxy_loc_conf_t *plcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_script_compile_t sc;
+
+ if (plcf->upstream.store != NGX_CONF_UNSET) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ plcf->upstream.store = 0;
+ return NGX_CONF_OK;
+ }
+
+#if (NGX_HTTP_CACHE)
+ if (plcf->upstream.cache > 0) {
+ return "is incompatible with \"proxy_cache\"";
+ }
+#endif
+
+ plcf->upstream.store = 1;
+
+ if (ngx_strcmp(value[1].data, "on") == 0) {
+ return NGX_CONF_OK;
+ }
+
+ /* include the terminating '\0' into script */
+ value[1].len++;
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = &value[1];
+ sc.lengths = &plcf->upstream.store_lengths;
+ sc.values = &plcf->upstream.store_values;
+ sc.variables = ngx_http_script_variables_count(&value[1]);
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static char *
+ngx_http_proxy_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_proxy_loc_conf_t *plcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_complex_value_t cv;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ if (plcf->upstream.cache != NGX_CONF_UNSET) {
+ return "is duplicate";
+ }
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ plcf->upstream.cache = 0;
+ return NGX_CONF_OK;
+ }
+
+ if (plcf->upstream.store > 0) {
+ return "is incompatible with \"proxy_store\"";
+ }
+
+ plcf->upstream.cache = 1;
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cv.lengths != NULL) {
+
+ plcf->upstream.cache_value = ngx_palloc(cf->pool,
+ sizeof(ngx_http_complex_value_t));
+ if (plcf->upstream.cache_value == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *plcf->upstream.cache_value = cv;
+
+ return NGX_CONF_OK;
+ }
+
+ plcf->upstream.cache_zone = ngx_shared_memory_add(cf, &value[1], 0,
+ &ngx_http_proxy_module);
+ if (plcf->upstream.cache_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_proxy_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_proxy_loc_conf_t *plcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ if (plcf->cache_key.value.data) {
+ return "is duplicate";
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &plcf->cache_key;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+#endif
+
+
+#if (NGX_HTTP_SSL)
+
+static char *
+ngx_http_proxy_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_proxy_loc_conf_t *plcf = conf;
+
+ ngx_str_t *value;
+
+ if (plcf->ssl_passwords != NGX_CONF_UNSET_PTR) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ plcf->ssl_passwords = ngx_ssl_read_password_file(cf, &value[1]);
+
+ if (plcf->ssl_passwords == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+#endif
+
+
+static char *
+ngx_http_proxy_lowat_check(ngx_conf_t *cf, void *post, void *data)
+{
+#if (NGX_FREEBSD)
+ ssize_t *np = data;
+
+ if ((u_long) *np >= ngx_freebsd_net_inet_tcp_sendspace) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"proxy_send_lowat\" must be less than %d "
+ "(sysctl net.inet.tcp.sendspace)",
+ ngx_freebsd_net_inet_tcp_sendspace);
+
+ return NGX_CONF_ERROR;
+ }
+
+#elif !(NGX_HAVE_SO_SNDLOWAT)
+ ssize_t *np = data;
+
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "\"proxy_send_lowat\" is not supported, ignored");
+
+ *np = 0;
+
+#endif
+
+ return NGX_CONF_OK;
+}
+
+
+#if (NGX_HTTP_SSL)
+
+static ngx_int_t
+ngx_http_proxy_set_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *plcf)
+{
+ ngx_pool_cleanup_t *cln;
+
+ plcf->upstream.ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t));
+ if (plcf->upstream.ssl == NULL) {
+ return NGX_ERROR;
+ }
+
+ plcf->upstream.ssl->log = cf->log;
+
+ if (ngx_ssl_create(plcf->upstream.ssl, plcf->ssl_protocols, NULL)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ cln = ngx_pool_cleanup_add(cf->pool, 0);
+ if (cln == NULL) {
+ return NGX_ERROR;
+ }
+
+ cln->handler = ngx_ssl_cleanup_ctx;
+ cln->data = plcf->upstream.ssl;
+
+ if (plcf->ssl_certificate.len) {
+
+ if (plcf->ssl_certificate_key.len == 0) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"proxy_ssl_certificate_key\" is defined "
+ "for certificate \"%V\"", &plcf->ssl_certificate);
+ return NGX_ERROR;
+ }
+
+ if (ngx_ssl_certificate(cf, plcf->upstream.ssl, &plcf->ssl_certificate,
+ &plcf->ssl_certificate_key, plcf->ssl_passwords)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ if (ngx_ssl_ciphers(cf, plcf->upstream.ssl, &plcf->ssl_ciphers, 0)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ if (plcf->upstream.ssl_verify) {
+ if (plcf->ssl_trusted_certificate.len == 0) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no proxy_ssl_trusted_certificate for proxy_ssl_verify");
+ return NGX_ERROR;
+ }
+
+ if (ngx_ssl_trusted_certificate(cf, plcf->upstream.ssl,
+ &plcf->ssl_trusted_certificate,
+ plcf->ssl_verify_depth)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ if (ngx_ssl_crl(cf, plcf->upstream.ssl, &plcf->ssl_crl) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+#endif
+
+
+static void
+ngx_http_proxy_set_vars(ngx_url_t *u, ngx_http_proxy_vars_t *v)
+{
+ if (u->family != AF_UNIX) {
+
+ if (u->no_port || u->port == u->default_port) {
+
+ v->host_header = u->host;
+
+ if (u->default_port == 80) {
+ ngx_str_set(&v->port, "80");
+
+ } else {
+ ngx_str_set(&v->port, "443");
+ }
+
+ } else {
+ v->host_header.len = u->host.len + 1 + u->port_text.len;
+ v->host_header.data = u->host.data;
+ v->port = u->port_text;
+ }
+
+ v->key_start.len += v->host_header.len;
+
+ } else {
+ ngx_str_set(&v->host_header, "localhost");
+ ngx_str_null(&v->port);
+ v->key_start.len += sizeof("unix:") - 1 + u->host.len + 1;
+ }
+
+ v->uri = u->uri;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_random_index_module.c b/app/nginx/src/http/modules/ngx_http_random_index_module.c
new file mode 100644
index 0000000..b47ee4f
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_random_index_module.c
@@ -0,0 +1,317 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_flag_t enable;
+} ngx_http_random_index_loc_conf_t;
+
+
+#define NGX_HTTP_RANDOM_INDEX_PREALLOCATE 50
+
+
+static ngx_int_t ngx_http_random_index_error(ngx_http_request_t *r,
+ ngx_dir_t *dir, ngx_str_t *name);
+static ngx_int_t ngx_http_random_index_init(ngx_conf_t *cf);
+static void *ngx_http_random_index_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_random_index_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+
+
+static ngx_command_t ngx_http_random_index_commands[] = {
+
+ { ngx_string("random_index"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_random_index_loc_conf_t, enable),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_random_index_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_random_index_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_random_index_create_loc_conf, /* create location configuration */
+ ngx_http_random_index_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_random_index_module = {
+ NGX_MODULE_V1,
+ &ngx_http_random_index_module_ctx, /* module context */
+ ngx_http_random_index_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_random_index_handler(ngx_http_request_t *r)
+{
+ u_char *last, *filename;
+ size_t len, allocated, root;
+ ngx_err_t err;
+ ngx_int_t rc;
+ ngx_str_t path, uri, *name;
+ ngx_dir_t dir;
+ ngx_uint_t n, level;
+ ngx_array_t names;
+ ngx_http_random_index_loc_conf_t *rlcf;
+
+ if (r->uri.data[r->uri.len - 1] != '/') {
+ return NGX_DECLINED;
+ }
+
+ if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))) {
+ return NGX_DECLINED;
+ }
+
+ rlcf = ngx_http_get_module_loc_conf(r, ngx_http_random_index_module);
+
+ if (!rlcf->enable) {
+ return NGX_DECLINED;
+ }
+
+#if (NGX_HAVE_D_TYPE)
+ len = NGX_DIR_MASK_LEN;
+#else
+ len = NGX_HTTP_RANDOM_INDEX_PREALLOCATE;
+#endif
+
+ last = ngx_http_map_uri_to_path(r, &path, &root, len);
+ if (last == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ allocated = path.len;
+
+ path.len = last - path.data - 1;
+ path.data[path.len] = '\0';
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http random index: \"%s\"", path.data);
+
+ if (ngx_open_dir(&path, &dir) == NGX_ERROR) {
+ err = ngx_errno;
+
+ if (err == NGX_ENOENT
+ || err == NGX_ENOTDIR
+ || err == NGX_ENAMETOOLONG)
+ {
+ level = NGX_LOG_ERR;
+ rc = NGX_HTTP_NOT_FOUND;
+
+ } else if (err == NGX_EACCES) {
+ level = NGX_LOG_ERR;
+ rc = NGX_HTTP_FORBIDDEN;
+
+ } else {
+ level = NGX_LOG_CRIT;
+ rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_log_error(level, r->connection->log, err,
+ ngx_open_dir_n " \"%s\" failed", path.data);
+
+ return rc;
+ }
+
+ if (ngx_array_init(&names, r->pool, 32, sizeof(ngx_str_t)) != NGX_OK) {
+ return ngx_http_random_index_error(r, &dir, &path);
+ }
+
+ filename = path.data;
+ filename[path.len] = '/';
+
+ for ( ;; ) {
+ ngx_set_errno(0);
+
+ if (ngx_read_dir(&dir) == NGX_ERROR) {
+ err = ngx_errno;
+
+ if (err != NGX_ENOMOREFILES) {
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
+ ngx_read_dir_n " \"%V\" failed", &path);
+ return ngx_http_random_index_error(r, &dir, &path);
+ }
+
+ break;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http random index file: \"%s\"", ngx_de_name(&dir));
+
+ if (ngx_de_name(&dir)[0] == '.') {
+ continue;
+ }
+
+ len = ngx_de_namelen(&dir);
+
+ if (dir.type == 0 || ngx_de_is_link(&dir)) {
+
+ /* 1 byte for '/' and 1 byte for terminating '\0' */
+
+ if (path.len + 1 + len + 1 > allocated) {
+ allocated = path.len + 1 + len + 1
+ + NGX_HTTP_RANDOM_INDEX_PREALLOCATE;
+
+ filename = ngx_pnalloc(r->pool, allocated);
+ if (filename == NULL) {
+ return ngx_http_random_index_error(r, &dir, &path);
+ }
+
+ last = ngx_cpystrn(filename, path.data, path.len + 1);
+ *last++ = '/';
+ }
+
+ ngx_cpystrn(last, ngx_de_name(&dir), len + 1);
+
+ if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) {
+ err = ngx_errno;
+
+ if (err != NGX_ENOENT) {
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
+ ngx_de_info_n " \"%s\" failed", filename);
+ return ngx_http_random_index_error(r, &dir, &path);
+ }
+
+ if (ngx_de_link_info(filename, &dir) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+ ngx_de_link_info_n " \"%s\" failed",
+ filename);
+ return ngx_http_random_index_error(r, &dir, &path);
+ }
+ }
+ }
+
+ if (!ngx_de_is_file(&dir)) {
+ continue;
+ }
+
+ name = ngx_array_push(&names);
+ if (name == NULL) {
+ return ngx_http_random_index_error(r, &dir, &path);
+ }
+
+ name->len = len;
+
+ name->data = ngx_pnalloc(r->pool, len);
+ if (name->data == NULL) {
+ return ngx_http_random_index_error(r, &dir, &path);
+ }
+
+ ngx_memcpy(name->data, ngx_de_name(&dir), len);
+ }
+
+ if (ngx_close_dir(&dir) == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
+ ngx_close_dir_n " \"%V\" failed", &path);
+ }
+
+ n = names.nelts;
+
+ if (n == 0) {
+ return NGX_DECLINED;
+ }
+
+ name = names.elts;
+
+ n = (ngx_uint_t) (((uint64_t) ngx_random() * n) / 0x80000000);
+
+ uri.len = r->uri.len + name[n].len;
+
+ uri.data = ngx_pnalloc(r->pool, uri.len);
+ if (uri.data == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ last = ngx_copy(uri.data, r->uri.data, r->uri.len);
+ ngx_memcpy(last, name[n].data, name[n].len);
+
+ return ngx_http_internal_redirect(r, &uri, &r->args);
+}
+
+
+static ngx_int_t
+ngx_http_random_index_error(ngx_http_request_t *r, ngx_dir_t *dir,
+ ngx_str_t *name)
+{
+ if (ngx_close_dir(dir) == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
+ ngx_close_dir_n " \"%V\" failed", name);
+ }
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+}
+
+
+static void *
+ngx_http_random_index_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_random_index_loc_conf_t *conf;
+
+ conf = ngx_palloc(cf->pool, sizeof(ngx_http_random_index_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->enable = NGX_CONF_UNSET;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_random_index_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_random_index_loc_conf_t *prev = parent;
+ ngx_http_random_index_loc_conf_t *conf = child;
+
+ ngx_conf_merge_value(conf->enable, prev->enable, 0);
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_random_index_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_random_index_handler;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_range_filter_module.c b/app/nginx/src/http/modules/ngx_http_range_filter_module.c
new file mode 100644
index 0000000..951a00d
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_range_filter_module.c
@@ -0,0 +1,920 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#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 multipart format:
+ *
+ * "HTTP/1.0 206 Partial Content" CRLF
+ * ... header ...
+ * "Content-Type: multipart/byteranges; boundary=0123456789" CRLF
+ * CRLF
+ * CRLF
+ * "--0123456789" CRLF
+ * "Content-Type: image/jpeg" CRLF
+ * "Content-Range: bytes START0-END0/SIZE" CRLF
+ * CRLF
+ * ... data ...
+ * CRLF
+ * "--0123456789" CRLF
+ * "Content-Type: image/jpeg" CRLF
+ * "Content-Range: bytes START1-END1/SIZE" CRLF
+ * CRLF
+ * ... data ...
+ * CRLF
+ * "--0123456789--" CRLF
+ */
+
+
+typedef struct {
+ off_t start;
+ off_t end;
+ ngx_str_t content_range;
+} ngx_http_range_t;
+
+
+typedef struct {
+ off_t offset;
+ ngx_str_t boundary_header;
+ ngx_array_t ranges;
+} ngx_http_range_filter_ctx_t;
+
+
+static ngx_int_t ngx_http_range_parse(ngx_http_request_t *r,
+ ngx_http_range_filter_ctx_t *ctx, ngx_uint_t ranges);
+static ngx_int_t ngx_http_range_singlepart_header(ngx_http_request_t *r,
+ ngx_http_range_filter_ctx_t *ctx);
+static ngx_int_t ngx_http_range_multipart_header(ngx_http_request_t *r,
+ ngx_http_range_filter_ctx_t *ctx);
+static ngx_int_t ngx_http_range_not_satisfiable(ngx_http_request_t *r);
+static ngx_int_t ngx_http_range_test_overlapped(ngx_http_request_t *r,
+ ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);
+static ngx_int_t ngx_http_range_singlepart_body(ngx_http_request_t *r,
+ ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);
+static ngx_int_t ngx_http_range_multipart_body(ngx_http_request_t *r,
+ ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in);
+
+static ngx_int_t ngx_http_range_header_filter_init(ngx_conf_t *cf);
+static ngx_int_t ngx_http_range_body_filter_init(ngx_conf_t *cf);
+
+
+static ngx_http_module_t ngx_http_range_header_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_range_header_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL, /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_range_header_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_range_header_filter_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_module_t ngx_http_range_body_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_range_body_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL, /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_range_body_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_range_body_filter_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_range_header_filter(ngx_http_request_t *r)
+{
+ time_t if_range_time;
+ ngx_str_t *if_range, *etag;
+ ngx_uint_t ranges;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_range_filter_ctx_t *ctx;
+
+ if (r->http_version < NGX_HTTP_VERSION_10
+ || r->headers_out.status != NGX_HTTP_OK
+ || (r != r->main && !r->subrequest_ranges)
+ || r->headers_out.content_length_n == -1
+ || !r->allow_ranges)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->max_ranges == 0) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ if (r->headers_in.range == NULL
+ || r->headers_in.range->value.len < 7
+ || ngx_strncasecmp(r->headers_in.range->value.data,
+ (u_char *) "bytes=", 6)
+ != 0)
+ {
+ goto next_filter;
+ }
+
+ if (r->headers_in.if_range) {
+
+ if_range = &r->headers_in.if_range->value;
+
+ if (if_range->len >= 2 && if_range->data[if_range->len - 1] == '"') {
+
+ if (r->headers_out.etag == NULL) {
+ goto next_filter;
+ }
+
+ etag = &r->headers_out.etag->value;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http ir:%V etag:%V", if_range, etag);
+
+ if (if_range->len != etag->len
+ || ngx_strncmp(if_range->data, etag->data, etag->len) != 0)
+ {
+ goto next_filter;
+ }
+
+ goto parse;
+ }
+
+ if (r->headers_out.last_modified_time == (time_t) -1) {
+ goto next_filter;
+ }
+
+ if_range_time = ngx_parse_http_time(if_range->data, if_range->len);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http ir:%T lm:%T",
+ if_range_time, r->headers_out.last_modified_time);
+
+ if (if_range_time != r->headers_out.last_modified_time) {
+ goto next_filter;
+ }
+ }
+
+parse:
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_range_filter_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->offset = r->headers_out.content_offset;
+
+ ranges = r->single_range ? 1 : clcf->max_ranges;
+
+ switch (ngx_http_range_parse(r, ctx, ranges)) {
+
+ case NGX_OK:
+ ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);
+
+ r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
+ r->headers_out.status_line.len = 0;
+
+ if (ctx->ranges.nelts == 1) {
+ return ngx_http_range_singlepart_header(r, ctx);
+ }
+
+ return ngx_http_range_multipart_header(r, ctx);
+
+ case NGX_HTTP_RANGE_NOT_SATISFIABLE:
+ return ngx_http_range_not_satisfiable(r);
+
+ case NGX_ERROR:
+ return NGX_ERROR;
+
+ default: /* NGX_DECLINED */
+ break;
+ }
+
+next_filter:
+
+ r->headers_out.accept_ranges = ngx_list_push(&r->headers_out.headers);
+ if (r->headers_out.accept_ranges == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->headers_out.accept_ranges->hash = 1;
+ ngx_str_set(&r->headers_out.accept_ranges->key, "Accept-Ranges");
+ ngx_str_set(&r->headers_out.accept_ranges->value, "bytes");
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_range_parse(ngx_http_request_t *r, ngx_http_range_filter_ctx_t *ctx,
+ ngx_uint_t ranges)
+{
+ u_char *p;
+ off_t start, end, size, content_length, cutoff,
+ cutlim;
+ ngx_uint_t suffix;
+ ngx_http_range_t *range;
+ ngx_http_range_filter_ctx_t *mctx;
+
+ if (r != r->main) {
+ mctx = ngx_http_get_module_ctx(r->main,
+ ngx_http_range_body_filter_module);
+ if (mctx) {
+ ctx->ranges = mctx->ranges;
+ return NGX_OK;
+ }
+ }
+
+ if (ngx_array_init(&ctx->ranges, r->pool, 1, sizeof(ngx_http_range_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ p = r->headers_in.range->value.data + 6;
+ size = 0;
+ content_length = r->headers_out.content_length_n;
+
+ cutoff = NGX_MAX_OFF_T_VALUE / 10;
+ cutlim = NGX_MAX_OFF_T_VALUE % 10;
+
+ for ( ;; ) {
+ start = 0;
+ end = 0;
+ suffix = 0;
+
+ while (*p == ' ') { p++; }
+
+ if (*p != '-') {
+ if (*p < '0' || *p > '9') {
+ return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ }
+
+ while (*p >= '0' && *p <= '9') {
+ if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) {
+ return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ }
+
+ start = start * 10 + *p++ - '0';
+ }
+
+ while (*p == ' ') { p++; }
+
+ if (*p++ != '-') {
+ return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ }
+
+ while (*p == ' ') { p++; }
+
+ if (*p == ',' || *p == '\0') {
+ end = content_length;
+ goto found;
+ }
+
+ } else {
+ suffix = 1;
+ p++;
+ }
+
+ if (*p < '0' || *p > '9') {
+ return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ }
+
+ while (*p >= '0' && *p <= '9') {
+ if (end >= cutoff && (end > cutoff || *p - '0' > cutlim)) {
+ return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ }
+
+ end = end * 10 + *p++ - '0';
+ }
+
+ while (*p == ' ') { p++; }
+
+ if (*p != ',' && *p != '\0') {
+ return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ }
+
+ if (suffix) {
+ start = content_length - end;
+ end = content_length - 1;
+ }
+
+ if (end >= content_length) {
+ end = content_length;
+
+ } else {
+ end++;
+ }
+
+ found:
+
+ if (start < end) {
+ range = ngx_array_push(&ctx->ranges);
+ if (range == NULL) {
+ return NGX_ERROR;
+ }
+
+ range->start = start;
+ range->end = end;
+
+ size += end - start;
+
+ if (ranges-- == 0) {
+ return NGX_DECLINED;
+ }
+ }
+
+ if (*p++ != ',') {
+ break;
+ }
+ }
+
+ if (ctx->ranges.nelts == 0) {
+ return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+ }
+
+ if (size > content_length) {
+ return NGX_DECLINED;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_range_singlepart_header(ngx_http_request_t *r,
+ ngx_http_range_filter_ctx_t *ctx)
+{
+ ngx_table_elt_t *content_range;
+ ngx_http_range_t *range;
+
+ if (r != r->main) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ content_range = ngx_list_push(&r->headers_out.headers);
+ if (content_range == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->headers_out.content_range = content_range;
+
+ content_range->hash = 1;
+ ngx_str_set(&content_range->key, "Content-Range");
+
+ content_range->value.data = ngx_pnalloc(r->pool,
+ sizeof("bytes -/") - 1 + 3 * NGX_OFF_T_LEN);
+ if (content_range->value.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ /* "Content-Range: bytes SSSS-EEEE/TTTT" header */
+
+ range = ctx->ranges.elts;
+
+ content_range->value.len = ngx_sprintf(content_range->value.data,
+ "bytes %O-%O/%O",
+ range->start, range->end - 1,
+ r->headers_out.content_length_n)
+ - content_range->value.data;
+
+ r->headers_out.content_length_n = range->end - range->start;
+ r->headers_out.content_offset = range->start;
+
+ if (r->headers_out.content_length) {
+ r->headers_out.content_length->hash = 0;
+ r->headers_out.content_length = NULL;
+ }
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_range_multipart_header(ngx_http_request_t *r,
+ ngx_http_range_filter_ctx_t *ctx)
+{
+ size_t len;
+ ngx_uint_t i;
+ ngx_http_range_t *range;
+ ngx_atomic_uint_t boundary;
+
+ len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
+ + sizeof(CRLF "Content-Type: ") - 1
+ + r->headers_out.content_type.len
+ + sizeof(CRLF "Content-Range: bytes ") - 1;
+
+ if (r->headers_out.content_type_len == r->headers_out.content_type.len
+ && r->headers_out.charset.len)
+ {
+ len += sizeof("; charset=") - 1 + r->headers_out.charset.len;
+ }
+
+ ctx->boundary_header.data = ngx_pnalloc(r->pool, len);
+ if (ctx->boundary_header.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ boundary = ngx_next_temp_number(0);
+
+ /*
+ * The boundary header of the range:
+ * CRLF
+ * "--0123456789" CRLF
+ * "Content-Type: image/jpeg" CRLF
+ * "Content-Range: bytes "
+ */
+
+ if (r->headers_out.content_type_len == r->headers_out.content_type.len
+ && r->headers_out.charset.len)
+ {
+ ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
+ CRLF "--%0muA" CRLF
+ "Content-Type: %V; charset=%V" CRLF
+ "Content-Range: bytes ",
+ boundary,
+ &r->headers_out.content_type,
+ &r->headers_out.charset)
+ - ctx->boundary_header.data;
+
+ } else if (r->headers_out.content_type.len) {
+ ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
+ CRLF "--%0muA" CRLF
+ "Content-Type: %V" CRLF
+ "Content-Range: bytes ",
+ boundary,
+ &r->headers_out.content_type)
+ - ctx->boundary_header.data;
+
+ } else {
+ ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
+ CRLF "--%0muA" CRLF
+ "Content-Range: bytes ",
+ boundary)
+ - ctx->boundary_header.data;
+ }
+
+ r->headers_out.content_type.data =
+ ngx_pnalloc(r->pool,
+ sizeof("Content-Type: multipart/byteranges; boundary=") - 1
+ + NGX_ATOMIC_T_LEN);
+
+ if (r->headers_out.content_type.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->headers_out.content_type_lowcase = NULL;
+
+ /* "Content-Type: multipart/byteranges; boundary=0123456789" */
+
+ r->headers_out.content_type.len =
+ ngx_sprintf(r->headers_out.content_type.data,
+ "multipart/byteranges; boundary=%0muA",
+ boundary)
+ - r->headers_out.content_type.data;
+
+ r->headers_out.content_type_len = r->headers_out.content_type.len;
+
+ r->headers_out.charset.len = 0;
+
+ /* the size of the last boundary CRLF "--0123456789--" CRLF */
+
+ len = sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN + sizeof("--" CRLF) - 1;
+
+ range = ctx->ranges.elts;
+ for (i = 0; i < ctx->ranges.nelts; i++) {
+
+ /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */
+
+ range[i].content_range.data =
+ ngx_pnalloc(r->pool, 3 * NGX_OFF_T_LEN + 2 + 4);
+
+ if (range[i].content_range.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ range[i].content_range.len = ngx_sprintf(range[i].content_range.data,
+ "%O-%O/%O" CRLF CRLF,
+ range[i].start, range[i].end - 1,
+ r->headers_out.content_length_n)
+ - range[i].content_range.data;
+
+ len += ctx->boundary_header.len + range[i].content_range.len
+ + (size_t) (range[i].end - range[i].start);
+ }
+
+ r->headers_out.content_length_n = len;
+
+ if (r->headers_out.content_length) {
+ r->headers_out.content_length->hash = 0;
+ r->headers_out.content_length = NULL;
+ }
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_range_not_satisfiable(ngx_http_request_t *r)
+{
+ ngx_table_elt_t *content_range;
+
+ r->headers_out.status = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+
+ content_range = ngx_list_push(&r->headers_out.headers);
+ if (content_range == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->headers_out.content_range = content_range;
+
+ content_range->hash = 1;
+ ngx_str_set(&content_range->key, "Content-Range");
+
+ content_range->value.data = ngx_pnalloc(r->pool,
+ sizeof("bytes */") - 1 + NGX_OFF_T_LEN);
+ if (content_range->value.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ content_range->value.len = ngx_sprintf(content_range->value.data,
+ "bytes */%O",
+ r->headers_out.content_length_n)
+ - content_range->value.data;
+
+ ngx_http_clear_content_length(r);
+
+ return NGX_HTTP_RANGE_NOT_SATISFIABLE;
+}
+
+
+static ngx_int_t
+ngx_http_range_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ ngx_http_range_filter_ctx_t *ctx;
+
+ if (in == NULL) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module);
+
+ if (ctx == NULL) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ if (ctx->ranges.nelts == 1) {
+ return ngx_http_range_singlepart_body(r, ctx, in);
+ }
+
+ /*
+ * multipart ranges are supported only if whole body is in a single buffer
+ */
+
+ if (ngx_buf_special(in->buf)) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ if (ngx_http_range_test_overlapped(r, ctx, in) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return ngx_http_range_multipart_body(r, ctx, in);
+}
+
+
+static ngx_int_t
+ngx_http_range_test_overlapped(ngx_http_request_t *r,
+ ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
+{
+ off_t start, last;
+ ngx_buf_t *buf;
+ ngx_uint_t i;
+ ngx_http_range_t *range;
+
+ if (ctx->offset) {
+ goto overlapped;
+ }
+
+ buf = in->buf;
+
+ if (!buf->last_buf) {
+ start = ctx->offset;
+ last = ctx->offset + ngx_buf_size(buf);
+
+ range = ctx->ranges.elts;
+ for (i = 0; i < ctx->ranges.nelts; i++) {
+ if (start > range[i].start || last < range[i].end) {
+ goto overlapped;
+ }
+ }
+ }
+
+ ctx->offset = ngx_buf_size(buf);
+
+ return NGX_OK;
+
+overlapped:
+
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "range in overlapped buffers");
+
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_range_singlepart_body(ngx_http_request_t *r,
+ ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
+{
+ off_t start, last;
+ ngx_buf_t *buf;
+ ngx_chain_t *out, *cl, **ll;
+ ngx_http_range_t *range;
+
+ out = NULL;
+ ll = &out;
+ range = ctx->ranges.elts;
+
+ for (cl = in; cl; cl = cl->next) {
+
+ buf = cl->buf;
+
+ start = ctx->offset;
+ last = ctx->offset + ngx_buf_size(buf);
+
+ ctx->offset = last;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http range body buf: %O-%O", start, last);
+
+ if (ngx_buf_special(buf)) {
+ *ll = cl;
+ ll = &cl->next;
+ continue;
+ }
+
+ if (range->end <= start || range->start >= last) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http range body skip");
+
+ if (buf->in_file) {
+ buf->file_pos = buf->file_last;
+ }
+
+ buf->pos = buf->last;
+ buf->sync = 1;
+
+ continue;
+ }
+
+ if (range->start > start) {
+
+ if (buf->in_file) {
+ buf->file_pos += range->start - start;
+ }
+
+ if (ngx_buf_in_memory(buf)) {
+ buf->pos += (size_t) (range->start - start);
+ }
+ }
+
+ if (range->end <= last) {
+
+ if (buf->in_file) {
+ buf->file_last -= last - range->end;
+ }
+
+ if (ngx_buf_in_memory(buf)) {
+ buf->last -= (size_t) (last - range->end);
+ }
+
+ buf->last_buf = (r == r->main) ? 1 : 0;
+ buf->last_in_chain = 1;
+ *ll = cl;
+ cl->next = NULL;
+
+ break;
+ }
+
+ *ll = cl;
+ ll = &cl->next;
+ }
+
+ if (out == NULL) {
+ return NGX_OK;
+ }
+
+ return ngx_http_next_body_filter(r, out);
+}
+
+
+static ngx_int_t
+ngx_http_range_multipart_body(ngx_http_request_t *r,
+ ngx_http_range_filter_ctx_t *ctx, ngx_chain_t *in)
+{
+ ngx_buf_t *b, *buf;
+ ngx_uint_t i;
+ ngx_chain_t *out, *hcl, *rcl, *dcl, **ll;
+ ngx_http_range_t *range;
+
+ ll = &out;
+ buf = in->buf;
+ range = ctx->ranges.elts;
+
+ for (i = 0; i < ctx->ranges.nelts; i++) {
+
+ /*
+ * The boundary header of the range:
+ * CRLF
+ * "--0123456789" CRLF
+ * "Content-Type: image/jpeg" CRLF
+ * "Content-Range: bytes "
+ */
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->memory = 1;
+ b->pos = ctx->boundary_header.data;
+ b->last = ctx->boundary_header.data + ctx->boundary_header.len;
+
+ hcl = ngx_alloc_chain_link(r->pool);
+ if (hcl == NULL) {
+ return NGX_ERROR;
+ }
+
+ hcl->buf = b;
+
+
+ /* "SSSS-EEEE/TTTT" CRLF CRLF */
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->temporary = 1;
+ b->pos = range[i].content_range.data;
+ b->last = range[i].content_range.data + range[i].content_range.len;
+
+ rcl = ngx_alloc_chain_link(r->pool);
+ if (rcl == NULL) {
+ return NGX_ERROR;
+ }
+
+ rcl->buf = b;
+
+
+ /* the range data */
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->in_file = buf->in_file;
+ b->temporary = buf->temporary;
+ b->memory = buf->memory;
+ b->mmap = buf->mmap;
+ b->file = buf->file;
+
+ if (buf->in_file) {
+ b->file_pos = buf->file_pos + range[i].start;
+ b->file_last = buf->file_pos + range[i].end;
+ }
+
+ if (ngx_buf_in_memory(buf)) {
+ b->pos = buf->pos + (size_t) range[i].start;
+ b->last = buf->pos + (size_t) range[i].end;
+ }
+
+ dcl = ngx_alloc_chain_link(r->pool);
+ if (dcl == NULL) {
+ return NGX_ERROR;
+ }
+
+ dcl->buf = b;
+
+ *ll = hcl;
+ hcl->next = rcl;
+ rcl->next = dcl;
+ ll = &dcl->next;
+ }
+
+ /* the last boundary CRLF "--0123456789--" CRLF */
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->temporary = 1;
+ b->last_buf = 1;
+
+ b->pos = ngx_pnalloc(r->pool, sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN
+ + sizeof("--" CRLF) - 1);
+ if (b->pos == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->last = ngx_cpymem(b->pos, ctx->boundary_header.data,
+ sizeof(CRLF "--") - 1 + NGX_ATOMIC_T_LEN);
+ *b->last++ = '-'; *b->last++ = '-';
+ *b->last++ = CR; *b->last++ = LF;
+
+ hcl = ngx_alloc_chain_link(r->pool);
+ if (hcl == NULL) {
+ return NGX_ERROR;
+ }
+
+ hcl->buf = b;
+ hcl->next = NULL;
+
+ *ll = hcl;
+
+ return ngx_http_next_body_filter(r, out);
+}
+
+
+static ngx_int_t
+ngx_http_range_header_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_range_header_filter;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_range_body_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_range_body_filter;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_realip_module.c b/app/nginx/src/http/modules/ngx_http_realip_module.c
new file mode 100644
index 0000000..5e3355c
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_realip_module.c
@@ -0,0 +1,570 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_REALIP_XREALIP 0
+#define NGX_HTTP_REALIP_XFWD 1
+#define NGX_HTTP_REALIP_HEADER 2
+#define NGX_HTTP_REALIP_PROXY 3
+
+
+typedef struct {
+ ngx_array_t *from; /* array of ngx_cidr_t */
+ ngx_uint_t type;
+ ngx_uint_t hash;
+ ngx_str_t header;
+ ngx_flag_t recursive;
+} ngx_http_realip_loc_conf_t;
+
+
+typedef struct {
+ ngx_connection_t *connection;
+ struct sockaddr *sockaddr;
+ socklen_t socklen;
+ ngx_str_t addr_text;
+} ngx_http_realip_ctx_t;
+
+
+static ngx_int_t ngx_http_realip_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_realip_set_addr(ngx_http_request_t *r,
+ ngx_addr_t *addr);
+static void ngx_http_realip_cleanup(void *data);
+static char *ngx_http_realip_from(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_realip(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static void *ngx_http_realip_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_realip_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_realip_add_variables(ngx_conf_t *cf);
+static ngx_int_t ngx_http_realip_init(ngx_conf_t *cf);
+static ngx_http_realip_ctx_t *ngx_http_realip_get_module_ctx(
+ ngx_http_request_t *r);
+
+
+static ngx_int_t ngx_http_realip_remote_addr_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_realip_remote_port_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+
+
+static ngx_command_t ngx_http_realip_commands[] = {
+
+ { ngx_string("set_real_ip_from"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_realip_from,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("real_ip_header"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_realip,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("real_ip_recursive"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_realip_loc_conf_t, recursive),
+ NULL },
+
+ ngx_null_command
+};
+
+
+
+static ngx_http_module_t ngx_http_realip_module_ctx = {
+ ngx_http_realip_add_variables, /* preconfiguration */
+ ngx_http_realip_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_realip_create_loc_conf, /* create location configuration */
+ ngx_http_realip_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_realip_module = {
+ NGX_MODULE_V1,
+ &ngx_http_realip_module_ctx, /* module context */
+ ngx_http_realip_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_variable_t ngx_http_realip_vars[] = {
+
+ { ngx_string("realip_remote_addr"), NULL,
+ ngx_http_realip_remote_addr_variable, 0, 0, 0 },
+
+ { ngx_string("realip_remote_port"), NULL,
+ ngx_http_realip_remote_port_variable, 0, 0, 0 },
+
+ { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+
+static ngx_int_t
+ngx_http_realip_handler(ngx_http_request_t *r)
+{
+ u_char *p;
+ size_t len;
+ ngx_str_t *value;
+ ngx_uint_t i, hash;
+ ngx_addr_t addr;
+ ngx_array_t *xfwd;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header;
+ ngx_connection_t *c;
+ ngx_http_realip_ctx_t *ctx;
+ ngx_http_realip_loc_conf_t *rlcf;
+
+ rlcf = ngx_http_get_module_loc_conf(r, ngx_http_realip_module);
+
+ if (rlcf->from == NULL) {
+ return NGX_DECLINED;
+ }
+
+ ctx = ngx_http_realip_get_module_ctx(r);
+
+ if (ctx) {
+ return NGX_DECLINED;
+ }
+
+ switch (rlcf->type) {
+
+ case NGX_HTTP_REALIP_XREALIP:
+
+ if (r->headers_in.x_real_ip == NULL) {
+ return NGX_DECLINED;
+ }
+
+ value = &r->headers_in.x_real_ip->value;
+ xfwd = NULL;
+
+ break;
+
+ case NGX_HTTP_REALIP_XFWD:
+
+ xfwd = &r->headers_in.x_forwarded_for;
+
+ if (xfwd->elts == NULL) {
+ return NGX_DECLINED;
+ }
+
+ value = NULL;
+
+ break;
+
+ case NGX_HTTP_REALIP_PROXY:
+
+ value = &r->connection->proxy_protocol_addr;
+
+ if (value->len == 0) {
+ return NGX_DECLINED;
+ }
+
+ xfwd = NULL;
+
+ break;
+
+ default: /* NGX_HTTP_REALIP_HEADER */
+
+ part = &r->headers_in.headers.part;
+ header = part->elts;
+
+ hash = rlcf->hash;
+ len = rlcf->header.len;
+ p = rlcf->header.data;
+
+ for (i = 0; /* void */ ; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (hash == header[i].hash
+ && len == header[i].key.len
+ && ngx_strncmp(p, header[i].lowcase_key, len) == 0)
+ {
+ value = &header[i].value;
+ xfwd = NULL;
+
+ goto found;
+ }
+ }
+
+ return NGX_DECLINED;
+ }
+
+found:
+
+ c = r->connection;
+
+ addr.sockaddr = c->sockaddr;
+ addr.socklen = c->socklen;
+ /* addr.name = c->addr_text; */
+
+ if (ngx_http_get_forwarded_addr(r, &addr, xfwd, value, rlcf->from,
+ rlcf->recursive)
+ != NGX_DECLINED)
+ {
+ if (rlcf->type == NGX_HTTP_REALIP_PROXY) {
+ ngx_inet_set_port(addr.sockaddr, c->proxy_protocol_port);
+ }
+
+ return ngx_http_realip_set_addr(r, &addr);
+ }
+
+ return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_realip_set_addr(ngx_http_request_t *r, ngx_addr_t *addr)
+{
+ size_t len;
+ u_char *p;
+ u_char text[NGX_SOCKADDR_STRLEN];
+ ngx_connection_t *c;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_realip_ctx_t *ctx;
+
+ cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_http_realip_ctx_t));
+ if (cln == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ctx = cln->data;
+
+ c = r->connection;
+
+ len = ngx_sock_ntop(addr->sockaddr, addr->socklen, text,
+ NGX_SOCKADDR_STRLEN, 0);
+ if (len == 0) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ p = ngx_pnalloc(c->pool, len);
+ if (p == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_memcpy(p, text, len);
+
+ cln->handler = ngx_http_realip_cleanup;
+ ngx_http_set_ctx(r, ctx, ngx_http_realip_module);
+
+ ctx->connection = c;
+ ctx->sockaddr = c->sockaddr;
+ ctx->socklen = c->socklen;
+ ctx->addr_text = c->addr_text;
+
+ c->sockaddr = addr->sockaddr;
+ c->socklen = addr->socklen;
+ c->addr_text.len = len;
+ c->addr_text.data = p;
+
+ return NGX_DECLINED;
+}
+
+
+static void
+ngx_http_realip_cleanup(void *data)
+{
+ ngx_http_realip_ctx_t *ctx = data;
+
+ ngx_connection_t *c;
+
+ c = ctx->connection;
+
+ c->sockaddr = ctx->sockaddr;
+ c->socklen = ctx->socklen;
+ c->addr_text = ctx->addr_text;
+}
+
+
+static char *
+ngx_http_realip_from(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_realip_loc_conf_t *rlcf = conf;
+
+ ngx_int_t rc;
+ ngx_str_t *value;
+ ngx_cidr_t *cidr;
+
+ value = cf->args->elts;
+
+ if (rlcf->from == NULL) {
+ rlcf->from = ngx_array_create(cf->pool, 2,
+ sizeof(ngx_cidr_t));
+ if (rlcf->from == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ cidr = ngx_array_push(rlcf->from);
+ if (cidr == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+ if (ngx_strcmp(value[1].data, "unix:") == 0) {
+ cidr->family = AF_UNIX;
+ return NGX_CONF_OK;
+ }
+
+#endif
+
+ rc = ngx_ptocidr(&value[1], cidr);
+
+ if (rc == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"",
+ &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "low address bits of %V are meaningless", &value[1]);
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_realip(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_realip_loc_conf_t *rlcf = conf;
+
+ ngx_str_t *value;
+
+ if (rlcf->type != NGX_CONF_UNSET_UINT) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "X-Real-IP") == 0) {
+ rlcf->type = NGX_HTTP_REALIP_XREALIP;
+ return NGX_CONF_OK;
+ }
+
+ if (ngx_strcmp(value[1].data, "X-Forwarded-For") == 0) {
+ rlcf->type = NGX_HTTP_REALIP_XFWD;
+ return NGX_CONF_OK;
+ }
+
+ if (ngx_strcmp(value[1].data, "proxy_protocol") == 0) {
+ rlcf->type = NGX_HTTP_REALIP_PROXY;
+ return NGX_CONF_OK;
+ }
+
+ rlcf->type = NGX_HTTP_REALIP_HEADER;
+ rlcf->hash = ngx_hash_strlow(value[1].data, value[1].data, value[1].len);
+ rlcf->header = value[1];
+
+ return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_realip_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_realip_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_realip_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->from = NULL;
+ * conf->hash = 0;
+ * conf->header = { 0, NULL };
+ */
+
+ conf->type = NGX_CONF_UNSET_UINT;
+ conf->recursive = NGX_CONF_UNSET;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_realip_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_realip_loc_conf_t *prev = parent;
+ ngx_http_realip_loc_conf_t *conf = child;
+
+ if (conf->from == NULL) {
+ conf->from = prev->from;
+ }
+
+ ngx_conf_merge_uint_value(conf->type, prev->type, NGX_HTTP_REALIP_XREALIP);
+ ngx_conf_merge_value(conf->recursive, prev->recursive, 0);
+
+ if (conf->header.len == 0) {
+ conf->hash = prev->hash;
+ conf->header = prev->header;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_realip_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var, *v;
+
+ for (v = ngx_http_realip_vars; v->name.len; v++) {
+ var = ngx_http_add_variable(cf, &v->name, v->flags);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = v->get_handler;
+ var->data = v->data;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_realip_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_POST_READ_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_realip_handler;
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_realip_handler;
+
+ return NGX_OK;
+}
+
+
+static ngx_http_realip_ctx_t *
+ngx_http_realip_get_module_ctx(ngx_http_request_t *r)
+{
+ ngx_pool_cleanup_t *cln;
+ ngx_http_realip_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_realip_module);
+
+ if (ctx == NULL && (r->internal || r->filter_finalize)) {
+
+ /*
+ * if module context was reset, the original address
+ * can still be found in the cleanup handler
+ */
+
+ for (cln = r->pool->cleanup; cln; cln = cln->next) {
+ if (cln->handler == ngx_http_realip_cleanup) {
+ ctx = cln->data;
+ break;
+ }
+ }
+ }
+
+ return ctx;
+}
+
+
+static ngx_int_t
+ngx_http_realip_remote_addr_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_str_t *addr_text;
+ ngx_http_realip_ctx_t *ctx;
+
+ ctx = ngx_http_realip_get_module_ctx(r);
+
+ addr_text = ctx ? &ctx->addr_text : &r->connection->addr_text;
+
+ v->len = addr_text->len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = addr_text->data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_realip_remote_port_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_uint_t port;
+ struct sockaddr *sa;
+ ngx_http_realip_ctx_t *ctx;
+
+ ctx = ngx_http_realip_get_module_ctx(r);
+
+ sa = ctx ? ctx->sockaddr : r->connection->sockaddr;
+
+ v->len = 0;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ v->data = ngx_pnalloc(r->pool, sizeof("65535") - 1);
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ port = ngx_inet_get_port(sa);
+
+ if (port > 0 && port < 65536) {
+ v->len = ngx_sprintf(v->data, "%ui", port) - v->data;
+ }
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_referer_module.c b/app/nginx/src/http/modules/ngx_http_referer_module.c
new file mode 100644
index 0000000..3f0f78e
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_referer_module.c
@@ -0,0 +1,671 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_REFERER_NO_URI_PART ((void *) 4)
+
+
+typedef struct {
+ ngx_hash_combined_t hash;
+
+#if (NGX_PCRE)
+ ngx_array_t *regex;
+ ngx_array_t *server_name_regex;
+#endif
+
+ ngx_flag_t no_referer;
+ ngx_flag_t blocked_referer;
+ ngx_flag_t server_names;
+
+ ngx_hash_keys_arrays_t *keys;
+
+ ngx_uint_t referer_hash_max_size;
+ ngx_uint_t referer_hash_bucket_size;
+} ngx_http_referer_conf_t;
+
+
+static void * ngx_http_referer_create_conf(ngx_conf_t *cf);
+static char * ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static ngx_int_t ngx_http_add_referer(ngx_conf_t *cf,
+ ngx_hash_keys_arrays_t *keys, ngx_str_t *value, ngx_str_t *uri);
+static ngx_int_t ngx_http_add_regex_referer(ngx_conf_t *cf,
+ ngx_http_referer_conf_t *rlcf, ngx_str_t *name);
+#if (NGX_PCRE)
+static ngx_int_t ngx_http_add_regex_server_name(ngx_conf_t *cf,
+ ngx_http_referer_conf_t *rlcf, ngx_http_regex_t *regex);
+#endif
+static int ngx_libc_cdecl ngx_http_cmp_referer_wildcards(const void *one,
+ const void *two);
+
+
+static ngx_command_t ngx_http_referer_commands[] = {
+
+ { ngx_string("valid_referers"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_valid_referers,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("referer_hash_max_size"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_referer_conf_t, referer_hash_max_size),
+ NULL },
+
+ { ngx_string("referer_hash_bucket_size"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_referer_conf_t, referer_hash_bucket_size),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_referer_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_referer_create_conf, /* create location configuration */
+ ngx_http_referer_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_referer_module = {
+ NGX_MODULE_V1,
+ &ngx_http_referer_module_ctx, /* module context */
+ ngx_http_referer_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_referer_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ u_char *p, *ref, *last;
+ size_t len;
+ ngx_str_t *uri;
+ ngx_uint_t i, key;
+ ngx_http_referer_conf_t *rlcf;
+ u_char buf[256];
+#if (NGX_PCRE)
+ ngx_int_t rc;
+ ngx_str_t referer;
+#endif
+
+ rlcf = ngx_http_get_module_loc_conf(r, ngx_http_referer_module);
+
+ if (rlcf->hash.hash.buckets == NULL
+ && rlcf->hash.wc_head == NULL
+ && rlcf->hash.wc_tail == NULL
+#if (NGX_PCRE)
+ && rlcf->regex == NULL
+ && rlcf->server_name_regex == NULL
+#endif
+ )
+ {
+ goto valid;
+ }
+
+ if (r->headers_in.referer == NULL) {
+ if (rlcf->no_referer) {
+ goto valid;
+ }
+
+ goto invalid;
+ }
+
+ len = r->headers_in.referer->value.len;
+ ref = r->headers_in.referer->value.data;
+
+ if (len >= sizeof("http://i.ru") - 1) {
+ last = ref + len;
+
+ if (ngx_strncasecmp(ref, (u_char *) "http://", 7) == 0) {
+ ref += 7;
+ len -= 7;
+ goto valid_scheme;
+
+ } else if (ngx_strncasecmp(ref, (u_char *) "https://", 8) == 0) {
+ ref += 8;
+ len -= 8;
+ goto valid_scheme;
+ }
+ }
+
+ if (rlcf->blocked_referer) {
+ goto valid;
+ }
+
+ goto invalid;
+
+valid_scheme:
+
+ i = 0;
+ key = 0;
+
+ for (p = ref; p < last; p++) {
+ if (*p == '/' || *p == ':') {
+ break;
+ }
+
+ if (i == 256) {
+ goto invalid;
+ }
+
+ buf[i] = ngx_tolower(*p);
+ key = ngx_hash(key, buf[i++]);
+ }
+
+ uri = ngx_hash_find_combined(&rlcf->hash, key, buf, p - ref);
+
+ if (uri) {
+ goto uri;
+ }
+
+#if (NGX_PCRE)
+
+ if (rlcf->server_name_regex) {
+ referer.len = p - ref;
+ referer.data = buf;
+
+ rc = ngx_regex_exec_array(rlcf->server_name_regex, &referer,
+ r->connection->log);
+
+ if (rc == NGX_OK) {
+ goto valid;
+ }
+
+ if (rc == NGX_ERROR) {
+ return rc;
+ }
+
+ /* NGX_DECLINED */
+ }
+
+ if (rlcf->regex) {
+ referer.len = len;
+ referer.data = ref;
+
+ rc = ngx_regex_exec_array(rlcf->regex, &referer, r->connection->log);
+
+ if (rc == NGX_OK) {
+ goto valid;
+ }
+
+ if (rc == NGX_ERROR) {
+ return rc;
+ }
+
+ /* NGX_DECLINED */
+ }
+
+#endif
+
+invalid:
+
+ *v = ngx_http_variable_true_value;
+
+ return NGX_OK;
+
+uri:
+
+ for ( /* void */ ; p < last; p++) {
+ if (*p == '/') {
+ break;
+ }
+ }
+
+ len = last - p;
+
+ if (uri == NGX_HTTP_REFERER_NO_URI_PART) {
+ goto valid;
+ }
+
+ if (len < uri->len || ngx_strncmp(uri->data, p, uri->len) != 0) {
+ goto invalid;
+ }
+
+valid:
+
+ *v = ngx_http_variable_null_value;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_referer_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_referer_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_referer_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->hash = { NULL };
+ * conf->server_names = 0;
+ * conf->keys = NULL;
+ */
+
+#if (NGX_PCRE)
+ conf->regex = NGX_CONF_UNSET_PTR;
+ conf->server_name_regex = NGX_CONF_UNSET_PTR;
+#endif
+
+ conf->no_referer = NGX_CONF_UNSET;
+ conf->blocked_referer = NGX_CONF_UNSET;
+ conf->referer_hash_max_size = NGX_CONF_UNSET_UINT;
+ conf->referer_hash_bucket_size = NGX_CONF_UNSET_UINT;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_referer_conf_t *prev = parent;
+ ngx_http_referer_conf_t *conf = child;
+
+ ngx_uint_t n;
+ ngx_hash_init_t hash;
+ ngx_http_server_name_t *sn;
+ ngx_http_core_srv_conf_t *cscf;
+
+ if (conf->keys == NULL) {
+ conf->hash = prev->hash;
+
+#if (NGX_PCRE)
+ ngx_conf_merge_ptr_value(conf->regex, prev->regex, NULL);
+ ngx_conf_merge_ptr_value(conf->server_name_regex,
+ prev->server_name_regex, NULL);
+#endif
+ ngx_conf_merge_value(conf->no_referer, prev->no_referer, 0);
+ ngx_conf_merge_value(conf->blocked_referer, prev->blocked_referer, 0);
+ ngx_conf_merge_uint_value(conf->referer_hash_max_size,
+ prev->referer_hash_max_size, 2048);
+ ngx_conf_merge_uint_value(conf->referer_hash_bucket_size,
+ prev->referer_hash_bucket_size, 64);
+
+ return NGX_CONF_OK;
+ }
+
+ if (conf->server_names == 1) {
+ cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module);
+
+ sn = cscf->server_names.elts;
+ for (n = 0; n < cscf->server_names.nelts; n++) {
+
+#if (NGX_PCRE)
+ if (sn[n].regex) {
+
+ if (ngx_http_add_regex_server_name(cf, conf, sn[n].regex)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+#endif
+
+ if (ngx_http_add_referer(cf, conf->keys, &sn[n].name, NULL)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+ }
+ }
+
+ if ((conf->no_referer == 1 || conf->blocked_referer == 1)
+ && conf->keys->keys.nelts == 0
+ && conf->keys->dns_wc_head.nelts == 0
+ && conf->keys->dns_wc_tail.nelts == 0)
+ {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "the \"none\" or \"blocked\" referers are specified "
+ "in the \"valid_referers\" directive "
+ "without any valid referer");
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_uint_value(conf->referer_hash_max_size,
+ prev->referer_hash_max_size, 2048);
+ ngx_conf_merge_uint_value(conf->referer_hash_bucket_size,
+ prev->referer_hash_bucket_size, 64);
+ conf->referer_hash_bucket_size = ngx_align(conf->referer_hash_bucket_size,
+ ngx_cacheline_size);
+
+ hash.key = ngx_hash_key_lc;
+ hash.max_size = conf->referer_hash_max_size;
+ hash.bucket_size = conf->referer_hash_bucket_size;
+ hash.name = "referer_hash";
+ hash.pool = cf->pool;
+
+ if (conf->keys->keys.nelts) {
+ hash.hash = &conf->hash.hash;
+ hash.temp_pool = NULL;
+
+ if (ngx_hash_init(&hash, conf->keys->keys.elts, conf->keys->keys.nelts)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (conf->keys->dns_wc_head.nelts) {
+
+ ngx_qsort(conf->keys->dns_wc_head.elts,
+ (size_t) conf->keys->dns_wc_head.nelts,
+ sizeof(ngx_hash_key_t),
+ ngx_http_cmp_referer_wildcards);
+
+ hash.hash = NULL;
+ hash.temp_pool = cf->temp_pool;
+
+ if (ngx_hash_wildcard_init(&hash, conf->keys->dns_wc_head.elts,
+ conf->keys->dns_wc_head.nelts)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
+ }
+
+ if (conf->keys->dns_wc_tail.nelts) {
+
+ ngx_qsort(conf->keys->dns_wc_tail.elts,
+ (size_t) conf->keys->dns_wc_tail.nelts,
+ sizeof(ngx_hash_key_t),
+ ngx_http_cmp_referer_wildcards);
+
+ hash.hash = NULL;
+ hash.temp_pool = cf->temp_pool;
+
+ if (ngx_hash_wildcard_init(&hash, conf->keys->dns_wc_tail.elts,
+ conf->keys->dns_wc_tail.nelts)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash;
+ }
+
+#if (NGX_PCRE)
+ ngx_conf_merge_ptr_value(conf->regex, prev->regex, NULL);
+ ngx_conf_merge_ptr_value(conf->server_name_regex, prev->server_name_regex,
+ NULL);
+#endif
+
+ if (conf->no_referer == NGX_CONF_UNSET) {
+ conf->no_referer = 0;
+ }
+
+ if (conf->blocked_referer == NGX_CONF_UNSET) {
+ conf->blocked_referer = 0;
+ }
+
+ conf->keys = NULL;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_referer_conf_t *rlcf = conf;
+
+ u_char *p;
+ ngx_str_t *value, uri, name;
+ ngx_uint_t i;
+ ngx_http_variable_t *var;
+
+ ngx_str_set(&name, "invalid_referer");
+
+ var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
+ if (var == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ var->get_handler = ngx_http_referer_variable;
+
+ if (rlcf->keys == NULL) {
+ rlcf->keys = ngx_pcalloc(cf->temp_pool, sizeof(ngx_hash_keys_arrays_t));
+ if (rlcf->keys == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ rlcf->keys->pool = cf->pool;
+ rlcf->keys->temp_pool = cf->pool;
+
+ if (ngx_hash_keys_array_init(rlcf->keys, NGX_HASH_SMALL) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+ if (value[i].len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid referer \"%V\"", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_strcmp(value[i].data, "none") == 0) {
+ rlcf->no_referer = 1;
+ continue;
+ }
+
+ if (ngx_strcmp(value[i].data, "blocked") == 0) {
+ rlcf->blocked_referer = 1;
+ continue;
+ }
+
+ if (ngx_strcmp(value[i].data, "server_names") == 0) {
+ rlcf->server_names = 1;
+ continue;
+ }
+
+ if (value[i].data[0] == '~') {
+ if (ngx_http_add_regex_referer(cf, rlcf, &value[i]) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ ngx_str_null(&uri);
+
+ p = (u_char *) ngx_strchr(value[i].data, '/');
+
+ if (p) {
+ uri.len = (value[i].data + value[i].len) - p;
+ uri.data = p;
+ value[i].len = p - value[i].data;
+ }
+
+ if (ngx_http_add_referer(cf, rlcf->keys, &value[i], &uri) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_add_referer(ngx_conf_t *cf, ngx_hash_keys_arrays_t *keys,
+ ngx_str_t *value, ngx_str_t *uri)
+{
+ ngx_int_t rc;
+ ngx_str_t *u;
+
+ if (uri == NULL || uri->len == 0) {
+ u = NGX_HTTP_REFERER_NO_URI_PART;
+
+ } else {
+ u = ngx_palloc(cf->pool, sizeof(ngx_str_t));
+ if (u == NULL) {
+ return NGX_ERROR;
+ }
+
+ *u = *uri;
+ }
+
+ rc = ngx_hash_add_key(keys, value, u, NGX_HASH_WILDCARD_KEY);
+
+ if (rc == NGX_OK) {
+ return NGX_OK;
+ }
+
+ if (rc == NGX_DECLINED) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid hostname or wildcard \"%V\"", value);
+ }
+
+ if (rc == NGX_BUSY) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "conflicting parameter \"%V\"", value);
+ }
+
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_add_regex_referer(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf,
+ ngx_str_t *name)
+{
+#if (NGX_PCRE)
+ ngx_regex_elt_t *re;
+ ngx_regex_compile_t rc;
+ u_char errstr[NGX_MAX_CONF_ERRSTR];
+
+ if (name->len == 1) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "empty regex in \"%V\"", name);
+ return NGX_ERROR;
+ }
+
+ if (rlcf->regex == NGX_CONF_UNSET_PTR) {
+ rlcf->regex = ngx_array_create(cf->pool, 2, sizeof(ngx_regex_elt_t));
+ if (rlcf->regex == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ re = ngx_array_push(rlcf->regex);
+ if (re == NULL) {
+ return NGX_ERROR;
+ }
+
+ name->len--;
+ name->data++;
+
+ ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+ rc.pattern = *name;
+ rc.pool = cf->pool;
+ rc.options = NGX_REGEX_CASELESS;
+ rc.err.len = NGX_MAX_CONF_ERRSTR;
+ rc.err.data = errstr;
+
+ if (ngx_regex_compile(&rc) != NGX_OK) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
+ return NGX_ERROR;
+ }
+
+ re->regex = rc.regex;
+ re->name = name->data;
+
+ return NGX_OK;
+
+#else
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the using of the regex \"%V\" requires PCRE library",
+ name);
+
+ return NGX_ERROR;
+
+#endif
+}
+
+
+#if (NGX_PCRE)
+
+static ngx_int_t
+ngx_http_add_regex_server_name(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf,
+ ngx_http_regex_t *regex)
+{
+ ngx_regex_elt_t *re;
+
+ if (rlcf->server_name_regex == NGX_CONF_UNSET_PTR) {
+ rlcf->server_name_regex = ngx_array_create(cf->pool, 2,
+ sizeof(ngx_regex_elt_t));
+ if (rlcf->server_name_regex == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ re = ngx_array_push(rlcf->server_name_regex);
+ if (re == NULL) {
+ return NGX_ERROR;
+ }
+
+ re->regex = regex->regex;
+ re->name = regex->name.data;
+
+ return NGX_OK;
+}
+
+#endif
+
+
+static int ngx_libc_cdecl
+ngx_http_cmp_referer_wildcards(const void *one, const void *two)
+{
+ ngx_hash_key_t *first, *second;
+
+ first = (ngx_hash_key_t *) one;
+ second = (ngx_hash_key_t *) two;
+
+ return ngx_dns_strcmp(first->key.data, second->key.data);
+}
diff --git a/app/nginx/src/http/modules/ngx_http_rewrite_module.c b/app/nginx/src/http/modules/ngx_http_rewrite_module.c
new file mode 100644
index 0000000..a6f1fc8
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_rewrite_module.c
@@ -0,0 +1,1022 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_array_t *codes; /* uintptr_t */
+
+ ngx_uint_t stack_size;
+
+ ngx_flag_t log;
+ ngx_flag_t uninitialized_variable_warn;
+} ngx_http_rewrite_loc_conf_t;
+
+
+static void *ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_rewrite_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_rewrite_init(ngx_conf_t *cf);
+static char *ngx_http_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_rewrite_break(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_rewrite_if(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char * ngx_http_rewrite_if_condition(ngx_conf_t *cf,
+ ngx_http_rewrite_loc_conf_t *lcf);
+static char *ngx_http_rewrite_variable(ngx_conf_t *cf,
+ ngx_http_rewrite_loc_conf_t *lcf, ngx_str_t *value);
+static char *ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char * ngx_http_rewrite_value(ngx_conf_t *cf,
+ ngx_http_rewrite_loc_conf_t *lcf, ngx_str_t *value);
+
+
+static ngx_command_t ngx_http_rewrite_commands[] = {
+
+ { ngx_string("rewrite"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_TAKE23,
+ ngx_http_rewrite,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("return"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_TAKE12,
+ ngx_http_rewrite_return,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("break"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_NOARGS,
+ ngx_http_rewrite_break,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("if"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_1MORE,
+ ngx_http_rewrite_if,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("set"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_TAKE2,
+ ngx_http_rewrite_set,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("rewrite_log"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF
+ |NGX_HTTP_LIF_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_rewrite_loc_conf_t, log),
+ NULL },
+
+ { ngx_string("uninitialized_variable_warn"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF
+ |NGX_HTTP_LIF_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_rewrite_loc_conf_t, uninitialized_variable_warn),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_rewrite_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_rewrite_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_rewrite_create_loc_conf, /* create location configuration */
+ ngx_http_rewrite_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_rewrite_module = {
+ NGX_MODULE_V1,
+ &ngx_http_rewrite_module_ctx, /* module context */
+ ngx_http_rewrite_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_rewrite_handler(ngx_http_request_t *r)
+{
+ ngx_int_t index;
+ ngx_http_script_code_pt code;
+ ngx_http_script_engine_t *e;
+ ngx_http_core_srv_conf_t *cscf;
+ ngx_http_core_main_conf_t *cmcf;
+ ngx_http_rewrite_loc_conf_t *rlcf;
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+ index = cmcf->phase_engine.location_rewrite_index;
+
+ if (r->phase_handler == index && r->loc_conf == cscf->ctx->loc_conf) {
+ /* skipping location rewrite phase for server null location */
+ return NGX_DECLINED;
+ }
+
+ rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module);
+
+ if (rlcf->codes == NULL) {
+ return NGX_DECLINED;
+ }
+
+ e = ngx_pcalloc(r->pool, sizeof(ngx_http_script_engine_t));
+ if (e == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ e->sp = ngx_pcalloc(r->pool,
+ rlcf->stack_size * sizeof(ngx_http_variable_value_t));
+ if (e->sp == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ e->ip = rlcf->codes->elts;
+ e->request = r;
+ e->quote = 1;
+ e->log = rlcf->log;
+ e->status = NGX_DECLINED;
+
+ while (*(uintptr_t *) e->ip) {
+ code = *(ngx_http_script_code_pt *) e->ip;
+ code(e);
+ }
+
+ if (e->status < NGX_HTTP_BAD_REQUEST) {
+ return e->status;
+ }
+
+ if (r->err_status == 0) {
+ return e->status;
+ }
+
+ return r->err_status;
+}
+
+
+static ngx_int_t
+ngx_http_rewrite_var(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_http_variable_t *var;
+ ngx_http_core_main_conf_t *cmcf;
+ ngx_http_rewrite_loc_conf_t *rlcf;
+
+ rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module);
+
+ if (rlcf->uninitialized_variable_warn == 0) {
+ *v = ngx_http_variable_null_value;
+ return NGX_OK;
+ }
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ var = cmcf->variables.elts;
+
+ /*
+ * the ngx_http_rewrite_module sets variables directly in r->variables,
+ * and they should be handled by ngx_http_get_indexed_variable(),
+ * so the handler is called only if the variable is not initialized
+ */
+
+ ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+ "using uninitialized \"%V\" variable", &var[data].name);
+
+ *v = ngx_http_variable_null_value;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_rewrite_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_rewrite_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->stack_size = NGX_CONF_UNSET_UINT;
+ conf->log = NGX_CONF_UNSET;
+ conf->uninitialized_variable_warn = NGX_CONF_UNSET;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_rewrite_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_rewrite_loc_conf_t *prev = parent;
+ ngx_http_rewrite_loc_conf_t *conf = child;
+
+ uintptr_t *code;
+
+ ngx_conf_merge_value(conf->log, prev->log, 0);
+ ngx_conf_merge_value(conf->uninitialized_variable_warn,
+ prev->uninitialized_variable_warn, 1);
+ ngx_conf_merge_uint_value(conf->stack_size, prev->stack_size, 10);
+
+ if (conf->codes == NULL) {
+ return NGX_CONF_OK;
+ }
+
+ if (conf->codes == prev->codes) {
+ return NGX_CONF_OK;
+ }
+
+ code = ngx_array_push_n(conf->codes, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_rewrite_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_SERVER_REWRITE_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_rewrite_handler;
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_rewrite_handler;
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_http_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_rewrite_loc_conf_t *lcf = conf;
+
+ ngx_str_t *value;
+ ngx_uint_t last;
+ ngx_regex_compile_t rc;
+ ngx_http_script_code_pt *code;
+ ngx_http_script_compile_t sc;
+ ngx_http_script_regex_code_t *regex;
+ ngx_http_script_regex_end_code_t *regex_end;
+ u_char errstr[NGX_MAX_CONF_ERRSTR];
+
+ regex = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(ngx_http_script_regex_code_t));
+ if (regex == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(regex, sizeof(ngx_http_script_regex_code_t));
+
+ value = cf->args->elts;
+
+ ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+ rc.pattern = value[1];
+ rc.err.len = NGX_MAX_CONF_ERRSTR;
+ rc.err.data = errstr;
+
+ /* TODO: NGX_REGEX_CASELESS */
+
+ regex->regex = ngx_http_regex_compile(cf, &rc);
+ if (regex->regex == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ regex->code = ngx_http_script_regex_start_code;
+ regex->uri = 1;
+ regex->name = value[1];
+
+ if (value[2].data[value[2].len - 1] == '?') {
+
+ /* the last "?" drops the original arguments */
+ value[2].len--;
+
+ } else {
+ regex->add_args = 1;
+ }
+
+ last = 0;
+
+ if (ngx_strncmp(value[2].data, "http://", sizeof("http://") - 1) == 0
+ || ngx_strncmp(value[2].data, "https://", sizeof("https://") - 1) == 0
+ || ngx_strncmp(value[2].data, "$scheme", sizeof("$scheme") - 1) == 0)
+ {
+ regex->status = NGX_HTTP_MOVED_TEMPORARILY;
+ regex->redirect = 1;
+ last = 1;
+ }
+
+ if (cf->args->nelts == 4) {
+ if (ngx_strcmp(value[3].data, "last") == 0) {
+ last = 1;
+
+ } else if (ngx_strcmp(value[3].data, "break") == 0) {
+ regex->break_cycle = 1;
+ last = 1;
+
+ } else if (ngx_strcmp(value[3].data, "redirect") == 0) {
+ regex->status = NGX_HTTP_MOVED_TEMPORARILY;
+ regex->redirect = 1;
+ last = 1;
+
+ } else if (ngx_strcmp(value[3].data, "permanent") == 0) {
+ regex->status = NGX_HTTP_MOVED_PERMANENTLY;
+ regex->redirect = 1;
+ last = 1;
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[3]);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = &value[2];
+ sc.lengths = &regex->lengths;
+ sc.values = &lcf->codes;
+ sc.variables = ngx_http_script_variables_count(&value[2]);
+ sc.main = regex;
+ sc.complete_lengths = 1;
+ sc.compile_args = !regex->redirect;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ regex = sc.main;
+
+ regex->size = sc.size;
+ regex->args = sc.args;
+
+ if (sc.variables == 0 && !sc.dup_capture) {
+ regex->lengths = NULL;
+ }
+
+ regex_end = ngx_http_script_add_code(lcf->codes,
+ sizeof(ngx_http_script_regex_end_code_t),
+ &regex);
+ if (regex_end == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ regex_end->code = ngx_http_script_regex_end_code;
+ regex_end->uri = regex->uri;
+ regex_end->args = regex->args;
+ regex_end->add_args = regex->add_args;
+ regex_end->redirect = regex->redirect;
+
+ if (last) {
+ code = ngx_http_script_add_code(lcf->codes, sizeof(uintptr_t), &regex);
+ if (code == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *code = NULL;
+ }
+
+ regex->next = (u_char *) lcf->codes->elts + lcf->codes->nelts
+ - (u_char *) regex;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_rewrite_loc_conf_t *lcf = conf;
+
+ u_char *p;
+ ngx_str_t *value, *v;
+ ngx_http_script_return_code_t *ret;
+ ngx_http_compile_complex_value_t ccv;
+
+ ret = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(ngx_http_script_return_code_t));
+ if (ret == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ ngx_memzero(ret, sizeof(ngx_http_script_return_code_t));
+
+ ret->code = ngx_http_script_return_code;
+
+ p = value[1].data;
+
+ ret->status = ngx_atoi(p, value[1].len);
+
+ if (ret->status == (uintptr_t) NGX_ERROR) {
+
+ if (cf->args->nelts == 2
+ && (ngx_strncmp(p, "http://", sizeof("http://") - 1) == 0
+ || ngx_strncmp(p, "https://", sizeof("https://") - 1) == 0
+ || ngx_strncmp(p, "$scheme", sizeof("$scheme") - 1) == 0))
+ {
+ ret->status = NGX_HTTP_MOVED_TEMPORARILY;
+ v = &value[1];
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid return code \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+
+ if (ret->status > 999) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid return code \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (cf->args->nelts == 2) {
+ return NGX_CONF_OK;
+ }
+
+ v = &value[2];
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = v;
+ ccv.complex_value = &ret->text;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_rewrite_break(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_rewrite_loc_conf_t *lcf = conf;
+
+ ngx_http_script_code_pt *code;
+
+ code = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *code = ngx_http_script_break_code;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_rewrite_if(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_rewrite_loc_conf_t *lcf = conf;
+
+ void *mconf;
+ char *rv;
+ u_char *elts;
+ ngx_uint_t i;
+ ngx_conf_t save;
+ ngx_http_module_t *module;
+ ngx_http_conf_ctx_t *ctx, *pctx;
+ ngx_http_core_loc_conf_t *clcf, *pclcf;
+ ngx_http_script_if_code_t *if_code;
+ ngx_http_rewrite_loc_conf_t *nlcf;
+
+ ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
+ if (ctx == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pctx = cf->ctx;
+ ctx->main_conf = pctx->main_conf;
+ ctx->srv_conf = pctx->srv_conf;
+
+ ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+ if (ctx->loc_conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 0; cf->cycle->modules[i]; i++) {
+ if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) {
+ continue;
+ }
+
+ module = cf->cycle->modules[i]->ctx;
+
+ if (module->create_loc_conf) {
+
+ mconf = module->create_loc_conf(cf);
+ if (mconf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ctx->loc_conf[cf->cycle->modules[i]->ctx_index] = mconf;
+ }
+ }
+
+ pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index];
+
+ clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
+ clcf->loc_conf = ctx->loc_conf;
+ clcf->name = pclcf->name;
+ clcf->noname = 1;
+
+ if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_http_rewrite_if_condition(cf, lcf) != NGX_CONF_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if_code = ngx_array_push_n(lcf->codes, sizeof(ngx_http_script_if_code_t));
+ if (if_code == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if_code->code = ngx_http_script_if_code;
+
+ elts = lcf->codes->elts;
+
+
+ /* the inner directives must be compiled to the same code array */
+
+ nlcf = ctx->loc_conf[ngx_http_rewrite_module.ctx_index];
+ nlcf->codes = lcf->codes;
+
+
+ save = *cf;
+ cf->ctx = ctx;
+
+ if (cf->cmd_type == NGX_HTTP_SRV_CONF) {
+ if_code->loc_conf = NULL;
+ cf->cmd_type = NGX_HTTP_SIF_CONF;
+
+ } else {
+ if_code->loc_conf = ctx->loc_conf;
+ cf->cmd_type = NGX_HTTP_LIF_CONF;
+ }
+
+ rv = ngx_conf_parse(cf, NULL);
+
+ *cf = save;
+
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+
+
+ if (elts != lcf->codes->elts) {
+ if_code = (ngx_http_script_if_code_t *)
+ ((u_char *) if_code + ((u_char *) lcf->codes->elts - elts));
+ }
+
+ if_code->next = (u_char *) lcf->codes->elts + lcf->codes->nelts
+ - (u_char *) if_code;
+
+ /* the code array belong to parent block */
+
+ nlcf->codes = NULL;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_rewrite_if_condition(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf)
+{
+ u_char *p;
+ size_t len;
+ ngx_str_t *value;
+ ngx_uint_t cur, last;
+ ngx_regex_compile_t rc;
+ ngx_http_script_code_pt *code;
+ ngx_http_script_file_code_t *fop;
+ ngx_http_script_regex_code_t *regex;
+ u_char errstr[NGX_MAX_CONF_ERRSTR];
+
+ value = cf->args->elts;
+ last = cf->args->nelts - 1;
+
+ if (value[1].len < 1 || value[1].data[0] != '(') {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid condition \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (value[1].len == 1) {
+ cur = 2;
+
+ } else {
+ cur = 1;
+ value[1].len--;
+ value[1].data++;
+ }
+
+ if (value[last].len < 1 || value[last].data[value[last].len - 1] != ')') {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid condition \"%V\"", &value[last]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (value[last].len == 1) {
+ last--;
+
+ } else {
+ value[last].len--;
+ value[last].data[value[last].len] = '\0';
+ }
+
+ len = value[cur].len;
+ p = value[cur].data;
+
+ if (len > 1 && p[0] == '$') {
+
+ if (cur != last && cur + 2 != last) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid condition \"%V\"", &value[cur]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_http_rewrite_variable(cf, lcf, &value[cur]) != NGX_CONF_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cur == last) {
+ return NGX_CONF_OK;
+ }
+
+ cur++;
+
+ len = value[cur].len;
+ p = value[cur].data;
+
+ if (len == 1 && p[0] == '=') {
+
+ if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ code = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *code = ngx_http_script_equal_code;
+
+ return NGX_CONF_OK;
+ }
+
+ if (len == 2 && p[0] == '!' && p[1] == '=') {
+
+ if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ code = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *code = ngx_http_script_not_equal_code;
+ return NGX_CONF_OK;
+ }
+
+ if ((len == 1 && p[0] == '~')
+ || (len == 2 && p[0] == '~' && p[1] == '*')
+ || (len == 2 && p[0] == '!' && p[1] == '~')
+ || (len == 3 && p[0] == '!' && p[1] == '~' && p[2] == '*'))
+ {
+ regex = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(ngx_http_script_regex_code_t));
+ if (regex == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(regex, sizeof(ngx_http_script_regex_code_t));
+
+ ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
+
+ rc.pattern = value[last];
+ rc.options = (p[len - 1] == '*') ? NGX_REGEX_CASELESS : 0;
+ rc.err.len = NGX_MAX_CONF_ERRSTR;
+ rc.err.data = errstr;
+
+ regex->regex = ngx_http_regex_compile(cf, &rc);
+ if (regex->regex == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ regex->code = ngx_http_script_regex_start_code;
+ regex->next = sizeof(ngx_http_script_regex_code_t);
+ regex->test = 1;
+ if (p[0] == '!') {
+ regex->negative_test = 1;
+ }
+ regex->name = value[last];
+
+ return NGX_CONF_OK;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "unexpected \"%V\" in condition", &value[cur]);
+ return NGX_CONF_ERROR;
+
+ } else if ((len == 2 && p[0] == '-')
+ || (len == 3 && p[0] == '!' && p[1] == '-'))
+ {
+ if (cur + 1 != last) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid condition \"%V\"", &value[cur]);
+ return NGX_CONF_ERROR;
+ }
+
+ value[last].data[value[last].len] = '\0';
+ value[last].len++;
+
+ if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ fop = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(ngx_http_script_file_code_t));
+ if (fop == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ fop->code = ngx_http_script_file_code;
+
+ if (p[1] == 'f') {
+ fop->op = ngx_http_script_file_plain;
+ return NGX_CONF_OK;
+ }
+
+ if (p[1] == 'd') {
+ fop->op = ngx_http_script_file_dir;
+ return NGX_CONF_OK;
+ }
+
+ if (p[1] == 'e') {
+ fop->op = ngx_http_script_file_exists;
+ return NGX_CONF_OK;
+ }
+
+ if (p[1] == 'x') {
+ fop->op = ngx_http_script_file_exec;
+ return NGX_CONF_OK;
+ }
+
+ if (p[0] == '!') {
+ if (p[2] == 'f') {
+ fop->op = ngx_http_script_file_not_plain;
+ return NGX_CONF_OK;
+ }
+
+ if (p[2] == 'd') {
+ fop->op = ngx_http_script_file_not_dir;
+ return NGX_CONF_OK;
+ }
+
+ if (p[2] == 'e') {
+ fop->op = ngx_http_script_file_not_exists;
+ return NGX_CONF_OK;
+ }
+
+ if (p[2] == 'x') {
+ fop->op = ngx_http_script_file_not_exec;
+ return NGX_CONF_OK;
+ }
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid condition \"%V\"", &value[cur]);
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid condition \"%V\"", &value[cur]);
+
+ return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_http_rewrite_variable(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf,
+ ngx_str_t *value)
+{
+ ngx_int_t index;
+ ngx_http_script_var_code_t *var_code;
+
+ value->len--;
+ value->data++;
+
+ index = ngx_http_get_variable_index(cf, value);
+
+ if (index == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ var_code = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(ngx_http_script_var_code_t));
+ if (var_code == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ var_code->code = ngx_http_script_var_code;
+ var_code->index = index;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_rewrite_loc_conf_t *lcf = conf;
+
+ ngx_int_t index;
+ ngx_str_t *value;
+ ngx_http_variable_t *v;
+ ngx_http_script_var_code_t *vcode;
+ ngx_http_script_var_handler_code_t *vhcode;
+
+ value = cf->args->elts;
+
+ if (value[1].data[0] != '$') {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid variable name \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ value[1].len--;
+ value[1].data++;
+
+ v = ngx_http_add_variable(cf, &value[1],
+ NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_WEAK);
+ if (v == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ index = ngx_http_get_variable_index(cf, &value[1]);
+ if (index == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (v->get_handler == NULL) {
+ v->get_handler = ngx_http_rewrite_var;
+ v->data = index;
+ }
+
+ if (ngx_http_rewrite_value(cf, lcf, &value[2]) != NGX_CONF_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (v->set_handler) {
+ vhcode = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(ngx_http_script_var_handler_code_t));
+ if (vhcode == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ vhcode->code = ngx_http_script_var_set_handler_code;
+ vhcode->handler = v->set_handler;
+ vhcode->data = v->data;
+
+ return NGX_CONF_OK;
+ }
+
+ vcode = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(ngx_http_script_var_code_t));
+ if (vcode == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ vcode->code = ngx_http_script_set_var_code;
+ vcode->index = (uintptr_t) index;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_rewrite_value(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf,
+ ngx_str_t *value)
+{
+ ngx_int_t n;
+ ngx_http_script_compile_t sc;
+ ngx_http_script_value_code_t *val;
+ ngx_http_script_complex_value_code_t *complex;
+
+ n = ngx_http_script_variables_count(value);
+
+ if (n == 0) {
+ val = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(ngx_http_script_value_code_t));
+ if (val == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ n = ngx_atoi(value->data, value->len);
+
+ if (n == NGX_ERROR) {
+ n = 0;
+ }
+
+ val->code = ngx_http_script_value_code;
+ val->value = (uintptr_t) n;
+ val->text_len = (uintptr_t) value->len;
+ val->text_data = (uintptr_t) value->data;
+
+ return NGX_CONF_OK;
+ }
+
+ complex = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(ngx_http_script_complex_value_code_t));
+ if (complex == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ complex->code = ngx_http_script_complex_value_code;
+ complex->lengths = NULL;
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = value;
+ sc.lengths = &complex->lengths;
+ sc.values = &lcf->codes;
+ sc.variables = n;
+ sc.complete_lengths = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_scgi_module.c b/app/nginx/src/http/modules/ngx_http_scgi_module.c
new file mode 100644
index 0000000..d1e37dd
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_scgi_module.c
@@ -0,0 +1,2005 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Manlio Perillo (manlio.perillo@gmail.com)
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_array_t caches; /* ngx_http_file_cache_t * */
+} ngx_http_scgi_main_conf_t;
+
+
+typedef struct {
+ ngx_array_t *flushes;
+ ngx_array_t *lengths;
+ ngx_array_t *values;
+ ngx_uint_t number;
+ ngx_hash_t hash;
+} ngx_http_scgi_params_t;
+
+
+typedef struct {
+ ngx_http_upstream_conf_t upstream;
+
+ ngx_http_scgi_params_t params;
+#if (NGX_HTTP_CACHE)
+ ngx_http_scgi_params_t params_cache;
+#endif
+ ngx_array_t *params_source;
+
+ ngx_array_t *scgi_lengths;
+ ngx_array_t *scgi_values;
+
+#if (NGX_HTTP_CACHE)
+ ngx_http_complex_value_t cache_key;
+#endif
+} ngx_http_scgi_loc_conf_t;
+
+
+static ngx_int_t ngx_http_scgi_eval(ngx_http_request_t *r,
+ ngx_http_scgi_loc_conf_t *scf);
+static ngx_int_t ngx_http_scgi_create_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_scgi_reinit_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_scgi_process_status_line(ngx_http_request_t *r);
+static ngx_int_t ngx_http_scgi_process_header(ngx_http_request_t *r);
+static void ngx_http_scgi_abort_request(ngx_http_request_t *r);
+static void ngx_http_scgi_finalize_request(ngx_http_request_t *r, ngx_int_t rc);
+
+static void *ngx_http_scgi_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_scgi_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_scgi_merge_loc_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static ngx_int_t ngx_http_scgi_init_params(ngx_conf_t *cf,
+ ngx_http_scgi_loc_conf_t *conf, ngx_http_scgi_params_t *params,
+ ngx_keyval_t *default_params);
+
+static char *ngx_http_scgi_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_scgi_store(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+#if (NGX_HTTP_CACHE)
+static ngx_int_t ngx_http_scgi_create_key(ngx_http_request_t *r);
+static char *ngx_http_scgi_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_scgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+#endif
+
+
+static ngx_conf_bitmask_t ngx_http_scgi_next_upstream_masks[] = {
+ { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
+ { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT },
+ { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER },
+ { ngx_string("non_idempotent"), NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT },
+ { ngx_string("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 },
+ { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 },
+ { ngx_string("http_403"), NGX_HTTP_UPSTREAM_FT_HTTP_403 },
+ { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 },
+ { ngx_string("http_429"), NGX_HTTP_UPSTREAM_FT_HTTP_429 },
+ { ngx_string("updating"), NGX_HTTP_UPSTREAM_FT_UPDATING },
+ { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF },
+ { ngx_null_string, 0 }
+};
+
+
+ngx_module_t ngx_http_scgi_module;
+
+
+static ngx_command_t ngx_http_scgi_commands[] = {
+
+ { ngx_string("scgi_pass"),
+ NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
+ ngx_http_scgi_pass,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("scgi_store"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_scgi_store,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("scgi_store_access"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
+ ngx_conf_set_access_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.store_access),
+ NULL },
+
+ { ngx_string("scgi_buffering"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.buffering),
+ NULL },
+
+ { ngx_string("scgi_request_buffering"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.request_buffering),
+ NULL },
+
+ { ngx_string("scgi_ignore_client_abort"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.ignore_client_abort),
+ NULL },
+
+ { ngx_string("scgi_bind"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+ ngx_http_upstream_bind_set_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.local),
+ NULL },
+
+ { ngx_string("scgi_connect_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.connect_timeout),
+ NULL },
+
+ { ngx_string("scgi_send_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.send_timeout),
+ NULL },
+
+ { ngx_string("scgi_buffer_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.buffer_size),
+ NULL },
+
+ { ngx_string("scgi_pass_request_headers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.pass_request_headers),
+ NULL },
+
+ { ngx_string("scgi_pass_request_body"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.pass_request_body),
+ NULL },
+
+ { ngx_string("scgi_intercept_errors"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.intercept_errors),
+ NULL },
+
+ { ngx_string("scgi_read_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.read_timeout),
+ NULL },
+
+ { ngx_string("scgi_buffers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_conf_set_bufs_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.bufs),
+ NULL },
+
+ { ngx_string("scgi_busy_buffers_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.busy_buffers_size_conf),
+ NULL },
+
+ { ngx_string("scgi_force_ranges"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.force_ranges),
+ NULL },
+
+ { ngx_string("scgi_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_scgi_loc_conf_t, upstream.limit_rate),
+ NULL },
+
+#if (NGX_HTTP_CACHE)
+
+ { ngx_string("scgi_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_scgi_cache,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("scgi_cache_key"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_scgi_cache_key,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("scgi_cache_path"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE,
+ ngx_http_file_cache_set_slot,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ offsetof(ngx_http_scgi_main_conf_t, caches),
+ &ngx_http_scgi_module },
+
+ { ngx_string("scgi_cache_bypass"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_set_predicate_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_bypass),
+ NULL },
+
+ { ngx_string("scgi_no_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_set_predicate_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.no_cache),
+ NULL },
+
+ { ngx_string("scgi_cache_valid"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_file_cache_valid_set_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_valid),
+ NULL },
+
+ { ngx_string("scgi_cache_min_uses"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_min_uses),
+ NULL },
+
+ { ngx_string("scgi_cache_max_range_offset"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_off_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_max_range_offset),
+ NULL },
+
+ { ngx_string("scgi_cache_use_stale"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_use_stale),
+ &ngx_http_scgi_next_upstream_masks },
+
+ { ngx_string("scgi_cache_methods"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_methods),
+ &ngx_http_upstream_cache_method_mask },
+
+ { ngx_string("scgi_cache_lock"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_lock),
+ NULL },
+
+ { ngx_string("scgi_cache_lock_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_lock_timeout),
+ NULL },
+
+ { ngx_string("scgi_cache_lock_age"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_lock_age),
+ NULL },
+
+ { ngx_string("scgi_cache_revalidate"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_revalidate),
+ NULL },
+
+ { ngx_string("scgi_cache_background_update"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.cache_background_update),
+ NULL },
+
+#endif
+
+ { ngx_string("scgi_temp_path"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
+ ngx_conf_set_path_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.temp_path),
+ NULL },
+
+ { ngx_string("scgi_max_temp_file_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.max_temp_file_size_conf),
+ NULL },
+
+ { ngx_string("scgi_temp_file_write_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.temp_file_write_size_conf),
+ NULL },
+
+ { ngx_string("scgi_next_upstream"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.next_upstream),
+ &ngx_http_scgi_next_upstream_masks },
+
+ { ngx_string("scgi_next_upstream_tries"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.next_upstream_tries),
+ NULL },
+
+ { ngx_string("scgi_next_upstream_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.next_upstream_timeout),
+ NULL },
+
+ { ngx_string("scgi_param"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE23,
+ ngx_http_upstream_param_set_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, params_source),
+ NULL },
+
+ { ngx_string("scgi_pass_header"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.pass_headers),
+ NULL },
+
+ { ngx_string("scgi_hide_header"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.hide_headers),
+ NULL },
+
+ { ngx_string("scgi_ignore_headers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_scgi_loc_conf_t, upstream.ignore_headers),
+ &ngx_http_upstream_ignore_headers_masks },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_scgi_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ ngx_http_scgi_create_main_conf, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_scgi_create_loc_conf, /* create location configuration */
+ ngx_http_scgi_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_scgi_module = {
+ NGX_MODULE_V1,
+ &ngx_http_scgi_module_ctx, /* module context */
+ ngx_http_scgi_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_http_scgi_hide_headers[] = {
+ ngx_string("Status"),
+ ngx_string("X-Accel-Expires"),
+ ngx_string("X-Accel-Redirect"),
+ ngx_string("X-Accel-Limit-Rate"),
+ ngx_string("X-Accel-Buffering"),
+ ngx_string("X-Accel-Charset"),
+ ngx_null_string
+};
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_keyval_t ngx_http_scgi_cache_headers[] = {
+ { ngx_string("HTTP_IF_MODIFIED_SINCE"),
+ ngx_string("$upstream_cache_last_modified") },
+ { ngx_string("HTTP_IF_UNMODIFIED_SINCE"), ngx_string("") },
+ { ngx_string("HTTP_IF_NONE_MATCH"), ngx_string("$upstream_cache_etag") },
+ { ngx_string("HTTP_IF_MATCH"), ngx_string("") },
+ { ngx_string("HTTP_RANGE"), ngx_string("") },
+ { ngx_string("HTTP_IF_RANGE"), ngx_string("") },
+ { ngx_null_string, ngx_null_string }
+};
+
+#endif
+
+
+static ngx_path_init_t ngx_http_scgi_temp_path = {
+ ngx_string(NGX_HTTP_SCGI_TEMP_PATH), { 1, 2, 0 }
+};
+
+
+static ngx_int_t
+ngx_http_scgi_handler(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_http_status_t *status;
+ ngx_http_upstream_t *u;
+ ngx_http_scgi_loc_conf_t *scf;
+#if (NGX_HTTP_CACHE)
+ ngx_http_scgi_main_conf_t *smcf;
+#endif
+
+ if (ngx_http_upstream_create(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ status = ngx_pcalloc(r->pool, sizeof(ngx_http_status_t));
+ if (status == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_http_set_ctx(r, status, ngx_http_scgi_module);
+
+ scf = ngx_http_get_module_loc_conf(r, ngx_http_scgi_module);
+
+ if (scf->scgi_lengths) {
+ if (ngx_http_scgi_eval(r, scf) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ u = r->upstream;
+
+ ngx_str_set(&u->schema, "scgi://");
+ u->output.tag = (ngx_buf_tag_t) &ngx_http_scgi_module;
+
+ u->conf = &scf->upstream;
+
+#if (NGX_HTTP_CACHE)
+ smcf = ngx_http_get_module_main_conf(r, ngx_http_scgi_module);
+
+ u->caches = &smcf->caches;
+ u->create_key = ngx_http_scgi_create_key;
+#endif
+
+ u->create_request = ngx_http_scgi_create_request;
+ u->reinit_request = ngx_http_scgi_reinit_request;
+ u->process_header = ngx_http_scgi_process_status_line;
+ u->abort_request = ngx_http_scgi_abort_request;
+ u->finalize_request = ngx_http_scgi_finalize_request;
+ r->state = 0;
+
+ u->buffering = scf->upstream.buffering;
+
+ u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t));
+ if (u->pipe == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ u->pipe->input_filter = ngx_event_pipe_copy_input_filter;
+ u->pipe->input_ctx = r;
+
+ if (!scf->upstream.request_buffering
+ && scf->upstream.pass_request_body
+ && !r->headers_in.chunked)
+ {
+ r->request_body_no_buffering = 1;
+ }
+
+ rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);
+
+ if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+ return rc;
+ }
+
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_scgi_eval(ngx_http_request_t *r, ngx_http_scgi_loc_conf_t * scf)
+{
+ ngx_url_t url;
+ ngx_http_upstream_t *u;
+
+ ngx_memzero(&url, sizeof(ngx_url_t));
+
+ if (ngx_http_script_run(r, &url.url, scf->scgi_lengths->elts, 0,
+ scf->scgi_values->elts)
+ == NULL)
+ {
+ return NGX_ERROR;
+ }
+
+ url.no_resolve = 1;
+
+ if (ngx_parse_url(r->pool, &url) != NGX_OK) {
+ if (url.err) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "%s in upstream \"%V\"", url.err, &url.url);
+ }
+
+ return NGX_ERROR;
+ }
+
+ u = r->upstream;
+
+ u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t));
+ if (u->resolved == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (url.addrs) {
+ u->resolved->sockaddr = url.addrs[0].sockaddr;
+ u->resolved->socklen = url.addrs[0].socklen;
+ u->resolved->name = url.addrs[0].name;
+ u->resolved->naddrs = 1;
+ }
+
+ u->resolved->host = url.host;
+ u->resolved->port = url.port;
+ u->resolved->no_port = url.no_port;
+
+ return NGX_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_int_t
+ngx_http_scgi_create_key(ngx_http_request_t *r)
+{
+ ngx_str_t *key;
+ ngx_http_scgi_loc_conf_t *scf;
+
+ key = ngx_array_push(&r->cache->keys);
+ if (key == NULL) {
+ return NGX_ERROR;
+ }
+
+ scf = ngx_http_get_module_loc_conf(r, ngx_http_scgi_module);
+
+ if (ngx_http_complex_value(r, &scf->cache_key, key) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_scgi_create_request(ngx_http_request_t *r)
+{
+ off_t content_length_n;
+ u_char ch, *key, *val, *lowcase_key;
+ size_t len, key_len, val_len, allocated;
+ ngx_buf_t *b;
+ ngx_str_t content_length;
+ ngx_uint_t i, n, hash, skip_empty, header_params;
+ ngx_chain_t *cl, *body;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header, **ignored;
+ ngx_http_scgi_params_t *params;
+ ngx_http_script_code_pt code;
+ ngx_http_script_engine_t e, le;
+ ngx_http_scgi_loc_conf_t *scf;
+ ngx_http_script_len_code_pt lcode;
+ u_char buffer[NGX_OFF_T_LEN];
+
+ content_length_n = 0;
+ body = r->upstream->request_bufs;
+
+ while (body) {
+ content_length_n += ngx_buf_size(body->buf);
+ body = body->next;
+ }
+
+ content_length.data = buffer;
+ content_length.len = ngx_sprintf(buffer, "%O", content_length_n) - buffer;
+
+ len = sizeof("CONTENT_LENGTH") + content_length.len + 1;
+
+ header_params = 0;
+ ignored = NULL;
+
+ scf = ngx_http_get_module_loc_conf(r, ngx_http_scgi_module);
+
+#if (NGX_HTTP_CACHE)
+ params = r->upstream->cacheable ? &scf->params_cache : &scf->params;
+#else
+ params = &scf->params;
+#endif
+
+ if (params->lengths) {
+ ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
+
+ ngx_http_script_flush_no_cacheable_variables(r, params->flushes);
+ le.flushed = 1;
+
+ le.ip = params->lengths->elts;
+ le.request = r;
+
+ while (*(uintptr_t *) le.ip) {
+
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ key_len = lcode(&le);
+
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ skip_empty = lcode(&le);
+
+ for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) {
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ }
+ le.ip += sizeof(uintptr_t);
+
+ if (skip_empty && val_len == 0) {
+ continue;
+ }
+
+ len += key_len + val_len + 1;
+ }
+ }
+
+ if (scf->upstream.pass_request_headers) {
+
+ allocated = 0;
+ lowcase_key = NULL;
+
+ if (params->number) {
+ n = 0;
+ part = &r->headers_in.headers.part;
+
+ while (part) {
+ n += part->nelts;
+ part = part->next;
+ }
+
+ ignored = ngx_palloc(r->pool, n * sizeof(void *));
+ if (ignored == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ part = &r->headers_in.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (params->number) {
+ if (allocated < header[i].key.len) {
+ allocated = header[i].key.len + 16;
+ lowcase_key = ngx_pnalloc(r->pool, allocated);
+ if (lowcase_key == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ hash = 0;
+
+ for (n = 0; n < header[i].key.len; n++) {
+ ch = header[i].key.data[n];
+
+ if (ch >= 'A' && ch <= 'Z') {
+ ch |= 0x20;
+
+ } else if (ch == '-') {
+ ch = '_';
+ }
+
+ hash = ngx_hash(hash, ch);
+ lowcase_key[n] = ch;
+ }
+
+ if (ngx_hash_find(&params->hash, hash, lowcase_key, n)) {
+ ignored[header_params++] = &header[i];
+ continue;
+ }
+ }
+
+ len += sizeof("HTTP_") - 1 + header[i].key.len + 1
+ + header[i].value.len + 1;
+ }
+ }
+
+ /* netstring: "length:" + packet + "," */
+
+ b = ngx_create_temp_buf(r->pool, NGX_SIZE_T_LEN + 1 + len + 1);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+
+ b->last = ngx_sprintf(b->last, "%ui:CONTENT_LENGTH%Z%V%Z",
+ len, &content_length);
+
+ if (params->lengths) {
+ ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+ e.ip = params->values->elts;
+ e.pos = b->last;
+ e.request = r;
+ e.flushed = 1;
+
+ le.ip = params->lengths->elts;
+
+ while (*(uintptr_t *) le.ip) {
+
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ lcode(&le); /* key length */
+
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ skip_empty = lcode(&le);
+
+ for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) {
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ }
+ le.ip += sizeof(uintptr_t);
+
+ if (skip_empty && val_len == 0) {
+ e.skip = 1;
+
+ while (*(uintptr_t *) e.ip) {
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code((ngx_http_script_engine_t *) &e);
+ }
+ e.ip += sizeof(uintptr_t);
+
+ e.skip = 0;
+
+ continue;
+ }
+
+#if (NGX_DEBUG)
+ key = e.pos;
+#endif
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code((ngx_http_script_engine_t *) & e);
+
+#if (NGX_DEBUG)
+ val = e.pos;
+#endif
+ while (*(uintptr_t *) e.ip) {
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code((ngx_http_script_engine_t *) &e);
+ }
+ *e.pos++ = '\0';
+ e.ip += sizeof(uintptr_t);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "scgi param: \"%s: %s\"", key, val);
+ }
+
+ b->last = e.pos;
+ }
+
+ if (scf->upstream.pass_request_headers) {
+
+ part = &r->headers_in.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ for (n = 0; n < header_params; n++) {
+ if (&header[i] == ignored[n]) {
+ goto next;
+ }
+ }
+
+ key = b->last;
+ b->last = ngx_cpymem(key, "HTTP_", sizeof("HTTP_") - 1);
+
+ for (n = 0; n < header[i].key.len; n++) {
+ ch = header[i].key.data[n];
+
+ if (ch >= 'a' && ch <= 'z') {
+ ch &= ~0x20;
+
+ } else if (ch == '-') {
+ ch = '_';
+ }
+
+ *b->last++ = ch;
+ }
+
+ *b->last++ = (u_char) 0;
+
+ val = b->last;
+ b->last = ngx_copy(val, header[i].value.data, header[i].value.len);
+ *b->last++ = (u_char) 0;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "scgi param: \"%s: %s\"", key, val);
+
+ next:
+
+ continue;
+ }
+ }
+
+ *b->last++ = (u_char) ',';
+
+ if (r->request_body_no_buffering) {
+ r->upstream->request_bufs = cl;
+
+ } else if (scf->upstream.pass_request_body) {
+ body = r->upstream->request_bufs;
+ r->upstream->request_bufs = cl;
+
+ while (body) {
+ b = ngx_alloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(b, body->buf, sizeof(ngx_buf_t));
+
+ cl->next = ngx_alloc_chain_link(r->pool);
+ if (cl->next == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = cl->next;
+ cl->buf = b;
+
+ body = body->next;
+ }
+
+ } else {
+ r->upstream->request_bufs = cl;
+ }
+
+ cl->next = NULL;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_scgi_reinit_request(ngx_http_request_t *r)
+{
+ ngx_http_status_t *status;
+
+ status = ngx_http_get_module_ctx(r, ngx_http_scgi_module);
+
+ if (status == NULL) {
+ return NGX_OK;
+ }
+
+ status->code = 0;
+ status->count = 0;
+ status->start = NULL;
+ status->end = NULL;
+
+ r->upstream->process_header = ngx_http_scgi_process_status_line;
+ r->state = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_scgi_process_status_line(ngx_http_request_t *r)
+{
+ size_t len;
+ ngx_int_t rc;
+ ngx_http_status_t *status;
+ ngx_http_upstream_t *u;
+
+ status = ngx_http_get_module_ctx(r, ngx_http_scgi_module);
+
+ if (status == NULL) {
+ return NGX_ERROR;
+ }
+
+ u = r->upstream;
+
+ rc = ngx_http_parse_status_line(r, &u->buffer, status);
+
+ if (rc == NGX_AGAIN) {
+ return rc;
+ }
+
+ if (rc == NGX_ERROR) {
+ u->process_header = ngx_http_scgi_process_header;
+ return ngx_http_scgi_process_header(r);
+ }
+
+ if (u->state && u->state->status == 0) {
+ u->state->status = status->code;
+ }
+
+ u->headers_in.status_n = status->code;
+
+ len = status->end - status->start;
+ u->headers_in.status_line.len = len;
+
+ u->headers_in.status_line.data = ngx_pnalloc(r->pool, len);
+ if (u->headers_in.status_line.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(u->headers_in.status_line.data, status->start, len);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http scgi status %ui \"%V\"",
+ u->headers_in.status_n, &u->headers_in.status_line);
+
+ u->process_header = ngx_http_scgi_process_header;
+
+ return ngx_http_scgi_process_header(r);
+}
+
+
+static ngx_int_t
+ngx_http_scgi_process_header(ngx_http_request_t *r)
+{
+ ngx_str_t *status_line;
+ ngx_int_t rc, status;
+ ngx_table_elt_t *h;
+ ngx_http_upstream_t *u;
+ ngx_http_upstream_header_t *hh;
+ ngx_http_upstream_main_conf_t *umcf;
+
+ umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
+
+ for ( ;; ) {
+
+ rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1);
+
+ if (rc == NGX_OK) {
+
+ /* a header line has been parsed successfully */
+
+ h = ngx_list_push(&r->upstream->headers_in.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->hash = r->header_hash;
+
+ h->key.len = r->header_name_end - r->header_name_start;
+ h->value.len = r->header_end - r->header_start;
+
+ h->key.data = ngx_pnalloc(r->pool,
+ h->key.len + 1 + h->value.len + 1
+ + h->key.len);
+ if (h->key.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->value.data = h->key.data + h->key.len + 1;
+ h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1;
+
+ ngx_memcpy(h->key.data, r->header_name_start, h->key.len);
+ h->key.data[h->key.len] = '\0';
+ ngx_memcpy(h->value.data, r->header_start, h->value.len);
+ h->value.data[h->value.len] = '\0';
+
+ if (h->key.len == r->lowcase_index) {
+ ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
+
+ } else {
+ ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
+ }
+
+ hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
+ h->lowcase_key, h->key.len);
+
+ if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http scgi header: \"%V: %V\"", &h->key, &h->value);
+
+ continue;
+ }
+
+ if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+ /* a whole header has been parsed successfully */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http scgi header done");
+
+ u = r->upstream;
+
+ if (u->headers_in.status_n) {
+ goto done;
+ }
+
+ if (u->headers_in.status) {
+ status_line = &u->headers_in.status->value;
+
+ status = ngx_atoi(status_line->data, 3);
+ if (status == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent invalid status \"%V\"",
+ status_line);
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+ }
+
+ u->headers_in.status_n = status;
+ u->headers_in.status_line = *status_line;
+
+ } else if (u->headers_in.location) {
+ u->headers_in.status_n = 302;
+ ngx_str_set(&u->headers_in.status_line,
+ "302 Moved Temporarily");
+
+ } else {
+ u->headers_in.status_n = 200;
+ ngx_str_set(&u->headers_in.status_line, "200 OK");
+ }
+
+ if (u->state && u->state->status == 0) {
+ u->state->status = u->headers_in.status_n;
+ }
+
+ done:
+
+ if (u->headers_in.status_n == NGX_HTTP_SWITCHING_PROTOCOLS
+ && r->headers_in.upgrade)
+ {
+ u->upgrade = 1;
+ }
+
+ return NGX_OK;
+ }
+
+ if (rc == NGX_AGAIN) {
+ return NGX_AGAIN;
+ }
+
+ /* there was error while a header line parsing */
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent invalid header");
+
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+ }
+}
+
+
+static void
+ngx_http_scgi_abort_request(ngx_http_request_t *r)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "abort http scgi request");
+
+ return;
+}
+
+
+static void
+ngx_http_scgi_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "finalize http scgi request");
+
+ return;
+}
+
+
+static void *
+ngx_http_scgi_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_scgi_main_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_scgi_main_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+#if (NGX_HTTP_CACHE)
+ if (ngx_array_init(&conf->caches, cf->pool, 4,
+ sizeof(ngx_http_file_cache_t *))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+#endif
+
+ return conf;
+}
+
+
+static void *
+ngx_http_scgi_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_scgi_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_scgi_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->upstream.store = NGX_CONF_UNSET;
+ conf->upstream.store_access = NGX_CONF_UNSET_UINT;
+ conf->upstream.next_upstream_tries = NGX_CONF_UNSET_UINT;
+ conf->upstream.buffering = NGX_CONF_UNSET;
+ conf->upstream.request_buffering = NGX_CONF_UNSET;
+ conf->upstream.ignore_client_abort = NGX_CONF_UNSET;
+ conf->upstream.force_ranges = NGX_CONF_UNSET;
+
+ conf->upstream.local = NGX_CONF_UNSET_PTR;
+
+ conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC;
+
+ conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE;
+ conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE;
+ conf->upstream.limit_rate = NGX_CONF_UNSET_SIZE;
+
+ conf->upstream.busy_buffers_size_conf = NGX_CONF_UNSET_SIZE;
+ conf->upstream.max_temp_file_size_conf = NGX_CONF_UNSET_SIZE;
+ conf->upstream.temp_file_write_size_conf = NGX_CONF_UNSET_SIZE;
+
+ conf->upstream.pass_request_headers = NGX_CONF_UNSET;
+ conf->upstream.pass_request_body = NGX_CONF_UNSET;
+
+#if (NGX_HTTP_CACHE)
+ conf->upstream.cache = NGX_CONF_UNSET;
+ conf->upstream.cache_min_uses = NGX_CONF_UNSET_UINT;
+ conf->upstream.cache_max_range_offset = NGX_CONF_UNSET;
+ conf->upstream.cache_bypass = NGX_CONF_UNSET_PTR;
+ conf->upstream.no_cache = NGX_CONF_UNSET_PTR;
+ conf->upstream.cache_valid = NGX_CONF_UNSET_PTR;
+ conf->upstream.cache_lock = NGX_CONF_UNSET;
+ conf->upstream.cache_lock_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.cache_lock_age = NGX_CONF_UNSET_MSEC;
+ conf->upstream.cache_revalidate = NGX_CONF_UNSET;
+ conf->upstream.cache_background_update = NGX_CONF_UNSET;
+#endif
+
+ conf->upstream.hide_headers = NGX_CONF_UNSET_PTR;
+ conf->upstream.pass_headers = NGX_CONF_UNSET_PTR;
+
+ conf->upstream.intercept_errors = NGX_CONF_UNSET;
+
+ /* "scgi_cyclic_temp_file" is disabled */
+ conf->upstream.cyclic_temp_file = 0;
+
+ conf->upstream.change_buffering = 1;
+
+ ngx_str_set(&conf->upstream.module, "scgi");
+
+ return conf;
+}
+
+
+static char *
+ngx_http_scgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_scgi_loc_conf_t *prev = parent;
+ ngx_http_scgi_loc_conf_t *conf = child;
+
+ size_t size;
+ ngx_int_t rc;
+ ngx_hash_init_t hash;
+ ngx_http_core_loc_conf_t *clcf;
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->upstream.store > 0) {
+ conf->upstream.cache = 0;
+ }
+
+ if (conf->upstream.cache > 0) {
+ conf->upstream.store = 0;
+ }
+
+#endif
+
+ if (conf->upstream.store == NGX_CONF_UNSET) {
+ ngx_conf_merge_value(conf->upstream.store, prev->upstream.store, 0);
+
+ conf->upstream.store_lengths = prev->upstream.store_lengths;
+ conf->upstream.store_values = prev->upstream.store_values;
+ }
+
+ ngx_conf_merge_uint_value(conf->upstream.store_access,
+ prev->upstream.store_access, 0600);
+
+ ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries,
+ prev->upstream.next_upstream_tries, 0);
+
+ ngx_conf_merge_value(conf->upstream.buffering,
+ prev->upstream.buffering, 1);
+
+ ngx_conf_merge_value(conf->upstream.request_buffering,
+ prev->upstream.request_buffering, 1);
+
+ ngx_conf_merge_value(conf->upstream.ignore_client_abort,
+ prev->upstream.ignore_client_abort, 0);
+
+ ngx_conf_merge_value(conf->upstream.force_ranges,
+ prev->upstream.force_ranges, 0);
+
+ ngx_conf_merge_ptr_value(conf->upstream.local,
+ prev->upstream.local, NULL);
+
+ ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
+ prev->upstream.connect_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->upstream.send_timeout,
+ prev->upstream.send_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->upstream.read_timeout,
+ prev->upstream.read_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->upstream.next_upstream_timeout,
+ prev->upstream.next_upstream_timeout, 0);
+
+ ngx_conf_merge_size_value(conf->upstream.send_lowat,
+ prev->upstream.send_lowat, 0);
+
+ ngx_conf_merge_size_value(conf->upstream.buffer_size,
+ prev->upstream.buffer_size,
+ (size_t) ngx_pagesize);
+
+ ngx_conf_merge_size_value(conf->upstream.limit_rate,
+ prev->upstream.limit_rate, 0);
+
+
+ ngx_conf_merge_bufs_value(conf->upstream.bufs, prev->upstream.bufs,
+ 8, ngx_pagesize);
+
+ if (conf->upstream.bufs.num < 2) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "there must be at least 2 \"scgi_buffers\"");
+ return NGX_CONF_ERROR;
+ }
+
+
+ size = conf->upstream.buffer_size;
+ if (size < conf->upstream.bufs.size) {
+ size = conf->upstream.bufs.size;
+ }
+
+
+ ngx_conf_merge_size_value(conf->upstream.busy_buffers_size_conf,
+ prev->upstream.busy_buffers_size_conf,
+ NGX_CONF_UNSET_SIZE);
+
+ if (conf->upstream.busy_buffers_size_conf == NGX_CONF_UNSET_SIZE) {
+ conf->upstream.busy_buffers_size = 2 * size;
+ } else {
+ conf->upstream.busy_buffers_size =
+ conf->upstream.busy_buffers_size_conf;
+ }
+
+ if (conf->upstream.busy_buffers_size < size) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"scgi_busy_buffers_size\" must be equal to or greater "
+ "than the maximum of the value of \"scgi_buffer_size\" and "
+ "one of the \"scgi_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+ if (conf->upstream.busy_buffers_size
+ > (conf->upstream.bufs.num - 1) * conf->upstream.bufs.size)
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"scgi_busy_buffers_size\" must be less than "
+ "the size of all \"scgi_buffers\" minus one buffer");
+
+ return NGX_CONF_ERROR;
+ }
+
+
+ ngx_conf_merge_size_value(conf->upstream.temp_file_write_size_conf,
+ prev->upstream.temp_file_write_size_conf,
+ NGX_CONF_UNSET_SIZE);
+
+ if (conf->upstream.temp_file_write_size_conf == NGX_CONF_UNSET_SIZE) {
+ conf->upstream.temp_file_write_size = 2 * size;
+ } else {
+ conf->upstream.temp_file_write_size =
+ conf->upstream.temp_file_write_size_conf;
+ }
+
+ if (conf->upstream.temp_file_write_size < size) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"scgi_temp_file_write_size\" must be equal to or greater than "
+ "the maximum of the value of \"scgi_buffer_size\" and "
+ "one of the \"scgi_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+
+ ngx_conf_merge_size_value(conf->upstream.max_temp_file_size_conf,
+ prev->upstream.max_temp_file_size_conf,
+ NGX_CONF_UNSET_SIZE);
+
+ if (conf->upstream.max_temp_file_size_conf == NGX_CONF_UNSET_SIZE) {
+ conf->upstream.max_temp_file_size = 1024 * 1024 * 1024;
+ } else {
+ conf->upstream.max_temp_file_size =
+ conf->upstream.max_temp_file_size_conf;
+ }
+
+ if (conf->upstream.max_temp_file_size != 0
+ && conf->upstream.max_temp_file_size < size)
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"scgi_max_temp_file_size\" must be equal to zero to disable "
+ "temporary files usage or must be equal to or greater than "
+ "the maximum of the value of \"scgi_buffer_size\" and "
+ "one of the \"scgi_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+
+ ngx_conf_merge_bitmask_value(conf->upstream.ignore_headers,
+ prev->upstream.ignore_headers,
+ NGX_CONF_BITMASK_SET);
+
+
+ ngx_conf_merge_bitmask_value(conf->upstream.next_upstream,
+ prev->upstream.next_upstream,
+ (NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_ERROR
+ |NGX_HTTP_UPSTREAM_FT_TIMEOUT));
+
+ if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) {
+ conf->upstream.next_upstream = NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_OFF;
+ }
+
+ if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path,
+ prev->upstream.temp_path,
+ &ngx_http_scgi_temp_path)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->upstream.cache == NGX_CONF_UNSET) {
+ ngx_conf_merge_value(conf->upstream.cache,
+ prev->upstream.cache, 0);
+
+ conf->upstream.cache_zone = prev->upstream.cache_zone;
+ conf->upstream.cache_value = prev->upstream.cache_value;
+ }
+
+ if (conf->upstream.cache_zone && conf->upstream.cache_zone->data == NULL) {
+ ngx_shm_zone_t *shm_zone;
+
+ shm_zone = conf->upstream.cache_zone;
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"scgi_cache\" zone \"%V\" is unknown",
+ &shm_zone->shm.name);
+
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_uint_value(conf->upstream.cache_min_uses,
+ prev->upstream.cache_min_uses, 1);
+
+ ngx_conf_merge_off_value(conf->upstream.cache_max_range_offset,
+ prev->upstream.cache_max_range_offset,
+ NGX_MAX_OFF_T_VALUE);
+
+ ngx_conf_merge_bitmask_value(conf->upstream.cache_use_stale,
+ prev->upstream.cache_use_stale,
+ (NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_OFF));
+
+ if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_OFF) {
+ conf->upstream.cache_use_stale = NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_OFF;
+ }
+
+ if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_ERROR) {
+ conf->upstream.cache_use_stale |= NGX_HTTP_UPSTREAM_FT_NOLIVE;
+ }
+
+ if (conf->upstream.cache_methods == 0) {
+ conf->upstream.cache_methods = prev->upstream.cache_methods;
+ }
+
+ conf->upstream.cache_methods |= NGX_HTTP_GET|NGX_HTTP_HEAD;
+
+ ngx_conf_merge_ptr_value(conf->upstream.cache_bypass,
+ prev->upstream.cache_bypass, NULL);
+
+ ngx_conf_merge_ptr_value(conf->upstream.no_cache,
+ prev->upstream.no_cache, NULL);
+
+ ngx_conf_merge_ptr_value(conf->upstream.cache_valid,
+ prev->upstream.cache_valid, NULL);
+
+ if (conf->cache_key.value.data == NULL) {
+ conf->cache_key = prev->cache_key;
+ }
+
+ if (conf->upstream.cache && conf->cache_key.value.data == NULL) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "no \"scgi_cache_key\" for \"scgi_cache\"");
+ }
+
+ ngx_conf_merge_value(conf->upstream.cache_lock,
+ prev->upstream.cache_lock, 0);
+
+ ngx_conf_merge_msec_value(conf->upstream.cache_lock_timeout,
+ prev->upstream.cache_lock_timeout, 5000);
+
+ ngx_conf_merge_msec_value(conf->upstream.cache_lock_age,
+ prev->upstream.cache_lock_age, 5000);
+
+ ngx_conf_merge_value(conf->upstream.cache_revalidate,
+ prev->upstream.cache_revalidate, 0);
+
+ ngx_conf_merge_value(conf->upstream.cache_background_update,
+ prev->upstream.cache_background_update, 0);
+
+#endif
+
+ ngx_conf_merge_value(conf->upstream.pass_request_headers,
+ prev->upstream.pass_request_headers, 1);
+ ngx_conf_merge_value(conf->upstream.pass_request_body,
+ prev->upstream.pass_request_body, 1);
+
+ ngx_conf_merge_value(conf->upstream.intercept_errors,
+ prev->upstream.intercept_errors, 0);
+
+ hash.max_size = 512;
+ hash.bucket_size = ngx_align(64, ngx_cacheline_size);
+ hash.name = "scgi_hide_headers_hash";
+
+ if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream,
+ &prev->upstream, ngx_http_scgi_hide_headers, &hash)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+ if (clcf->noname
+ && conf->upstream.upstream == NULL && conf->scgi_lengths == NULL)
+ {
+ conf->upstream.upstream = prev->upstream.upstream;
+ conf->scgi_lengths = prev->scgi_lengths;
+ conf->scgi_values = prev->scgi_values;
+ }
+
+ if (clcf->lmt_excpt && clcf->handler == NULL
+ && (conf->upstream.upstream || conf->scgi_lengths))
+ {
+ clcf->handler = ngx_http_scgi_handler;
+ }
+
+ if (conf->params_source == NULL) {
+ conf->params = prev->params;
+#if (NGX_HTTP_CACHE)
+ conf->params_cache = prev->params_cache;
+#endif
+ conf->params_source = prev->params_source;
+ }
+
+ rc = ngx_http_scgi_init_params(cf, conf, &conf->params, NULL);
+ if (rc != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->upstream.cache) {
+ rc = ngx_http_scgi_init_params(cf, conf, &conf->params_cache,
+ ngx_http_scgi_cache_headers);
+ if (rc != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+#endif
+
+ /*
+ * special handling to preserve conf->params in the "http" section
+ * to inherit it to all servers
+ */
+
+ if (prev->params.hash.buckets == NULL
+ && conf->params_source == prev->params_source)
+ {
+ prev->params = conf->params;
+#if (NGX_HTTP_CACHE)
+ prev->params_cache = conf->params_cache;
+#endif
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_scgi_init_params(ngx_conf_t *cf, ngx_http_scgi_loc_conf_t *conf,
+ ngx_http_scgi_params_t *params, ngx_keyval_t *default_params)
+{
+ u_char *p;
+ size_t size;
+ uintptr_t *code;
+ ngx_uint_t i, nsrc;
+ ngx_array_t headers_names, params_merged;
+ ngx_keyval_t *h;
+ ngx_hash_key_t *hk;
+ ngx_hash_init_t hash;
+ ngx_http_upstream_param_t *src, *s;
+ ngx_http_script_compile_t sc;
+ ngx_http_script_copy_code_t *copy;
+
+ if (params->hash.buckets) {
+ return NGX_OK;
+ }
+
+ if (conf->params_source == NULL && default_params == NULL) {
+ params->hash.buckets = (void *) 1;
+ return NGX_OK;
+ }
+
+ params->lengths = ngx_array_create(cf->pool, 64, 1);
+ if (params->lengths == NULL) {
+ return NGX_ERROR;
+ }
+
+ params->values = ngx_array_create(cf->pool, 512, 1);
+ if (params->values == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ if (conf->params_source) {
+ src = conf->params_source->elts;
+ nsrc = conf->params_source->nelts;
+
+ } else {
+ src = NULL;
+ nsrc = 0;
+ }
+
+ if (default_params) {
+ if (ngx_array_init(&params_merged, cf->temp_pool, 4,
+ sizeof(ngx_http_upstream_param_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ for (i = 0; i < nsrc; i++) {
+
+ s = ngx_array_push(&params_merged);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ *s = src[i];
+ }
+
+ h = default_params;
+
+ while (h->key.len) {
+
+ src = params_merged.elts;
+ nsrc = params_merged.nelts;
+
+ for (i = 0; i < nsrc; i++) {
+ if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) {
+ goto next;
+ }
+ }
+
+ s = ngx_array_push(&params_merged);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ s->key = h->key;
+ s->value = h->value;
+ s->skip_empty = 1;
+
+ next:
+
+ h++;
+ }
+
+ src = params_merged.elts;
+ nsrc = params_merged.nelts;
+ }
+
+ for (i = 0; i < nsrc; i++) {
+
+ if (src[i].key.len > sizeof("HTTP_") - 1
+ && ngx_strncmp(src[i].key.data, "HTTP_", sizeof("HTTP_") - 1) == 0)
+ {
+ hk = ngx_array_push(&headers_names);
+ if (hk == NULL) {
+ return NGX_ERROR;
+ }
+
+ hk->key.len = src[i].key.len - 5;
+ hk->key.data = src[i].key.data + 5;
+ hk->key_hash = ngx_hash_key_lc(hk->key.data, hk->key.len);
+ hk->value = (void *) 1;
+
+ if (src[i].value.len == 0) {
+ continue;
+ }
+ }
+
+ copy = ngx_array_push_n(params->lengths,
+ sizeof(ngx_http_script_copy_code_t));
+ if (copy == NULL) {
+ return NGX_ERROR;
+ }
+
+ copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code;
+ copy->len = src[i].key.len + 1;
+
+ copy = ngx_array_push_n(params->lengths,
+ sizeof(ngx_http_script_copy_code_t));
+ if (copy == NULL) {
+ return NGX_ERROR;
+ }
+
+ copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code;
+ copy->len = src[i].skip_empty;
+
+
+ size = (sizeof(ngx_http_script_copy_code_t)
+ + src[i].key.len + 1 + sizeof(uintptr_t) - 1)
+ & ~(sizeof(uintptr_t) - 1);
+
+ copy = ngx_array_push_n(params->values, size);
+ if (copy == NULL) {
+ return NGX_ERROR;
+ }
+
+ copy->code = ngx_http_script_copy_code;
+ copy->len = src[i].key.len + 1;
+
+ p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t);
+ (void) ngx_cpystrn(p, src[i].key.data, src[i].key.len + 1);
+
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = &src[i].value;
+ sc.flushes = &params->flushes;
+ sc.lengths = &params->lengths;
+ sc.values = &params->values;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ code = ngx_array_push_n(params->lengths, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+
+
+ code = ngx_array_push_n(params->values, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+ }
+
+ code = ngx_array_push_n(params->lengths, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+
+ params->number = headers_names.nelts;
+
+ hash.hash = &params->hash;
+ hash.key = ngx_hash_key_lc;
+ hash.max_size = 512;
+ hash.bucket_size = 64;
+ hash.name = "scgi_params_hash";
+ hash.pool = cf->pool;
+ hash.temp_pool = NULL;
+
+ return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts);
+}
+
+
+static char *
+ngx_http_scgi_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_scgi_loc_conf_t *scf = conf;
+
+ ngx_url_t u;
+ ngx_str_t *value, *url;
+ ngx_uint_t n;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_script_compile_t sc;
+
+ if (scf->upstream.upstream || scf->scgi_lengths) {
+ return "is duplicate";
+ }
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+ clcf->handler = ngx_http_scgi_handler;
+
+ value = cf->args->elts;
+
+ url = &value[1];
+
+ n = ngx_http_script_variables_count(url);
+
+ if (n) {
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = url;
+ sc.lengths = &scf->scgi_lengths;
+ sc.values = &scf->scgi_values;
+ sc.variables = n;
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+ }
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.url = value[1];
+ u.no_resolve = 1;
+
+ scf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
+ if (scf->upstream.upstream == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (clcf->name.data[clcf->name.len - 1] == '/') {
+ clcf->auto_redirect = 1;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_scgi_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_scgi_loc_conf_t *scf = conf;
+
+ ngx_str_t *value;
+ ngx_http_script_compile_t sc;
+
+ if (scf->upstream.store != NGX_CONF_UNSET) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ scf->upstream.store = 0;
+ return NGX_CONF_OK;
+ }
+
+#if (NGX_HTTP_CACHE)
+ if (scf->upstream.cache > 0) {
+ return "is incompatible with \"scgi_cache\"";
+ }
+#endif
+
+ scf->upstream.store = 1;
+
+ if (ngx_strcmp(value[1].data, "on") == 0) {
+ return NGX_CONF_OK;
+ }
+
+ /* include the terminating '\0' into script */
+ value[1].len++;
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = &value[1];
+ sc.lengths = &scf->upstream.store_lengths;
+ sc.values = &scf->upstream.store_values;
+ sc.variables = ngx_http_script_variables_count(&value[1]);
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static char *
+ngx_http_scgi_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_scgi_loc_conf_t *scf = conf;
+
+ ngx_str_t *value;
+ ngx_http_complex_value_t cv;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ if (scf->upstream.cache != NGX_CONF_UNSET) {
+ return "is duplicate";
+ }
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ scf->upstream.cache = 0;
+ return NGX_CONF_OK;
+ }
+
+ if (scf->upstream.store > 0) {
+ return "is incompatible with \"scgi_store\"";
+ }
+
+ scf->upstream.cache = 1;
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cv.lengths != NULL) {
+
+ scf->upstream.cache_value = ngx_palloc(cf->pool,
+ sizeof(ngx_http_complex_value_t));
+ if (scf->upstream.cache_value == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *scf->upstream.cache_value = cv;
+
+ return NGX_CONF_OK;
+ }
+
+ scf->upstream.cache_zone = ngx_shared_memory_add(cf, &value[1], 0,
+ &ngx_http_scgi_module);
+ if (scf->upstream.cache_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_scgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_scgi_loc_conf_t *scf = conf;
+
+ ngx_str_t *value;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ if (scf->cache_key.value.data) {
+ return "is duplicate";
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &scf->cache_key;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+#endif
diff --git a/app/nginx/src/http/modules/ngx_http_secure_link_module.c b/app/nginx/src/http/modules/ngx_http_secure_link_module.c
new file mode 100644
index 0000000..907ba6e
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_secure_link_module.c
@@ -0,0 +1,368 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_md5.h>
+
+
+typedef struct {
+ ngx_http_complex_value_t *variable;
+ ngx_http_complex_value_t *md5;
+ ngx_str_t secret;
+} ngx_http_secure_link_conf_t;
+
+
+typedef struct {
+ ngx_str_t expires;
+} ngx_http_secure_link_ctx_t;
+
+
+static ngx_int_t ngx_http_secure_link_old_variable(ngx_http_request_t *r,
+ ngx_http_secure_link_conf_t *conf, ngx_http_variable_value_t *v,
+ uintptr_t data);
+static ngx_int_t ngx_http_secure_link_expires_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static void *ngx_http_secure_link_create_conf(ngx_conf_t *cf);
+static char *ngx_http_secure_link_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static ngx_int_t ngx_http_secure_link_add_variables(ngx_conf_t *cf);
+
+
+static ngx_command_t ngx_http_secure_link_commands[] = {
+
+ { ngx_string("secure_link"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_set_complex_value_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_secure_link_conf_t, variable),
+ NULL },
+
+ { ngx_string("secure_link_md5"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_set_complex_value_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_secure_link_conf_t, md5),
+ NULL },
+
+ { ngx_string("secure_link_secret"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_secure_link_conf_t, secret),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_secure_link_module_ctx = {
+ ngx_http_secure_link_add_variables, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_secure_link_create_conf, /* create location configuration */
+ ngx_http_secure_link_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_secure_link_module = {
+ NGX_MODULE_V1,
+ &ngx_http_secure_link_module_ctx, /* module context */
+ ngx_http_secure_link_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_http_secure_link_name = ngx_string("secure_link");
+static ngx_str_t ngx_http_secure_link_expires_name =
+ ngx_string("secure_link_expires");
+
+
+static ngx_int_t
+ngx_http_secure_link_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ u_char *p, *last;
+ ngx_str_t val, hash;
+ time_t expires;
+ ngx_md5_t md5;
+ ngx_http_secure_link_ctx_t *ctx;
+ ngx_http_secure_link_conf_t *conf;
+ u_char hash_buf[16], md5_buf[16];
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_secure_link_module);
+
+ if (conf->secret.data) {
+ return ngx_http_secure_link_old_variable(r, conf, v, data);
+ }
+
+ if (conf->variable == NULL || conf->md5 == NULL) {
+ goto not_found;
+ }
+
+ if (ngx_http_complex_value(r, conf->variable, &val) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "secure link: \"%V\"", &val);
+
+ last = val.data + val.len;
+
+ p = ngx_strlchr(val.data, last, ',');
+ expires = 0;
+
+ if (p) {
+ val.len = p++ - val.data;
+
+ expires = ngx_atotm(p, last - p);
+ if (expires <= 0) {
+ goto not_found;
+ }
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_secure_link_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_secure_link_module);
+
+ ctx->expires.len = last - p;
+ ctx->expires.data = p;
+ }
+
+ if (val.len > 24) {
+ goto not_found;
+ }
+
+ hash.len = 16;
+ hash.data = hash_buf;
+
+ if (ngx_decode_base64url(&hash, &val) != NGX_OK) {
+ goto not_found;
+ }
+
+ if (hash.len != 16) {
+ goto not_found;
+ }
+
+ if (ngx_http_complex_value(r, conf->md5, &val) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "secure link md5: \"%V\"", &val);
+
+ ngx_md5_init(&md5);
+ ngx_md5_update(&md5, val.data, val.len);
+ ngx_md5_final(md5_buf, &md5);
+
+ if (ngx_memcmp(hash_buf, md5_buf, 16) != 0) {
+ goto not_found;
+ }
+
+ v->data = (u_char *) ((expires && expires < ngx_time()) ? "0" : "1");
+ v->len = 1;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ return NGX_OK;
+
+not_found:
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_secure_link_old_variable(ngx_http_request_t *r,
+ ngx_http_secure_link_conf_t *conf, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ u_char *p, *start, *end, *last;
+ size_t len;
+ ngx_int_t n;
+ ngx_uint_t i;
+ ngx_md5_t md5;
+ u_char hash[16];
+
+ p = &r->unparsed_uri.data[1];
+ last = r->unparsed_uri.data + r->unparsed_uri.len;
+
+ while (p < last) {
+ if (*p++ == '/') {
+ start = p;
+ goto md5_start;
+ }
+ }
+
+ goto not_found;
+
+md5_start:
+
+ while (p < last) {
+ if (*p++ == '/') {
+ end = p - 1;
+ goto url_start;
+ }
+ }
+
+ goto not_found;
+
+url_start:
+
+ len = last - p;
+
+ if (end - start != 32 || len == 0) {
+ goto not_found;
+ }
+
+ ngx_md5_init(&md5);
+ ngx_md5_update(&md5, p, len);
+ ngx_md5_update(&md5, conf->secret.data, conf->secret.len);
+ ngx_md5_final(hash, &md5);
+
+ for (i = 0; i < 16; i++) {
+ n = ngx_hextoi(&start[2 * i], 2);
+ if (n == NGX_ERROR || n != hash[i]) {
+ goto not_found;
+ }
+ }
+
+ v->len = len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = p;
+
+ return NGX_OK;
+
+not_found:
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_secure_link_expires_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_http_secure_link_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_secure_link_module);
+
+ if (ctx) {
+ v->len = ctx->expires.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = ctx->expires.data;
+
+ } else {
+ v->not_found = 1;
+ }
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_secure_link_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_secure_link_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_secure_link_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->variable = NULL;
+ * conf->md5 = NULL;
+ * conf->secret = { 0, NULL };
+ */
+
+ return conf;
+}
+
+
+static char *
+ngx_http_secure_link_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_secure_link_conf_t *prev = parent;
+ ngx_http_secure_link_conf_t *conf = child;
+
+ if (conf->secret.data) {
+ if (conf->variable || conf->md5) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"secure_link_secret\" cannot be mixed with "
+ "\"secure_link\" and \"secure_link_md5\"");
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+ }
+
+ if (conf->variable == NULL) {
+ conf->variable = prev->variable;
+ }
+
+ if (conf->md5 == NULL) {
+ conf->md5 = prev->md5;
+ }
+
+ if (conf->variable == NULL && conf->md5 == NULL) {
+ conf->secret = prev->secret;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_secure_link_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var;
+
+ var = ngx_http_add_variable(cf, &ngx_http_secure_link_name, 0);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = ngx_http_secure_link_variable;
+
+ var = ngx_http_add_variable(cf, &ngx_http_secure_link_expires_name, 0);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = ngx_http_secure_link_expires_variable;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_slice_filter_module.c b/app/nginx/src/http/modules/ngx_http_slice_filter_module.c
new file mode 100644
index 0000000..7758342
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_slice_filter_module.c
@@ -0,0 +1,543 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ size_t size;
+} ngx_http_slice_loc_conf_t;
+
+
+typedef struct {
+ off_t start;
+ off_t end;
+ ngx_str_t range;
+ ngx_str_t etag;
+ unsigned last:1;
+ unsigned active:1;
+ ngx_http_request_t *sr;
+} ngx_http_slice_ctx_t;
+
+
+typedef struct {
+ off_t start;
+ off_t end;
+ off_t complete_length;
+} ngx_http_slice_content_range_t;
+
+
+static ngx_int_t ngx_http_slice_header_filter(ngx_http_request_t *r);
+static ngx_int_t ngx_http_slice_body_filter(ngx_http_request_t *r,
+ ngx_chain_t *in);
+static ngx_int_t ngx_http_slice_parse_content_range(ngx_http_request_t *r,
+ ngx_http_slice_content_range_t *cr);
+static ngx_int_t ngx_http_slice_range_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static off_t ngx_http_slice_get_start(ngx_http_request_t *r);
+static void *ngx_http_slice_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_slice_merge_loc_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static ngx_int_t ngx_http_slice_add_variables(ngx_conf_t *cf);
+static ngx_int_t ngx_http_slice_init(ngx_conf_t *cf);
+
+
+static ngx_command_t ngx_http_slice_filter_commands[] = {
+
+ { ngx_string("slice"),
+ 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_slice_loc_conf_t, size),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_slice_filter_module_ctx = {
+ ngx_http_slice_add_variables, /* preconfiguration */
+ ngx_http_slice_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_slice_create_loc_conf, /* create location configuration */
+ ngx_http_slice_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_slice_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_slice_filter_module_ctx, /* module context */
+ ngx_http_slice_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_http_slice_range_name = ngx_string("slice_range");
+
+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_slice_header_filter(ngx_http_request_t *r)
+{
+ off_t end;
+ ngx_int_t rc;
+ ngx_table_elt_t *h;
+ ngx_http_slice_ctx_t *ctx;
+ ngx_http_slice_loc_conf_t *slcf;
+ ngx_http_slice_content_range_t cr;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
+ if (ctx == NULL) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ if (r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT) {
+ if (r == r->main) {
+ ngx_http_set_ctx(r, NULL, ngx_http_slice_filter_module);
+ return ngx_http_next_header_filter(r);
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "unexpected status code %ui in slice response",
+ r->headers_out.status);
+ return NGX_ERROR;
+ }
+
+ h = r->headers_out.etag;
+
+ if (ctx->etag.len) {
+ if (h == NULL
+ || h->value.len != ctx->etag.len
+ || ngx_strncmp(h->value.data, ctx->etag.data, ctx->etag.len)
+ != 0)
+ {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "etag mismatch in slice response");
+ return NGX_ERROR;
+ }
+ }
+
+ if (h) {
+ ctx->etag = h->value;
+ }
+
+ if (ngx_http_slice_parse_content_range(r, &cr) != NGX_OK) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid range in slice response");
+ return NGX_ERROR;
+ }
+
+ if (cr.complete_length == -1) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "no complete length in slice response");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http slice response range: %O-%O/%O",
+ cr.start, cr.end, cr.complete_length);
+
+ slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
+
+ end = ngx_min(cr.start + (off_t) slcf->size, cr.complete_length);
+
+ if (cr.start != ctx->start || cr.end != end) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "unexpected range in slice response: %O-%O",
+ cr.start, cr.end);
+ return NGX_ERROR;
+ }
+
+ ctx->start = end;
+ ctx->active = 1;
+
+ r->headers_out.status = NGX_HTTP_OK;
+ r->headers_out.status_line.len = 0;
+ r->headers_out.content_length_n = cr.complete_length;
+ r->headers_out.content_offset = cr.start;
+ r->headers_out.content_range->hash = 0;
+ r->headers_out.content_range = NULL;
+
+ r->allow_ranges = 1;
+ r->subrequest_ranges = 1;
+ r->single_range = 1;
+
+ rc = ngx_http_next_header_filter(r);
+
+ if (r != r->main) {
+ return rc;
+ }
+
+ if (r->headers_out.status == NGX_HTTP_PARTIAL_CONTENT) {
+ if (ctx->start + (off_t) slcf->size <= r->headers_out.content_offset) {
+ ctx->start = slcf->size
+ * (r->headers_out.content_offset / slcf->size);
+ }
+
+ ctx->end = r->headers_out.content_offset
+ + r->headers_out.content_length_n;
+
+ } else {
+ ctx->end = cr.complete_length;
+ }
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_slice_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ ngx_int_t rc;
+ ngx_chain_t *cl;
+ ngx_http_slice_ctx_t *ctx;
+ ngx_http_slice_loc_conf_t *slcf;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
+
+ if (ctx == NULL || r != r->main) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ for (cl = in; cl; cl = cl->next) {
+ if (cl->buf->last_buf) {
+ cl->buf->last_buf = 0;
+ cl->buf->last_in_chain = 1;
+ cl->buf->sync = 1;
+ ctx->last = 1;
+ }
+ }
+
+ rc = ngx_http_next_body_filter(r, in);
+
+ if (rc == NGX_ERROR || !ctx->last) {
+ return rc;
+ }
+
+ if (ctx->sr && !ctx->sr->done) {
+ return rc;
+ }
+
+ if (!ctx->active) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "missing slice response");
+ return NGX_ERROR;
+ }
+
+ if (ctx->start >= ctx->end) {
+ ngx_http_set_ctx(r, NULL, ngx_http_slice_filter_module);
+ ngx_http_send_special(r, NGX_HTTP_LAST);
+ return rc;
+ }
+
+ if (r->buffered) {
+ return rc;
+ }
+
+ if (ngx_http_subrequest(r, &r->uri, &r->args, &ctx->sr, NULL,
+ NGX_HTTP_SUBREQUEST_CLONE)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(ctx->sr, ctx, ngx_http_slice_filter_module);
+
+ slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
+
+ ctx->range.len = ngx_sprintf(ctx->range.data, "bytes=%O-%O", ctx->start,
+ ctx->start + (off_t) slcf->size - 1)
+ - ctx->range.data;
+
+ ctx->active = 0;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http slice subrequest: \"%V\"", &ctx->range);
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_slice_parse_content_range(ngx_http_request_t *r,
+ ngx_http_slice_content_range_t *cr)
+{
+ off_t start, end, complete_length, cutoff, cutlim;
+ u_char *p;
+ ngx_table_elt_t *h;
+
+ h = r->headers_out.content_range;
+
+ if (h == NULL
+ || h->value.len < 7
+ || ngx_strncmp(h->value.data, "bytes ", 6) != 0)
+ {
+ return NGX_ERROR;
+ }
+
+ p = h->value.data + 6;
+
+ cutoff = NGX_MAX_OFF_T_VALUE / 10;
+ cutlim = NGX_MAX_OFF_T_VALUE % 10;
+
+ start = 0;
+ end = 0;
+ complete_length = 0;
+
+ while (*p == ' ') { p++; }
+
+ if (*p < '0' || *p > '9') {
+ return NGX_ERROR;
+ }
+
+ while (*p >= '0' && *p <= '9') {
+ if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) {
+ return NGX_ERROR;
+ }
+
+ start = start * 10 + *p++ - '0';
+ }
+
+ while (*p == ' ') { p++; }
+
+ if (*p++ != '-') {
+ return NGX_ERROR;
+ }
+
+ while (*p == ' ') { p++; }
+
+ if (*p < '0' || *p > '9') {
+ return NGX_ERROR;
+ }
+
+ while (*p >= '0' && *p <= '9') {
+ if (end >= cutoff && (end > cutoff || *p - '0' > cutlim)) {
+ return NGX_ERROR;
+ }
+
+ end = end * 10 + *p++ - '0';
+ }
+
+ end++;
+
+ while (*p == ' ') { p++; }
+
+ if (*p++ != '/') {
+ return NGX_ERROR;
+ }
+
+ while (*p == ' ') { p++; }
+
+ if (*p != '*') {
+ if (*p < '0' || *p > '9') {
+ return NGX_ERROR;
+ }
+
+ while (*p >= '0' && *p <= '9') {
+ if (complete_length >= cutoff
+ && (complete_length > cutoff || *p - '0' > cutlim))
+ {
+ return NGX_ERROR;
+ }
+
+ complete_length = complete_length * 10 + *p++ - '0';
+ }
+
+ } else {
+ complete_length = -1;
+ p++;
+ }
+
+ while (*p == ' ') { p++; }
+
+ if (*p != '\0') {
+ return NGX_ERROR;
+ }
+
+ cr->start = start;
+ cr->end = end;
+ cr->complete_length = complete_length;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_slice_range_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ u_char *p;
+ ngx_http_slice_ctx_t *ctx;
+ ngx_http_slice_loc_conf_t *slcf;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
+
+ if (ctx == NULL) {
+ if (r != r->main || r->headers_out.status) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
+
+ if (slcf->size == 0) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_slice_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_slice_filter_module);
+
+ p = ngx_pnalloc(r->pool, sizeof("bytes=-") - 1 + 2 * NGX_OFF_T_LEN);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->start = slcf->size * (ngx_http_slice_get_start(r) / slcf->size);
+
+ ctx->range.data = p;
+ ctx->range.len = ngx_sprintf(p, "bytes=%O-%O", ctx->start,
+ ctx->start + (off_t) slcf->size - 1)
+ - p;
+ }
+
+ v->data = ctx->range.data;
+ v->valid = 1;
+ v->not_found = 0;
+ v->no_cacheable = 1;
+ v->len = ctx->range.len;
+
+ return NGX_OK;
+}
+
+
+static off_t
+ngx_http_slice_get_start(ngx_http_request_t *r)
+{
+ off_t start, cutoff, cutlim;
+ u_char *p;
+ ngx_table_elt_t *h;
+
+ if (r->headers_in.if_range) {
+ return 0;
+ }
+
+ h = r->headers_in.range;
+
+ if (h == NULL
+ || h->value.len < 7
+ || ngx_strncasecmp(h->value.data, (u_char *) "bytes=", 6) != 0)
+ {
+ return 0;
+ }
+
+ p = h->value.data + 6;
+
+ if (ngx_strchr(p, ',')) {
+ return 0;
+ }
+
+ while (*p == ' ') { p++; }
+
+ if (*p == '-') {
+ return 0;
+ }
+
+ cutoff = NGX_MAX_OFF_T_VALUE / 10;
+ cutlim = NGX_MAX_OFF_T_VALUE % 10;
+
+ start = 0;
+
+ while (*p >= '0' && *p <= '9') {
+ if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) {
+ return 0;
+ }
+
+ start = start * 10 + *p++ - '0';
+ }
+
+ return start;
+}
+
+
+static void *
+ngx_http_slice_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_slice_loc_conf_t *slcf;
+
+ slcf = ngx_palloc(cf->pool, sizeof(ngx_http_slice_loc_conf_t));
+ if (slcf == NULL) {
+ return NULL;
+ }
+
+ slcf->size = NGX_CONF_UNSET_SIZE;
+
+ return slcf;
+}
+
+
+static char *
+ngx_http_slice_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_slice_loc_conf_t *prev = parent;
+ ngx_http_slice_loc_conf_t *conf = child;
+
+ ngx_conf_merge_size_value(conf->size, prev->size, 0);
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_slice_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var;
+
+ var = ngx_http_add_variable(cf, &ngx_http_slice_range_name, 0);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = ngx_http_slice_range_variable;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_slice_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_slice_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_slice_body_filter;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_split_clients_module.c b/app/nginx/src/http/modules/ngx_http_split_clients_module.c
new file mode 100644
index 0000000..2f92c9e
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_split_clients_module.c
@@ -0,0 +1,246 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ uint32_t percent;
+ ngx_http_variable_value_t value;
+} ngx_http_split_clients_part_t;
+
+
+typedef struct {
+ ngx_http_complex_value_t value;
+ ngx_array_t parts;
+} ngx_http_split_clients_ctx_t;
+
+
+static char *ngx_conf_split_clients_block(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_split_clients(ngx_conf_t *cf, ngx_command_t *dummy,
+ void *conf);
+
+static ngx_command_t ngx_http_split_clients_commands[] = {
+
+ { ngx_string("split_clients"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2,
+ ngx_conf_split_clients_block,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_split_clients_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_split_clients_module = {
+ NGX_MODULE_V1,
+ &ngx_http_split_clients_module_ctx, /* module context */
+ ngx_http_split_clients_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_split_clients_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_http_split_clients_ctx_t *ctx = (ngx_http_split_clients_ctx_t *) data;
+
+ uint32_t hash;
+ ngx_str_t val;
+ ngx_uint_t i;
+ ngx_http_split_clients_part_t *part;
+
+ *v = ngx_http_variable_null_value;
+
+ if (ngx_http_complex_value(r, &ctx->value, &val) != NGX_OK) {
+ return NGX_OK;
+ }
+
+ hash = ngx_murmur_hash2(val.data, val.len);
+
+ part = ctx->parts.elts;
+
+ for (i = 0; i < ctx->parts.nelts; i++) {
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http split: %uD %uD", hash, part[i].percent);
+
+ if (hash < part[i].percent || part[i].percent == 0) {
+ *v = part[i].value;
+ return NGX_OK;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_conf_split_clients_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *rv;
+ uint32_t sum, last;
+ ngx_str_t *value, name;
+ ngx_uint_t i;
+ ngx_conf_t save;
+ ngx_http_variable_t *var;
+ ngx_http_split_clients_ctx_t *ctx;
+ ngx_http_split_clients_part_t *part;
+ ngx_http_compile_complex_value_t ccv;
+
+ ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_split_clients_ctx_t));
+ if (ctx == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &ctx->value;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ name = value[2];
+
+ if (name.data[0] != '$') {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid variable name \"%V\"", &name);
+ return NGX_CONF_ERROR;
+ }
+
+ name.len--;
+ name.data++;
+
+ var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
+ if (var == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ var->get_handler = ngx_http_split_clients_variable;
+ var->data = (uintptr_t) ctx;
+
+ if (ngx_array_init(&ctx->parts, cf->pool, 2,
+ sizeof(ngx_http_split_clients_part_t))
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ save = *cf;
+ cf->ctx = ctx;
+ cf->handler = ngx_http_split_clients;
+ cf->handler_conf = conf;
+
+ rv = ngx_conf_parse(cf, NULL);
+
+ *cf = save;
+
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+
+ sum = 0;
+ last = 0;
+ part = ctx->parts.elts;
+
+ for (i = 0; i < ctx->parts.nelts; i++) {
+ sum = part[i].percent ? sum + part[i].percent : 10000;
+ if (sum > 10000) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "percent total is greater than 100%%");
+ return NGX_CONF_ERROR;
+ }
+
+ if (part[i].percent) {
+ last += part[i].percent * (uint64_t) 0xffffffff / 10000;
+ part[i].percent = last;
+ }
+ }
+
+ return rv;
+}
+
+
+static char *
+ngx_http_split_clients(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+ ngx_int_t n;
+ ngx_str_t *value;
+ ngx_http_split_clients_ctx_t *ctx;
+ ngx_http_split_clients_part_t *part;
+
+ ctx = cf->ctx;
+ value = cf->args->elts;
+
+ part = ngx_array_push(&ctx->parts);
+ if (part == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (value[0].len == 1 && value[0].data[0] == '*') {
+ part->percent = 0;
+
+ } else {
+ if (value[0].len == 0 || value[0].data[value[0].len - 1] != '%') {
+ goto invalid;
+ }
+
+ n = ngx_atofp(value[0].data, value[0].len - 1, 2);
+ if (n == NGX_ERROR || n == 0) {
+ goto invalid;
+ }
+
+ part->percent = (uint32_t) n;
+ }
+
+ part->value.len = value[1].len;
+ part->value.valid = 1;
+ part->value.no_cacheable = 0;
+ part->value.not_found = 0;
+ part->value.data = value[1].data;
+
+ return NGX_CONF_OK;
+
+invalid:
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid percent value \"%V\"", &value[0]);
+ return NGX_CONF_ERROR;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_ssi_filter_module.c b/app/nginx/src/http/modules/ngx_http_ssi_filter_module.c
new file mode 100644
index 0000000..6fb1fbe
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_ssi_filter_module.c
@@ -0,0 +1,2930 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#define NGX_HTTP_SSI_ERROR 1
+
+#define NGX_HTTP_SSI_DATE_LEN 2048
+
+#define NGX_HTTP_SSI_ADD_PREFIX 1
+#define NGX_HTTP_SSI_ADD_ZERO 2
+
+
+typedef struct {
+ ngx_flag_t enable;
+ ngx_flag_t silent_errors;
+ ngx_flag_t ignore_recycled_buffers;
+ ngx_flag_t last_modified;
+
+ ngx_hash_t types;
+
+ size_t min_file_chunk;
+ size_t value_len;
+
+ ngx_array_t *types_keys;
+} ngx_http_ssi_loc_conf_t;
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_uint_t key;
+ ngx_str_t value;
+} ngx_http_ssi_var_t;
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_chain_t *bufs;
+ ngx_uint_t count;
+} ngx_http_ssi_block_t;
+
+
+typedef enum {
+ ssi_start_state = 0,
+ ssi_tag_state,
+ ssi_comment0_state,
+ ssi_comment1_state,
+ ssi_sharp_state,
+ ssi_precommand_state,
+ ssi_command_state,
+ ssi_preparam_state,
+ ssi_param_state,
+ ssi_preequal_state,
+ ssi_prevalue_state,
+ ssi_double_quoted_value_state,
+ ssi_quoted_value_state,
+ ssi_quoted_symbol_state,
+ ssi_postparam_state,
+ ssi_comment_end0_state,
+ ssi_comment_end1_state,
+ ssi_error_state,
+ ssi_error_end0_state,
+ ssi_error_end1_state
+} ngx_http_ssi_state_e;
+
+
+static ngx_int_t ngx_http_ssi_output(ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ctx);
+static void ngx_http_ssi_buffered(ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ctx);
+static ngx_int_t ngx_http_ssi_parse(ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ctx);
+static ngx_str_t *ngx_http_ssi_get_variable(ngx_http_request_t *r,
+ ngx_str_t *name, ngx_uint_t key);
+static ngx_int_t ngx_http_ssi_evaluate_string(ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ctx, ngx_str_t *text, ngx_uint_t flags);
+static ngx_int_t ngx_http_ssi_regex_match(ngx_http_request_t *r,
+ ngx_str_t *pattern, ngx_str_t *str);
+
+static ngx_int_t ngx_http_ssi_include(ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+static ngx_int_t ngx_http_ssi_stub_output(ngx_http_request_t *r, void *data,
+ ngx_int_t rc);
+static ngx_int_t ngx_http_ssi_set_variable(ngx_http_request_t *r, void *data,
+ ngx_int_t rc);
+static ngx_int_t ngx_http_ssi_echo(ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+static ngx_int_t ngx_http_ssi_config(ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+static ngx_int_t ngx_http_ssi_set(ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+static ngx_int_t ngx_http_ssi_if(ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+static ngx_int_t ngx_http_ssi_else(ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+static ngx_int_t ngx_http_ssi_endif(ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+static ngx_int_t ngx_http_ssi_block(ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+static ngx_int_t ngx_http_ssi_endblock(ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
+
+static ngx_int_t ngx_http_ssi_date_gmt_local_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t gmt);
+
+static ngx_int_t ngx_http_ssi_preconfiguration(ngx_conf_t *cf);
+static void *ngx_http_ssi_create_main_conf(ngx_conf_t *cf);
+static char *ngx_http_ssi_init_main_conf(ngx_conf_t *cf, void *conf);
+static void *ngx_http_ssi_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_ssi_merge_loc_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static ngx_int_t ngx_http_ssi_filter_init(ngx_conf_t *cf);
+
+
+static ngx_command_t ngx_http_ssi_filter_commands[] = {
+
+ { ngx_string("ssi"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+ |NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_ssi_loc_conf_t, enable),
+ NULL },
+
+ { ngx_string("ssi_silent_errors"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_ssi_loc_conf_t, silent_errors),
+ NULL },
+
+ { ngx_string("ssi_ignore_recycled_buffers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_ssi_loc_conf_t, ignore_recycled_buffers),
+ NULL },
+
+ { ngx_string("ssi_min_file_chunk"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_ssi_loc_conf_t, min_file_chunk),
+ NULL },
+
+ { ngx_string("ssi_value_length"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_ssi_loc_conf_t, value_len),
+ NULL },
+
+ { ngx_string("ssi_types"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_types_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_ssi_loc_conf_t, types_keys),
+ &ngx_http_html_default_types[0] },
+
+ { ngx_string("ssi_last_modified"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_ssi_loc_conf_t, last_modified),
+ NULL },
+
+ ngx_null_command
+};
+
+
+
+static ngx_http_module_t ngx_http_ssi_filter_module_ctx = {
+ ngx_http_ssi_preconfiguration, /* preconfiguration */
+ ngx_http_ssi_filter_init, /* postconfiguration */
+
+ ngx_http_ssi_create_main_conf, /* create main configuration */
+ ngx_http_ssi_init_main_conf, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_ssi_create_loc_conf, /* create location configuration */
+ ngx_http_ssi_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_ssi_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_ssi_filter_module_ctx, /* module context */
+ ngx_http_ssi_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
+
+
+static u_char ngx_http_ssi_string[] = "<!--";
+
+static ngx_str_t ngx_http_ssi_none = ngx_string("(none)");
+static ngx_str_t ngx_http_ssi_timefmt = ngx_string("%A, %d-%b-%Y %H:%M:%S %Z");
+static ngx_str_t ngx_http_ssi_null_string = ngx_null_string;
+
+
+#define NGX_HTTP_SSI_INCLUDE_VIRTUAL 0
+#define NGX_HTTP_SSI_INCLUDE_FILE 1
+#define NGX_HTTP_SSI_INCLUDE_WAIT 2
+#define NGX_HTTP_SSI_INCLUDE_SET 3
+#define NGX_HTTP_SSI_INCLUDE_STUB 4
+
+#define NGX_HTTP_SSI_ECHO_VAR 0
+#define NGX_HTTP_SSI_ECHO_DEFAULT 1
+#define NGX_HTTP_SSI_ECHO_ENCODING 2
+
+#define NGX_HTTP_SSI_CONFIG_ERRMSG 0
+#define NGX_HTTP_SSI_CONFIG_TIMEFMT 1
+
+#define NGX_HTTP_SSI_SET_VAR 0
+#define NGX_HTTP_SSI_SET_VALUE 1
+
+#define NGX_HTTP_SSI_IF_EXPR 0
+
+#define NGX_HTTP_SSI_BLOCK_NAME 0
+
+
+static ngx_http_ssi_param_t ngx_http_ssi_include_params[] = {
+ { ngx_string("virtual"), NGX_HTTP_SSI_INCLUDE_VIRTUAL, 0, 0 },
+ { ngx_string("file"), NGX_HTTP_SSI_INCLUDE_FILE, 0, 0 },
+ { ngx_string("wait"), NGX_HTTP_SSI_INCLUDE_WAIT, 0, 0 },
+ { ngx_string("set"), NGX_HTTP_SSI_INCLUDE_SET, 0, 0 },
+ { ngx_string("stub"), NGX_HTTP_SSI_INCLUDE_STUB, 0, 0 },
+ { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t ngx_http_ssi_echo_params[] = {
+ { ngx_string("var"), NGX_HTTP_SSI_ECHO_VAR, 1, 0 },
+ { ngx_string("default"), NGX_HTTP_SSI_ECHO_DEFAULT, 0, 0 },
+ { ngx_string("encoding"), NGX_HTTP_SSI_ECHO_ENCODING, 0, 0 },
+ { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t ngx_http_ssi_config_params[] = {
+ { ngx_string("errmsg"), NGX_HTTP_SSI_CONFIG_ERRMSG, 0, 0 },
+ { ngx_string("timefmt"), NGX_HTTP_SSI_CONFIG_TIMEFMT, 0, 0 },
+ { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t ngx_http_ssi_set_params[] = {
+ { ngx_string("var"), NGX_HTTP_SSI_SET_VAR, 1, 0 },
+ { ngx_string("value"), NGX_HTTP_SSI_SET_VALUE, 1, 0 },
+ { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t ngx_http_ssi_if_params[] = {
+ { ngx_string("expr"), NGX_HTTP_SSI_IF_EXPR, 1, 0 },
+ { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t ngx_http_ssi_block_params[] = {
+ { ngx_string("name"), NGX_HTTP_SSI_BLOCK_NAME, 1, 0 },
+ { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_param_t ngx_http_ssi_no_params[] = {
+ { ngx_null_string, 0, 0, 0 }
+};
+
+
+static ngx_http_ssi_command_t ngx_http_ssi_commands[] = {
+ { ngx_string("include"), ngx_http_ssi_include,
+ ngx_http_ssi_include_params, 0, 0, 1 },
+ { ngx_string("echo"), ngx_http_ssi_echo,
+ ngx_http_ssi_echo_params, 0, 0, 0 },
+ { ngx_string("config"), ngx_http_ssi_config,
+ ngx_http_ssi_config_params, 0, 0, 0 },
+ { ngx_string("set"), ngx_http_ssi_set, ngx_http_ssi_set_params, 0, 0, 0 },
+
+ { ngx_string("if"), ngx_http_ssi_if, ngx_http_ssi_if_params, 0, 0, 0 },
+ { ngx_string("elif"), ngx_http_ssi_if, ngx_http_ssi_if_params,
+ NGX_HTTP_SSI_COND_IF, 0, 0 },
+ { ngx_string("else"), ngx_http_ssi_else, ngx_http_ssi_no_params,
+ NGX_HTTP_SSI_COND_IF, 0, 0 },
+ { ngx_string("endif"), ngx_http_ssi_endif, ngx_http_ssi_no_params,
+ NGX_HTTP_SSI_COND_ELSE, 0, 0 },
+
+ { ngx_string("block"), ngx_http_ssi_block,
+ ngx_http_ssi_block_params, 0, 0, 0 },
+ { ngx_string("endblock"), ngx_http_ssi_endblock,
+ ngx_http_ssi_no_params, 0, 1, 0 },
+
+ { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+
+static ngx_http_variable_t ngx_http_ssi_vars[] = {
+
+ { ngx_string("date_local"), NULL, ngx_http_ssi_date_gmt_local_variable, 0,
+ NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("date_gmt"), NULL, ngx_http_ssi_date_gmt_local_variable, 1,
+ NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+
+
+static ngx_int_t
+ngx_http_ssi_header_filter(ngx_http_request_t *r)
+{
+ ngx_http_ssi_ctx_t *ctx;
+ ngx_http_ssi_loc_conf_t *slcf;
+
+ slcf = ngx_http_get_module_loc_conf(r, ngx_http_ssi_filter_module);
+
+ if (!slcf->enable
+ || r->headers_out.content_length_n == 0
+ || ngx_http_test_content_type(r, &slcf->types) == NULL)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_ssi_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_ssi_filter_module);
+
+
+ ctx->value_len = slcf->value_len;
+ ctx->last_out = &ctx->out;
+
+ ctx->encoding = NGX_HTTP_SSI_ENTITY_ENCODING;
+ ctx->output = 1;
+
+ ctx->params.elts = ctx->params_array;
+ ctx->params.size = sizeof(ngx_table_elt_t);
+ ctx->params.nalloc = NGX_HTTP_SSI_PARAMS_N;
+ ctx->params.pool = r->pool;
+
+ ctx->timefmt = ngx_http_ssi_timefmt;
+ ngx_str_set(&ctx->errmsg,
+ "[an error occurred while processing the directive]");
+
+ r->filter_need_in_memory = 1;
+
+ if (r == r->main) {
+ ngx_http_clear_content_length(r);
+ ngx_http_clear_accept_ranges(r);
+
+ if (!slcf->last_modified) {
+ ngx_http_clear_last_modified(r);
+ ngx_http_clear_etag(r);
+
+ } else {
+ ngx_http_weak_etag(r);
+ }
+ }
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_ssi_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ size_t len;
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_uint_t i, index;
+ ngx_chain_t *cl, **ll;
+ ngx_table_elt_t *param;
+ ngx_http_ssi_ctx_t *ctx, *mctx;
+ ngx_http_ssi_block_t *bl;
+ ngx_http_ssi_param_t *prm;
+ ngx_http_ssi_command_t *cmd;
+ ngx_http_ssi_loc_conf_t *slcf;
+ ngx_http_ssi_main_conf_t *smcf;
+ ngx_str_t *params[NGX_HTTP_SSI_MAX_PARAMS + 1];
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_ssi_filter_module);
+
+ if (ctx == NULL
+ || (in == NULL
+ && ctx->buf == NULL
+ && ctx->in == NULL
+ && ctx->busy == NULL))
+ {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ /* add the incoming chain to the chain ctx->in */
+
+ if (in) {
+ if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http ssi filter \"%V?%V\"", &r->uri, &r->args);
+
+ if (ctx->wait) {
+
+ if (r != r->connection->data) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http ssi filter wait \"%V?%V\" non-active",
+ &ctx->wait->uri, &ctx->wait->args);
+
+ return NGX_AGAIN;
+ }
+
+ if (ctx->wait->done) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http ssi filter wait \"%V?%V\" done",
+ &ctx->wait->uri, &ctx->wait->args);
+
+ ctx->wait = NULL;
+
+ } else {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http ssi filter wait \"%V?%V\"",
+ &ctx->wait->uri, &ctx->wait->args);
+
+ return ngx_http_next_body_filter(r, NULL);
+ }
+ }
+
+ slcf = ngx_http_get_module_loc_conf(r, ngx_http_ssi_filter_module);
+
+ while (ctx->in || ctx->buf) {
+
+ if (ctx->buf == NULL) {
+ ctx->buf = ctx->in->buf;
+ ctx->in = ctx->in->next;
+ ctx->pos = ctx->buf->pos;
+ }
+
+ if (ctx->state == ssi_start_state) {
+ ctx->copy_start = ctx->pos;
+ ctx->copy_end = ctx->pos;
+ }
+
+ b = NULL;
+
+ while (ctx->pos < ctx->buf->last) {
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "saved: %uz state: %ui", ctx->saved, ctx->state);
+
+ rc = ngx_http_ssi_parse(r, ctx);
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "parse: %i, looked: %uz %p-%p",
+ rc, ctx->looked, ctx->copy_start, ctx->copy_end);
+
+ if (rc == NGX_ERROR) {
+ return rc;
+ }
+
+ if (ctx->copy_start != ctx->copy_end) {
+
+ if (ctx->output) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "saved: %uz", ctx->saved);
+
+ if (ctx->saved) {
+
+ if (ctx->free) {
+ cl = ctx->free;
+ ctx->free = ctx->free->next;
+ b = cl->buf;
+ ngx_memzero(b, sizeof(ngx_buf_t));
+
+ } else {
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ }
+
+ b->memory = 1;
+ b->pos = ngx_http_ssi_string;
+ b->last = ngx_http_ssi_string + ctx->saved;
+
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ ctx->saved = 0;
+ }
+
+ if (ctx->free) {
+ cl = ctx->free;
+ ctx->free = ctx->free->next;
+ b = cl->buf;
+
+ } else {
+ b = ngx_alloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ }
+
+ ngx_memcpy(b, ctx->buf, sizeof(ngx_buf_t));
+
+ b->pos = ctx->copy_start;
+ b->last = ctx->copy_end;
+ b->shadow = NULL;
+ b->last_buf = 0;
+ b->recycled = 0;
+
+ if (b->in_file) {
+ if (slcf->min_file_chunk < (size_t) (b->last - b->pos))
+ {
+ b->file_last = b->file_pos
+ + (b->last - ctx->buf->pos);
+ b->file_pos += b->pos - ctx->buf->pos;
+
+ } else {
+ b->in_file = 0;
+ }
+ }
+
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ } else {
+ if (ctx->block
+ && ctx->saved + (ctx->copy_end - ctx->copy_start))
+ {
+ b = ngx_create_temp_buf(r->pool,
+ ctx->saved + (ctx->copy_end - ctx->copy_start));
+
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ctx->saved) {
+ b->last = ngx_cpymem(b->pos, ngx_http_ssi_string,
+ ctx->saved);
+ }
+
+ b->last = ngx_cpymem(b->last, ctx->copy_start,
+ ctx->copy_end - ctx->copy_start);
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+
+ b = NULL;
+
+ mctx = ngx_http_get_module_ctx(r->main,
+ ngx_http_ssi_filter_module);
+ bl = mctx->blocks->elts;
+ for (ll = &bl[mctx->blocks->nelts - 1].bufs;
+ *ll;
+ ll = &(*ll)->next)
+ {
+ /* void */
+ }
+
+ *ll = cl;
+ }
+
+ ctx->saved = 0;
+ }
+ }
+
+ if (ctx->state == ssi_start_state) {
+ ctx->copy_start = ctx->pos;
+ ctx->copy_end = ctx->pos;
+
+ } else {
+ ctx->copy_start = NULL;
+ ctx->copy_end = NULL;
+ }
+
+ if (rc == NGX_AGAIN) {
+ continue;
+ }
+
+
+ b = NULL;
+
+ if (rc == NGX_OK) {
+
+ smcf = ngx_http_get_module_main_conf(r,
+ ngx_http_ssi_filter_module);
+
+ cmd = ngx_hash_find(&smcf->hash, ctx->key, ctx->command.data,
+ ctx->command.len);
+
+ if (cmd == NULL) {
+ if (ctx->output) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid SSI command: \"%V\"",
+ &ctx->command);
+ goto ssi_error;
+ }
+
+ continue;
+ }
+
+ if (!ctx->output && !cmd->block) {
+
+ if (ctx->block) {
+
+ /* reconstruct the SSI command text */
+
+ len = 5 + ctx->command.len + 4;
+
+ param = ctx->params.elts;
+ for (i = 0; i < ctx->params.nelts; i++) {
+ len += 1 + param[i].key.len + 2
+ + param[i].value.len + 1;
+ }
+
+ b = ngx_create_temp_buf(r->pool, len);
+
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+
+ *b->last++ = '<';
+ *b->last++ = '!';
+ *b->last++ = '-';
+ *b->last++ = '-';
+ *b->last++ = '#';
+
+ b->last = ngx_cpymem(b->last, ctx->command.data,
+ ctx->command.len);
+
+ for (i = 0; i < ctx->params.nelts; i++) {
+ *b->last++ = ' ';
+ b->last = ngx_cpymem(b->last, param[i].key.data,
+ param[i].key.len);
+ *b->last++ = '=';
+ *b->last++ = '"';
+ b->last = ngx_cpymem(b->last, param[i].value.data,
+ param[i].value.len);
+ *b->last++ = '"';
+ }
+
+ *b->last++ = ' ';
+ *b->last++ = '-';
+ *b->last++ = '-';
+ *b->last++ = '>';
+
+ mctx = ngx_http_get_module_ctx(r->main,
+ ngx_http_ssi_filter_module);
+ bl = mctx->blocks->elts;
+ for (ll = &bl[mctx->blocks->nelts - 1].bufs;
+ *ll;
+ ll = &(*ll)->next)
+ {
+ /* void */
+ }
+
+ *ll = cl;
+
+ b = NULL;
+
+ continue;
+ }
+
+ if (cmd->conditional == 0) {
+ continue;
+ }
+ }
+
+ if (cmd->conditional
+ && (ctx->conditional == 0
+ || ctx->conditional > cmd->conditional))
+ {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid context of SSI command: \"%V\"",
+ &ctx->command);
+ goto ssi_error;
+ }
+
+ if (ctx->params.nelts > NGX_HTTP_SSI_MAX_PARAMS) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "too many SSI command parameters: \"%V\"",
+ &ctx->command);
+ goto ssi_error;
+ }
+
+ ngx_memzero(params,
+ (NGX_HTTP_SSI_MAX_PARAMS + 1) * sizeof(ngx_str_t *));
+
+ param = ctx->params.elts;
+
+ for (i = 0; i < ctx->params.nelts; i++) {
+
+ for (prm = cmd->params; prm->name.len; prm++) {
+
+ if (param[i].key.len != prm->name.len
+ || ngx_strncmp(param[i].key.data, prm->name.data,
+ prm->name.len) != 0)
+ {
+ continue;
+ }
+
+ if (!prm->multiple) {
+ if (params[prm->index]) {
+ ngx_log_error(NGX_LOG_ERR,
+ r->connection->log, 0,
+ "duplicate \"%V\" parameter "
+ "in \"%V\" SSI command",
+ &param[i].key, &ctx->command);
+
+ goto ssi_error;
+ }
+
+ params[prm->index] = &param[i].value;
+
+ break;
+ }
+
+ for (index = prm->index; params[index]; index++) {
+ /* void */
+ }
+
+ params[index] = &param[i].value;
+
+ break;
+ }
+
+ if (prm->name.len == 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid parameter name: \"%V\" "
+ "in \"%V\" SSI command",
+ &param[i].key, &ctx->command);
+
+ goto ssi_error;
+ }
+ }
+
+ for (prm = cmd->params; prm->name.len; prm++) {
+ if (prm->mandatory && params[prm->index] == 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "mandatory \"%V\" parameter is absent "
+ "in \"%V\" SSI command",
+ &prm->name, &ctx->command);
+
+ goto ssi_error;
+ }
+ }
+
+ if (cmd->flush && ctx->out) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi flush");
+
+ if (ngx_http_ssi_output(r, ctx) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+
+ rc = cmd->handler(r, ctx, params);
+
+ if (rc == NGX_OK) {
+ continue;
+ }
+
+ if (rc == NGX_DONE || rc == NGX_AGAIN || rc == NGX_ERROR) {
+ ngx_http_ssi_buffered(r, ctx);
+ return rc;
+ }
+ }
+
+
+ /* rc == NGX_HTTP_SSI_ERROR */
+
+ ssi_error:
+
+ if (slcf->silent_errors) {
+ continue;
+ }
+
+ if (ctx->free) {
+ cl = ctx->free;
+ ctx->free = ctx->free->next;
+ b = cl->buf;
+ ngx_memzero(b, sizeof(ngx_buf_t));
+
+ } else {
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ }
+
+ b->memory = 1;
+ b->pos = ctx->errmsg.data;
+ b->last = ctx->errmsg.data + ctx->errmsg.len;
+
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ continue;
+ }
+
+ if (ctx->buf->last_buf || ngx_buf_in_memory(ctx->buf)) {
+ if (b == NULL) {
+ if (ctx->free) {
+ cl = ctx->free;
+ ctx->free = ctx->free->next;
+ b = cl->buf;
+ ngx_memzero(b, sizeof(ngx_buf_t));
+
+ } else {
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ }
+
+ b->sync = 1;
+
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+ }
+
+ b->last_buf = ctx->buf->last_buf;
+ b->shadow = ctx->buf;
+
+ if (slcf->ignore_recycled_buffers == 0) {
+ b->recycled = ctx->buf->recycled;
+ }
+ }
+
+ ctx->buf = NULL;
+
+ ctx->saved = ctx->looked;
+ }
+
+ if (ctx->out == NULL && ctx->busy == NULL) {
+ return NGX_OK;
+ }
+
+ return ngx_http_ssi_output(r, ctx);
+}
+
+
+static ngx_int_t
+ngx_http_ssi_output(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx)
+{
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+
+#if 1
+ b = NULL;
+ for (cl = ctx->out; cl; cl = cl->next) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi out: %p %p", cl->buf, cl->buf->pos);
+ if (cl->buf == b) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "the same buf was used in ssi");
+ ngx_debug_point();
+ return NGX_ERROR;
+ }
+ b = cl->buf;
+ }
+#endif
+
+ rc = ngx_http_next_body_filter(r, ctx->out);
+
+ if (ctx->busy == NULL) {
+ ctx->busy = ctx->out;
+
+ } else {
+ for (cl = ctx->busy; cl->next; cl = cl->next) { /* void */ }
+ cl->next = ctx->out;
+ }
+
+ ctx->out = NULL;
+ ctx->last_out = &ctx->out;
+
+ while (ctx->busy) {
+
+ cl = ctx->busy;
+ b = cl->buf;
+
+ if (ngx_buf_size(b) != 0) {
+ break;
+ }
+
+ if (b->shadow) {
+ b->shadow->pos = b->shadow->last;
+ }
+
+ ctx->busy = cl->next;
+
+ if (ngx_buf_in_memory(b) || b->in_file) {
+ /* add data bufs only to the free buf chain */
+
+ cl->next = ctx->free;
+ ctx->free = cl;
+ }
+ }
+
+ ngx_http_ssi_buffered(r, ctx);
+
+ return rc;
+}
+
+
+static void
+ngx_http_ssi_buffered(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx)
+{
+ if (ctx->in || ctx->buf) {
+ r->buffered |= NGX_HTTP_SSI_BUFFERED;
+
+ } else {
+ r->buffered &= ~NGX_HTTP_SSI_BUFFERED;
+ }
+}
+
+
+static ngx_int_t
+ngx_http_ssi_parse(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx)
+{
+ u_char *p, *value, *last, *copy_end, ch;
+ size_t looked;
+ ngx_http_ssi_state_e state;
+
+ state = ctx->state;
+ looked = ctx->looked;
+ last = ctx->buf->last;
+ copy_end = ctx->copy_end;
+
+ for (p = ctx->pos; p < last; p++) {
+
+ ch = *p;
+
+ if (state == ssi_start_state) {
+
+ /* the tight loop */
+
+ for ( ;; ) {
+ if (ch == '<') {
+ copy_end = p;
+ looked = 1;
+ state = ssi_tag_state;
+
+ goto tag_started;
+ }
+
+ if (++p == last) {
+ break;
+ }
+
+ ch = *p;
+ }
+
+ ctx->state = state;
+ ctx->pos = p;
+ ctx->looked = looked;
+ ctx->copy_end = p;
+
+ if (ctx->copy_start == NULL) {
+ ctx->copy_start = ctx->buf->pos;
+ }
+
+ return NGX_AGAIN;
+
+ tag_started:
+
+ continue;
+ }
+
+ switch (state) {
+
+ case ssi_start_state:
+ /* not reached */
+ break;
+
+ case ssi_tag_state:
+ switch (ch) {
+ case '!':
+ looked = 2;
+ state = ssi_comment0_state;
+ break;
+
+ case '<':
+ copy_end = p;
+ break;
+
+ default:
+ copy_end = p;
+ looked = 0;
+ state = ssi_start_state;
+ break;
+ }
+
+ break;
+
+ case ssi_comment0_state:
+ switch (ch) {
+ case '-':
+ looked = 3;
+ state = ssi_comment1_state;
+ break;
+
+ case '<':
+ copy_end = p;
+ looked = 1;
+ state = ssi_tag_state;
+ break;
+
+ default:
+ copy_end = p;
+ looked = 0;
+ state = ssi_start_state;
+ break;
+ }
+
+ break;
+
+ case ssi_comment1_state:
+ switch (ch) {
+ case '-':
+ looked = 4;
+ state = ssi_sharp_state;
+ break;
+
+ case '<':
+ copy_end = p;
+ looked = 1;
+ state = ssi_tag_state;
+ break;
+
+ default:
+ copy_end = p;
+ looked = 0;
+ state = ssi_start_state;
+ break;
+ }
+
+ break;
+
+ case ssi_sharp_state:
+ switch (ch) {
+ case '#':
+ if (p - ctx->pos < 4) {
+ ctx->saved = 0;
+ }
+ looked = 0;
+ state = ssi_precommand_state;
+ break;
+
+ case '<':
+ copy_end = p;
+ looked = 1;
+ state = ssi_tag_state;
+ break;
+
+ default:
+ copy_end = p;
+ looked = 0;
+ state = ssi_start_state;
+ break;
+ }
+
+ break;
+
+ case ssi_precommand_state:
+ switch (ch) {
+ case ' ':
+ case CR:
+ case LF:
+ case '\t':
+ break;
+
+ default:
+ ctx->command.len = 1;
+ ctx->command.data = ngx_pnalloc(r->pool,
+ NGX_HTTP_SSI_COMMAND_LEN);
+ if (ctx->command.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->command.data[0] = ch;
+
+ ctx->key = 0;
+ ctx->key = ngx_hash(ctx->key, ch);
+
+ ctx->params.nelts = 0;
+
+ state = ssi_command_state;
+ break;
+ }
+
+ break;
+
+ case ssi_command_state:
+ switch (ch) {
+ case ' ':
+ case CR:
+ case LF:
+ case '\t':
+ state = ssi_preparam_state;
+ break;
+
+ case '-':
+ state = ssi_comment_end0_state;
+ break;
+
+ default:
+ if (ctx->command.len == NGX_HTTP_SSI_COMMAND_LEN) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "the \"%V%c...\" SSI command is too long",
+ &ctx->command, ch);
+
+ state = ssi_error_state;
+ break;
+ }
+
+ ctx->command.data[ctx->command.len++] = ch;
+ ctx->key = ngx_hash(ctx->key, ch);
+ }
+
+ break;
+
+ case ssi_preparam_state:
+ switch (ch) {
+ case ' ':
+ case CR:
+ case LF:
+ case '\t':
+ break;
+
+ case '-':
+ state = ssi_comment_end0_state;
+ break;
+
+ default:
+ ctx->param = ngx_array_push(&ctx->params);
+ if (ctx->param == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->param->key.len = 1;
+ ctx->param->key.data = ngx_pnalloc(r->pool,
+ NGX_HTTP_SSI_PARAM_LEN);
+ if (ctx->param->key.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->param->key.data[0] = ch;
+
+ ctx->param->value.len = 0;
+
+ if (ctx->value_buf == NULL) {
+ ctx->param->value.data = ngx_pnalloc(r->pool,
+ ctx->value_len + 1);
+ if (ctx->param->value.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ } else {
+ ctx->param->value.data = ctx->value_buf;
+ }
+
+ state = ssi_param_state;
+ break;
+ }
+
+ break;
+
+ case ssi_param_state:
+ switch (ch) {
+ case ' ':
+ case CR:
+ case LF:
+ case '\t':
+ state = ssi_preequal_state;
+ break;
+
+ case '=':
+ state = ssi_prevalue_state;
+ break;
+
+ case '-':
+ state = ssi_error_end0_state;
+
+ ctx->param->key.data[ctx->param->key.len++] = ch;
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid \"%V\" parameter in \"%V\" SSI command",
+ &ctx->param->key, &ctx->command);
+ break;
+
+ default:
+ if (ctx->param->key.len == NGX_HTTP_SSI_PARAM_LEN) {
+ state = ssi_error_state;
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "too long \"%V%c...\" parameter in "
+ "\"%V\" SSI command",
+ &ctx->param->key, ch, &ctx->command);
+ break;
+ }
+
+ ctx->param->key.data[ctx->param->key.len++] = ch;
+ }
+
+ break;
+
+ case ssi_preequal_state:
+ switch (ch) {
+ case ' ':
+ case CR:
+ case LF:
+ case '\t':
+ break;
+
+ case '=':
+ state = ssi_prevalue_state;
+ break;
+
+ default:
+ if (ch == '-') {
+ state = ssi_error_end0_state;
+ } else {
+ state = ssi_error_state;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "unexpected \"%c\" symbol after \"%V\" "
+ "parameter in \"%V\" SSI command",
+ ch, &ctx->param->key, &ctx->command);
+ break;
+ }
+
+ break;
+
+ case ssi_prevalue_state:
+ switch (ch) {
+ case ' ':
+ case CR:
+ case LF:
+ case '\t':
+ break;
+
+ case '"':
+ state = ssi_double_quoted_value_state;
+ break;
+
+ case '\'':
+ state = ssi_quoted_value_state;
+ break;
+
+ default:
+ if (ch == '-') {
+ state = ssi_error_end0_state;
+ } else {
+ state = ssi_error_state;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "unexpected \"%c\" symbol before value of "
+ "\"%V\" parameter in \"%V\" SSI command",
+ ch, &ctx->param->key, &ctx->command);
+ break;
+ }
+
+ break;
+
+ case ssi_double_quoted_value_state:
+ switch (ch) {
+ case '"':
+ state = ssi_postparam_state;
+ break;
+
+ case '\\':
+ ctx->saved_state = ssi_double_quoted_value_state;
+ state = ssi_quoted_symbol_state;
+
+ /* fall through */
+
+ default:
+ if (ctx->param->value.len == ctx->value_len) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "too long \"%V%c...\" value of \"%V\" "
+ "parameter in \"%V\" SSI command",
+ &ctx->param->value, ch, &ctx->param->key,
+ &ctx->command);
+ state = ssi_error_state;
+ break;
+ }
+
+ ctx->param->value.data[ctx->param->value.len++] = ch;
+ }
+
+ break;
+
+ case ssi_quoted_value_state:
+ switch (ch) {
+ case '\'':
+ state = ssi_postparam_state;
+ break;
+
+ case '\\':
+ ctx->saved_state = ssi_quoted_value_state;
+ state = ssi_quoted_symbol_state;
+
+ /* fall through */
+
+ default:
+ if (ctx->param->value.len == ctx->value_len) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "too long \"%V%c...\" value of \"%V\" "
+ "parameter in \"%V\" SSI command",
+ &ctx->param->value, ch, &ctx->param->key,
+ &ctx->command);
+ state = ssi_error_state;
+ break;
+ }
+
+ ctx->param->value.data[ctx->param->value.len++] = ch;
+ }
+
+ break;
+
+ case ssi_quoted_symbol_state:
+ state = ctx->saved_state;
+
+ if (ctx->param->value.len == ctx->value_len) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "too long \"%V%c...\" value of \"%V\" "
+ "parameter in \"%V\" SSI command",
+ &ctx->param->value, ch, &ctx->param->key,
+ &ctx->command);
+ state = ssi_error_state;
+ break;
+ }
+
+ ctx->param->value.data[ctx->param->value.len++] = ch;
+
+ break;
+
+ case ssi_postparam_state:
+
+ if (ctx->param->value.len + 1 < ctx->value_len / 2) {
+ value = ngx_pnalloc(r->pool, ctx->param->value.len + 1);
+ if (value == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(value, ctx->param->value.data,
+ ctx->param->value.len);
+
+ ctx->value_buf = ctx->param->value.data;
+ ctx->param->value.data = value;
+
+ } else {
+ ctx->value_buf = NULL;
+ }
+
+ switch (ch) {
+ case ' ':
+ case CR:
+ case LF:
+ case '\t':
+ state = ssi_preparam_state;
+ break;
+
+ case '-':
+ state = ssi_comment_end0_state;
+ break;
+
+ default:
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "unexpected \"%c\" symbol after \"%V\" value "
+ "of \"%V\" parameter in \"%V\" SSI command",
+ ch, &ctx->param->value, &ctx->param->key,
+ &ctx->command);
+ state = ssi_error_state;
+ break;
+ }
+
+ break;
+
+ case ssi_comment_end0_state:
+ switch (ch) {
+ case '-':
+ state = ssi_comment_end1_state;
+ break;
+
+ default:
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "unexpected \"%c\" symbol in \"%V\" SSI command",
+ ch, &ctx->command);
+ state = ssi_error_state;
+ break;
+ }
+
+ break;
+
+ case ssi_comment_end1_state:
+ switch (ch) {
+ case '>':
+ ctx->state = ssi_start_state;
+ ctx->pos = p + 1;
+ ctx->looked = looked;
+ ctx->copy_end = copy_end;
+
+ if (ctx->copy_start == NULL && copy_end) {
+ ctx->copy_start = ctx->buf->pos;
+ }
+
+ return NGX_OK;
+
+ default:
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "unexpected \"%c\" symbol in \"%V\" SSI command",
+ ch, &ctx->command);
+ state = ssi_error_state;
+ break;
+ }
+
+ break;
+
+ case ssi_error_state:
+ switch (ch) {
+ case '-':
+ state = ssi_error_end0_state;
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ case ssi_error_end0_state:
+ switch (ch) {
+ case '-':
+ state = ssi_error_end1_state;
+ break;
+
+ default:
+ state = ssi_error_state;
+ break;
+ }
+
+ break;
+
+ case ssi_error_end1_state:
+ switch (ch) {
+ case '>':
+ ctx->state = ssi_start_state;
+ ctx->pos = p + 1;
+ ctx->looked = looked;
+ ctx->copy_end = copy_end;
+
+ if (ctx->copy_start == NULL && copy_end) {
+ ctx->copy_start = ctx->buf->pos;
+ }
+
+ return NGX_HTTP_SSI_ERROR;
+
+ default:
+ state = ssi_error_state;
+ break;
+ }
+
+ break;
+ }
+ }
+
+ ctx->state = state;
+ ctx->pos = p;
+ ctx->looked = looked;
+
+ ctx->copy_end = (state == ssi_start_state) ? p : copy_end;
+
+ if (ctx->copy_start == NULL && ctx->copy_end) {
+ ctx->copy_start = ctx->buf->pos;
+ }
+
+ return NGX_AGAIN;
+}
+
+
+static ngx_str_t *
+ngx_http_ssi_get_variable(ngx_http_request_t *r, ngx_str_t *name,
+ ngx_uint_t key)
+{
+ ngx_uint_t i;
+ ngx_list_part_t *part;
+ ngx_http_ssi_var_t *var;
+ ngx_http_ssi_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
+
+#if (NGX_PCRE)
+ {
+ ngx_str_t *value;
+
+ if (key >= '0' && key <= '9') {
+ i = key - '0';
+
+ if (i < ctx->ncaptures) {
+ value = ngx_palloc(r->pool, sizeof(ngx_str_t));
+ if (value == NULL) {
+ return NULL;
+ }
+
+ i *= 2;
+
+ value->data = ctx->captures_data + ctx->captures[i];
+ value->len = ctx->captures[i + 1] - ctx->captures[i];
+
+ return value;
+ }
+ }
+ }
+#endif
+
+ if (ctx->variables == NULL) {
+ return NULL;
+ }
+
+ part = &ctx->variables->part;
+ var = part->elts;
+
+ for (i = 0; /* void */ ; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ var = part->elts;
+ i = 0;
+ }
+
+ if (name->len != var[i].name.len) {
+ continue;
+ }
+
+ if (key != var[i].key) {
+ continue;
+ }
+
+ if (ngx_strncmp(name->data, var[i].name.data, name->len) == 0) {
+ return &var[i].value;
+ }
+ }
+
+ return NULL;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_evaluate_string(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t *text, ngx_uint_t flags)
+{
+ u_char ch, *p, **value, *data, *part_data;
+ size_t *size, len, prefix, part_len;
+ ngx_str_t var, *val;
+ ngx_int_t key;
+ ngx_uint_t i, n, bracket, quoted;
+ ngx_array_t lengths, values;
+ ngx_http_variable_value_t *vv;
+
+ n = ngx_http_script_variables_count(text);
+
+ if (n == 0) {
+
+ data = text->data;
+ p = data;
+
+ if ((flags & NGX_HTTP_SSI_ADD_PREFIX) && text->data[0] != '/') {
+
+ for (prefix = r->uri.len; prefix; prefix--) {
+ if (r->uri.data[prefix - 1] == '/') {
+ break;
+ }
+ }
+
+ if (prefix) {
+ len = prefix + text->len;
+
+ data = ngx_pnalloc(r->pool, len);
+ if (data == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_copy(data, r->uri.data, prefix);
+ }
+ }
+
+ quoted = 0;
+
+ for (i = 0; i < text->len; i++) {
+ ch = text->data[i];
+
+ if (!quoted) {
+
+ if (ch == '\\') {
+ quoted = 1;
+ continue;
+ }
+
+ } else {
+ quoted = 0;
+
+ if (ch != '\\' && ch != '\'' && ch != '"' && ch != '$') {
+ *p++ = '\\';
+ }
+ }
+
+ *p++ = ch;
+ }
+
+ text->len = p - data;
+ text->data = data;
+
+ return NGX_OK;
+ }
+
+ if (ngx_array_init(&lengths, r->pool, 8, sizeof(size_t *)) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_array_init(&values, r->pool, 8, sizeof(u_char *)) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ len = 0;
+ i = 0;
+
+ while (i < text->len) {
+
+ if (text->data[i] == '$') {
+
+ var.len = 0;
+
+ if (++i == text->len) {
+ goto invalid_variable;
+ }
+
+ if (text->data[i] == '{') {
+ bracket = 1;
+
+ if (++i == text->len) {
+ goto invalid_variable;
+ }
+
+ var.data = &text->data[i];
+
+ } else {
+ bracket = 0;
+ var.data = &text->data[i];
+ }
+
+ for ( /* void */ ; i < text->len; i++, var.len++) {
+ ch = text->data[i];
+
+ if (ch == '}' && bracket) {
+ i++;
+ bracket = 0;
+ break;
+ }
+
+ if ((ch >= 'A' && ch <= 'Z')
+ || (ch >= 'a' && ch <= 'z')
+ || (ch >= '0' && ch <= '9')
+ || ch == '_')
+ {
+ continue;
+ }
+
+ break;
+ }
+
+ if (bracket) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "the closing bracket in \"%V\" "
+ "variable is missing", &var);
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ if (var.len == 0) {
+ goto invalid_variable;
+ }
+
+ key = ngx_hash_strlow(var.data, var.data, var.len);
+
+ val = ngx_http_ssi_get_variable(r, &var, key);
+
+ if (val == NULL) {
+ vv = ngx_http_get_variable(r, &var, key);
+ if (vv == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (vv->not_found) {
+ continue;
+ }
+
+ part_data = vv->data;
+ part_len = vv->len;
+
+ } else {
+ part_data = val->data;
+ part_len = val->len;
+ }
+
+ } else {
+ part_data = &text->data[i];
+ quoted = 0;
+
+ for (p = part_data; i < text->len; i++) {
+ ch = text->data[i];
+
+ if (!quoted) {
+
+ if (ch == '\\') {
+ quoted = 1;
+ continue;
+ }
+
+ if (ch == '$') {
+ break;
+ }
+
+ } else {
+ quoted = 0;
+
+ if (ch != '\\' && ch != '\'' && ch != '"' && ch != '$') {
+ *p++ = '\\';
+ }
+ }
+
+ *p++ = ch;
+ }
+
+ part_len = p - part_data;
+ }
+
+ len += part_len;
+
+ size = ngx_array_push(&lengths);
+ if (size == NULL) {
+ return NGX_ERROR;
+ }
+
+ *size = part_len;
+
+ value = ngx_array_push(&values);
+ if (value == NULL) {
+ return NGX_ERROR;
+ }
+
+ *value = part_data;
+ }
+
+ prefix = 0;
+
+ size = lengths.elts;
+ value = values.elts;
+
+ if (flags & NGX_HTTP_SSI_ADD_PREFIX) {
+ for (i = 0; i < values.nelts; i++) {
+ if (size[i] != 0) {
+ if (*value[i] != '/') {
+ for (prefix = r->uri.len; prefix; prefix--) {
+ if (r->uri.data[prefix - 1] == '/') {
+ len += prefix;
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+ }
+ }
+
+ p = ngx_pnalloc(r->pool, len + ((flags & NGX_HTTP_SSI_ADD_ZERO) ? 1 : 0));
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ text->len = len;
+ text->data = p;
+
+ p = ngx_copy(p, r->uri.data, prefix);
+
+ for (i = 0; i < values.nelts; i++) {
+ p = ngx_copy(p, value[i], size[i]);
+ }
+
+ return NGX_OK;
+
+invalid_variable:
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid variable name in \"%V\"", text);
+
+ return NGX_HTTP_SSI_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_regex_match(ngx_http_request_t *r, ngx_str_t *pattern,
+ ngx_str_t *str)
+{
+#if (NGX_PCRE)
+ int rc, *captures;
+ u_char *p, errstr[NGX_MAX_CONF_ERRSTR];
+ size_t size;
+ ngx_int_t key;
+ ngx_str_t *vv, name, value;
+ ngx_uint_t i, n;
+ ngx_http_ssi_ctx_t *ctx;
+ ngx_http_ssi_var_t *var;
+ ngx_regex_compile_t rgc;
+
+ ngx_memzero(&rgc, sizeof(ngx_regex_compile_t));
+
+ rgc.pattern = *pattern;
+ rgc.pool = r->pool;
+ rgc.err.len = NGX_MAX_CONF_ERRSTR;
+ rgc.err.data = errstr;
+
+ if (ngx_regex_compile(&rgc) != NGX_OK) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "%V", &rgc.err);
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ n = (rgc.captures + 1) * 3;
+
+ captures = ngx_palloc(r->pool, n * sizeof(int));
+ if (captures == NULL) {
+ return NGX_ERROR;
+ }
+
+ rc = ngx_regex_exec(rgc.regex, str, captures, n);
+
+ if (rc < NGX_REGEX_NO_MATCHED) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ ngx_regex_exec_n " failed: %d on \"%V\" using \"%V\"",
+ rc, str, pattern);
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ if (rc == NGX_REGEX_NO_MATCHED) {
+ return NGX_DECLINED;
+ }
+
+ ctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
+
+ ctx->ncaptures = rc;
+ ctx->captures = captures;
+ ctx->captures_data = str->data;
+
+ if (rgc.named_captures > 0) {
+
+ if (ctx->variables == NULL) {
+ ctx->variables = ngx_list_create(r->pool, 4,
+ sizeof(ngx_http_ssi_var_t));
+ if (ctx->variables == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ size = rgc.name_size;
+ p = rgc.names;
+
+ for (i = 0; i < (ngx_uint_t) rgc.named_captures; i++, p += size) {
+
+ name.data = &p[2];
+ name.len = ngx_strlen(name.data);
+
+ n = 2 * ((p[0] << 8) + p[1]);
+
+ value.data = &str->data[captures[n]];
+ value.len = captures[n + 1] - captures[n];
+
+ key = ngx_hash_strlow(name.data, name.data, name.len);
+
+ vv = ngx_http_ssi_get_variable(r, &name, key);
+
+ if (vv) {
+ *vv = value;
+ continue;
+ }
+
+ var = ngx_list_push(ctx->variables);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->name = name;
+ var->key = key;
+ var->value = value;
+ }
+ }
+
+ return NGX_OK;
+
+#else
+
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "the using of the regex \"%V\" in SSI requires PCRE library",
+ pattern);
+ return NGX_HTTP_SSI_ERROR;
+
+#endif
+}
+
+
+static ngx_int_t
+ngx_http_ssi_include(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ ngx_int_t rc, key;
+ ngx_str_t *uri, *file, *wait, *set, *stub, args;
+ ngx_buf_t *b;
+ ngx_uint_t flags, i;
+ ngx_chain_t *cl, *tl, **ll, *out;
+ ngx_http_request_t *sr;
+ ngx_http_ssi_var_t *var;
+ ngx_http_ssi_ctx_t *mctx;
+ ngx_http_ssi_block_t *bl;
+ ngx_http_post_subrequest_t *psr;
+
+ uri = params[NGX_HTTP_SSI_INCLUDE_VIRTUAL];
+ file = params[NGX_HTTP_SSI_INCLUDE_FILE];
+ wait = params[NGX_HTTP_SSI_INCLUDE_WAIT];
+ set = params[NGX_HTTP_SSI_INCLUDE_SET];
+ stub = params[NGX_HTTP_SSI_INCLUDE_STUB];
+
+ if (uri && file) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "inclusion may be either virtual=\"%V\" or file=\"%V\"",
+ uri, file);
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ if (uri == NULL && file == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "no parameter in \"include\" SSI command");
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ if (set && stub) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "\"set\" and \"stub\" cannot be used together "
+ "in \"include\" SSI command");
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ if (wait) {
+ if (uri == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "\"wait\" cannot be used with file=\"%V\"", file);
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ if (wait->len == 2
+ && ngx_strncasecmp(wait->data, (u_char *) "no", 2) == 0)
+ {
+ wait = NULL;
+
+ } else if (wait->len != 3
+ || ngx_strncasecmp(wait->data, (u_char *) "yes", 3) != 0)
+ {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid value \"%V\" in the \"wait\" parameter",
+ wait);
+ return NGX_HTTP_SSI_ERROR;
+ }
+ }
+
+ if (uri == NULL) {
+ uri = file;
+ wait = (ngx_str_t *) -1;
+ }
+
+ rc = ngx_http_ssi_evaluate_string(r, ctx, uri, NGX_HTTP_SSI_ADD_PREFIX);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi include: \"%V\"", uri);
+
+ ngx_str_null(&args);
+ flags = NGX_HTTP_LOG_UNSAFE;
+
+ if (ngx_http_parse_unsafe_uri(r, uri, &args, &flags) != NGX_OK) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ psr = NULL;
+
+ mctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
+
+ if (stub) {
+ if (mctx->blocks) {
+ bl = mctx->blocks->elts;
+ for (i = 0; i < mctx->blocks->nelts; i++) {
+ if (stub->len == bl[i].name.len
+ && ngx_strncmp(stub->data, bl[i].name.data, stub->len) == 0)
+ {
+ goto found;
+ }
+ }
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "\"stub\"=\"%V\" for \"include\" not found", stub);
+ return NGX_HTTP_SSI_ERROR;
+
+ found:
+
+ psr = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
+ if (psr == NULL) {
+ return NGX_ERROR;
+ }
+
+ psr->handler = ngx_http_ssi_stub_output;
+
+ if (bl[i].count++) {
+
+ out = NULL;
+ ll = &out;
+
+ for (tl = bl[i].bufs; tl; tl = tl->next) {
+
+ if (ctx->free) {
+ cl = ctx->free;
+ ctx->free = ctx->free->next;
+ b = cl->buf;
+
+ } else {
+ b = ngx_alloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ }
+
+ ngx_memcpy(b, tl->buf, sizeof(ngx_buf_t));
+
+ b->pos = b->start;
+
+ *ll = cl;
+ cl->next = NULL;
+ ll = &cl->next;
+ }
+
+ psr->data = out;
+
+ } else {
+ psr->data = bl[i].bufs;
+ }
+ }
+
+ if (wait) {
+ flags |= NGX_HTTP_SUBREQUEST_WAITED;
+ }
+
+ if (set) {
+ key = ngx_hash_strlow(set->data, set->data, set->len);
+
+ psr = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
+ if (psr == NULL) {
+ return NGX_ERROR;
+ }
+
+ psr->handler = ngx_http_ssi_set_variable;
+ psr->data = ngx_http_ssi_get_variable(r, set, key);
+
+ if (psr->data == NULL) {
+
+ if (mctx->variables == NULL) {
+ mctx->variables = ngx_list_create(r->pool, 4,
+ sizeof(ngx_http_ssi_var_t));
+ if (mctx->variables == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ var = ngx_list_push(mctx->variables);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->name = *set;
+ var->key = key;
+ var->value = ngx_http_ssi_null_string;
+ psr->data = &var->value;
+ }
+
+ flags |= NGX_HTTP_SUBREQUEST_IN_MEMORY|NGX_HTTP_SUBREQUEST_WAITED;
+ }
+
+ if (ngx_http_subrequest(r, uri, &args, &sr, psr, flags) != NGX_OK) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ if (wait == NULL && set == NULL) {
+ return NGX_OK;
+ }
+
+ if (ctx->wait == NULL) {
+ ctx->wait = sr;
+
+ return NGX_AGAIN;
+
+ } else {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "can only wait for one subrequest at a time");
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_stub_output(ngx_http_request_t *r, void *data, ngx_int_t rc)
+{
+ ngx_chain_t *out;
+
+ if (rc == NGX_ERROR || r->connection->error || r->request_output) {
+ return rc;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi stub output: \"%V?%V\"", &r->uri, &r->args);
+
+ out = data;
+
+ if (!r->header_sent) {
+ r->headers_out.content_type_len =
+ r->parent->headers_out.content_type_len;
+ r->headers_out.content_type = r->parent->headers_out.content_type;
+
+ if (ngx_http_send_header(r) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+
+ return ngx_http_output_filter(r, out);
+}
+
+
+static ngx_int_t
+ngx_http_ssi_set_variable(ngx_http_request_t *r, void *data, ngx_int_t rc)
+{
+ ngx_str_t *value = data;
+
+ if (r->upstream) {
+ value->len = r->upstream->buffer.last - r->upstream->buffer.pos;
+ value->data = r->upstream->buffer.pos;
+ }
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_echo(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ u_char *p;
+ uintptr_t len;
+ ngx_int_t key;
+ ngx_buf_t *b;
+ ngx_str_t *var, *value, *enc, text;
+ ngx_chain_t *cl;
+ ngx_http_variable_value_t *vv;
+
+ var = params[NGX_HTTP_SSI_ECHO_VAR];
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi echo \"%V\"", var);
+
+ key = ngx_hash_strlow(var->data, var->data, var->len);
+
+ value = ngx_http_ssi_get_variable(r, var, key);
+
+ if (value == NULL) {
+ vv = ngx_http_get_variable(r, var, key);
+
+ if (vv == NULL) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ if (!vv->not_found) {
+ text.data = vv->data;
+ text.len = vv->len;
+ value = &text;
+ }
+ }
+
+ if (value == NULL) {
+ value = params[NGX_HTTP_SSI_ECHO_DEFAULT];
+
+ if (value == NULL) {
+ value = &ngx_http_ssi_none;
+
+ } else if (value->len == 0) {
+ return NGX_OK;
+ }
+
+ } else {
+ if (value->len == 0) {
+ return NGX_OK;
+ }
+ }
+
+ enc = params[NGX_HTTP_SSI_ECHO_ENCODING];
+
+ if (enc) {
+ if (enc->len == 4 && ngx_strncmp(enc->data, "none", 4) == 0) {
+
+ ctx->encoding = NGX_HTTP_SSI_NO_ENCODING;
+
+ } else if (enc->len == 3 && ngx_strncmp(enc->data, "url", 3) == 0) {
+
+ ctx->encoding = NGX_HTTP_SSI_URL_ENCODING;
+
+ } else if (enc->len == 6 && ngx_strncmp(enc->data, "entity", 6) == 0) {
+
+ ctx->encoding = NGX_HTTP_SSI_ENTITY_ENCODING;
+
+ } else {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "unknown encoding \"%V\" in the \"echo\" command",
+ enc);
+ }
+ }
+
+ p = value->data;
+
+ switch (ctx->encoding) {
+
+ case NGX_HTTP_SSI_URL_ENCODING:
+ len = 2 * ngx_escape_uri(NULL, value->data, value->len,
+ NGX_ESCAPE_HTML);
+
+ if (len) {
+ p = ngx_pnalloc(r->pool, value->len + len);
+ if (p == NULL) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ (void) ngx_escape_uri(p, value->data, value->len, NGX_ESCAPE_HTML);
+ }
+
+ len += value->len;
+ break;
+
+ case NGX_HTTP_SSI_ENTITY_ENCODING:
+ len = ngx_escape_html(NULL, value->data, value->len);
+
+ if (len) {
+ p = ngx_pnalloc(r->pool, value->len + len);
+ if (p == NULL) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ (void) ngx_escape_html(p, value->data, value->len);
+ }
+
+ len += value->len;
+ break;
+
+ default: /* NGX_HTTP_SSI_NO_ENCODING */
+ len = value->len;
+ break;
+ }
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ b->memory = 1;
+ b->pos = p;
+ b->last = p + len;
+
+ cl->buf = b;
+ cl->next = NULL;
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_config(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ ngx_str_t *value;
+
+ value = params[NGX_HTTP_SSI_CONFIG_TIMEFMT];
+
+ if (value) {
+ ctx->timefmt.len = value->len;
+ ctx->timefmt.data = ngx_pnalloc(r->pool, value->len + 1);
+ if (ctx->timefmt.data == NULL) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ ngx_cpystrn(ctx->timefmt.data, value->data, value->len + 1);
+ }
+
+ value = params[NGX_HTTP_SSI_CONFIG_ERRMSG];
+
+ if (value) {
+ ctx->errmsg = *value;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_set(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ ngx_int_t key, rc;
+ ngx_str_t *name, *value, *vv;
+ ngx_http_ssi_var_t *var;
+ ngx_http_ssi_ctx_t *mctx;
+
+ mctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
+
+ if (mctx->variables == NULL) {
+ mctx->variables = ngx_list_create(r->pool, 4,
+ sizeof(ngx_http_ssi_var_t));
+ if (mctx->variables == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ name = params[NGX_HTTP_SSI_SET_VAR];
+ value = params[NGX_HTTP_SSI_SET_VALUE];
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi set \"%V\" \"%V\"", name, value);
+
+ rc = ngx_http_ssi_evaluate_string(r, ctx, value, 0);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ key = ngx_hash_strlow(name->data, name->data, name->len);
+
+ vv = ngx_http_ssi_get_variable(r, name, key);
+
+ if (vv) {
+ *vv = *value;
+ return NGX_OK;
+ }
+
+ var = ngx_list_push(mctx->variables);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->name = *name;
+ var->key = key;
+ var->value = *value;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "set: \"%V\"=\"%V\"", name, value);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_if(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ u_char *p, *last;
+ ngx_str_t *expr, left, right;
+ ngx_int_t rc;
+ ngx_uint_t negative, noregex, flags;
+
+ if (ctx->command.len == 2) {
+ if (ctx->conditional) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "the \"if\" command inside the \"if\" command");
+ return NGX_HTTP_SSI_ERROR;
+ }
+ }
+
+ if (ctx->output_chosen) {
+ ctx->output = 0;
+ return NGX_OK;
+ }
+
+ expr = params[NGX_HTTP_SSI_IF_EXPR];
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi if expr=\"%V\"", expr);
+
+ left.data = expr->data;
+ last = expr->data + expr->len;
+
+ for (p = left.data; p < last; p++) {
+ if (*p >= 'A' && *p <= 'Z') {
+ *p |= 0x20;
+ continue;
+ }
+
+ if ((*p >= 'a' && *p <= 'z')
+ || (*p >= '0' && *p <= '9')
+ || *p == '$' || *p == '{' || *p == '}' || *p == '_'
+ || *p == '"' || *p == '\'')
+ {
+ continue;
+ }
+
+ break;
+ }
+
+ left.len = p - left.data;
+
+ while (p < last && *p == ' ') {
+ p++;
+ }
+
+ flags = 0;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "left: \"%V\"", &left);
+
+ rc = ngx_http_ssi_evaluate_string(r, ctx, &left, flags);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "evaluated left: \"%V\"", &left);
+
+ if (p == last) {
+ if (left.len) {
+ ctx->output = 1;
+ ctx->output_chosen = 1;
+
+ } else {
+ ctx->output = 0;
+ }
+
+ ctx->conditional = NGX_HTTP_SSI_COND_IF;
+
+ return NGX_OK;
+ }
+
+ if (p < last && *p == '=') {
+ negative = 0;
+ p++;
+
+ } else if (p + 1 < last && *p == '!' && *(p + 1) == '=') {
+ negative = 1;
+ p += 2;
+
+ } else {
+ goto invalid_expression;
+ }
+
+ while (p < last && *p == ' ') {
+ p++;
+ }
+
+ if (p < last - 1 && *p == '/') {
+ if (*(last - 1) != '/') {
+ goto invalid_expression;
+ }
+
+ noregex = 0;
+ flags = NGX_HTTP_SSI_ADD_ZERO;
+ last--;
+ p++;
+
+ } else {
+ noregex = 1;
+ flags = 0;
+
+ if (p < last - 1 && p[0] == '\\' && p[1] == '/') {
+ p++;
+ }
+ }
+
+ right.len = last - p;
+ right.data = p;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "right: \"%V\"", &right);
+
+ rc = ngx_http_ssi_evaluate_string(r, ctx, &right, flags);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "evaluated right: \"%V\"", &right);
+
+ if (noregex) {
+ if (left.len != right.len) {
+ rc = -1;
+
+ } else {
+ rc = ngx_strncmp(left.data, right.data, right.len);
+ }
+
+ } else {
+ right.data[right.len] = '\0';
+
+ rc = ngx_http_ssi_regex_match(r, &right, &left);
+
+ if (rc == NGX_OK) {
+ rc = 0;
+ } else if (rc == NGX_DECLINED) {
+ rc = -1;
+ } else {
+ return rc;
+ }
+ }
+
+ if ((rc == 0 && !negative) || (rc != 0 && negative)) {
+ ctx->output = 1;
+ ctx->output_chosen = 1;
+
+ } else {
+ ctx->output = 0;
+ }
+
+ ctx->conditional = NGX_HTTP_SSI_COND_IF;
+
+ return NGX_OK;
+
+invalid_expression:
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid expression in \"%V\"", expr);
+
+ return NGX_HTTP_SSI_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_else(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi else");
+
+ if (ctx->output_chosen) {
+ ctx->output = 0;
+ } else {
+ ctx->output = 1;
+ }
+
+ ctx->conditional = NGX_HTTP_SSI_COND_ELSE;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_endif(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi endif");
+
+ ctx->output = 1;
+ ctx->output_chosen = 0;
+ ctx->conditional = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_block(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ ngx_http_ssi_ctx_t *mctx;
+ ngx_http_ssi_block_t *bl;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi block");
+
+ mctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
+
+ if (mctx->blocks == NULL) {
+ mctx->blocks = ngx_array_create(r->pool, 4,
+ sizeof(ngx_http_ssi_block_t));
+ if (mctx->blocks == NULL) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+ }
+
+ bl = ngx_array_push(mctx->blocks);
+ if (bl == NULL) {
+ return NGX_HTTP_SSI_ERROR;
+ }
+
+ bl->name = *params[NGX_HTTP_SSI_BLOCK_NAME];
+ bl->bufs = NULL;
+ bl->count = 0;
+
+ ctx->output = 0;
+ ctx->block = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_endblock(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
+ ngx_str_t **params)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "ssi endblock");
+
+ ctx->output = 1;
+ ctx->block = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_date_gmt_local_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t gmt)
+{
+ time_t now;
+ ngx_http_ssi_ctx_t *ctx;
+ ngx_str_t *timefmt;
+ struct tm tm;
+ char buf[NGX_HTTP_SSI_DATE_LEN];
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ now = ngx_time();
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_ssi_filter_module);
+
+ timefmt = ctx ? &ctx->timefmt : &ngx_http_ssi_timefmt;
+
+ if (timefmt->len == sizeof("%s") - 1
+ && timefmt->data[0] == '%' && timefmt->data[1] == 's')
+ {
+ v->data = ngx_pnalloc(r->pool, NGX_TIME_T_LEN);
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = ngx_sprintf(v->data, "%T", now) - v->data;
+
+ return NGX_OK;
+ }
+
+ if (gmt) {
+ ngx_libc_gmtime(now, &tm);
+ } else {
+ ngx_libc_localtime(now, &tm);
+ }
+
+ v->len = strftime(buf, NGX_HTTP_SSI_DATE_LEN,
+ (char *) timefmt->data, &tm);
+ if (v->len == 0) {
+ return NGX_ERROR;
+ }
+
+ v->data = ngx_pnalloc(r->pool, v->len);
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(v->data, buf, v->len);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_preconfiguration(ngx_conf_t *cf)
+{
+ ngx_int_t rc;
+ ngx_http_variable_t *var, *v;
+ ngx_http_ssi_command_t *cmd;
+ ngx_http_ssi_main_conf_t *smcf;
+
+ for (v = ngx_http_ssi_vars; v->name.len; v++) {
+ var = ngx_http_add_variable(cf, &v->name, v->flags);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = v->get_handler;
+ var->data = v->data;
+ }
+
+ smcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_ssi_filter_module);
+
+ for (cmd = ngx_http_ssi_commands; cmd->name.len; cmd++) {
+ rc = ngx_hash_add_key(&smcf->commands, &cmd->name, cmd,
+ NGX_HASH_READONLY_KEY);
+
+ if (rc == NGX_OK) {
+ continue;
+ }
+
+ if (rc == NGX_BUSY) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "conflicting SSI command \"%V\"", &cmd->name);
+ }
+
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_ssi_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_ssi_main_conf_t *smcf;
+
+ smcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssi_main_conf_t));
+ if (smcf == NULL) {
+ return NULL;
+ }
+
+ smcf->commands.pool = cf->pool;
+ smcf->commands.temp_pool = cf->temp_pool;
+
+ if (ngx_hash_keys_array_init(&smcf->commands, NGX_HASH_SMALL) != NGX_OK) {
+ return NULL;
+ }
+
+ return smcf;
+}
+
+
+static char *
+ngx_http_ssi_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+ ngx_http_ssi_main_conf_t *smcf = conf;
+
+ ngx_hash_init_t hash;
+
+ hash.hash = &smcf->hash;
+ hash.key = ngx_hash_key;
+ hash.max_size = 1024;
+ hash.bucket_size = ngx_cacheline_size;
+ hash.name = "ssi_command_hash";
+ hash.pool = cf->pool;
+ hash.temp_pool = NULL;
+
+ if (ngx_hash_init(&hash, smcf->commands.keys.elts,
+ smcf->commands.keys.nelts)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_ssi_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_ssi_loc_conf_t *slcf;
+
+ slcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssi_loc_conf_t));
+ if (slcf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->types = { NULL };
+ * conf->types_keys = NULL;
+ */
+
+ slcf->enable = NGX_CONF_UNSET;
+ slcf->silent_errors = NGX_CONF_UNSET;
+ slcf->ignore_recycled_buffers = NGX_CONF_UNSET;
+ slcf->last_modified = NGX_CONF_UNSET;
+
+ slcf->min_file_chunk = NGX_CONF_UNSET_SIZE;
+ slcf->value_len = NGX_CONF_UNSET_SIZE;
+
+ return slcf;
+}
+
+
+static char *
+ngx_http_ssi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_ssi_loc_conf_t *prev = parent;
+ ngx_http_ssi_loc_conf_t *conf = child;
+
+ ngx_conf_merge_value(conf->enable, prev->enable, 0);
+ ngx_conf_merge_value(conf->silent_errors, prev->silent_errors, 0);
+ ngx_conf_merge_value(conf->ignore_recycled_buffers,
+ prev->ignore_recycled_buffers, 0);
+ ngx_conf_merge_value(conf->last_modified, prev->last_modified, 0);
+
+ ngx_conf_merge_size_value(conf->min_file_chunk, prev->min_file_chunk, 1024);
+ ngx_conf_merge_size_value(conf->value_len, prev->value_len, 255);
+
+ if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
+ &prev->types_keys, &prev->types,
+ ngx_http_html_default_types)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssi_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_ssi_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_ssi_body_filter;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_ssi_filter_module.h b/app/nginx/src/http/modules/ngx_http_ssi_filter_module.h
new file mode 100644
index 0000000..0bd01a0
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_ssi_filter_module.h
@@ -0,0 +1,114 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_SSI_FILTER_H_INCLUDED_
+#define _NGX_HTTP_SSI_FILTER_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_SSI_MAX_PARAMS 16
+
+#define NGX_HTTP_SSI_COMMAND_LEN 32
+#define NGX_HTTP_SSI_PARAM_LEN 32
+#define NGX_HTTP_SSI_PARAMS_N 4
+
+
+#define NGX_HTTP_SSI_COND_IF 1
+#define NGX_HTTP_SSI_COND_ELSE 2
+
+
+#define NGX_HTTP_SSI_NO_ENCODING 0
+#define NGX_HTTP_SSI_URL_ENCODING 1
+#define NGX_HTTP_SSI_ENTITY_ENCODING 2
+
+
+typedef struct {
+ ngx_hash_t hash;
+ ngx_hash_keys_arrays_t commands;
+} ngx_http_ssi_main_conf_t;
+
+
+typedef struct {
+ ngx_buf_t *buf;
+
+ u_char *pos;
+ u_char *copy_start;
+ u_char *copy_end;
+
+ ngx_uint_t key;
+ ngx_str_t command;
+ ngx_array_t params;
+ ngx_table_elt_t *param;
+ ngx_table_elt_t params_array[NGX_HTTP_SSI_PARAMS_N];
+
+ ngx_chain_t *in;
+ ngx_chain_t *out;
+ ngx_chain_t **last_out;
+ ngx_chain_t *busy;
+ ngx_chain_t *free;
+
+ ngx_uint_t state;
+ ngx_uint_t saved_state;
+ size_t saved;
+ size_t looked;
+
+ size_t value_len;
+
+ ngx_list_t *variables;
+ ngx_array_t *blocks;
+
+#if (NGX_PCRE)
+ ngx_uint_t ncaptures;
+ int *captures;
+ u_char *captures_data;
+#endif
+
+ unsigned conditional:2;
+ unsigned encoding:2;
+ unsigned block:1;
+ unsigned output:1;
+ unsigned output_chosen:1;
+
+ ngx_http_request_t *wait;
+ void *value_buf;
+ ngx_str_t timefmt;
+ ngx_str_t errmsg;
+} ngx_http_ssi_ctx_t;
+
+
+typedef ngx_int_t (*ngx_http_ssi_command_pt) (ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ctx, ngx_str_t **);
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_uint_t index;
+
+ unsigned mandatory:1;
+ unsigned multiple:1;
+} ngx_http_ssi_param_t;
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_http_ssi_command_pt handler;
+ ngx_http_ssi_param_t *params;
+
+ unsigned conditional:2;
+ unsigned block:1;
+ unsigned flush:1;
+} ngx_http_ssi_command_t;
+
+
+extern ngx_module_t ngx_http_ssi_filter_module;
+
+
+#endif /* _NGX_HTTP_SSI_FILTER_H_INCLUDED_ */
diff --git a/app/nginx/src/http/modules/ngx_http_ssl_module.c b/app/nginx/src/http/modules/ngx_http_ssl_module.c
new file mode 100644
index 0000000..2771ac1
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_ssl_module.c
@@ -0,0 +1,992 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c,
+ ngx_pool_t *pool, ngx_str_t *s);
+
+
+#define NGX_DEFAULT_CIPHERS "HIGH:!aNULL:!MD5"
+#define NGX_DEFAULT_ECDH_CURVE "auto"
+
+#define NGX_HTTP_NPN_ADVERTISE "\x08http/1.1"
+
+
+#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
+static int ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn,
+ const unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen, void *arg);
+#endif
+
+#ifdef TLSEXT_TYPE_next_proto_neg
+static int ngx_http_ssl_npn_advertised(ngx_ssl_conn_t *ssl_conn,
+ const unsigned char **out, unsigned int *outlen, void *arg);
+#endif
+
+static ngx_int_t ngx_http_ssl_static_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_ssl_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+
+static ngx_int_t ngx_http_ssl_add_variables(ngx_conf_t *cf);
+static void *ngx_http_ssl_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+
+static char *ngx_http_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf);
+
+
+static ngx_conf_bitmask_t ngx_http_ssl_protocols[] = {
+ { ngx_string("SSLv2"), NGX_SSL_SSLv2 },
+ { ngx_string("SSLv3"), NGX_SSL_SSLv3 },
+ { ngx_string("TLSv1"), NGX_SSL_TLSv1 },
+ { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 },
+ { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_enum_t ngx_http_ssl_verify[] = {
+ { ngx_string("off"), 0 },
+ { ngx_string("on"), 1 },
+ { ngx_string("optional"), 2 },
+ { ngx_string("optional_no_ca"), 3 },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t ngx_http_ssl_commands[] = {
+
+ { ngx_string("ssl"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_http_ssl_enable,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, enable),
+ NULL },
+
+ { ngx_string("ssl_certificate"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, certificates),
+ NULL },
+
+ { ngx_string("ssl_certificate_key"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, certificate_keys),
+ NULL },
+
+ { ngx_string("ssl_password_file"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_http_ssl_password_file,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("ssl_dhparam"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, dhparam),
+ NULL },
+
+ { ngx_string("ssl_ecdh_curve"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, ecdh_curve),
+ NULL },
+
+ { ngx_string("ssl_protocols"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, protocols),
+ &ngx_http_ssl_protocols },
+
+ { ngx_string("ssl_ciphers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, ciphers),
+ NULL },
+
+ { ngx_string("ssl_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_ssl_srv_conf_t, buffer_size),
+ NULL },
+
+ { ngx_string("ssl_verify_client"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, verify),
+ &ngx_http_ssl_verify },
+
+ { ngx_string("ssl_verify_depth"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, verify_depth),
+ NULL },
+
+ { ngx_string("ssl_client_certificate"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, client_certificate),
+ NULL },
+
+ { ngx_string("ssl_trusted_certificate"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, trusted_certificate),
+ NULL },
+
+ { ngx_string("ssl_prefer_server_ciphers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, prefer_server_ciphers),
+ NULL },
+
+ { ngx_string("ssl_session_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE12,
+ ngx_http_ssl_session_cache,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("ssl_session_tickets"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, session_tickets),
+ NULL },
+
+ { ngx_string("ssl_session_ticket_key"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, session_ticket_keys),
+ NULL },
+
+ { ngx_string("ssl_session_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_sec_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, session_timeout),
+ NULL },
+
+ { ngx_string("ssl_crl"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, crl),
+ NULL },
+
+ { ngx_string("ssl_stapling"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, stapling),
+ NULL },
+
+ { ngx_string("ssl_stapling_file"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, stapling_file),
+ NULL },
+
+ { ngx_string("ssl_stapling_responder"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, stapling_responder),
+ NULL },
+
+ { ngx_string("ssl_stapling_verify"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, stapling_verify),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_ssl_module_ctx = {
+ ngx_http_ssl_add_variables, /* preconfiguration */
+ ngx_http_ssl_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_http_ssl_create_srv_conf, /* create server configuration */
+ ngx_http_ssl_merge_srv_conf, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_ssl_module = {
+ NGX_MODULE_V1,
+ &ngx_http_ssl_module_ctx, /* module context */
+ ngx_http_ssl_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_variable_t ngx_http_ssl_vars[] = {
+
+ { ngx_string("ssl_protocol"), NULL, ngx_http_ssl_static_variable,
+ (uintptr_t) ngx_ssl_get_protocol, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_cipher"), NULL, ngx_http_ssl_static_variable,
+ (uintptr_t) ngx_ssl_get_cipher_name, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_ciphers"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_ciphers, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_curves"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_curves, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_session_id"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_session_id, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_session_reused"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_session_reused, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_server_name"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_server_name, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_cert"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_certificate, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_raw_cert"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_raw_certificate,
+ NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_s_dn"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_subject_dn, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_i_dn"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_issuer_dn, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_s_dn_legacy"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_subject_dn_legacy, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_i_dn_legacy"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_issuer_dn_legacy, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_serial"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_serial_number, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_fingerprint"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_fingerprint, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_verify"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_client_verify, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_v_start"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_client_v_start, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_v_end"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_client_v_end, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_client_v_remain"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_client_v_remain, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+
+static ngx_str_t ngx_http_ssl_sess_id_ctx = ngx_string("HTTP");
+
+
+#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
+
+static int
+ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in, unsigned int inlen,
+ void *arg)
+{
+ unsigned int srvlen;
+ unsigned char *srv;
+#if (NGX_DEBUG)
+ unsigned int i;
+#endif
+#if (NGX_HTTP_V2)
+ ngx_http_connection_t *hc;
+#endif
+#if (NGX_HTTP_V2 || NGX_DEBUG)
+ ngx_connection_t *c;
+
+ c = ngx_ssl_get_connection(ssl_conn);
+#endif
+
+#if (NGX_DEBUG)
+ for (i = 0; i < inlen; i += in[i] + 1) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "SSL ALPN supported by client: %*s",
+ (size_t) in[i], &in[i + 1]);
+ }
+#endif
+
+#if (NGX_HTTP_V2)
+ hc = c->data;
+
+ if (hc->addr_conf->http2) {
+ srv =
+ (unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE;
+ srvlen = sizeof(NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1;
+
+ } else
+#endif
+ {
+ srv = (unsigned char *) NGX_HTTP_NPN_ADVERTISE;
+ srvlen = sizeof(NGX_HTTP_NPN_ADVERTISE) - 1;
+ }
+
+ if (SSL_select_next_proto((unsigned char **) out, outlen, srv, srvlen,
+ in, inlen)
+ != OPENSSL_NPN_NEGOTIATED)
+ {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "SSL ALPN selected: %*s", (size_t) *outlen, *out);
+
+ return SSL_TLSEXT_ERR_OK;
+}
+
+#endif
+
+
+#ifdef TLSEXT_TYPE_next_proto_neg
+
+static int
+ngx_http_ssl_npn_advertised(ngx_ssl_conn_t *ssl_conn,
+ const unsigned char **out, unsigned int *outlen, void *arg)
+{
+#if (NGX_HTTP_V2 || NGX_DEBUG)
+ ngx_connection_t *c;
+
+ c = ngx_ssl_get_connection(ssl_conn);
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "SSL NPN advertised");
+#endif
+
+#if (NGX_HTTP_V2)
+ {
+ ngx_http_connection_t *hc;
+
+ hc = c->data;
+
+ if (hc->addr_conf->http2) {
+ *out =
+ (unsigned char *) NGX_HTTP_V2_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE;
+ *outlen = sizeof(NGX_HTTP_V2_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1;
+
+ return SSL_TLSEXT_ERR_OK;
+ }
+ }
+#endif
+
+ *out = (unsigned char *) NGX_HTTP_NPN_ADVERTISE;
+ *outlen = sizeof(NGX_HTTP_NPN_ADVERTISE) - 1;
+
+ return SSL_TLSEXT_ERR_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_ssl_static_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_ssl_variable_handler_pt handler = (ngx_ssl_variable_handler_pt) data;
+
+ size_t len;
+ ngx_str_t s;
+
+ if (r->connection->ssl) {
+
+ (void) handler(r->connection, NULL, &s);
+
+ v->data = s.data;
+
+ for (len = 0; v->data[len]; len++) { /* void */ }
+
+ v->len = len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ return NGX_OK;
+ }
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssl_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_ssl_variable_handler_pt handler = (ngx_ssl_variable_handler_pt) data;
+
+ ngx_str_t s;
+
+ if (r->connection->ssl) {
+
+ if (handler(r->connection, r->pool, &s) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ v->len = s.len;
+ v->data = s.data;
+
+ if (v->len) {
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ return NGX_OK;
+ }
+ }
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_ssl_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var, *v;
+
+ for (v = ngx_http_ssl_vars; v->name.len; v++) {
+ var = ngx_http_add_variable(cf, &v->name, v->flags);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = v->get_handler;
+ var->data = v->data;
+ }
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_ssl_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_http_ssl_srv_conf_t *sscf;
+
+ sscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssl_srv_conf_t));
+ if (sscf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * sscf->protocols = 0;
+ * sscf->dhparam = { 0, NULL };
+ * sscf->ecdh_curve = { 0, NULL };
+ * sscf->client_certificate = { 0, NULL };
+ * sscf->trusted_certificate = { 0, NULL };
+ * sscf->crl = { 0, NULL };
+ * sscf->ciphers = { 0, NULL };
+ * sscf->shm_zone = NULL;
+ * sscf->stapling_file = { 0, NULL };
+ * sscf->stapling_responder = { 0, NULL };
+ */
+
+ sscf->enable = NGX_CONF_UNSET;
+ sscf->prefer_server_ciphers = NGX_CONF_UNSET;
+ sscf->buffer_size = NGX_CONF_UNSET_SIZE;
+ sscf->verify = NGX_CONF_UNSET_UINT;
+ sscf->verify_depth = NGX_CONF_UNSET_UINT;
+ sscf->certificates = NGX_CONF_UNSET_PTR;
+ sscf->certificate_keys = NGX_CONF_UNSET_PTR;
+ sscf->passwords = NGX_CONF_UNSET_PTR;
+ sscf->builtin_session_cache = NGX_CONF_UNSET;
+ sscf->session_timeout = NGX_CONF_UNSET;
+ sscf->session_tickets = NGX_CONF_UNSET;
+ sscf->session_ticket_keys = NGX_CONF_UNSET_PTR;
+ sscf->stapling = NGX_CONF_UNSET;
+ sscf->stapling_verify = NGX_CONF_UNSET;
+
+ return sscf;
+}
+
+
+static char *
+ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_ssl_srv_conf_t *prev = parent;
+ ngx_http_ssl_srv_conf_t *conf = child;
+
+ ngx_pool_cleanup_t *cln;
+
+ if (conf->enable == NGX_CONF_UNSET) {
+ if (prev->enable == NGX_CONF_UNSET) {
+ conf->enable = 0;
+
+ } else {
+ conf->enable = prev->enable;
+ conf->file = prev->file;
+ conf->line = prev->line;
+ }
+ }
+
+ ngx_conf_merge_value(conf->session_timeout,
+ prev->session_timeout, 300);
+
+ ngx_conf_merge_value(conf->prefer_server_ciphers,
+ prev->prefer_server_ciphers, 0);
+
+ ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols,
+ (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1
+ |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2));
+
+ ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size,
+ NGX_SSL_BUFSIZE);
+
+ ngx_conf_merge_uint_value(conf->verify, prev->verify, 0);
+ ngx_conf_merge_uint_value(conf->verify_depth, prev->verify_depth, 1);
+
+ ngx_conf_merge_ptr_value(conf->certificates, prev->certificates, NULL);
+ ngx_conf_merge_ptr_value(conf->certificate_keys, prev->certificate_keys,
+ NULL);
+
+ ngx_conf_merge_ptr_value(conf->passwords, prev->passwords, NULL);
+
+ ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, "");
+
+ ngx_conf_merge_str_value(conf->client_certificate, prev->client_certificate,
+ "");
+ ngx_conf_merge_str_value(conf->trusted_certificate,
+ prev->trusted_certificate, "");
+ ngx_conf_merge_str_value(conf->crl, prev->crl, "");
+
+ ngx_conf_merge_str_value(conf->ecdh_curve, prev->ecdh_curve,
+ NGX_DEFAULT_ECDH_CURVE);
+
+ ngx_conf_merge_str_value(conf->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS);
+
+ ngx_conf_merge_value(conf->stapling, prev->stapling, 0);
+ ngx_conf_merge_value(conf->stapling_verify, prev->stapling_verify, 0);
+ ngx_conf_merge_str_value(conf->stapling_file, prev->stapling_file, "");
+ ngx_conf_merge_str_value(conf->stapling_responder,
+ prev->stapling_responder, "");
+
+ conf->ssl.log = cf->log;
+
+ if (conf->enable) {
+
+ if (conf->certificates == NULL) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate\" is defined for "
+ "the \"ssl\" directive in %s:%ui",
+ conf->file, conf->line);
+ return NGX_CONF_ERROR;
+ }
+
+ if (conf->certificate_keys == NULL) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate_key\" is defined for "
+ "the \"ssl\" directive in %s:%ui",
+ conf->file, conf->line);
+ return NGX_CONF_ERROR;
+ }
+
+ if (conf->certificate_keys->nelts < conf->certificates->nelts) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate_key\" is defined "
+ "for certificate \"%V\" and "
+ "the \"ssl\" directive in %s:%ui",
+ ((ngx_str_t *) conf->certificates->elts)
+ + conf->certificates->nelts - 1,
+ conf->file, conf->line);
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+
+ if (conf->certificates == NULL) {
+ return NGX_CONF_OK;
+ }
+
+ if (conf->certificate_keys == NULL
+ || conf->certificate_keys->nelts < conf->certificates->nelts)
+ {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate_key\" is defined "
+ "for certificate \"%V\"",
+ ((ngx_str_t *) conf->certificates->elts)
+ + conf->certificates->nelts - 1);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (ngx_ssl_create(&conf->ssl, conf->protocols, conf) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+
+ if (SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx,
+ ngx_http_ssl_servername)
+ == 0)
+ {
+ ngx_log_error(NGX_LOG_WARN, cf->log, 0,
+ "nginx was built with SNI support, however, now it is linked "
+ "dynamically to an OpenSSL library which has no tlsext support, "
+ "therefore SNI is not available");
+ }
+
+#endif
+
+#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
+ SSL_CTX_set_alpn_select_cb(conf->ssl.ctx, ngx_http_ssl_alpn_select, NULL);
+#endif
+
+#ifdef TLSEXT_TYPE_next_proto_neg
+ SSL_CTX_set_next_protos_advertised_cb(conf->ssl.ctx,
+ ngx_http_ssl_npn_advertised, NULL);
+#endif
+
+ cln = ngx_pool_cleanup_add(cf->pool, 0);
+ if (cln == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ cln->handler = ngx_ssl_cleanup_ctx;
+ cln->data = &conf->ssl;
+
+ if (ngx_ssl_certificates(cf, &conf->ssl, conf->certificates,
+ conf->certificate_keys, conf->passwords)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_ssl_ciphers(cf, &conf->ssl, &conf->ciphers,
+ conf->prefer_server_ciphers)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->ssl.buffer_size = conf->buffer_size;
+
+ if (conf->verify) {
+
+ if (conf->client_certificate.len == 0 && conf->verify != 3) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no ssl_client_certificate for ssl_client_verify");
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_ssl_client_certificate(cf, &conf->ssl,
+ &conf->client_certificate,
+ conf->verify_depth)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (ngx_ssl_trusted_certificate(cf, &conf->ssl,
+ &conf->trusted_certificate,
+ conf->verify_depth)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_ssl_crl(cf, &conf->ssl, &conf->crl) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_ssl_dhparam(cf, &conf->ssl, &conf->dhparam) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_ssl_ecdh_curve(cf, &conf->ssl, &conf->ecdh_curve) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_value(conf->builtin_session_cache,
+ prev->builtin_session_cache, NGX_SSL_NONE_SCACHE);
+
+ if (conf->shm_zone == NULL) {
+ conf->shm_zone = prev->shm_zone;
+ }
+
+ if (ngx_ssl_session_cache(&conf->ssl, &ngx_http_ssl_sess_id_ctx,
+ conf->builtin_session_cache,
+ conf->shm_zone, conf->session_timeout)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_value(conf->session_tickets, prev->session_tickets, 1);
+
+#ifdef SSL_OP_NO_TICKET
+ if (!conf->session_tickets) {
+ SSL_CTX_set_options(conf->ssl.ctx, SSL_OP_NO_TICKET);
+ }
+#endif
+
+ ngx_conf_merge_ptr_value(conf->session_ticket_keys,
+ prev->session_ticket_keys, NULL);
+
+ if (ngx_ssl_session_ticket_keys(cf, &conf->ssl, conf->session_ticket_keys)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ if (conf->stapling) {
+
+ if (ngx_ssl_stapling(cf, &conf->ssl, &conf->stapling_file,
+ &conf->stapling_responder, conf->stapling_verify)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_ssl_srv_conf_t *sscf = conf;
+
+ char *rv;
+
+ rv = ngx_conf_set_flag_slot(cf, cmd, conf);
+
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+
+ sscf->file = cf->conf_file->file.name.data;
+ sscf->line = cf->conf_file->line;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_ssl_srv_conf_t *sscf = conf;
+
+ ngx_str_t *value;
+
+ if (sscf->passwords != NGX_CONF_UNSET_PTR) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ sscf->passwords = ngx_ssl_read_password_file(cf, &value[1]);
+
+ if (sscf->passwords == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_ssl_srv_conf_t *sscf = conf;
+
+ size_t len;
+ ngx_str_t *value, name, size;
+ ngx_int_t n;
+ ngx_uint_t i, j;
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+
+ if (ngx_strcmp(value[i].data, "off") == 0) {
+ sscf->builtin_session_cache = NGX_SSL_NO_SCACHE;
+ continue;
+ }
+
+ if (ngx_strcmp(value[i].data, "none") == 0) {
+ sscf->builtin_session_cache = NGX_SSL_NONE_SCACHE;
+ continue;
+ }
+
+ if (ngx_strcmp(value[i].data, "builtin") == 0) {
+ sscf->builtin_session_cache = NGX_SSL_DFLT_BUILTIN_SCACHE;
+ continue;
+ }
+
+ if (value[i].len > sizeof("builtin:") - 1
+ && ngx_strncmp(value[i].data, "builtin:", sizeof("builtin:") - 1)
+ == 0)
+ {
+ n = ngx_atoi(value[i].data + sizeof("builtin:") - 1,
+ value[i].len - (sizeof("builtin:") - 1));
+
+ if (n == NGX_ERROR) {
+ goto invalid;
+ }
+
+ sscf->builtin_session_cache = n;
+
+ continue;
+ }
+
+ if (value[i].len > sizeof("shared:") - 1
+ && ngx_strncmp(value[i].data, "shared:", sizeof("shared:") - 1)
+ == 0)
+ {
+ len = 0;
+
+ for (j = sizeof("shared:") - 1; j < value[i].len; j++) {
+ if (value[i].data[j] == ':') {
+ break;
+ }
+
+ len++;
+ }
+
+ if (len == 0) {
+ goto invalid;
+ }
+
+ name.len = len;
+ name.data = value[i].data + sizeof("shared:") - 1;
+
+ size.len = value[i].len - j - 1;
+ size.data = name.data + len + 1;
+
+ n = ngx_parse_size(&size);
+
+ if (n == NGX_ERROR) {
+ goto invalid;
+ }
+
+ if (n < (ngx_int_t) (8 * ngx_pagesize)) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "session cache \"%V\" is too small",
+ &value[i]);
+
+ return NGX_CONF_ERROR;
+ }
+
+ sscf->shm_zone = ngx_shared_memory_add(cf, &name, n,
+ &ngx_http_ssl_module);
+ if (sscf->shm_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ sscf->shm_zone->init = ngx_ssl_session_cache_init;
+
+ continue;
+ }
+
+ goto invalid;
+ }
+
+ if (sscf->shm_zone && sscf->builtin_session_cache == NGX_CONF_UNSET) {
+ sscf->builtin_session_cache = NGX_SSL_NO_BUILTIN_SCACHE;
+ }
+
+ return NGX_CONF_OK;
+
+invalid:
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid session cache \"%V\"", &value[i]);
+
+ return NGX_CONF_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_ssl_init(ngx_conf_t *cf)
+{
+ ngx_uint_t s;
+ ngx_http_ssl_srv_conf_t *sscf;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_core_srv_conf_t **cscfp;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+ cscfp = cmcf->servers.elts;
+
+ for (s = 0; s < cmcf->servers.nelts; s++) {
+
+ sscf = cscfp[s]->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
+
+ if (sscf->ssl.ctx == NULL || !sscf->stapling) {
+ continue;
+ }
+
+ clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
+
+ if (ngx_ssl_stapling_resolver(cf, &sscf->ssl, clcf->resolver,
+ clcf->resolver_timeout)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_ssl_module.h b/app/nginx/src/http/modules/ngx_http_ssl_module.h
new file mode 100644
index 0000000..57f5941
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_ssl_module.h
@@ -0,0 +1,66 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_SSL_H_INCLUDED_
+#define _NGX_HTTP_SSL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_flag_t enable;
+
+ ngx_ssl_t ssl;
+
+ ngx_flag_t prefer_server_ciphers;
+
+ ngx_uint_t protocols;
+
+ ngx_uint_t verify;
+ ngx_uint_t verify_depth;
+
+ size_t buffer_size;
+
+ ssize_t builtin_session_cache;
+
+ time_t session_timeout;
+
+ ngx_array_t *certificates;
+ ngx_array_t *certificate_keys;
+
+ ngx_str_t dhparam;
+ ngx_str_t ecdh_curve;
+ ngx_str_t client_certificate;
+ ngx_str_t trusted_certificate;
+ ngx_str_t crl;
+
+ ngx_str_t ciphers;
+
+ ngx_array_t *passwords;
+
+ ngx_shm_zone_t *shm_zone;
+
+ ngx_flag_t session_tickets;
+ ngx_array_t *session_ticket_keys;
+
+ ngx_flag_t stapling;
+ ngx_flag_t stapling_verify;
+ ngx_str_t stapling_file;
+ ngx_str_t stapling_responder;
+
+ u_char *file;
+ ngx_uint_t line;
+} ngx_http_ssl_srv_conf_t;
+
+
+extern ngx_module_t ngx_http_ssl_module;
+
+
+#endif /* _NGX_HTTP_SSL_H_INCLUDED_ */
diff --git a/app/nginx/src/http/modules/ngx_http_static_module.c b/app/nginx/src/http/modules/ngx_http_static_module.c
new file mode 100644
index 0000000..f2435a7
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_static_module.c
@@ -0,0 +1,287 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_static_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_static_init(ngx_conf_t *cf);
+
+
+static ngx_http_module_t ngx_http_static_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_static_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_static_module = {
+ NGX_MODULE_V1,
+ &ngx_http_static_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_static_handler(ngx_http_request_t *r)
+{
+ u_char *last, *location;
+ size_t root, len;
+ ngx_str_t path;
+ ngx_int_t rc;
+ ngx_uint_t level;
+ ngx_log_t *log;
+ ngx_buf_t *b;
+ ngx_chain_t out;
+ ngx_open_file_info_t of;
+ ngx_http_core_loc_conf_t *clcf;
+
+ if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))) {
+ return NGX_HTTP_NOT_ALLOWED;
+ }
+
+ if (r->uri.data[r->uri.len - 1] == '/') {
+ return NGX_DECLINED;
+ }
+
+ log = r->connection->log;
+
+ /*
+ * ngx_http_map_uri_to_path() allocates memory for terminating '\0'
+ * so we do not need to reserve memory for '/' for possible redirect
+ */
+
+ last = ngx_http_map_uri_to_path(r, &path, &root, 0);
+ if (last == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ path.len = last - path.data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "http filename: \"%s\"", path.data);
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+ of.read_ahead = clcf->read_ahead;
+ of.directio = clcf->directio;
+ of.valid = clcf->open_file_cache_valid;
+ of.min_uses = clcf->open_file_cache_min_uses;
+ of.errors = clcf->open_file_cache_errors;
+ of.events = clcf->open_file_cache_events;
+
+ if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+ != NGX_OK)
+ {
+ switch (of.err) {
+
+ case 0:
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+
+ case NGX_ENOENT:
+ case NGX_ENOTDIR:
+ case NGX_ENAMETOOLONG:
+
+ level = NGX_LOG_ERR;
+ rc = NGX_HTTP_NOT_FOUND;
+ break;
+
+ case NGX_EACCES:
+#if (NGX_HAVE_OPENAT)
+ case NGX_EMLINK:
+ case NGX_ELOOP:
+#endif
+
+ level = NGX_LOG_ERR;
+ rc = NGX_HTTP_FORBIDDEN;
+ break;
+
+ default:
+
+ level = NGX_LOG_CRIT;
+ rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ }
+
+ if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
+ ngx_log_error(level, log, of.err,
+ "%s \"%s\" failed", of.failed, path.data);
+ }
+
+ return rc;
+ }
+
+ r->root_tested = !r->error_page;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", of.fd);
+
+ if (of.is_dir) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir");
+
+ ngx_http_clear_location(r);
+
+ r->headers_out.location = ngx_list_push(&r->headers_out.headers);
+ if (r->headers_out.location == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ len = r->uri.len + 1;
+
+ if (!clcf->alias && clcf->root_lengths == NULL && r->args.len == 0) {
+ location = path.data + clcf->root.len;
+
+ *last = '/';
+
+ } else {
+ if (r->args.len) {
+ len += r->args.len + 1;
+ }
+
+ location = ngx_pnalloc(r->pool, len);
+ if (location == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ last = ngx_copy(location, r->uri.data, r->uri.len);
+
+ *last = '/';
+
+ if (r->args.len) {
+ *++last = '?';
+ ngx_memcpy(++last, r->args.data, r->args.len);
+ }
+ }
+
+ r->headers_out.location->hash = 1;
+ ngx_str_set(&r->headers_out.location->key, "Location");
+ r->headers_out.location->value.len = len;
+ r->headers_out.location->value.data = location;
+
+ return NGX_HTTP_MOVED_PERMANENTLY;
+ }
+
+#if !(NGX_WIN32) /* the not regular files are probably Unix specific */
+
+ if (!of.is_file) {
+ ngx_log_error(NGX_LOG_CRIT, log, 0,
+ "\"%s\" is not a regular file", path.data);
+
+ return NGX_HTTP_NOT_FOUND;
+ }
+
+#endif
+
+ if (r->method == NGX_HTTP_POST) {
+ return NGX_HTTP_NOT_ALLOWED;
+ }
+
+ rc = ngx_http_discard_request_body(r);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ log->action = "sending response to client";
+
+ r->headers_out.status = NGX_HTTP_OK;
+ r->headers_out.content_length_n = of.size;
+ r->headers_out.last_modified_time = of.mtime;
+
+ if (ngx_http_set_etag(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (ngx_http_set_content_type(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (r != r->main && of.size == 0) {
+ return ngx_http_send_header(r);
+ }
+
+ r->allow_ranges = 1;
+
+ /* we need to allocate all before the header would be sent */
+
+ b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
+ if (b == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
+ if (b->file == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ return rc;
+ }
+
+ b->file_pos = 0;
+ b->file_last = of.size;
+
+ b->in_file = b->file_last ? 1: 0;
+ b->last_buf = (r == r->main) ? 1: 0;
+ b->last_in_chain = 1;
+
+ b->file->fd = of.fd;
+ b->file->name = path;
+ b->file->log = log;
+ b->file->directio = of.is_directio;
+
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_output_filter(r, &out);
+}
+
+
+static ngx_int_t
+ngx_http_static_init(ngx_conf_t *cf)
+{
+ ngx_http_handler_pt *h;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = ngx_http_static_handler;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_stub_status_module.c b/app/nginx/src/http/modules/ngx_http_stub_status_module.c
new file mode 100644
index 0000000..61199f2
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_stub_status_module.c
@@ -0,0 +1,236 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_stub_status_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_stub_status_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_stub_status_add_variables(ngx_conf_t *cf);
+static char *ngx_http_set_stub_status(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_command_t ngx_http_status_commands[] = {
+
+ { ngx_string("stub_status"),
+ NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
+ ngx_http_set_stub_status,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_stub_status_module_ctx = {
+ ngx_http_stub_status_add_variables, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_stub_status_module = {
+ NGX_MODULE_V1,
+ &ngx_http_stub_status_module_ctx, /* module context */
+ ngx_http_status_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_variable_t ngx_http_stub_status_vars[] = {
+
+ { ngx_string("connections_active"), NULL, ngx_http_stub_status_variable,
+ 0, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("connections_reading"), NULL, ngx_http_stub_status_variable,
+ 1, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("connections_writing"), NULL, ngx_http_stub_status_variable,
+ 2, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("connections_waiting"), NULL, ngx_http_stub_status_variable,
+ 3, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+
+static ngx_int_t
+ngx_http_stub_status_handler(ngx_http_request_t *r)
+{
+ size_t size;
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_chain_t out;
+ ngx_atomic_int_t ap, hn, ac, rq, rd, wr, wa;
+
+ if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
+ return NGX_HTTP_NOT_ALLOWED;
+ }
+
+ rc = ngx_http_discard_request_body(r);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ r->headers_out.content_type_len = sizeof("text/plain") - 1;
+ ngx_str_set(&r->headers_out.content_type, "text/plain");
+ r->headers_out.content_type_lowcase = NULL;
+
+ if (r->method == NGX_HTTP_HEAD) {
+ r->headers_out.status = NGX_HTTP_OK;
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ return rc;
+ }
+ }
+
+ size = sizeof("Active connections: \n") + NGX_ATOMIC_T_LEN
+ + sizeof("server accepts handled requests\n") - 1
+ + 6 + 3 * NGX_ATOMIC_T_LEN
+ + sizeof("Reading: Writing: Waiting: \n") + 3 * NGX_ATOMIC_T_LEN;
+
+ b = ngx_create_temp_buf(r->pool, size);
+ if (b == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ out.buf = b;
+ out.next = NULL;
+
+ ap = *ngx_stat_accepted;
+ hn = *ngx_stat_handled;
+ ac = *ngx_stat_active;
+ rq = *ngx_stat_requests;
+ rd = *ngx_stat_reading;
+ wr = *ngx_stat_writing;
+ wa = *ngx_stat_waiting;
+
+ b->last = ngx_sprintf(b->last, "Active connections: %uA \n", ac);
+
+ b->last = ngx_cpymem(b->last, "server accepts handled requests\n",
+ sizeof("server accepts handled requests\n") - 1);
+
+ b->last = ngx_sprintf(b->last, " %uA %uA %uA \n", ap, hn, rq);
+
+ b->last = ngx_sprintf(b->last, "Reading: %uA Writing: %uA Waiting: %uA \n",
+ rd, wr, wa);
+
+ r->headers_out.status = NGX_HTTP_OK;
+ r->headers_out.content_length_n = b->last - b->pos;
+
+ b->last_buf = (r == r->main) ? 1 : 0;
+ b->last_in_chain = 1;
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ return rc;
+ }
+
+ return ngx_http_output_filter(r, &out);
+}
+
+
+static ngx_int_t
+ngx_http_stub_status_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ u_char *p;
+ ngx_atomic_int_t value;
+
+ p = ngx_pnalloc(r->pool, NGX_ATOMIC_T_LEN);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ switch (data) {
+ case 0:
+ value = *ngx_stat_active;
+ break;
+
+ case 1:
+ value = *ngx_stat_reading;
+ break;
+
+ case 2:
+ value = *ngx_stat_writing;
+ break;
+
+ case 3:
+ value = *ngx_stat_waiting;
+ break;
+
+ /* suppress warning */
+ default:
+ value = 0;
+ break;
+ }
+
+ v->len = ngx_sprintf(p, "%uA", value) - p;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = p;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_stub_status_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var, *v;
+
+ for (v = ngx_http_stub_status_vars; v->name.len; v++) {
+ var = ngx_http_add_variable(cf, &v->name, v->flags);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = v->get_handler;
+ var->data = v->data;
+ }
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_http_set_stub_status(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf;
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+ clcf->handler = ngx_http_stub_status_handler;
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_sub_filter_module.c b/app/nginx/src/http/modules/ngx_http_sub_filter_module.c
new file mode 100644
index 0000000..de58c6f
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_sub_filter_module.c
@@ -0,0 +1,1018 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_http_complex_value_t match;
+ ngx_http_complex_value_t value;
+} ngx_http_sub_pair_t;
+
+
+typedef struct {
+ ngx_str_t match;
+ ngx_http_complex_value_t *value;
+} ngx_http_sub_match_t;
+
+
+typedef struct {
+ ngx_uint_t min_match_len;
+ ngx_uint_t max_match_len;
+
+ u_char index[257];
+ u_char shift[256];
+} ngx_http_sub_tables_t;
+
+
+typedef struct {
+ ngx_uint_t dynamic; /* unsigned dynamic:1; */
+
+ ngx_array_t *pairs;
+
+ ngx_http_sub_tables_t *tables;
+
+ ngx_hash_t types;
+
+ ngx_flag_t once;
+ ngx_flag_t last_modified;
+
+ ngx_array_t *types_keys;
+ ngx_array_t *matches;
+} ngx_http_sub_loc_conf_t;
+
+
+typedef struct {
+ ngx_str_t saved;
+ ngx_str_t looked;
+
+ ngx_uint_t once; /* unsigned once:1 */
+
+ ngx_buf_t *buf;
+
+ u_char *pos;
+ u_char *copy_start;
+ u_char *copy_end;
+
+ ngx_chain_t *in;
+ ngx_chain_t *out;
+ ngx_chain_t **last_out;
+ ngx_chain_t *busy;
+ ngx_chain_t *free;
+
+ ngx_str_t *sub;
+ ngx_uint_t applied;
+
+ ngx_int_t offset;
+ ngx_uint_t index;
+
+ ngx_http_sub_tables_t *tables;
+ ngx_array_t *matches;
+} ngx_http_sub_ctx_t;
+
+
+static ngx_uint_t ngx_http_sub_cmp_index;
+
+
+static ngx_int_t ngx_http_sub_output(ngx_http_request_t *r,
+ ngx_http_sub_ctx_t *ctx);
+static ngx_int_t ngx_http_sub_parse(ngx_http_request_t *r,
+ ngx_http_sub_ctx_t *ctx, ngx_uint_t flush);
+static ngx_int_t ngx_http_sub_match(ngx_http_sub_ctx_t *ctx, ngx_int_t start,
+ ngx_str_t *m);
+
+static char * ngx_http_sub_filter(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static void *ngx_http_sub_create_conf(ngx_conf_t *cf);
+static char *ngx_http_sub_merge_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+static void ngx_http_sub_init_tables(ngx_http_sub_tables_t *tables,
+ ngx_http_sub_match_t *match, ngx_uint_t n);
+static ngx_int_t ngx_http_sub_cmp_matches(const void *one, const void *two);
+static ngx_int_t ngx_http_sub_filter_init(ngx_conf_t *cf);
+
+
+static ngx_command_t ngx_http_sub_filter_commands[] = {
+
+ { ngx_string("sub_filter"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_http_sub_filter,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("sub_filter_types"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_types_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_sub_loc_conf_t, types_keys),
+ &ngx_http_html_default_types[0] },
+
+ { ngx_string("sub_filter_once"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_sub_loc_conf_t, once),
+ NULL },
+
+ { ngx_string("sub_filter_last_modified"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_sub_loc_conf_t, last_modified),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_sub_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_sub_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_sub_create_conf, /* create location configuration */
+ ngx_http_sub_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_sub_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_sub_filter_module_ctx, /* module context */
+ ngx_http_sub_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_sub_header_filter(ngx_http_request_t *r)
+{
+ ngx_str_t *m;
+ ngx_uint_t i, j, n;
+ ngx_http_sub_ctx_t *ctx;
+ ngx_http_sub_pair_t *pairs;
+ ngx_http_sub_match_t *matches;
+ ngx_http_sub_loc_conf_t *slcf;
+
+ slcf = ngx_http_get_module_loc_conf(r, ngx_http_sub_filter_module);
+
+ if (slcf->pairs == NULL
+ || r->headers_out.content_length_n == 0
+ || ngx_http_test_content_type(r, &slcf->types) == NULL)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_sub_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (slcf->dynamic == 0) {
+ ctx->tables = slcf->tables;
+ ctx->matches = slcf->matches;
+
+ } else {
+ pairs = slcf->pairs->elts;
+ n = slcf->pairs->nelts;
+
+ matches = ngx_pcalloc(r->pool, sizeof(ngx_http_sub_match_t) * n);
+ if (matches == NULL) {
+ return NGX_ERROR;
+ }
+
+ j = 0;
+ for (i = 0; i < n; i++) {
+ matches[j].value = &pairs[i].value;
+
+ if (pairs[i].match.lengths == NULL) {
+ matches[j].match = pairs[i].match.value;
+ j++;
+ continue;
+ }
+
+ m = &matches[j].match;
+ if (ngx_http_complex_value(r, &pairs[i].match, m) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (m->len == 0) {
+ continue;
+ }
+
+ ngx_strlow(m->data, m->data, m->len);
+ j++;
+ }
+
+ if (j == 0) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx->matches = ngx_palloc(r->pool, sizeof(ngx_array_t));
+ if (ctx->matches == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->matches->elts = matches;
+ ctx->matches->nelts = j;
+
+ ctx->tables = ngx_palloc(r->pool, sizeof(ngx_http_sub_tables_t));
+ if (ctx->tables == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_sub_init_tables(ctx->tables, ctx->matches->elts,
+ ctx->matches->nelts);
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_sub_filter_module);
+
+ ctx->saved.data = ngx_pnalloc(r->pool, ctx->tables->max_match_len - 1);
+ if (ctx->saved.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->looked.data = ngx_pnalloc(r->pool, ctx->tables->max_match_len - 1);
+ if (ctx->looked.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->offset = ctx->tables->min_match_len - 1;
+ ctx->last_out = &ctx->out;
+
+ r->filter_need_in_memory = 1;
+
+ if (r == r->main) {
+ ngx_http_clear_content_length(r);
+
+ if (!slcf->last_modified) {
+ ngx_http_clear_last_modified(r);
+ ngx_http_clear_etag(r);
+
+ } else {
+ ngx_http_weak_etag(r);
+ }
+ }
+
+ return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t
+ngx_http_sub_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_str_t *sub;
+ ngx_uint_t flush, last;
+ ngx_chain_t *cl;
+ ngx_http_sub_ctx_t *ctx;
+ ngx_http_sub_match_t *match;
+ ngx_http_sub_loc_conf_t *slcf;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_sub_filter_module);
+
+ if (ctx == NULL) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ if ((in == NULL
+ && ctx->buf == NULL
+ && ctx->in == NULL
+ && ctx->busy == NULL))
+ {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ if (ctx->once && (ctx->buf == NULL || ctx->in == NULL)) {
+
+ if (ctx->busy) {
+ if (ngx_http_sub_output(r, ctx) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ }
+
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ /* add the incoming chain to the chain ctx->in */
+
+ if (in) {
+ if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http sub filter \"%V\"", &r->uri);
+
+ flush = 0;
+ last = 0;
+
+ while (ctx->in || ctx->buf) {
+
+ if (ctx->buf == NULL) {
+ ctx->buf = ctx->in->buf;
+ ctx->in = ctx->in->next;
+ ctx->pos = ctx->buf->pos;
+ }
+
+ if (ctx->buf->flush || ctx->buf->recycled) {
+ flush = 1;
+ }
+
+ if (ctx->in == NULL) {
+ last = flush;
+ }
+
+ b = NULL;
+
+ while (ctx->pos < ctx->buf->last) {
+
+ rc = ngx_http_sub_parse(r, ctx, last);
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "parse: %i, looked: \"%V\" %p-%p",
+ rc, &ctx->looked, ctx->copy_start, ctx->copy_end);
+
+ if (rc == NGX_ERROR) {
+ return rc;
+ }
+
+ if (ctx->saved.len) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "saved: \"%V\"", &ctx->saved);
+
+ cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = cl->buf;
+
+ ngx_memzero(b, sizeof(ngx_buf_t));
+
+ b->pos = ngx_pnalloc(r->pool, ctx->saved.len);
+ if (b->pos == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(b->pos, ctx->saved.data, ctx->saved.len);
+ b->last = b->pos + ctx->saved.len;
+ b->memory = 1;
+
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ ctx->saved.len = 0;
+ }
+
+ if (ctx->copy_start != ctx->copy_end) {
+
+ cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = cl->buf;
+
+ ngx_memcpy(b, ctx->buf, sizeof(ngx_buf_t));
+
+ b->pos = ctx->copy_start;
+ b->last = ctx->copy_end;
+ b->shadow = NULL;
+ b->last_buf = 0;
+ b->last_in_chain = 0;
+ b->recycled = 0;
+
+ if (b->in_file) {
+ b->file_last = b->file_pos + (b->last - ctx->buf->pos);
+ b->file_pos += b->pos - ctx->buf->pos;
+ }
+
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+ }
+
+ if (rc == NGX_AGAIN) {
+ continue;
+ }
+
+
+ /* rc == NGX_OK */
+
+ cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = cl->buf;
+
+ ngx_memzero(b, sizeof(ngx_buf_t));
+
+ slcf = ngx_http_get_module_loc_conf(r, ngx_http_sub_filter_module);
+
+ if (ctx->sub == NULL) {
+ ctx->sub = ngx_pcalloc(r->pool, sizeof(ngx_str_t)
+ * ctx->matches->nelts);
+ if (ctx->sub == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ sub = &ctx->sub[ctx->index];
+
+ if (sub->data == NULL) {
+ match = ctx->matches->elts;
+
+ if (ngx_http_complex_value(r, match[ctx->index].value, sub)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ if (sub->len) {
+ b->memory = 1;
+ b->pos = sub->data;
+ b->last = sub->data + sub->len;
+
+ } else {
+ b->sync = 1;
+ }
+
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ ctx->index = 0;
+ ctx->once = slcf->once && (++ctx->applied == ctx->matches->nelts);
+
+ continue;
+ }
+
+ if (ctx->looked.len
+ && (ctx->buf->last_buf || ctx->buf->last_in_chain))
+ {
+ cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = cl->buf;
+
+ ngx_memzero(b, sizeof(ngx_buf_t));
+
+ b->pos = ctx->looked.data;
+ b->last = b->pos + ctx->looked.len;
+ b->memory = 1;
+
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+
+ ctx->looked.len = 0;
+ }
+
+ if (ctx->buf->last_buf || ctx->buf->flush || ctx->buf->sync
+ || ngx_buf_in_memory(ctx->buf))
+ {
+ if (b == NULL) {
+ cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = cl->buf;
+
+ ngx_memzero(b, sizeof(ngx_buf_t));
+
+ b->sync = 1;
+
+ *ctx->last_out = cl;
+ ctx->last_out = &cl->next;
+ }
+
+ b->last_buf = ctx->buf->last_buf;
+ b->last_in_chain = ctx->buf->last_in_chain;
+ b->flush = ctx->buf->flush;
+ b->shadow = ctx->buf;
+
+ b->recycled = ctx->buf->recycled;
+ }
+
+ ctx->buf = NULL;
+ }
+
+ if (ctx->out == NULL && ctx->busy == NULL) {
+ return NGX_OK;
+ }
+
+ return ngx_http_sub_output(r, ctx);
+}
+
+
+static ngx_int_t
+ngx_http_sub_output(ngx_http_request_t *r, ngx_http_sub_ctx_t *ctx)
+{
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+
+#if 1
+ b = NULL;
+ for (cl = ctx->out; cl; cl = cl->next) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "sub out: %p %p", cl->buf, cl->buf->pos);
+ if (cl->buf == b) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "the same buf was used in sub");
+ ngx_debug_point();
+ return NGX_ERROR;
+ }
+ b = cl->buf;
+ }
+#endif
+
+ rc = ngx_http_next_body_filter(r, ctx->out);
+
+ if (ctx->busy == NULL) {
+ ctx->busy = ctx->out;
+
+ } else {
+ for (cl = ctx->busy; cl->next; cl = cl->next) { /* void */ }
+ cl->next = ctx->out;
+ }
+
+ ctx->out = NULL;
+ ctx->last_out = &ctx->out;
+
+ while (ctx->busy) {
+
+ cl = ctx->busy;
+ b = cl->buf;
+
+ if (ngx_buf_size(b) != 0) {
+ break;
+ }
+
+ if (b->shadow) {
+ b->shadow->pos = b->shadow->last;
+ }
+
+ ctx->busy = cl->next;
+
+ if (ngx_buf_in_memory(b) || b->in_file) {
+ /* add data bufs only to the free buf chain */
+
+ cl->next = ctx->free;
+ ctx->free = cl;
+ }
+ }
+
+ if (ctx->in || ctx->buf) {
+ r->buffered |= NGX_HTTP_SUB_BUFFERED;
+
+ } else {
+ r->buffered &= ~NGX_HTTP_SUB_BUFFERED;
+ }
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_sub_parse(ngx_http_request_t *r, ngx_http_sub_ctx_t *ctx,
+ ngx_uint_t flush)
+{
+ u_char *p, c;
+ ngx_str_t *m;
+ ngx_int_t offset, start, next, end, len, rc;
+ ngx_uint_t shift, i, j;
+ ngx_http_sub_match_t *match;
+ ngx_http_sub_tables_t *tables;
+ ngx_http_sub_loc_conf_t *slcf;
+
+ slcf = ngx_http_get_module_loc_conf(r, ngx_http_sub_filter_module);
+ tables = ctx->tables;
+ match = ctx->matches->elts;
+
+ offset = ctx->offset;
+ end = ctx->buf->last - ctx->pos;
+
+ if (ctx->once) {
+ /* sets start and next to end */
+ offset = end + (ngx_int_t) tables->min_match_len - 1;
+ goto again;
+ }
+
+ while (offset < end) {
+
+ c = offset < 0 ? ctx->looked.data[ctx->looked.len + offset]
+ : ctx->pos[offset];
+
+ c = ngx_tolower(c);
+
+ shift = tables->shift[c];
+ if (shift > 0) {
+ offset += shift;
+ continue;
+ }
+
+ /* a potential match */
+
+ start = offset - (ngx_int_t) tables->min_match_len + 1;
+
+ i = ngx_max((ngx_uint_t) tables->index[c], ctx->index);
+ j = tables->index[c + 1];
+
+ while (i != j) {
+
+ if (slcf->once && ctx->sub && ctx->sub[i].data) {
+ goto next;
+ }
+
+ m = &match[i].match;
+
+ rc = ngx_http_sub_match(ctx, start, m);
+
+ if (rc == NGX_DECLINED) {
+ goto next;
+ }
+
+ ctx->index = i;
+
+ if (rc == NGX_AGAIN) {
+ goto again;
+ }
+
+ ctx->offset = offset + (ngx_int_t) m->len;
+ next = start + (ngx_int_t) m->len;
+ end = ngx_max(next, 0);
+ rc = NGX_OK;
+
+ goto done;
+
+ next:
+
+ i++;
+ }
+
+ offset++;
+ ctx->index = 0;
+ }
+
+ if (flush) {
+ for ( ;; ) {
+ start = offset - (ngx_int_t) tables->min_match_len + 1;
+
+ if (start >= end) {
+ break;
+ }
+
+ for (i = 0; i < ctx->matches->nelts; i++) {
+ m = &match[i].match;
+
+ if (ngx_http_sub_match(ctx, start, m) == NGX_AGAIN) {
+ goto again;
+ }
+ }
+
+ offset++;
+ }
+ }
+
+again:
+
+ ctx->offset = offset;
+ start = offset - (ngx_int_t) tables->min_match_len + 1;
+ next = start;
+ rc = NGX_AGAIN;
+
+done:
+
+ /* send [ - looked.len, start ] to client */
+
+ ctx->saved.len = ctx->looked.len + ngx_min(start, 0);
+ ngx_memcpy(ctx->saved.data, ctx->looked.data, ctx->saved.len);
+
+ ctx->copy_start = ctx->pos;
+ ctx->copy_end = ctx->pos + ngx_max(start, 0);
+
+ /* save [ next, end ] in looked */
+
+ len = ngx_min(next, 0);
+ p = ctx->looked.data;
+ p = ngx_movemem(p, p + ctx->looked.len + len, - len);
+
+ len = ngx_max(next, 0);
+ p = ngx_cpymem(p, ctx->pos + len, end - len);
+ ctx->looked.len = p - ctx->looked.data;
+
+ /* update position */
+
+ ctx->pos += end;
+ ctx->offset -= end;
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_sub_match(ngx_http_sub_ctx_t *ctx, ngx_int_t start, ngx_str_t *m)
+{
+ u_char *p, *last, *pat, *pat_end;
+
+ pat = m->data;
+ pat_end = m->data + m->len;
+
+ if (start >= 0) {
+ p = ctx->pos + start;
+
+ } else {
+ last = ctx->looked.data + ctx->looked.len;
+ p = last + start;
+
+ while (p < last && pat < pat_end) {
+ if (ngx_tolower(*p) != *pat) {
+ return NGX_DECLINED;
+ }
+
+ p++;
+ pat++;
+ }
+
+ p = ctx->pos;
+ }
+
+ while (p < ctx->buf->last && pat < pat_end) {
+ if (ngx_tolower(*p) != *pat) {
+ return NGX_DECLINED;
+ }
+
+ p++;
+ pat++;
+ }
+
+ if (pat != pat_end) {
+ /* partial match */
+ return NGX_AGAIN;
+ }
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_http_sub_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_sub_loc_conf_t *slcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_sub_pair_t *pair;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ if (value[1].len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "empty search pattern");
+ return NGX_CONF_ERROR;
+ }
+
+ if (slcf->pairs == NULL) {
+ slcf->pairs = ngx_array_create(cf->pool, 1,
+ sizeof(ngx_http_sub_pair_t));
+ if (slcf->pairs == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (slcf->pairs->nelts == 255) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "number of search patterns exceeds 255");
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_strlow(value[1].data, value[1].data, value[1].len);
+
+ pair = ngx_array_push(slcf->pairs);
+ if (pair == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &pair->match;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ccv.complex_value->lengths != NULL) {
+ slcf->dynamic = 1;
+
+ } else {
+ ngx_strlow(pair->match.value.data, pair->match.value.data,
+ pair->match.value.len);
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[2];
+ ccv.complex_value = &pair->value;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_sub_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_sub_loc_conf_t *slcf;
+
+ slcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_sub_loc_conf_t));
+ if (slcf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->dynamic = 0;
+ * conf->pairs = NULL;
+ * conf->tables = NULL;
+ * conf->types = { NULL };
+ * conf->types_keys = NULL;
+ * conf->matches = NULL;
+ */
+
+ slcf->once = NGX_CONF_UNSET;
+ slcf->last_modified = NGX_CONF_UNSET;
+
+ return slcf;
+}
+
+
+static char *
+ngx_http_sub_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_uint_t i, n;
+ ngx_http_sub_pair_t *pairs;
+ ngx_http_sub_match_t *matches;
+ ngx_http_sub_loc_conf_t *prev = parent;
+ ngx_http_sub_loc_conf_t *conf = child;
+
+ ngx_conf_merge_value(conf->once, prev->once, 1);
+ ngx_conf_merge_value(conf->last_modified, prev->last_modified, 0);
+
+ if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
+ &prev->types_keys, &prev->types,
+ ngx_http_html_default_types)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ if (conf->pairs == NULL) {
+ conf->dynamic = prev->dynamic;
+ conf->pairs = prev->pairs;
+ conf->matches = prev->matches;
+ conf->tables = prev->tables;
+ }
+
+ if (conf->pairs && conf->dynamic == 0 && conf->tables == NULL) {
+ pairs = conf->pairs->elts;
+ n = conf->pairs->nelts;
+
+ matches = ngx_palloc(cf->pool, sizeof(ngx_http_sub_match_t) * n);
+ if (matches == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 0; i < n; i++) {
+ matches[i].match = pairs[i].match.value;
+ matches[i].value = &pairs[i].value;
+ }
+
+ conf->matches = ngx_palloc(cf->pool, sizeof(ngx_array_t));
+ if (conf->matches == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->matches->elts = matches;
+ conf->matches->nelts = n;
+
+ conf->tables = ngx_palloc(cf->pool, sizeof(ngx_http_sub_tables_t));
+ if (conf->tables == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_http_sub_init_tables(conf->tables, conf->matches->elts,
+ conf->matches->nelts);
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static void
+ngx_http_sub_init_tables(ngx_http_sub_tables_t *tables,
+ ngx_http_sub_match_t *match, ngx_uint_t n)
+{
+ u_char c;
+ ngx_uint_t i, j, min, max, ch;
+
+ min = match[0].match.len;
+ max = match[0].match.len;
+
+ for (i = 1; i < n; i++) {
+ min = ngx_min(min, match[i].match.len);
+ max = ngx_max(max, match[i].match.len);
+ }
+
+ tables->min_match_len = min;
+ tables->max_match_len = max;
+
+ ngx_http_sub_cmp_index = tables->min_match_len - 1;
+ ngx_sort(match, n, sizeof(ngx_http_sub_match_t), ngx_http_sub_cmp_matches);
+
+ min = ngx_min(min, 255);
+ ngx_memset(tables->shift, min, 256);
+
+ ch = 0;
+
+ for (i = 0; i < n; i++) {
+
+ for (j = 0; j < min; j++) {
+ c = match[i].match.data[tables->min_match_len - 1 - j];
+ tables->shift[c] = ngx_min(tables->shift[c], (u_char) j);
+ }
+
+ c = match[i].match.data[tables->min_match_len - 1];
+ while (ch <= (ngx_uint_t) c) {
+ tables->index[ch++] = (u_char) i;
+ }
+ }
+
+ while (ch < 257) {
+ tables->index[ch++] = (u_char) n;
+ }
+}
+
+
+static ngx_int_t
+ngx_http_sub_cmp_matches(const void *one, const void *two)
+{
+ ngx_int_t c1, c2;
+ ngx_http_sub_match_t *first, *second;
+
+ first = (ngx_http_sub_match_t *) one;
+ second = (ngx_http_sub_match_t *) two;
+
+ c1 = first->match.data[ngx_http_sub_cmp_index];
+ c2 = second->match.data[ngx_http_sub_cmp_index];
+
+ return c1 - c2;
+}
+
+
+static ngx_int_t
+ngx_http_sub_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_sub_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_sub_body_filter;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_upstream_hash_module.c b/app/nginx/src/http/modules/ngx_http_upstream_hash_module.c
new file mode 100644
index 0000000..6c28c64
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_upstream_hash_module.c
@@ -0,0 +1,676 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ uint32_t hash;
+ ngx_str_t *server;
+} ngx_http_upstream_chash_point_t;
+
+
+typedef struct {
+ ngx_uint_t number;
+ ngx_http_upstream_chash_point_t point[1];
+} ngx_http_upstream_chash_points_t;
+
+
+typedef struct {
+ ngx_http_complex_value_t key;
+ ngx_http_upstream_chash_points_t *points;
+} ngx_http_upstream_hash_srv_conf_t;
+
+
+typedef struct {
+ /* the round robin data must be first */
+ ngx_http_upstream_rr_peer_data_t rrp;
+ ngx_http_upstream_hash_srv_conf_t *conf;
+ ngx_str_t key;
+ ngx_uint_t tries;
+ ngx_uint_t rehash;
+ uint32_t hash;
+ ngx_event_get_peer_pt get_rr_peer;
+} ngx_http_upstream_hash_peer_data_t;
+
+
+static ngx_int_t ngx_http_upstream_init_hash(ngx_conf_t *cf,
+ ngx_http_upstream_srv_conf_t *us);
+static ngx_int_t ngx_http_upstream_init_hash_peer(ngx_http_request_t *r,
+ ngx_http_upstream_srv_conf_t *us);
+static ngx_int_t ngx_http_upstream_get_hash_peer(ngx_peer_connection_t *pc,
+ void *data);
+
+static ngx_int_t ngx_http_upstream_init_chash(ngx_conf_t *cf,
+ ngx_http_upstream_srv_conf_t *us);
+static int ngx_libc_cdecl
+ ngx_http_upstream_chash_cmp_points(const void *one, const void *two);
+static ngx_uint_t ngx_http_upstream_find_chash_point(
+ ngx_http_upstream_chash_points_t *points, uint32_t hash);
+static ngx_int_t ngx_http_upstream_init_chash_peer(ngx_http_request_t *r,
+ ngx_http_upstream_srv_conf_t *us);
+static ngx_int_t ngx_http_upstream_get_chash_peer(ngx_peer_connection_t *pc,
+ void *data);
+
+static void *ngx_http_upstream_hash_create_conf(ngx_conf_t *cf);
+static char *ngx_http_upstream_hash(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_command_t ngx_http_upstream_hash_commands[] = {
+
+ { ngx_string("hash"),
+ NGX_HTTP_UPS_CONF|NGX_CONF_TAKE12,
+ ngx_http_upstream_hash,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_upstream_hash_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_http_upstream_hash_create_conf, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_upstream_hash_module = {
+ NGX_MODULE_V1,
+ &ngx_http_upstream_hash_module_ctx, /* module context */
+ ngx_http_upstream_hash_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_upstream_init_hash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
+{
+ if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ us->peer.init = ngx_http_upstream_init_hash_peer;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_init_hash_peer(ngx_http_request_t *r,
+ ngx_http_upstream_srv_conf_t *us)
+{
+ ngx_http_upstream_hash_srv_conf_t *hcf;
+ ngx_http_upstream_hash_peer_data_t *hp;
+
+ hp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_hash_peer_data_t));
+ if (hp == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->upstream->peer.data = &hp->rrp;
+
+ if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ r->upstream->peer.get = ngx_http_upstream_get_hash_peer;
+
+ hcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_hash_module);
+
+ if (ngx_http_complex_value(r, &hcf->key, &hp->key) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "upstream hash key:\"%V\"", &hp->key);
+
+ hp->conf = hcf;
+ hp->tries = 0;
+ hp->rehash = 0;
+ hp->hash = 0;
+ hp->get_rr_peer = ngx_http_upstream_get_round_robin_peer;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_get_hash_peer(ngx_peer_connection_t *pc, void *data)
+{
+ ngx_http_upstream_hash_peer_data_t *hp = data;
+
+ time_t now;
+ u_char buf[NGX_INT_T_LEN];
+ size_t size;
+ uint32_t hash;
+ ngx_int_t w;
+ uintptr_t m;
+ ngx_uint_t n, p;
+ ngx_http_upstream_rr_peer_t *peer;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "get hash peer, try: %ui", pc->tries);
+
+ ngx_http_upstream_rr_peers_wlock(hp->rrp.peers);
+
+ if (hp->tries > 20 || hp->rrp.peers->single) {
+ ngx_http_upstream_rr_peers_unlock(hp->rrp.peers);
+ return hp->get_rr_peer(pc, &hp->rrp);
+ }
+
+ now = ngx_time();
+
+ pc->cached = 0;
+ pc->connection = NULL;
+
+ for ( ;; ) {
+
+ /*
+ * Hash expression is compatible with Cache::Memcached:
+ * ((crc32([REHASH] KEY) >> 16) & 0x7fff) + PREV_HASH
+ * with REHASH omitted at the first iteration.
+ */
+
+ ngx_crc32_init(hash);
+
+ if (hp->rehash > 0) {
+ size = ngx_sprintf(buf, "%ui", hp->rehash) - buf;
+ ngx_crc32_update(&hash, buf, size);
+ }
+
+ ngx_crc32_update(&hash, hp->key.data, hp->key.len);
+ ngx_crc32_final(hash);
+
+ hash = (hash >> 16) & 0x7fff;
+
+ hp->hash += hash;
+ hp->rehash++;
+
+ w = hp->hash % hp->rrp.peers->total_weight;
+ peer = hp->rrp.peers->peer;
+ p = 0;
+
+ while (w >= peer->weight) {
+ w -= peer->weight;
+ peer = peer->next;
+ p++;
+ }
+
+ n = p / (8 * sizeof(uintptr_t));
+ m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
+
+ if (hp->rrp.tried[n] & m) {
+ goto next;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "get hash peer, value:%uD, peer:%ui", hp->hash, p);
+
+ if (peer->down) {
+ goto next;
+ }
+
+ if (peer->max_fails
+ && peer->fails >= peer->max_fails
+ && now - peer->checked <= peer->fail_timeout)
+ {
+ goto next;
+ }
+
+ if (peer->max_conns && peer->conns >= peer->max_conns) {
+ goto next;
+ }
+
+ break;
+
+ next:
+
+ if (++hp->tries > 20) {
+ ngx_http_upstream_rr_peers_unlock(hp->rrp.peers);
+ return hp->get_rr_peer(pc, &hp->rrp);
+ }
+ }
+
+ hp->rrp.current = peer;
+
+ pc->sockaddr = peer->sockaddr;
+ pc->socklen = peer->socklen;
+ pc->name = &peer->name;
+
+ peer->conns++;
+
+ if (now - peer->checked > peer->fail_timeout) {
+ peer->checked = now;
+ }
+
+ ngx_http_upstream_rr_peers_unlock(hp->rrp.peers);
+
+ hp->rrp.tried[n] |= m;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_init_chash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
+{
+ u_char *host, *port, c;
+ size_t host_len, port_len, size;
+ uint32_t hash, base_hash;
+ ngx_str_t *server;
+ ngx_uint_t npoints, i, j;
+ ngx_http_upstream_rr_peer_t *peer;
+ ngx_http_upstream_rr_peers_t *peers;
+ ngx_http_upstream_chash_points_t *points;
+ ngx_http_upstream_hash_srv_conf_t *hcf;
+ union {
+ uint32_t value;
+ u_char byte[4];
+ } prev_hash;
+
+ if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ us->peer.init = ngx_http_upstream_init_chash_peer;
+
+ peers = us->peer.data;
+ npoints = peers->total_weight * 160;
+
+ size = sizeof(ngx_http_upstream_chash_points_t)
+ + sizeof(ngx_http_upstream_chash_point_t) * (npoints - 1);
+
+ points = ngx_palloc(cf->pool, size);
+ if (points == NULL) {
+ return NGX_ERROR;
+ }
+
+ points->number = 0;
+
+ for (peer = peers->peer; peer; peer = peer->next) {
+ server = &peer->server;
+
+ /*
+ * Hash expression is compatible with Cache::Memcached::Fast:
+ * crc32(HOST \0 PORT PREV_HASH).
+ */
+
+ if (server->len >= 5
+ && ngx_strncasecmp(server->data, (u_char *) "unix:", 5) == 0)
+ {
+ host = server->data + 5;
+ host_len = server->len - 5;
+ port = NULL;
+ port_len = 0;
+ goto done;
+ }
+
+ for (j = 0; j < server->len; j++) {
+ c = server->data[server->len - j - 1];
+
+ if (c == ':') {
+ host = server->data;
+ host_len = server->len - j - 1;
+ port = server->data + server->len - j;
+ port_len = j;
+ goto done;
+ }
+
+ if (c < '0' || c > '9') {
+ break;
+ }
+ }
+
+ host = server->data;
+ host_len = server->len;
+ port = NULL;
+ port_len = 0;
+
+ done:
+
+ ngx_crc32_init(base_hash);
+ ngx_crc32_update(&base_hash, host, host_len);
+ ngx_crc32_update(&base_hash, (u_char *) "", 1);
+ ngx_crc32_update(&base_hash, port, port_len);
+
+ prev_hash.value = 0;
+ npoints = peer->weight * 160;
+
+ for (j = 0; j < npoints; j++) {
+ hash = base_hash;
+
+ ngx_crc32_update(&hash, prev_hash.byte, 4);
+ ngx_crc32_final(hash);
+
+ points->point[points->number].hash = hash;
+ points->point[points->number].server = server;
+ points->number++;
+
+#if (NGX_HAVE_LITTLE_ENDIAN)
+ prev_hash.value = hash;
+#else
+ prev_hash.byte[0] = (u_char) (hash & 0xff);
+ prev_hash.byte[1] = (u_char) ((hash >> 8) & 0xff);
+ prev_hash.byte[2] = (u_char) ((hash >> 16) & 0xff);
+ prev_hash.byte[3] = (u_char) ((hash >> 24) & 0xff);
+#endif
+ }
+ }
+
+ ngx_qsort(points->point,
+ points->number,
+ sizeof(ngx_http_upstream_chash_point_t),
+ ngx_http_upstream_chash_cmp_points);
+
+ for (i = 0, j = 1; j < points->number; j++) {
+ if (points->point[i].hash != points->point[j].hash) {
+ points->point[++i] = points->point[j];
+ }
+ }
+
+ points->number = i + 1;
+
+ hcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_hash_module);
+ hcf->points = points;
+
+ return NGX_OK;
+}
+
+
+static int ngx_libc_cdecl
+ngx_http_upstream_chash_cmp_points(const void *one, const void *two)
+{
+ ngx_http_upstream_chash_point_t *first =
+ (ngx_http_upstream_chash_point_t *) one;
+ ngx_http_upstream_chash_point_t *second =
+ (ngx_http_upstream_chash_point_t *) two;
+
+ if (first->hash < second->hash) {
+ return -1;
+
+ } else if (first->hash > second->hash) {
+ return 1;
+
+ } else {
+ return 0;
+ }
+}
+
+
+static ngx_uint_t
+ngx_http_upstream_find_chash_point(ngx_http_upstream_chash_points_t *points,
+ uint32_t hash)
+{
+ ngx_uint_t i, j, k;
+ ngx_http_upstream_chash_point_t *point;
+
+ /* find first point >= hash */
+
+ point = &points->point[0];
+
+ i = 0;
+ j = points->number;
+
+ while (i < j) {
+ k = (i + j) / 2;
+
+ if (hash > point[k].hash) {
+ i = k + 1;
+
+ } else if (hash < point[k].hash) {
+ j = k;
+
+ } else {
+ return k;
+ }
+ }
+
+ return i;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_init_chash_peer(ngx_http_request_t *r,
+ ngx_http_upstream_srv_conf_t *us)
+{
+ uint32_t hash;
+ ngx_http_upstream_hash_srv_conf_t *hcf;
+ ngx_http_upstream_hash_peer_data_t *hp;
+
+ if (ngx_http_upstream_init_hash_peer(r, us) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ r->upstream->peer.get = ngx_http_upstream_get_chash_peer;
+
+ hp = r->upstream->peer.data;
+ hcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_hash_module);
+
+ hash = ngx_crc32_long(hp->key.data, hp->key.len);
+
+ ngx_http_upstream_rr_peers_rlock(hp->rrp.peers);
+
+ hp->hash = ngx_http_upstream_find_chash_point(hcf->points, hash);
+
+ ngx_http_upstream_rr_peers_unlock(hp->rrp.peers);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_get_chash_peer(ngx_peer_connection_t *pc, void *data)
+{
+ ngx_http_upstream_hash_peer_data_t *hp = data;
+
+ time_t now;
+ intptr_t m;
+ ngx_str_t *server;
+ ngx_int_t total;
+ ngx_uint_t i, n, best_i;
+ ngx_http_upstream_rr_peer_t *peer, *best;
+ ngx_http_upstream_chash_point_t *point;
+ ngx_http_upstream_chash_points_t *points;
+ ngx_http_upstream_hash_srv_conf_t *hcf;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "get consistent hash peer, try: %ui", pc->tries);
+
+ ngx_http_upstream_rr_peers_wlock(hp->rrp.peers);
+
+ pc->cached = 0;
+ pc->connection = NULL;
+
+ now = ngx_time();
+ hcf = hp->conf;
+
+ points = hcf->points;
+ point = &points->point[0];
+
+ for ( ;; ) {
+ server = point[hp->hash % points->number].server;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "consistent hash peer:%uD, server:\"%V\"",
+ hp->hash, server);
+
+ best = NULL;
+ best_i = 0;
+ total = 0;
+
+ for (peer = hp->rrp.peers->peer, i = 0;
+ peer;
+ peer = peer->next, i++)
+ {
+ n = i / (8 * sizeof(uintptr_t));
+ m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
+
+ if (hp->rrp.tried[n] & m) {
+ continue;
+ }
+
+ if (peer->down) {
+ continue;
+ }
+
+ if (peer->server.len != server->len
+ || ngx_strncmp(peer->server.data, server->data, server->len)
+ != 0)
+ {
+ continue;
+ }
+
+ if (peer->max_fails
+ && peer->fails >= peer->max_fails
+ && now - peer->checked <= peer->fail_timeout)
+ {
+ continue;
+ }
+
+ if (peer->max_conns && peer->conns >= peer->max_conns) {
+ continue;
+ }
+
+ peer->current_weight += peer->effective_weight;
+ total += peer->effective_weight;
+
+ if (peer->effective_weight < peer->weight) {
+ peer->effective_weight++;
+ }
+
+ if (best == NULL || peer->current_weight > best->current_weight) {
+ best = peer;
+ best_i = i;
+ }
+ }
+
+ if (best) {
+ best->current_weight -= total;
+ goto found;
+ }
+
+ hp->hash++;
+ hp->tries++;
+
+ if (hp->tries >= points->number) {
+ pc->name = hp->rrp.peers->name;
+ ngx_http_upstream_rr_peers_unlock(hp->rrp.peers);
+ return NGX_BUSY;
+ }
+ }
+
+found:
+
+ hp->rrp.current = best;
+
+ pc->sockaddr = best->sockaddr;
+ pc->socklen = best->socklen;
+ pc->name = &best->name;
+
+ best->conns++;
+
+ if (now - best->checked > best->fail_timeout) {
+ best->checked = now;
+ }
+
+ ngx_http_upstream_rr_peers_unlock(hp->rrp.peers);
+
+ n = best_i / (8 * sizeof(uintptr_t));
+ m = (uintptr_t) 1 << best_i % (8 * sizeof(uintptr_t));
+
+ hp->rrp.tried[n] |= m;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_upstream_hash_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_upstream_hash_srv_conf_t *conf;
+
+ conf = ngx_palloc(cf->pool, sizeof(ngx_http_upstream_hash_srv_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->points = NULL;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_upstream_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_upstream_hash_srv_conf_t *hcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_upstream_srv_conf_t *uscf;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &hcf->key;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
+
+ if (uscf->peer.init_upstream) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "load balancing method redefined");
+ }
+
+ uscf->flags = NGX_HTTP_UPSTREAM_CREATE
+ |NGX_HTTP_UPSTREAM_WEIGHT
+ |NGX_HTTP_UPSTREAM_MAX_CONNS
+ |NGX_HTTP_UPSTREAM_MAX_FAILS
+ |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
+ |NGX_HTTP_UPSTREAM_DOWN;
+
+ if (cf->args->nelts == 2) {
+ uscf->peer.init_upstream = ngx_http_upstream_init_hash;
+
+ } else if (ngx_strcmp(value[2].data, "consistent") == 0) {
+ uscf->peer.init_upstream = ngx_http_upstream_init_chash;
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[2]);
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_upstream_ip_hash_module.c b/app/nginx/src/http/modules/ngx_http_upstream_ip_hash_module.c
new file mode 100644
index 0000000..296108f
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_upstream_ip_hash_module.c
@@ -0,0 +1,272 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ /* the round robin data must be first */
+ ngx_http_upstream_rr_peer_data_t rrp;
+
+ ngx_uint_t hash;
+
+ u_char addrlen;
+ u_char *addr;
+
+ u_char tries;
+
+ ngx_event_get_peer_pt get_rr_peer;
+} ngx_http_upstream_ip_hash_peer_data_t;
+
+
+static ngx_int_t ngx_http_upstream_init_ip_hash_peer(ngx_http_request_t *r,
+ ngx_http_upstream_srv_conf_t *us);
+static ngx_int_t ngx_http_upstream_get_ip_hash_peer(ngx_peer_connection_t *pc,
+ void *data);
+static char *ngx_http_upstream_ip_hash(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_command_t ngx_http_upstream_ip_hash_commands[] = {
+
+ { ngx_string("ip_hash"),
+ NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS,
+ ngx_http_upstream_ip_hash,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_upstream_ip_hash_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_upstream_ip_hash_module = {
+ NGX_MODULE_V1,
+ &ngx_http_upstream_ip_hash_module_ctx, /* module context */
+ ngx_http_upstream_ip_hash_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static u_char ngx_http_upstream_ip_hash_pseudo_addr[3];
+
+
+static ngx_int_t
+ngx_http_upstream_init_ip_hash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
+{
+ if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ us->peer.init = ngx_http_upstream_init_ip_hash_peer;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_init_ip_hash_peer(ngx_http_request_t *r,
+ ngx_http_upstream_srv_conf_t *us)
+{
+ struct sockaddr_in *sin;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+#endif
+ ngx_http_upstream_ip_hash_peer_data_t *iphp;
+
+ iphp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_ip_hash_peer_data_t));
+ if (iphp == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->upstream->peer.data = &iphp->rrp;
+
+ if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ r->upstream->peer.get = ngx_http_upstream_get_ip_hash_peer;
+
+ switch (r->connection->sockaddr->sa_family) {
+
+ case AF_INET:
+ sin = (struct sockaddr_in *) r->connection->sockaddr;
+ iphp->addr = (u_char *) &sin->sin_addr.s_addr;
+ iphp->addrlen = 3;
+ break;
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) r->connection->sockaddr;
+ iphp->addr = (u_char *) &sin6->sin6_addr.s6_addr;
+ iphp->addrlen = 16;
+ break;
+#endif
+
+ default:
+ iphp->addr = ngx_http_upstream_ip_hash_pseudo_addr;
+ iphp->addrlen = 3;
+ }
+
+ iphp->hash = 89;
+ iphp->tries = 0;
+ iphp->get_rr_peer = ngx_http_upstream_get_round_robin_peer;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_get_ip_hash_peer(ngx_peer_connection_t *pc, void *data)
+{
+ ngx_http_upstream_ip_hash_peer_data_t *iphp = data;
+
+ time_t now;
+ ngx_int_t w;
+ uintptr_t m;
+ ngx_uint_t i, n, p, hash;
+ ngx_http_upstream_rr_peer_t *peer;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "get ip hash peer, try: %ui", pc->tries);
+
+ /* TODO: cached */
+
+ ngx_http_upstream_rr_peers_wlock(iphp->rrp.peers);
+
+ if (iphp->tries > 20 || iphp->rrp.peers->single) {
+ ngx_http_upstream_rr_peers_unlock(iphp->rrp.peers);
+ return iphp->get_rr_peer(pc, &iphp->rrp);
+ }
+
+ now = ngx_time();
+
+ pc->cached = 0;
+ pc->connection = NULL;
+
+ hash = iphp->hash;
+
+ for ( ;; ) {
+
+ for (i = 0; i < (ngx_uint_t) iphp->addrlen; i++) {
+ hash = (hash * 113 + iphp->addr[i]) % 6271;
+ }
+
+ w = hash % iphp->rrp.peers->total_weight;
+ peer = iphp->rrp.peers->peer;
+ p = 0;
+
+ while (w >= peer->weight) {
+ w -= peer->weight;
+ peer = peer->next;
+ p++;
+ }
+
+ n = p / (8 * sizeof(uintptr_t));
+ m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
+
+ if (iphp->rrp.tried[n] & m) {
+ goto next;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "get ip hash peer, hash: %ui %04XL", p, (uint64_t) m);
+
+ if (peer->down) {
+ goto next;
+ }
+
+ if (peer->max_fails
+ && peer->fails >= peer->max_fails
+ && now - peer->checked <= peer->fail_timeout)
+ {
+ goto next;
+ }
+
+ if (peer->max_conns && peer->conns >= peer->max_conns) {
+ goto next;
+ }
+
+ break;
+
+ next:
+
+ if (++iphp->tries > 20) {
+ ngx_http_upstream_rr_peers_unlock(iphp->rrp.peers);
+ return iphp->get_rr_peer(pc, &iphp->rrp);
+ }
+ }
+
+ iphp->rrp.current = peer;
+
+ pc->sockaddr = peer->sockaddr;
+ pc->socklen = peer->socklen;
+ pc->name = &peer->name;
+
+ peer->conns++;
+
+ if (now - peer->checked > peer->fail_timeout) {
+ peer->checked = now;
+ }
+
+ ngx_http_upstream_rr_peers_unlock(iphp->rrp.peers);
+
+ iphp->rrp.tried[n] |= m;
+ iphp->hash = hash;
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_http_upstream_ip_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_upstream_srv_conf_t *uscf;
+
+ uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
+
+ if (uscf->peer.init_upstream) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "load balancing method redefined");
+ }
+
+ uscf->peer.init_upstream = ngx_http_upstream_init_ip_hash;
+
+ uscf->flags = NGX_HTTP_UPSTREAM_CREATE
+ |NGX_HTTP_UPSTREAM_WEIGHT
+ |NGX_HTTP_UPSTREAM_MAX_CONNS
+ |NGX_HTTP_UPSTREAM_MAX_FAILS
+ |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
+ |NGX_HTTP_UPSTREAM_DOWN;
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_upstream_keepalive_module.c b/app/nginx/src/http/modules/ngx_http_upstream_keepalive_module.c
new file mode 100644
index 0000000..0048e6b
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_upstream_keepalive_module.c
@@ -0,0 +1,529 @@
+
+/*
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_uint_t max_cached;
+
+ ngx_queue_t cache;
+ ngx_queue_t free;
+
+ ngx_http_upstream_init_pt original_init_upstream;
+ ngx_http_upstream_init_peer_pt original_init_peer;
+
+} ngx_http_upstream_keepalive_srv_conf_t;
+
+
+typedef struct {
+ ngx_http_upstream_keepalive_srv_conf_t *conf;
+
+ ngx_queue_t queue;
+ ngx_connection_t *connection;
+
+ socklen_t socklen;
+ ngx_sockaddr_t sockaddr;
+
+} ngx_http_upstream_keepalive_cache_t;
+
+
+typedef struct {
+ ngx_http_upstream_keepalive_srv_conf_t *conf;
+
+ ngx_http_upstream_t *upstream;
+
+ void *data;
+
+ ngx_event_get_peer_pt original_get_peer;
+ ngx_event_free_peer_pt original_free_peer;
+
+#if (NGX_HTTP_SSL)
+ ngx_event_set_peer_session_pt original_set_session;
+ ngx_event_save_peer_session_pt original_save_session;
+#endif
+
+} ngx_http_upstream_keepalive_peer_data_t;
+
+
+static ngx_int_t ngx_http_upstream_init_keepalive_peer(ngx_http_request_t *r,
+ ngx_http_upstream_srv_conf_t *us);
+static ngx_int_t ngx_http_upstream_get_keepalive_peer(ngx_peer_connection_t *pc,
+ void *data);
+static void ngx_http_upstream_free_keepalive_peer(ngx_peer_connection_t *pc,
+ void *data, ngx_uint_t state);
+
+static void ngx_http_upstream_keepalive_dummy_handler(ngx_event_t *ev);
+static void ngx_http_upstream_keepalive_close_handler(ngx_event_t *ev);
+static void ngx_http_upstream_keepalive_close(ngx_connection_t *c);
+
+#if (NGX_HTTP_SSL)
+static ngx_int_t ngx_http_upstream_keepalive_set_session(
+ ngx_peer_connection_t *pc, void *data);
+static void ngx_http_upstream_keepalive_save_session(ngx_peer_connection_t *pc,
+ void *data);
+#endif
+
+static void *ngx_http_upstream_keepalive_create_conf(ngx_conf_t *cf);
+static char *ngx_http_upstream_keepalive(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_command_t ngx_http_upstream_keepalive_commands[] = {
+
+ { ngx_string("keepalive"),
+ NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1,
+ ngx_http_upstream_keepalive,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_upstream_keepalive_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_http_upstream_keepalive_create_conf, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_upstream_keepalive_module = {
+ NGX_MODULE_V1,
+ &ngx_http_upstream_keepalive_module_ctx, /* module context */
+ ngx_http_upstream_keepalive_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_upstream_init_keepalive(ngx_conf_t *cf,
+ ngx_http_upstream_srv_conf_t *us)
+{
+ ngx_uint_t i;
+ ngx_http_upstream_keepalive_srv_conf_t *kcf;
+ ngx_http_upstream_keepalive_cache_t *cached;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0,
+ "init keepalive");
+
+ kcf = ngx_http_conf_upstream_srv_conf(us,
+ ngx_http_upstream_keepalive_module);
+
+ if (kcf->original_init_upstream(cf, us) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ kcf->original_init_peer = us->peer.init;
+
+ us->peer.init = ngx_http_upstream_init_keepalive_peer;
+
+ /* allocate cache items and add to free queue */
+
+ cached = ngx_pcalloc(cf->pool,
+ sizeof(ngx_http_upstream_keepalive_cache_t) * kcf->max_cached);
+ if (cached == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_queue_init(&kcf->cache);
+ ngx_queue_init(&kcf->free);
+
+ for (i = 0; i < kcf->max_cached; i++) {
+ ngx_queue_insert_head(&kcf->free, &cached[i].queue);
+ cached[i].conf = kcf;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_init_keepalive_peer(ngx_http_request_t *r,
+ ngx_http_upstream_srv_conf_t *us)
+{
+ ngx_http_upstream_keepalive_peer_data_t *kp;
+ ngx_http_upstream_keepalive_srv_conf_t *kcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "init keepalive peer");
+
+ kcf = ngx_http_conf_upstream_srv_conf(us,
+ ngx_http_upstream_keepalive_module);
+
+ kp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_keepalive_peer_data_t));
+ if (kp == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (kcf->original_init_peer(r, us) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ kp->conf = kcf;
+ kp->upstream = r->upstream;
+ kp->data = r->upstream->peer.data;
+ kp->original_get_peer = r->upstream->peer.get;
+ kp->original_free_peer = r->upstream->peer.free;
+
+ r->upstream->peer.data = kp;
+ r->upstream->peer.get = ngx_http_upstream_get_keepalive_peer;
+ r->upstream->peer.free = ngx_http_upstream_free_keepalive_peer;
+
+#if (NGX_HTTP_SSL)
+ kp->original_set_session = r->upstream->peer.set_session;
+ kp->original_save_session = r->upstream->peer.save_session;
+ r->upstream->peer.set_session = ngx_http_upstream_keepalive_set_session;
+ r->upstream->peer.save_session = ngx_http_upstream_keepalive_save_session;
+#endif
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_get_keepalive_peer(ngx_peer_connection_t *pc, void *data)
+{
+ ngx_http_upstream_keepalive_peer_data_t *kp = data;
+ ngx_http_upstream_keepalive_cache_t *item;
+
+ ngx_int_t rc;
+ ngx_queue_t *q, *cache;
+ ngx_connection_t *c;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "get keepalive peer");
+
+ /* ask balancer */
+
+ rc = kp->original_get_peer(pc, kp->data);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ /* search cache for suitable connection */
+
+ cache = &kp->conf->cache;
+
+ for (q = ngx_queue_head(cache);
+ q != ngx_queue_sentinel(cache);
+ q = ngx_queue_next(q))
+ {
+ item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
+ c = item->connection;
+
+ if (ngx_memn2cmp((u_char *) &item->sockaddr, (u_char *) pc->sockaddr,
+ item->socklen, pc->socklen)
+ == 0)
+ {
+ ngx_queue_remove(q);
+ ngx_queue_insert_head(&kp->conf->free, q);
+
+ goto found;
+ }
+ }
+
+ return NGX_OK;
+
+found:
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "get keepalive peer: using connection %p", c);
+
+ c->idle = 0;
+ c->sent = 0;
+ c->log = pc->log;
+ c->read->log = pc->log;
+ c->write->log = pc->log;
+ c->pool->log = pc->log;
+
+ pc->connection = c;
+ pc->cached = 1;
+
+ return NGX_DONE;
+}
+
+
+static void
+ngx_http_upstream_free_keepalive_peer(ngx_peer_connection_t *pc, void *data,
+ ngx_uint_t state)
+{
+ ngx_http_upstream_keepalive_peer_data_t *kp = data;
+ ngx_http_upstream_keepalive_cache_t *item;
+
+ ngx_queue_t *q;
+ ngx_connection_t *c;
+ ngx_http_upstream_t *u;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "free keepalive peer");
+
+ /* cache valid connections */
+
+ u = kp->upstream;
+ c = pc->connection;
+
+ if (state & NGX_PEER_FAILED
+ || c == NULL
+ || c->read->eof
+ || c->read->error
+ || c->read->timedout
+ || c->write->error
+ || c->write->timedout)
+ {
+ goto invalid;
+ }
+
+ if (!u->keepalive) {
+ goto invalid;
+ }
+
+ if (!u->request_body_sent) {
+ goto invalid;
+ }
+
+ if (ngx_terminate || ngx_exiting) {
+ goto invalid;
+ }
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ goto invalid;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "free keepalive peer: saving connection %p", c);
+
+ if (ngx_queue_empty(&kp->conf->free)) {
+
+ q = ngx_queue_last(&kp->conf->cache);
+ ngx_queue_remove(q);
+
+ item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
+
+ ngx_http_upstream_keepalive_close(item->connection);
+
+ } else {
+ q = ngx_queue_head(&kp->conf->free);
+ ngx_queue_remove(q);
+
+ item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
+ }
+
+ ngx_queue_insert_head(&kp->conf->cache, q);
+
+ item->connection = c;
+
+ pc->connection = NULL;
+
+ if (c->read->timer_set) {
+ ngx_del_timer(c->read);
+ }
+ if (c->write->timer_set) {
+ ngx_del_timer(c->write);
+ }
+
+ c->write->handler = ngx_http_upstream_keepalive_dummy_handler;
+ c->read->handler = ngx_http_upstream_keepalive_close_handler;
+
+ c->data = item;
+ c->idle = 1;
+ c->log = ngx_cycle->log;
+ c->read->log = ngx_cycle->log;
+ c->write->log = ngx_cycle->log;
+ c->pool->log = ngx_cycle->log;
+
+ item->socklen = pc->socklen;
+ ngx_memcpy(&item->sockaddr, pc->sockaddr, pc->socklen);
+
+ if (c->read->ready) {
+ ngx_http_upstream_keepalive_close_handler(c->read);
+ }
+
+invalid:
+
+ kp->original_free_peer(pc, kp->data, state);
+}
+
+
+static void
+ngx_http_upstream_keepalive_dummy_handler(ngx_event_t *ev)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+ "keepalive dummy handler");
+}
+
+
+static void
+ngx_http_upstream_keepalive_close_handler(ngx_event_t *ev)
+{
+ ngx_http_upstream_keepalive_srv_conf_t *conf;
+ ngx_http_upstream_keepalive_cache_t *item;
+
+ int n;
+ char buf[1];
+ ngx_connection_t *c;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+ "keepalive close handler");
+
+ c = ev->data;
+
+ if (c->close) {
+ goto close;
+ }
+
+ n = recv(c->fd, buf, 1, MSG_PEEK);
+
+ if (n == -1 && ngx_socket_errno == NGX_EAGAIN) {
+ ev->ready = 0;
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ goto close;
+ }
+
+ return;
+ }
+
+close:
+
+ item = c->data;
+ conf = item->conf;
+
+ ngx_http_upstream_keepalive_close(c);
+
+ ngx_queue_remove(&item->queue);
+ ngx_queue_insert_head(&conf->free, &item->queue);
+}
+
+
+static void
+ngx_http_upstream_keepalive_close(ngx_connection_t *c)
+{
+
+#if (NGX_HTTP_SSL)
+
+ if (c->ssl) {
+ c->ssl->no_wait_shutdown = 1;
+ c->ssl->no_send_shutdown = 1;
+
+ if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
+ c->ssl->handler = ngx_http_upstream_keepalive_close;
+ return;
+ }
+ }
+
+#endif
+
+ ngx_destroy_pool(c->pool);
+ ngx_close_connection(c);
+}
+
+
+#if (NGX_HTTP_SSL)
+
+static ngx_int_t
+ngx_http_upstream_keepalive_set_session(ngx_peer_connection_t *pc, void *data)
+{
+ ngx_http_upstream_keepalive_peer_data_t *kp = data;
+
+ return kp->original_set_session(pc, kp->data);
+}
+
+
+static void
+ngx_http_upstream_keepalive_save_session(ngx_peer_connection_t *pc, void *data)
+{
+ ngx_http_upstream_keepalive_peer_data_t *kp = data;
+
+ kp->original_save_session(pc, kp->data);
+ return;
+}
+
+#endif
+
+
+static void *
+ngx_http_upstream_keepalive_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_upstream_keepalive_srv_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool,
+ sizeof(ngx_http_upstream_keepalive_srv_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->original_init_upstream = NULL;
+ * conf->original_init_peer = NULL;
+ * conf->max_cached = 0;
+ */
+
+ return conf;
+}
+
+
+static char *
+ngx_http_upstream_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_upstream_srv_conf_t *uscf;
+ ngx_http_upstream_keepalive_srv_conf_t *kcf = conf;
+
+ ngx_int_t n;
+ ngx_str_t *value;
+
+ if (kcf->max_cached) {
+ return "is duplicate";
+ }
+
+ /* read options */
+
+ value = cf->args->elts;
+
+ n = ngx_atoi(value[1].data, value[1].len);
+
+ if (n == NGX_ERROR || n == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid value \"%V\" in \"%V\" directive",
+ &value[1], &cmd->name);
+ return NGX_CONF_ERROR;
+ }
+
+ kcf->max_cached = n;
+
+ uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
+
+ kcf->original_init_upstream = uscf->peer.init_upstream
+ ? uscf->peer.init_upstream
+ : ngx_http_upstream_init_round_robin;
+
+ uscf->peer.init_upstream = ngx_http_upstream_init_keepalive;
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_upstream_least_conn_module.c b/app/nginx/src/http/modules/ngx_http_upstream_least_conn_module.c
new file mode 100644
index 0000000..ebe0627
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_upstream_least_conn_module.c
@@ -0,0 +1,314 @@
+
+/*
+ * Copyright (C) Maxim Dounin
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_upstream_init_least_conn_peer(ngx_http_request_t *r,
+ ngx_http_upstream_srv_conf_t *us);
+static ngx_int_t ngx_http_upstream_get_least_conn_peer(
+ ngx_peer_connection_t *pc, void *data);
+static char *ngx_http_upstream_least_conn(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_command_t ngx_http_upstream_least_conn_commands[] = {
+
+ { ngx_string("least_conn"),
+ NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS,
+ ngx_http_upstream_least_conn,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_upstream_least_conn_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_upstream_least_conn_module = {
+ NGX_MODULE_V1,
+ &ngx_http_upstream_least_conn_module_ctx, /* module context */
+ ngx_http_upstream_least_conn_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_http_upstream_init_least_conn(ngx_conf_t *cf,
+ ngx_http_upstream_srv_conf_t *us)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0,
+ "init least conn");
+
+ if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ us->peer.init = ngx_http_upstream_init_least_conn_peer;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_init_least_conn_peer(ngx_http_request_t *r,
+ ngx_http_upstream_srv_conf_t *us)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "init least conn peer");
+
+ if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ r->upstream->peer.get = ngx_http_upstream_get_least_conn_peer;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_get_least_conn_peer(ngx_peer_connection_t *pc, void *data)
+{
+ ngx_http_upstream_rr_peer_data_t *rrp = data;
+
+ time_t now;
+ uintptr_t m;
+ ngx_int_t rc, total;
+ ngx_uint_t i, n, p, many;
+ ngx_http_upstream_rr_peer_t *peer, *best;
+ ngx_http_upstream_rr_peers_t *peers;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "get least conn peer, try: %ui", pc->tries);
+
+ if (rrp->peers->single) {
+ return ngx_http_upstream_get_round_robin_peer(pc, rrp);
+ }
+
+ pc->cached = 0;
+ pc->connection = NULL;
+
+ now = ngx_time();
+
+ peers = rrp->peers;
+
+ ngx_http_upstream_rr_peers_wlock(peers);
+
+ best = NULL;
+ total = 0;
+
+#if (NGX_SUPPRESS_WARN)
+ many = 0;
+ p = 0;
+#endif
+
+ for (peer = peers->peer, i = 0;
+ peer;
+ peer = peer->next, i++)
+ {
+ n = i / (8 * sizeof(uintptr_t));
+ m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
+
+ if (rrp->tried[n] & m) {
+ continue;
+ }
+
+ if (peer->down) {
+ continue;
+ }
+
+ if (peer->max_fails
+ && peer->fails >= peer->max_fails
+ && now - peer->checked <= peer->fail_timeout)
+ {
+ continue;
+ }
+
+ if (peer->max_conns && peer->conns >= peer->max_conns) {
+ continue;
+ }
+
+ /*
+ * select peer with least number of connections; if there are
+ * multiple peers with the same number of connections, select
+ * based on round-robin
+ */
+
+ if (best == NULL
+ || peer->conns * best->weight < best->conns * peer->weight)
+ {
+ best = peer;
+ many = 0;
+ p = i;
+
+ } else if (peer->conns * best->weight == best->conns * peer->weight) {
+ many = 1;
+ }
+ }
+
+ if (best == NULL) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "get least conn peer, no peer found");
+
+ goto failed;
+ }
+
+ if (many) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "get least conn peer, many");
+
+ for (peer = best, i = p;
+ peer;
+ peer = peer->next, i++)
+ {
+ n = i / (8 * sizeof(uintptr_t));
+ m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
+
+ if (rrp->tried[n] & m) {
+ continue;
+ }
+
+ if (peer->down) {
+ continue;
+ }
+
+ if (peer->conns * best->weight != best->conns * peer->weight) {
+ continue;
+ }
+
+ if (peer->max_fails
+ && peer->fails >= peer->max_fails
+ && now - peer->checked <= peer->fail_timeout)
+ {
+ continue;
+ }
+
+ if (peer->max_conns && peer->conns >= peer->max_conns) {
+ continue;
+ }
+
+ peer->current_weight += peer->effective_weight;
+ total += peer->effective_weight;
+
+ if (peer->effective_weight < peer->weight) {
+ peer->effective_weight++;
+ }
+
+ if (peer->current_weight > best->current_weight) {
+ best = peer;
+ p = i;
+ }
+ }
+ }
+
+ best->current_weight -= total;
+
+ if (now - best->checked > best->fail_timeout) {
+ best->checked = now;
+ }
+
+ pc->sockaddr = best->sockaddr;
+ pc->socklen = best->socklen;
+ pc->name = &best->name;
+
+ best->conns++;
+
+ rrp->current = best;
+
+ n = p / (8 * sizeof(uintptr_t));
+ m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
+
+ rrp->tried[n] |= m;
+
+ ngx_http_upstream_rr_peers_unlock(peers);
+
+ return NGX_OK;
+
+failed:
+
+ if (peers->next) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "get least conn peer, backup servers");
+
+ rrp->peers = peers->next;
+
+ n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1))
+ / (8 * sizeof(uintptr_t));
+
+ for (i = 0; i < n; i++) {
+ rrp->tried[i] = 0;
+ }
+
+ ngx_http_upstream_rr_peers_unlock(peers);
+
+ rc = ngx_http_upstream_get_least_conn_peer(pc, rrp);
+
+ if (rc != NGX_BUSY) {
+ return rc;
+ }
+
+ ngx_http_upstream_rr_peers_wlock(peers);
+ }
+
+ ngx_http_upstream_rr_peers_unlock(peers);
+
+ pc->name = peers->name;
+
+ return NGX_BUSY;
+}
+
+
+static char *
+ngx_http_upstream_least_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_upstream_srv_conf_t *uscf;
+
+ uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
+
+ if (uscf->peer.init_upstream) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "load balancing method redefined");
+ }
+
+ uscf->peer.init_upstream = ngx_http_upstream_init_least_conn;
+
+ uscf->flags = NGX_HTTP_UPSTREAM_CREATE
+ |NGX_HTTP_UPSTREAM_WEIGHT
+ |NGX_HTTP_UPSTREAM_MAX_CONNS
+ |NGX_HTTP_UPSTREAM_MAX_FAILS
+ |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
+ |NGX_HTTP_UPSTREAM_DOWN
+ |NGX_HTTP_UPSTREAM_BACKUP;
+
+ return NGX_CONF_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_upstream_zone_module.c b/app/nginx/src/http/modules/ngx_http_upstream_zone_module.c
new file mode 100644
index 0000000..7e5bd74
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_upstream_zone_module.c
@@ -0,0 +1,246 @@
+
+/*
+ * Copyright (C) Ruslan Ermilov
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static char *ngx_http_upstream_zone(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static ngx_int_t ngx_http_upstream_init_zone(ngx_shm_zone_t *shm_zone,
+ void *data);
+static ngx_http_upstream_rr_peers_t *ngx_http_upstream_zone_copy_peers(
+ ngx_slab_pool_t *shpool, ngx_http_upstream_srv_conf_t *uscf);
+
+
+static ngx_command_t ngx_http_upstream_zone_commands[] = {
+
+ { ngx_string("zone"),
+ NGX_HTTP_UPS_CONF|NGX_CONF_TAKE12,
+ ngx_http_upstream_zone,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_upstream_zone_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_upstream_zone_module = {
+ NGX_MODULE_V1,
+ &ngx_http_upstream_zone_module_ctx, /* module context */
+ ngx_http_upstream_zone_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static char *
+ngx_http_upstream_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ssize_t size;
+ ngx_str_t *value;
+ ngx_http_upstream_srv_conf_t *uscf;
+ ngx_http_upstream_main_conf_t *umcf;
+
+ uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
+ umcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_upstream_module);
+
+ value = cf->args->elts;
+
+ if (!value[1].len) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid zone name \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (cf->args->nelts == 3) {
+ size = ngx_parse_size(&value[2]);
+
+ if (size == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid zone size \"%V\"", &value[2]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (size < (ssize_t) (8 * ngx_pagesize)) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "zone \"%V\" is too small", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+ size = 0;
+ }
+
+ uscf->shm_zone = ngx_shared_memory_add(cf, &value[1], size,
+ &ngx_http_upstream_module);
+ if (uscf->shm_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ uscf->shm_zone->init = ngx_http_upstream_init_zone;
+ uscf->shm_zone->data = umcf;
+
+ uscf->shm_zone->noreuse = 1;
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_init_zone(ngx_shm_zone_t *shm_zone, void *data)
+{
+ size_t len;
+ ngx_uint_t i;
+ ngx_slab_pool_t *shpool;
+ ngx_http_upstream_rr_peers_t *peers, **peersp;
+ ngx_http_upstream_srv_conf_t *uscf, **uscfp;
+ ngx_http_upstream_main_conf_t *umcf;
+
+ shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+ umcf = shm_zone->data;
+ uscfp = umcf->upstreams.elts;
+
+ if (shm_zone->shm.exists) {
+ peers = shpool->data;
+
+ for (i = 0; i < umcf->upstreams.nelts; i++) {
+ uscf = uscfp[i];
+
+ if (uscf->shm_zone != shm_zone) {
+ continue;
+ }
+
+ uscf->peer.data = peers;
+ peers = peers->zone_next;
+ }
+
+ return NGX_OK;
+ }
+
+ len = sizeof(" in upstream zone \"\"") + shm_zone->shm.name.len;
+
+ shpool->log_ctx = ngx_slab_alloc(shpool, len);
+ if (shpool->log_ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_sprintf(shpool->log_ctx, " in upstream zone \"%V\"%Z",
+ &shm_zone->shm.name);
+
+
+ /* copy peers to shared memory */
+
+ peersp = (ngx_http_upstream_rr_peers_t **) (void *) &shpool->data;
+
+ for (i = 0; i < umcf->upstreams.nelts; i++) {
+ uscf = uscfp[i];
+
+ if (uscf->shm_zone != shm_zone) {
+ continue;
+ }
+
+ peers = ngx_http_upstream_zone_copy_peers(shpool, uscf);
+ if (peers == NULL) {
+ return NGX_ERROR;
+ }
+
+ *peersp = peers;
+ peersp = &peers->zone_next;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_http_upstream_rr_peers_t *
+ngx_http_upstream_zone_copy_peers(ngx_slab_pool_t *shpool,
+ ngx_http_upstream_srv_conf_t *uscf)
+{
+ ngx_http_upstream_rr_peer_t *peer, **peerp;
+ ngx_http_upstream_rr_peers_t *peers, *backup;
+
+ peers = ngx_slab_alloc(shpool, sizeof(ngx_http_upstream_rr_peers_t));
+ if (peers == NULL) {
+ return NULL;
+ }
+
+ ngx_memcpy(peers, uscf->peer.data, sizeof(ngx_http_upstream_rr_peers_t));
+
+ peers->shpool = shpool;
+
+ for (peerp = &peers->peer; *peerp; peerp = &peer->next) {
+ /* pool is unlocked */
+ peer = ngx_slab_calloc_locked(shpool,
+ sizeof(ngx_http_upstream_rr_peer_t));
+ if (peer == NULL) {
+ return NULL;
+ }
+
+ ngx_memcpy(peer, *peerp, sizeof(ngx_http_upstream_rr_peer_t));
+
+ *peerp = peer;
+ }
+
+ if (peers->next == NULL) {
+ goto done;
+ }
+
+ backup = ngx_slab_alloc(shpool, sizeof(ngx_http_upstream_rr_peers_t));
+ if (backup == NULL) {
+ return NULL;
+ }
+
+ ngx_memcpy(backup, peers->next, sizeof(ngx_http_upstream_rr_peers_t));
+
+ backup->shpool = shpool;
+
+ for (peerp = &backup->peer; *peerp; peerp = &peer->next) {
+ /* pool is unlocked */
+ peer = ngx_slab_calloc_locked(shpool,
+ sizeof(ngx_http_upstream_rr_peer_t));
+ if (peer == NULL) {
+ return NULL;
+ }
+
+ ngx_memcpy(peer, *peerp, sizeof(ngx_http_upstream_rr_peer_t));
+
+ *peerp = peer;
+ }
+
+ peers->next = backup;
+
+done:
+
+ uscf->peer.data = peers;
+
+ return peers;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_userid_filter_module.c b/app/nginx/src/http/modules/ngx_http_userid_filter_module.c
new file mode 100644
index 0000000..0dbacba
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_userid_filter_module.c
@@ -0,0 +1,842 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_USERID_OFF 0
+#define NGX_HTTP_USERID_LOG 1
+#define NGX_HTTP_USERID_V1 2
+#define NGX_HTTP_USERID_ON 3
+
+/* 31 Dec 2037 23:55:55 GMT */
+#define NGX_HTTP_USERID_MAX_EXPIRES 2145916555
+
+
+typedef struct {
+ ngx_uint_t enable;
+
+ ngx_int_t service;
+
+ ngx_str_t name;
+ ngx_str_t domain;
+ ngx_str_t path;
+ ngx_str_t p3p;
+
+ time_t expires;
+
+ u_char mark;
+} ngx_http_userid_conf_t;
+
+
+typedef struct {
+ uint32_t uid_got[4];
+ uint32_t uid_set[4];
+ ngx_str_t cookie;
+ ngx_uint_t reset;
+} ngx_http_userid_ctx_t;
+
+
+static ngx_http_userid_ctx_t *ngx_http_userid_get_uid(ngx_http_request_t *r,
+ ngx_http_userid_conf_t *conf);
+static ngx_int_t ngx_http_userid_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, ngx_str_t *name, uint32_t *uid);
+static ngx_int_t ngx_http_userid_set_uid(ngx_http_request_t *r,
+ ngx_http_userid_ctx_t *ctx, ngx_http_userid_conf_t *conf);
+static ngx_int_t ngx_http_userid_create_uid(ngx_http_request_t *r,
+ ngx_http_userid_ctx_t *ctx, ngx_http_userid_conf_t *conf);
+
+static ngx_int_t ngx_http_userid_add_variables(ngx_conf_t *cf);
+static ngx_int_t ngx_http_userid_init(ngx_conf_t *cf);
+static void *ngx_http_userid_create_conf(ngx_conf_t *cf);
+static char *ngx_http_userid_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_http_userid_domain(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_userid_path(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_userid_expires(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_userid_p3p(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_userid_mark(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static ngx_int_t ngx_http_userid_init_worker(ngx_cycle_t *cycle);
+
+
+
+static uint32_t start_value;
+static uint32_t sequencer_v1 = 1;
+static uint32_t sequencer_v2 = 0x03030302;
+
+
+static u_char expires[] = "; expires=Thu, 31-Dec-37 23:55:55 GMT";
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+
+
+static ngx_conf_enum_t ngx_http_userid_state[] = {
+ { ngx_string("off"), NGX_HTTP_USERID_OFF },
+ { ngx_string("log"), NGX_HTTP_USERID_LOG },
+ { ngx_string("v1"), NGX_HTTP_USERID_V1 },
+ { ngx_string("on"), NGX_HTTP_USERID_ON },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_post_handler_pt ngx_http_userid_domain_p =
+ ngx_http_userid_domain;
+static ngx_conf_post_handler_pt ngx_http_userid_path_p = ngx_http_userid_path;
+static ngx_conf_post_handler_pt ngx_http_userid_p3p_p = ngx_http_userid_p3p;
+
+
+static ngx_command_t ngx_http_userid_commands[] = {
+
+ { ngx_string("userid"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_userid_conf_t, enable),
+ ngx_http_userid_state },
+
+ { ngx_string("userid_service"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_userid_conf_t, service),
+ NULL },
+
+ { ngx_string("userid_name"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_userid_conf_t, name),
+ NULL },
+
+ { ngx_string("userid_domain"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_userid_conf_t, domain),
+ &ngx_http_userid_domain_p },
+
+ { ngx_string("userid_path"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_userid_conf_t, path),
+ &ngx_http_userid_path_p },
+
+ { ngx_string("userid_expires"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_userid_expires,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("userid_p3p"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_userid_conf_t, p3p),
+ &ngx_http_userid_p3p_p },
+
+ { ngx_string("userid_mark"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_userid_mark,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_userid_filter_module_ctx = {
+ ngx_http_userid_add_variables, /* preconfiguration */
+ ngx_http_userid_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_userid_create_conf, /* create location configuration */
+ ngx_http_userid_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_userid_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_userid_filter_module_ctx, /* module context */
+ ngx_http_userid_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ ngx_http_userid_init_worker, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_http_userid_got = ngx_string("uid_got");
+static ngx_str_t ngx_http_userid_set = ngx_string("uid_set");
+static ngx_str_t ngx_http_userid_reset = ngx_string("uid_reset");
+static ngx_uint_t ngx_http_userid_reset_index;
+
+
+static ngx_int_t
+ngx_http_userid_filter(ngx_http_request_t *r)
+{
+ ngx_http_userid_ctx_t *ctx;
+ ngx_http_userid_conf_t *conf;
+
+ if (r != r->main) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_userid_filter_module);
+
+ if (conf->enable < NGX_HTTP_USERID_V1) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx = ngx_http_userid_get_uid(r, conf);
+
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_http_userid_set_uid(r, ctx, conf) == NGX_OK) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_userid_got_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_http_userid_ctx_t *ctx;
+ ngx_http_userid_conf_t *conf;
+
+ conf = ngx_http_get_module_loc_conf(r->main, ngx_http_userid_filter_module);
+
+ if (conf->enable == NGX_HTTP_USERID_OFF) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ ctx = ngx_http_userid_get_uid(r->main, conf);
+
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ctx->uid_got[3] != 0) {
+ return ngx_http_userid_variable(r->main, v, &conf->name, ctx->uid_got);
+ }
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_set_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_http_userid_ctx_t *ctx;
+ ngx_http_userid_conf_t *conf;
+
+ conf = ngx_http_get_module_loc_conf(r->main, ngx_http_userid_filter_module);
+
+ if (conf->enable < NGX_HTTP_USERID_V1) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ ctx = ngx_http_userid_get_uid(r->main, conf);
+
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_http_userid_create_uid(r->main, ctx, conf) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ctx->uid_set[3] == 0) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ return ngx_http_userid_variable(r->main, v, &conf->name, ctx->uid_set);
+}
+
+
+static ngx_http_userid_ctx_t *
+ngx_http_userid_get_uid(ngx_http_request_t *r, ngx_http_userid_conf_t *conf)
+{
+ ngx_int_t n;
+ ngx_str_t src, dst;
+ ngx_table_elt_t **cookies;
+ ngx_http_userid_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_userid_filter_module);
+
+ if (ctx) {
+ return ctx;
+ }
+
+ if (ctx == NULL) {
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_userid_ctx_t));
+ if (ctx == NULL) {
+ return NULL;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_userid_filter_module);
+ }
+
+ n = ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &conf->name,
+ &ctx->cookie);
+ if (n == NGX_DECLINED) {
+ return ctx;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "uid cookie: \"%V\"", &ctx->cookie);
+
+ if (ctx->cookie.len < 22) {
+ cookies = r->headers_in.cookies.elts;
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client sent too short userid cookie \"%V\"",
+ &cookies[n]->value);
+ return ctx;
+ }
+
+ src = ctx->cookie;
+
+ /*
+ * we have to limit the encoded string to 22 characters because
+ * 1) cookie may be marked by "userid_mark",
+ * 2) and there are already the millions cookies with a garbage
+ * instead of the correct base64 trail "=="
+ */
+
+ src.len = 22;
+
+ dst.data = (u_char *) ctx->uid_got;
+
+ if (ngx_decode_base64(&dst, &src) == NGX_ERROR) {
+ cookies = r->headers_in.cookies.elts;
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client sent invalid userid cookie \"%V\"",
+ &cookies[n]->value);
+ return ctx;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "uid: %08XD%08XD%08XD%08XD",
+ ctx->uid_got[0], ctx->uid_got[1],
+ ctx->uid_got[2], ctx->uid_got[3]);
+
+ return ctx;
+}
+
+
+static ngx_int_t
+ngx_http_userid_set_uid(ngx_http_request_t *r, ngx_http_userid_ctx_t *ctx,
+ ngx_http_userid_conf_t *conf)
+{
+ u_char *cookie, *p;
+ size_t len;
+ ngx_str_t src, dst;
+ ngx_table_elt_t *set_cookie, *p3p;
+
+ if (ngx_http_userid_create_uid(r, ctx, conf) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ctx->uid_set[3] == 0) {
+ return NGX_OK;
+ }
+
+ len = conf->name.len + 1 + ngx_base64_encoded_length(16) + conf->path.len;
+
+ if (conf->expires) {
+ len += sizeof(expires) - 1 + 2;
+ }
+
+ if (conf->domain.len) {
+ len += conf->domain.len;
+ }
+
+ cookie = ngx_pnalloc(r->pool, len);
+ if (cookie == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_copy(cookie, conf->name.data, conf->name.len);
+ *p++ = '=';
+
+ if (ctx->uid_got[3] == 0 || ctx->reset) {
+ src.len = 16;
+ src.data = (u_char *) ctx->uid_set;
+ dst.data = p;
+
+ ngx_encode_base64(&dst, &src);
+
+ p += dst.len;
+
+ if (conf->mark) {
+ *(p - 2) = conf->mark;
+ }
+
+ } else {
+ p = ngx_cpymem(p, ctx->cookie.data, 22);
+ *p++ = conf->mark;
+ *p++ = '=';
+ }
+
+ if (conf->expires == NGX_HTTP_USERID_MAX_EXPIRES) {
+ p = ngx_cpymem(p, expires, sizeof(expires) - 1);
+
+ } else if (conf->expires) {
+ p = ngx_cpymem(p, expires, sizeof("; expires=") - 1);
+ p = ngx_http_cookie_time(p, ngx_time() + conf->expires);
+ }
+
+ p = ngx_copy(p, conf->domain.data, conf->domain.len);
+
+ p = ngx_copy(p, conf->path.data, conf->path.len);
+
+ set_cookie = ngx_list_push(&r->headers_out.headers);
+ if (set_cookie == NULL) {
+ return NGX_ERROR;
+ }
+
+ set_cookie->hash = 1;
+ ngx_str_set(&set_cookie->key, "Set-Cookie");
+ set_cookie->value.len = p - cookie;
+ set_cookie->value.data = cookie;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "uid cookie: \"%V\"", &set_cookie->value);
+
+ if (conf->p3p.len == 0) {
+ return NGX_OK;
+ }
+
+ p3p = ngx_list_push(&r->headers_out.headers);
+ if (p3p == NULL) {
+ return NGX_ERROR;
+ }
+
+ p3p->hash = 1;
+ ngx_str_set(&p3p->key, "P3P");
+ p3p->value = conf->p3p;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_create_uid(ngx_http_request_t *r, ngx_http_userid_ctx_t *ctx,
+ ngx_http_userid_conf_t *conf)
+{
+ ngx_connection_t *c;
+ struct sockaddr_in *sin;
+ ngx_http_variable_value_t *vv;
+#if (NGX_HAVE_INET6)
+ u_char *p;
+ struct sockaddr_in6 *sin6;
+#endif
+
+ if (ctx->uid_set[3] != 0) {
+ return NGX_OK;
+ }
+
+ if (ctx->uid_got[3] != 0) {
+
+ vv = ngx_http_get_indexed_variable(r, ngx_http_userid_reset_index);
+
+ if (vv->len == 0 || (vv->len == 1 && vv->data[0] == '0')) {
+
+ if (conf->mark == '\0'
+ || (ctx->cookie.len > 23
+ && ctx->cookie.data[22] == conf->mark
+ && ctx->cookie.data[23] == '='))
+ {
+ return NGX_OK;
+ }
+
+ ctx->uid_set[0] = ctx->uid_got[0];
+ ctx->uid_set[1] = ctx->uid_got[1];
+ ctx->uid_set[2] = ctx->uid_got[2];
+ ctx->uid_set[3] = ctx->uid_got[3];
+
+ return NGX_OK;
+
+ } else {
+ ctx->reset = 1;
+
+ if (vv->len == 3 && ngx_strncmp(vv->data, "log", 3) == 0) {
+ ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+ "userid cookie \"%V=%08XD%08XD%08XD%08XD\" was reset",
+ &conf->name, ctx->uid_got[0], ctx->uid_got[1],
+ ctx->uid_got[2], ctx->uid_got[3]);
+ }
+ }
+ }
+
+ /*
+ * TODO: in the threaded mode the sequencers should be in TLS and their
+ * ranges should be divided between threads
+ */
+
+ if (conf->enable == NGX_HTTP_USERID_V1) {
+ if (conf->service == NGX_CONF_UNSET) {
+ ctx->uid_set[0] = 0;
+ } else {
+ ctx->uid_set[0] = conf->service;
+ }
+ ctx->uid_set[1] = (uint32_t) ngx_time();
+ ctx->uid_set[2] = start_value;
+ ctx->uid_set[3] = sequencer_v1;
+ sequencer_v1 += 0x100;
+
+ } else {
+ if (conf->service == NGX_CONF_UNSET) {
+
+ c = r->connection;
+
+ if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ switch (c->local_sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) c->local_sockaddr;
+
+ p = (u_char *) &ctx->uid_set[0];
+
+ *p++ = sin6->sin6_addr.s6_addr[12];
+ *p++ = sin6->sin6_addr.s6_addr[13];
+ *p++ = sin6->sin6_addr.s6_addr[14];
+ *p = sin6->sin6_addr.s6_addr[15];
+
+ break;
+#endif
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) c->local_sockaddr;
+ ctx->uid_set[0] = sin->sin_addr.s_addr;
+ break;
+ }
+
+ } else {
+ ctx->uid_set[0] = htonl(conf->service);
+ }
+
+ ctx->uid_set[1] = htonl((uint32_t) ngx_time());
+ ctx->uid_set[2] = htonl(start_value);
+ ctx->uid_set[3] = htonl(sequencer_v2);
+ sequencer_v2 += 0x100;
+ if (sequencer_v2 < 0x03030302) {
+ sequencer_v2 = 0x03030302;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ ngx_str_t *name, uint32_t *uid)
+{
+ v->len = name->len + sizeof("=00001111222233334444555566667777") - 1;
+ v->data = ngx_pnalloc(r->pool, v->len);
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ ngx_sprintf(v->data, "%V=%08XD%08XD%08XD%08XD",
+ name, uid[0], uid[1], uid[2], uid[3]);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_reset_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ *v = ngx_http_variable_null_value;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_add_variables(ngx_conf_t *cf)
+{
+ ngx_int_t n;
+ ngx_http_variable_t *var;
+
+ var = ngx_http_add_variable(cf, &ngx_http_userid_got, 0);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = ngx_http_userid_got_variable;
+
+ var = ngx_http_add_variable(cf, &ngx_http_userid_set, 0);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = ngx_http_userid_set_variable;
+
+ var = ngx_http_add_variable(cf, &ngx_http_userid_reset,
+ NGX_HTTP_VAR_CHANGEABLE);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = ngx_http_userid_reset_variable;
+
+ n = ngx_http_get_variable_index(cf, &ngx_http_userid_reset);
+ if (n == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_userid_reset_index = n;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_userid_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_userid_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_userid_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->name = { 0, NULL };
+ * conf->domain = { 0, NULL };
+ * conf->path = { 0, NULL };
+ * conf->p3p = { 0, NULL };
+ */
+
+ conf->enable = NGX_CONF_UNSET_UINT;
+ conf->service = NGX_CONF_UNSET;
+ conf->expires = NGX_CONF_UNSET;
+ conf->mark = (u_char) '\xFF';
+
+ return conf;
+}
+
+
+static char *
+ngx_http_userid_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_userid_conf_t *prev = parent;
+ ngx_http_userid_conf_t *conf = child;
+
+ ngx_conf_merge_uint_value(conf->enable, prev->enable,
+ NGX_HTTP_USERID_OFF);
+
+ ngx_conf_merge_str_value(conf->name, prev->name, "uid");
+ ngx_conf_merge_str_value(conf->domain, prev->domain, "");
+ ngx_conf_merge_str_value(conf->path, prev->path, "; path=/");
+ ngx_conf_merge_str_value(conf->p3p, prev->p3p, "");
+
+ ngx_conf_merge_value(conf->service, prev->service, NGX_CONF_UNSET);
+ ngx_conf_merge_sec_value(conf->expires, prev->expires, 0);
+
+ if (conf->mark == (u_char) '\xFF') {
+ if (prev->mark == (u_char) '\xFF') {
+ conf->mark = '\0';
+ } else {
+ conf->mark = prev->mark;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_userid_filter;
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_http_userid_domain(ngx_conf_t *cf, void *post, void *data)
+{
+ ngx_str_t *domain = data;
+
+ u_char *p, *new;
+
+ if (ngx_strcmp(domain->data, "none") == 0) {
+ ngx_str_set(domain, "");
+ return NGX_CONF_OK;
+ }
+
+ new = ngx_pnalloc(cf->pool, sizeof("; domain=") - 1 + domain->len);
+ if (new == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ p = ngx_cpymem(new, "; domain=", sizeof("; domain=") - 1);
+ ngx_memcpy(p, domain->data, domain->len);
+
+ domain->len += sizeof("; domain=") - 1;
+ domain->data = new;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_userid_path(ngx_conf_t *cf, void *post, void *data)
+{
+ ngx_str_t *path = data;
+
+ u_char *p, *new;
+
+ new = ngx_pnalloc(cf->pool, sizeof("; path=") - 1 + path->len);
+ if (new == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ p = ngx_cpymem(new, "; path=", sizeof("; path=") - 1);
+ ngx_memcpy(p, path->data, path->len);
+
+ path->len += sizeof("; path=") - 1;
+ path->data = new;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_userid_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_userid_conf_t *ucf = conf;
+
+ ngx_str_t *value;
+
+ if (ucf->expires != NGX_CONF_UNSET) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "max") == 0) {
+ ucf->expires = NGX_HTTP_USERID_MAX_EXPIRES;
+ return NGX_CONF_OK;
+ }
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ ucf->expires = 0;
+ return NGX_CONF_OK;
+ }
+
+ ucf->expires = ngx_parse_time(&value[1], 1);
+ if (ucf->expires == (time_t) NGX_ERROR) {
+ return "invalid value";
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_userid_p3p(ngx_conf_t *cf, void *post, void *data)
+{
+ ngx_str_t *p3p = data;
+
+ if (ngx_strcmp(p3p->data, "none") == 0) {
+ ngx_str_set(p3p, "");
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_userid_mark(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_userid_conf_t *ucf = conf;
+
+ ngx_str_t *value;
+
+ if (ucf->mark != (u_char) '\xFF') {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ ucf->mark = '\0';
+ return NGX_CONF_OK;
+ }
+
+ if (value[1].len != 1
+ || !((value[1].data[0] >= '0' && value[1].data[0] <= '9')
+ || (value[1].data[0] >= 'A' && value[1].data[0] <= 'Z')
+ || (value[1].data[0] >= 'a' && value[1].data[0] <= 'z')
+ || value[1].data[0] == '='))
+ {
+ return "value must be \"off\" or a single letter, digit or \"=\"";
+ }
+
+ ucf->mark = value[1].data[0];
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_userid_init_worker(ngx_cycle_t *cycle)
+{
+ struct timeval tp;
+
+ ngx_gettimeofday(&tp);
+
+ /* use the most significant usec part that fits to 16 bits */
+ start_value = (((uint32_t) tp.tv_usec / 20) << 16) | ngx_pid;
+
+ return NGX_OK;
+}
diff --git a/app/nginx/src/http/modules/ngx_http_uwsgi_module.c b/app/nginx/src/http/modules/ngx_http_uwsgi_module.c
new file mode 100644
index 0000000..b7e7c12
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_uwsgi_module.c
@@ -0,0 +1,2393 @@
+
+/*
+ * Copyright (C) Unbit S.a.s. 2009-2010
+ * Copyright (C) 2008 Manlio Perillo (manlio.perillo@gmail.com)
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_array_t caches; /* ngx_http_file_cache_t * */
+} ngx_http_uwsgi_main_conf_t;
+
+
+typedef struct {
+ ngx_array_t *flushes;
+ ngx_array_t *lengths;
+ ngx_array_t *values;
+ ngx_uint_t number;
+ ngx_hash_t hash;
+} ngx_http_uwsgi_params_t;
+
+
+typedef struct {
+ ngx_http_upstream_conf_t upstream;
+
+ ngx_http_uwsgi_params_t params;
+#if (NGX_HTTP_CACHE)
+ ngx_http_uwsgi_params_t params_cache;
+#endif
+ ngx_array_t *params_source;
+
+ ngx_array_t *uwsgi_lengths;
+ ngx_array_t *uwsgi_values;
+
+#if (NGX_HTTP_CACHE)
+ ngx_http_complex_value_t cache_key;
+#endif
+
+ ngx_str_t uwsgi_string;
+
+ ngx_uint_t modifier1;
+ ngx_uint_t modifier2;
+
+#if (NGX_HTTP_SSL)
+ ngx_uint_t ssl;
+ ngx_uint_t ssl_protocols;
+ ngx_str_t ssl_ciphers;
+ ngx_uint_t ssl_verify_depth;
+ ngx_str_t ssl_trusted_certificate;
+ ngx_str_t ssl_crl;
+ ngx_str_t ssl_certificate;
+ ngx_str_t ssl_certificate_key;
+ ngx_array_t *ssl_passwords;
+#endif
+} ngx_http_uwsgi_loc_conf_t;
+
+
+static ngx_int_t ngx_http_uwsgi_eval(ngx_http_request_t *r,
+ ngx_http_uwsgi_loc_conf_t *uwcf);
+static ngx_int_t ngx_http_uwsgi_create_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_uwsgi_reinit_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_uwsgi_process_status_line(ngx_http_request_t *r);
+static ngx_int_t ngx_http_uwsgi_process_header(ngx_http_request_t *r);
+static void ngx_http_uwsgi_abort_request(ngx_http_request_t *r);
+static void ngx_http_uwsgi_finalize_request(ngx_http_request_t *r,
+ ngx_int_t rc);
+
+static void *ngx_http_uwsgi_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_uwsgi_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_uwsgi_merge_loc_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static ngx_int_t ngx_http_uwsgi_init_params(ngx_conf_t *cf,
+ ngx_http_uwsgi_loc_conf_t *conf, ngx_http_uwsgi_params_t *params,
+ ngx_keyval_t *default_params);
+
+static char *ngx_http_uwsgi_pass(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_uwsgi_store(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+#if (NGX_HTTP_CACHE)
+static ngx_int_t ngx_http_uwsgi_create_key(ngx_http_request_t *r);
+static char *ngx_http_uwsgi_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_uwsgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+#endif
+
+#if (NGX_HTTP_SSL)
+static char *ngx_http_uwsgi_ssl_password_file(ngx_conf_t *cf,
+ ngx_command_t *cmd, void *conf);
+static ngx_int_t ngx_http_uwsgi_set_ssl(ngx_conf_t *cf,
+ ngx_http_uwsgi_loc_conf_t *uwcf);
+#endif
+
+
+static ngx_conf_num_bounds_t ngx_http_uwsgi_modifier_bounds = {
+ ngx_conf_check_num_bounds, 0, 255
+};
+
+
+static ngx_conf_bitmask_t ngx_http_uwsgi_next_upstream_masks[] = {
+ { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
+ { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT },
+ { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER },
+ { ngx_string("non_idempotent"), NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT },
+ { ngx_string("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 },
+ { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 },
+ { ngx_string("http_403"), NGX_HTTP_UPSTREAM_FT_HTTP_403 },
+ { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 },
+ { ngx_string("http_429"), NGX_HTTP_UPSTREAM_FT_HTTP_429 },
+ { ngx_string("updating"), NGX_HTTP_UPSTREAM_FT_UPDATING },
+ { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF },
+ { ngx_null_string, 0 }
+};
+
+
+#if (NGX_HTTP_SSL)
+
+static ngx_conf_bitmask_t ngx_http_uwsgi_ssl_protocols[] = {
+ { ngx_string("SSLv2"), NGX_SSL_SSLv2 },
+ { ngx_string("SSLv3"), NGX_SSL_SSLv3 },
+ { ngx_string("TLSv1"), NGX_SSL_TLSv1 },
+ { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 },
+ { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 },
+ { ngx_null_string, 0 }
+};
+
+#endif
+
+
+ngx_module_t ngx_http_uwsgi_module;
+
+
+static ngx_command_t ngx_http_uwsgi_commands[] = {
+
+ { ngx_string("uwsgi_pass"),
+ NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
+ ngx_http_uwsgi_pass,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("uwsgi_modifier1"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, modifier1),
+ &ngx_http_uwsgi_modifier_bounds },
+
+ { ngx_string("uwsgi_modifier2"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, modifier2),
+ &ngx_http_uwsgi_modifier_bounds },
+
+ { ngx_string("uwsgi_store"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_uwsgi_store,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("uwsgi_store_access"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
+ ngx_conf_set_access_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.store_access),
+ NULL },
+
+ { ngx_string("uwsgi_buffering"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.buffering),
+ NULL },
+
+ { ngx_string("uwsgi_request_buffering"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.request_buffering),
+ NULL },
+
+ { ngx_string("uwsgi_ignore_client_abort"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ignore_client_abort),
+ NULL },
+
+ { ngx_string("uwsgi_bind"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+ ngx_http_upstream_bind_set_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.local),
+ NULL },
+
+ { ngx_string("uwsgi_connect_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.connect_timeout),
+ NULL },
+
+ { ngx_string("uwsgi_send_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.send_timeout),
+ NULL },
+
+ { ngx_string("uwsgi_buffer_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.buffer_size),
+ NULL },
+
+ { ngx_string("uwsgi_pass_request_headers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.pass_request_headers),
+ NULL },
+
+ { ngx_string("uwsgi_pass_request_body"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.pass_request_body),
+ NULL },
+
+ { ngx_string("uwsgi_intercept_errors"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.intercept_errors),
+ NULL },
+
+ { ngx_string("uwsgi_read_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.read_timeout),
+ NULL },
+
+ { ngx_string("uwsgi_buffers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_conf_set_bufs_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.bufs),
+ NULL },
+
+ { ngx_string("uwsgi_busy_buffers_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.busy_buffers_size_conf),
+ NULL },
+
+ { ngx_string("uwsgi_force_ranges"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.force_ranges),
+ NULL },
+
+ { ngx_string("uwsgi_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_uwsgi_loc_conf_t, upstream.limit_rate),
+ NULL },
+
+#if (NGX_HTTP_CACHE)
+
+ { ngx_string("uwsgi_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_uwsgi_cache,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("uwsgi_cache_key"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_uwsgi_cache_key,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("uwsgi_cache_path"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE,
+ ngx_http_file_cache_set_slot,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_main_conf_t, caches),
+ &ngx_http_uwsgi_module },
+
+ { ngx_string("uwsgi_cache_bypass"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_set_predicate_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_bypass),
+ NULL },
+
+ { ngx_string("uwsgi_no_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_set_predicate_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.no_cache),
+ NULL },
+
+ { ngx_string("uwsgi_cache_valid"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_file_cache_valid_set_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_valid),
+ NULL },
+
+ { ngx_string("uwsgi_cache_min_uses"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_min_uses),
+ NULL },
+
+ { ngx_string("uwsgi_cache_max_range_offset"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_off_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_max_range_offset),
+ NULL },
+
+ { ngx_string("uwsgi_cache_use_stale"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_use_stale),
+ &ngx_http_uwsgi_next_upstream_masks },
+
+ { ngx_string("uwsgi_cache_methods"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_methods),
+ &ngx_http_upstream_cache_method_mask },
+
+ { ngx_string("uwsgi_cache_lock"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_lock),
+ NULL },
+
+ { ngx_string("uwsgi_cache_lock_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_lock_timeout),
+ NULL },
+
+ { ngx_string("uwsgi_cache_lock_age"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_lock_age),
+ NULL },
+
+ { ngx_string("uwsgi_cache_revalidate"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_revalidate),
+ NULL },
+
+ { ngx_string("uwsgi_cache_background_update"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.cache_background_update),
+ NULL },
+
+#endif
+
+ { ngx_string("uwsgi_temp_path"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
+ ngx_conf_set_path_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.temp_path),
+ NULL },
+
+ { ngx_string("uwsgi_max_temp_file_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.max_temp_file_size_conf),
+ NULL },
+
+ { ngx_string("uwsgi_temp_file_write_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.temp_file_write_size_conf),
+ NULL },
+
+ { ngx_string("uwsgi_next_upstream"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.next_upstream),
+ &ngx_http_uwsgi_next_upstream_masks },
+
+ { ngx_string("uwsgi_next_upstream_tries"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.next_upstream_tries),
+ NULL },
+
+ { ngx_string("uwsgi_next_upstream_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.next_upstream_timeout),
+ NULL },
+
+ { ngx_string("uwsgi_param"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE23,
+ ngx_http_upstream_param_set_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, params_source),
+ NULL },
+
+ { ngx_string("uwsgi_string"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, uwsgi_string),
+ NULL },
+
+ { ngx_string("uwsgi_pass_header"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.pass_headers),
+ NULL },
+
+ { ngx_string("uwsgi_hide_header"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.hide_headers),
+ NULL },
+
+ { ngx_string("uwsgi_ignore_headers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ignore_headers),
+ &ngx_http_upstream_ignore_headers_masks },
+
+#if (NGX_HTTP_SSL)
+
+ { ngx_string("uwsgi_ssl_session_reuse"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ssl_session_reuse),
+ NULL },
+
+ { ngx_string("uwsgi_ssl_protocols"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, ssl_protocols),
+ &ngx_http_uwsgi_ssl_protocols },
+
+ { ngx_string("uwsgi_ssl_ciphers"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, ssl_ciphers),
+ NULL },
+
+ { ngx_string("uwsgi_ssl_name"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_set_complex_value_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ssl_name),
+ NULL },
+
+ { ngx_string("uwsgi_ssl_server_name"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ssl_server_name),
+ NULL },
+
+ { ngx_string("uwsgi_ssl_verify"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ssl_verify),
+ NULL },
+
+ { ngx_string("uwsgi_ssl_verify_depth"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, ssl_verify_depth),
+ NULL },
+
+ { ngx_string("uwsgi_ssl_trusted_certificate"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, ssl_trusted_certificate),
+ NULL },
+
+ { ngx_string("uwsgi_ssl_crl"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, ssl_crl),
+ NULL },
+
+ { ngx_string("uwsgi_ssl_certificate"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, ssl_certificate),
+ NULL },
+
+ { ngx_string("uwsgi_ssl_certificate_key"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_uwsgi_loc_conf_t, ssl_certificate_key),
+ NULL },
+
+ { ngx_string("uwsgi_ssl_password_file"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_uwsgi_ssl_password_file,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+#endif
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_uwsgi_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ ngx_http_uwsgi_create_main_conf, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_uwsgi_create_loc_conf, /* create location configuration */
+ ngx_http_uwsgi_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_uwsgi_module = {
+ NGX_MODULE_V1,
+ &ngx_http_uwsgi_module_ctx, /* module context */
+ ngx_http_uwsgi_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_http_uwsgi_hide_headers[] = {
+ ngx_string("X-Accel-Expires"),
+ ngx_string("X-Accel-Redirect"),
+ ngx_string("X-Accel-Limit-Rate"),
+ ngx_string("X-Accel-Buffering"),
+ ngx_string("X-Accel-Charset"),
+ ngx_null_string
+};
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_keyval_t ngx_http_uwsgi_cache_headers[] = {
+ { ngx_string("HTTP_IF_MODIFIED_SINCE"),
+ ngx_string("$upstream_cache_last_modified") },
+ { ngx_string("HTTP_IF_UNMODIFIED_SINCE"), ngx_string("") },
+ { ngx_string("HTTP_IF_NONE_MATCH"), ngx_string("$upstream_cache_etag") },
+ { ngx_string("HTTP_IF_MATCH"), ngx_string("") },
+ { ngx_string("HTTP_RANGE"), ngx_string("") },
+ { ngx_string("HTTP_IF_RANGE"), ngx_string("") },
+ { ngx_null_string, ngx_null_string }
+};
+
+#endif
+
+
+static ngx_path_init_t ngx_http_uwsgi_temp_path = {
+ ngx_string(NGX_HTTP_UWSGI_TEMP_PATH), { 1, 2, 0 }
+};
+
+
+static ngx_int_t
+ngx_http_uwsgi_handler(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+ ngx_http_status_t *status;
+ ngx_http_upstream_t *u;
+ ngx_http_uwsgi_loc_conf_t *uwcf;
+#if (NGX_HTTP_CACHE)
+ ngx_http_uwsgi_main_conf_t *uwmcf;
+#endif
+
+ if (ngx_http_upstream_create(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ status = ngx_pcalloc(r->pool, sizeof(ngx_http_status_t));
+ if (status == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_http_set_ctx(r, status, ngx_http_uwsgi_module);
+
+ uwcf = ngx_http_get_module_loc_conf(r, ngx_http_uwsgi_module);
+
+ u = r->upstream;
+
+ if (uwcf->uwsgi_lengths == NULL) {
+
+#if (NGX_HTTP_SSL)
+ u->ssl = (uwcf->upstream.ssl != NULL);
+
+ if (u->ssl) {
+ ngx_str_set(&u->schema, "suwsgi://");
+
+ } else {
+ ngx_str_set(&u->schema, "uwsgi://");
+ }
+#else
+ ngx_str_set(&u->schema, "uwsgi://");
+#endif
+
+ } else {
+ if (ngx_http_uwsgi_eval(r, uwcf) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ u->output.tag = (ngx_buf_tag_t) &ngx_http_uwsgi_module;
+
+ u->conf = &uwcf->upstream;
+
+#if (NGX_HTTP_CACHE)
+ uwmcf = ngx_http_get_module_main_conf(r, ngx_http_uwsgi_module);
+
+ u->caches = &uwmcf->caches;
+ u->create_key = ngx_http_uwsgi_create_key;
+#endif
+
+ u->create_request = ngx_http_uwsgi_create_request;
+ u->reinit_request = ngx_http_uwsgi_reinit_request;
+ u->process_header = ngx_http_uwsgi_process_status_line;
+ u->abort_request = ngx_http_uwsgi_abort_request;
+ u->finalize_request = ngx_http_uwsgi_finalize_request;
+ r->state = 0;
+
+ u->buffering = uwcf->upstream.buffering;
+
+ u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t));
+ if (u->pipe == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ u->pipe->input_filter = ngx_event_pipe_copy_input_filter;
+ u->pipe->input_ctx = r;
+
+ if (!uwcf->upstream.request_buffering
+ && uwcf->upstream.pass_request_body
+ && !r->headers_in.chunked)
+ {
+ r->request_body_no_buffering = 1;
+ }
+
+ rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);
+
+ if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+ return rc;
+ }
+
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_uwsgi_eval(ngx_http_request_t *r, ngx_http_uwsgi_loc_conf_t * uwcf)
+{
+ size_t add;
+ ngx_url_t url;
+ ngx_http_upstream_t *u;
+
+ ngx_memzero(&url, sizeof(ngx_url_t));
+
+ if (ngx_http_script_run(r, &url.url, uwcf->uwsgi_lengths->elts, 0,
+ uwcf->uwsgi_values->elts)
+ == NULL)
+ {
+ return NGX_ERROR;
+ }
+
+ if (url.url.len > 8
+ && ngx_strncasecmp(url.url.data, (u_char *) "uwsgi://", 8) == 0)
+ {
+ add = 8;
+
+ } else if (url.url.len > 9
+ && ngx_strncasecmp(url.url.data, (u_char *) "suwsgi://", 9) == 0)
+ {
+
+#if (NGX_HTTP_SSL)
+ add = 9;
+ r->upstream->ssl = 1;
+#else
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "suwsgi protocol requires SSL support");
+ return NGX_ERROR;
+#endif
+
+ } else {
+ add = 0;
+ }
+
+ u = r->upstream;
+
+ if (add) {
+ u->schema.len = add;
+ u->schema.data = url.url.data;
+
+ url.url.data += add;
+ url.url.len -= add;
+
+ } else {
+ ngx_str_set(&u->schema, "uwsgi://");
+ }
+
+ url.no_resolve = 1;
+
+ if (ngx_parse_url(r->pool, &url) != NGX_OK) {
+ if (url.err) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "%s in upstream \"%V\"", url.err, &url.url);
+ }
+
+ return NGX_ERROR;
+ }
+
+ u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t));
+ if (u->resolved == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (url.addrs) {
+ u->resolved->sockaddr = url.addrs[0].sockaddr;
+ u->resolved->socklen = url.addrs[0].socklen;
+ u->resolved->name = url.addrs[0].name;
+ u->resolved->naddrs = 1;
+ }
+
+ u->resolved->host = url.host;
+ u->resolved->port = url.port;
+ u->resolved->no_port = url.no_port;
+
+ return NGX_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_int_t
+ngx_http_uwsgi_create_key(ngx_http_request_t *r)
+{
+ ngx_str_t *key;
+ ngx_http_uwsgi_loc_conf_t *uwcf;
+
+ key = ngx_array_push(&r->cache->keys);
+ if (key == NULL) {
+ return NGX_ERROR;
+ }
+
+ uwcf = ngx_http_get_module_loc_conf(r, ngx_http_uwsgi_module);
+
+ if (ngx_http_complex_value(r, &uwcf->cache_key, key) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_uwsgi_create_request(ngx_http_request_t *r)
+{
+ u_char ch, *lowcase_key;
+ size_t key_len, val_len, len, allocated;
+ ngx_uint_t i, n, hash, skip_empty, header_params;
+ ngx_buf_t *b;
+ ngx_chain_t *cl, *body;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header, **ignored;
+ ngx_http_uwsgi_params_t *params;
+ ngx_http_script_code_pt code;
+ ngx_http_script_engine_t e, le;
+ ngx_http_uwsgi_loc_conf_t *uwcf;
+ ngx_http_script_len_code_pt lcode;
+
+ len = 0;
+ header_params = 0;
+ ignored = NULL;
+
+ uwcf = ngx_http_get_module_loc_conf(r, ngx_http_uwsgi_module);
+
+#if (NGX_HTTP_CACHE)
+ params = r->upstream->cacheable ? &uwcf->params_cache : &uwcf->params;
+#else
+ params = &uwcf->params;
+#endif
+
+ if (params->lengths) {
+ ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
+
+ ngx_http_script_flush_no_cacheable_variables(r, params->flushes);
+ le.flushed = 1;
+
+ le.ip = params->lengths->elts;
+ le.request = r;
+
+ while (*(uintptr_t *) le.ip) {
+
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ key_len = lcode(&le);
+
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ skip_empty = lcode(&le);
+
+ for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode (&le)) {
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ }
+ le.ip += sizeof(uintptr_t);
+
+ if (skip_empty && val_len == 0) {
+ continue;
+ }
+
+ len += 2 + key_len + 2 + val_len;
+ }
+ }
+
+ if (uwcf->upstream.pass_request_headers) {
+
+ allocated = 0;
+ lowcase_key = NULL;
+
+ if (params->number) {
+ n = 0;
+ part = &r->headers_in.headers.part;
+
+ while (part) {
+ n += part->nelts;
+ part = part->next;
+ }
+
+ ignored = ngx_palloc(r->pool, n * sizeof(void *));
+ if (ignored == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ part = &r->headers_in.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */ ; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (params->number) {
+ if (allocated < header[i].key.len) {
+ allocated = header[i].key.len + 16;
+ lowcase_key = ngx_pnalloc(r->pool, allocated);
+ if (lowcase_key == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ hash = 0;
+
+ for (n = 0; n < header[i].key.len; n++) {
+ ch = header[i].key.data[n];
+
+ if (ch >= 'A' && ch <= 'Z') {
+ ch |= 0x20;
+
+ } else if (ch == '-') {
+ ch = '_';
+ }
+
+ hash = ngx_hash(hash, ch);
+ lowcase_key[n] = ch;
+ }
+
+ if (ngx_hash_find(&params->hash, hash, lowcase_key, n)) {
+ ignored[header_params++] = &header[i];
+ continue;
+ }
+ }
+
+ len += 2 + sizeof("HTTP_") - 1 + header[i].key.len
+ + 2 + header[i].value.len;
+ }
+ }
+
+ len += uwcf->uwsgi_string.len;
+
+#if 0
+ /* allow custom uwsgi packet */
+ if (len > 0 && len < 2) {
+ ngx_log_error (NGX_LOG_ALERT, r->connection->log, 0,
+ "uwsgi request is too little: %uz", len);
+ return NGX_ERROR;
+ }
+#endif
+
+ b = ngx_create_temp_buf(r->pool, len + 4);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+
+ *b->last++ = (u_char) uwcf->modifier1;
+ *b->last++ = (u_char) (len & 0xff);
+ *b->last++ = (u_char) ((len >> 8) & 0xff);
+ *b->last++ = (u_char) uwcf->modifier2;
+
+ if (params->lengths) {
+ ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
+
+ e.ip = params->values->elts;
+ e.pos = b->last;
+ e.request = r;
+ e.flushed = 1;
+
+ le.ip = params->lengths->elts;
+
+ while (*(uintptr_t *) le.ip) {
+
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ key_len = (u_char) lcode (&le);
+
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ skip_empty = lcode(&le);
+
+ for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) {
+ lcode = *(ngx_http_script_len_code_pt *) le.ip;
+ }
+ le.ip += sizeof(uintptr_t);
+
+ if (skip_empty && val_len == 0) {
+ e.skip = 1;
+
+ while (*(uintptr_t *) e.ip) {
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code((ngx_http_script_engine_t *) &e);
+ }
+ e.ip += sizeof(uintptr_t);
+
+ e.skip = 0;
+
+ continue;
+ }
+
+ *e.pos++ = (u_char) (key_len & 0xff);
+ *e.pos++ = (u_char) ((key_len >> 8) & 0xff);
+
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code((ngx_http_script_engine_t *) & e);
+
+ *e.pos++ = (u_char) (val_len & 0xff);
+ *e.pos++ = (u_char) ((val_len >> 8) & 0xff);
+
+ while (*(uintptr_t *) e.ip) {
+ code = *(ngx_http_script_code_pt *) e.ip;
+ code((ngx_http_script_engine_t *) & e);
+ }
+
+ e.ip += sizeof(uintptr_t);
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "uwsgi param: \"%*s: %*s\"",
+ key_len, e.pos - (key_len + 2 + val_len),
+ val_len, e.pos - val_len);
+ }
+
+ b->last = e.pos;
+ }
+
+ if (uwcf->upstream.pass_request_headers) {
+
+ part = &r->headers_in.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */ ; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ for (n = 0; n < header_params; n++) {
+ if (&header[i] == ignored[n]) {
+ goto next;
+ }
+ }
+
+ key_len = sizeof("HTTP_") - 1 + header[i].key.len;
+ *b->last++ = (u_char) (key_len & 0xff);
+ *b->last++ = (u_char) ((key_len >> 8) & 0xff);
+
+ b->last = ngx_cpymem(b->last, "HTTP_", sizeof("HTTP_") - 1);
+ for (n = 0; n < header[i].key.len; n++) {
+ ch = header[i].key.data[n];
+
+ if (ch >= 'a' && ch <= 'z') {
+ ch &= ~0x20;
+
+ } else if (ch == '-') {
+ ch = '_';
+ }
+
+ *b->last++ = ch;
+ }
+
+ val_len = header[i].value.len;
+ *b->last++ = (u_char) (val_len & 0xff);
+ *b->last++ = (u_char) ((val_len >> 8) & 0xff);
+ b->last = ngx_copy(b->last, header[i].value.data, val_len);
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "uwsgi param: \"%*s: %*s\"",
+ key_len, b->last - (key_len + 2 + val_len),
+ val_len, b->last - val_len);
+ next:
+
+ continue;
+ }
+ }
+
+ b->last = ngx_copy(b->last, uwcf->uwsgi_string.data,
+ uwcf->uwsgi_string.len);
+
+ if (r->request_body_no_buffering) {
+ r->upstream->request_bufs = cl;
+
+ } else if (uwcf->upstream.pass_request_body) {
+ body = r->upstream->request_bufs;
+ r->upstream->request_bufs = cl;
+
+ while (body) {
+ b = ngx_alloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(b, body->buf, sizeof(ngx_buf_t));
+
+ cl->next = ngx_alloc_chain_link(r->pool);
+ if (cl->next == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl = cl->next;
+ cl->buf = b;
+
+ body = body->next;
+ }
+
+ } else {
+ r->upstream->request_bufs = cl;
+ }
+
+ cl->next = NULL;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_uwsgi_reinit_request(ngx_http_request_t *r)
+{
+ ngx_http_status_t *status;
+
+ status = ngx_http_get_module_ctx(r, ngx_http_uwsgi_module);
+
+ if (status == NULL) {
+ return NGX_OK;
+ }
+
+ status->code = 0;
+ status->count = 0;
+ status->start = NULL;
+ status->end = NULL;
+
+ r->upstream->process_header = ngx_http_uwsgi_process_status_line;
+ r->state = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_uwsgi_process_status_line(ngx_http_request_t *r)
+{
+ size_t len;
+ ngx_int_t rc;
+ ngx_http_status_t *status;
+ ngx_http_upstream_t *u;
+
+ status = ngx_http_get_module_ctx(r, ngx_http_uwsgi_module);
+
+ if (status == NULL) {
+ return NGX_ERROR;
+ }
+
+ u = r->upstream;
+
+ rc = ngx_http_parse_status_line(r, &u->buffer, status);
+
+ if (rc == NGX_AGAIN) {
+ return rc;
+ }
+
+ if (rc == NGX_ERROR) {
+ u->process_header = ngx_http_uwsgi_process_header;
+ return ngx_http_uwsgi_process_header(r);
+ }
+
+ if (u->state && u->state->status == 0) {
+ u->state->status = status->code;
+ }
+
+ u->headers_in.status_n = status->code;
+
+ len = status->end - status->start;
+ u->headers_in.status_line.len = len;
+
+ u->headers_in.status_line.data = ngx_pnalloc(r->pool, len);
+ if (u->headers_in.status_line.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(u->headers_in.status_line.data, status->start, len);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http uwsgi status %ui \"%V\"",
+ u->headers_in.status_n, &u->headers_in.status_line);
+
+ u->process_header = ngx_http_uwsgi_process_header;
+
+ return ngx_http_uwsgi_process_header(r);
+}
+
+
+static ngx_int_t
+ngx_http_uwsgi_process_header(ngx_http_request_t *r)
+{
+ ngx_str_t *status_line;
+ ngx_int_t rc, status;
+ ngx_table_elt_t *h;
+ ngx_http_upstream_t *u;
+ ngx_http_upstream_header_t *hh;
+ ngx_http_upstream_main_conf_t *umcf;
+
+ umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
+
+ for ( ;; ) {
+
+ rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1);
+
+ if (rc == NGX_OK) {
+
+ /* a header line has been parsed successfully */
+
+ h = ngx_list_push(&r->upstream->headers_in.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->hash = r->header_hash;
+
+ h->key.len = r->header_name_end - r->header_name_start;
+ h->value.len = r->header_end - r->header_start;
+
+ h->key.data = ngx_pnalloc(r->pool,
+ h->key.len + 1 + h->value.len + 1
+ + h->key.len);
+ if (h->key.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->value.data = h->key.data + h->key.len + 1;
+ h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1;
+
+ ngx_memcpy(h->key.data, r->header_name_start, h->key.len);
+ h->key.data[h->key.len] = '\0';
+ ngx_memcpy(h->value.data, r->header_start, h->value.len);
+ h->value.data[h->value.len] = '\0';
+
+ if (h->key.len == r->lowcase_index) {
+ ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
+
+ } else {
+ ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
+ }
+
+ hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
+ h->lowcase_key, h->key.len);
+
+ if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http uwsgi header: \"%V: %V\"", &h->key, &h->value);
+
+ continue;
+ }
+
+ if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+ /* a whole header has been parsed successfully */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http uwsgi header done");
+
+ u = r->upstream;
+
+ if (u->headers_in.status_n) {
+ goto done;
+ }
+
+ if (u->headers_in.status) {
+ status_line = &u->headers_in.status->value;
+
+ status = ngx_atoi(status_line->data, 3);
+ if (status == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent invalid status \"%V\"",
+ status_line);
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+ }
+
+ u->headers_in.status_n = status;
+ u->headers_in.status_line = *status_line;
+
+ } else if (u->headers_in.location) {
+ u->headers_in.status_n = 302;
+ ngx_str_set(&u->headers_in.status_line,
+ "302 Moved Temporarily");
+
+ } else {
+ u->headers_in.status_n = 200;
+ ngx_str_set(&u->headers_in.status_line, "200 OK");
+ }
+
+ if (u->state && u->state->status == 0) {
+ u->state->status = u->headers_in.status_n;
+ }
+
+ done:
+
+ if (u->headers_in.status_n == NGX_HTTP_SWITCHING_PROTOCOLS
+ && r->headers_in.upgrade)
+ {
+ u->upgrade = 1;
+ }
+
+ return NGX_OK;
+ }
+
+ if (rc == NGX_AGAIN) {
+ return NGX_AGAIN;
+ }
+
+ /* there was error while a header line parsing */
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "upstream sent invalid header");
+
+ return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+ }
+}
+
+
+static void
+ngx_http_uwsgi_abort_request(ngx_http_request_t *r)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "abort http uwsgi request");
+
+ return;
+}
+
+
+static void
+ngx_http_uwsgi_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "finalize http uwsgi request");
+
+ return;
+}
+
+
+static void *
+ngx_http_uwsgi_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_uwsgi_main_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_uwsgi_main_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+#if (NGX_HTTP_CACHE)
+ if (ngx_array_init(&conf->caches, cf->pool, 4,
+ sizeof(ngx_http_file_cache_t *))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+#endif
+
+ return conf;
+}
+
+
+static void *
+ngx_http_uwsgi_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_uwsgi_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_uwsgi_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ conf->modifier1 = NGX_CONF_UNSET_UINT;
+ conf->modifier2 = NGX_CONF_UNSET_UINT;
+
+ conf->upstream.store = NGX_CONF_UNSET;
+ conf->upstream.store_access = NGX_CONF_UNSET_UINT;
+ conf->upstream.next_upstream_tries = NGX_CONF_UNSET_UINT;
+ conf->upstream.buffering = NGX_CONF_UNSET;
+ conf->upstream.request_buffering = NGX_CONF_UNSET;
+ conf->upstream.ignore_client_abort = NGX_CONF_UNSET;
+ conf->upstream.force_ranges = NGX_CONF_UNSET;
+
+ conf->upstream.local = NGX_CONF_UNSET_PTR;
+
+ conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC;
+
+ conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE;
+ conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE;
+ conf->upstream.limit_rate = NGX_CONF_UNSET_SIZE;
+
+ conf->upstream.busy_buffers_size_conf = NGX_CONF_UNSET_SIZE;
+ conf->upstream.max_temp_file_size_conf = NGX_CONF_UNSET_SIZE;
+ conf->upstream.temp_file_write_size_conf = NGX_CONF_UNSET_SIZE;
+
+ conf->upstream.pass_request_headers = NGX_CONF_UNSET;
+ conf->upstream.pass_request_body = NGX_CONF_UNSET;
+
+#if (NGX_HTTP_CACHE)
+ conf->upstream.cache = NGX_CONF_UNSET;
+ conf->upstream.cache_min_uses = NGX_CONF_UNSET_UINT;
+ conf->upstream.cache_max_range_offset = NGX_CONF_UNSET;
+ conf->upstream.cache_bypass = NGX_CONF_UNSET_PTR;
+ conf->upstream.no_cache = NGX_CONF_UNSET_PTR;
+ conf->upstream.cache_valid = NGX_CONF_UNSET_PTR;
+ conf->upstream.cache_lock = NGX_CONF_UNSET;
+ conf->upstream.cache_lock_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.cache_lock_age = NGX_CONF_UNSET_MSEC;
+ conf->upstream.cache_revalidate = NGX_CONF_UNSET;
+ conf->upstream.cache_background_update = NGX_CONF_UNSET;
+#endif
+
+ conf->upstream.hide_headers = NGX_CONF_UNSET_PTR;
+ conf->upstream.pass_headers = NGX_CONF_UNSET_PTR;
+
+ conf->upstream.intercept_errors = NGX_CONF_UNSET;
+
+#if (NGX_HTTP_SSL)
+ conf->upstream.ssl_session_reuse = NGX_CONF_UNSET;
+ conf->upstream.ssl_server_name = NGX_CONF_UNSET;
+ conf->upstream.ssl_verify = NGX_CONF_UNSET;
+ conf->ssl_verify_depth = NGX_CONF_UNSET_UINT;
+ conf->ssl_passwords = NGX_CONF_UNSET_PTR;
+#endif
+
+ /* "uwsgi_cyclic_temp_file" is disabled */
+ conf->upstream.cyclic_temp_file = 0;
+
+ conf->upstream.change_buffering = 1;
+
+ ngx_str_set(&conf->upstream.module, "uwsgi");
+
+ return conf;
+}
+
+
+static char *
+ngx_http_uwsgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_uwsgi_loc_conf_t *prev = parent;
+ ngx_http_uwsgi_loc_conf_t *conf = child;
+
+ size_t size;
+ ngx_int_t rc;
+ ngx_hash_init_t hash;
+ ngx_http_core_loc_conf_t *clcf;
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->upstream.store > 0) {
+ conf->upstream.cache = 0;
+ }
+
+ if (conf->upstream.cache > 0) {
+ conf->upstream.store = 0;
+ }
+
+#endif
+
+ if (conf->upstream.store == NGX_CONF_UNSET) {
+ ngx_conf_merge_value(conf->upstream.store, prev->upstream.store, 0);
+
+ conf->upstream.store_lengths = prev->upstream.store_lengths;
+ conf->upstream.store_values = prev->upstream.store_values;
+ }
+
+ ngx_conf_merge_uint_value(conf->upstream.store_access,
+ prev->upstream.store_access, 0600);
+
+ ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries,
+ prev->upstream.next_upstream_tries, 0);
+
+ ngx_conf_merge_value(conf->upstream.buffering,
+ prev->upstream.buffering, 1);
+
+ ngx_conf_merge_value(conf->upstream.request_buffering,
+ prev->upstream.request_buffering, 1);
+
+ ngx_conf_merge_value(conf->upstream.ignore_client_abort,
+ prev->upstream.ignore_client_abort, 0);
+
+ ngx_conf_merge_value(conf->upstream.force_ranges,
+ prev->upstream.force_ranges, 0);
+
+ ngx_conf_merge_ptr_value(conf->upstream.local,
+ prev->upstream.local, NULL);
+
+ ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
+ prev->upstream.connect_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->upstream.send_timeout,
+ prev->upstream.send_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->upstream.read_timeout,
+ prev->upstream.read_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->upstream.next_upstream_timeout,
+ prev->upstream.next_upstream_timeout, 0);
+
+ ngx_conf_merge_size_value(conf->upstream.send_lowat,
+ prev->upstream.send_lowat, 0);
+
+ ngx_conf_merge_size_value(conf->upstream.buffer_size,
+ prev->upstream.buffer_size,
+ (size_t) ngx_pagesize);
+
+ ngx_conf_merge_size_value(conf->upstream.limit_rate,
+ prev->upstream.limit_rate, 0);
+
+
+ ngx_conf_merge_bufs_value(conf->upstream.bufs, prev->upstream.bufs,
+ 8, ngx_pagesize);
+
+ if (conf->upstream.bufs.num < 2) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "there must be at least 2 \"uwsgi_buffers\"");
+ return NGX_CONF_ERROR;
+ }
+
+
+ size = conf->upstream.buffer_size;
+ if (size < conf->upstream.bufs.size) {
+ size = conf->upstream.bufs.size;
+ }
+
+
+ ngx_conf_merge_size_value(conf->upstream.busy_buffers_size_conf,
+ prev->upstream.busy_buffers_size_conf,
+ NGX_CONF_UNSET_SIZE);
+
+ if (conf->upstream.busy_buffers_size_conf == NGX_CONF_UNSET_SIZE) {
+ conf->upstream.busy_buffers_size = 2 * size;
+ } else {
+ conf->upstream.busy_buffers_size =
+ conf->upstream.busy_buffers_size_conf;
+ }
+
+ if (conf->upstream.busy_buffers_size < size) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"uwsgi_busy_buffers_size\" must be equal to or greater "
+ "than the maximum of the value of \"uwsgi_buffer_size\" and "
+ "one of the \"uwsgi_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+ if (conf->upstream.busy_buffers_size
+ > (conf->upstream.bufs.num - 1) * conf->upstream.bufs.size)
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"uwsgi_busy_buffers_size\" must be less than "
+ "the size of all \"uwsgi_buffers\" minus one buffer");
+
+ return NGX_CONF_ERROR;
+ }
+
+
+ ngx_conf_merge_size_value(conf->upstream.temp_file_write_size_conf,
+ prev->upstream.temp_file_write_size_conf,
+ NGX_CONF_UNSET_SIZE);
+
+ if (conf->upstream.temp_file_write_size_conf == NGX_CONF_UNSET_SIZE) {
+ conf->upstream.temp_file_write_size = 2 * size;
+ } else {
+ conf->upstream.temp_file_write_size =
+ conf->upstream.temp_file_write_size_conf;
+ }
+
+ if (conf->upstream.temp_file_write_size < size) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"uwsgi_temp_file_write_size\" must be equal to or greater than "
+ "the maximum of the value of \"uwsgi_buffer_size\" and "
+ "one of the \"uwsgi_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+
+ ngx_conf_merge_size_value(conf->upstream.max_temp_file_size_conf,
+ prev->upstream.max_temp_file_size_conf,
+ NGX_CONF_UNSET_SIZE);
+
+ if (conf->upstream.max_temp_file_size_conf == NGX_CONF_UNSET_SIZE) {
+ conf->upstream.max_temp_file_size = 1024 * 1024 * 1024;
+ } else {
+ conf->upstream.max_temp_file_size =
+ conf->upstream.max_temp_file_size_conf;
+ }
+
+ if (conf->upstream.max_temp_file_size != 0
+ && conf->upstream.max_temp_file_size < size)
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"uwsgi_max_temp_file_size\" must be equal to zero to disable "
+ "temporary files usage or must be equal to or greater than "
+ "the maximum of the value of \"uwsgi_buffer_size\" and "
+ "one of the \"uwsgi_buffers\"");
+
+ return NGX_CONF_ERROR;
+ }
+
+
+ ngx_conf_merge_bitmask_value(conf->upstream.ignore_headers,
+ prev->upstream.ignore_headers,
+ NGX_CONF_BITMASK_SET);
+
+
+ ngx_conf_merge_bitmask_value(conf->upstream.next_upstream,
+ prev->upstream.next_upstream,
+ (NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_ERROR
+ |NGX_HTTP_UPSTREAM_FT_TIMEOUT));
+
+ if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) {
+ conf->upstream.next_upstream = NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_OFF;
+ }
+
+ if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path,
+ prev->upstream.temp_path,
+ &ngx_http_uwsgi_temp_path)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->upstream.cache == NGX_CONF_UNSET) {
+ ngx_conf_merge_value(conf->upstream.cache,
+ prev->upstream.cache, 0);
+
+ conf->upstream.cache_zone = prev->upstream.cache_zone;
+ conf->upstream.cache_value = prev->upstream.cache_value;
+ }
+
+ if (conf->upstream.cache_zone && conf->upstream.cache_zone->data == NULL) {
+ ngx_shm_zone_t *shm_zone;
+
+ shm_zone = conf->upstream.cache_zone;
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"uwsgi_cache\" zone \"%V\" is unknown",
+ &shm_zone->shm.name);
+
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_uint_value(conf->upstream.cache_min_uses,
+ prev->upstream.cache_min_uses, 1);
+
+ ngx_conf_merge_off_value(conf->upstream.cache_max_range_offset,
+ prev->upstream.cache_max_range_offset,
+ NGX_MAX_OFF_T_VALUE);
+
+ ngx_conf_merge_bitmask_value(conf->upstream.cache_use_stale,
+ prev->upstream.cache_use_stale,
+ (NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_OFF));
+
+ if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_OFF) {
+ conf->upstream.cache_use_stale = NGX_CONF_BITMASK_SET
+ |NGX_HTTP_UPSTREAM_FT_OFF;
+ }
+
+ if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_ERROR) {
+ conf->upstream.cache_use_stale |= NGX_HTTP_UPSTREAM_FT_NOLIVE;
+ }
+
+ if (conf->upstream.cache_methods == 0) {
+ conf->upstream.cache_methods = prev->upstream.cache_methods;
+ }
+
+ conf->upstream.cache_methods |= NGX_HTTP_GET|NGX_HTTP_HEAD;
+
+ ngx_conf_merge_ptr_value(conf->upstream.cache_bypass,
+ prev->upstream.cache_bypass, NULL);
+
+ ngx_conf_merge_ptr_value(conf->upstream.no_cache,
+ prev->upstream.no_cache, NULL);
+
+ ngx_conf_merge_ptr_value(conf->upstream.cache_valid,
+ prev->upstream.cache_valid, NULL);
+
+ if (conf->cache_key.value.data == NULL) {
+ conf->cache_key = prev->cache_key;
+ }
+
+ if (conf->upstream.cache && conf->cache_key.value.data == NULL) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "no \"uwsgi_cache_key\" for \"uwsgi_cache\"");
+ }
+
+ ngx_conf_merge_value(conf->upstream.cache_lock,
+ prev->upstream.cache_lock, 0);
+
+ ngx_conf_merge_msec_value(conf->upstream.cache_lock_timeout,
+ prev->upstream.cache_lock_timeout, 5000);
+
+ ngx_conf_merge_msec_value(conf->upstream.cache_lock_age,
+ prev->upstream.cache_lock_age, 5000);
+
+ ngx_conf_merge_value(conf->upstream.cache_revalidate,
+ prev->upstream.cache_revalidate, 0);
+
+ ngx_conf_merge_value(conf->upstream.cache_background_update,
+ prev->upstream.cache_background_update, 0);
+
+#endif
+
+ ngx_conf_merge_value(conf->upstream.pass_request_headers,
+ prev->upstream.pass_request_headers, 1);
+ ngx_conf_merge_value(conf->upstream.pass_request_body,
+ prev->upstream.pass_request_body, 1);
+
+ ngx_conf_merge_value(conf->upstream.intercept_errors,
+ prev->upstream.intercept_errors, 0);
+
+#if (NGX_HTTP_SSL)
+
+ ngx_conf_merge_value(conf->upstream.ssl_session_reuse,
+ prev->upstream.ssl_session_reuse, 1);
+
+ ngx_conf_merge_bitmask_value(conf->ssl_protocols, prev->ssl_protocols,
+ (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1
+ |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2));
+
+ ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers,
+ "DEFAULT");
+
+ if (conf->upstream.ssl_name == NULL) {
+ conf->upstream.ssl_name = prev->upstream.ssl_name;
+ }
+
+ ngx_conf_merge_value(conf->upstream.ssl_server_name,
+ prev->upstream.ssl_server_name, 0);
+ ngx_conf_merge_value(conf->upstream.ssl_verify,
+ prev->upstream.ssl_verify, 0);
+ ngx_conf_merge_uint_value(conf->ssl_verify_depth,
+ prev->ssl_verify_depth, 1);
+ ngx_conf_merge_str_value(conf->ssl_trusted_certificate,
+ prev->ssl_trusted_certificate, "");
+ ngx_conf_merge_str_value(conf->ssl_crl, prev->ssl_crl, "");
+
+ ngx_conf_merge_str_value(conf->ssl_certificate,
+ prev->ssl_certificate, "");
+ ngx_conf_merge_str_value(conf->ssl_certificate_key,
+ prev->ssl_certificate_key, "");
+ ngx_conf_merge_ptr_value(conf->ssl_passwords, prev->ssl_passwords, NULL);
+
+ if (conf->ssl && ngx_http_uwsgi_set_ssl(cf, conf) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+#endif
+
+ ngx_conf_merge_str_value(conf->uwsgi_string, prev->uwsgi_string, "");
+
+ hash.max_size = 512;
+ hash.bucket_size = ngx_align(64, ngx_cacheline_size);
+ hash.name = "uwsgi_hide_headers_hash";
+
+ if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream,
+ &prev->upstream, ngx_http_uwsgi_hide_headers, &hash)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+ if (clcf->noname
+ && conf->upstream.upstream == NULL && conf->uwsgi_lengths == NULL)
+ {
+ conf->upstream.upstream = prev->upstream.upstream;
+
+ conf->uwsgi_lengths = prev->uwsgi_lengths;
+ conf->uwsgi_values = prev->uwsgi_values;
+
+#if (NGX_HTTP_SSL)
+ conf->upstream.ssl = prev->upstream.ssl;
+#endif
+ }
+
+ if (clcf->lmt_excpt && clcf->handler == NULL
+ && (conf->upstream.upstream || conf->uwsgi_lengths))
+ {
+ clcf->handler = ngx_http_uwsgi_handler;
+ }
+
+ ngx_conf_merge_uint_value(conf->modifier1, prev->modifier1, 0);
+ ngx_conf_merge_uint_value(conf->modifier2, prev->modifier2, 0);
+
+ if (conf->params_source == NULL) {
+ conf->params = prev->params;
+#if (NGX_HTTP_CACHE)
+ conf->params_cache = prev->params_cache;
+#endif
+ conf->params_source = prev->params_source;
+ }
+
+ rc = ngx_http_uwsgi_init_params(cf, conf, &conf->params, NULL);
+ if (rc != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ if (conf->upstream.cache) {
+ rc = ngx_http_uwsgi_init_params(cf, conf, &conf->params_cache,
+ ngx_http_uwsgi_cache_headers);
+ if (rc != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+#endif
+
+ /*
+ * special handling to preserve conf->params in the "http" section
+ * to inherit it to all servers
+ */
+
+ if (prev->params.hash.buckets == NULL
+ && conf->params_source == prev->params_source)
+ {
+ prev->params = conf->params;
+#if (NGX_HTTP_CACHE)
+ prev->params_cache = conf->params_cache;
+#endif
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_uwsgi_init_params(ngx_conf_t *cf, ngx_http_uwsgi_loc_conf_t *conf,
+ ngx_http_uwsgi_params_t *params, ngx_keyval_t *default_params)
+{
+ u_char *p;
+ size_t size;
+ uintptr_t *code;
+ ngx_uint_t i, nsrc;
+ ngx_array_t headers_names, params_merged;
+ ngx_keyval_t *h;
+ ngx_hash_key_t *hk;
+ ngx_hash_init_t hash;
+ ngx_http_upstream_param_t *src, *s;
+ ngx_http_script_compile_t sc;
+ ngx_http_script_copy_code_t *copy;
+
+ if (params->hash.buckets) {
+ return NGX_OK;
+ }
+
+ if (conf->params_source == NULL && default_params == NULL) {
+ params->hash.buckets = (void *) 1;
+ return NGX_OK;
+ }
+
+ params->lengths = ngx_array_create(cf->pool, 64, 1);
+ if (params->lengths == NULL) {
+ return NGX_ERROR;
+ }
+
+ params->values = ngx_array_create(cf->pool, 512, 1);
+ if (params->values == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ if (conf->params_source) {
+ src = conf->params_source->elts;
+ nsrc = conf->params_source->nelts;
+
+ } else {
+ src = NULL;
+ nsrc = 0;
+ }
+
+ if (default_params) {
+ if (ngx_array_init(&params_merged, cf->temp_pool, 4,
+ sizeof(ngx_http_upstream_param_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ for (i = 0; i < nsrc; i++) {
+
+ s = ngx_array_push(&params_merged);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ *s = src[i];
+ }
+
+ h = default_params;
+
+ while (h->key.len) {
+
+ src = params_merged.elts;
+ nsrc = params_merged.nelts;
+
+ for (i = 0; i < nsrc; i++) {
+ if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) {
+ goto next;
+ }
+ }
+
+ s = ngx_array_push(&params_merged);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ s->key = h->key;
+ s->value = h->value;
+ s->skip_empty = 1;
+
+ next:
+
+ h++;
+ }
+
+ src = params_merged.elts;
+ nsrc = params_merged.nelts;
+ }
+
+ for (i = 0; i < nsrc; i++) {
+
+ if (src[i].key.len > sizeof("HTTP_") - 1
+ && ngx_strncmp(src[i].key.data, "HTTP_", sizeof("HTTP_") - 1) == 0)
+ {
+ hk = ngx_array_push(&headers_names);
+ if (hk == NULL) {
+ return NGX_ERROR;
+ }
+
+ hk->key.len = src[i].key.len - 5;
+ hk->key.data = src[i].key.data + 5;
+ hk->key_hash = ngx_hash_key_lc(hk->key.data, hk->key.len);
+ hk->value = (void *) 1;
+
+ if (src[i].value.len == 0) {
+ continue;
+ }
+ }
+
+ copy = ngx_array_push_n(params->lengths,
+ sizeof(ngx_http_script_copy_code_t));
+ if (copy == NULL) {
+ return NGX_ERROR;
+ }
+
+ copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code;
+ copy->len = src[i].key.len;
+
+ copy = ngx_array_push_n(params->lengths,
+ sizeof(ngx_http_script_copy_code_t));
+ if (copy == NULL) {
+ return NGX_ERROR;
+ }
+
+ copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code;
+ copy->len = src[i].skip_empty;
+
+
+ size = (sizeof(ngx_http_script_copy_code_t)
+ + src[i].key.len + sizeof(uintptr_t) - 1)
+ & ~(sizeof(uintptr_t) - 1);
+
+ copy = ngx_array_push_n(params->values, size);
+ if (copy == NULL) {
+ return NGX_ERROR;
+ }
+
+ copy->code = ngx_http_script_copy_code;
+ copy->len = src[i].key.len;
+
+ p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t);
+ ngx_memcpy(p, src[i].key.data, src[i].key.len);
+
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = &src[i].value;
+ sc.flushes = &params->flushes;
+ sc.lengths = &params->lengths;
+ sc.values = &params->values;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ code = ngx_array_push_n(params->lengths, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+
+
+ code = ngx_array_push_n(params->values, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+ }
+
+ code = ngx_array_push_n(params->lengths, sizeof(uintptr_t));
+ if (code == NULL) {
+ return NGX_ERROR;
+ }
+
+ *code = (uintptr_t) NULL;
+
+ params->number = headers_names.nelts;
+
+ hash.hash = &params->hash;
+ hash.key = ngx_hash_key_lc;
+ hash.max_size = 512;
+ hash.bucket_size = 64;
+ hash.name = "uwsgi_params_hash";
+ hash.pool = cf->pool;
+ hash.temp_pool = NULL;
+
+ return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts);
+}
+
+
+static char *
+ngx_http_uwsgi_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_uwsgi_loc_conf_t *uwcf = conf;
+
+ size_t add;
+ ngx_url_t u;
+ ngx_str_t *value, *url;
+ ngx_uint_t n;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_script_compile_t sc;
+
+ if (uwcf->upstream.upstream || uwcf->uwsgi_lengths) {
+ return "is duplicate";
+ }
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+ clcf->handler = ngx_http_uwsgi_handler;
+
+ value = cf->args->elts;
+
+ url = &value[1];
+
+ n = ngx_http_script_variables_count(url);
+
+ if (n) {
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = url;
+ sc.lengths = &uwcf->uwsgi_lengths;
+ sc.values = &uwcf->uwsgi_values;
+ sc.variables = n;
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+#if (NGX_HTTP_SSL)
+ uwcf->ssl = 1;
+#endif
+
+ return NGX_CONF_OK;
+ }
+
+ if (ngx_strncasecmp(url->data, (u_char *) "uwsgi://", 8) == 0) {
+ add = 8;
+
+ } else if (ngx_strncasecmp(url->data, (u_char *) "suwsgi://", 9) == 0) {
+
+#if (NGX_HTTP_SSL)
+ add = 9;
+ uwcf->ssl = 1;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "suwsgi protocol requires SSL support");
+ return NGX_CONF_ERROR;
+#endif
+
+ } else {
+ add = 0;
+ }
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.url.len = url->len - add;
+ u.url.data = url->data + add;
+ u.no_resolve = 1;
+
+ uwcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
+ if (uwcf->upstream.upstream == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (clcf->name.data[clcf->name.len - 1] == '/') {
+ clcf->auto_redirect = 1;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_uwsgi_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_uwsgi_loc_conf_t *uwcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_script_compile_t sc;
+
+ if (uwcf->upstream.store != NGX_CONF_UNSET) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ uwcf->upstream.store = 0;
+ return NGX_CONF_OK;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ if (uwcf->upstream.cache > 0) {
+ return "is incompatible with \"uwsgi_cache\"";
+ }
+
+#endif
+
+ uwcf->upstream.store = 1;
+
+ if (ngx_strcmp(value[1].data, "on") == 0) {
+ return NGX_CONF_OK;
+ }
+
+ /* include the terminating '\0' into script */
+ value[1].len++;
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = &value[1];
+ sc.lengths = &uwcf->upstream.store_lengths;
+ sc.values = &uwcf->upstream.store_values;
+ sc.variables = ngx_http_script_variables_count(&value[1]);
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static char *
+ngx_http_uwsgi_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_uwsgi_loc_conf_t *uwcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_complex_value_t cv;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ if (uwcf->upstream.cache != NGX_CONF_UNSET) {
+ return "is duplicate";
+ }
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ uwcf->upstream.cache = 0;
+ return NGX_CONF_OK;
+ }
+
+ if (uwcf->upstream.store > 0) {
+ return "is incompatible with \"uwsgi_store\"";
+ }
+
+ uwcf->upstream.cache = 1;
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (cv.lengths != NULL) {
+
+ uwcf->upstream.cache_value = ngx_palloc(cf->pool,
+ sizeof(ngx_http_complex_value_t));
+ if (uwcf->upstream.cache_value == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *uwcf->upstream.cache_value = cv;
+
+ return NGX_CONF_OK;
+ }
+
+ uwcf->upstream.cache_zone = ngx_shared_memory_add(cf, &value[1], 0,
+ &ngx_http_uwsgi_module);
+ if (uwcf->upstream.cache_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_uwsgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_uwsgi_loc_conf_t *uwcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ if (uwcf->cache_key.value.data) {
+ return "is duplicate";
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = &uwcf->cache_key;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+#endif
+
+
+#if (NGX_HTTP_SSL)
+
+static char *
+ngx_http_uwsgi_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_uwsgi_loc_conf_t *uwcf = conf;
+
+ ngx_str_t *value;
+
+ if (uwcf->ssl_passwords != NGX_CONF_UNSET_PTR) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ uwcf->ssl_passwords = ngx_ssl_read_password_file(cf, &value[1]);
+
+ if (uwcf->ssl_passwords == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_uwsgi_set_ssl(ngx_conf_t *cf, ngx_http_uwsgi_loc_conf_t *uwcf)
+{
+ ngx_pool_cleanup_t *cln;
+
+ uwcf->upstream.ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t));
+ if (uwcf->upstream.ssl == NULL) {
+ return NGX_ERROR;
+ }
+
+ uwcf->upstream.ssl->log = cf->log;
+
+ if (ngx_ssl_create(uwcf->upstream.ssl, uwcf->ssl_protocols, NULL)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ cln = ngx_pool_cleanup_add(cf->pool, 0);
+ if (cln == NULL) {
+ return NGX_ERROR;
+ }
+
+ cln->handler = ngx_ssl_cleanup_ctx;
+ cln->data = uwcf->upstream.ssl;
+
+ if (uwcf->ssl_certificate.len) {
+
+ if (uwcf->ssl_certificate_key.len == 0) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"uwsgi_ssl_certificate_key\" is defined "
+ "for certificate \"%V\"", &uwcf->ssl_certificate);
+ return NGX_ERROR;
+ }
+
+ if (ngx_ssl_certificate(cf, uwcf->upstream.ssl, &uwcf->ssl_certificate,
+ &uwcf->ssl_certificate_key, uwcf->ssl_passwords)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ if (ngx_ssl_ciphers(cf, uwcf->upstream.ssl, &uwcf->ssl_ciphers, 0)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ if (uwcf->upstream.ssl_verify) {
+ if (uwcf->ssl_trusted_certificate.len == 0) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no uwsgi_ssl_trusted_certificate for uwsgi_ssl_verify");
+ return NGX_ERROR;
+ }
+
+ if (ngx_ssl_trusted_certificate(cf, uwcf->upstream.ssl,
+ &uwcf->ssl_trusted_certificate,
+ uwcf->ssl_verify_depth)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ if (ngx_ssl_crl(cf, uwcf->upstream.ssl, &uwcf->ssl_crl) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+#endif
diff --git a/app/nginx/src/http/modules/ngx_http_xslt_filter_module.c b/app/nginx/src/http/modules/ngx_http_xslt_filter_module.c
new file mode 100644
index 0000000..695f3bf
--- /dev/null
+++ b/app/nginx/src/http/modules/ngx_http_xslt_filter_module.c
@@ -0,0 +1,1147 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxslt/xslt.h>
+#include <libxslt/xsltInternals.h>
+#include <libxslt/transform.h>
+#include <libxslt/variables.h>
+#include <libxslt/xsltutils.h>
+
+#if (NGX_HAVE_EXSLT)
+#include <libexslt/exslt.h>
+#endif
+
+
+#ifndef NGX_HTTP_XSLT_REUSE_DTD
+#define NGX_HTTP_XSLT_REUSE_DTD 1
+#endif
+
+
+typedef struct {
+ u_char *name;
+ void *data;
+} ngx_http_xslt_file_t;
+
+
+typedef struct {
+ ngx_array_t dtd_files; /* ngx_http_xslt_file_t */
+ ngx_array_t sheet_files; /* ngx_http_xslt_file_t */
+} ngx_http_xslt_filter_main_conf_t;
+
+
+typedef struct {
+ u_char *name;
+ ngx_http_complex_value_t value;
+ ngx_uint_t quote; /* unsigned quote:1; */
+} ngx_http_xslt_param_t;
+
+
+typedef struct {
+ xsltStylesheetPtr stylesheet;
+ ngx_array_t params; /* ngx_http_xslt_param_t */
+} ngx_http_xslt_sheet_t;
+
+
+typedef struct {
+ xmlDtdPtr dtd;
+ ngx_array_t sheets; /* ngx_http_xslt_sheet_t */
+ ngx_hash_t types;
+ ngx_array_t *types_keys;
+ ngx_array_t *params; /* ngx_http_xslt_param_t */
+ ngx_flag_t last_modified;
+} ngx_http_xslt_filter_loc_conf_t;
+
+
+typedef struct {
+ xmlDocPtr doc;
+ xmlParserCtxtPtr ctxt;
+ xsltTransformContextPtr transform;
+ ngx_http_request_t *request;
+ ngx_array_t params;
+
+ ngx_uint_t done; /* unsigned done:1; */
+} ngx_http_xslt_filter_ctx_t;
+
+
+static ngx_int_t ngx_http_xslt_send(ngx_http_request_t *r,
+ ngx_http_xslt_filter_ctx_t *ctx, ngx_buf_t *b);
+static ngx_int_t ngx_http_xslt_add_chunk(ngx_http_request_t *r,
+ ngx_http_xslt_filter_ctx_t *ctx, ngx_buf_t *b);
+
+
+static void ngx_http_xslt_sax_external_subset(void *data, const xmlChar *name,
+ const xmlChar *externalId, const xmlChar *systemId);
+static void ngx_cdecl ngx_http_xslt_sax_error(void *data, const char *msg, ...);
+
+
+static ngx_buf_t *ngx_http_xslt_apply_stylesheet(ngx_http_request_t *r,
+ ngx_http_xslt_filter_ctx_t *ctx);
+static ngx_int_t ngx_http_xslt_params(ngx_http_request_t *r,
+ ngx_http_xslt_filter_ctx_t *ctx, ngx_array_t *params, ngx_uint_t final);
+static u_char *ngx_http_xslt_content_type(xsltStylesheetPtr s);
+static u_char *ngx_http_xslt_encoding(xsltStylesheetPtr s);
+static void ngx_http_xslt_cleanup(void *data);
+
+static char *ngx_http_xslt_entities(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_xslt_stylesheet(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_http_xslt_param(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static void ngx_http_xslt_cleanup_dtd(void *data);
+static void ngx_http_xslt_cleanup_stylesheet(void *data);
+static void *ngx_http_xslt_filter_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_xslt_filter_create_conf(ngx_conf_t *cf);
+static char *ngx_http_xslt_filter_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static ngx_int_t ngx_http_xslt_filter_preconfiguration(ngx_conf_t *cf);
+static ngx_int_t ngx_http_xslt_filter_init(ngx_conf_t *cf);
+static void ngx_http_xslt_filter_exit(ngx_cycle_t *cycle);
+
+
+static ngx_str_t ngx_http_xslt_default_types[] = {
+ ngx_string("text/xml"),
+ ngx_null_string
+};
+
+
+static ngx_command_t ngx_http_xslt_filter_commands[] = {
+
+ { ngx_string("xml_entities"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_xslt_entities,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("xslt_stylesheet"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_xslt_stylesheet,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("xslt_param"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_http_xslt_param,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("xslt_string_param"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ ngx_http_xslt_param,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ (void *) 1 },
+
+ { ngx_string("xslt_types"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+ ngx_http_types_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_xslt_filter_loc_conf_t, types_keys),
+ &ngx_http_xslt_default_types[0] },
+
+ { ngx_string("xslt_last_modified"),
+ 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_xslt_filter_loc_conf_t, last_modified),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_xslt_filter_module_ctx = {
+ ngx_http_xslt_filter_preconfiguration, /* preconfiguration */
+ ngx_http_xslt_filter_init, /* postconfiguration */
+
+ ngx_http_xslt_filter_create_main_conf, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_xslt_filter_create_conf, /* create location configuration */
+ ngx_http_xslt_filter_merge_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_xslt_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_xslt_filter_module_ctx, /* module context */
+ ngx_http_xslt_filter_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ ngx_http_xslt_filter_exit, /* exit process */
+ ngx_http_xslt_filter_exit, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_xslt_header_filter(ngx_http_request_t *r)
+{
+ ngx_http_xslt_filter_ctx_t *ctx;
+ ngx_http_xslt_filter_loc_conf_t *conf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter header");
+
+ if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module);
+
+ if (conf->sheets.nelts == 0
+ || ngx_http_test_content_type(r, &conf->types) == NULL)
+ {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_xslt_filter_module);
+
+ if (ctx) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_xslt_filter_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_xslt_filter_module);
+
+ r->main_filter_need_in_memory = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_xslt_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ int wellFormed;
+ ngx_chain_t *cl;
+ ngx_http_xslt_filter_ctx_t *ctx;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter body");
+
+ if (in == NULL) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_xslt_filter_module);
+
+ if (ctx == NULL || ctx->done) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ for (cl = in; cl; cl = cl->next) {
+
+ if (ngx_http_xslt_add_chunk(r, ctx, cl->buf) != NGX_OK) {
+
+ if (ctx->ctxt->myDoc) {
+
+#if (NGX_HTTP_XSLT_REUSE_DTD)
+ ctx->ctxt->myDoc->extSubset = NULL;
+#endif
+ xmlFreeDoc(ctx->ctxt->myDoc);
+ }
+
+ xmlFreeParserCtxt(ctx->ctxt);
+
+ return ngx_http_xslt_send(r, ctx, NULL);
+ }
+
+ if (cl->buf->last_buf || cl->buf->last_in_chain) {
+
+ ctx->doc = ctx->ctxt->myDoc;
+
+#if (NGX_HTTP_XSLT_REUSE_DTD)
+ ctx->doc->extSubset = NULL;
+#endif
+
+ wellFormed = ctx->ctxt->wellFormed;
+
+ xmlFreeParserCtxt(ctx->ctxt);
+
+ if (wellFormed) {
+ return ngx_http_xslt_send(r, ctx,
+ ngx_http_xslt_apply_stylesheet(r, ctx));
+ }
+
+ xmlFreeDoc(ctx->doc);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "not well formed XML document");
+
+ return ngx_http_xslt_send(r, ctx, NULL);
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_xslt_send(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx,
+ ngx_buf_t *b)
+{
+ ngx_int_t rc;
+ ngx_chain_t out;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_xslt_filter_loc_conf_t *conf;
+
+ ctx->done = 1;
+
+ if (b == NULL) {
+ return ngx_http_filter_finalize_request(r, &ngx_http_xslt_filter_module,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ }
+
+ cln = ngx_pool_cleanup_add(r->pool, 0);
+
+ if (cln == NULL) {
+ ngx_free(b->pos);
+ return ngx_http_filter_finalize_request(r, &ngx_http_xslt_filter_module,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ }
+
+ if (r == r->main) {
+ r->headers_out.content_length_n = b->last - b->pos;
+
+ if (r->headers_out.content_length) {
+ r->headers_out.content_length->hash = 0;
+ r->headers_out.content_length = NULL;
+ }
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module);
+
+ if (!conf->last_modified) {
+ ngx_http_clear_last_modified(r);
+ ngx_http_clear_etag(r);
+
+ } else {
+ ngx_http_weak_etag(r);
+ }
+ }
+
+ rc = ngx_http_next_header_filter(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ ngx_free(b->pos);
+ return rc;
+ }
+
+ cln->handler = ngx_http_xslt_cleanup;
+ cln->data = b->pos;
+
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_next_body_filter(r, &out);
+}
+
+
+static ngx_int_t
+ngx_http_xslt_add_chunk(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx,
+ ngx_buf_t *b)
+{
+ int err;
+ xmlParserCtxtPtr ctxt;
+
+ if (ctx->ctxt == NULL) {
+
+ ctxt = xmlCreatePushParserCtxt(NULL, NULL, NULL, 0, NULL);
+ if (ctxt == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "xmlCreatePushParserCtxt() failed");
+ return NGX_ERROR;
+ }
+ xmlCtxtUseOptions(ctxt, XML_PARSE_NOENT|XML_PARSE_DTDLOAD
+ |XML_PARSE_NOWARNING);
+
+ ctxt->sax->externalSubset = ngx_http_xslt_sax_external_subset;
+ ctxt->sax->setDocumentLocator = NULL;
+ ctxt->sax->error = ngx_http_xslt_sax_error;
+ ctxt->sax->fatalError = ngx_http_xslt_sax_error;
+ ctxt->sax->_private = ctx;
+
+ ctx->ctxt = ctxt;
+ ctx->request = r;
+ }
+
+ err = xmlParseChunk(ctx->ctxt, (char *) b->pos, (int) (b->last - b->pos),
+ (b->last_buf) || (b->last_in_chain));
+
+ if (err == 0) {
+ b->pos = b->last;
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "xmlParseChunk() failed, error:%d", err);
+
+ return NGX_ERROR;
+}
+
+
+static void
+ngx_http_xslt_sax_external_subset(void *data, const xmlChar *name,
+ const xmlChar *externalId, const xmlChar *systemId)
+{
+ xmlParserCtxtPtr ctxt = data;
+
+ xmlDocPtr doc;
+ xmlDtdPtr dtd;
+ ngx_http_request_t *r;
+ ngx_http_xslt_filter_ctx_t *ctx;
+ ngx_http_xslt_filter_loc_conf_t *conf;
+
+ ctx = ctxt->sax->_private;
+ r = ctx->request;
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter extSubset: \"%s\" \"%s\" \"%s\"",
+ name ? name : (xmlChar *) "",
+ externalId ? externalId : (xmlChar *) "",
+ systemId ? systemId : (xmlChar *) "");
+
+ doc = ctxt->myDoc;
+
+#if (NGX_HTTP_XSLT_REUSE_DTD)
+
+ dtd = conf->dtd;
+
+#else
+
+ dtd = xmlCopyDtd(conf->dtd);
+ if (dtd == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "xmlCopyDtd() failed");
+ return;
+ }
+
+ if (doc->children == NULL) {
+ xmlAddChild((xmlNodePtr) doc, (xmlNodePtr) dtd);
+
+ } else {
+ xmlAddPrevSibling(doc->children, (xmlNodePtr) dtd);
+ }
+
+#endif
+
+ doc->extSubset = dtd;
+}
+
+
+static void ngx_cdecl
+ngx_http_xslt_sax_error(void *data, const char *msg, ...)
+{
+ xmlParserCtxtPtr ctxt = data;
+
+ size_t n;
+ va_list args;
+ ngx_http_xslt_filter_ctx_t *ctx;
+ u_char buf[NGX_MAX_ERROR_STR];
+
+ ctx = ctxt->sax->_private;
+
+ buf[0] = '\0';
+
+ va_start(args, msg);
+ n = (size_t) vsnprintf((char *) buf, NGX_MAX_ERROR_STR, msg, args);
+ va_end(args);
+
+ while (--n && (buf[n] == CR || buf[n] == LF)) { /* void */ }
+
+ ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0,
+ "libxml2 error: \"%*s\"", n + 1, buf);
+}
+
+
+static ngx_buf_t *
+ngx_http_xslt_apply_stylesheet(ngx_http_request_t *r,
+ ngx_http_xslt_filter_ctx_t *ctx)
+{
+ int len, rc, doc_type;
+ u_char *type, *encoding;
+ ngx_buf_t *b;
+ ngx_uint_t i;
+ xmlChar *buf;
+ xmlDocPtr doc, res;
+ ngx_http_xslt_sheet_t *sheet;
+ ngx_http_xslt_filter_loc_conf_t *conf;
+
+ conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module);
+ sheet = conf->sheets.elts;
+ doc = ctx->doc;
+
+ /* preallocate array for 4 params */
+
+ if (ngx_array_init(&ctx->params, r->pool, 4 * 2 + 1, sizeof(char *))
+ != NGX_OK)
+ {
+ xmlFreeDoc(doc);
+ return NULL;
+ }
+
+ for (i = 0; i < conf->sheets.nelts; i++) {
+
+ ctx->transform = xsltNewTransformContext(sheet[i].stylesheet, doc);
+ if (ctx->transform == NULL) {
+ xmlFreeDoc(doc);
+ return NULL;
+ }
+
+ if (conf->params
+ && ngx_http_xslt_params(r, ctx, conf->params, 0) != NGX_OK)
+ {
+ xsltFreeTransformContext(ctx->transform);
+ xmlFreeDoc(doc);
+ return NULL;
+ }
+
+ if (ngx_http_xslt_params(r, ctx, &sheet[i].params, 1) != NGX_OK) {
+ xsltFreeTransformContext(ctx->transform);
+ xmlFreeDoc(doc);
+ return NULL;
+ }
+
+ res = xsltApplyStylesheetUser(sheet[i].stylesheet, doc,
+ ctx->params.elts, NULL, NULL,
+ ctx->transform);
+
+ xsltFreeTransformContext(ctx->transform);
+ xmlFreeDoc(doc);
+
+ if (res == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "xsltApplyStylesheet() failed");
+ return NULL;
+ }
+
+ doc = res;
+
+ /* reset array elements */
+ ctx->params.nelts = 0;
+ }
+
+ /* there must be at least one stylesheet */
+
+ if (r == r->main) {
+ type = ngx_http_xslt_content_type(sheet[i - 1].stylesheet);
+
+ } else {
+ type = NULL;
+ }
+
+ encoding = ngx_http_xslt_encoding(sheet[i - 1].stylesheet);
+ doc_type = doc->type;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter type: %d t:%s e:%s",
+ doc_type, type ? type : (u_char *) "(null)",
+ encoding ? encoding : (u_char *) "(null)");
+
+ rc = xsltSaveResultToString(&buf, &len, doc, sheet[i - 1].stylesheet);
+
+ xmlFreeDoc(doc);
+
+ if (rc != 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "xsltSaveResultToString() failed");
+ return NULL;
+ }
+
+ if (len == 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "xsltSaveResultToString() returned zero-length result");
+ return NULL;
+ }
+
+ b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
+ if (b == NULL) {
+ ngx_free(buf);
+ return NULL;
+ }
+
+ b->pos = buf;
+ b->last = buf + len;
+ b->memory = 1;
+
+ if (encoding) {
+ r->headers_out.charset.len = ngx_strlen(encoding);
+ r->headers_out.charset.data = encoding;
+ }
+
+ if (r != r->main) {
+ return b;
+ }
+
+ b->last_buf = 1;
+
+ if (type) {
+ len = ngx_strlen(type);
+
+ r->headers_out.content_type_len = len;
+ r->headers_out.content_type.len = len;
+ r->headers_out.content_type.data = type;
+
+ } else if (doc_type == XML_HTML_DOCUMENT_NODE) {
+
+ r->headers_out.content_type_len = sizeof("text/html") - 1;
+ ngx_str_set(&r->headers_out.content_type, "text/html");
+ }
+
+ r->headers_out.content_type_lowcase = NULL;
+
+ return b;
+}
+
+
+static ngx_int_t
+ngx_http_xslt_params(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx,
+ ngx_array_t *params, ngx_uint_t final)
+{
+ u_char *p, *last, *value, *dst, *src, **s;
+ size_t len;
+ ngx_uint_t i;
+ ngx_str_t string;
+ ngx_http_xslt_param_t *param;
+
+ param = params->elts;
+
+ for (i = 0; i < params->nelts; i++) {
+
+ if (ngx_http_complex_value(r, &param[i].value, &string) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter param: \"%s\"", string.data);
+
+ if (param[i].name) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter param name: \"%s\"", param[i].name);
+
+ if (param[i].quote) {
+ if (xsltQuoteOneUserParam(ctx->transform, param[i].name,
+ string.data)
+ != 0)
+ {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "xsltQuoteOneUserParam(\"%s\", \"%s\") failed",
+ param[i].name, string.data);
+ return NGX_ERROR;
+ }
+
+ continue;
+ }
+
+ s = ngx_array_push(&ctx->params);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ *s = param[i].name;
+
+ s = ngx_array_push(&ctx->params);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ *s = string.data;
+
+ continue;
+ }
+
+ /*
+ * parse param1=value1:param2=value2 syntax as used by parameters
+ * specified in xslt_stylesheet directives
+ */
+
+ p = string.data;
+ last = string.data + string.len;
+
+ while (p && *p) {
+
+ value = p;
+ p = (u_char *) ngx_strchr(p, '=');
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid libxslt parameter \"%s\"", value);
+ return NGX_ERROR;
+ }
+ *p++ = '\0';
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter param name: \"%s\"", value);
+
+ s = ngx_array_push(&ctx->params);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ *s = value;
+
+ value = p;
+ p = (u_char *) ngx_strchr(p, ':');
+
+ if (p) {
+ len = p - value;
+ *p++ = '\0';
+
+ } else {
+ len = last - value;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter param value: \"%s\"", value);
+
+ dst = value;
+ src = value;
+
+ ngx_unescape_uri(&dst, &src, len, 0);
+
+ *dst = '\0';
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "xslt filter param unescaped: \"%s\"", value);
+
+ s = ngx_array_push(&ctx->params);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ *s = value;
+ }
+ }
+
+ if (final) {
+ s = ngx_array_push(&ctx->params);
+ if (s == NULL) {
+ return NGX_ERROR;
+ }
+
+ *s = NULL;
+ }
+
+ return NGX_OK;
+}
+
+
+static u_char *
+ngx_http_xslt_content_type(xsltStylesheetPtr s)
+{
+ u_char *type;
+
+ if (s->mediaType) {
+ return s->mediaType;
+ }
+
+ for (s = s->imports; s; s = s->next) {
+
+ type = ngx_http_xslt_content_type(s);
+
+ if (type) {
+ return type;
+ }
+ }
+
+ return NULL;
+}
+
+
+static u_char *
+ngx_http_xslt_encoding(xsltStylesheetPtr s)
+{
+ u_char *encoding;
+
+ if (s->encoding) {
+ return s->encoding;
+ }
+
+ for (s = s->imports; s; s = s->next) {
+
+ encoding = ngx_http_xslt_encoding(s);
+
+ if (encoding) {
+ return encoding;
+ }
+ }
+
+ return NULL;
+}
+
+
+static void
+ngx_http_xslt_cleanup(void *data)
+{
+ ngx_free(data);
+}
+
+
+static char *
+ngx_http_xslt_entities(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_xslt_filter_loc_conf_t *xlcf = conf;
+
+ ngx_str_t *value;
+ ngx_uint_t i;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_xslt_file_t *file;
+ ngx_http_xslt_filter_main_conf_t *xmcf;
+
+ if (xlcf->dtd) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ xmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_xslt_filter_module);
+
+ file = xmcf->dtd_files.elts;
+ for (i = 0; i < xmcf->dtd_files.nelts; i++) {
+ if (ngx_strcmp(file[i].name, value[1].data) == 0) {
+ xlcf->dtd = file[i].data;
+ return NGX_CONF_OK;
+ }
+ }
+
+ cln = ngx_pool_cleanup_add(cf->pool, 0);
+ if (cln == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ xlcf->dtd = xmlParseDTD(NULL, (xmlChar *) value[1].data);
+
+ if (xlcf->dtd == NULL) {
+ ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "xmlParseDTD() failed");
+ return NGX_CONF_ERROR;
+ }
+
+ cln->handler = ngx_http_xslt_cleanup_dtd;
+ cln->data = xlcf->dtd;
+
+ file = ngx_array_push(&xmcf->dtd_files);
+ if (file == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ file->name = value[1].data;
+ file->data = xlcf->dtd;
+
+ return NGX_CONF_OK;
+}
+
+
+
+static char *
+ngx_http_xslt_stylesheet(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_xslt_filter_loc_conf_t *xlcf = conf;
+
+ ngx_str_t *value;
+ ngx_uint_t i, n;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_xslt_file_t *file;
+ ngx_http_xslt_sheet_t *sheet;
+ ngx_http_xslt_param_t *param;
+ ngx_http_compile_complex_value_t ccv;
+ ngx_http_xslt_filter_main_conf_t *xmcf;
+
+ value = cf->args->elts;
+
+ if (xlcf->sheets.elts == NULL) {
+ if (ngx_array_init(&xlcf->sheets, cf->pool, 1,
+ sizeof(ngx_http_xslt_sheet_t))
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ sheet = ngx_array_push(&xlcf->sheets);
+ if (sheet == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(sheet, sizeof(ngx_http_xslt_sheet_t));
+
+ if (ngx_conf_full_name(cf->cycle, &value[1], 0) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ xmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_xslt_filter_module);
+
+ file = xmcf->sheet_files.elts;
+ for (i = 0; i < xmcf->sheet_files.nelts; i++) {
+ if (ngx_strcmp(file[i].name, value[1].data) == 0) {
+ sheet->stylesheet = file[i].data;
+ goto found;
+ }
+ }
+
+ cln = ngx_pool_cleanup_add(cf->pool, 0);
+ if (cln == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ sheet->stylesheet = xsltParseStylesheetFile(value[1].data);
+ if (sheet->stylesheet == NULL) {
+ ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
+ "xsltParseStylesheetFile(\"%s\") failed",
+ value[1].data);
+ return NGX_CONF_ERROR;
+ }
+
+ cln->handler = ngx_http_xslt_cleanup_stylesheet;
+ cln->data = sheet->stylesheet;
+
+ file = ngx_array_push(&xmcf->sheet_files);
+ if (file == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ file->name = value[1].data;
+ file->data = sheet->stylesheet;
+
+found:
+
+ n = cf->args->nelts;
+
+ if (n == 2) {
+ return NGX_CONF_OK;
+ }
+
+ if (ngx_array_init(&sheet->params, cf->pool, n - 2,
+ sizeof(ngx_http_xslt_param_t))
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ for (i = 2; i < n; i++) {
+
+ param = ngx_array_push(&sheet->params);
+ if (param == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(param, sizeof(ngx_http_xslt_param_t));
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[i];
+ ccv.complex_value = &param->value;
+ ccv.zero = 1;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_xslt_param(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_xslt_filter_loc_conf_t *xlcf = conf;
+
+ ngx_http_xslt_param_t *param;
+ ngx_http_compile_complex_value_t ccv;
+ ngx_str_t *value;
+
+ value = cf->args->elts;
+
+ if (xlcf->params == NULL) {
+ xlcf->params = ngx_array_create(cf->pool, 2,
+ sizeof(ngx_http_xslt_param_t));
+ if (xlcf->params == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ param = ngx_array_push(xlcf->params);
+ if (param == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ param->name = value[1].data;
+ param->quote = (cmd->post == NULL) ? 0 : 1;
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[2];
+ ccv.complex_value = &param->value;
+ ccv.zero = 1;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static void
+ngx_http_xslt_cleanup_dtd(void *data)
+{
+ xmlFreeDtd(data);
+}
+
+
+static void
+ngx_http_xslt_cleanup_stylesheet(void *data)
+{
+ xsltFreeStylesheet(data);
+}
+
+
+static void *
+ngx_http_xslt_filter_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_xslt_filter_main_conf_t *conf;
+
+ conf = ngx_palloc(cf->pool, sizeof(ngx_http_xslt_filter_main_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ if (ngx_array_init(&conf->dtd_files, cf->pool, 1,
+ sizeof(ngx_http_xslt_file_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ if (ngx_array_init(&conf->sheet_files, cf->pool, 1,
+ sizeof(ngx_http_xslt_file_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ return conf;
+}
+
+
+static void *
+ngx_http_xslt_filter_create_conf(ngx_conf_t *cf)
+{
+ ngx_http_xslt_filter_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_xslt_filter_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->dtd = NULL;
+ * conf->sheets = { NULL };
+ * conf->types = { NULL };
+ * conf->types_keys = NULL;
+ * conf->params = NULL;
+ */
+
+ conf->last_modified = NGX_CONF_UNSET;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_xslt_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_xslt_filter_loc_conf_t *prev = parent;
+ ngx_http_xslt_filter_loc_conf_t *conf = child;
+
+ if (conf->dtd == NULL) {
+ conf->dtd = prev->dtd;
+ }
+
+ if (conf->sheets.nelts == 0) {
+ conf->sheets = prev->sheets;
+ }
+
+ if (conf->params == NULL) {
+ conf->params = prev->params;
+ }
+
+ if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
+ &prev->types_keys, &prev->types,
+ ngx_http_xslt_default_types)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_value(conf->last_modified, prev->last_modified, 0);
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_xslt_filter_preconfiguration(ngx_conf_t *cf)
+{
+ xmlInitParser();
+
+#if (NGX_HAVE_EXSLT)
+ exsltRegisterAll();
+#endif
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_xslt_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_xslt_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_xslt_body_filter;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_xslt_filter_exit(ngx_cycle_t *cycle)
+{
+ xsltCleanupGlobals();
+ xmlCleanupParser();
+}
diff --git a/app/nginx/src/http/modules/perl/Makefile.PL b/app/nginx/src/http/modules/perl/Makefile.PL
new file mode 100644
index 0000000..7edadcb
--- /dev/null
+++ b/app/nginx/src/http/modules/perl/Makefile.PL
@@ -0,0 +1,35 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) Nginx, Inc.
+
+use 5.006001;
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+ NAME => 'nginx',
+ VERSION_FROM => 'nginx.pm', # finds $VERSION
+ PREREQ_PM => {}, # e.g., Module::Name => 1.1
+
+ ABSTRACT_FROM => 'nginx.pm', # retrieve abstract from module
+ AUTHOR => 'Igor Sysoev',
+
+ CCFLAGS => "$ENV{NGX_PM_CFLAGS}",
+ OPTIMIZE => '-O',
+
+ LDDLFLAGS => "$ENV{NGX_PM_LDFLAGS}",
+
+ INC => join(" ", map {
+ m#^/# ? "-I $_" : "-I ../../../../../$_"
+ } (split /\s+/, $ENV{NGX_INCS})),
+
+ depend => {
+ 'nginx.c' => join(" ", map {
+ m#^/# ? $_ : "../../../../../$_"
+ } (split(/\s+/, $ENV{NGX_DEPS}),
+ "src/http/modules/perl/ngx_http_perl_module.h"))
+ },
+
+ PM => {
+ 'nginx.pm' => '$(INST_LIBDIR)/nginx.pm'
+ }
+);
diff --git a/app/nginx/src/http/modules/perl/nginx.pm b/app/nginx/src/http/modules/perl/nginx.pm
new file mode 100644
index 0000000..e3f7361
--- /dev/null
+++ b/app/nginx/src/http/modules/perl/nginx.pm
@@ -0,0 +1,138 @@
+package nginx;
+
+use 5.006001;
+use strict;
+use warnings;
+
+require Exporter;
+
+our @ISA = qw(Exporter);
+
+our @EXPORT = qw(
+ OK
+ DECLINED
+
+ HTTP_OK
+ HTTP_CREATED
+ HTTP_ACCEPTED
+ HTTP_NO_CONTENT
+ HTTP_PARTIAL_CONTENT
+
+ HTTP_MOVED_PERMANENTLY
+ HTTP_MOVED_TEMPORARILY
+ HTTP_REDIRECT
+ HTTP_SEE_OTHER
+ HTTP_NOT_MODIFIED
+ HTTP_TEMPORARY_REDIRECT
+
+ HTTP_BAD_REQUEST
+ HTTP_UNAUTHORIZED
+ HTTP_PAYMENT_REQUIRED
+ HTTP_FORBIDDEN
+ HTTP_NOT_FOUND
+ HTTP_NOT_ALLOWED
+ HTTP_NOT_ACCEPTABLE
+ HTTP_REQUEST_TIME_OUT
+ HTTP_CONFLICT
+ HTTP_GONE
+ HTTP_LENGTH_REQUIRED
+ HTTP_REQUEST_ENTITY_TOO_LARGE
+ HTTP_REQUEST_URI_TOO_LARGE
+ HTTP_UNSUPPORTED_MEDIA_TYPE
+ HTTP_RANGE_NOT_SATISFIABLE
+
+ HTTP_INTERNAL_SERVER_ERROR
+ HTTP_SERVER_ERROR
+ HTTP_NOT_IMPLEMENTED
+ HTTP_BAD_GATEWAY
+ HTTP_SERVICE_UNAVAILABLE
+ HTTP_GATEWAY_TIME_OUT
+ HTTP_INSUFFICIENT_STORAGE
+);
+
+our $VERSION = '%%VERSION%%';
+
+require XSLoader;
+XSLoader::load('nginx', $VERSION);
+
+# Preloaded methods go here.
+
+use constant OK => 0;
+use constant DECLINED => -5;
+
+use constant HTTP_OK => 200;
+use constant HTTP_CREATED => 201;
+use constant HTTP_ACCEPTED => 202;
+use constant HTTP_NO_CONTENT => 204;
+use constant HTTP_PARTIAL_CONTENT => 206;
+
+use constant HTTP_MOVED_PERMANENTLY => 301;
+use constant HTTP_MOVED_TEMPORARILY => 302;
+use constant HTTP_REDIRECT => 302;
+use constant HTTP_SEE_OTHER => 303;
+use constant HTTP_NOT_MODIFIED => 304;
+use constant HTTP_TEMPORARY_REDIRECT => 307;
+
+use constant HTTP_BAD_REQUEST => 400;
+use constant HTTP_UNAUTHORIZED => 401;
+use constant HTTP_PAYMENT_REQUIRED => 402;
+use constant HTTP_FORBIDDEN => 403;
+use constant HTTP_NOT_FOUND => 404;
+use constant HTTP_NOT_ALLOWED => 405;
+use constant HTTP_NOT_ACCEPTABLE => 406;
+use constant HTTP_REQUEST_TIME_OUT => 408;
+use constant HTTP_CONFLICT => 409;
+use constant HTTP_GONE => 410;
+use constant HTTP_LENGTH_REQUIRED => 411;
+use constant HTTP_REQUEST_ENTITY_TOO_LARGE => 413;
+use constant HTTP_REQUEST_URI_TOO_LARGE => 414;
+use constant HTTP_UNSUPPORTED_MEDIA_TYPE => 415;
+use constant HTTP_RANGE_NOT_SATISFIABLE => 416;
+
+use constant HTTP_INTERNAL_SERVER_ERROR => 500;
+use constant HTTP_SERVER_ERROR => 500;
+use constant HTTP_NOT_IMPLEMENTED => 501;
+use constant HTTP_BAD_GATEWAY => 502;
+use constant HTTP_SERVICE_UNAVAILABLE => 503;
+use constant HTTP_GATEWAY_TIME_OUT => 504;
+use constant HTTP_INSUFFICIENT_STORAGE => 507;
+
+
+sub rflush {
+ my $r = shift;
+
+ $r->flush;
+}
+
+
+1;
+__END__
+
+=head1 NAME
+
+nginx - Perl interface to the nginx HTTP server API
+
+=head1 SYNOPSIS
+
+ use nginx;
+
+=head1 DESCRIPTION
+
+This module provides a Perl interface to the nginx HTTP server API.
+
+
+=head1 SEE ALSO
+
+http://nginx.org/en/docs/http/ngx_http_perl_module.html
+
+=head1 AUTHOR
+
+Igor Sysoev
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) Igor Sysoev
+Copyright (C) Nginx, Inc.
+
+
+=cut
diff --git a/app/nginx/src/http/modules/perl/nginx.xs b/app/nginx/src/http/modules/perl/nginx.xs
new file mode 100644
index 0000000..cca64da
--- /dev/null
+++ b/app/nginx/src/http/modules/perl/nginx.xs
@@ -0,0 +1,1039 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#define PERL_NO_GET_CONTEXT
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_perl_module.h>
+
+#include "XSUB.h"
+
+
+#define ngx_http_perl_set_request(r) \
+ r = INT2PTR(ngx_http_request_t *, SvIV((SV *) SvRV(ST(0))))
+
+
+#define ngx_http_perl_set_targ(p, len) \
+ \
+ SvUPGRADE(TARG, SVt_PV); \
+ SvPOK_on(TARG); \
+ sv_setpvn(TARG, (char *) p, len)
+
+
+static ngx_int_t
+ngx_http_perl_sv2str(pTHX_ ngx_http_request_t *r, ngx_str_t *s, SV *sv)
+{
+ u_char *p;
+ STRLEN len;
+
+ if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PV) {
+ sv = SvRV(sv);
+ }
+
+ p = (u_char *) SvPV(sv, len);
+
+ s->len = len;
+
+ if (SvREADONLY(sv) && SvPOK(sv)) {
+ s->data = p;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl sv2str: %08XD \"%V\"", sv->sv_flags, s);
+
+ return NGX_OK;
+ }
+
+ s->data = ngx_pnalloc(r->pool, len);
+ if (s->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->data, p, len);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl sv2str: %08XD \"%V\"", sv->sv_flags, s);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_perl_output(ngx_http_request_t *r, ngx_buf_t *b)
+{
+ ngx_chain_t out;
+#if (NGX_HTTP_SSI)
+ ngx_chain_t *cl;
+ ngx_http_perl_ctx_t *ctx;
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+ if (ctx->ssi) {
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+ *ctx->ssi->last_out = cl;
+ ctx->ssi->last_out = &cl->next;
+
+ return NGX_OK;
+ }
+#endif
+
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_output_filter(r, &out);
+}
+
+
+MODULE = nginx PACKAGE = nginx
+
+
+PROTOTYPES: DISABLE
+
+
+void
+status(r, code)
+ CODE:
+
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+
+ r->headers_out.status = SvIV(ST(1));
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl status: %d", r->headers_out.status);
+
+ XSRETURN_UNDEF;
+
+
+void
+send_http_header(r, ...)
+ CODE:
+
+ ngx_http_request_t *r;
+ SV *sv;
+
+ ngx_http_perl_set_request(r);
+
+ if (r->headers_out.status == 0) {
+ r->headers_out.status = NGX_HTTP_OK;
+ }
+
+ if (items != 1) {
+ sv = ST(1);
+
+ if (ngx_http_perl_sv2str(aTHX_ r, &r->headers_out.content_type, sv)
+ != NGX_OK)
+ {
+ XSRETURN_EMPTY;
+ }
+
+ r->headers_out.content_type_len = r->headers_out.content_type.len;
+
+ } else {
+ if (ngx_http_set_content_type(r) != NGX_OK) {
+ XSRETURN_EMPTY;
+ }
+ }
+
+ (void) ngx_http_send_header(r);
+
+
+void
+header_only(r)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+
+ sv_upgrade(TARG, SVt_IV);
+ sv_setiv(TARG, r->header_only);
+
+ ST(0) = TARG;
+
+
+void
+uri(r)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+ ngx_http_perl_set_targ(r->uri.data, r->uri.len);
+
+ ST(0) = TARG;
+
+
+void
+args(r)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+ ngx_http_perl_set_targ(r->args.data, r->args.len);
+
+ ST(0) = TARG;
+
+
+void
+request_method(r)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+ ngx_http_perl_set_targ(r->method_name.data, r->method_name.len);
+
+ ST(0) = TARG;
+
+
+void
+remote_addr(r)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+ ngx_http_perl_set_targ(r->connection->addr_text.data,
+ r->connection->addr_text.len);
+
+ ST(0) = TARG;
+
+
+void
+header_in(r, key)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+ SV *key;
+ u_char *p, *lowcase_key, *value, sep;
+ STRLEN len;
+ ssize_t size;
+ ngx_uint_t i, n, hash;
+ ngx_array_t *a;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *h, **ph;
+ ngx_http_header_t *hh;
+ ngx_http_core_main_conf_t *cmcf;
+
+ ngx_http_perl_set_request(r);
+
+ key = ST(1);
+
+ if (SvROK(key) && SvTYPE(SvRV(key)) == SVt_PV) {
+ key = SvRV(key);
+ }
+
+ p = (u_char *) SvPV(key, len);
+
+ /* look up hashed headers */
+
+ lowcase_key = ngx_pnalloc(r->pool, len);
+ if (lowcase_key == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ hash = ngx_hash_strlow(lowcase_key, p, len);
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ hh = ngx_hash_find(&cmcf->headers_in_hash, hash, lowcase_key, len);
+
+ if (hh) {
+
+ if (hh->offset == offsetof(ngx_http_headers_in_t, cookies)) {
+ sep = ';';
+ goto multi;
+ }
+#if (NGX_HTTP_X_FORWARDED_FOR)
+ if (hh->offset == offsetof(ngx_http_headers_in_t, x_forwarded_for)) {
+ sep = ',';
+ goto multi;
+ }
+#endif
+
+ ph = (ngx_table_elt_t **) ((char *) &r->headers_in + hh->offset);
+
+ if (*ph) {
+ ngx_http_perl_set_targ((*ph)->value.data, (*ph)->value.len);
+
+ goto done;
+ }
+
+ XSRETURN_UNDEF;
+
+ multi:
+
+ /* Cookie, X-Forwarded-For */
+
+ a = (ngx_array_t *) ((char *) &r->headers_in + hh->offset);
+
+ n = a->nelts;
+
+ if (n == 0) {
+ XSRETURN_UNDEF;
+ }
+
+ ph = a->elts;
+
+ if (n == 1) {
+ ngx_http_perl_set_targ((*ph)->value.data, (*ph)->value.len);
+
+ goto done;
+ }
+
+ size = - (ssize_t) (sizeof("; ") - 1);
+
+ for (i = 0; i < n; i++) {
+ size += ph[i]->value.len + sizeof("; ") - 1;
+ }
+
+ value = ngx_pnalloc(r->pool, size);
+ if (value == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ p = value;
+
+ for (i = 0; /* void */ ; i++) {
+ p = ngx_copy(p, ph[i]->value.data, ph[i]->value.len);
+
+ if (i == n - 1) {
+ break;
+ }
+
+ *p++ = sep; *p++ = ' ';
+ }
+
+ ngx_http_perl_set_targ(value, size);
+
+ goto done;
+ }
+
+ /* iterate over all headers */
+
+ part = &r->headers_in.headers.part;
+ h = part->elts;
+
+ for (i = 0; /* void */ ; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ h = part->elts;
+ i = 0;
+ }
+
+ if (len != h[i].key.len
+ || ngx_strcasecmp(p, h[i].key.data) != 0)
+ {
+ continue;
+ }
+
+ ngx_http_perl_set_targ(h[i].value.data, h[i].value.len);
+
+ goto done;
+ }
+
+ XSRETURN_UNDEF;
+
+ done:
+
+ ST(0) = TARG;
+
+
+void
+has_request_body(r, next)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+ ngx_http_perl_ctx_t *ctx;
+
+ ngx_http_perl_set_request(r);
+
+ if (r->headers_in.content_length_n <= 0 && !r->headers_in.chunked) {
+ XSRETURN_UNDEF;
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+ ctx->next = SvRV(ST(1));
+
+ r->request_body_in_single_buf = 1;
+ r->request_body_in_persistent_file = 1;
+ r->request_body_in_clean_file = 1;
+
+ if (r->request_body_in_file_only) {
+ r->request_body_file_log_level = 0;
+ }
+
+ ngx_http_read_client_request_body(r, ngx_http_perl_handle_request);
+
+ sv_upgrade(TARG, SVt_IV);
+ sv_setiv(TARG, 1);
+
+ ST(0) = TARG;
+
+
+void
+request_body(r)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+ u_char *p, *data;
+ size_t len;
+ ngx_buf_t *buf;
+ ngx_chain_t *cl;
+
+ ngx_http_perl_set_request(r);
+
+ if (r->request_body == NULL
+ || r->request_body->temp_file
+ || r->request_body->bufs == NULL)
+ {
+ XSRETURN_UNDEF;
+ }
+
+ cl = r->request_body->bufs;
+ buf = cl->buf;
+
+ if (cl->next == NULL) {
+ len = buf->last - buf->pos;
+ data = buf->pos;
+ goto done;
+ }
+
+ len = buf->last - buf->pos;
+ cl = cl->next;
+
+ for ( /* void */ ; cl; cl = cl->next) {
+ buf = cl->buf;
+ len += buf->last - buf->pos;
+ }
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ data = p;
+ cl = r->request_body->bufs;
+
+ for ( /* void */ ; cl; cl = cl->next) {
+ buf = cl->buf;
+ p = ngx_cpymem(p, buf->pos, buf->last - buf->pos);
+ }
+
+ done:
+
+ if (len == 0) {
+ XSRETURN_UNDEF;
+ }
+
+ ngx_http_perl_set_targ(data, len);
+
+ ST(0) = TARG;
+
+
+void
+request_body_file(r)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+
+ if (r->request_body == NULL || r->request_body->temp_file == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ ngx_http_perl_set_targ(r->request_body->temp_file->file.name.data,
+ r->request_body->temp_file->file.name.len);
+
+ ST(0) = TARG;
+
+
+void
+discard_request_body(r)
+ CODE:
+
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+
+ ngx_http_discard_request_body(r);
+
+
+void
+header_out(r, key, value)
+ CODE:
+
+ ngx_http_request_t *r;
+ SV *key;
+ SV *value;
+ ngx_table_elt_t *header;
+
+ ngx_http_perl_set_request(r);
+
+ key = ST(1);
+ value = ST(2);
+
+ header = ngx_list_push(&r->headers_out.headers);
+ if (header == NULL) {
+ XSRETURN_EMPTY;
+ }
+
+ header->hash = 1;
+
+ if (ngx_http_perl_sv2str(aTHX_ r, &header->key, key) != NGX_OK) {
+ XSRETURN_EMPTY;
+ }
+
+ if (ngx_http_perl_sv2str(aTHX_ r, &header->value, value) != NGX_OK) {
+ XSRETURN_EMPTY;
+ }
+
+ if (header->key.len == sizeof("Content-Length") - 1
+ && ngx_strncasecmp(header->key.data, (u_char *) "Content-Length",
+ sizeof("Content-Length") - 1) == 0)
+ {
+ r->headers_out.content_length_n = (off_t) SvIV(value);
+ r->headers_out.content_length = header;
+ }
+
+ if (header->key.len == sizeof("Content-Encoding") - 1
+ && ngx_strncasecmp(header->key.data, (u_char *) "Content-Encoding",
+ sizeof("Content-Encoding") - 1) == 0)
+ {
+ r->headers_out.content_encoding = header;
+ }
+
+
+void
+filename(r)
+ CODE:
+
+ dXSTARG;
+ size_t root;
+ ngx_http_request_t *r;
+ ngx_http_perl_ctx_t *ctx;
+
+ ngx_http_perl_set_request(r);
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+ if (ctx->filename.data) {
+ goto done;
+ }
+
+ if (ngx_http_map_uri_to_path(r, &ctx->filename, &root, 0) == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ ctx->filename.len--;
+ sv_setpv(PL_statname, (char *) ctx->filename.data);
+
+ done:
+
+ ngx_http_perl_set_targ(ctx->filename.data, ctx->filename.len);
+
+ ST(0) = TARG;
+
+
+void
+print(r, ...)
+ CODE:
+
+ ngx_http_request_t *r;
+ SV *sv;
+ int i;
+ u_char *p;
+ size_t size;
+ STRLEN len;
+ ngx_buf_t *b;
+
+ ngx_http_perl_set_request(r);
+
+ if (items == 2) {
+
+ /*
+ * do zero copy for prolate single read-only SV:
+ * $r->print("some text\n");
+ */
+
+ sv = ST(1);
+
+ if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PV) {
+ sv = SvRV(sv);
+ }
+
+ if (SvREADONLY(sv) && SvPOK(sv)) {
+
+ p = (u_char *) SvPV(sv, len);
+
+ if (len == 0) {
+ XSRETURN_EMPTY;
+ }
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ XSRETURN_EMPTY;
+ }
+
+ b->memory = 1;
+ b->pos = p;
+ b->last = p + len;
+ b->start = p;
+ b->end = b->last;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "$r->print: read-only SV: %z", len);
+
+ goto out;
+ }
+ }
+
+ size = 0;
+
+ for (i = 1; i < items; i++) {
+
+ sv = ST(i);
+
+ if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PV) {
+ sv = SvRV(sv);
+ }
+
+ (void) SvPV(sv, len);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "$r->print: copy SV: %z", len);
+
+ size += len;
+ }
+
+ if (size == 0) {
+ XSRETURN_EMPTY;
+ }
+
+ b = ngx_create_temp_buf(r->pool, size);
+ if (b == NULL) {
+ XSRETURN_EMPTY;
+ }
+
+ for (i = 1; i < items; i++) {
+ sv = ST(i);
+
+ if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PV) {
+ sv = SvRV(sv);
+ }
+
+ p = (u_char *) SvPV(sv, len);
+ b->last = ngx_cpymem(b->last, p, len);
+ }
+
+ out:
+
+ (void) ngx_http_perl_output(r, b);
+
+
+void
+sendfile(r, filename, offset = -1, bytes = 0)
+ CODE:
+
+ ngx_http_request_t *r;
+ char *filename;
+ off_t offset;
+ size_t bytes;
+ ngx_str_t path;
+ ngx_buf_t *b;
+ ngx_open_file_info_t of;
+ ngx_http_core_loc_conf_t *clcf;
+
+ ngx_http_perl_set_request(r);
+
+ filename = SvPV_nolen(ST(1));
+
+ if (filename == NULL) {
+ croak("sendfile(): NULL filename");
+ }
+
+ offset = items < 3 ? -1 : SvIV(ST(2));
+ bytes = items < 4 ? 0 : SvIV(ST(3));
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ XSRETURN_EMPTY;
+ }
+
+ b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
+ if (b->file == NULL) {
+ XSRETURN_EMPTY;
+ }
+
+ path.len = ngx_strlen(filename);
+
+ path.data = ngx_pnalloc(r->pool, path.len + 1);
+ if (path.data == NULL) {
+ XSRETURN_EMPTY;
+ }
+
+ (void) ngx_cpystrn(path.data, (u_char *) filename, path.len + 1);
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_memzero(&of, sizeof(ngx_open_file_info_t));
+
+ of.read_ahead = clcf->read_ahead;
+ of.directio = clcf->directio;
+ of.valid = clcf->open_file_cache_valid;
+ of.min_uses = clcf->open_file_cache_min_uses;
+ of.errors = clcf->open_file_cache_errors;
+ of.events = clcf->open_file_cache_events;
+
+ if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
+ XSRETURN_EMPTY;
+ }
+
+ if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
+ != NGX_OK)
+ {
+ if (of.err == 0) {
+ XSRETURN_EMPTY;
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+ "%s \"%s\" failed", of.failed, filename);
+ XSRETURN_EMPTY;
+ }
+
+ if (offset == -1) {
+ offset = 0;
+ }
+
+ if (bytes == 0) {
+ bytes = of.size - offset;
+ }
+
+ b->in_file = 1;
+
+ b->file_pos = offset;
+ b->file_last = offset + bytes;
+
+ b->file->fd = of.fd;
+ b->file->log = r->connection->log;
+ b->file->directio = of.is_directio;
+
+ (void) ngx_http_perl_output(r, b);
+
+
+void
+flush(r)
+ CODE:
+
+ ngx_http_request_t *r;
+ ngx_buf_t *b;
+
+ ngx_http_perl_set_request(r);
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ XSRETURN_EMPTY;
+ }
+
+ b->flush = 1;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "$r->flush");
+
+ (void) ngx_http_perl_output(r, b);
+
+ XSRETURN_EMPTY;
+
+
+void
+internal_redirect(r, uri)
+ CODE:
+
+ ngx_http_request_t *r;
+ SV *uri;
+ ngx_uint_t i;
+ ngx_http_perl_ctx_t *ctx;
+
+ ngx_http_perl_set_request(r);
+
+ uri = ST(1);
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+ if (ngx_http_perl_sv2str(aTHX_ r, &ctx->redirect_uri, uri) != NGX_OK) {
+ XSRETURN_EMPTY;
+ }
+
+ for (i = 0; i < ctx->redirect_uri.len; i++) {
+ if (ctx->redirect_uri.data[i] == '?') {
+
+ ctx->redirect_args.len = ctx->redirect_uri.len - (i + 1);
+ ctx->redirect_args.data = &ctx->redirect_uri.data[i + 1];
+ ctx->redirect_uri.len = i;
+
+ XSRETURN_EMPTY;
+ }
+ }
+
+
+void
+allow_ranges(r)
+ CODE:
+
+ ngx_http_request_t *r;
+
+ ngx_http_perl_set_request(r);
+
+ r->allow_ranges = 1;
+
+
+void
+unescape(r, text, type = 0)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+ SV *text;
+ int type;
+ u_char *p, *dst, *src;
+ STRLEN len;
+
+ ngx_http_perl_set_request(r);
+
+ text = ST(1);
+
+ src = (u_char *) SvPV(text, len);
+
+ p = ngx_pnalloc(r->pool, len + 1);
+ if (p == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ dst = p;
+
+ type = items < 3 ? 0 : SvIV(ST(2));
+
+ ngx_unescape_uri(&dst, &src, len, (ngx_uint_t) type);
+ *dst = '\0';
+
+ ngx_http_perl_set_targ(p, dst - p);
+
+ ST(0) = TARG;
+
+
+void
+variable(r, name, value = NULL)
+ CODE:
+
+ dXSTARG;
+ ngx_http_request_t *r;
+ SV *name, *value;
+ u_char *p, *lowcase;
+ STRLEN len;
+ ngx_str_t var, val;
+ ngx_uint_t i, hash;
+ ngx_http_perl_var_t *v;
+ ngx_http_perl_ctx_t *ctx;
+ ngx_http_variable_value_t *vv;
+
+ ngx_http_perl_set_request(r);
+
+ name = ST(1);
+
+ if (SvROK(name) && SvTYPE(SvRV(name)) == SVt_PV) {
+ name = SvRV(name);
+ }
+
+ if (items == 2) {
+ value = NULL;
+
+ } else {
+ value = ST(2);
+
+ if (SvROK(value) && SvTYPE(SvRV(value)) == SVt_PV) {
+ value = SvRV(value);
+ }
+
+ if (ngx_http_perl_sv2str(aTHX_ r, &val, value) != NGX_OK) {
+ XSRETURN_UNDEF;
+ }
+ }
+
+ p = (u_char *) SvPV(name, len);
+
+ lowcase = ngx_pnalloc(r->pool, len);
+ if (lowcase == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ hash = ngx_hash_strlow(lowcase, p, len);
+
+ var.len = len;
+ var.data = lowcase;
+#if (NGX_DEBUG)
+
+ if (value) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl variable: \"%V\"=\"%V\"", &var, &val);
+ } else {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl variable: \"%V\"", &var);
+ }
+#endif
+
+ vv = ngx_http_get_variable(r, &var, hash);
+ if (vv == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ if (vv->not_found) {
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+ if (ctx->variables) {
+
+ v = ctx->variables->elts;
+ for (i = 0; i < ctx->variables->nelts; i++) {
+
+ if (hash != v[i].hash
+ || len != v[i].name.len
+ || ngx_strncmp(lowcase, v[i].name.data, len) != 0)
+ {
+ continue;
+ }
+
+ if (value) {
+ v[i].value = val;
+ XSRETURN_UNDEF;
+ }
+
+ ngx_http_perl_set_targ(v[i].value.data, v[i].value.len);
+
+ goto done;
+ }
+ }
+
+ if (value) {
+ if (ctx->variables == NULL) {
+ ctx->variables = ngx_array_create(r->pool, 1,
+ sizeof(ngx_http_perl_var_t));
+ if (ctx->variables == NULL) {
+ XSRETURN_UNDEF;
+ }
+ }
+
+ v = ngx_array_push(ctx->variables);
+ if (v == NULL) {
+ XSRETURN_UNDEF;
+ }
+
+ v->hash = hash;
+ v->name.len = len;
+ v->name.data = lowcase;
+ v->value = val;
+
+ XSRETURN_UNDEF;
+ }
+
+ XSRETURN_UNDEF;
+ }
+
+ if (value) {
+ vv->len = val.len;
+ vv->valid = 1;
+ vv->no_cacheable = 0;
+ vv->not_found = 0;
+ vv->data = val.data;
+
+ XSRETURN_UNDEF;
+ }
+
+ ngx_http_perl_set_targ(vv->data, vv->len);
+
+ done:
+
+ ST(0) = TARG;
+
+
+void
+sleep(r, sleep, next)
+ CODE:
+
+ ngx_http_request_t *r;
+ ngx_msec_t sleep;
+ ngx_http_perl_ctx_t *ctx;
+
+ ngx_http_perl_set_request(r);
+
+ sleep = (ngx_msec_t) SvIV(ST(1));
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl sleep: %M", sleep);
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+ ctx->next = SvRV(ST(2));
+
+ r->connection->write->delayed = 1;
+ ngx_add_timer(r->connection->write, sleep);
+
+ r->write_event_handler = ngx_http_perl_sleep_handler;
+ r->main->count++;
+
+
+void
+log_error(r, err, msg)
+ CODE:
+
+ ngx_http_request_t *r;
+ SV *err, *msg;
+ u_char *p;
+ STRLEN len;
+ ngx_err_t e;
+
+ ngx_http_perl_set_request(r);
+
+ err = ST(1);
+
+ if (SvROK(err) && SvTYPE(SvRV(err)) == SVt_PV) {
+ err = SvRV(err);
+ }
+
+ e = SvIV(err);
+
+ msg = ST(2);
+
+ if (SvROK(msg) && SvTYPE(SvRV(msg)) == SVt_PV) {
+ msg = SvRV(msg);
+ }
+
+ p = (u_char *) SvPV(msg, len);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, e, "perl: %s", p);
diff --git a/app/nginx/src/http/modules/perl/ngx_http_perl_module.c b/app/nginx/src/http/modules/perl/ngx_http_perl_module.c
new file mode 100644
index 0000000..6d3be91
--- /dev/null
+++ b/app/nginx/src/http/modules/perl/ngx_http_perl_module.c
@@ -0,0 +1,1086 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_perl_module.h>
+
+
+typedef struct {
+ PerlInterpreter *perl;
+ HV *nginx;
+ ngx_array_t *modules;
+ ngx_array_t *requires;
+} ngx_http_perl_main_conf_t;
+
+
+typedef struct {
+ SV *sub;
+ ngx_str_t handler;
+} ngx_http_perl_loc_conf_t;
+
+
+typedef struct {
+ SV *sub;
+ ngx_str_t handler;
+} ngx_http_perl_variable_t;
+
+
+#if (NGX_HTTP_SSI)
+static ngx_int_t ngx_http_perl_ssi(ngx_http_request_t *r,
+ ngx_http_ssi_ctx_t *ssi_ctx, ngx_str_t **params);
+#endif
+
+static char *ngx_http_perl_init_interpreter(ngx_conf_t *cf,
+ ngx_http_perl_main_conf_t *pmcf);
+static PerlInterpreter *ngx_http_perl_create_interpreter(ngx_conf_t *cf,
+ ngx_http_perl_main_conf_t *pmcf);
+static ngx_int_t ngx_http_perl_run_requires(pTHX_ ngx_array_t *requires,
+ ngx_log_t *log);
+static ngx_int_t ngx_http_perl_call_handler(pTHX_ ngx_http_request_t *r,
+ HV *nginx, SV *sub, SV **args, ngx_str_t *handler, ngx_str_t *rv);
+static void ngx_http_perl_eval_anon_sub(pTHX_ ngx_str_t *handler, SV **sv);
+
+static ngx_int_t ngx_http_perl_preconfiguration(ngx_conf_t *cf);
+static void *ngx_http_perl_create_main_conf(ngx_conf_t *cf);
+static char *ngx_http_perl_init_main_conf(ngx_conf_t *cf, void *conf);
+static void *ngx_http_perl_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_perl_merge_loc_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_http_perl(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_perl_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+static void ngx_http_perl_cleanup_perl(void *data);
+#endif
+
+static ngx_int_t ngx_http_perl_init_worker(ngx_cycle_t *cycle);
+static void ngx_http_perl_exit(ngx_cycle_t *cycle);
+
+
+static ngx_command_t ngx_http_perl_commands[] = {
+
+ { ngx_string("perl_modules"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ offsetof(ngx_http_perl_main_conf_t, modules),
+ NULL },
+
+ { ngx_string("perl_require"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_MAIN_CONF_OFFSET,
+ offsetof(ngx_http_perl_main_conf_t, requires),
+ NULL },
+
+ { ngx_string("perl"),
+ NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1,
+ ngx_http_perl,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("perl_set"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE2,
+ ngx_http_perl_set,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_perl_module_ctx = {
+ ngx_http_perl_preconfiguration, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ ngx_http_perl_create_main_conf, /* create main configuration */
+ ngx_http_perl_init_main_conf, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_perl_create_loc_conf, /* create location configuration */
+ ngx_http_perl_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_perl_module = {
+ NGX_MODULE_V1,
+ &ngx_http_perl_module_ctx, /* module context */
+ ngx_http_perl_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ ngx_http_perl_init_worker, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ ngx_http_perl_exit, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+#if (NGX_HTTP_SSI)
+
+#define NGX_HTTP_PERL_SSI_SUB 0
+#define NGX_HTTP_PERL_SSI_ARG 1
+
+
+static ngx_http_ssi_param_t ngx_http_perl_ssi_params[] = {
+ { ngx_string("sub"), NGX_HTTP_PERL_SSI_SUB, 1, 0 },
+ { ngx_string("arg"), NGX_HTTP_PERL_SSI_ARG, 0, 1 },
+ { ngx_null_string, 0, 0, 0 }
+};
+
+static ngx_http_ssi_command_t ngx_http_perl_ssi_command = {
+ ngx_string("perl"), ngx_http_perl_ssi, ngx_http_perl_ssi_params, 0, 0, 1
+};
+
+#endif
+
+
+static ngx_str_t ngx_null_name = ngx_null_string;
+static HV *nginx_stash;
+
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+static ngx_uint_t ngx_perl_term;
+#else
+static PerlInterpreter *perl;
+#endif
+
+
+static void
+ngx_http_perl_xs_init(pTHX)
+{
+ newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, __FILE__);
+
+ nginx_stash = gv_stashpv("nginx", TRUE);
+}
+
+
+static ngx_int_t
+ngx_http_perl_handler(ngx_http_request_t *r)
+{
+ r->main->count++;
+
+ ngx_http_perl_handle_request(r);
+
+ return NGX_DONE;
+}
+
+
+void
+ngx_http_perl_handle_request(ngx_http_request_t *r)
+{
+ SV *sub;
+ ngx_int_t rc;
+ ngx_str_t uri, args, *handler;
+ ngx_http_perl_ctx_t *ctx;
+ ngx_http_perl_loc_conf_t *plcf;
+ ngx_http_perl_main_conf_t *pmcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "perl handler");
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+ if (ctx == NULL) {
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_perl_ctx_t));
+ if (ctx == NULL) {
+ ngx_http_finalize_request(r, NGX_ERROR);
+ return;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_perl_module);
+ }
+
+ pmcf = ngx_http_get_module_main_conf(r, ngx_http_perl_module);
+
+ {
+
+ dTHXa(pmcf->perl);
+ PERL_SET_CONTEXT(pmcf->perl);
+ PERL_SET_INTERP(pmcf->perl);
+
+ if (ctx->next == NULL) {
+ plcf = ngx_http_get_module_loc_conf(r, ngx_http_perl_module);
+ sub = plcf->sub;
+ handler = &plcf->handler;
+
+ } else {
+ sub = ctx->next;
+ handler = &ngx_null_name;
+ ctx->next = NULL;
+ }
+
+ rc = ngx_http_perl_call_handler(aTHX_ r, pmcf->nginx, sub, NULL, handler,
+ NULL);
+
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl handler done: %i", rc);
+
+ if (rc == NGX_DONE) {
+ ngx_http_finalize_request(r, rc);
+ return;
+ }
+
+ if (rc > 600) {
+ rc = NGX_OK;
+ }
+
+ if (ctx->redirect_uri.len) {
+ uri = ctx->redirect_uri;
+ args = ctx->redirect_args;
+
+ } else {
+ uri.len = 0;
+ }
+
+ ctx->filename.data = NULL;
+ ctx->redirect_uri.len = 0;
+
+ if (ctx->done || ctx->next) {
+ ngx_http_finalize_request(r, NGX_DONE);
+ return;
+ }
+
+ if (uri.len) {
+ ngx_http_internal_redirect(r, &uri, &args);
+ ngx_http_finalize_request(r, NGX_DONE);
+ return;
+ }
+
+ if (rc == NGX_OK || rc == NGX_HTTP_OK) {
+ ngx_http_send_special(r, NGX_HTTP_LAST);
+ ctx->done = 1;
+ }
+
+ ngx_http_finalize_request(r, rc);
+}
+
+
+void
+ngx_http_perl_sleep_handler(ngx_http_request_t *r)
+{
+ ngx_event_t *wev;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl sleep handler");
+
+ wev = r->connection->write;
+
+ if (wev->delayed) {
+
+ if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ }
+
+ return;
+ }
+
+ ngx_http_perl_handle_request(r);
+}
+
+
+static ngx_int_t
+ngx_http_perl_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_http_perl_variable_t *pv = (ngx_http_perl_variable_t *) data;
+
+ ngx_int_t rc;
+ ngx_str_t value;
+ ngx_http_perl_ctx_t *ctx;
+ ngx_http_perl_main_conf_t *pmcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl variable handler");
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+ if (ctx == NULL) {
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_perl_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_perl_module);
+ }
+
+ pmcf = ngx_http_get_module_main_conf(r, ngx_http_perl_module);
+
+ value.data = NULL;
+
+ {
+
+ dTHXa(pmcf->perl);
+ PERL_SET_CONTEXT(pmcf->perl);
+ PERL_SET_INTERP(pmcf->perl);
+
+ rc = ngx_http_perl_call_handler(aTHX_ r, pmcf->nginx, pv->sub, NULL,
+ &pv->handler, &value);
+
+ }
+
+ if (value.data) {
+ v->len = value.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = value.data;
+
+ } else {
+ v->not_found = 1;
+ }
+
+ ctx->filename.data = NULL;
+ ctx->redirect_uri.len = 0;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl variable done");
+
+ return rc;
+}
+
+
+#if (NGX_HTTP_SSI)
+
+static ngx_int_t
+ngx_http_perl_ssi(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ssi_ctx,
+ ngx_str_t **params)
+{
+ SV *sv, **asv;
+ ngx_int_t rc;
+ ngx_str_t *handler, **args;
+ ngx_uint_t i;
+ ngx_http_perl_ctx_t *ctx;
+ ngx_http_perl_main_conf_t *pmcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "perl ssi handler");
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
+
+ if (ctx == NULL) {
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_perl_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_perl_module);
+ }
+
+ pmcf = ngx_http_get_module_main_conf(r, ngx_http_perl_module);
+
+ ctx->ssi = ssi_ctx;
+
+ handler = params[NGX_HTTP_PERL_SSI_SUB];
+ handler->data[handler->len] = '\0';
+
+ {
+
+ dTHXa(pmcf->perl);
+ PERL_SET_CONTEXT(pmcf->perl);
+ PERL_SET_INTERP(pmcf->perl);
+
+#if 0
+
+ /* the code is disabled to force the precompiled perl code using only */
+
+ ngx_http_perl_eval_anon_sub(aTHX_ handler, &sv);
+
+ if (sv == &PL_sv_undef) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "eval_pv(\"%V\") failed", handler);
+ return NGX_ERROR;
+ }
+
+ if (sv == NULL) {
+ sv = newSVpvn((char *) handler->data, handler->len);
+ }
+
+#endif
+
+ sv = newSVpvn((char *) handler->data, handler->len);
+
+ args = &params[NGX_HTTP_PERL_SSI_ARG];
+
+ if (args[0]) {
+
+ for (i = 0; args[i]; i++) { /* void */ }
+
+ asv = ngx_pcalloc(r->pool, (i + 1) * sizeof(SV *));
+
+ if (asv == NULL) {
+ SvREFCNT_dec(sv);
+ return NGX_ERROR;
+ }
+
+ asv[0] = (SV *) (uintptr_t) i;
+
+ for (i = 0; args[i]; i++) {
+ asv[i + 1] = newSVpvn((char *) args[i]->data, args[i]->len);
+ }
+
+ } else {
+ asv = NULL;
+ }
+
+ rc = ngx_http_perl_call_handler(aTHX_ r, pmcf->nginx, sv, asv, handler,
+ NULL);
+
+ SvREFCNT_dec(sv);
+
+ }
+
+ ctx->filename.data = NULL;
+ ctx->redirect_uri.len = 0;
+ ctx->ssi = NULL;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "perl ssi done");
+
+ return rc;
+}
+
+#endif
+
+
+static char *
+ngx_http_perl_init_interpreter(ngx_conf_t *cf, ngx_http_perl_main_conf_t *pmcf)
+{
+ ngx_str_t *m;
+ ngx_uint_t i;
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+ ngx_pool_cleanup_t *cln;
+
+ cln = ngx_pool_cleanup_add(cf->pool, 0);
+ if (cln == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+#endif
+
+#ifdef NGX_PERL_MODULES
+ if (pmcf->modules == NGX_CONF_UNSET_PTR) {
+
+ pmcf->modules = ngx_array_create(cf->pool, 1, sizeof(ngx_str_t));
+ if (pmcf->modules == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ m = ngx_array_push(pmcf->modules);
+ if (m == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_str_set(m, NGX_PERL_MODULES);
+ }
+#endif
+
+ if (pmcf->modules != NGX_CONF_UNSET_PTR) {
+ m = pmcf->modules->elts;
+ for (i = 0; i < pmcf->modules->nelts; i++) {
+ if (ngx_conf_full_name(cf->cycle, &m[i], 0) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+ }
+
+#if !(NGX_HAVE_PERL_MULTIPLICITY)
+
+ if (perl) {
+
+ if (ngx_set_environment(cf->cycle, NULL) == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_http_perl_run_requires(aTHX_ pmcf->requires, cf->log)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ pmcf->perl = perl;
+ pmcf->nginx = nginx_stash;
+
+ return NGX_CONF_OK;
+ }
+
+#endif
+
+ if (nginx_stash == NULL) {
+ PERL_SYS_INIT(&ngx_argc, &ngx_argv);
+ }
+
+ pmcf->perl = ngx_http_perl_create_interpreter(cf, pmcf);
+
+ if (pmcf->perl == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pmcf->nginx = nginx_stash;
+
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+
+ cln->handler = ngx_http_perl_cleanup_perl;
+ cln->data = pmcf->perl;
+
+#else
+
+ perl = pmcf->perl;
+
+#endif
+
+ return NGX_CONF_OK;
+}
+
+
+static PerlInterpreter *
+ngx_http_perl_create_interpreter(ngx_conf_t *cf,
+ ngx_http_perl_main_conf_t *pmcf)
+{
+ int n;
+ STRLEN len;
+ SV *sv;
+ char *ver, **embedding;
+ ngx_str_t *m;
+ ngx_uint_t i;
+ PerlInterpreter *perl;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, "create perl interpreter");
+
+ if (ngx_set_environment(cf->cycle, NULL) == NULL) {
+ return NULL;
+ }
+
+ perl = perl_alloc();
+ if (perl == NULL) {
+ ngx_log_error(NGX_LOG_ALERT, cf->log, 0, "perl_alloc() failed");
+ return NULL;
+ }
+
+ {
+
+ dTHXa(perl);
+ PERL_SET_CONTEXT(perl);
+ PERL_SET_INTERP(perl);
+
+ perl_construct(perl);
+
+#ifdef PERL_EXIT_DESTRUCT_END
+ PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
+#endif
+
+ n = (pmcf->modules != NGX_CONF_UNSET_PTR) ? pmcf->modules->nelts * 2 : 0;
+
+ embedding = ngx_palloc(cf->pool, (5 + n) * sizeof(char *));
+ if (embedding == NULL) {
+ goto fail;
+ }
+
+ embedding[0] = "";
+
+ if (n++) {
+ m = pmcf->modules->elts;
+ for (i = 0; i < pmcf->modules->nelts; i++) {
+ embedding[2 * i + 1] = "-I";
+ embedding[2 * i + 2] = (char *) m[i].data;
+ }
+ }
+
+ embedding[n++] = "-Mnginx";
+ embedding[n++] = "-e";
+ embedding[n++] = "0";
+ embedding[n] = NULL;
+
+ n = perl_parse(perl, ngx_http_perl_xs_init, n, embedding, NULL);
+
+ if (n != 0) {
+ ngx_log_error(NGX_LOG_ALERT, cf->log, 0, "perl_parse() failed: %d", n);
+ goto fail;
+ }
+
+ sv = get_sv("nginx::VERSION", FALSE);
+ ver = SvPV(sv, len);
+
+ if (ngx_strcmp(ver, NGINX_VERSION) != 0) {
+ ngx_log_error(NGX_LOG_ALERT, cf->log, 0,
+ "version " NGINX_VERSION " of nginx.pm is required, "
+ "but %s was found", ver);
+ goto fail;
+ }
+
+ if (ngx_http_perl_run_requires(aTHX_ pmcf->requires, cf->log) != NGX_OK) {
+ goto fail;
+ }
+
+ }
+
+ return perl;
+
+fail:
+
+ (void) perl_destruct(perl);
+
+ perl_free(perl);
+
+ return NULL;
+}
+
+
+static ngx_int_t
+ngx_http_perl_run_requires(pTHX_ ngx_array_t *requires, ngx_log_t *log)
+{
+ u_char *err;
+ STRLEN len;
+ ngx_str_t *script;
+ ngx_uint_t i;
+
+ if (requires == NGX_CONF_UNSET_PTR) {
+ return NGX_OK;
+ }
+
+ script = requires->elts;
+ for (i = 0; i < requires->nelts; i++) {
+
+ require_pv((char *) script[i].data);
+
+ if (SvTRUE(ERRSV)) {
+
+ err = (u_char *) SvPV(ERRSV, len);
+ while (--len && (err[len] == CR || err[len] == LF)) { /* void */ }
+
+ ngx_log_error(NGX_LOG_EMERG, log, 0,
+ "require_pv(\"%s\") failed: \"%*s\"",
+ script[i].data, len + 1, err);
+
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_perl_call_handler(pTHX_ ngx_http_request_t *r, HV *nginx, SV *sub,
+ SV **args, ngx_str_t *handler, ngx_str_t *rv)
+{
+ SV *sv;
+ int n, status;
+ char *line;
+ u_char *err;
+ STRLEN len, n_a;
+ ngx_uint_t i;
+ ngx_connection_t *c;
+
+ dSP;
+
+ status = 0;
+
+ ENTER;
+ SAVETMPS;
+
+ PUSHMARK(sp);
+
+ sv = sv_2mortal(sv_bless(newRV_noinc(newSViv(PTR2IV(r))), nginx));
+ XPUSHs(sv);
+
+ if (args) {
+ EXTEND(sp, (intptr_t) args[0]);
+
+ for (i = 1; i <= (uintptr_t) args[0]; i++) {
+ PUSHs(sv_2mortal(args[i]));
+ }
+ }
+
+ PUTBACK;
+
+ c = r->connection;
+
+ n = call_sv(sub, G_EVAL);
+
+ SPAGAIN;
+
+ if (n) {
+ if (rv == NULL) {
+ status = POPi;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "call_sv: %d", status);
+
+ } else {
+ line = SvPVx(POPs, n_a);
+ rv->len = n_a;
+
+ rv->data = ngx_pnalloc(r->pool, n_a);
+ if (rv->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(rv->data, line, n_a);
+ }
+ }
+
+ PUTBACK;
+
+ FREETMPS;
+ LEAVE;
+
+ /* check $@ */
+
+ if (SvTRUE(ERRSV)) {
+
+ err = (u_char *) SvPV(ERRSV, len);
+ while (--len && (err[len] == CR || err[len] == LF)) { /* void */ }
+
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "call_sv(\"%V\") failed: \"%*s\"", handler, len + 1, err);
+
+ if (rv) {
+ return NGX_ERROR;
+ }
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (n != 1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "call_sv(\"%V\") returned %d results", handler, n);
+ status = NGX_OK;
+ }
+
+ if (rv) {
+ return NGX_OK;
+ }
+
+ return (ngx_int_t) status;
+}
+
+
+static void
+ngx_http_perl_eval_anon_sub(pTHX_ ngx_str_t *handler, SV **sv)
+{
+ u_char *p;
+
+ for (p = handler->data; *p; p++) {
+ if (*p != ' ' && *p != '\t' && *p != CR && *p != LF) {
+ break;
+ }
+ }
+
+ if (ngx_strncmp(p, "sub ", 4) == 0
+ || ngx_strncmp(p, "sub{", 4) == 0
+ || ngx_strncmp(p, "use ", 4) == 0)
+ {
+ *sv = eval_pv((char *) p, FALSE);
+
+ /* eval_pv() does not set ERRSV on failure */
+
+ return;
+ }
+
+ *sv = NULL;
+}
+
+
+static void *
+ngx_http_perl_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_perl_main_conf_t *pmcf;
+
+ pmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_perl_main_conf_t));
+ if (pmcf == NULL) {
+ return NULL;
+ }
+
+ pmcf->modules = NGX_CONF_UNSET_PTR;
+ pmcf->requires = NGX_CONF_UNSET_PTR;
+
+ return pmcf;
+}
+
+
+static char *
+ngx_http_perl_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+ ngx_http_perl_main_conf_t *pmcf = conf;
+
+ if (pmcf->perl == NULL) {
+ if (ngx_http_perl_init_interpreter(cf, pmcf) != NGX_CONF_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+
+static void
+ngx_http_perl_cleanup_perl(void *data)
+{
+ PerlInterpreter *perl = data;
+
+ PERL_SET_CONTEXT(perl);
+ PERL_SET_INTERP(perl);
+
+ (void) perl_destruct(perl);
+
+ perl_free(perl);
+
+ if (ngx_perl_term) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, "perl term");
+
+ PERL_SYS_TERM();
+ }
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_perl_preconfiguration(ngx_conf_t *cf)
+{
+#if (NGX_HTTP_SSI)
+ ngx_int_t rc;
+ ngx_http_ssi_main_conf_t *smcf;
+
+ smcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_ssi_filter_module);
+
+ rc = ngx_hash_add_key(&smcf->commands, &ngx_http_perl_ssi_command.name,
+ &ngx_http_perl_ssi_command, NGX_HASH_READONLY_KEY);
+
+ if (rc != NGX_OK) {
+ if (rc == NGX_BUSY) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "conflicting SSI command \"%V\"",
+ &ngx_http_perl_ssi_command.name);
+ }
+
+ return NGX_ERROR;
+ }
+#endif
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_perl_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_perl_loc_conf_t *plcf;
+
+ plcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_perl_loc_conf_t));
+ if (plcf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * plcf->handler = { 0, NULL };
+ */
+
+ return plcf;
+}
+
+
+static char *
+ngx_http_perl_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_perl_loc_conf_t *prev = parent;
+ ngx_http_perl_loc_conf_t *conf = child;
+
+ if (conf->sub == NULL) {
+ conf->sub = prev->sub;
+ conf->handler = prev->handler;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_perl(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_perl_loc_conf_t *plcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_perl_main_conf_t *pmcf;
+
+ value = cf->args->elts;
+
+ if (plcf->handler.data) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "duplicate perl handler \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ pmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_perl_module);
+
+ if (pmcf->perl == NULL) {
+ if (ngx_http_perl_init_interpreter(cf, pmcf) != NGX_CONF_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ plcf->handler = value[1];
+
+ {
+
+ dTHXa(pmcf->perl);
+ PERL_SET_CONTEXT(pmcf->perl);
+ PERL_SET_INTERP(pmcf->perl);
+
+ ngx_http_perl_eval_anon_sub(aTHX_ &value[1], &plcf->sub);
+
+ if (plcf->sub == &PL_sv_undef) {
+ ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
+ "eval_pv(\"%V\") failed", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (plcf->sub == NULL) {
+ plcf->sub = newSVpvn((char *) value[1].data, value[1].len);
+ }
+
+ }
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+ clcf->handler = ngx_http_perl_handler;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_perl_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_int_t index;
+ ngx_str_t *value;
+ ngx_http_variable_t *v;
+ ngx_http_perl_variable_t *pv;
+ ngx_http_perl_main_conf_t *pmcf;
+
+ value = cf->args->elts;
+
+ if (value[1].data[0] != '$') {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid variable name \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ value[1].len--;
+ value[1].data++;
+
+ v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE);
+ if (v == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ pv = ngx_palloc(cf->pool, sizeof(ngx_http_perl_variable_t));
+ if (pv == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ index = ngx_http_get_variable_index(cf, &value[1]);
+ if (index == NGX_ERROR) {
+ return NGX_CONF_ERROR;
+ }
+
+ pmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_perl_module);
+
+ if (pmcf->perl == NULL) {
+ if (ngx_http_perl_init_interpreter(cf, pmcf) != NGX_CONF_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ pv->handler = value[2];
+
+ {
+
+ dTHXa(pmcf->perl);
+ PERL_SET_CONTEXT(pmcf->perl);
+ PERL_SET_INTERP(pmcf->perl);
+
+ ngx_http_perl_eval_anon_sub(aTHX_ &value[2], &pv->sub);
+
+ if (pv->sub == &PL_sv_undef) {
+ ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
+ "eval_pv(\"%V\") failed", &value[2]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (pv->sub == NULL) {
+ pv->sub = newSVpvn((char *) value[2].data, value[2].len);
+ }
+
+ }
+
+ v->get_handler = ngx_http_perl_variable;
+ v->data = (uintptr_t) pv;
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_http_perl_init_worker(ngx_cycle_t *cycle)
+{
+ ngx_http_perl_main_conf_t *pmcf;
+
+ pmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_perl_module);
+
+ if (pmcf) {
+ dTHXa(pmcf->perl);
+ PERL_SET_CONTEXT(pmcf->perl);
+ PERL_SET_INTERP(pmcf->perl);
+
+ /* set worker's $$ */
+
+ sv_setiv(GvSV(gv_fetchpv("$", TRUE, SVt_PV)), (I32) ngx_pid);
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_perl_exit(ngx_cycle_t *cycle)
+{
+#if (NGX_HAVE_PERL_MULTIPLICITY)
+
+ /*
+ * the master exit hook is run before global pool cleanup,
+ * therefore just set flag here
+ */
+
+ ngx_perl_term = 1;
+
+#else
+
+ if (nginx_stash) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cycle->log, 0, "perl term");
+
+ (void) perl_destruct(perl);
+
+ perl_free(perl);
+
+ PERL_SYS_TERM();
+ }
+
+#endif
+}
diff --git a/app/nginx/src/http/modules/perl/ngx_http_perl_module.h b/app/nginx/src/http/modules/perl/ngx_http_perl_module.h
new file mode 100644
index 0000000..5e60b03
--- /dev/null
+++ b/app/nginx/src/http/modules/perl/ngx_http_perl_module.h
@@ -0,0 +1,67 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_PERL_MODULE_H_INCLUDED_
+#define _NGX_HTTP_PERL_MODULE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+#include <EXTERN.h>
+#include <perl.h>
+
+
+typedef ngx_http_request_t *nginx;
+
+typedef struct {
+ ngx_str_t filename;
+ ngx_str_t redirect_uri;
+ ngx_str_t redirect_args;
+
+ SV *next;
+
+ ngx_uint_t done; /* unsigned done:1; */
+
+ ngx_array_t *variables; /* array of ngx_http_perl_var_t */
+
+#if (NGX_HTTP_SSI)
+ ngx_http_ssi_ctx_t *ssi;
+#endif
+} ngx_http_perl_ctx_t;
+
+
+typedef struct {
+ ngx_uint_t hash;
+ ngx_str_t name;
+ ngx_str_t value;
+} ngx_http_perl_var_t;
+
+
+extern ngx_module_t ngx_http_perl_module;
+
+
+/*
+ * workaround for "unused variable `Perl___notused'" warning
+ * when building with perl 5.6.1
+ */
+#ifndef PERL_IMPLICIT_CONTEXT
+#undef dTHXa
+#define dTHXa(a)
+#endif
+
+
+extern void boot_DynaLoader(pTHX_ CV* cv);
+
+
+void ngx_http_perl_handle_request(ngx_http_request_t *r);
+void ngx_http_perl_sleep_handler(ngx_http_request_t *r);
+
+
+#endif /* _NGX_HTTP_PERL_MODULE_H_INCLUDED_ */
diff --git a/app/nginx/src/http/modules/perl/typemap b/app/nginx/src/http/modules/perl/typemap
new file mode 100644
index 0000000..e2f1a4c
--- /dev/null
+++ b/app/nginx/src/http/modules/perl/typemap
@@ -0,0 +1,3 @@
+TYPEMAP
+
+nginx T_PTROBJ