From e18a033b921d0d79fa8278f853548e6125b93e0c Mon Sep 17 00:00:00 2001 From: Konstantin Ananyev Date: Tue, 31 Oct 2017 12:40:17 +0000 Subject: Integrate TLDK with NGINX Created a clone of nginx (from https://github.com/nginx/nginx) to demonstrate and benchmark TLDK library integrated with real world application. A new nginx module is created and and BSD socket-like API is implemented on top of native TLDK API. Note, that right now only minimalistic subset of socket-like API is provided: - accept - close - readv - recv - writev so only limited nginx functionality is available for a moment. Change-Id: Ie1efe9349a0538da4348a48fb8306cbf636b5a92 Signed-off-by: Mohammad Abdul Awal Signed-off-by: Reshma Pattan Signed-off-by: Remy Horton Signed-off-by: Konstantin Ananyev --- .../src/http/modules/ngx_http_access_module.c | 469 ++ .../http/modules/ngx_http_addition_filter_module.c | 252 + .../src/http/modules/ngx_http_auth_basic_module.c | 467 ++ .../http/modules/ngx_http_auth_request_module.c | 444 ++ .../src/http/modules/ngx_http_autoindex_module.c | 1050 ++++ .../src/http/modules/ngx_http_browser_module.c | 715 +++ .../http/modules/ngx_http_charset_filter_module.c | 1685 ++++++ .../http/modules/ngx_http_chunked_filter_module.c | 243 + app/nginx/src/http/modules/ngx_http_dav_module.c | 1159 ++++ .../src/http/modules/ngx_http_degradation_module.c | 243 + .../src/http/modules/ngx_http_empty_gif_module.c | 140 + .../src/http/modules/ngx_http_fastcgi_module.c | 3788 ++++++++++++ app/nginx/src/http/modules/ngx_http_flv_module.c | 266 + app/nginx/src/http/modules/ngx_http_geo_module.c | 1658 +++++ app/nginx/src/http/modules/ngx_http_geoip_module.c | 925 +++ .../http/modules/ngx_http_gunzip_filter_module.c | 687 +++ .../src/http/modules/ngx_http_gzip_filter_module.c | 1245 ++++ .../src/http/modules/ngx_http_gzip_static_module.c | 331 + .../http/modules/ngx_http_headers_filter_module.c | 741 +++ .../http/modules/ngx_http_image_filter_module.c | 1675 +++++ app/nginx/src/http/modules/ngx_http_index_module.c | 540 ++ .../src/http/modules/ngx_http_limit_conn_module.c | 670 ++ .../src/http/modules/ngx_http_limit_req_module.c | 960 +++ app/nginx/src/http/modules/ngx_http_log_module.c | 1857 ++++++ app/nginx/src/http/modules/ngx_http_map_module.c | 589 ++ .../src/http/modules/ngx_http_memcached_module.c | 723 +++ app/nginx/src/http/modules/ngx_http_mp4_module.c | 3548 +++++++++++ .../modules/ngx_http_not_modified_filter_module.c | 266 + app/nginx/src/http/modules/ngx_http_proxy_module.c | 4424 ++++++++++++++ .../http/modules/ngx_http_random_index_module.c | 317 + .../http/modules/ngx_http_range_filter_module.c | 920 +++ .../src/http/modules/ngx_http_realip_module.c | 570 ++ .../src/http/modules/ngx_http_referer_module.c | 671 ++ .../src/http/modules/ngx_http_rewrite_module.c | 1022 ++++ app/nginx/src/http/modules/ngx_http_scgi_module.c | 2005 ++++++ .../src/http/modules/ngx_http_secure_link_module.c | 368 ++ .../http/modules/ngx_http_slice_filter_module.c | 543 ++ .../http/modules/ngx_http_split_clients_module.c | 246 + .../src/http/modules/ngx_http_ssi_filter_module.c | 2930 +++++++++ .../src/http/modules/ngx_http_ssi_filter_module.h | 114 + app/nginx/src/http/modules/ngx_http_ssl_module.c | 992 +++ app/nginx/src/http/modules/ngx_http_ssl_module.h | 66 + .../src/http/modules/ngx_http_static_module.c | 287 + .../src/http/modules/ngx_http_stub_status_module.c | 236 + .../src/http/modules/ngx_http_sub_filter_module.c | 1018 ++++ .../http/modules/ngx_http_upstream_hash_module.c | 676 +++ .../modules/ngx_http_upstream_ip_hash_module.c | 272 + .../modules/ngx_http_upstream_keepalive_module.c | 529 ++ .../modules/ngx_http_upstream_least_conn_module.c | 314 + .../http/modules/ngx_http_upstream_zone_module.c | 246 + .../http/modules/ngx_http_userid_filter_module.c | 842 +++ app/nginx/src/http/modules/ngx_http_uwsgi_module.c | 2393 ++++++++ .../src/http/modules/ngx_http_xslt_filter_module.c | 1147 ++++ app/nginx/src/http/modules/perl/Makefile.PL | 35 + app/nginx/src/http/modules/perl/nginx.pm | 138 + app/nginx/src/http/modules/perl/nginx.xs | 1039 ++++ .../src/http/modules/perl/ngx_http_perl_module.c | 1086 ++++ .../src/http/modules/perl/ngx_http_perl_module.h | 67 + app/nginx/src/http/modules/perl/typemap | 3 + app/nginx/src/http/ngx_http.c | 2081 +++++++ app/nginx/src/http/ngx_http.h | 176 + app/nginx/src/http/ngx_http_cache.h | 207 + app/nginx/src/http/ngx_http_config.h | 75 + app/nginx/src/http/ngx_http_copy_filter_module.c | 380 ++ app/nginx/src/http/ngx_http_core_module.c | 5359 ++++++++++++++++ app/nginx/src/http/ngx_http_core_module.h | 588 ++ app/nginx/src/http/ngx_http_file_cache.c | 2656 ++++++++ app/nginx/src/http/ngx_http_header_filter_module.c | 629 ++ app/nginx/src/http/ngx_http_parse.c | 2379 ++++++++ .../src/http/ngx_http_postpone_filter_module.c | 176 + app/nginx/src/http/ngx_http_request.c | 3671 +++++++++++ app/nginx/src/http/ngx_http_request.h | 598 ++ app/nginx/src/http/ngx_http_request_body.c | 1165 ++++ app/nginx/src/http/ngx_http_script.c | 1762 ++++++ app/nginx/src/http/ngx_http_script.h | 257 + app/nginx/src/http/ngx_http_special_response.c | 828 +++ app/nginx/src/http/ngx_http_upstream.c | 6388 ++++++++++++++++++++ app/nginx/src/http/ngx_http_upstream.h | 430 ++ app/nginx/src/http/ngx_http_upstream_round_robin.c | 842 +++ app/nginx/src/http/ngx_http_upstream_round_robin.h | 156 + app/nginx/src/http/ngx_http_variables.c | 2685 ++++++++ app/nginx/src/http/ngx_http_variables.h | 114 + app/nginx/src/http/ngx_http_write_filter_module.c | 327 + app/nginx/src/http/v2/ngx_http_v2.c | 4484 ++++++++++++++ app/nginx/src/http/v2/ngx_http_v2.h | 342 ++ app/nginx/src/http/v2/ngx_http_v2_filter_module.c | 1448 +++++ app/nginx/src/http/v2/ngx_http_v2_huff_decode.c | 2714 +++++++++ app/nginx/src/http/v2/ngx_http_v2_huff_encode.c | 254 + app/nginx/src/http/v2/ngx_http_v2_module.c | 509 ++ app/nginx/src/http/v2/ngx_http_v2_module.h | 44 + app/nginx/src/http/v2/ngx_http_v2_table.c | 349 ++ 91 files changed, 96925 insertions(+) create mode 100644 app/nginx/src/http/modules/ngx_http_access_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_addition_filter_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_auth_basic_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_auth_request_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_autoindex_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_browser_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_charset_filter_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_chunked_filter_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_dav_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_degradation_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_empty_gif_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_fastcgi_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_flv_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_geo_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_geoip_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_gunzip_filter_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_gzip_filter_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_gzip_static_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_headers_filter_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_image_filter_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_index_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_limit_conn_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_limit_req_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_log_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_map_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_memcached_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_mp4_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_not_modified_filter_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_proxy_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_random_index_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_range_filter_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_realip_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_referer_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_rewrite_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_scgi_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_secure_link_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_slice_filter_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_split_clients_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_ssi_filter_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_ssi_filter_module.h create mode 100644 app/nginx/src/http/modules/ngx_http_ssl_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_ssl_module.h create mode 100644 app/nginx/src/http/modules/ngx_http_static_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_stub_status_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_sub_filter_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_upstream_hash_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_upstream_ip_hash_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_upstream_keepalive_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_upstream_least_conn_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_upstream_zone_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_userid_filter_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_uwsgi_module.c create mode 100644 app/nginx/src/http/modules/ngx_http_xslt_filter_module.c create mode 100644 app/nginx/src/http/modules/perl/Makefile.PL create mode 100644 app/nginx/src/http/modules/perl/nginx.pm create mode 100644 app/nginx/src/http/modules/perl/nginx.xs create mode 100644 app/nginx/src/http/modules/perl/ngx_http_perl_module.c create mode 100644 app/nginx/src/http/modules/perl/ngx_http_perl_module.h create mode 100644 app/nginx/src/http/modules/perl/typemap create mode 100644 app/nginx/src/http/ngx_http.c create mode 100644 app/nginx/src/http/ngx_http.h create mode 100644 app/nginx/src/http/ngx_http_cache.h create mode 100644 app/nginx/src/http/ngx_http_config.h create mode 100644 app/nginx/src/http/ngx_http_copy_filter_module.c create mode 100644 app/nginx/src/http/ngx_http_core_module.c create mode 100644 app/nginx/src/http/ngx_http_core_module.h create mode 100644 app/nginx/src/http/ngx_http_file_cache.c create mode 100644 app/nginx/src/http/ngx_http_header_filter_module.c create mode 100644 app/nginx/src/http/ngx_http_parse.c create mode 100644 app/nginx/src/http/ngx_http_postpone_filter_module.c create mode 100644 app/nginx/src/http/ngx_http_request.c create mode 100644 app/nginx/src/http/ngx_http_request.h create mode 100644 app/nginx/src/http/ngx_http_request_body.c create mode 100644 app/nginx/src/http/ngx_http_script.c create mode 100644 app/nginx/src/http/ngx_http_script.h create mode 100644 app/nginx/src/http/ngx_http_special_response.c create mode 100644 app/nginx/src/http/ngx_http_upstream.c create mode 100644 app/nginx/src/http/ngx_http_upstream.h create mode 100644 app/nginx/src/http/ngx_http_upstream_round_robin.c create mode 100644 app/nginx/src/http/ngx_http_upstream_round_robin.h create mode 100644 app/nginx/src/http/ngx_http_variables.c create mode 100644 app/nginx/src/http/ngx_http_variables.h create mode 100644 app/nginx/src/http/ngx_http_write_filter_module.c create mode 100644 app/nginx/src/http/v2/ngx_http_v2.c create mode 100644 app/nginx/src/http/v2/ngx_http_v2.h create mode 100644 app/nginx/src/http/v2/ngx_http_v2_filter_module.c create mode 100644 app/nginx/src/http/v2/ngx_http_v2_huff_decode.c create mode 100644 app/nginx/src/http/v2/ngx_http_v2_huff_encode.c create mode 100644 app/nginx/src/http/v2/ngx_http_v2_module.c create mode 100644 app/nginx/src/http/v2/ngx_http_v2_module.h create mode 100644 app/nginx/src/http/v2/ngx_http_v2_table.c (limited to 'app/nginx/src/http') 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 +#include +#include + + +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 +#include +#include + + +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 +#include +#include +#include + + +#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 +#include +#include + + +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 +#include +#include + + +#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[] = + "" CRLF + "Index of " + ; + + static u_char header[] = + "" CRLF + "" CRLF + "

Index of " + ; + + static u_char tail[] = + "" CRLF + "" 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("

") - 1 + + sizeof("
../" CRLF) - 1
+          + sizeof("

") - 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("") - 1 + + entry[i].name.len - entry[i].utf_len + + entry[i].escape_html + + NGX_HTTP_AUTOINDEX_NAME_LEN + sizeof(">") - 2 + + sizeof("") - 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, "", sizeof("") - 1); + + b->last = ngx_cpymem(b->last, "
../" CRLF,
+                         sizeof("
../" 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, "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, "..>", sizeof("..>") - 1);
+
+        } else {
+            if (entry[i].dir && NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
+                *b->last++ = '/';
+                len++;
+            }
+
+            b->last = ngx_cpymem(b->last, "", sizeof("") - 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, "

", sizeof("

") - 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[] = "" CRLF "" CRLF; + static u_char tail[] = "" 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("" 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 +#include +#include + + +/* + * 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 +#include +#include + + +#define NGX_HTTP_CHARSET_OFF -2 +#define NGX_HTTP_NO_CHARSET -3 +#define NGX_HTTP_CHARSET_VAR 0x10000 + +/* 1 byte length and up to 3 bytes for the UTF-8 encoding of the UCS-2 */ +#define NGX_UTF_LEN 4 + +#define NGX_HTML_ENTITY_LEN (sizeof("􏿿") - 1) + + +typedef struct { + u_char **tables; + ngx_str_t name; + + unsigned length:16; + unsigned utf8:1; +} ngx_http_charset_t; + + +typedef struct { + ngx_int_t src; + ngx_int_t dst; +} ngx_http_charset_recode_t; + + +typedef struct { + ngx_int_t src; + ngx_int_t dst; + u_char *src2dst; + u_char *dst2src; +} ngx_http_charset_tables_t; + + +typedef struct { + ngx_array_t charsets; /* ngx_http_charset_t */ + ngx_array_t tables; /* ngx_http_charset_tables_t */ + ngx_array_t recodes; /* ngx_http_charset_recode_t */ +} ngx_http_charset_main_conf_t; + + +typedef struct { + ngx_int_t charset; + ngx_int_t source_charset; + ngx_flag_t override_charset; + + ngx_hash_t types; + ngx_array_t *types_keys; +} ngx_http_charset_loc_conf_t; + + +typedef struct { + u_char *table; + ngx_int_t charset; + ngx_str_t charset_name; + + ngx_chain_t *busy; + ngx_chain_t *free_bufs; + ngx_chain_t *free_buffers; + + size_t saved_len; + u_char saved[NGX_UTF_LEN]; + + unsigned length:16; + unsigned from_utf8:1; + unsigned to_utf8:1; +} ngx_http_charset_ctx_t; + + +typedef struct { + ngx_http_charset_tables_t *table; + ngx_http_charset_t *charset; + ngx_uint_t characters; +} ngx_http_charset_conf_ctx_t; + + +static ngx_int_t ngx_http_destination_charset(ngx_http_request_t *r, + ngx_str_t *name); +static ngx_int_t ngx_http_main_request_charset(ngx_http_request_t *r, + ngx_str_t *name); +static ngx_int_t ngx_http_source_charset(ngx_http_request_t *r, + ngx_str_t *name); +static ngx_int_t ngx_http_get_charset(ngx_http_request_t *r, ngx_str_t *name); +static ngx_inline void ngx_http_set_charset(ngx_http_request_t *r, + ngx_str_t *charset); +static ngx_int_t ngx_http_charset_ctx(ngx_http_request_t *r, + ngx_http_charset_t *charsets, ngx_int_t charset, ngx_int_t source_charset); +static ngx_uint_t ngx_http_charset_recode(ngx_buf_t *b, u_char *table); +static ngx_chain_t *ngx_http_charset_recode_from_utf8(ngx_pool_t *pool, + ngx_buf_t *buf, ngx_http_charset_ctx_t *ctx); +static ngx_chain_t *ngx_http_charset_recode_to_utf8(ngx_pool_t *pool, + ngx_buf_t *buf, ngx_http_charset_ctx_t *ctx); + +static ngx_chain_t *ngx_http_charset_get_buf(ngx_pool_t *pool, + ngx_http_charset_ctx_t *ctx); +static ngx_chain_t *ngx_http_charset_get_buffer(ngx_pool_t *pool, + ngx_http_charset_ctx_t *ctx, size_t size); + +static char *ngx_http_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_http_charset_map(ngx_conf_t *cf, ngx_command_t *dummy, + void *conf); + +static char *ngx_http_set_charset_slot(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_http_add_charset(ngx_array_t *charsets, ngx_str_t *name); + +static void *ngx_http_charset_create_main_conf(ngx_conf_t *cf); +static void *ngx_http_charset_create_loc_conf(ngx_conf_t *cf); +static char *ngx_http_charset_merge_loc_conf(ngx_conf_t *cf, + void *parent, void *child); +static ngx_int_t ngx_http_charset_postconfiguration(ngx_conf_t *cf); + + +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 +#include +#include + + +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 +#include +#include + + +#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, ©.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, ©.path, dir); + + if (rc != NGX_OK) { + return rc; + } + } + } + + if (ngx_is_dir(&fi)) { + + path.len -= 2; /* omit "/\0" */ + + if (r->method == NGX_HTTP_MOVE) { + if (ngx_rename_file(path.data, copy.path.data) != NGX_FILE_ERROR) { + return NGX_HTTP_CREATED; + } + } + + if (ngx_create_dir(copy.path.data, ngx_file_access(&fi)) + == NGX_FILE_ERROR) + { + return ngx_http_dav_error(r->connection->log, ngx_errno, + NGX_HTTP_NOT_FOUND, + ngx_create_dir_n, copy.path.data); + } + + copy.len = path.len; + + tree.init_handler = NULL; + tree.file_handler = ngx_http_dav_copy_tree_file; + tree.pre_tree_handler = ngx_http_dav_copy_dir; + tree.post_tree_handler = ngx_http_dav_copy_dir_time; + tree.spec_handler = ngx_http_dav_noop; + tree.data = © + tree.alloc = 0; + tree.log = r->connection->log; + + if (ngx_walk_tree(&tree, &path) == NGX_OK) { + + if (r->method == NGX_HTTP_MOVE) { + rc = ngx_http_dav_delete_path(r, &path, 1); + + if (rc != NGX_OK) { + return rc; + } + } + + return NGX_HTTP_CREATED; + } + + } else { + + if (r->method == NGX_HTTP_MOVE) { + + dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module); + + ext.access = 0; + ext.path_access = dlcf->access; + ext.time = -1; + ext.create_path = 1; + ext.delete_file = 0; + ext.log = r->connection->log; + + if (ngx_ext_rename_file(&path, ©.path, &ext) == NGX_OK) { + return NGX_HTTP_NO_CONTENT; + } + + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + dlcf = ngx_http_get_module_loc_conf(r, ngx_http_dav_module); + + cf.size = ngx_file_size(&fi); + cf.buf_size = 0; + cf.access = dlcf->access; + cf.time = ngx_file_mtime(&fi); + cf.log = r->connection->log; + + if (ngx_copy_file(path.data, copy.path.data, &cf) == NGX_OK) { + return NGX_HTTP_NO_CONTENT; + } + } + + return NGX_HTTP_INTERNAL_SERVER_ERROR; +} + + +static ngx_int_t +ngx_http_dav_copy_dir(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + u_char *p, *dir; + size_t len; + ngx_http_dav_copy_ctx_t *copy; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy dir: \"%s\"", path->data); + + copy = ctx->data; + + len = copy->path.len + path->len; + + dir = ngx_alloc(len + 1, ctx->log); + if (dir == NULL) { + return NGX_ABORT; + } + + p = ngx_cpymem(dir, copy->path.data, copy->path.len); + (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy dir to: \"%s\"", dir); + + if (ngx_create_dir(dir, ngx_dir_access(ctx->access)) == NGX_FILE_ERROR) { + (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_create_dir_n, + dir); + } + + ngx_free(dir); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_dav_copy_dir_time(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + u_char *p, *dir; + size_t len; + ngx_http_dav_copy_ctx_t *copy; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy dir time: \"%s\"", path->data); + + copy = ctx->data; + + len = copy->path.len + path->len; + + dir = ngx_alloc(len + 1, ctx->log); + if (dir == NULL) { + return NGX_ABORT; + } + + p = ngx_cpymem(dir, copy->path.data, copy->path.len); + (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy dir time to: \"%s\"", dir); + +#if (NGX_WIN32) + { + ngx_fd_t fd; + + fd = ngx_open_file(dir, NGX_FILE_RDWR, NGX_FILE_OPEN, 0); + + if (fd == NGX_INVALID_FILE) { + (void) ngx_http_dav_error(ctx->log, ngx_errno, 0, ngx_open_file_n, dir); + goto failed; + } + + if (ngx_set_file_time(NULL, fd, ctx->mtime) != NGX_OK) { + ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno, + ngx_set_file_time_n " \"%s\" failed", dir); + } + + if (ngx_close_file(fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno, + ngx_close_file_n " \"%s\" failed", dir); + } + } + +failed: + +#else + + if (ngx_set_file_time(dir, 0, ctx->mtime) != NGX_OK) { + ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno, + ngx_set_file_time_n " \"%s\" failed", dir); + } + +#endif + + ngx_free(dir); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_dav_copy_tree_file(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + u_char *p, *file; + size_t len; + ngx_copy_file_t cf; + ngx_http_dav_copy_ctx_t *copy; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy file: \"%s\"", path->data); + + copy = ctx->data; + + len = copy->path.len + path->len; + + file = ngx_alloc(len + 1, ctx->log); + if (file == NULL) { + return NGX_ABORT; + } + + p = ngx_cpymem(file, copy->path.data, copy->path.len); + (void) ngx_cpystrn(p, path->data + copy->len, path->len - copy->len + 1); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http copy file to: \"%s\"", file); + + cf.size = ctx->size; + cf.buf_size = 0; + cf.access = ctx->access; + cf.time = ctx->mtime; + cf.log = ctx->log; + + (void) ngx_copy_file(path->data, file, &cf); + + ngx_free(file); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_dav_depth(ngx_http_request_t *r, ngx_int_t dflt) +{ + ngx_table_elt_t *depth; + + depth = r->headers_in.depth; + + if (depth == NULL) { + return dflt; + } + + if (depth->value.len == 1) { + + if (depth->value.data[0] == '0') { + return 0; + } + + if (depth->value.data[0] == '1') { + return 1; + } + + } else { + + if (depth->value.len == sizeof("infinity") - 1 + && ngx_strcmp(depth->value.data, "infinity") == 0) + { + return NGX_HTTP_DAV_INFINITY_DEPTH; + } + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent invalid \"Depth\" header: \"%V\"", + &depth->value); + + return NGX_HTTP_DAV_INVALID_DEPTH; +} + + +static ngx_int_t +ngx_http_dav_error(ngx_log_t *log, ngx_err_t err, ngx_int_t not_found, + char *failed, u_char *path) +{ + ngx_int_t rc; + ngx_uint_t level; + + if (err == NGX_ENOENT || err == NGX_ENOTDIR || err == NGX_ENAMETOOLONG) { + level = NGX_LOG_ERR; + rc = not_found; + + } else if (err == NGX_EACCES || err == NGX_EPERM) { + level = NGX_LOG_ERR; + rc = NGX_HTTP_FORBIDDEN; + + } else if (err == NGX_EEXIST) { + level = NGX_LOG_ERR; + rc = NGX_HTTP_NOT_ALLOWED; + + } else if (err == NGX_ENOSPC) { + level = NGX_LOG_CRIT; + rc = NGX_HTTP_INSUFFICIENT_STORAGE; + + } else { + level = NGX_LOG_CRIT; + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_log_error(level, log, err, "%s \"%s\" failed", failed, path); + + return rc; +} + + +static ngx_int_t +ngx_http_dav_location(ngx_http_request_t *r, u_char *path) +{ + u_char *location; + ngx_http_core_loc_conf_t *clcf; + + r->headers_out.location = ngx_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 +#include +#include + + +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 +#include +#include + + +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 +#include +#include + + +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(¶ms->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(¶ms_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(¶ms_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(¶ms_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 = ¶ms->flushes; + sc.lengths = ¶ms->lengths; + sc.values = ¶ms->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 = ¶ms->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 +#include +#include + + +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 +#include +#include + + +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 +#include +#include + +#include +#include + + +#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 +#include +#include + +#include + + +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 +#include +#include + +#include + + +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 +#include +#include + + +#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 +#include +#include + + +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 +#include +#include + +#include + + +#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 +#include +#include + + +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 +#include +#include + + +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 +#include +#include + + +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 +#include +#include + +#if (NGX_ZLIB) +#include +#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 +#include +#include + + +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 +#include +#include + + +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 +#include +#include + + +#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 +#include +#include + + +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 +#include +#include + + +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 +#include +#include + + +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 +#include +#include + + +/* + * 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 +#include +#include + + +#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 +#include +#include + + +#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 +#include +#include + + +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 = ®ex->lengths; + sc.values = &lcf->codes; + sc.variables = ngx_http_script_variables_count(&value[2]); + sc.main = regex; + sc.complete_lengths = 1; + sc.compile_args = !regex->redirect; + + if (ngx_http_script_compile(&sc) != NGX_OK) { + return NGX_CONF_ERROR; + } + + regex = sc.main; + + regex->size = sc.size; + regex->args = sc.args; + + if (sc.variables == 0 && !sc.dup_capture) { + regex->lengths = NULL; + } + + regex_end = ngx_http_script_add_code(lcf->codes, + sizeof(ngx_http_script_regex_end_code_t), + ®ex); + if (regex_end == NULL) { + return NGX_CONF_ERROR; + } + + regex_end->code = ngx_http_script_regex_end_code; + regex_end->uri = regex->uri; + regex_end->args = regex->args; + regex_end->add_args = regex->add_args; + regex_end->redirect = regex->redirect; + + if (last) { + code = ngx_http_script_add_code(lcf->codes, sizeof(uintptr_t), ®ex); + if (code == NULL) { + return NGX_CONF_ERROR; + } + + *code = NULL; + } + + regex->next = (u_char *) lcf->codes->elts + lcf->codes->nelts + - (u_char *) regex; + + return NGX_CONF_OK; +} + + +static char * +ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_rewrite_loc_conf_t *lcf = conf; + + u_char *p; + ngx_str_t *value, *v; + ngx_http_script_return_code_t *ret; + ngx_http_compile_complex_value_t ccv; + + ret = ngx_http_script_start_code(cf->pool, &lcf->codes, + sizeof(ngx_http_script_return_code_t)); + if (ret == NULL) { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + ngx_memzero(ret, sizeof(ngx_http_script_return_code_t)); + + ret->code = ngx_http_script_return_code; + + p = value[1].data; + + ret->status = ngx_atoi(p, value[1].len); + + if (ret->status == (uintptr_t) NGX_ERROR) { + + if (cf->args->nelts == 2 + && (ngx_strncmp(p, "http://", sizeof("http://") - 1) == 0 + || ngx_strncmp(p, "https://", sizeof("https://") - 1) == 0 + || ngx_strncmp(p, "$scheme", sizeof("$scheme") - 1) == 0)) + { + ret->status = NGX_HTTP_MOVED_TEMPORARILY; + v = &value[1]; + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid return code \"%V\"", &value[1]); + return NGX_CONF_ERROR; + } + + } else { + + if (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 +#include +#include + + +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(¶ms->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(¶ms_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(¶ms_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(¶ms_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 = ¶ms->flushes; + sc.lengths = ¶ms->lengths; + sc.values = ¶ms->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 = ¶ms->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 +#include +#include +#include + + +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 +#include +#include + + +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 +#include +#include + + +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 +#include +#include + +#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[] = "" CRLF +"" CRLF +"" CRLF +"" CRLF +"" CRLF +"" CRLF +; + + +static u_char ngx_http_msie_refresh_head[] = +"" CRLF; + + +static char ngx_http_error_301_page[] = +"" CRLF +"301 Moved Permanently" CRLF +"" CRLF +"

301 Moved Permanently

" CRLF +; + + +static char ngx_http_error_302_page[] = +"" CRLF +"302 Found" CRLF +"" CRLF +"

302 Found

" CRLF +; + + +static char ngx_http_error_303_page[] = +"" CRLF +"303 See Other" CRLF +"" CRLF +"

303 See Other

" CRLF +; + + +static char ngx_http_error_307_page[] = +"" CRLF +"307 Temporary Redirect" CRLF +"" CRLF +"

307 Temporary Redirect

" CRLF +; + + +static char ngx_http_error_400_page[] = +"" CRLF +"400 Bad Request" CRLF +"" CRLF +"

400 Bad Request

" CRLF +; + + +static char ngx_http_error_401_page[] = +"" CRLF +"401 Authorization Required" CRLF +"" CRLF +"

401 Authorization Required

" CRLF +; + + +static char ngx_http_error_402_page[] = +"" CRLF +"402 Payment Required" CRLF +"" CRLF +"

402 Payment Required

" CRLF +; + + +static char ngx_http_error_403_page[] = +"" CRLF +"403 Forbidden" CRLF +"" CRLF +"

403 Forbidden

" CRLF +; + + +static char ngx_http_error_404_page[] = +"" CRLF +"404 Not Found" CRLF +"" CRLF +"

404 Not Found

" CRLF +; + + +static char ngx_http_error_405_page[] = +"" CRLF +"405 Not Allowed" CRLF +"" CRLF +"

405 Not Allowed

" CRLF +; + + +static char ngx_http_error_406_page[] = +"" CRLF +"406 Not Acceptable" CRLF +"" CRLF +"

406 Not Acceptable

" CRLF +; + + +static char ngx_http_error_408_page[] = +"" CRLF +"408 Request Time-out" CRLF +"" CRLF +"

408 Request Time-out

" CRLF +; + + +static char ngx_http_error_409_page[] = +"" CRLF +"409 Conflict" CRLF +"" CRLF +"

409 Conflict

" CRLF +; + + +static char ngx_http_error_410_page[] = +"" CRLF +"410 Gone" CRLF +"" CRLF +"

410 Gone

" CRLF +; + + +static char ngx_http_error_411_page[] = +"" CRLF +"411 Length Required" CRLF +"" CRLF +"

411 Length Required

" CRLF +; + + +static char ngx_http_error_412_page[] = +"" CRLF +"412 Precondition Failed" CRLF +"" CRLF +"

412 Precondition Failed

" CRLF +; + + +static char ngx_http_error_413_page[] = +"" CRLF +"413 Request Entity Too Large" CRLF +"" CRLF +"

413 Request Entity Too Large

" CRLF +; + + +static char ngx_http_error_414_page[] = +"" CRLF +"414 Request-URI Too Large" CRLF +"" CRLF +"

414 Request-URI Too Large

" CRLF +; + + +static char ngx_http_error_415_page[] = +"" CRLF +"415 Unsupported Media Type" CRLF +"" CRLF +"

415 Unsupported Media Type

" CRLF +; + + +static char ngx_http_error_416_page[] = +"" CRLF +"416 Requested Range Not Satisfiable" CRLF +"" CRLF +"

416 Requested Range Not Satisfiable

" CRLF +; + + +static char ngx_http_error_421_page[] = +"" CRLF +"421 Misdirected Request" CRLF +"" CRLF +"

421 Misdirected Request

" CRLF +; + + +static char ngx_http_error_429_page[] = +"" CRLF +"429 Too Many Requests" CRLF +"" CRLF +"

429 Too Many Requests

" CRLF +; + + +static char ngx_http_error_494_page[] = +"" CRLF +"400 Request Header Or Cookie Too Large" +CRLF +"" CRLF +"

400 Bad Request

" CRLF +"
Request Header Or Cookie Too Large
" CRLF +; + + +static char ngx_http_error_495_page[] = +"" CRLF +"400 The SSL certificate error" +CRLF +"" CRLF +"

400 Bad Request

" CRLF +"
The SSL certificate error
" CRLF +; + + +static char ngx_http_error_496_page[] = +"" CRLF +"400 No required SSL certificate was sent" +CRLF +"" CRLF +"

400 Bad Request

" CRLF +"
No required SSL certificate was sent
" CRLF +; + + +static char ngx_http_error_497_page[] = +"" CRLF +"400 The plain HTTP request was sent to HTTPS port" +CRLF +"" CRLF +"

400 Bad Request

" CRLF +"
The plain HTTP request was sent to HTTPS port
" CRLF +; + + +static char ngx_http_error_500_page[] = +"" CRLF +"500 Internal Server Error" CRLF +"" CRLF +"

500 Internal Server Error

" CRLF +; + + +static char ngx_http_error_501_page[] = +"" CRLF +"501 Not Implemented" CRLF +"" CRLF +"

501 Not Implemented

" CRLF +; + + +static char ngx_http_error_502_page[] = +"" CRLF +"502 Bad Gateway" CRLF +"" CRLF +"

502 Bad Gateway

" CRLF +; + + +static char ngx_http_error_503_page[] = +"" CRLF +"503 Service Temporarily Unavailable" CRLF +"" CRLF +"

503 Service Temporarily Unavailable

" CRLF +; + + +static char ngx_http_error_504_page[] = +"" CRLF +"504 Gateway Time-out" CRLF +"" CRLF +"

504 Gateway Time-out

" CRLF +; + + +static char ngx_http_error_507_page[] = +"" CRLF +"507 Insufficient Storage" CRLF +"" CRLF +"

507 Insufficient Storage

" CRLF +; + + +static ngx_str_t ngx_http_error_pages[] = { + + ngx_null_string, /* 201, 204 */ + +#define NGX_HTTP_LAST_2XX 202 +#define NGX_HTTP_OFF_3XX (NGX_HTTP_LAST_2XX - 201) + + /* ngx_null_string, */ /* 300 */ + ngx_string(ngx_http_error_301_page), + ngx_string(ngx_http_error_302_page), + ngx_string(ngx_http_error_303_page), + ngx_null_string, /* 304 */ + ngx_null_string, /* 305 */ + ngx_null_string, /* 306 */ + ngx_string(ngx_http_error_307_page), + +#define NGX_HTTP_LAST_3XX 308 +#define NGX_HTTP_OFF_4XX (NGX_HTTP_LAST_3XX - 301 + NGX_HTTP_OFF_3XX) + + ngx_string(ngx_http_error_400_page), + ngx_string(ngx_http_error_401_page), + ngx_string(ngx_http_error_402_page), + ngx_string(ngx_http_error_403_page), + ngx_string(ngx_http_error_404_page), + ngx_string(ngx_http_error_405_page), + ngx_string(ngx_http_error_406_page), + ngx_null_string, /* 407 */ + ngx_string(ngx_http_error_408_page), + ngx_string(ngx_http_error_409_page), + ngx_string(ngx_http_error_410_page), + ngx_string(ngx_http_error_411_page), + ngx_string(ngx_http_error_412_page), + ngx_string(ngx_http_error_413_page), + ngx_string(ngx_http_error_414_page), + ngx_string(ngx_http_error_415_page), + ngx_string(ngx_http_error_416_page), + ngx_null_string, /* 417 */ + ngx_null_string, /* 418 */ + ngx_null_string, /* 419 */ + ngx_null_string, /* 420 */ + ngx_string(ngx_http_error_421_page), + ngx_null_string, /* 422 */ + ngx_null_string, /* 423 */ + ngx_null_string, /* 424 */ + ngx_null_string, /* 425 */ + ngx_null_string, /* 426 */ + ngx_null_string, /* 427 */ + ngx_null_string, /* 428 */ + ngx_string(ngx_http_error_429_page), + +#define NGX_HTTP_LAST_4XX 430 +#define NGX_HTTP_OFF_5XX (NGX_HTTP_LAST_4XX - 400 + NGX_HTTP_OFF_4XX) + + ngx_string(ngx_http_error_494_page), /* 494, request header too large */ + ngx_string(ngx_http_error_495_page), /* 495, https certificate error */ + ngx_string(ngx_http_error_496_page), /* 496, https no certificate */ + ngx_string(ngx_http_error_497_page), /* 497, http to https */ + ngx_string(ngx_http_error_404_page), /* 498, canceled */ + ngx_null_string, /* 499, client has closed connection */ + + ngx_string(ngx_http_error_500_page), + ngx_string(ngx_http_error_501_page), + ngx_string(ngx_http_error_502_page), + ngx_string(ngx_http_error_503_page), + ngx_string(ngx_http_error_504_page), + ngx_null_string, /* 505 */ + ngx_null_string, /* 506 */ + ngx_string(ngx_http_error_507_page) + +#define NGX_HTTP_LAST_5XX 508 + +}; + + +ngx_int_t +ngx_http_special_response_handler(ngx_http_request_t *r, ngx_int_t error) +{ + ngx_uint_t i, err; + ngx_http_err_page_t *err_page; + ngx_http_core_loc_conf_t *clcf; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http special response: %i, \"%V?%V\"", + error, &r->uri, &r->args); + + r->err_status = error; + + if (r->keepalive) { + switch (error) { + case NGX_HTTP_BAD_REQUEST: + case NGX_HTTP_REQUEST_ENTITY_TOO_LARGE: + case NGX_HTTP_REQUEST_URI_TOO_LARGE: + case NGX_HTTP_TO_HTTPS: + case NGX_HTTPS_CERT_ERROR: + case NGX_HTTPS_NO_CERT: + case NGX_HTTP_INTERNAL_SERVER_ERROR: + case NGX_HTTP_NOT_IMPLEMENTED: + r->keepalive = 0; + } + } + + if (r->lingering_close) { + switch (error) { + case NGX_HTTP_BAD_REQUEST: + case NGX_HTTP_TO_HTTPS: + case NGX_HTTPS_CERT_ERROR: + case NGX_HTTPS_NO_CERT: + r->lingering_close = 0; + } + } + + r->headers_out.content_type.len = 0; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (!r->error_page && clcf->error_pages && r->uri_changes != 0) { + + if (clcf->recursive_error_pages == 0) { + r->error_page = 1; + } + + err_page = clcf->error_pages->elts; + + for (i = 0; i < clcf->error_pages->nelts; i++) { + if (err_page[i].status == error) { + return ngx_http_send_error_page(r, &err_page[i]); + } + } + } + + r->expect_tested = 1; + + if (ngx_http_discard_request_body(r) != NGX_OK) { + r->keepalive = 0; + } + + if (clcf->msie_refresh + && r->headers_in.msie + && (error == NGX_HTTP_MOVED_PERMANENTLY + || error == NGX_HTTP_MOVED_TEMPORARILY)) + { + return ngx_http_send_refresh(r); + } + + if (error == NGX_HTTP_CREATED) { + /* 201 */ + err = 0; + + } else if (error == NGX_HTTP_NO_CONTENT) { + /* 204 */ + err = 0; + + } else if (error >= NGX_HTTP_MOVED_PERMANENTLY + && error < NGX_HTTP_LAST_3XX) + { + /* 3XX */ + err = error - NGX_HTTP_MOVED_PERMANENTLY + NGX_HTTP_OFF_3XX; + + } else if (error >= NGX_HTTP_BAD_REQUEST + && error < NGX_HTTP_LAST_4XX) + { + /* 4XX */ + err = error - NGX_HTTP_BAD_REQUEST + NGX_HTTP_OFF_4XX; + + } else if (error >= NGX_HTTP_NGINX_CODES + && error < NGX_HTTP_LAST_5XX) + { + /* 49X, 5XX */ + err = error - NGX_HTTP_NGINX_CODES + NGX_HTTP_OFF_5XX; + switch (error) { + case NGX_HTTP_TO_HTTPS: + case NGX_HTTPS_CERT_ERROR: + case NGX_HTTPS_NO_CERT: + case NGX_HTTP_REQUEST_HEADER_TOO_LARGE: + r->err_status = NGX_HTTP_BAD_REQUEST; + } + + } else { + /* unknown code, zero body */ + err = 0; + } + + return ngx_http_send_special_response(r, clcf, err); +} + + +ngx_int_t +ngx_http_filter_finalize_request(ngx_http_request_t *r, ngx_module_t *m, + ngx_int_t error) +{ + void *ctx; + ngx_int_t rc; + + ngx_http_clean_header(r); + + ctx = NULL; + + if (m) { + ctx = r->ctx[m->ctx_index]; + } + + /* clear the modules contexts */ + ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module); + + if (m) { + r->ctx[m->ctx_index] = ctx; + } + + r->filter_finalize = 1; + + rc = ngx_http_special_response_handler(r, error); + + /* NGX_ERROR resets any pending data */ + + switch (rc) { + + case NGX_OK: + case NGX_DONE: + return NGX_ERROR; + + default: + return rc; + } +} + + +void +ngx_http_clean_header(ngx_http_request_t *r) +{ + ngx_memzero(&r->headers_out.status, + sizeof(ngx_http_headers_out_t) + - offsetof(ngx_http_headers_out_t, status)); + + r->headers_out.headers.part.nelts = 0; + r->headers_out.headers.part.next = NULL; + r->headers_out.headers.last = &r->headers_out.headers.part; + + r->headers_out.content_length_n = -1; + r->headers_out.last_modified_time = -1; +} + + +static ngx_int_t +ngx_http_send_error_page(ngx_http_request_t *r, ngx_http_err_page_t *err_page) +{ + ngx_int_t overwrite; + ngx_str_t uri, args; + ngx_table_elt_t *location; + ngx_http_core_loc_conf_t *clcf; + + overwrite = err_page->overwrite; + + if (overwrite && overwrite != NGX_HTTP_OK) { + r->expect_tested = 1; + } + + if (overwrite >= 0) { + r->err_status = overwrite; + } + + if (ngx_http_complex_value(r, &err_page->value, &uri) != NGX_OK) { + return NGX_ERROR; + } + + if (uri.len && uri.data[0] == '/') { + + if (err_page->value.lengths) { + ngx_http_split_args(r, &uri, &args); + + } else { + args = err_page->args; + } + + if (r->method != NGX_HTTP_HEAD) { + r->method = NGX_HTTP_GET; + r->method_name = ngx_http_core_get_method; + } + + return ngx_http_internal_redirect(r, &uri, &args); + } + + if (uri.len && uri.data[0] == '@') { + return ngx_http_named_location(r, &uri); + } + + location = ngx_list_push(&r->headers_out.headers); + + if (location == NULL) { + return NGX_ERROR; + } + + if (overwrite != NGX_HTTP_MOVED_PERMANENTLY + && overwrite != NGX_HTTP_MOVED_TEMPORARILY + && overwrite != NGX_HTTP_SEE_OTHER + && overwrite != NGX_HTTP_TEMPORARY_REDIRECT) + { + r->err_status = NGX_HTTP_MOVED_TEMPORARILY; + } + + location->hash = 1; + ngx_str_set(&location->key, "Location"); + location->value = uri; + + ngx_http_clear_location(r); + + r->headers_out.location = location; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->msie_refresh && r->headers_in.msie) { + return ngx_http_send_refresh(r); + } + + return ngx_http_send_special_response(r, clcf, r->err_status + - NGX_HTTP_MOVED_PERMANENTLY + + NGX_HTTP_OFF_3XX); +} + + +static ngx_int_t +ngx_http_send_special_response(ngx_http_request_t *r, + ngx_http_core_loc_conf_t *clcf, ngx_uint_t err) +{ + u_char *tail; + size_t len; + ngx_int_t rc; + ngx_buf_t *b; + ngx_uint_t msie_padding; + ngx_chain_t out[3]; + + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + len = sizeof(ngx_http_error_full_tail) - 1; + tail = ngx_http_error_full_tail; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + len = sizeof(ngx_http_error_build_tail) - 1; + tail = ngx_http_error_build_tail; + + } else { + len = sizeof(ngx_http_error_tail) - 1; + tail = ngx_http_error_tail; + } + + msie_padding = 0; + + if (ngx_http_error_pages[err].len) { + r->headers_out.content_length_n = ngx_http_error_pages[err].len + len; + if (clcf->msie_padding + && (r->headers_in.msie || r->headers_in.chrome) + && r->http_version >= NGX_HTTP_VERSION_10 + && err >= NGX_HTTP_OFF_4XX) + { + r->headers_out.content_length_n += + sizeof(ngx_http_msie_padding) - 1; + msie_padding = 1; + } + + r->headers_out.content_type_len = sizeof("text/html") - 1; + ngx_str_set(&r->headers_out.content_type, "text/html"); + r->headers_out.content_type_lowcase = NULL; + + } else { + r->headers_out.content_length_n = 0; + } + + if (r->headers_out.content_length) { + r->headers_out.content_length->hash = 0; + r->headers_out.content_length = NULL; + } + + ngx_http_clear_accept_ranges(r); + ngx_http_clear_last_modified(r); + ngx_http_clear_etag(r); + + rc = ngx_http_send_header(r); + + if (rc == NGX_ERROR || r->header_only) { + return rc; + } + + if (ngx_http_error_pages[err].len == 0) { + return ngx_http_send_special(r, NGX_HTTP_LAST); + } + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NGX_ERROR; + } + + b->memory = 1; + b->pos = ngx_http_error_pages[err].data; + b->last = ngx_http_error_pages[err].data + ngx_http_error_pages[err].len; + + out[0].buf = b; + out[0].next = &out[1]; + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NGX_ERROR; + } + + b->memory = 1; + + b->pos = tail; + b->last = tail + len; + + out[1].buf = b; + out[1].next = NULL; + + if (msie_padding) { + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NGX_ERROR; + } + + b->memory = 1; + b->pos = ngx_http_msie_padding; + b->last = ngx_http_msie_padding + sizeof(ngx_http_msie_padding) - 1; + + out[1].next = &out[2]; + out[2].buf = b; + out[2].next = NULL; + } + + if (r == r->main) { + b->last_buf = 1; + } + + b->last_in_chain = 1; + + return ngx_http_output_filter(r, &out[0]); +} + + +static ngx_int_t +ngx_http_send_refresh(ngx_http_request_t *r) +{ + u_char *p, *location; + size_t len, size; + uintptr_t escape; + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t out; + + len = r->headers_out.location->value.len; + location = r->headers_out.location->value.data; + + escape = 2 * ngx_escape_uri(NULL, location, len, NGX_ESCAPE_REFRESH); + + size = sizeof(ngx_http_msie_refresh_head) - 1 + + escape + len + + sizeof(ngx_http_msie_refresh_tail) - 1; + + r->err_status = NGX_HTTP_OK; + + r->headers_out.content_type_len = sizeof("text/html") - 1; + ngx_str_set(&r->headers_out.content_type, "text/html"); + r->headers_out.content_type_lowcase = NULL; + + r->headers_out.location->hash = 0; + r->headers_out.location = NULL; + + r->headers_out.content_length_n = size; + + if (r->headers_out.content_length) { + r->headers_out.content_length->hash = 0; + r->headers_out.content_length = NULL; + } + + ngx_http_clear_accept_ranges(r); + ngx_http_clear_last_modified(r); + ngx_http_clear_etag(r); + + rc = ngx_http_send_header(r); + + if (rc == NGX_ERROR || r->header_only) { + return rc; + } + + b = ngx_create_temp_buf(r->pool, size); + if (b == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(b->pos, ngx_http_msie_refresh_head, + sizeof(ngx_http_msie_refresh_head) - 1); + + if (escape == 0) { + p = ngx_cpymem(p, location, len); + + } else { + p = (u_char *) ngx_escape_uri(p, location, len, NGX_ESCAPE_REFRESH); + } + + b->last = ngx_cpymem(p, ngx_http_msie_refresh_tail, + sizeof(ngx_http_msie_refresh_tail) - 1); + + b->last_buf = (r == r->main) ? 1 : 0; + b->last_in_chain = 1; + + out.buf = b; + out.next = NULL; + + return ngx_http_output_filter(r, &out); +} diff --git a/app/nginx/src/http/ngx_http_upstream.c b/app/nginx/src/http/ngx_http_upstream.c new file mode 100644 index 0000000..3695286 --- /dev/null +++ b/app/nginx/src/http/ngx_http_upstream.c @@ -0,0 +1,6388 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#if (NGX_HTTP_CACHE) +static ngx_int_t ngx_http_upstream_cache(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static ngx_int_t ngx_http_upstream_cache_get(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_http_file_cache_t **cache); +static ngx_int_t ngx_http_upstream_cache_send(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static ngx_int_t ngx_http_upstream_cache_background_update( + ngx_http_request_t *r, ngx_http_upstream_t *u); +static ngx_int_t ngx_http_upstream_cache_check_range(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static ngx_int_t ngx_http_upstream_cache_status(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_upstream_cache_last_modified(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_upstream_cache_etag(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +#endif + +static void ngx_http_upstream_init_request(ngx_http_request_t *r); +static void ngx_http_upstream_resolve_handler(ngx_resolver_ctx_t *ctx); +static void ngx_http_upstream_rd_check_broken_connection(ngx_http_request_t *r); +static void ngx_http_upstream_wr_check_broken_connection(ngx_http_request_t *r); +static void ngx_http_upstream_check_broken_connection(ngx_http_request_t *r, + ngx_event_t *ev); +static void ngx_http_upstream_connect(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static ngx_int_t ngx_http_upstream_reinit(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void ngx_http_upstream_send_request(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_uint_t do_write); +static ngx_int_t ngx_http_upstream_send_request_body(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_uint_t do_write); +static void ngx_http_upstream_send_request_handler(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void ngx_http_upstream_read_request_handler(ngx_http_request_t *r); +static void ngx_http_upstream_process_header(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static ngx_int_t ngx_http_upstream_test_next(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static ngx_int_t ngx_http_upstream_intercept_errors(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static ngx_int_t ngx_http_upstream_test_connect(ngx_connection_t *c); +static ngx_int_t ngx_http_upstream_process_headers(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void ngx_http_upstream_process_body_in_memory(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void ngx_http_upstream_send_response(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void ngx_http_upstream_upgrade(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void ngx_http_upstream_upgraded_read_downstream(ngx_http_request_t *r); +static void ngx_http_upstream_upgraded_write_downstream(ngx_http_request_t *r); +static void ngx_http_upstream_upgraded_read_upstream(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void ngx_http_upstream_upgraded_write_upstream(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void ngx_http_upstream_process_upgraded(ngx_http_request_t *r, + ngx_uint_t from_upstream, ngx_uint_t do_write); +static void + ngx_http_upstream_process_non_buffered_downstream(ngx_http_request_t *r); +static void + ngx_http_upstream_process_non_buffered_upstream(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void + ngx_http_upstream_process_non_buffered_request(ngx_http_request_t *r, + ngx_uint_t do_write); +static ngx_int_t ngx_http_upstream_non_buffered_filter_init(void *data); +static ngx_int_t ngx_http_upstream_non_buffered_filter(void *data, + ssize_t bytes); +#if (NGX_THREADS) +static ngx_int_t ngx_http_upstream_thread_handler(ngx_thread_task_t *task, + ngx_file_t *file); +static void ngx_http_upstream_thread_event_handler(ngx_event_t *ev); +#endif +static ngx_int_t ngx_http_upstream_output_filter(void *data, + ngx_chain_t *chain); +static void ngx_http_upstream_process_downstream(ngx_http_request_t *r); +static void ngx_http_upstream_process_upstream(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void ngx_http_upstream_process_request(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void ngx_http_upstream_store(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void ngx_http_upstream_dummy_handler(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void ngx_http_upstream_next(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_uint_t ft_type); +static void ngx_http_upstream_cleanup(void *data); +static void ngx_http_upstream_finalize_request(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_int_t rc); + +static ngx_int_t ngx_http_upstream_process_header_line(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_process_content_length(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_process_last_modified(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_process_set_cookie(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t + ngx_http_upstream_process_cache_control(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_ignore_header_line(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_process_expires(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_process_accel_expires(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_process_limit_rate(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_process_buffering(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_process_charset(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_process_connection(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t + ngx_http_upstream_process_transfer_encoding(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_process_vary(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_copy_header_line(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t + ngx_http_upstream_copy_multi_header_lines(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_copy_content_type(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_copy_last_modified(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_rewrite_location(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_rewrite_refresh(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_rewrite_set_cookie(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_copy_allow_ranges(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); + +#if (NGX_HTTP_GZIP) +static ngx_int_t ngx_http_upstream_copy_content_encoding(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); +#endif + +static ngx_int_t ngx_http_upstream_add_variables(ngx_conf_t *cf); +static ngx_int_t ngx_http_upstream_addr_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_upstream_status_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_upstream_response_time_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_upstream_response_length_variable( + ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_upstream_header_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_upstream_cookie_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + +static char *ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy); +static char *ngx_http_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +static ngx_int_t ngx_http_upstream_set_local(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_http_upstream_local_t *local); + +static void *ngx_http_upstream_create_main_conf(ngx_conf_t *cf); +static char *ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf); + +#if (NGX_HTTP_SSL) +static void ngx_http_upstream_ssl_init_connection(ngx_http_request_t *, + ngx_http_upstream_t *u, ngx_connection_t *c); +static void ngx_http_upstream_ssl_handshake(ngx_connection_t *c); +static ngx_int_t ngx_http_upstream_ssl_name(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_connection_t *c); +#endif + + +static ngx_http_upstream_header_t ngx_http_upstream_headers_in[] = { + + { ngx_string("Status"), + ngx_http_upstream_process_header_line, + offsetof(ngx_http_upstream_headers_in_t, status), + ngx_http_upstream_copy_header_line, 0, 0 }, + + { ngx_string("Content-Type"), + ngx_http_upstream_process_header_line, + offsetof(ngx_http_upstream_headers_in_t, content_type), + ngx_http_upstream_copy_content_type, 0, 1 }, + + { ngx_string("Content-Length"), + ngx_http_upstream_process_content_length, 0, + ngx_http_upstream_ignore_header_line, 0, 0 }, + + { ngx_string("Date"), + ngx_http_upstream_process_header_line, + offsetof(ngx_http_upstream_headers_in_t, date), + ngx_http_upstream_copy_header_line, + offsetof(ngx_http_headers_out_t, date), 0 }, + + { ngx_string("Last-Modified"), + ngx_http_upstream_process_last_modified, 0, + ngx_http_upstream_copy_last_modified, 0, 0 }, + + { ngx_string("ETag"), + ngx_http_upstream_process_header_line, + offsetof(ngx_http_upstream_headers_in_t, etag), + ngx_http_upstream_copy_header_line, + offsetof(ngx_http_headers_out_t, etag), 0 }, + + { ngx_string("Server"), + ngx_http_upstream_process_header_line, + offsetof(ngx_http_upstream_headers_in_t, server), + ngx_http_upstream_copy_header_line, + offsetof(ngx_http_headers_out_t, server), 0 }, + + { ngx_string("WWW-Authenticate"), + ngx_http_upstream_process_header_line, + offsetof(ngx_http_upstream_headers_in_t, www_authenticate), + ngx_http_upstream_copy_header_line, 0, 0 }, + + { ngx_string("Location"), + ngx_http_upstream_process_header_line, + offsetof(ngx_http_upstream_headers_in_t, location), + ngx_http_upstream_rewrite_location, 0, 0 }, + + { ngx_string("Refresh"), + ngx_http_upstream_ignore_header_line, 0, + ngx_http_upstream_rewrite_refresh, 0, 0 }, + + { ngx_string("Set-Cookie"), + ngx_http_upstream_process_set_cookie, + offsetof(ngx_http_upstream_headers_in_t, cookies), + ngx_http_upstream_rewrite_set_cookie, 0, 1 }, + + { ngx_string("Content-Disposition"), + ngx_http_upstream_ignore_header_line, 0, + ngx_http_upstream_copy_header_line, 0, 1 }, + + { ngx_string("Cache-Control"), + ngx_http_upstream_process_cache_control, 0, + ngx_http_upstream_copy_multi_header_lines, + offsetof(ngx_http_headers_out_t, cache_control), 1 }, + + { ngx_string("Expires"), + ngx_http_upstream_process_expires, 0, + ngx_http_upstream_copy_header_line, + offsetof(ngx_http_headers_out_t, expires), 1 }, + + { ngx_string("Accept-Ranges"), + ngx_http_upstream_process_header_line, + offsetof(ngx_http_upstream_headers_in_t, accept_ranges), + ngx_http_upstream_copy_allow_ranges, + offsetof(ngx_http_headers_out_t, accept_ranges), 1 }, + + { ngx_string("Content-Range"), + ngx_http_upstream_ignore_header_line, 0, + ngx_http_upstream_copy_header_line, + offsetof(ngx_http_headers_out_t, content_range), 0 }, + + { ngx_string("Connection"), + ngx_http_upstream_process_connection, 0, + ngx_http_upstream_ignore_header_line, 0, 0 }, + + { ngx_string("Keep-Alive"), + ngx_http_upstream_ignore_header_line, 0, + ngx_http_upstream_ignore_header_line, 0, 0 }, + + { ngx_string("Vary"), + ngx_http_upstream_process_vary, 0, + ngx_http_upstream_copy_header_line, 0, 0 }, + + { ngx_string("X-Powered-By"), + ngx_http_upstream_ignore_header_line, 0, + ngx_http_upstream_copy_header_line, 0, 0 }, + + { ngx_string("X-Accel-Expires"), + ngx_http_upstream_process_accel_expires, 0, + ngx_http_upstream_copy_header_line, 0, 0 }, + + { ngx_string("X-Accel-Redirect"), + ngx_http_upstream_process_header_line, + offsetof(ngx_http_upstream_headers_in_t, x_accel_redirect), + ngx_http_upstream_copy_header_line, 0, 0 }, + + { ngx_string("X-Accel-Limit-Rate"), + ngx_http_upstream_process_limit_rate, 0, + ngx_http_upstream_copy_header_line, 0, 0 }, + + { ngx_string("X-Accel-Buffering"), + ngx_http_upstream_process_buffering, 0, + ngx_http_upstream_copy_header_line, 0, 0 }, + + { ngx_string("X-Accel-Charset"), + ngx_http_upstream_process_charset, 0, + ngx_http_upstream_copy_header_line, 0, 0 }, + + { ngx_string("Transfer-Encoding"), + ngx_http_upstream_process_transfer_encoding, 0, + ngx_http_upstream_ignore_header_line, 0, 0 }, + +#if (NGX_HTTP_GZIP) + { ngx_string("Content-Encoding"), + ngx_http_upstream_process_header_line, + offsetof(ngx_http_upstream_headers_in_t, content_encoding), + ngx_http_upstream_copy_content_encoding, 0, 0 }, +#endif + + { ngx_null_string, NULL, 0, NULL, 0, 0 } +}; + + +static ngx_command_t ngx_http_upstream_commands[] = { + + { ngx_string("upstream"), + NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1, + ngx_http_upstream, + 0, + 0, + NULL }, + + { ngx_string("server"), + NGX_HTTP_UPS_CONF|NGX_CONF_1MORE, + ngx_http_upstream_server, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_upstream_module_ctx = { + ngx_http_upstream_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + ngx_http_upstream_create_main_conf, /* create main configuration */ + ngx_http_upstream_init_main_conf, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_upstream_module = { + NGX_MODULE_V1, + &ngx_http_upstream_module_ctx, /* module context */ + ngx_http_upstream_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_variable_t ngx_http_upstream_vars[] = { + + { ngx_string("upstream_addr"), NULL, + ngx_http_upstream_addr_variable, 0, + NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("upstream_status"), NULL, + ngx_http_upstream_status_variable, 0, + NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("upstream_connect_time"), NULL, + ngx_http_upstream_response_time_variable, 2, + NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("upstream_header_time"), NULL, + ngx_http_upstream_response_time_variable, 1, + NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("upstream_response_time"), NULL, + ngx_http_upstream_response_time_variable, 0, + NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("upstream_response_length"), NULL, + ngx_http_upstream_response_length_variable, 0, + NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("upstream_bytes_received"), NULL, + ngx_http_upstream_response_length_variable, 1, + NGX_HTTP_VAR_NOCACHEABLE, 0 }, + +#if (NGX_HTTP_CACHE) + + { ngx_string("upstream_cache_status"), NULL, + ngx_http_upstream_cache_status, 0, + NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("upstream_cache_last_modified"), NULL, + ngx_http_upstream_cache_last_modified, 0, + NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, + + { ngx_string("upstream_cache_etag"), NULL, + ngx_http_upstream_cache_etag, 0, + NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, + +#endif + + { ngx_string("upstream_http_"), NULL, ngx_http_upstream_header_variable, + 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_PREFIX, 0 }, + + { ngx_string("upstream_cookie_"), NULL, ngx_http_upstream_cookie_variable, + 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_PREFIX, 0 }, + + { ngx_null_string, NULL, NULL, 0, 0, 0 } +}; + + +static ngx_http_upstream_next_t ngx_http_upstream_next_errors[] = { + { 500, NGX_HTTP_UPSTREAM_FT_HTTP_500 }, + { 502, NGX_HTTP_UPSTREAM_FT_HTTP_502 }, + { 503, NGX_HTTP_UPSTREAM_FT_HTTP_503 }, + { 504, NGX_HTTP_UPSTREAM_FT_HTTP_504 }, + { 403, NGX_HTTP_UPSTREAM_FT_HTTP_403 }, + { 404, NGX_HTTP_UPSTREAM_FT_HTTP_404 }, + { 429, NGX_HTTP_UPSTREAM_FT_HTTP_429 }, + { 0, 0 } +}; + + +ngx_conf_bitmask_t ngx_http_upstream_cache_method_mask[] = { + { ngx_string("GET"), NGX_HTTP_GET }, + { ngx_string("HEAD"), NGX_HTTP_HEAD }, + { ngx_string("POST"), NGX_HTTP_POST }, + { ngx_null_string, 0 } +}; + + +ngx_conf_bitmask_t ngx_http_upstream_ignore_headers_masks[] = { + { ngx_string("X-Accel-Redirect"), NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT }, + { ngx_string("X-Accel-Expires"), NGX_HTTP_UPSTREAM_IGN_XA_EXPIRES }, + { ngx_string("X-Accel-Limit-Rate"), NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE }, + { ngx_string("X-Accel-Buffering"), NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING }, + { ngx_string("X-Accel-Charset"), NGX_HTTP_UPSTREAM_IGN_XA_CHARSET }, + { ngx_string("Expires"), NGX_HTTP_UPSTREAM_IGN_EXPIRES }, + { ngx_string("Cache-Control"), NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL }, + { ngx_string("Set-Cookie"), NGX_HTTP_UPSTREAM_IGN_SET_COOKIE }, + { ngx_string("Vary"), NGX_HTTP_UPSTREAM_IGN_VARY }, + { ngx_null_string, 0 } +}; + + +ngx_int_t +ngx_http_upstream_create(ngx_http_request_t *r) +{ + ngx_http_upstream_t *u; + + u = r->upstream; + + if (u && u->cleanup) { + r->main->count++; + ngx_http_upstream_cleanup(r); + } + + u = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_t)); + if (u == NULL) { + return NGX_ERROR; + } + + r->upstream = u; + + u->peer.log = r->connection->log; + u->peer.log_error = NGX_ERROR_ERR; + +#if (NGX_HTTP_CACHE) + r->cache = NULL; +#endif + + u->headers_in.content_length_n = -1; + u->headers_in.last_modified_time = -1; + + return NGX_OK; +} + + +void +ngx_http_upstream_init(ngx_http_request_t *r) +{ + ngx_connection_t *c; + + c = r->connection; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http init upstream, client timer: %d", c->read->timer_set); + +#if (NGX_HTTP_V2) + if (r->stream) { + ngx_http_upstream_init_request(r); + return; + } +#endif + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + if (ngx_event_flags & NGX_USE_CLEAR_EVENT) { + + if (!c->write->active) { + if (ngx_add_event(c->write, NGX_WRITE_EVENT, NGX_CLEAR_EVENT) + == NGX_ERROR) + { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + } + } + + ngx_http_upstream_init_request(r); +} + + +static void +ngx_http_upstream_init_request(ngx_http_request_t *r) +{ + ngx_str_t *host; + ngx_uint_t i; + ngx_resolver_ctx_t *ctx, temp; + ngx_http_cleanup_t *cln; + ngx_http_upstream_t *u; + ngx_http_core_loc_conf_t *clcf; + ngx_http_upstream_srv_conf_t *uscf, **uscfp; + ngx_http_upstream_main_conf_t *umcf; + + if (r->aio) { + return; + } + + u = r->upstream; + +#if (NGX_HTTP_CACHE) + + if (u->conf->cache) { + ngx_int_t rc; + + rc = ngx_http_upstream_cache(r, u); + + if (rc == NGX_BUSY) { + r->write_event_handler = ngx_http_upstream_init_request; + return; + } + + r->write_event_handler = ngx_http_request_empty_handler; + + if (rc == NGX_ERROR) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + if (rc == NGX_OK) { + rc = ngx_http_upstream_cache_send(r, u); + + if (rc == NGX_DONE) { + return; + } + + if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) { + rc = NGX_DECLINED; + r->cached = 0; + } + + if (ngx_http_upstream_cache_background_update(r, u) != NGX_OK) { + rc = NGX_ERROR; + } + } + + if (rc != NGX_DECLINED) { + ngx_http_finalize_request(r, rc); + return; + } + } + +#endif + + u->store = u->conf->store; + + if (!u->store && !r->post_action && !u->conf->ignore_client_abort) { + r->read_event_handler = ngx_http_upstream_rd_check_broken_connection; + r->write_event_handler = ngx_http_upstream_wr_check_broken_connection; + } + + if (r->request_body) { + u->request_bufs = r->request_body->bufs; + } + + if (u->create_request(r) != NGX_OK) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + if (ngx_http_upstream_set_local(r, u, u->conf->local) != NGX_OK) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + u->output.alignment = clcf->directio_alignment; + u->output.pool = r->pool; + u->output.bufs.num = 1; + u->output.bufs.size = clcf->client_body_buffer_size; + + if (u->output.output_filter == NULL) { + u->output.output_filter = ngx_chain_writer; + u->output.filter_ctx = &u->writer; + } + + u->writer.pool = r->pool; + + if (r->upstream_states == NULL) { + + r->upstream_states = ngx_array_create(r->pool, 1, + sizeof(ngx_http_upstream_state_t)); + if (r->upstream_states == NULL) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + } else { + + u->state = ngx_array_push(r->upstream_states); + if (u->state == NULL) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + ngx_memzero(u->state, sizeof(ngx_http_upstream_state_t)); + } + + cln = ngx_http_cleanup_add(r, 0); + if (cln == NULL) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + cln->handler = ngx_http_upstream_cleanup; + cln->data = r; + u->cleanup = &cln->handler; + + if (u->resolved == NULL) { + + uscf = u->conf->upstream; + + } else { + +#if (NGX_HTTP_SSL) + u->ssl_name = u->resolved->host; +#endif + + host = &u->resolved->host; + + umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); + + uscfp = umcf->upstreams.elts; + + for (i = 0; i < umcf->upstreams.nelts; i++) { + + uscf = uscfp[i]; + + if (uscf->host.len == host->len + && ((uscf->port == 0 && u->resolved->no_port) + || uscf->port == u->resolved->port) + && ngx_strncasecmp(uscf->host.data, host->data, host->len) == 0) + { + goto found; + } + } + + if (u->resolved->sockaddr) { + + if (u->resolved->port == 0 + && u->resolved->sockaddr->sa_family != AF_UNIX) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "no port in upstream \"%V\"", host); + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + if (ngx_http_upstream_create_round_robin_peer(r, u->resolved) + != NGX_OK) + { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + ngx_http_upstream_connect(r, u); + + return; + } + + if (u->resolved->port == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "no port in upstream \"%V\"", host); + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + temp.name = *host; + + ctx = ngx_resolve_start(clcf->resolver, &temp); + if (ctx == NULL) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + if (ctx == NGX_NO_RESOLVER) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "no resolver defined to resolve %V", host); + + ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY); + return; + } + + ctx->name = *host; + ctx->handler = ngx_http_upstream_resolve_handler; + ctx->data = r; + ctx->timeout = clcf->resolver_timeout; + + u->resolved->ctx = ctx; + + if (ngx_resolve_name(ctx) != NGX_OK) { + u->resolved->ctx = NULL; + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + return; + } + +found: + + if (uscf == NULL) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "no upstream configuration"); + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + u->upstream = uscf; + +#if (NGX_HTTP_SSL) + u->ssl_name = uscf->host; +#endif + + if (uscf->peer.init(r, uscf) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + u->peer.start_time = ngx_current_msec; + + if (u->conf->next_upstream_tries + && u->peer.tries > u->conf->next_upstream_tries) + { + u->peer.tries = u->conf->next_upstream_tries; + } + + ngx_http_upstream_connect(r, u); +} + + +#if (NGX_HTTP_CACHE) + +static ngx_int_t +ngx_http_upstream_cache(ngx_http_request_t *r, ngx_http_upstream_t *u) +{ + ngx_int_t rc; + ngx_http_cache_t *c; + ngx_http_file_cache_t *cache; + + c = r->cache; + + if (c == NULL) { + + if (!(r->method & u->conf->cache_methods)) { + return NGX_DECLINED; + } + + rc = ngx_http_upstream_cache_get(r, u, &cache); + + if (rc != NGX_OK) { + return rc; + } + + if (r->method == NGX_HTTP_HEAD && u->conf->cache_convert_head) { + u->method = ngx_http_core_get_method; + } + + if (ngx_http_file_cache_new(r) != NGX_OK) { + return NGX_ERROR; + } + + if (u->create_key(r) != NGX_OK) { + return NGX_ERROR; + } + + /* TODO: add keys */ + + ngx_http_file_cache_create_key(r); + + if (r->cache->header_start + 256 >= u->conf->buffer_size) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "%V_buffer_size %uz is not enough for cache key, " + "it should be increased to at least %uz", + &u->conf->module, u->conf->buffer_size, + ngx_align(r->cache->header_start + 256, 1024)); + + r->cache = NULL; + return NGX_DECLINED; + } + + u->cacheable = 1; + + c = r->cache; + + c->body_start = u->conf->buffer_size; + c->min_uses = u->conf->cache_min_uses; + c->file_cache = cache; + + switch (ngx_http_test_predicates(r, u->conf->cache_bypass)) { + + case NGX_ERROR: + return NGX_ERROR; + + case NGX_DECLINED: + u->cache_status = NGX_HTTP_CACHE_BYPASS; + return NGX_DECLINED; + + default: /* NGX_OK */ + break; + } + + c->lock = u->conf->cache_lock; + c->lock_timeout = u->conf->cache_lock_timeout; + c->lock_age = u->conf->cache_lock_age; + + u->cache_status = NGX_HTTP_CACHE_MISS; + } + + rc = ngx_http_file_cache_open(r); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http upstream cache: %i", rc); + + switch (rc) { + + case NGX_HTTP_CACHE_STALE: + + if (((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING) + || c->stale_updating) && !r->cache_updater + && u->conf->cache_background_update) + { + r->cache->background = 1; + u->cache_status = rc; + rc = NGX_OK; + } + + break; + + case NGX_HTTP_CACHE_UPDATING: + + if (((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING) + || c->stale_updating) && !r->cache_updater) + { + u->cache_status = rc; + rc = NGX_OK; + + } else { + rc = NGX_HTTP_CACHE_STALE; + } + + break; + + case NGX_OK: + u->cache_status = NGX_HTTP_CACHE_HIT; + } + + switch (rc) { + + case NGX_OK: + + return NGX_OK; + + case NGX_HTTP_CACHE_STALE: + + c->valid_sec = 0; + c->updating_sec = 0; + c->error_sec = 0; + + u->buffer.start = NULL; + u->cache_status = NGX_HTTP_CACHE_EXPIRED; + + break; + + case NGX_DECLINED: + + if ((size_t) (u->buffer.end - u->buffer.start) < u->conf->buffer_size) { + u->buffer.start = NULL; + + } else { + u->buffer.pos = u->buffer.start + c->header_start; + u->buffer.last = u->buffer.pos; + } + + break; + + case NGX_HTTP_CACHE_SCARCE: + + u->cacheable = 0; + + break; + + case NGX_AGAIN: + + return NGX_BUSY; + + case NGX_ERROR: + + return NGX_ERROR; + + default: + + /* cached NGX_HTTP_BAD_GATEWAY, NGX_HTTP_GATEWAY_TIME_OUT, etc. */ + + u->cache_status = NGX_HTTP_CACHE_HIT; + + return rc; + } + + if (ngx_http_upstream_cache_check_range(r, u) == NGX_DECLINED) { + u->cacheable = 0; + } + + r->cached = 0; + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_http_upstream_cache_get(ngx_http_request_t *r, ngx_http_upstream_t *u, + ngx_http_file_cache_t **cache) +{ + ngx_str_t *name, val; + ngx_uint_t i; + ngx_http_file_cache_t **caches; + + if (u->conf->cache_zone) { + *cache = u->conf->cache_zone->data; + return NGX_OK; + } + + if (ngx_http_complex_value(r, u->conf->cache_value, &val) != NGX_OK) { + return NGX_ERROR; + } + + if (val.len == 0 + || (val.len == 3 && ngx_strncmp(val.data, "off", 3) == 0)) + { + return NGX_DECLINED; + } + + caches = u->caches->elts; + + for (i = 0; i < u->caches->nelts; i++) { + name = &caches[i]->shm_zone->shm.name; + + if (name->len == val.len + && ngx_strncmp(name->data, val.data, val.len) == 0) + { + *cache = caches[i]; + return NGX_OK; + } + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "cache \"%V\" not found", &val); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_upstream_cache_send(ngx_http_request_t *r, ngx_http_upstream_t *u) +{ + ngx_int_t rc; + ngx_http_cache_t *c; + + r->cached = 1; + c = r->cache; + + if (c->header_start == c->body_start) { + r->http_version = NGX_HTTP_VERSION_9; + return ngx_http_cache_send(r); + } + + /* TODO: cache stack */ + + u->buffer = *c->buf; + u->buffer.pos += c->header_start; + + ngx_memzero(&u->headers_in, sizeof(ngx_http_upstream_headers_in_t)); + u->headers_in.content_length_n = -1; + u->headers_in.last_modified_time = -1; + + if (ngx_list_init(&u->headers_in.headers, r->pool, 8, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + rc = u->process_header(r); + + if (rc == NGX_OK) { + + if (ngx_http_upstream_process_headers(r, u) != NGX_OK) { + return NGX_DONE; + } + + return ngx_http_cache_send(r); + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + /* rc == NGX_HTTP_UPSTREAM_INVALID_HEADER */ + + /* TODO: delete file */ + + return rc; +} + + +static ngx_int_t +ngx_http_upstream_cache_background_update(ngx_http_request_t *r, + ngx_http_upstream_t *u) +{ + ngx_http_request_t *sr; + + if (!r->cached || !r->cache->background) { + return NGX_OK; + } + + if (ngx_http_subrequest(r, &r->uri, &r->args, &sr, NULL, + NGX_HTTP_SUBREQUEST_CLONE) + != NGX_OK) + { + return NGX_ERROR; + } + + sr->header_only = 1; + sr->cache_updater = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_cache_check_range(ngx_http_request_t *r, + ngx_http_upstream_t *u) +{ + off_t offset; + u_char *p, *start; + ngx_table_elt_t *h; + + h = r->headers_in.range; + + if (h == NULL + || !u->cacheable + || u->conf->cache_max_range_offset == NGX_MAX_OFF_T_VALUE) + { + return NGX_OK; + } + + if (u->conf->cache_max_range_offset == 0) { + return NGX_DECLINED; + } + + if (h->value.len < 7 + || ngx_strncasecmp(h->value.data, (u_char *) "bytes=", 6) != 0) + { + return NGX_OK; + } + + p = h->value.data + 6; + + while (*p == ' ') { p++; } + + if (*p == '-') { + return NGX_DECLINED; + } + + start = p; + + while (*p >= '0' && *p <= '9') { p++; } + + offset = ngx_atoof(start, p - start); + + if (offset >= u->conf->cache_max_range_offset) { + return NGX_DECLINED; + } + + return NGX_OK; +} + +#endif + + +static void +ngx_http_upstream_resolve_handler(ngx_resolver_ctx_t *ctx) +{ + ngx_connection_t *c; + ngx_http_request_t *r; + ngx_http_upstream_t *u; + ngx_http_upstream_resolved_t *ur; + + r = ctx->data; + c = r->connection; + + u = r->upstream; + ur = u->resolved; + + ngx_http_set_log_request(c->log, r); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream resolve: \"%V?%V\"", &r->uri, &r->args); + + if (ctx->state) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "%V could not be resolved (%i: %s)", + &ctx->name, ctx->state, + ngx_resolver_strerror(ctx->state)); + + ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY); + goto failed; + } + + ur->naddrs = ctx->naddrs; + ur->addrs = ctx->addrs; + +#if (NGX_DEBUG) + { + u_char text[NGX_SOCKADDR_STRLEN]; + ngx_str_t addr; + ngx_uint_t i; + + addr.data = text; + + for (i = 0; i < ctx->naddrs; i++) { + addr.len = ngx_sock_ntop(ur->addrs[i].sockaddr, ur->addrs[i].socklen, + text, NGX_SOCKADDR_STRLEN, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "name was resolved to %V", &addr); + } + } +#endif + + if (ngx_http_upstream_create_round_robin_peer(r, ur) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + goto failed; + } + + ngx_resolve_name_done(ctx); + ur->ctx = NULL; + + u->peer.start_time = ngx_current_msec; + + if (u->conf->next_upstream_tries + && u->peer.tries > u->conf->next_upstream_tries) + { + u->peer.tries = u->conf->next_upstream_tries; + } + + ngx_http_upstream_connect(r, u); + +failed: + + ngx_http_run_posted_requests(c); +} + + +static void +ngx_http_upstream_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_http_request_t *r; + ngx_http_upstream_t *u; + + c = ev->data; + r = c->data; + + u = r->upstream; + c = r->connection; + + ngx_http_set_log_request(c->log, r); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream request: \"%V?%V\"", &r->uri, &r->args); + + if (ev->delayed && ev->timedout) { + ev->delayed = 0; + ev->timedout = 0; + } + + if (ev->write) { + u->write_event_handler(r, u); + + } else { + u->read_event_handler(r, u); + } + + ngx_http_run_posted_requests(c); +} + + +static void +ngx_http_upstream_rd_check_broken_connection(ngx_http_request_t *r) +{ + ngx_http_upstream_check_broken_connection(r, r->connection->read); +} + + +static void +ngx_http_upstream_wr_check_broken_connection(ngx_http_request_t *r) +{ + ngx_http_upstream_check_broken_connection(r, r->connection->write); +} + + +static void +ngx_http_upstream_check_broken_connection(ngx_http_request_t *r, + ngx_event_t *ev) +{ + int n; + char buf[1]; + ngx_err_t err; + ngx_int_t event; + ngx_connection_t *c; + ngx_http_upstream_t *u; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ev->log, 0, + "http upstream check client, write event:%d, \"%V\"", + ev->write, &r->uri); + + c = r->connection; + u = r->upstream; + + if (c->error) { + if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && ev->active) { + + event = ev->write ? NGX_WRITE_EVENT : NGX_READ_EVENT; + + if (ngx_del_event(ev, event, 0) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + } + + if (!u->cacheable) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_CLIENT_CLOSED_REQUEST); + } + + return; + } + +#if (NGX_HTTP_V2) + if (r->stream) { + return; + } +#endif + +#if (NGX_HAVE_KQUEUE) + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { + + if (!ev->pending_eof) { + return; + } + + ev->eof = 1; + c->error = 1; + + if (ev->kq_errno) { + ev->error = 1; + } + + if (!u->cacheable && u->peer.connection) { + ngx_log_error(NGX_LOG_INFO, ev->log, ev->kq_errno, + "kevent() reported that client prematurely closed " + "connection, so upstream connection is closed too"); + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + ngx_log_error(NGX_LOG_INFO, ev->log, ev->kq_errno, + "kevent() reported that client prematurely closed " + "connection"); + + if (u->peer.connection == NULL) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_CLIENT_CLOSED_REQUEST); + } + + return; + } + +#endif + +#if (NGX_HAVE_EPOLLRDHUP) + + if ((ngx_event_flags & NGX_USE_EPOLL_EVENT) && ngx_use_epoll_rdhup) { + socklen_t len; + + if (!ev->pending_eof) { + return; + } + + ev->eof = 1; + c->error = 1; + + err = 0; + len = sizeof(ngx_err_t); + + /* + * BSDs and Linux return 0 and set a pending error in err + * Solaris returns -1 and sets errno + */ + + if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len) + == -1) + { + err = ngx_socket_errno; + } + + if (err) { + ev->error = 1; + } + + if (!u->cacheable && u->peer.connection) { + ngx_log_error(NGX_LOG_INFO, ev->log, err, + "epoll_wait() reported that client prematurely closed " + "connection, so upstream connection is closed too"); + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + ngx_log_error(NGX_LOG_INFO, ev->log, err, + "epoll_wait() reported that client prematurely closed " + "connection"); + + if (u->peer.connection == NULL) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_CLIENT_CLOSED_REQUEST); + } + + return; + } + +#endif + + n = recv(c->fd, buf, 1, MSG_PEEK); + + err = ngx_socket_errno; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ev->log, err, + "http upstream recv(): %d", n); + + if (ev->write && (n >= 0 || err == NGX_EAGAIN)) { + return; + } + + if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && ev->active) { + + event = ev->write ? NGX_WRITE_EVENT : NGX_READ_EVENT; + + if (ngx_del_event(ev, event, 0) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + } + + if (n > 0) { + return; + } + + if (n == -1) { + if (err == NGX_EAGAIN) { + return; + } + + ev->error = 1; + + } else { /* n == 0 */ + err = 0; + } + + ev->eof = 1; + c->error = 1; + + if (!u->cacheable && u->peer.connection) { + ngx_log_error(NGX_LOG_INFO, ev->log, err, + "client prematurely closed connection, " + "so upstream connection is closed too"); + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + ngx_log_error(NGX_LOG_INFO, ev->log, err, + "client prematurely closed connection"); + + if (u->peer.connection == NULL) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_CLIENT_CLOSED_REQUEST); + } +} + + +static void +ngx_http_upstream_connect(ngx_http_request_t *r, ngx_http_upstream_t *u) +{ + ngx_int_t rc; + ngx_connection_t *c; + + r->connection->log->action = "connecting to upstream"; + + if (u->state && u->state->response_time) { + u->state->response_time = ngx_current_msec - u->state->response_time; + } + + u->state = ngx_array_push(r->upstream_states); + if (u->state == NULL) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + ngx_memzero(u->state, sizeof(ngx_http_upstream_state_t)); + + u->state->response_time = ngx_current_msec; + u->state->connect_time = (ngx_msec_t) -1; + u->state->header_time = (ngx_msec_t) -1; + + rc = ngx_event_connect_peer(&u->peer); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http upstream connect: %i", rc); + + if (rc == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + u->state->peer = u->peer.name; + + if (rc == NGX_BUSY) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no live upstreams"); + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_NOLIVE); + return; + } + + if (rc == NGX_DECLINED) { + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); + return; + } + + /* rc == NGX_OK || rc == NGX_AGAIN || rc == NGX_DONE */ + + c = u->peer.connection; + + c->data = r; + + c->write->handler = ngx_http_upstream_handler; + c->read->handler = ngx_http_upstream_handler; + + u->write_event_handler = ngx_http_upstream_send_request_handler; + u->read_event_handler = ngx_http_upstream_process_header; + + c->sendfile &= r->connection->sendfile; + u->output.sendfile = c->sendfile; + + if (c->pool == NULL) { + + /* we need separate pool here to be able to cache SSL connections */ + + c->pool = ngx_create_pool(128, r->connection->log); + if (c->pool == NULL) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + } + + c->log = r->connection->log; + c->pool->log = c->log; + c->read->log = c->log; + c->write->log = c->log; + + /* init or reinit the ngx_output_chain() and ngx_chain_writer() contexts */ + + u->writer.out = NULL; + u->writer.last = &u->writer.out; + u->writer.connection = c; + u->writer.limit = 0; + + if (u->request_sent) { + if (ngx_http_upstream_reinit(r, u) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + } + + if (r->request_body + && r->request_body->buf + && r->request_body->temp_file + && r == r->main) + { + /* + * the r->request_body->buf can be reused for one request only, + * the subrequests should allocate their own temporary bufs + */ + + u->output.free = ngx_alloc_chain_link(r->pool); + if (u->output.free == NULL) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + u->output.free->buf = r->request_body->buf; + u->output.free->next = NULL; + u->output.allocated = 1; + + r->request_body->buf->pos = r->request_body->buf->start; + r->request_body->buf->last = r->request_body->buf->start; + r->request_body->buf->tag = u->output.tag; + } + + u->request_sent = 0; + u->request_body_sent = 0; + + if (rc == NGX_AGAIN) { + ngx_add_timer(c->write, u->conf->connect_timeout); + return; + } + +#if (NGX_HTTP_SSL) + + if (u->ssl && c->ssl == NULL) { + ngx_http_upstream_ssl_init_connection(r, u, c); + return; + } + +#endif + + ngx_http_upstream_send_request(r, u, 1); +} + + +#if (NGX_HTTP_SSL) + +static void +ngx_http_upstream_ssl_init_connection(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_connection_t *c) +{ + int tcp_nodelay; + ngx_int_t rc; + ngx_http_core_loc_conf_t *clcf; + + if (ngx_http_upstream_test_connect(c) != NGX_OK) { + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); + return; + } + + if (ngx_ssl_create_connection(u->conf->ssl, c, + NGX_SSL_BUFFER|NGX_SSL_CLIENT) + != NGX_OK) + { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + c->sendfile = 0; + u->output.sendfile = 0; + + if (u->conf->ssl_server_name || u->conf->ssl_verify) { + if (ngx_http_upstream_ssl_name(r, u, c) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + } + + if (u->conf->ssl_session_reuse) { + if (u->peer.set_session(&u->peer, u->peer.data) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + /* abbreviated SSL handshake may interact badly with Nagle */ + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->tcp_nodelay && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); + + tcp_nodelay = 1; + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) == -1) + { + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + } + + r->connection->log->action = "SSL handshaking to upstream"; + + rc = ngx_ssl_handshake(c); + + if (rc == NGX_AGAIN) { + + if (!c->write->timer_set) { + ngx_add_timer(c->write, u->conf->connect_timeout); + } + + c->ssl->handler = ngx_http_upstream_ssl_handshake; + return; + } + + ngx_http_upstream_ssl_handshake(c); +} + + +static void +ngx_http_upstream_ssl_handshake(ngx_connection_t *c) +{ + long rc; + ngx_http_request_t *r; + ngx_http_upstream_t *u; + + r = c->data; + u = r->upstream; + + ngx_http_set_log_request(c->log, r); + + if (c->ssl->handshaked) { + + if (u->conf->ssl_verify) { + rc = SSL_get_verify_result(c->ssl->connection); + + if (rc != X509_V_OK) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "upstream SSL certificate verify error: (%l:%s)", + rc, X509_verify_cert_error_string(rc)); + goto failed; + } + + if (ngx_ssl_check_host(c, &u->ssl_name) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "upstream SSL certificate does not match \"%V\"", + &u->ssl_name); + goto failed; + } + } + + if (u->conf->ssl_session_reuse) { + u->peer.save_session(&u->peer, u->peer.data); + } + + c->write->handler = ngx_http_upstream_handler; + c->read->handler = ngx_http_upstream_handler; + + c = r->connection; + + ngx_http_upstream_send_request(r, u, 1); + + ngx_http_run_posted_requests(c); + return; + } + + if (c->write->timedout) { + c = r->connection; + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT); + ngx_http_run_posted_requests(c); + return; + } + +failed: + + c = r->connection; + + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); + + ngx_http_run_posted_requests(c); +} + + +static ngx_int_t +ngx_http_upstream_ssl_name(ngx_http_request_t *r, ngx_http_upstream_t *u, + ngx_connection_t *c) +{ + u_char *p, *last; + ngx_str_t name; + + if (u->conf->ssl_name) { + if (ngx_http_complex_value(r, u->conf->ssl_name, &name) != NGX_OK) { + return NGX_ERROR; + } + + } else { + name = u->ssl_name; + } + + if (name.len == 0) { + goto done; + } + + /* + * ssl name here may contain port, notably if derived from $proxy_host + * or $http_host; we have to strip it + */ + + p = name.data; + last = name.data + name.len; + + if (*p == '[') { + p = ngx_strlchr(p, last, ']'); + + if (p == NULL) { + p = name.data; + } + } + + p = ngx_strlchr(p, last, ':'); + + if (p != NULL) { + name.len = p - name.data; + } + + if (!u->conf->ssl_server_name) { + goto done; + } + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + + /* as per RFC 6066, literal IPv4 and IPv6 addresses are not permitted */ + + if (name.len == 0 || *name.data == '[') { + goto done; + } + + if (ngx_inet_addr(name.data, name.len) != INADDR_NONE) { + goto done; + } + + /* + * SSL_set_tlsext_host_name() needs a null-terminated string, + * hence we explicitly null-terminate name here + */ + + p = ngx_pnalloc(r->pool, name.len + 1); + if (p == NULL) { + return NGX_ERROR; + } + + (void) ngx_cpystrn(p, name.data, name.len + 1); + + name.data = p; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "upstream SSL server name: \"%s\"", name.data); + + if (SSL_set_tlsext_host_name(c->ssl->connection, + (char *) name.data) + == 0) + { + ngx_ssl_error(NGX_LOG_ERR, r->connection->log, 0, + "SSL_set_tlsext_host_name(\"%s\") failed", name.data); + return NGX_ERROR; + } + +#endif + +done: + + u->ssl_name = name; + + return NGX_OK; +} + +#endif + + +static ngx_int_t +ngx_http_upstream_reinit(ngx_http_request_t *r, ngx_http_upstream_t *u) +{ + off_t file_pos; + ngx_chain_t *cl; + + if (u->reinit_request(r) != NGX_OK) { + return NGX_ERROR; + } + + u->keepalive = 0; + u->upgrade = 0; + + ngx_memzero(&u->headers_in, sizeof(ngx_http_upstream_headers_in_t)); + u->headers_in.content_length_n = -1; + u->headers_in.last_modified_time = -1; + + if (ngx_list_init(&u->headers_in.headers, r->pool, 8, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + /* reinit the request chain */ + + file_pos = 0; + + for (cl = u->request_bufs; cl; cl = cl->next) { + cl->buf->pos = cl->buf->start; + + /* there is at most one file */ + + if (cl->buf->in_file) { + cl->buf->file_pos = file_pos; + file_pos = cl->buf->file_last; + } + } + + /* reinit the subrequest's ngx_output_chain() context */ + + if (r->request_body && r->request_body->temp_file + && r != r->main && u->output.buf) + { + u->output.free = ngx_alloc_chain_link(r->pool); + if (u->output.free == NULL) { + return NGX_ERROR; + } + + u->output.free->buf = u->output.buf; + u->output.free->next = NULL; + + u->output.buf->pos = u->output.buf->start; + u->output.buf->last = u->output.buf->start; + } + + u->output.buf = NULL; + u->output.in = NULL; + u->output.busy = NULL; + + /* reinit u->buffer */ + + u->buffer.pos = u->buffer.start; + +#if (NGX_HTTP_CACHE) + + if (r->cache) { + u->buffer.pos += r->cache->header_start; + } + +#endif + + u->buffer.last = u->buffer.pos; + + return NGX_OK; +} + + +static void +ngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t *u, + ngx_uint_t do_write) +{ + ngx_int_t rc; + ngx_connection_t *c; + + c = u->peer.connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream send request"); + + if (u->state->connect_time == (ngx_msec_t) -1) { + u->state->connect_time = ngx_current_msec - u->state->response_time; + } + + if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) { + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); + return; + } + + c->log->action = "sending request to upstream"; + + rc = ngx_http_upstream_send_request_body(r, u, do_write); + + if (rc == NGX_ERROR) { + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); + return; + } + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + ngx_http_upstream_finalize_request(r, u, rc); + return; + } + + if (rc == NGX_AGAIN) { + if (!c->write->ready) { + ngx_add_timer(c->write, u->conf->send_timeout); + + } else if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + if (ngx_handle_write_event(c->write, u->conf->send_lowat) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + return; + } + + /* rc == NGX_OK */ + + u->request_body_sent = 1; + + if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) { + if (ngx_tcp_push(c->fd) == NGX_ERROR) { + ngx_log_error(NGX_LOG_CRIT, c->log, ngx_socket_errno, + ngx_tcp_push_n " failed"); + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + c->tcp_nopush = NGX_TCP_NOPUSH_UNSET; + } + + u->write_event_handler = ngx_http_upstream_dummy_handler; + + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + ngx_add_timer(c->read, u->conf->read_timeout); + + if (c->read->ready) { + ngx_http_upstream_process_header(r, u); + return; + } +} + + +static ngx_int_t +ngx_http_upstream_send_request_body(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_uint_t do_write) +{ + int tcp_nodelay; + ngx_int_t rc; + ngx_chain_t *out, *cl, *ln; + ngx_connection_t *c; + ngx_http_core_loc_conf_t *clcf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http upstream send request body"); + + if (!r->request_body_no_buffering) { + + /* buffered request body */ + + if (!u->request_sent) { + u->request_sent = 1; + out = u->request_bufs; + + } else { + out = NULL; + } + + return ngx_output_chain(&u->output, out); + } + + if (!u->request_sent) { + u->request_sent = 1; + out = u->request_bufs; + + if (r->request_body->bufs) { + for (cl = out; cl->next; cl = out->next) { /* void */ } + cl->next = r->request_body->bufs; + r->request_body->bufs = NULL; + } + + c = u->peer.connection; + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->tcp_nodelay && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); + + tcp_nodelay = 1; + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) == -1) + { + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + return NGX_ERROR; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + r->read_event_handler = ngx_http_upstream_read_request_handler; + + } else { + out = NULL; + } + + for ( ;; ) { + + if (do_write) { + rc = ngx_output_chain(&u->output, out); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + while (out) { + ln = out; + out = out->next; + ngx_free_chain(r->pool, ln); + } + + if (rc == NGX_OK && !r->reading_body) { + break; + } + } + + if (r->reading_body) { + /* read client request body */ + + rc = ngx_http_read_unbuffered_request_body(r); + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + return rc; + } + + out = r->request_body->bufs; + r->request_body->bufs = NULL; + } + + /* stop if there is nothing to send */ + + if (out == NULL) { + rc = NGX_AGAIN; + break; + } + + do_write = 1; + } + + if (!r->reading_body) { + if (!u->store && !r->post_action && !u->conf->ignore_client_abort) { + r->read_event_handler = + ngx_http_upstream_rd_check_broken_connection; + } + } + + return rc; +} + + +static void +ngx_http_upstream_send_request_handler(ngx_http_request_t *r, + ngx_http_upstream_t *u) +{ + ngx_connection_t *c; + + c = u->peer.connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http upstream send request handler"); + + if (c->write->timedout) { + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT); + return; + } + +#if (NGX_HTTP_SSL) + + if (u->ssl && c->ssl == NULL) { + ngx_http_upstream_ssl_init_connection(r, u, c); + return; + } + +#endif + + if (u->header_sent) { + u->write_event_handler = ngx_http_upstream_dummy_handler; + + (void) ngx_handle_write_event(c->write, 0); + + return; + } + + ngx_http_upstream_send_request(r, u, 1); +} + + +static void +ngx_http_upstream_read_request_handler(ngx_http_request_t *r) +{ + ngx_connection_t *c; + ngx_http_upstream_t *u; + + c = r->connection; + u = r->upstream; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http upstream read request handler"); + + if (c->read->timedout) { + c->timedout = 1; + ngx_http_upstream_finalize_request(r, u, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + ngx_http_upstream_send_request(r, u, 0); +} + + +static void +ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u) +{ + ssize_t n; + ngx_int_t rc; + ngx_connection_t *c; + + c = u->peer.connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream process header"); + + c->log->action = "reading response header from upstream"; + + if (c->read->timedout) { + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT); + return; + } + + if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) { + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); + return; + } + + if (u->buffer.start == NULL) { + u->buffer.start = ngx_palloc(r->pool, u->conf->buffer_size); + if (u->buffer.start == NULL) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + u->buffer.pos = u->buffer.start; + u->buffer.last = u->buffer.start; + u->buffer.end = u->buffer.start + u->conf->buffer_size; + u->buffer.temporary = 1; + + u->buffer.tag = u->output.tag; + + if (ngx_list_init(&u->headers_in.headers, r->pool, 8, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + +#if (NGX_HTTP_CACHE) + + if (r->cache) { + u->buffer.pos += r->cache->header_start; + u->buffer.last = u->buffer.pos; + } +#endif + } + + for ( ;; ) { + + n = c->recv(c, u->buffer.last, u->buffer.end - u->buffer.last); + + if (n == NGX_AGAIN) { +#if 0 + ngx_add_timer(rev, u->read_timeout); +#endif + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + return; + } + + if (n == 0) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "upstream prematurely closed connection"); + } + + if (n == NGX_ERROR || n == 0) { + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); + return; + } + + u->state->bytes_received += n; + + u->buffer.last += n; + +#if 0 + u->valid_header_in = 0; + + u->peer.cached = 0; +#endif + + rc = u->process_header(r); + + if (rc == NGX_AGAIN) { + + if (u->buffer.last == u->buffer.end) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "upstream sent too big header"); + + ngx_http_upstream_next(r, u, + NGX_HTTP_UPSTREAM_FT_INVALID_HEADER); + return; + } + + continue; + } + + break; + } + + if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) { + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER); + return; + } + + if (rc == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + /* rc == NGX_OK */ + + u->state->header_time = ngx_current_msec - u->state->response_time; + + if (u->headers_in.status_n >= NGX_HTTP_SPECIAL_RESPONSE) { + + if (ngx_http_upstream_test_next(r, u) == NGX_OK) { + return; + } + + if (ngx_http_upstream_intercept_errors(r, u) == NGX_OK) { + return; + } + } + + if (ngx_http_upstream_process_headers(r, u) != NGX_OK) { + return; + } + + if (!r->subrequest_in_memory) { + ngx_http_upstream_send_response(r, u); + return; + } + + /* subrequest content in memory */ + + if (u->input_filter == NULL) { + u->input_filter_init = ngx_http_upstream_non_buffered_filter_init; + u->input_filter = ngx_http_upstream_non_buffered_filter; + u->input_filter_ctx = r; + } + + if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + n = u->buffer.last - u->buffer.pos; + + if (n) { + u->buffer.last = u->buffer.pos; + + u->state->response_length += n; + + if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + } + + if (u->length == 0) { + ngx_http_upstream_finalize_request(r, u, 0); + return; + } + + u->read_event_handler = ngx_http_upstream_process_body_in_memory; + + ngx_http_upstream_process_body_in_memory(r, u); +} + + +static ngx_int_t +ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u) +{ + ngx_uint_t status; + ngx_http_upstream_next_t *un; + + status = u->headers_in.status_n; + + for (un = ngx_http_upstream_next_errors; un->status; un++) { + + if (status != un->status) { + continue; + } + + if (u->peer.tries > 1 && (u->conf->next_upstream & un->mask)) { + ngx_http_upstream_next(r, u, un->mask); + return NGX_OK; + } + +#if (NGX_HTTP_CACHE) + + if (u->cache_status == NGX_HTTP_CACHE_EXPIRED + && ((u->conf->cache_use_stale & un->mask) || r->cache->stale_error)) + { + ngx_int_t rc; + + rc = u->reinit_request(r); + + if (rc == NGX_OK) { + u->cache_status = NGX_HTTP_CACHE_STALE; + rc = ngx_http_upstream_cache_send(r, u); + } + + ngx_http_upstream_finalize_request(r, u, rc); + return NGX_OK; + } + +#endif + } + +#if (NGX_HTTP_CACHE) + + if (status == NGX_HTTP_NOT_MODIFIED + && u->cache_status == NGX_HTTP_CACHE_EXPIRED + && u->conf->cache_revalidate) + { + time_t now, valid, updating, error; + ngx_int_t rc; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http upstream not modified"); + + now = ngx_time(); + + valid = r->cache->valid_sec; + updating = r->cache->updating_sec; + error = r->cache->error_sec; + + rc = u->reinit_request(r); + + if (rc != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, rc); + return NGX_OK; + } + + u->cache_status = NGX_HTTP_CACHE_REVALIDATED; + rc = ngx_http_upstream_cache_send(r, u); + + if (valid == 0) { + valid = r->cache->valid_sec; + updating = r->cache->updating_sec; + error = r->cache->error_sec; + } + + if (valid == 0) { + valid = ngx_http_file_cache_valid(u->conf->cache_valid, + u->headers_in.status_n); + if (valid) { + valid = now + valid; + } + } + + if (valid) { + r->cache->valid_sec = valid; + r->cache->updating_sec = updating; + r->cache->error_sec = error; + + r->cache->date = now; + + ngx_http_file_cache_update_header(r); + } + + ngx_http_upstream_finalize_request(r, u, rc); + return NGX_OK; + } + +#endif + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_http_upstream_intercept_errors(ngx_http_request_t *r, + ngx_http_upstream_t *u) +{ + ngx_int_t status; + ngx_uint_t i; + ngx_table_elt_t *h; + ngx_http_err_page_t *err_page; + ngx_http_core_loc_conf_t *clcf; + + status = u->headers_in.status_n; + + if (status == NGX_HTTP_NOT_FOUND && u->conf->intercept_404) { + ngx_http_upstream_finalize_request(r, u, NGX_HTTP_NOT_FOUND); + return NGX_OK; + } + + if (!u->conf->intercept_errors) { + return NGX_DECLINED; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->error_pages == NULL) { + return NGX_DECLINED; + } + + err_page = clcf->error_pages->elts; + for (i = 0; i < clcf->error_pages->nelts; i++) { + + if (err_page[i].status == status) { + + if (status == NGX_HTTP_UNAUTHORIZED + && u->headers_in.www_authenticate) + { + h = ngx_list_push(&r->headers_out.headers); + + if (h == NULL) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_OK; + } + + *h = *u->headers_in.www_authenticate; + + r->headers_out.www_authenticate = h; + } + +#if (NGX_HTTP_CACHE) + + if (r->cache) { + time_t valid; + + valid = ngx_http_file_cache_valid(u->conf->cache_valid, status); + + if (valid) { + r->cache->valid_sec = ngx_time() + valid; + r->cache->error = status; + } + + ngx_http_file_cache_free(r->cache, u->pipe->temp_file); + } +#endif + ngx_http_upstream_finalize_request(r, u, status); + + return NGX_OK; + } + } + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_http_upstream_test_connect(ngx_connection_t *c) +{ + int err; + socklen_t len; + +#if (NGX_HAVE_KQUEUE) + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { + if (c->write->pending_eof || c->read->pending_eof) { + if (c->write->pending_eof) { + err = c->write->kq_errno; + + } else { + err = c->read->kq_errno; + } + + c->log->action = "connecting to upstream"; + (void) ngx_connection_error(c, err, + "kevent() reported that connect() failed"); + return NGX_ERROR; + } + + } else +#endif + { + err = 0; + len = sizeof(int); + + /* + * BSDs and Linux return 0 and set a pending error in err + * Solaris returns -1 and sets errno + */ + + if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len) + == -1) + { + err = ngx_socket_errno; + } + + if (err) { + c->log->action = "connecting to upstream"; + (void) ngx_connection_error(c, err, "connect() failed"); + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_process_headers(ngx_http_request_t *r, ngx_http_upstream_t *u) +{ + ngx_str_t uri, args; + ngx_uint_t i, flags; + ngx_list_part_t *part; + ngx_table_elt_t *h; + ngx_http_upstream_header_t *hh; + ngx_http_upstream_main_conf_t *umcf; + + umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); + + if (u->headers_in.x_accel_redirect + && !(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT)) + { + ngx_http_upstream_finalize_request(r, u, NGX_DECLINED); + + part = &u->headers_in.headers.part; + h = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + h = part->elts; + i = 0; + } + + hh = ngx_hash_find(&umcf->headers_in_hash, h[i].hash, + h[i].lowcase_key, h[i].key.len); + + if (hh && hh->redirect) { + if (hh->copy_handler(r, &h[i], hh->conf) != NGX_OK) { + ngx_http_finalize_request(r, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_DONE; + } + } + } + + uri = u->headers_in.x_accel_redirect->value; + + if (uri.data[0] == '@') { + ngx_http_named_location(r, &uri); + + } else { + ngx_str_null(&args); + flags = NGX_HTTP_LOG_UNSAFE; + + if (ngx_http_parse_unsafe_uri(r, &uri, &args, &flags) != NGX_OK) { + ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND); + return NGX_DONE; + } + + if (r->method != NGX_HTTP_HEAD) { + r->method = NGX_HTTP_GET; + r->method_name = ngx_http_core_get_method; + } + + ngx_http_internal_redirect(r, &uri, &args); + } + + ngx_http_finalize_request(r, NGX_DONE); + return NGX_DONE; + } + + part = &u->headers_in.headers.part; + h = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + h = part->elts; + i = 0; + } + + if (ngx_hash_find(&u->conf->hide_headers_hash, h[i].hash, + h[i].lowcase_key, h[i].key.len)) + { + continue; + } + + hh = ngx_hash_find(&umcf->headers_in_hash, h[i].hash, + h[i].lowcase_key, h[i].key.len); + + if (hh) { + if (hh->copy_handler(r, &h[i], hh->conf) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_DONE; + } + + continue; + } + + if (ngx_http_upstream_copy_header_line(r, &h[i], 0) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_DONE; + } + } + + if (r->headers_out.server && r->headers_out.server->value.data == NULL) { + r->headers_out.server->hash = 0; + } + + if (r->headers_out.date && r->headers_out.date->value.data == NULL) { + r->headers_out.date->hash = 0; + } + + r->headers_out.status = u->headers_in.status_n; + r->headers_out.status_line = u->headers_in.status_line; + + r->headers_out.content_length_n = u->headers_in.content_length_n; + + r->disable_not_modified = !u->cacheable; + + if (u->conf->force_ranges) { + r->allow_ranges = 1; + r->single_range = 1; + +#if (NGX_HTTP_CACHE) + if (r->cached) { + r->single_range = 0; + } +#endif + } + + u->length = -1; + + return NGX_OK; +} + + +static void +ngx_http_upstream_process_body_in_memory(ngx_http_request_t *r, + ngx_http_upstream_t *u) +{ + size_t size; + ssize_t n; + ngx_buf_t *b; + ngx_event_t *rev; + ngx_connection_t *c; + + c = u->peer.connection; + rev = c->read; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream process body on memory"); + + if (rev->timedout) { + ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out"); + ngx_http_upstream_finalize_request(r, u, NGX_HTTP_GATEWAY_TIME_OUT); + return; + } + + b = &u->buffer; + + for ( ;; ) { + + size = b->end - b->last; + + if (size == 0) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "upstream buffer is too small to read response"); + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + n = c->recv(c, b->last, size); + + if (n == NGX_AGAIN) { + break; + } + + if (n == 0 || n == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, n); + return; + } + + u->state->bytes_received += n; + u->state->response_length += n; + + if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + if (!rev->ready) { + break; + } + } + + if (u->length == 0) { + ngx_http_upstream_finalize_request(r, u, 0); + return; + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + if (rev->active) { + ngx_add_timer(rev, u->conf->read_timeout); + + } else if (rev->timer_set) { + ngx_del_timer(rev); + } +} + + +static void +ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u) +{ + int tcp_nodelay; + ssize_t n; + ngx_int_t rc; + ngx_event_pipe_t *p; + ngx_connection_t *c; + ngx_http_core_loc_conf_t *clcf; + + rc = ngx_http_send_header(r); + + if (rc == NGX_ERROR || rc > NGX_OK || r->post_action) { + ngx_http_upstream_finalize_request(r, u, rc); + return; + } + + u->header_sent = 1; + + if (u->upgrade) { + +#if (NGX_HTTP_CACHE) + + if (r->cache) { + ngx_http_file_cache_free(r->cache, u->pipe->temp_file); + } + +#endif + + ngx_http_upstream_upgrade(r, u); + return; + } + + c = r->connection; + + if (r->header_only) { + + if (!u->buffering) { + ngx_http_upstream_finalize_request(r, u, rc); + return; + } + + if (!u->cacheable && !u->store) { + ngx_http_upstream_finalize_request(r, u, rc); + return; + } + + u->pipe->downstream_error = 1; + } + + if (r->request_body && r->request_body->temp_file) { + ngx_pool_run_cleanup_file(r->pool, r->request_body->temp_file->file.fd); + r->request_body->temp_file->file.fd = NGX_INVALID_FILE; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (!u->buffering) { + +#if (NGX_HTTP_CACHE) + + if (r->cache) { + ngx_http_file_cache_free(r->cache, u->pipe->temp_file); + } + +#endif + + if (u->input_filter == NULL) { + u->input_filter_init = ngx_http_upstream_non_buffered_filter_init; + u->input_filter = ngx_http_upstream_non_buffered_filter; + u->input_filter_ctx = r; + } + + u->read_event_handler = ngx_http_upstream_process_non_buffered_upstream; + r->write_event_handler = + ngx_http_upstream_process_non_buffered_downstream; + + r->limit_rate = 0; + + if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + if (clcf->tcp_nodelay && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); + + tcp_nodelay = 1; + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) == -1) + { + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + n = u->buffer.last - u->buffer.pos; + + if (n) { + u->buffer.last = u->buffer.pos; + + u->state->response_length += n; + + if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + ngx_http_upstream_process_non_buffered_downstream(r); + + } else { + u->buffer.pos = u->buffer.start; + u->buffer.last = u->buffer.start; + + if (ngx_http_send_special(r, NGX_HTTP_FLUSH) == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + if (u->peer.connection->read->ready || u->length == 0) { + ngx_http_upstream_process_non_buffered_upstream(r, u); + } + } + + return; + } + + /* TODO: preallocate event_pipe bufs, look "Content-Length" */ + +#if (NGX_HTTP_CACHE) + + if (r->cache && r->cache->file.fd != NGX_INVALID_FILE) { + ngx_pool_run_cleanup_file(r->pool, r->cache->file.fd); + r->cache->file.fd = NGX_INVALID_FILE; + } + + switch (ngx_http_test_predicates(r, u->conf->no_cache)) { + + case NGX_ERROR: + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + + case NGX_DECLINED: + u->cacheable = 0; + break; + + default: /* NGX_OK */ + + if (u->cache_status == NGX_HTTP_CACHE_BYPASS) { + + /* create cache if previously bypassed */ + + if (ngx_http_file_cache_create(r) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + } + + break; + } + + if (u->cacheable) { + time_t now, valid; + + now = ngx_time(); + + valid = r->cache->valid_sec; + + if (valid == 0) { + valid = ngx_http_file_cache_valid(u->conf->cache_valid, + u->headers_in.status_n); + if (valid) { + r->cache->valid_sec = now + valid; + } + } + + if (valid) { + r->cache->date = now; + r->cache->body_start = (u_short) (u->buffer.pos - u->buffer.start); + + if (u->headers_in.status_n == NGX_HTTP_OK + || u->headers_in.status_n == NGX_HTTP_PARTIAL_CONTENT) + { + r->cache->last_modified = u->headers_in.last_modified_time; + + if (u->headers_in.etag) { + r->cache->etag = u->headers_in.etag->value; + + } else { + ngx_str_null(&r->cache->etag); + } + + } else { + r->cache->last_modified = -1; + ngx_str_null(&r->cache->etag); + } + + if (ngx_http_file_cache_set_header(r, u->buffer.start) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + } else { + u->cacheable = 0; + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http cacheable: %d", u->cacheable); + + if (u->cacheable == 0 && r->cache) { + ngx_http_file_cache_free(r->cache, u->pipe->temp_file); + } + + if (r->header_only && !u->cacheable && !u->store) { + ngx_http_upstream_finalize_request(r, u, 0); + return; + } + +#endif + + p = u->pipe; + + p->output_filter = ngx_http_upstream_output_filter; + p->output_ctx = r; + p->tag = u->output.tag; + p->bufs = u->conf->bufs; + p->busy_size = u->conf->busy_buffers_size; + p->upstream = u->peer.connection; + p->downstream = c; + p->pool = r->pool; + p->log = c->log; + p->limit_rate = u->conf->limit_rate; + p->start_sec = ngx_time(); + + p->cacheable = u->cacheable || u->store; + + p->temp_file = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)); + if (p->temp_file == NULL) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + p->temp_file->file.fd = NGX_INVALID_FILE; + p->temp_file->file.log = c->log; + p->temp_file->path = u->conf->temp_path; + p->temp_file->pool = r->pool; + + if (p->cacheable) { + p->temp_file->persistent = 1; + +#if (NGX_HTTP_CACHE) + if (r->cache && !r->cache->file_cache->use_temp_path) { + p->temp_file->path = r->cache->file_cache->path; + p->temp_file->file.name = r->cache->file.name; + } +#endif + + } else { + p->temp_file->log_level = NGX_LOG_WARN; + p->temp_file->warn = "an upstream response is buffered " + "to a temporary file"; + } + + p->max_temp_file_size = u->conf->max_temp_file_size; + p->temp_file_write_size = u->conf->temp_file_write_size; + +#if (NGX_THREADS) + if (clcf->aio == NGX_HTTP_AIO_THREADS && clcf->aio_write) { + p->thread_handler = ngx_http_upstream_thread_handler; + p->thread_ctx = r; + } +#endif + + p->preread_bufs = ngx_alloc_chain_link(r->pool); + if (p->preread_bufs == NULL) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + p->preread_bufs->buf = &u->buffer; + p->preread_bufs->next = NULL; + u->buffer.recycled = 1; + + p->preread_size = u->buffer.last - u->buffer.pos; + + if (u->cacheable) { + + p->buf_to_file = ngx_calloc_buf(r->pool); + if (p->buf_to_file == NULL) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + p->buf_to_file->start = u->buffer.start; + p->buf_to_file->pos = u->buffer.start; + p->buf_to_file->last = u->buffer.pos; + p->buf_to_file->temporary = 1; + } + + if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + /* the posted aio operation may corrupt a shadow buffer */ + p->single_buf = 1; + } + + /* TODO: p->free_bufs = 0 if use ngx_create_chain_of_bufs() */ + p->free_bufs = 1; + + /* + * event_pipe would do u->buffer.last += p->preread_size + * as though these bytes were read + */ + u->buffer.last = u->buffer.pos; + + if (u->conf->cyclic_temp_file) { + + /* + * we need to disable the use of sendfile() if we use cyclic temp file + * because the writing a new data may interfere with sendfile() + * that uses the same kernel file pages (at least on FreeBSD) + */ + + p->cyclic_temp_file = 1; + c->sendfile = 0; + + } else { + p->cyclic_temp_file = 0; + } + + p->read_timeout = u->conf->read_timeout; + p->send_timeout = clcf->send_timeout; + p->send_lowat = clcf->send_lowat; + + p->length = -1; + + if (u->input_filter_init + && u->input_filter_init(p->input_ctx) != NGX_OK) + { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + u->read_event_handler = ngx_http_upstream_process_upstream; + r->write_event_handler = ngx_http_upstream_process_downstream; + + ngx_http_upstream_process_upstream(r, u); +} + + +static void +ngx_http_upstream_upgrade(ngx_http_request_t *r, ngx_http_upstream_t *u) +{ + int tcp_nodelay; + ngx_connection_t *c; + ngx_http_core_loc_conf_t *clcf; + + c = r->connection; + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + /* TODO: prevent upgrade if not requested or not possible */ + + r->keepalive = 0; + c->log->action = "proxying upgraded connection"; + + u->read_event_handler = ngx_http_upstream_upgraded_read_upstream; + u->write_event_handler = ngx_http_upstream_upgraded_write_upstream; + r->read_event_handler = ngx_http_upstream_upgraded_read_downstream; + r->write_event_handler = ngx_http_upstream_upgraded_write_downstream; + + if (clcf->tcp_nodelay) { + tcp_nodelay = 1; + + if (c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) == -1) + { + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + if (u->peer.connection->tcp_nodelay == NGX_TCP_NODELAY_UNSET) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, u->peer.connection->log, 0, + "tcp_nodelay"); + + if (setsockopt(u->peer.connection->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) == -1) + { + ngx_connection_error(u->peer.connection, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + u->peer.connection->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + } + + if (ngx_http_send_special(r, NGX_HTTP_FLUSH) == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + if (u->peer.connection->read->ready + || u->buffer.pos != u->buffer.last) + { + ngx_post_event(c->read, &ngx_posted_events); + ngx_http_upstream_process_upgraded(r, 1, 1); + return; + } + + ngx_http_upstream_process_upgraded(r, 0, 1); +} + + +static void +ngx_http_upstream_upgraded_read_downstream(ngx_http_request_t *r) +{ + ngx_http_upstream_process_upgraded(r, 0, 0); +} + + +static void +ngx_http_upstream_upgraded_write_downstream(ngx_http_request_t *r) +{ + ngx_http_upstream_process_upgraded(r, 1, 1); +} + + +static void +ngx_http_upstream_upgraded_read_upstream(ngx_http_request_t *r, + ngx_http_upstream_t *u) +{ + ngx_http_upstream_process_upgraded(r, 1, 0); +} + + +static void +ngx_http_upstream_upgraded_write_upstream(ngx_http_request_t *r, + ngx_http_upstream_t *u) +{ + ngx_http_upstream_process_upgraded(r, 0, 1); +} + + +static void +ngx_http_upstream_process_upgraded(ngx_http_request_t *r, + ngx_uint_t from_upstream, ngx_uint_t do_write) +{ + size_t size; + ssize_t n; + ngx_buf_t *b; + ngx_connection_t *c, *downstream, *upstream, *dst, *src; + ngx_http_upstream_t *u; + ngx_http_core_loc_conf_t *clcf; + + c = r->connection; + u = r->upstream; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream process upgraded, fu:%ui", from_upstream); + + downstream = c; + upstream = u->peer.connection; + + if (downstream->write->timedout) { + c->timedout = 1; + ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out"); + ngx_http_upstream_finalize_request(r, u, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + if (upstream->read->timedout || upstream->write->timedout) { + ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out"); + ngx_http_upstream_finalize_request(r, u, NGX_HTTP_GATEWAY_TIME_OUT); + return; + } + + if (from_upstream) { + src = upstream; + dst = downstream; + b = &u->buffer; + + } else { + src = downstream; + dst = upstream; + b = &u->from_client; + + if (r->header_in->last > r->header_in->pos) { + b = r->header_in; + b->end = b->last; + do_write = 1; + } + + if (b->start == NULL) { + b->start = ngx_palloc(r->pool, u->conf->buffer_size); + if (b->start == NULL) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + b->pos = b->start; + b->last = b->start; + b->end = b->start + u->conf->buffer_size; + b->temporary = 1; + b->tag = u->output.tag; + } + } + + for ( ;; ) { + + if (do_write) { + + size = b->last - b->pos; + + if (size && dst->write->ready) { + + n = dst->send(dst, b->pos, size); + + if (n == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + if (n > 0) { + b->pos += n; + + if (b->pos == b->last) { + b->pos = b->start; + b->last = b->start; + } + } + } + } + + size = b->end - b->last; + + if (size && src->read->ready) { + + n = src->recv(src, b->last, size); + + if (n == NGX_AGAIN || n == 0) { + break; + } + + if (n > 0) { + do_write = 1; + b->last += n; + + if (from_upstream) { + u->state->bytes_received += n; + } + + continue; + } + + if (n == NGX_ERROR) { + src->read->eof = 1; + } + } + + break; + } + + if ((upstream->read->eof && u->buffer.pos == u->buffer.last) + || (downstream->read->eof && u->from_client.pos == u->from_client.last) + || (downstream->read->eof && upstream->read->eof)) + { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream upgraded done"); + ngx_http_upstream_finalize_request(r, u, 0); + return; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (ngx_handle_write_event(upstream->write, u->conf->send_lowat) + != NGX_OK) + { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + if (upstream->write->active && !upstream->write->ready) { + ngx_add_timer(upstream->write, u->conf->send_timeout); + + } else if (upstream->write->timer_set) { + ngx_del_timer(upstream->write); + } + + if (ngx_handle_read_event(upstream->read, 0) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + if (upstream->read->active && !upstream->read->ready) { + ngx_add_timer(upstream->read, u->conf->read_timeout); + + } else if (upstream->read->timer_set) { + ngx_del_timer(upstream->read); + } + + if (ngx_handle_write_event(downstream->write, clcf->send_lowat) + != NGX_OK) + { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + if (ngx_handle_read_event(downstream->read, 0) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + if (downstream->write->active && !downstream->write->ready) { + ngx_add_timer(downstream->write, clcf->send_timeout); + + } else if (downstream->write->timer_set) { + ngx_del_timer(downstream->write); + } +} + + +static void +ngx_http_upstream_process_non_buffered_downstream(ngx_http_request_t *r) +{ + ngx_event_t *wev; + ngx_connection_t *c; + ngx_http_upstream_t *u; + + c = r->connection; + u = r->upstream; + wev = c->write; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream process non buffered downstream"); + + c->log->action = "sending to client"; + + if (wev->timedout) { + c->timedout = 1; + ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out"); + ngx_http_upstream_finalize_request(r, u, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + ngx_http_upstream_process_non_buffered_request(r, 1); +} + + +static void +ngx_http_upstream_process_non_buffered_upstream(ngx_http_request_t *r, + ngx_http_upstream_t *u) +{ + ngx_connection_t *c; + + c = u->peer.connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream process non buffered upstream"); + + c->log->action = "reading upstream"; + + if (c->read->timedout) { + ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out"); + ngx_http_upstream_finalize_request(r, u, NGX_HTTP_GATEWAY_TIME_OUT); + return; + } + + ngx_http_upstream_process_non_buffered_request(r, 0); +} + + +static void +ngx_http_upstream_process_non_buffered_request(ngx_http_request_t *r, + ngx_uint_t do_write) +{ + size_t size; + ssize_t n; + ngx_buf_t *b; + ngx_int_t rc; + ngx_connection_t *downstream, *upstream; + ngx_http_upstream_t *u; + ngx_http_core_loc_conf_t *clcf; + + u = r->upstream; + downstream = r->connection; + upstream = u->peer.connection; + + b = &u->buffer; + + do_write = do_write || u->length == 0; + + for ( ;; ) { + + if (do_write) { + + if (u->out_bufs || u->busy_bufs) { + rc = ngx_http_output_filter(r, u->out_bufs); + + if (rc == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + ngx_chain_update_chains(r->pool, &u->free_bufs, &u->busy_bufs, + &u->out_bufs, u->output.tag); + } + + if (u->busy_bufs == NULL) { + + if (u->length == 0 + || (upstream->read->eof && u->length == -1)) + { + ngx_http_upstream_finalize_request(r, u, 0); + return; + } + + if (upstream->read->eof) { + ngx_log_error(NGX_LOG_ERR, upstream->log, 0, + "upstream prematurely closed connection"); + + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_BAD_GATEWAY); + return; + } + + if (upstream->read->error) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_BAD_GATEWAY); + return; + } + + b->pos = b->start; + b->last = b->start; + } + } + + size = b->end - b->last; + + if (size && upstream->read->ready) { + + n = upstream->recv(upstream, b->last, size); + + if (n == NGX_AGAIN) { + break; + } + + if (n > 0) { + u->state->bytes_received += n; + u->state->response_length += n; + + if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + } + + do_write = 1; + + continue; + } + + break; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (downstream->data == r) { + if (ngx_handle_write_event(downstream->write, clcf->send_lowat) + != NGX_OK) + { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + } + + if (downstream->write->active && !downstream->write->ready) { + ngx_add_timer(downstream->write, clcf->send_timeout); + + } else if (downstream->write->timer_set) { + ngx_del_timer(downstream->write); + } + + if (ngx_handle_read_event(upstream->read, 0) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + + if (upstream->read->active && !upstream->read->ready) { + ngx_add_timer(upstream->read, u->conf->read_timeout); + + } else if (upstream->read->timer_set) { + ngx_del_timer(upstream->read); + } +} + + +static ngx_int_t +ngx_http_upstream_non_buffered_filter_init(void *data) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_non_buffered_filter(void *data, ssize_t bytes) +{ + ngx_http_request_t *r = data; + + ngx_buf_t *b; + ngx_chain_t *cl, **ll; + ngx_http_upstream_t *u; + + u = r->upstream; + + for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) { + ll = &cl->next; + } + + cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs); + if (cl == NULL) { + return NGX_ERROR; + } + + *ll = cl; + + cl->buf->flush = 1; + cl->buf->memory = 1; + + b = &u->buffer; + + cl->buf->pos = b->last; + b->last += bytes; + cl->buf->last = b->last; + cl->buf->tag = u->output.tag; + + if (u->length == -1) { + return NGX_OK; + } + + u->length -= bytes; + + return NGX_OK; +} + + +#if (NGX_THREADS) + +static ngx_int_t +ngx_http_upstream_thread_handler(ngx_thread_task_t *task, ngx_file_t *file) +{ + ngx_str_t name; + ngx_event_pipe_t *p; + ngx_thread_pool_t *tp; + ngx_http_request_t *r; + ngx_http_core_loc_conf_t *clcf; + + r = file->thread_ctx; + p = r->upstream->pipe; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + tp = clcf->thread_pool; + + if (tp == NULL) { + if (ngx_http_complex_value(r, clcf->thread_pool_value, &name) + != NGX_OK) + { + return NGX_ERROR; + } + + tp = ngx_thread_pool_get((ngx_cycle_t *) ngx_cycle, &name); + + if (tp == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "thread pool \"%V\" not found", &name); + return NGX_ERROR; + } + } + + task->event.data = r; + task->event.handler = ngx_http_upstream_thread_event_handler; + + if (ngx_thread_task_post(tp, task) != NGX_OK) { + return NGX_ERROR; + } + + r->main->blocked++; + r->aio = 1; + p->aio = 1; + + return NGX_OK; +} + + +static void +ngx_http_upstream_thread_event_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_http_request_t *r; + + r = ev->data; + c = r->connection; + + ngx_http_set_log_request(c->log, r); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream thread: \"%V?%V\"", &r->uri, &r->args); + + r->main->blocked--; + r->aio = 0; + + if (r->done) { + /* + * trigger connection event handler if the subrequest was + * already finalized; this can happen if the handler is used + * for sendfile() in threads + */ + + c->write->handler(c->write); + + } else { + r->write_event_handler(r); + ngx_http_run_posted_requests(c); + } +} + +#endif + + +static ngx_int_t +ngx_http_upstream_output_filter(void *data, ngx_chain_t *chain) +{ + ngx_int_t rc; + ngx_event_pipe_t *p; + ngx_http_request_t *r; + + r = data; + p = r->upstream->pipe; + + rc = ngx_http_output_filter(r, chain); + + p->aio = r->aio; + + return rc; +} + + +static void +ngx_http_upstream_process_downstream(ngx_http_request_t *r) +{ + ngx_event_t *wev; + ngx_connection_t *c; + ngx_event_pipe_t *p; + ngx_http_upstream_t *u; + + c = r->connection; + u = r->upstream; + p = u->pipe; + wev = c->write; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream process downstream"); + + c->log->action = "sending to client"; + +#if (NGX_THREADS) + p->aio = r->aio; +#endif + + if (wev->timedout) { + + p->downstream_error = 1; + c->timedout = 1; + ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out"); + + } else { + + if (wev->delayed) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http downstream delayed"); + + if (ngx_handle_write_event(wev, p->send_lowat) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + } + + return; + } + + if (ngx_event_pipe(p, 1) == NGX_ABORT) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + } + + ngx_http_upstream_process_request(r, u); +} + + +static void +ngx_http_upstream_process_upstream(ngx_http_request_t *r, + ngx_http_upstream_t *u) +{ + ngx_event_t *rev; + ngx_event_pipe_t *p; + ngx_connection_t *c; + + c = u->peer.connection; + p = u->pipe; + rev = c->read; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream process upstream"); + + c->log->action = "reading upstream"; + + if (rev->timedout) { + + p->upstream_error = 1; + ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out"); + + } else { + + if (rev->delayed) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream delayed"); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + } + + return; + } + + if (ngx_event_pipe(p, 0) == NGX_ABORT) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + } + + ngx_http_upstream_process_request(r, u); +} + + +static void +ngx_http_upstream_process_request(ngx_http_request_t *r, + ngx_http_upstream_t *u) +{ + ngx_temp_file_t *tf; + ngx_event_pipe_t *p; + + p = u->pipe; + +#if (NGX_THREADS) + + if (p->writing && !p->aio) { + + /* + * make sure to call ngx_event_pipe() + * if there is an incomplete aio write + */ + + if (ngx_event_pipe(p, 1) == NGX_ABORT) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + return; + } + } + + if (p->writing) { + return; + } + +#endif + + if (u->peer.connection) { + + if (u->store) { + + if (p->upstream_eof || p->upstream_done) { + + tf = p->temp_file; + + if (u->headers_in.status_n == NGX_HTTP_OK + && (p->upstream_done || p->length == -1) + && (u->headers_in.content_length_n == -1 + || u->headers_in.content_length_n == tf->offset)) + { + ngx_http_upstream_store(r, u); + } + } + } + +#if (NGX_HTTP_CACHE) + + if (u->cacheable) { + + if (p->upstream_done) { + ngx_http_file_cache_update(r, p->temp_file); + + } else if (p->upstream_eof) { + + tf = p->temp_file; + + if (p->length == -1 + && (u->headers_in.content_length_n == -1 + || u->headers_in.content_length_n + == tf->offset - (off_t) r->cache->body_start)) + { + ngx_http_file_cache_update(r, tf); + + } else { + ngx_http_file_cache_free(r->cache, tf); + } + + } else if (p->upstream_error) { + ngx_http_file_cache_free(r->cache, p->temp_file); + } + } + +#endif + + if (p->upstream_done || p->upstream_eof || p->upstream_error) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http upstream exit: %p", p->out); + + if (p->upstream_done + || (p->upstream_eof && p->length == -1)) + { + ngx_http_upstream_finalize_request(r, u, 0); + return; + } + + if (p->upstream_eof) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream prematurely closed connection"); + } + + ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY); + return; + } + } + + if (p->downstream_error) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http upstream downstream error"); + + if (!u->cacheable && !u->store && u->peer.connection) { + ngx_http_upstream_finalize_request(r, u, NGX_ERROR); + } + } +} + + +static void +ngx_http_upstream_store(ngx_http_request_t *r, ngx_http_upstream_t *u) +{ + size_t root; + time_t lm; + ngx_str_t path; + ngx_temp_file_t *tf; + ngx_ext_rename_file_t ext; + + tf = u->pipe->temp_file; + + if (tf->file.fd == NGX_INVALID_FILE) { + + /* create file for empty 200 response */ + + tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)); + if (tf == NULL) { + return; + } + + tf->file.fd = NGX_INVALID_FILE; + tf->file.log = r->connection->log; + tf->path = u->conf->temp_path; + tf->pool = r->pool; + tf->persistent = 1; + + if (ngx_create_temp_file(&tf->file, tf->path, tf->pool, + tf->persistent, tf->clean, tf->access) + != NGX_OK) + { + return; + } + + u->pipe->temp_file = tf; + } + + ext.access = u->conf->store_access; + ext.path_access = u->conf->store_access; + ext.time = -1; + ext.create_path = 1; + ext.delete_file = 1; + ext.log = r->connection->log; + + if (u->headers_in.last_modified) { + + lm = ngx_parse_http_time(u->headers_in.last_modified->value.data, + u->headers_in.last_modified->value.len); + + if (lm != NGX_ERROR) { + ext.time = lm; + ext.fd = tf->file.fd; + } + } + + if (u->conf->store_lengths == NULL) { + + if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) { + return; + } + + } else { + if (ngx_http_script_run(r, &path, u->conf->store_lengths->elts, 0, + u->conf->store_values->elts) + == NULL) + { + return; + } + } + + path.len--; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "upstream stores \"%s\" to \"%s\"", + tf->file.name.data, path.data); + + (void) ngx_ext_rename_file(&tf->file.name, &path, &ext); + + u->store = 0; +} + + +static void +ngx_http_upstream_dummy_handler(ngx_http_request_t *r, ngx_http_upstream_t *u) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http upstream dummy handler"); +} + + +static void +ngx_http_upstream_next(ngx_http_request_t *r, ngx_http_upstream_t *u, + ngx_uint_t ft_type) +{ + ngx_msec_t timeout; + ngx_uint_t status, state; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http next upstream, %xi", ft_type); + + if (u->peer.sockaddr) { + + if (ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_403 + || ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_404) + { + state = NGX_PEER_NEXT; + + } else { + state = NGX_PEER_FAILED; + } + + u->peer.free(&u->peer, u->peer.data, state); + u->peer.sockaddr = NULL; + } + + if (ft_type == NGX_HTTP_UPSTREAM_FT_TIMEOUT) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_ETIMEDOUT, + "upstream timed out"); + } + + if (u->peer.cached && ft_type == NGX_HTTP_UPSTREAM_FT_ERROR) { + /* TODO: inform balancer instead */ + u->peer.tries++; + } + + switch (ft_type) { + + case NGX_HTTP_UPSTREAM_FT_TIMEOUT: + status = NGX_HTTP_GATEWAY_TIME_OUT; + break; + + case NGX_HTTP_UPSTREAM_FT_HTTP_500: + status = NGX_HTTP_INTERNAL_SERVER_ERROR; + break; + + case NGX_HTTP_UPSTREAM_FT_HTTP_403: + status = NGX_HTTP_FORBIDDEN; + break; + + case NGX_HTTP_UPSTREAM_FT_HTTP_404: + status = NGX_HTTP_NOT_FOUND; + break; + + case NGX_HTTP_UPSTREAM_FT_HTTP_429: + status = NGX_HTTP_TOO_MANY_REQUESTS; + break; + + /* + * NGX_HTTP_UPSTREAM_FT_BUSY_LOCK and NGX_HTTP_UPSTREAM_FT_MAX_WAITING + * never reach here + */ + + default: + status = NGX_HTTP_BAD_GATEWAY; + } + + if (r->connection->error) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + u->state->status = status; + + timeout = u->conf->next_upstream_timeout; + + if (u->request_sent + && (r->method & (NGX_HTTP_POST|NGX_HTTP_LOCK|NGX_HTTP_PATCH))) + { + ft_type |= NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT; + } + + if (u->peer.tries == 0 + || ((u->conf->next_upstream & ft_type) != ft_type) + || (u->request_sent && r->request_body_no_buffering) + || (timeout && ngx_current_msec - u->peer.start_time >= timeout)) + { +#if (NGX_HTTP_CACHE) + + if (u->cache_status == NGX_HTTP_CACHE_EXPIRED + && ((u->conf->cache_use_stale & ft_type) || r->cache->stale_error)) + { + ngx_int_t rc; + + rc = u->reinit_request(r); + + if (rc == NGX_OK) { + u->cache_status = NGX_HTTP_CACHE_STALE; + rc = ngx_http_upstream_cache_send(r, u); + } + + ngx_http_upstream_finalize_request(r, u, rc); + return; + } +#endif + + ngx_http_upstream_finalize_request(r, u, status); + return; + } + + if (u->peer.connection) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "close http upstream connection: %d", + u->peer.connection->fd); +#if (NGX_HTTP_SSL) + + if (u->peer.connection->ssl) { + u->peer.connection->ssl->no_wait_shutdown = 1; + u->peer.connection->ssl->no_send_shutdown = 1; + + (void) ngx_ssl_shutdown(u->peer.connection); + } +#endif + + if (u->peer.connection->pool) { + ngx_destroy_pool(u->peer.connection->pool); + } + + ngx_close_connection(u->peer.connection); + u->peer.connection = NULL; + } + + ngx_http_upstream_connect(r, u); +} + + +static void +ngx_http_upstream_cleanup(void *data) +{ + ngx_http_request_t *r = data; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "cleanup http upstream request: \"%V\"", &r->uri); + + ngx_http_upstream_finalize_request(r, r->upstream, NGX_DONE); +} + + +static void +ngx_http_upstream_finalize_request(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_int_t rc) +{ + ngx_uint_t flush; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "finalize http upstream request: %i", rc); + + if (u->cleanup == NULL) { + /* the request was already finalized */ + ngx_http_finalize_request(r, NGX_DONE); + return; + } + + *u->cleanup = NULL; + u->cleanup = NULL; + + if (u->resolved && u->resolved->ctx) { + ngx_resolve_name_done(u->resolved->ctx); + u->resolved->ctx = NULL; + } + + if (u->state && u->state->response_time) { + u->state->response_time = ngx_current_msec - u->state->response_time; + + if (u->pipe && u->pipe->read_length) { + u->state->bytes_received += u->pipe->read_length + - u->pipe->preread_size; + u->state->response_length = u->pipe->read_length; + } + } + + u->finalize_request(r, rc); + + if (u->peer.free && u->peer.sockaddr) { + u->peer.free(&u->peer, u->peer.data, 0); + u->peer.sockaddr = NULL; + } + + if (u->peer.connection) { + +#if (NGX_HTTP_SSL) + + /* TODO: do not shutdown persistent connection */ + + if (u->peer.connection->ssl) { + + /* + * We send the "close notify" shutdown alert to the upstream only + * and do not wait its "close notify" shutdown alert. + * It is acceptable according to the TLS standard. + */ + + u->peer.connection->ssl->no_wait_shutdown = 1; + + (void) ngx_ssl_shutdown(u->peer.connection); + } +#endif + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "close http upstream connection: %d", + u->peer.connection->fd); + + if (u->peer.connection->pool) { + ngx_destroy_pool(u->peer.connection->pool); + } + + ngx_close_connection(u->peer.connection); + } + + u->peer.connection = NULL; + + if (u->pipe && u->pipe->temp_file) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http upstream temp fd: %d", + u->pipe->temp_file->file.fd); + } + + if (u->store && u->pipe && u->pipe->temp_file + && u->pipe->temp_file->file.fd != NGX_INVALID_FILE) + { + if (ngx_delete_file(u->pipe->temp_file->file.name.data) + == NGX_FILE_ERROR) + { + ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno, + ngx_delete_file_n " \"%s\" failed", + u->pipe->temp_file->file.name.data); + } + } + +#if (NGX_HTTP_CACHE) + + if (r->cache) { + + if (u->cacheable) { + + if (rc == NGX_HTTP_BAD_GATEWAY || rc == NGX_HTTP_GATEWAY_TIME_OUT) { + time_t valid; + + valid = ngx_http_file_cache_valid(u->conf->cache_valid, rc); + + if (valid) { + r->cache->valid_sec = ngx_time() + valid; + r->cache->error = rc; + } + } + } + + ngx_http_file_cache_free(r->cache, u->pipe->temp_file); + } + +#endif + + if (r->subrequest_in_memory + && u->headers_in.status_n >= NGX_HTTP_SPECIAL_RESPONSE) + { + u->buffer.last = u->buffer.pos; + } + + r->read_event_handler = ngx_http_block_reading; + + if (rc == NGX_DECLINED) { + return; + } + + r->connection->log->action = "sending to client"; + + if (!u->header_sent + || rc == NGX_HTTP_REQUEST_TIME_OUT + || rc == NGX_HTTP_CLIENT_CLOSED_REQUEST + || (u->pipe && u->pipe->downstream_error)) + { + ngx_http_finalize_request(r, rc); + return; + } + + flush = 0; + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + rc = NGX_ERROR; + flush = 1; + } + + if (r->header_only) { + ngx_http_finalize_request(r, rc); + return; + } + + if (rc == 0) { + rc = ngx_http_send_special(r, NGX_HTTP_LAST); + + } else if (flush) { + r->keepalive = 0; + rc = ngx_http_send_special(r, NGX_HTTP_FLUSH); + } + + ngx_http_finalize_request(r, rc); +} + + +static ngx_int_t +ngx_http_upstream_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + ngx_table_elt_t **ph; + + ph = (ngx_table_elt_t **) ((char *) &r->upstream->headers_in + offset); + + if (*ph == NULL) { + *ph = h; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_ignore_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_process_content_length(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset) +{ + ngx_http_upstream_t *u; + + u = r->upstream; + + u->headers_in.content_length = h; + u->headers_in.content_length_n = ngx_atoof(h->value.data, h->value.len); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_process_last_modified(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset) +{ + ngx_http_upstream_t *u; + + u = r->upstream; + + u->headers_in.last_modified = h; + +#if (NGX_HTTP_CACHE) + + if (u->cacheable) { + u->headers_in.last_modified_time = ngx_parse_http_time(h->value.data, + h->value.len); + } + +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_process_set_cookie(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + ngx_array_t *pa; + ngx_table_elt_t **ph; + ngx_http_upstream_t *u; + + u = r->upstream; + pa = &u->headers_in.cookies; + + if (pa->elts == NULL) { + if (ngx_array_init(pa, r->pool, 1, sizeof(ngx_table_elt_t *)) != NGX_OK) + { + return NGX_ERROR; + } + } + + ph = ngx_array_push(pa); + if (ph == NULL) { + return NGX_ERROR; + } + + *ph = h; + +#if (NGX_HTTP_CACHE) + if (!(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_SET_COOKIE)) { + u->cacheable = 0; + } +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_process_cache_control(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset) +{ + ngx_array_t *pa; + ngx_table_elt_t **ph; + ngx_http_upstream_t *u; + + u = r->upstream; + pa = &u->headers_in.cache_control; + + if (pa->elts == NULL) { + if (ngx_array_init(pa, r->pool, 2, sizeof(ngx_table_elt_t *)) != NGX_OK) + { + return NGX_ERROR; + } + } + + ph = ngx_array_push(pa); + if (ph == NULL) { + return NGX_ERROR; + } + + *ph = h; + +#if (NGX_HTTP_CACHE) + { + u_char *p, *start, *last; + ngx_int_t n; + + if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL) { + return NGX_OK; + } + + if (r->cache == NULL) { + return NGX_OK; + } + + if (r->cache->valid_sec != 0 && u->headers_in.x_accel_expires != NULL) { + return NGX_OK; + } + + start = h->value.data; + last = start + h->value.len; + + if (ngx_strlcasestrn(start, last, (u_char *) "no-cache", 8 - 1) != NULL + || ngx_strlcasestrn(start, last, (u_char *) "no-store", 8 - 1) != NULL + || ngx_strlcasestrn(start, last, (u_char *) "private", 7 - 1) != NULL) + { + u->cacheable = 0; + return NGX_OK; + } + + p = ngx_strlcasestrn(start, last, (u_char *) "s-maxage=", 9 - 1); + offset = 9; + + if (p == NULL) { + p = ngx_strlcasestrn(start, last, (u_char *) "max-age=", 8 - 1); + offset = 8; + } + + if (p) { + n = 0; + + for (p += offset; p < last; p++) { + if (*p == ',' || *p == ';' || *p == ' ') { + break; + } + + if (*p >= '0' && *p <= '9') { + n = n * 10 + *p - '0'; + continue; + } + + u->cacheable = 0; + return NGX_OK; + } + + if (n == 0) { + u->cacheable = 0; + return NGX_OK; + } + + r->cache->valid_sec = ngx_time() + n; + } + + p = ngx_strlcasestrn(start, last, (u_char *) "stale-while-revalidate=", + 23 - 1); + + if (p) { + n = 0; + + for (p += 23; p < last; p++) { + if (*p == ',' || *p == ';' || *p == ' ') { + break; + } + + if (*p >= '0' && *p <= '9') { + n = n * 10 + *p - '0'; + continue; + } + + u->cacheable = 0; + return NGX_OK; + } + + r->cache->updating_sec = n; + r->cache->error_sec = n; + } + + p = ngx_strlcasestrn(start, last, (u_char *) "stale-if-error=", 15 - 1); + + if (p) { + n = 0; + + for (p += 15; p < last; p++) { + if (*p == ',' || *p == ';' || *p == ' ') { + break; + } + + if (*p >= '0' && *p <= '9') { + n = n * 10 + *p - '0'; + continue; + } + + u->cacheable = 0; + return NGX_OK; + } + + r->cache->error_sec = n; + } + } +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_process_expires(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + ngx_http_upstream_t *u; + + u = r->upstream; + u->headers_in.expires = h; + +#if (NGX_HTTP_CACHE) + { + time_t expires; + + if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_EXPIRES) { + return NGX_OK; + } + + if (r->cache == NULL) { + return NGX_OK; + } + + if (r->cache->valid_sec != 0) { + return NGX_OK; + } + + expires = ngx_parse_http_time(h->value.data, h->value.len); + + if (expires == NGX_ERROR || expires < ngx_time()) { + u->cacheable = 0; + return NGX_OK; + } + + r->cache->valid_sec = expires; + } +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_process_accel_expires(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset) +{ + ngx_http_upstream_t *u; + + u = r->upstream; + u->headers_in.x_accel_expires = h; + +#if (NGX_HTTP_CACHE) + { + u_char *p; + size_t len; + ngx_int_t n; + + if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_EXPIRES) { + return NGX_OK; + } + + if (r->cache == NULL) { + return NGX_OK; + } + + len = h->value.len; + p = h->value.data; + + if (p[0] != '@') { + n = ngx_atoi(p, len); + + switch (n) { + case 0: + u->cacheable = 0; + /* fall through */ + + case NGX_ERROR: + return NGX_OK; + + default: + r->cache->valid_sec = ngx_time() + n; + return NGX_OK; + } + } + + p++; + len--; + + n = ngx_atoi(p, len); + + if (n != NGX_ERROR) { + r->cache->valid_sec = n; + } + } +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_process_limit_rate(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + ngx_int_t n; + ngx_http_upstream_t *u; + + u = r->upstream; + u->headers_in.x_accel_limit_rate = h; + + if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE) { + return NGX_OK; + } + + n = ngx_atoi(h->value.data, h->value.len); + + if (n != NGX_ERROR) { + r->limit_rate = (size_t) n; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_process_buffering(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + u_char c0, c1, c2; + ngx_http_upstream_t *u; + + u = r->upstream; + + if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING) { + return NGX_OK; + } + + if (u->conf->change_buffering) { + + if (h->value.len == 2) { + c0 = ngx_tolower(h->value.data[0]); + c1 = ngx_tolower(h->value.data[1]); + + if (c0 == 'n' && c1 == 'o') { + u->buffering = 0; + } + + } else if (h->value.len == 3) { + c0 = ngx_tolower(h->value.data[0]); + c1 = ngx_tolower(h->value.data[1]); + c2 = ngx_tolower(h->value.data[2]); + + if (c0 == 'y' && c1 == 'e' && c2 == 's') { + u->buffering = 1; + } + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_process_charset(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + if (r->upstream->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_CHARSET) { + return NGX_OK; + } + + r->headers_out.override_charset = &h->value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + r->upstream->headers_in.connection = h; + + if (ngx_strlcasestrn(h->value.data, h->value.data + h->value.len, + (u_char *) "close", 5 - 1) + != NULL) + { + r->upstream->headers_in.connection_close = 1; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_process_transfer_encoding(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset) +{ + r->upstream->headers_in.transfer_encoding = h; + + if (ngx_strlcasestrn(h->value.data, h->value.data + h->value.len, + (u_char *) "chunked", 7 - 1) + != NULL) + { + r->upstream->headers_in.chunked = 1; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_process_vary(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset) +{ + ngx_http_upstream_t *u; + + u = r->upstream; + u->headers_in.vary = h; + +#if (NGX_HTTP_CACHE) + + if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_VARY) { + return NGX_OK; + } + + if (r->cache == NULL) { + return NGX_OK; + } + + if (h->value.len > NGX_HTTP_CACHE_VARY_LEN + || (h->value.len == 1 && h->value.data[0] == '*')) + { + u->cacheable = 0; + } + + r->cache->vary = h->value; + +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_copy_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + ngx_table_elt_t *ho, **ph; + + ho = ngx_list_push(&r->headers_out.headers); + if (ho == NULL) { + return NGX_ERROR; + } + + *ho = *h; + + if (offset) { + ph = (ngx_table_elt_t **) ((char *) &r->headers_out + offset); + *ph = ho; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_copy_multi_header_lines(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset) +{ + ngx_array_t *pa; + ngx_table_elt_t *ho, **ph; + + pa = (ngx_array_t *) ((char *) &r->headers_out + offset); + + if (pa->elts == NULL) { + if (ngx_array_init(pa, r->pool, 2, sizeof(ngx_table_elt_t *)) != NGX_OK) + { + return NGX_ERROR; + } + } + + ph = ngx_array_push(pa); + if (ph == NULL) { + return NGX_ERROR; + } + + ho = ngx_list_push(&r->headers_out.headers); + if (ho == NULL) { + return NGX_ERROR; + } + + *ho = *h; + *ph = ho; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_copy_content_type(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + u_char *p, *last; + + r->headers_out.content_type_len = h->value.len; + r->headers_out.content_type = h->value; + r->headers_out.content_type_lowcase = NULL; + + for (p = h->value.data; *p; p++) { + + if (*p != ';') { + continue; + } + + last = p; + + while (*++p == ' ') { /* void */ } + + if (*p == '\0') { + return NGX_OK; + } + + if (ngx_strncasecmp(p, (u_char *) "charset=", 8) != 0) { + continue; + } + + p += 8; + + r->headers_out.content_type_len = last - h->value.data; + + if (*p == '"') { + p++; + } + + last = h->value.data + h->value.len; + + if (*(last - 1) == '"') { + last--; + } + + r->headers_out.charset.len = last - p; + r->headers_out.charset.data = p; + + return NGX_OK; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_copy_last_modified(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + ngx_table_elt_t *ho; + + ho = ngx_list_push(&r->headers_out.headers); + if (ho == NULL) { + return NGX_ERROR; + } + + *ho = *h; + + r->headers_out.last_modified = ho; + +#if (NGX_HTTP_CACHE) + + if (r->upstream->cacheable) { + r->headers_out.last_modified_time = + r->upstream->headers_in.last_modified_time; + } + +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_rewrite_location(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + ngx_int_t rc; + ngx_table_elt_t *ho; + + ho = ngx_list_push(&r->headers_out.headers); + if (ho == NULL) { + return NGX_ERROR; + } + + *ho = *h; + + if (r->upstream->rewrite_redirect) { + rc = r->upstream->rewrite_redirect(r, ho, 0); + + if (rc == NGX_DECLINED) { + return NGX_OK; + } + + if (rc == NGX_OK) { + r->headers_out.location = ho; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "rewritten location: \"%V\"", &ho->value); + } + + return rc; + } + + if (ho->value.data[0] != '/') { + r->headers_out.location = ho; + } + + /* + * we do not set r->headers_out.location here to avoid handling + * relative redirects in ngx_http_header_filter() + */ + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_rewrite_refresh(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + u_char *p; + ngx_int_t rc; + ngx_table_elt_t *ho; + + ho = ngx_list_push(&r->headers_out.headers); + if (ho == NULL) { + return NGX_ERROR; + } + + *ho = *h; + + if (r->upstream->rewrite_redirect) { + + p = ngx_strcasestrn(ho->value.data, "url=", 4 - 1); + + if (p) { + rc = r->upstream->rewrite_redirect(r, ho, p + 4 - ho->value.data); + + } else { + return NGX_OK; + } + + if (rc == NGX_DECLINED) { + return NGX_OK; + } + + if (rc == NGX_OK) { + r->headers_out.refresh = ho; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "rewritten refresh: \"%V\"", &ho->value); + } + + return rc; + } + + r->headers_out.refresh = ho; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_rewrite_set_cookie(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + ngx_int_t rc; + ngx_table_elt_t *ho; + + ho = ngx_list_push(&r->headers_out.headers); + if (ho == NULL) { + return NGX_ERROR; + } + + *ho = *h; + + if (r->upstream->rewrite_cookie) { + rc = r->upstream->rewrite_cookie(r, ho); + + if (rc == NGX_DECLINED) { + return NGX_OK; + } + +#if (NGX_DEBUG) + if (rc == NGX_OK) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "rewritten cookie: \"%V\"", &ho->value); + } +#endif + + return rc; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_copy_allow_ranges(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset) +{ + ngx_table_elt_t *ho; + + if (r->upstream->conf->force_ranges) { + return NGX_OK; + } + +#if (NGX_HTTP_CACHE) + + if (r->cached) { + r->allow_ranges = 1; + return NGX_OK; + } + + if (r->upstream->cacheable) { + r->allow_ranges = 1; + r->single_range = 1; + return NGX_OK; + } + +#endif + + ho = ngx_list_push(&r->headers_out.headers); + if (ho == NULL) { + return NGX_ERROR; + } + + *ho = *h; + + r->headers_out.accept_ranges = ho; + + return NGX_OK; +} + + +#if (NGX_HTTP_GZIP) + +static ngx_int_t +ngx_http_upstream_copy_content_encoding(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset) +{ + ngx_table_elt_t *ho; + + ho = ngx_list_push(&r->headers_out.headers); + if (ho == NULL) { + return NGX_ERROR; + } + + *ho = *h; + + r->headers_out.content_encoding = ho; + + return NGX_OK; +} + +#endif + + +static ngx_int_t +ngx_http_upstream_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_upstream_vars; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_addr_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + size_t len; + ngx_uint_t i; + ngx_http_upstream_state_t *state; + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + if (r->upstream_states == NULL || r->upstream_states->nelts == 0) { + v->not_found = 1; + return NGX_OK; + } + + len = 0; + state = r->upstream_states->elts; + + for (i = 0; i < r->upstream_states->nelts; i++) { + if (state[i].peer) { + len += state[i].peer->len + 2; + + } else { + len += 3; + } + } + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + v->data = p; + + i = 0; + + for ( ;; ) { + if (state[i].peer) { + p = ngx_cpymem(p, state[i].peer->data, state[i].peer->len); + } + + if (++i == r->upstream_states->nelts) { + break; + } + + if (state[i].peer) { + *p++ = ','; + *p++ = ' '; + + } else { + *p++ = ' '; + *p++ = ':'; + *p++ = ' '; + + if (++i == r->upstream_states->nelts) { + break; + } + + continue; + } + } + + v->len = p - v->data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_status_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + size_t len; + ngx_uint_t i; + ngx_http_upstream_state_t *state; + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + if (r->upstream_states == NULL || r->upstream_states->nelts == 0) { + v->not_found = 1; + return NGX_OK; + } + + len = r->upstream_states->nelts * (3 + 2); + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + v->data = p; + + i = 0; + state = r->upstream_states->elts; + + for ( ;; ) { + if (state[i].status) { + p = ngx_sprintf(p, "%ui", state[i].status); + + } else { + *p++ = '-'; + } + + if (++i == r->upstream_states->nelts) { + break; + } + + if (state[i].peer) { + *p++ = ','; + *p++ = ' '; + + } else { + *p++ = ' '; + *p++ = ':'; + *p++ = ' '; + + if (++i == r->upstream_states->nelts) { + break; + } + + continue; + } + } + + v->len = p - v->data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_response_time_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + size_t len; + ngx_uint_t i; + ngx_msec_int_t ms; + ngx_http_upstream_state_t *state; + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + if (r->upstream_states == NULL || r->upstream_states->nelts == 0) { + v->not_found = 1; + return NGX_OK; + } + + len = r->upstream_states->nelts * (NGX_TIME_T_LEN + 4 + 2); + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + v->data = p; + + i = 0; + state = r->upstream_states->elts; + + for ( ;; ) { + if (state[i].status) { + + if (data == 1 && state[i].header_time != (ngx_msec_t) -1) { + ms = state[i].header_time; + + } else if (data == 2 && state[i].connect_time != (ngx_msec_t) -1) { + ms = state[i].connect_time; + + } else { + ms = state[i].response_time; + } + + ms = ngx_max(ms, 0); + p = ngx_sprintf(p, "%T.%03M", (time_t) ms / 1000, ms % 1000); + + } else { + *p++ = '-'; + } + + if (++i == r->upstream_states->nelts) { + break; + } + + if (state[i].peer) { + *p++ = ','; + *p++ = ' '; + + } else { + *p++ = ' '; + *p++ = ':'; + *p++ = ' '; + + if (++i == r->upstream_states->nelts) { + break; + } + + continue; + } + } + + v->len = p - v->data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_response_length_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + size_t len; + ngx_uint_t i; + ngx_http_upstream_state_t *state; + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + if (r->upstream_states == NULL || r->upstream_states->nelts == 0) { + v->not_found = 1; + return NGX_OK; + } + + len = r->upstream_states->nelts * (NGX_OFF_T_LEN + 2); + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + v->data = p; + + i = 0; + state = r->upstream_states->elts; + + for ( ;; ) { + + if (data == 1) { + p = ngx_sprintf(p, "%O", state[i].bytes_received); + + } else { + p = ngx_sprintf(p, "%O", state[i].response_length); + } + + if (++i == r->upstream_states->nelts) { + break; + } + + if (state[i].peer) { + *p++ = ','; + *p++ = ' '; + + } else { + *p++ = ' '; + *p++ = ':'; + *p++ = ' '; + + if (++i == r->upstream_states->nelts) { + break; + } + + continue; + } + } + + v->len = p - v->data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_header_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->upstream == NULL) { + v->not_found = 1; + return NGX_OK; + } + + return ngx_http_variable_unknown_header(v, (ngx_str_t *) data, + &r->upstream->headers_in.headers.part, + sizeof("upstream_http_") - 1); +} + + +static ngx_int_t +ngx_http_upstream_cookie_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_str_t *name = (ngx_str_t *) data; + + ngx_str_t cookie, s; + + if (r->upstream == NULL) { + v->not_found = 1; + return NGX_OK; + } + + s.len = name->len - (sizeof("upstream_cookie_") - 1); + s.data = name->data + sizeof("upstream_cookie_") - 1; + + if (ngx_http_parse_set_cookie_lines(&r->upstream->headers_in.cookies, + &s, &cookie) + == NGX_DECLINED) + { + v->not_found = 1; + return NGX_OK; + } + + v->len = cookie.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = cookie.data; + + return NGX_OK; +} + + +#if (NGX_HTTP_CACHE) + +static ngx_int_t +ngx_http_upstream_cache_status(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_uint_t n; + + if (r->upstream == NULL || r->upstream->cache_status == 0) { + v->not_found = 1; + return NGX_OK; + } + + n = r->upstream->cache_status - 1; + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->len = ngx_http_cache_status[n].len; + v->data = ngx_http_cache_status[n].data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_cache_last_modified(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + if (r->upstream == NULL + || !r->upstream->conf->cache_revalidate + || r->upstream->cache_status != NGX_HTTP_CACHE_EXPIRED + || r->cache->last_modified == -1) + { + v->not_found = 1; + return NGX_OK; + } + + p = ngx_pnalloc(r->pool, sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = ngx_http_time(p, r->cache->last_modified) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_cache_etag(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->upstream == NULL + || !r->upstream->conf->cache_revalidate + || r->upstream->cache_status != NGX_HTTP_CACHE_EXPIRED + || r->cache->etag.len == 0) + { + v->not_found = 1; + return NGX_OK; + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->len = r->cache->etag.len; + v->data = r->cache->etag.data; + + return NGX_OK; +} + +#endif + + +static char * +ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy) +{ + char *rv; + void *mconf; + ngx_str_t *value; + ngx_url_t u; + ngx_uint_t m; + ngx_conf_t pcf; + ngx_http_module_t *module; + ngx_http_conf_ctx_t *ctx, *http_ctx; + ngx_http_upstream_srv_conf_t *uscf; + + ngx_memzero(&u, sizeof(ngx_url_t)); + + value = cf->args->elts; + u.host = value[1]; + u.no_resolve = 1; + u.no_port = 1; + + uscf = ngx_http_upstream_add(cf, &u, 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); + if (uscf == NULL) { + return NGX_CONF_ERROR; + } + + + ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)); + if (ctx == NULL) { + return NGX_CONF_ERROR; + } + + http_ctx = cf->ctx; + ctx->main_conf = http_ctx->main_conf; + + /* the upstream{}'s srv_conf */ + + ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); + if (ctx->srv_conf == NULL) { + return NGX_CONF_ERROR; + } + + ctx->srv_conf[ngx_http_upstream_module.ctx_index] = uscf; + + uscf->srv_conf = ctx->srv_conf; + + + /* the upstream{}'s loc_conf */ + + ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); + if (ctx->loc_conf == NULL) { + return NGX_CONF_ERROR; + } + + for (m = 0; cf->cycle->modules[m]; m++) { + if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) { + continue; + } + + module = cf->cycle->modules[m]->ctx; + + if (module->create_srv_conf) { + mconf = module->create_srv_conf(cf); + if (mconf == NULL) { + return NGX_CONF_ERROR; + } + + ctx->srv_conf[cf->cycle->modules[m]->ctx_index] = mconf; + } + + if (module->create_loc_conf) { + mconf = module->create_loc_conf(cf); + if (mconf == NULL) { + return NGX_CONF_ERROR; + } + + ctx->loc_conf[cf->cycle->modules[m]->ctx_index] = mconf; + } + } + + uscf->servers = ngx_array_create(cf->pool, 4, + sizeof(ngx_http_upstream_server_t)); + if (uscf->servers == NULL) { + return NGX_CONF_ERROR; + } + + + /* parse inside upstream{} */ + + pcf = *cf; + cf->ctx = ctx; + cf->cmd_type = NGX_HTTP_UPS_CONF; + + rv = ngx_conf_parse(cf, NULL); + + *cf = pcf; + + if (rv != NGX_CONF_OK) { + return rv; + } + + if (uscf->servers->nelts == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "no servers are inside upstream"); + return NGX_CONF_ERROR; + } + + return rv; +} + + +static char * +ngx_http_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_upstream_srv_conf_t *uscf = conf; + + time_t fail_timeout; + ngx_str_t *value, s; + ngx_url_t u; + ngx_int_t weight, max_conns, max_fails; + ngx_uint_t i; + ngx_http_upstream_server_t *us; + + us = ngx_array_push(uscf->servers); + if (us == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(us, sizeof(ngx_http_upstream_server_t)); + + value = cf->args->elts; + + weight = 1; + max_conns = 0; + max_fails = 1; + fail_timeout = 10; + + for (i = 2; i < cf->args->nelts; i++) { + + if (ngx_strncmp(value[i].data, "weight=", 7) == 0) { + + if (!(uscf->flags & NGX_HTTP_UPSTREAM_WEIGHT)) { + goto not_supported; + } + + weight = ngx_atoi(&value[i].data[7], value[i].len - 7); + + if (weight == NGX_ERROR || weight == 0) { + goto invalid; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "max_conns=", 10) == 0) { + + if (!(uscf->flags & NGX_HTTP_UPSTREAM_MAX_CONNS)) { + goto not_supported; + } + + max_conns = ngx_atoi(&value[i].data[10], value[i].len - 10); + + if (max_conns == NGX_ERROR) { + goto invalid; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "max_fails=", 10) == 0) { + + if (!(uscf->flags & NGX_HTTP_UPSTREAM_MAX_FAILS)) { + goto not_supported; + } + + max_fails = ngx_atoi(&value[i].data[10], value[i].len - 10); + + if (max_fails == NGX_ERROR) { + goto invalid; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "fail_timeout=", 13) == 0) { + + if (!(uscf->flags & NGX_HTTP_UPSTREAM_FAIL_TIMEOUT)) { + goto not_supported; + } + + s.len = value[i].len - 13; + s.data = &value[i].data[13]; + + fail_timeout = ngx_parse_time(&s, 1); + + if (fail_timeout == (time_t) NGX_ERROR) { + goto invalid; + } + + continue; + } + + if (ngx_strcmp(value[i].data, "backup") == 0) { + + if (!(uscf->flags & NGX_HTTP_UPSTREAM_BACKUP)) { + goto not_supported; + } + + us->backup = 1; + + continue; + } + + if (ngx_strcmp(value[i].data, "down") == 0) { + + if (!(uscf->flags & NGX_HTTP_UPSTREAM_DOWN)) { + goto not_supported; + } + + us->down = 1; + + continue; + } + + goto invalid; + } + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = value[1]; + u.default_port = 80; + + if (ngx_parse_url(cf->pool, &u) != NGX_OK) { + if (u.err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in upstream \"%V\"", u.err, &u.url); + } + + return NGX_CONF_ERROR; + } + + us->name = u.url; + us->addrs = u.addrs; + us->naddrs = u.naddrs; + us->weight = weight; + us->max_conns = max_conns; + us->max_fails = max_fails; + us->fail_timeout = fail_timeout; + + return NGX_CONF_OK; + +invalid: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[i]); + + return NGX_CONF_ERROR; + +not_supported: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "balancing method does not support parameter \"%V\"", + &value[i]); + + return NGX_CONF_ERROR; +} + + +ngx_http_upstream_srv_conf_t * +ngx_http_upstream_add(ngx_conf_t *cf, ngx_url_t *u, ngx_uint_t flags) +{ + ngx_uint_t i; + ngx_http_upstream_server_t *us; + ngx_http_upstream_srv_conf_t *uscf, **uscfp; + ngx_http_upstream_main_conf_t *umcf; + + if (!(flags & NGX_HTTP_UPSTREAM_CREATE)) { + + if (ngx_parse_url(cf->pool, u) != NGX_OK) { + if (u->err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in upstream \"%V\"", u->err, &u->url); + } + + return NULL; + } + } + + umcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_upstream_module); + + uscfp = umcf->upstreams.elts; + + for (i = 0; i < umcf->upstreams.nelts; i++) { + + if (uscfp[i]->host.len != u->host.len + || ngx_strncasecmp(uscfp[i]->host.data, u->host.data, u->host.len) + != 0) + { + continue; + } + + if ((flags & NGX_HTTP_UPSTREAM_CREATE) + && (uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE)) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "duplicate upstream \"%V\"", &u->host); + return NULL; + } + + if ((uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE) && !u->no_port) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "upstream \"%V\" may not have port %d", + &u->host, u->port); + return NULL; + } + + if ((flags & NGX_HTTP_UPSTREAM_CREATE) && !uscfp[i]->no_port) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "upstream \"%V\" may not have port %d in %s:%ui", + &u->host, uscfp[i]->port, + uscfp[i]->file_name, uscfp[i]->line); + return NULL; + } + + if (uscfp[i]->port && u->port + && uscfp[i]->port != u->port) + { + continue; + } + + if (flags & NGX_HTTP_UPSTREAM_CREATE) { + uscfp[i]->flags = flags; + uscfp[i]->port = 0; + } + + return uscfp[i]; + } + + uscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_srv_conf_t)); + if (uscf == NULL) { + return NULL; + } + + uscf->flags = flags; + uscf->host = u->host; + uscf->file_name = cf->conf_file->file.name.data; + uscf->line = cf->conf_file->line; + uscf->port = u->port; + uscf->no_port = u->no_port; + + if (u->naddrs == 1 && (u->port || u->family == AF_UNIX)) { + uscf->servers = ngx_array_create(cf->pool, 1, + sizeof(ngx_http_upstream_server_t)); + if (uscf->servers == NULL) { + return NULL; + } + + us = ngx_array_push(uscf->servers); + if (us == NULL) { + return NULL; + } + + ngx_memzero(us, sizeof(ngx_http_upstream_server_t)); + + us->addrs = u->addrs; + us->naddrs = 1; + } + + uscfp = ngx_array_push(&umcf->upstreams); + if (uscfp == NULL) { + return NULL; + } + + *uscfp = uscf; + + return uscf; +} + + +char * +ngx_http_upstream_bind_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + char *p = conf; + + ngx_int_t rc; + ngx_str_t *value; + ngx_http_complex_value_t cv; + ngx_http_upstream_local_t **plocal, *local; + ngx_http_compile_complex_value_t ccv; + + plocal = (ngx_http_upstream_local_t **) (p + cmd->offset); + + if (*plocal != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + if (cf->args->nelts == 2 && ngx_strcmp(value[1].data, "off") == 0) { + *plocal = NULL; + return NGX_CONF_OK; + } + + 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; + } + + local = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_local_t)); + if (local == NULL) { + return NGX_CONF_ERROR; + } + + *plocal = local; + + if (cv.lengths) { + local->value = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); + if (local->value == NULL) { + return NGX_CONF_ERROR; + } + + *local->value = cv; + + } else { + local->addr = ngx_palloc(cf->pool, sizeof(ngx_addr_t)); + if (local->addr == NULL) { + return NGX_CONF_ERROR; + } + + rc = ngx_parse_addr_port(cf->pool, local->addr, value[1].data, + value[1].len); + + switch (rc) { + case NGX_OK: + local->addr->name = value[1]; + break; + + case NGX_DECLINED: + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid address \"%V\"", &value[1]); + /* fall through */ + + default: + return NGX_CONF_ERROR; + } + } + + if (cf->args->nelts > 2) { + if (ngx_strcmp(value[2].data, "transparent") == 0) { +#if (NGX_HAVE_TRANSPARENT_PROXY) + local->transparent = 1; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "transparent proxying is not supported " + "on this platform, ignored"); +#endif + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_http_upstream_set_local(ngx_http_request_t *r, ngx_http_upstream_t *u, + ngx_http_upstream_local_t *local) +{ + ngx_int_t rc; + ngx_str_t val; + ngx_addr_t *addr; + + if (local == NULL) { + u->peer.local = NULL; + return NGX_OK; + } + +#if (NGX_HAVE_TRANSPARENT_PROXY) + u->peer.transparent = local->transparent; +#endif + + if (local->value == NULL) { + u->peer.local = local->addr; + return NGX_OK; + } + + if (ngx_http_complex_value(r, local->value, &val) != NGX_OK) { + return NGX_ERROR; + } + + if (val.len == 0) { + return NGX_OK; + } + + addr = ngx_palloc(r->pool, sizeof(ngx_addr_t)); + if (addr == NULL) { + return NGX_ERROR; + } + + rc = ngx_parse_addr_port(r->pool, addr, val.data, val.len); + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "invalid local address \"%V\"", &val); + return NGX_OK; + } + + addr->name = val; + u->peer.local = addr; + + return NGX_OK; +} + + +char * +ngx_http_upstream_param_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + char *p = conf; + + ngx_str_t *value; + ngx_array_t **a; + ngx_http_upstream_param_t *param; + + a = (ngx_array_t **) (p + cmd->offset); + + if (*a == NULL) { + *a = ngx_array_create(cf->pool, 4, sizeof(ngx_http_upstream_param_t)); + if (*a == NULL) { + return NGX_CONF_ERROR; + } + } + + param = ngx_array_push(*a); + if (param == NULL) { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + param->key = value[1]; + param->value = value[2]; + param->skip_empty = 0; + + if (cf->args->nelts == 4) { + if (ngx_strcmp(value[3].data, "if_not_empty") != 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[3]); + return NGX_CONF_ERROR; + } + + param->skip_empty = 1; + } + + return NGX_CONF_OK; +} + + +ngx_int_t +ngx_http_upstream_hide_headers_hash(ngx_conf_t *cf, + ngx_http_upstream_conf_t *conf, ngx_http_upstream_conf_t *prev, + ngx_str_t *default_hide_headers, ngx_hash_init_t *hash) +{ + ngx_str_t *h; + ngx_uint_t i, j; + ngx_array_t hide_headers; + ngx_hash_key_t *hk; + + if (conf->hide_headers == NGX_CONF_UNSET_PTR + && conf->pass_headers == NGX_CONF_UNSET_PTR) + { + conf->hide_headers = prev->hide_headers; + conf->pass_headers = prev->pass_headers; + + conf->hide_headers_hash = prev->hide_headers_hash; + + if (conf->hide_headers_hash.buckets) { + return NGX_OK; + } + + } else { + if (conf->hide_headers == NGX_CONF_UNSET_PTR) { + conf->hide_headers = prev->hide_headers; + } + + if (conf->pass_headers == NGX_CONF_UNSET_PTR) { + conf->pass_headers = prev->pass_headers; + } + } + + if (ngx_array_init(&hide_headers, cf->temp_pool, 4, sizeof(ngx_hash_key_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + for (h = default_hide_headers; h->len; h++) { + hk = ngx_array_push(&hide_headers); + if (hk == NULL) { + return NGX_ERROR; + } + + hk->key = *h; + hk->key_hash = ngx_hash_key_lc(h->data, h->len); + hk->value = (void *) 1; + } + + if (conf->hide_headers != NGX_CONF_UNSET_PTR) { + + h = conf->hide_headers->elts; + + for (i = 0; i < conf->hide_headers->nelts; i++) { + + hk = hide_headers.elts; + + for (j = 0; j < hide_headers.nelts; j++) { + if (ngx_strcasecmp(h[i].data, hk[j].key.data) == 0) { + goto exist; + } + } + + hk = ngx_array_push(&hide_headers); + if (hk == NULL) { + return NGX_ERROR; + } + + hk->key = h[i]; + hk->key_hash = ngx_hash_key_lc(h[i].data, h[i].len); + hk->value = (void *) 1; + + exist: + + continue; + } + } + + if (conf->pass_headers != NGX_CONF_UNSET_PTR) { + + h = conf->pass_headers->elts; + hk = hide_headers.elts; + + for (i = 0; i < conf->pass_headers->nelts; i++) { + for (j = 0; j < hide_headers.nelts; j++) { + + if (hk[j].key.data == NULL) { + continue; + } + + if (ngx_strcasecmp(h[i].data, hk[j].key.data) == 0) { + hk[j].key.data = NULL; + break; + } + } + } + } + + hash->hash = &conf->hide_headers_hash; + hash->key = ngx_hash_key_lc; + hash->pool = cf->pool; + hash->temp_pool = NULL; + + if (ngx_hash_init(hash, hide_headers.elts, hide_headers.nelts) != NGX_OK) { + return NGX_ERROR; + } + + /* + * special handling to preserve conf->hide_headers_hash + * in the "http" section to inherit it to all servers + */ + + if (prev->hide_headers_hash.buckets == NULL + && conf->hide_headers == prev->hide_headers + && conf->pass_headers == prev->pass_headers) + { + prev->hide_headers_hash = conf->hide_headers_hash; + } + + return NGX_OK; +} + + +static void * +ngx_http_upstream_create_main_conf(ngx_conf_t *cf) +{ + ngx_http_upstream_main_conf_t *umcf; + + umcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_main_conf_t)); + if (umcf == NULL) { + return NULL; + } + + if (ngx_array_init(&umcf->upstreams, cf->pool, 4, + sizeof(ngx_http_upstream_srv_conf_t *)) + != NGX_OK) + { + return NULL; + } + + return umcf; +} + + +static char * +ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf) +{ + ngx_http_upstream_main_conf_t *umcf = conf; + + ngx_uint_t i; + ngx_array_t headers_in; + ngx_hash_key_t *hk; + ngx_hash_init_t hash; + ngx_http_upstream_init_pt init; + ngx_http_upstream_header_t *header; + ngx_http_upstream_srv_conf_t **uscfp; + + uscfp = umcf->upstreams.elts; + + for (i = 0; i < umcf->upstreams.nelts; i++) { + + init = uscfp[i]->peer.init_upstream ? uscfp[i]->peer.init_upstream: + ngx_http_upstream_init_round_robin; + + if (init(cf, uscfp[i]) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + + /* upstream_headers_in_hash */ + + if (ngx_array_init(&headers_in, cf->temp_pool, 32, sizeof(ngx_hash_key_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + for (header = ngx_http_upstream_headers_in; header->name.len; header++) { + hk = ngx_array_push(&headers_in); + if (hk == NULL) { + return NGX_CONF_ERROR; + } + + hk->key = header->name; + hk->key_hash = ngx_hash_key_lc(header->name.data, header->name.len); + hk->value = header; + } + + hash.hash = &umcf->headers_in_hash; + hash.key = ngx_hash_key_lc; + hash.max_size = 512; + hash.bucket_size = ngx_align(64, ngx_cacheline_size); + hash.name = "upstream_headers_in_hash"; + hash.pool = cf->pool; + hash.temp_pool = NULL; + + if (ngx_hash_init(&hash, headers_in.elts, headers_in.nelts) != NGX_OK) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} diff --git a/app/nginx/src/http/ngx_http_upstream.h b/app/nginx/src/http/ngx_http_upstream.h new file mode 100644 index 0000000..c552ac0 --- /dev/null +++ b/app/nginx/src/http/ngx_http_upstream.h @@ -0,0 +1,430 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_UPSTREAM_H_INCLUDED_ +#define _NGX_HTTP_UPSTREAM_H_INCLUDED_ + + +#include +#include +#include +#include +#include +#include + + +#define NGX_HTTP_UPSTREAM_FT_ERROR 0x00000002 +#define NGX_HTTP_UPSTREAM_FT_TIMEOUT 0x00000004 +#define NGX_HTTP_UPSTREAM_FT_INVALID_HEADER 0x00000008 +#define NGX_HTTP_UPSTREAM_FT_HTTP_500 0x00000010 +#define NGX_HTTP_UPSTREAM_FT_HTTP_502 0x00000020 +#define NGX_HTTP_UPSTREAM_FT_HTTP_503 0x00000040 +#define NGX_HTTP_UPSTREAM_FT_HTTP_504 0x00000080 +#define NGX_HTTP_UPSTREAM_FT_HTTP_403 0x00000100 +#define NGX_HTTP_UPSTREAM_FT_HTTP_404 0x00000200 +#define NGX_HTTP_UPSTREAM_FT_HTTP_429 0x00000400 +#define NGX_HTTP_UPSTREAM_FT_UPDATING 0x00000800 +#define NGX_HTTP_UPSTREAM_FT_BUSY_LOCK 0x00001000 +#define NGX_HTTP_UPSTREAM_FT_MAX_WAITING 0x00002000 +#define NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT 0x00004000 +#define NGX_HTTP_UPSTREAM_FT_NOLIVE 0x40000000 +#define NGX_HTTP_UPSTREAM_FT_OFF 0x80000000 + +#define NGX_HTTP_UPSTREAM_FT_STATUS (NGX_HTTP_UPSTREAM_FT_HTTP_500 \ + |NGX_HTTP_UPSTREAM_FT_HTTP_502 \ + |NGX_HTTP_UPSTREAM_FT_HTTP_503 \ + |NGX_HTTP_UPSTREAM_FT_HTTP_504 \ + |NGX_HTTP_UPSTREAM_FT_HTTP_403 \ + |NGX_HTTP_UPSTREAM_FT_HTTP_404 \ + |NGX_HTTP_UPSTREAM_FT_HTTP_429) + +#define NGX_HTTP_UPSTREAM_INVALID_HEADER 40 + + +#define NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT 0x00000002 +#define NGX_HTTP_UPSTREAM_IGN_XA_EXPIRES 0x00000004 +#define NGX_HTTP_UPSTREAM_IGN_EXPIRES 0x00000008 +#define NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL 0x00000010 +#define NGX_HTTP_UPSTREAM_IGN_SET_COOKIE 0x00000020 +#define NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE 0x00000040 +#define NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING 0x00000080 +#define NGX_HTTP_UPSTREAM_IGN_XA_CHARSET 0x00000100 +#define NGX_HTTP_UPSTREAM_IGN_VARY 0x00000200 + + +typedef struct { + ngx_uint_t status; + ngx_msec_t response_time; + ngx_msec_t connect_time; + ngx_msec_t header_time; + off_t response_length; + off_t bytes_received; + + ngx_str_t *peer; +} ngx_http_upstream_state_t; + + +typedef struct { + ngx_hash_t headers_in_hash; + ngx_array_t upstreams; + /* ngx_http_upstream_srv_conf_t */ +} ngx_http_upstream_main_conf_t; + +typedef struct ngx_http_upstream_srv_conf_s ngx_http_upstream_srv_conf_t; + +typedef ngx_int_t (*ngx_http_upstream_init_pt)(ngx_conf_t *cf, + ngx_http_upstream_srv_conf_t *us); +typedef ngx_int_t (*ngx_http_upstream_init_peer_pt)(ngx_http_request_t *r, + ngx_http_upstream_srv_conf_t *us); + + +typedef struct { + ngx_http_upstream_init_pt init_upstream; + ngx_http_upstream_init_peer_pt init; + void *data; +} ngx_http_upstream_peer_t; + + +typedef struct { + ngx_str_t name; + ngx_addr_t *addrs; + ngx_uint_t naddrs; + ngx_uint_t weight; + ngx_uint_t max_conns; + ngx_uint_t max_fails; + time_t fail_timeout; + ngx_msec_t slow_start; + + unsigned down:1; + unsigned backup:1; + + NGX_COMPAT_BEGIN(6) + NGX_COMPAT_END +} ngx_http_upstream_server_t; + + +#define NGX_HTTP_UPSTREAM_CREATE 0x0001 +#define NGX_HTTP_UPSTREAM_WEIGHT 0x0002 +#define NGX_HTTP_UPSTREAM_MAX_FAILS 0x0004 +#define NGX_HTTP_UPSTREAM_FAIL_TIMEOUT 0x0008 +#define NGX_HTTP_UPSTREAM_DOWN 0x0010 +#define NGX_HTTP_UPSTREAM_BACKUP 0x0020 +#define NGX_HTTP_UPSTREAM_MAX_CONNS 0x0100 + + +struct ngx_http_upstream_srv_conf_s { + ngx_http_upstream_peer_t peer; + void **srv_conf; + + ngx_array_t *servers; /* ngx_http_upstream_server_t */ + + ngx_uint_t flags; + ngx_str_t host; + u_char *file_name; + ngx_uint_t line; + in_port_t port; + ngx_uint_t no_port; /* unsigned no_port:1 */ + +#if (NGX_HTTP_UPSTREAM_ZONE) + ngx_shm_zone_t *shm_zone; +#endif +}; + + +typedef struct { + ngx_addr_t *addr; + ngx_http_complex_value_t *value; +#if (NGX_HAVE_TRANSPARENT_PROXY) + ngx_uint_t transparent; /* unsigned transparent:1; */ +#endif +} ngx_http_upstream_local_t; + + +typedef struct { + ngx_http_upstream_srv_conf_t *upstream; + + ngx_msec_t connect_timeout; + ngx_msec_t send_timeout; + ngx_msec_t read_timeout; + ngx_msec_t next_upstream_timeout; + + size_t send_lowat; + size_t buffer_size; + size_t limit_rate; + + size_t busy_buffers_size; + size_t max_temp_file_size; + size_t temp_file_write_size; + + size_t busy_buffers_size_conf; + size_t max_temp_file_size_conf; + size_t temp_file_write_size_conf; + + ngx_bufs_t bufs; + + ngx_uint_t ignore_headers; + ngx_uint_t next_upstream; + ngx_uint_t store_access; + ngx_uint_t next_upstream_tries; + ngx_flag_t buffering; + ngx_flag_t request_buffering; + ngx_flag_t pass_request_headers; + ngx_flag_t pass_request_body; + + ngx_flag_t ignore_client_abort; + ngx_flag_t intercept_errors; + ngx_flag_t cyclic_temp_file; + ngx_flag_t force_ranges; + + ngx_path_t *temp_path; + + ngx_hash_t hide_headers_hash; + ngx_array_t *hide_headers; + ngx_array_t *pass_headers; + + ngx_http_upstream_local_t *local; + +#if (NGX_HTTP_CACHE) + ngx_shm_zone_t *cache_zone; + ngx_http_complex_value_t *cache_value; + + ngx_uint_t cache_min_uses; + ngx_uint_t cache_use_stale; + ngx_uint_t cache_methods; + + off_t cache_max_range_offset; + + ngx_flag_t cache_lock; + ngx_msec_t cache_lock_timeout; + ngx_msec_t cache_lock_age; + + ngx_flag_t cache_revalidate; + ngx_flag_t cache_convert_head; + ngx_flag_t cache_background_update; + + ngx_array_t *cache_valid; + ngx_array_t *cache_bypass; + ngx_array_t *cache_purge; + ngx_array_t *no_cache; +#endif + + ngx_array_t *store_lengths; + ngx_array_t *store_values; + +#if (NGX_HTTP_CACHE) + signed cache:2; +#endif + signed store:2; + unsigned intercept_404:1; + unsigned change_buffering:1; + +#if (NGX_HTTP_SSL || NGX_COMPAT) + ngx_ssl_t *ssl; + ngx_flag_t ssl_session_reuse; + + ngx_http_complex_value_t *ssl_name; + ngx_flag_t ssl_server_name; + ngx_flag_t ssl_verify; +#endif + + ngx_str_t module; + + NGX_COMPAT_BEGIN(2) + NGX_COMPAT_END +} ngx_http_upstream_conf_t; + + +typedef struct { + ngx_str_t name; + ngx_http_header_handler_pt handler; + ngx_uint_t offset; + ngx_http_header_handler_pt copy_handler; + ngx_uint_t conf; + ngx_uint_t redirect; /* unsigned redirect:1; */ +} ngx_http_upstream_header_t; + + +typedef struct { + ngx_list_t headers; + + ngx_uint_t status_n; + ngx_str_t status_line; + + ngx_table_elt_t *status; + ngx_table_elt_t *date; + ngx_table_elt_t *server; + ngx_table_elt_t *connection; + + ngx_table_elt_t *expires; + ngx_table_elt_t *etag; + ngx_table_elt_t *x_accel_expires; + ngx_table_elt_t *x_accel_redirect; + ngx_table_elt_t *x_accel_limit_rate; + + ngx_table_elt_t *content_type; + ngx_table_elt_t *content_length; + + ngx_table_elt_t *last_modified; + ngx_table_elt_t *location; + ngx_table_elt_t *accept_ranges; + ngx_table_elt_t *www_authenticate; + ngx_table_elt_t *transfer_encoding; + ngx_table_elt_t *vary; + +#if (NGX_HTTP_GZIP) + ngx_table_elt_t *content_encoding; +#endif + + ngx_array_t cache_control; + ngx_array_t cookies; + + off_t content_length_n; + time_t last_modified_time; + + unsigned connection_close:1; + unsigned chunked:1; +} ngx_http_upstream_headers_in_t; + + +typedef struct { + ngx_str_t host; + in_port_t port; + ngx_uint_t no_port; /* unsigned no_port:1 */ + + ngx_uint_t naddrs; + ngx_resolver_addr_t *addrs; + + struct sockaddr *sockaddr; + socklen_t socklen; + ngx_str_t name; + + ngx_resolver_ctx_t *ctx; +} ngx_http_upstream_resolved_t; + + +typedef void (*ngx_http_upstream_handler_pt)(ngx_http_request_t *r, + ngx_http_upstream_t *u); + + +struct ngx_http_upstream_s { + ngx_http_upstream_handler_pt read_event_handler; + ngx_http_upstream_handler_pt write_event_handler; + + ngx_peer_connection_t peer; + + ngx_event_pipe_t *pipe; + + ngx_chain_t *request_bufs; + + ngx_output_chain_ctx_t output; + ngx_chain_writer_ctx_t writer; + + ngx_http_upstream_conf_t *conf; + ngx_http_upstream_srv_conf_t *upstream; +#if (NGX_HTTP_CACHE) + ngx_array_t *caches; +#endif + + ngx_http_upstream_headers_in_t headers_in; + + ngx_http_upstream_resolved_t *resolved; + + ngx_buf_t from_client; + + ngx_buf_t buffer; + off_t length; + + ngx_chain_t *out_bufs; + ngx_chain_t *busy_bufs; + ngx_chain_t *free_bufs; + + ngx_int_t (*input_filter_init)(void *data); + ngx_int_t (*input_filter)(void *data, ssize_t bytes); + void *input_filter_ctx; + +#if (NGX_HTTP_CACHE) + ngx_int_t (*create_key)(ngx_http_request_t *r); +#endif + ngx_int_t (*create_request)(ngx_http_request_t *r); + ngx_int_t (*reinit_request)(ngx_http_request_t *r); + ngx_int_t (*process_header)(ngx_http_request_t *r); + void (*abort_request)(ngx_http_request_t *r); + void (*finalize_request)(ngx_http_request_t *r, + ngx_int_t rc); + ngx_int_t (*rewrite_redirect)(ngx_http_request_t *r, + ngx_table_elt_t *h, size_t prefix); + ngx_int_t (*rewrite_cookie)(ngx_http_request_t *r, + ngx_table_elt_t *h); + + ngx_msec_t timeout; + + ngx_http_upstream_state_t *state; + + ngx_str_t method; + ngx_str_t schema; + ngx_str_t uri; + +#if (NGX_HTTP_SSL || NGX_COMPAT) + ngx_str_t ssl_name; +#endif + + ngx_http_cleanup_pt *cleanup; + + unsigned store:1; + unsigned cacheable:1; + unsigned accel:1; + unsigned ssl:1; +#if (NGX_HTTP_CACHE) + unsigned cache_status:3; +#endif + + unsigned buffering:1; + unsigned keepalive:1; + unsigned upgrade:1; + + unsigned request_sent:1; + unsigned request_body_sent:1; + unsigned header_sent:1; +}; + + +typedef struct { + ngx_uint_t status; + ngx_uint_t mask; +} ngx_http_upstream_next_t; + + +typedef struct { + ngx_str_t key; + ngx_str_t value; + ngx_uint_t skip_empty; +} ngx_http_upstream_param_t; + + +ngx_int_t ngx_http_upstream_create(ngx_http_request_t *r); +void ngx_http_upstream_init(ngx_http_request_t *r); +ngx_http_upstream_srv_conf_t *ngx_http_upstream_add(ngx_conf_t *cf, + ngx_url_t *u, ngx_uint_t flags); +char *ngx_http_upstream_bind_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +char *ngx_http_upstream_param_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +ngx_int_t ngx_http_upstream_hide_headers_hash(ngx_conf_t *cf, + ngx_http_upstream_conf_t *conf, ngx_http_upstream_conf_t *prev, + ngx_str_t *default_hide_headers, ngx_hash_init_t *hash); + + +#define ngx_http_conf_upstream_srv_conf(uscf, module) \ + uscf->srv_conf[module.ctx_index] + + +extern ngx_module_t ngx_http_upstream_module; +extern ngx_conf_bitmask_t ngx_http_upstream_cache_method_mask[]; +extern ngx_conf_bitmask_t ngx_http_upstream_ignore_headers_masks[]; + + +#endif /* _NGX_HTTP_UPSTREAM_H_INCLUDED_ */ diff --git a/app/nginx/src/http/ngx_http_upstream_round_robin.c b/app/nginx/src/http/ngx_http_upstream_round_robin.c new file mode 100644 index 0000000..f6051ae --- /dev/null +++ b/app/nginx/src/http/ngx_http_upstream_round_robin.c @@ -0,0 +1,842 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define ngx_http_upstream_tries(p) ((p)->number \ + + ((p)->next ? (p)->next->number : 0)) + + +static ngx_http_upstream_rr_peer_t *ngx_http_upstream_get_peer( + ngx_http_upstream_rr_peer_data_t *rrp); + +#if (NGX_HTTP_SSL) + +static ngx_int_t ngx_http_upstream_empty_set_session(ngx_peer_connection_t *pc, + void *data); +static void ngx_http_upstream_empty_save_session(ngx_peer_connection_t *pc, + void *data); + +#endif + + +ngx_int_t +ngx_http_upstream_init_round_robin(ngx_conf_t *cf, + ngx_http_upstream_srv_conf_t *us) +{ + ngx_url_t u; + ngx_uint_t i, j, n, w; + ngx_http_upstream_server_t *server; + ngx_http_upstream_rr_peer_t *peer, **peerp; + ngx_http_upstream_rr_peers_t *peers, *backup; + + us->peer.init = ngx_http_upstream_init_round_robin_peer; + + if (us->servers) { + server = us->servers->elts; + + n = 0; + w = 0; + + for (i = 0; i < us->servers->nelts; i++) { + if (server[i].backup) { + continue; + } + + n += server[i].naddrs; + w += server[i].naddrs * server[i].weight; + } + + if (n == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no servers in upstream \"%V\" in %s:%ui", + &us->host, us->file_name, us->line); + return NGX_ERROR; + } + + peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)); + if (peers == NULL) { + return NGX_ERROR; + } + + peer = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peer_t) * n); + if (peer == NULL) { + return NGX_ERROR; + } + + peers->single = (n == 1); + peers->number = n; + peers->weighted = (w != n); + peers->total_weight = w; + peers->name = &us->host; + + n = 0; + peerp = &peers->peer; + + for (i = 0; i < us->servers->nelts; i++) { + if (server[i].backup) { + continue; + } + + for (j = 0; j < server[i].naddrs; j++) { + peer[n].sockaddr = server[i].addrs[j].sockaddr; + peer[n].socklen = server[i].addrs[j].socklen; + peer[n].name = server[i].addrs[j].name; + peer[n].weight = server[i].weight; + peer[n].effective_weight = server[i].weight; + peer[n].current_weight = 0; + peer[n].max_conns = server[i].max_conns; + peer[n].max_fails = server[i].max_fails; + peer[n].fail_timeout = server[i].fail_timeout; + peer[n].down = server[i].down; + peer[n].server = server[i].name; + + *peerp = &peer[n]; + peerp = &peer[n].next; + n++; + } + } + + us->peer.data = peers; + + /* backup servers */ + + n = 0; + w = 0; + + for (i = 0; i < us->servers->nelts; i++) { + if (!server[i].backup) { + continue; + } + + n += server[i].naddrs; + w += server[i].naddrs * server[i].weight; + } + + if (n == 0) { + return NGX_OK; + } + + backup = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)); + if (backup == NULL) { + return NGX_ERROR; + } + + peer = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peer_t) * n); + if (peer == NULL) { + return NGX_ERROR; + } + + peers->single = 0; + backup->single = 0; + backup->number = n; + backup->weighted = (w != n); + backup->total_weight = w; + backup->name = &us->host; + + n = 0; + peerp = &backup->peer; + + for (i = 0; i < us->servers->nelts; i++) { + if (!server[i].backup) { + continue; + } + + for (j = 0; j < server[i].naddrs; j++) { + peer[n].sockaddr = server[i].addrs[j].sockaddr; + peer[n].socklen = server[i].addrs[j].socklen; + peer[n].name = server[i].addrs[j].name; + peer[n].weight = server[i].weight; + peer[n].effective_weight = server[i].weight; + peer[n].current_weight = 0; + peer[n].max_conns = server[i].max_conns; + peer[n].max_fails = server[i].max_fails; + peer[n].fail_timeout = server[i].fail_timeout; + peer[n].down = server[i].down; + peer[n].server = server[i].name; + + *peerp = &peer[n]; + peerp = &peer[n].next; + n++; + } + } + + peers->next = backup; + + return NGX_OK; + } + + + /* an upstream implicitly defined by proxy_pass, etc. */ + + if (us->port == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no port in upstream \"%V\" in %s:%ui", + &us->host, us->file_name, us->line); + return NGX_ERROR; + } + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.host = us->host; + u.port = us->port; + + if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) { + if (u.err) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "%s in upstream \"%V\" in %s:%ui", + u.err, &us->host, us->file_name, us->line); + } + + return NGX_ERROR; + } + + n = u.naddrs; + + peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)); + if (peers == NULL) { + return NGX_ERROR; + } + + peer = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peer_t) * n); + if (peer == NULL) { + return NGX_ERROR; + } + + peers->single = (n == 1); + peers->number = n; + peers->weighted = 0; + peers->total_weight = n; + peers->name = &us->host; + + peerp = &peers->peer; + + for (i = 0; i < u.naddrs; i++) { + peer[i].sockaddr = u.addrs[i].sockaddr; + peer[i].socklen = u.addrs[i].socklen; + peer[i].name = u.addrs[i].name; + peer[i].weight = 1; + peer[i].effective_weight = 1; + peer[i].current_weight = 0; + peer[i].max_conns = 0; + peer[i].max_fails = 1; + peer[i].fail_timeout = 10; + *peerp = &peer[i]; + peerp = &peer[i].next; + } + + us->peer.data = peers; + + /* implicitly defined upstream has no backup servers */ + + return NGX_OK; +} + + +ngx_int_t +ngx_http_upstream_init_round_robin_peer(ngx_http_request_t *r, + ngx_http_upstream_srv_conf_t *us) +{ + ngx_uint_t n; + ngx_http_upstream_rr_peer_data_t *rrp; + + rrp = r->upstream->peer.data; + + if (rrp == NULL) { + rrp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_rr_peer_data_t)); + if (rrp == NULL) { + return NGX_ERROR; + } + + r->upstream->peer.data = rrp; + } + + rrp->peers = us->peer.data; + rrp->current = NULL; + rrp->config = 0; + + n = rrp->peers->number; + + if (rrp->peers->next && rrp->peers->next->number > n) { + n = rrp->peers->next->number; + } + + if (n <= 8 * sizeof(uintptr_t)) { + rrp->tried = &rrp->data; + rrp->data = 0; + + } else { + n = (n + (8 * sizeof(uintptr_t) - 1)) / (8 * sizeof(uintptr_t)); + + rrp->tried = ngx_pcalloc(r->pool, n * sizeof(uintptr_t)); + if (rrp->tried == NULL) { + return NGX_ERROR; + } + } + + r->upstream->peer.get = ngx_http_upstream_get_round_robin_peer; + r->upstream->peer.free = ngx_http_upstream_free_round_robin_peer; + r->upstream->peer.tries = ngx_http_upstream_tries(rrp->peers); +#if (NGX_HTTP_SSL) + r->upstream->peer.set_session = + ngx_http_upstream_set_round_robin_peer_session; + r->upstream->peer.save_session = + ngx_http_upstream_save_round_robin_peer_session; +#endif + + return NGX_OK; +} + + +ngx_int_t +ngx_http_upstream_create_round_robin_peer(ngx_http_request_t *r, + ngx_http_upstream_resolved_t *ur) +{ + u_char *p; + size_t len; + socklen_t socklen; + ngx_uint_t i, n; + struct sockaddr *sockaddr; + ngx_http_upstream_rr_peer_t *peer, **peerp; + ngx_http_upstream_rr_peers_t *peers; + ngx_http_upstream_rr_peer_data_t *rrp; + + rrp = r->upstream->peer.data; + + if (rrp == NULL) { + rrp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_rr_peer_data_t)); + if (rrp == NULL) { + return NGX_ERROR; + } + + r->upstream->peer.data = rrp; + } + + peers = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_rr_peers_t)); + if (peers == NULL) { + return NGX_ERROR; + } + + peer = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_rr_peer_t) + * ur->naddrs); + if (peer == NULL) { + return NGX_ERROR; + } + + peers->single = (ur->naddrs == 1); + peers->number = ur->naddrs; + peers->name = &ur->host; + + if (ur->sockaddr) { + peer[0].sockaddr = ur->sockaddr; + peer[0].socklen = ur->socklen; + peer[0].name = ur->name.data ? ur->name : ur->host; + peer[0].weight = 1; + peer[0].effective_weight = 1; + peer[0].current_weight = 0; + peer[0].max_conns = 0; + peer[0].max_fails = 1; + peer[0].fail_timeout = 10; + peers->peer = peer; + + } else { + peerp = &peers->peer; + + for (i = 0; i < ur->naddrs; i++) { + + socklen = ur->addrs[i].socklen; + + sockaddr = ngx_palloc(r->pool, socklen); + if (sockaddr == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(sockaddr, ur->addrs[i].sockaddr, socklen); + ngx_inet_set_port(sockaddr, ur->port); + + p = ngx_pnalloc(r->pool, NGX_SOCKADDR_STRLEN); + if (p == NULL) { + return NGX_ERROR; + } + + len = ngx_sock_ntop(sockaddr, socklen, p, NGX_SOCKADDR_STRLEN, 1); + + peer[i].sockaddr = sockaddr; + peer[i].socklen = socklen; + peer[i].name.len = len; + peer[i].name.data = p; + peer[i].weight = 1; + peer[i].effective_weight = 1; + peer[i].current_weight = 0; + peer[i].max_conns = 0; + peer[i].max_fails = 1; + peer[i].fail_timeout = 10; + *peerp = &peer[i]; + peerp = &peer[i].next; + } + } + + rrp->peers = peers; + rrp->current = NULL; + rrp->config = 0; + + if (rrp->peers->number <= 8 * sizeof(uintptr_t)) { + rrp->tried = &rrp->data; + rrp->data = 0; + + } else { + n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1)) + / (8 * sizeof(uintptr_t)); + + rrp->tried = ngx_pcalloc(r->pool, n * sizeof(uintptr_t)); + if (rrp->tried == NULL) { + return NGX_ERROR; + } + } + + r->upstream->peer.get = ngx_http_upstream_get_round_robin_peer; + r->upstream->peer.free = ngx_http_upstream_free_round_robin_peer; + r->upstream->peer.tries = ngx_http_upstream_tries(rrp->peers); +#if (NGX_HTTP_SSL) + r->upstream->peer.set_session = ngx_http_upstream_empty_set_session; + r->upstream->peer.save_session = ngx_http_upstream_empty_save_session; +#endif + + return NGX_OK; +} + + +ngx_int_t +ngx_http_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, void *data) +{ + ngx_http_upstream_rr_peer_data_t *rrp = data; + + ngx_int_t rc; + ngx_uint_t i, n; + ngx_http_upstream_rr_peer_t *peer; + ngx_http_upstream_rr_peers_t *peers; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "get rr peer, try: %ui", pc->tries); + + pc->cached = 0; + pc->connection = NULL; + + peers = rrp->peers; + ngx_http_upstream_rr_peers_wlock(peers); + + if (peers->single) { + peer = peers->peer; + + if (peer->down) { + goto failed; + } + + if (peer->max_conns && peer->conns >= peer->max_conns) { + goto failed; + } + + rrp->current = peer; + + } else { + + /* there are several peers */ + + peer = ngx_http_upstream_get_peer(rrp); + + if (peer == NULL) { + goto failed; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "get rr peer, current: %p %i", + peer, peer->current_weight); + } + + pc->sockaddr = peer->sockaddr; + pc->socklen = peer->socklen; + pc->name = &peer->name; + + peer->conns++; + + ngx_http_upstream_rr_peers_unlock(peers); + + return NGX_OK; + +failed: + + if (peers->next) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, "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_round_robin_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 ngx_http_upstream_rr_peer_t * +ngx_http_upstream_get_peer(ngx_http_upstream_rr_peer_data_t *rrp) +{ + time_t now; + uintptr_t m; + ngx_int_t total; + ngx_uint_t i, n, p; + ngx_http_upstream_rr_peer_t *peer, *best; + + now = ngx_time(); + + best = NULL; + total = 0; + +#if (NGX_SUPPRESS_WARN) + p = 0; +#endif + + for (peer = 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 (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; + } + + 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; + p = i; + } + } + + if (best == NULL) { + return NULL; + } + + rrp->current = best; + + n = p / (8 * sizeof(uintptr_t)); + m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t)); + + rrp->tried[n] |= m; + + best->current_weight -= total; + + if (now - best->checked > best->fail_timeout) { + best->checked = now; + } + + return best; +} + + +void +ngx_http_upstream_free_round_robin_peer(ngx_peer_connection_t *pc, void *data, + ngx_uint_t state) +{ + ngx_http_upstream_rr_peer_data_t *rrp = data; + + time_t now; + ngx_http_upstream_rr_peer_t *peer; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "free rr peer %ui %ui", pc->tries, state); + + /* TODO: NGX_PEER_KEEPALIVE */ + + peer = rrp->current; + + ngx_http_upstream_rr_peers_rlock(rrp->peers); + ngx_http_upstream_rr_peer_lock(rrp->peers, peer); + + if (rrp->peers->single) { + + peer->conns--; + + ngx_http_upstream_rr_peer_unlock(rrp->peers, peer); + ngx_http_upstream_rr_peers_unlock(rrp->peers); + + pc->tries = 0; + return; + } + + if (state & NGX_PEER_FAILED) { + now = ngx_time(); + + peer->fails++; + peer->accessed = now; + peer->checked = now; + + if (peer->max_fails) { + peer->effective_weight -= peer->weight / peer->max_fails; + + if (peer->fails >= peer->max_fails) { + ngx_log_error(NGX_LOG_WARN, pc->log, 0, + "upstream server temporarily disabled"); + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "free rr peer failed: %p %i", + peer, peer->effective_weight); + + if (peer->effective_weight < 0) { + peer->effective_weight = 0; + } + + } else { + + /* mark peer live if check passed */ + + if (peer->accessed < peer->checked) { + peer->fails = 0; + } + } + + peer->conns--; + + ngx_http_upstream_rr_peer_unlock(rrp->peers, peer); + ngx_http_upstream_rr_peers_unlock(rrp->peers); + + if (pc->tries) { + pc->tries--; + } +} + + +#if (NGX_HTTP_SSL) + +ngx_int_t +ngx_http_upstream_set_round_robin_peer_session(ngx_peer_connection_t *pc, + void *data) +{ + ngx_http_upstream_rr_peer_data_t *rrp = data; + + ngx_int_t rc; + ngx_ssl_session_t *ssl_session; + ngx_http_upstream_rr_peer_t *peer; +#if (NGX_HTTP_UPSTREAM_ZONE) + int len; +#if OPENSSL_VERSION_NUMBER >= 0x0090707fL + const +#endif + u_char *p; + ngx_http_upstream_rr_peers_t *peers; + u_char buf[NGX_SSL_MAX_SESSION_SIZE]; +#endif + + peer = rrp->current; + +#if (NGX_HTTP_UPSTREAM_ZONE) + peers = rrp->peers; + + if (peers->shpool) { + ngx_http_upstream_rr_peers_rlock(peers); + ngx_http_upstream_rr_peer_lock(peers, peer); + + if (peer->ssl_session == NULL) { + ngx_http_upstream_rr_peer_unlock(peers, peer); + ngx_http_upstream_rr_peers_unlock(peers); + return NGX_OK; + } + + len = peer->ssl_session_len; + + ngx_memcpy(buf, peer->ssl_session, len); + + ngx_http_upstream_rr_peer_unlock(peers, peer); + ngx_http_upstream_rr_peers_unlock(peers); + + p = buf; + ssl_session = d2i_SSL_SESSION(NULL, &p, len); + + rc = ngx_ssl_set_session(pc->connection, ssl_session); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "set session: %p", ssl_session); + + ngx_ssl_free_session(ssl_session); + + return rc; + } +#endif + + ssl_session = peer->ssl_session; + + rc = ngx_ssl_set_session(pc->connection, ssl_session); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "set session: %p", ssl_session); + + return rc; +} + + +void +ngx_http_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc, + void *data) +{ + ngx_http_upstream_rr_peer_data_t *rrp = data; + + ngx_ssl_session_t *old_ssl_session, *ssl_session; + ngx_http_upstream_rr_peer_t *peer; +#if (NGX_HTTP_UPSTREAM_ZONE) + int len; + u_char *p; + ngx_http_upstream_rr_peers_t *peers; + u_char buf[NGX_SSL_MAX_SESSION_SIZE]; +#endif + +#if (NGX_HTTP_UPSTREAM_ZONE) + peers = rrp->peers; + + if (peers->shpool) { + + ssl_session = SSL_get0_session(pc->connection->ssl->connection); + + if (ssl_session == NULL) { + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "save session: %p", ssl_session); + + len = i2d_SSL_SESSION(ssl_session, NULL); + + /* do not cache too big session */ + + if (len > NGX_SSL_MAX_SESSION_SIZE) { + return; + } + + p = buf; + (void) i2d_SSL_SESSION(ssl_session, &p); + + peer = rrp->current; + + ngx_http_upstream_rr_peers_rlock(peers); + ngx_http_upstream_rr_peer_lock(peers, peer); + + if (len > peer->ssl_session_len) { + ngx_shmtx_lock(&peers->shpool->mutex); + + if (peer->ssl_session) { + ngx_slab_free_locked(peers->shpool, peer->ssl_session); + } + + peer->ssl_session = ngx_slab_alloc_locked(peers->shpool, len); + + ngx_shmtx_unlock(&peers->shpool->mutex); + + if (peer->ssl_session == NULL) { + peer->ssl_session_len = 0; + + ngx_http_upstream_rr_peer_unlock(peers, peer); + ngx_http_upstream_rr_peers_unlock(peers); + return; + } + + peer->ssl_session_len = len; + } + + ngx_memcpy(peer->ssl_session, buf, len); + + ngx_http_upstream_rr_peer_unlock(peers, peer); + ngx_http_upstream_rr_peers_unlock(peers); + + return; + } +#endif + + ssl_session = ngx_ssl_get_session(pc->connection); + + if (ssl_session == NULL) { + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "save session: %p", ssl_session); + + peer = rrp->current; + + old_ssl_session = peer->ssl_session; + peer->ssl_session = ssl_session; + + if (old_ssl_session) { + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "old session: %p", old_ssl_session); + + /* TODO: may block */ + + ngx_ssl_free_session(old_ssl_session); + } +} + + +static ngx_int_t +ngx_http_upstream_empty_set_session(ngx_peer_connection_t *pc, void *data) +{ + return NGX_OK; +} + + +static void +ngx_http_upstream_empty_save_session(ngx_peer_connection_t *pc, void *data) +{ + return; +} + +#endif diff --git a/app/nginx/src/http/ngx_http_upstream_round_robin.h b/app/nginx/src/http/ngx_http_upstream_round_robin.h new file mode 100644 index 0000000..45f258d --- /dev/null +++ b/app/nginx/src/http/ngx_http_upstream_round_robin.h @@ -0,0 +1,156 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_UPSTREAM_ROUND_ROBIN_H_INCLUDED_ +#define _NGX_HTTP_UPSTREAM_ROUND_ROBIN_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct ngx_http_upstream_rr_peer_s ngx_http_upstream_rr_peer_t; + +struct ngx_http_upstream_rr_peer_s { + struct sockaddr *sockaddr; + socklen_t socklen; + ngx_str_t name; + ngx_str_t server; + + ngx_int_t current_weight; + ngx_int_t effective_weight; + ngx_int_t weight; + + ngx_uint_t conns; + ngx_uint_t max_conns; + + ngx_uint_t fails; + time_t accessed; + time_t checked; + + ngx_uint_t max_fails; + time_t fail_timeout; + ngx_msec_t slow_start; + ngx_msec_t start_time; + + ngx_uint_t down; + +#if (NGX_HTTP_SSL || NGX_COMPAT) + void *ssl_session; + int ssl_session_len; +#endif + +#if (NGX_HTTP_UPSTREAM_ZONE) + ngx_atomic_t lock; +#endif + + ngx_http_upstream_rr_peer_t *next; + + NGX_COMPAT_BEGIN(32) + NGX_COMPAT_END +}; + + +typedef struct ngx_http_upstream_rr_peers_s ngx_http_upstream_rr_peers_t; + +struct ngx_http_upstream_rr_peers_s { + ngx_uint_t number; + +#if (NGX_HTTP_UPSTREAM_ZONE) + ngx_slab_pool_t *shpool; + ngx_atomic_t rwlock; + ngx_http_upstream_rr_peers_t *zone_next; +#endif + + ngx_uint_t total_weight; + + unsigned single:1; + unsigned weighted:1; + + ngx_str_t *name; + + ngx_http_upstream_rr_peers_t *next; + + ngx_http_upstream_rr_peer_t *peer; +}; + + +#if (NGX_HTTP_UPSTREAM_ZONE) + +#define ngx_http_upstream_rr_peers_rlock(peers) \ + \ + if (peers->shpool) { \ + ngx_rwlock_rlock(&peers->rwlock); \ + } + +#define ngx_http_upstream_rr_peers_wlock(peers) \ + \ + if (peers->shpool) { \ + ngx_rwlock_wlock(&peers->rwlock); \ + } + +#define ngx_http_upstream_rr_peers_unlock(peers) \ + \ + if (peers->shpool) { \ + ngx_rwlock_unlock(&peers->rwlock); \ + } + + +#define ngx_http_upstream_rr_peer_lock(peers, peer) \ + \ + if (peers->shpool) { \ + ngx_rwlock_wlock(&peer->lock); \ + } + +#define ngx_http_upstream_rr_peer_unlock(peers, peer) \ + \ + if (peers->shpool) { \ + ngx_rwlock_unlock(&peer->lock); \ + } + +#else + +#define ngx_http_upstream_rr_peers_rlock(peers) +#define ngx_http_upstream_rr_peers_wlock(peers) +#define ngx_http_upstream_rr_peers_unlock(peers) +#define ngx_http_upstream_rr_peer_lock(peers, peer) +#define ngx_http_upstream_rr_peer_unlock(peers, peer) + +#endif + + +typedef struct { + ngx_uint_t config; + ngx_http_upstream_rr_peers_t *peers; + ngx_http_upstream_rr_peer_t *current; + uintptr_t *tried; + uintptr_t data; +} ngx_http_upstream_rr_peer_data_t; + + +ngx_int_t ngx_http_upstream_init_round_robin(ngx_conf_t *cf, + ngx_http_upstream_srv_conf_t *us); +ngx_int_t ngx_http_upstream_init_round_robin_peer(ngx_http_request_t *r, + ngx_http_upstream_srv_conf_t *us); +ngx_int_t ngx_http_upstream_create_round_robin_peer(ngx_http_request_t *r, + ngx_http_upstream_resolved_t *ur); +ngx_int_t ngx_http_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, + void *data); +void ngx_http_upstream_free_round_robin_peer(ngx_peer_connection_t *pc, + void *data, ngx_uint_t state); + +#if (NGX_HTTP_SSL) +ngx_int_t + ngx_http_upstream_set_round_robin_peer_session(ngx_peer_connection_t *pc, + void *data); +void ngx_http_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc, + void *data); +#endif + + +#endif /* _NGX_HTTP_UPSTREAM_ROUND_ROBIN_H_INCLUDED_ */ diff --git a/app/nginx/src/http/ngx_http_variables.c b/app/nginx/src/http/ngx_http_variables.c new file mode 100644 index 0000000..6138819 --- /dev/null +++ b/app/nginx/src/http/ngx_http_variables.c @@ -0,0 +1,2685 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +static ngx_http_variable_t *ngx_http_add_prefix_variable(ngx_conf_t *cf, + ngx_str_t *name, ngx_uint_t flags); + +static ngx_int_t ngx_http_variable_request(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +#if 0 +static void ngx_http_variable_request_set(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +#endif +static ngx_int_t ngx_http_variable_request_get_size(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static void ngx_http_variable_request_set_size(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_header(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + +static ngx_int_t ngx_http_variable_cookies(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_headers(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_headers_internal(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data, u_char sep); + +static ngx_int_t ngx_http_variable_unknown_header_in(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_unknown_header_out(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_request_line(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_cookie(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_argument(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +#if (NGX_HAVE_TCP_INFO) +static ngx_int_t ngx_http_variable_tcpinfo(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +#endif + +static ngx_int_t ngx_http_variable_content_length(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_host(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_binary_remote_addr(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_remote_addr(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_remote_port(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_proxy_protocol_addr(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_proxy_protocol_port(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_server_addr(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_server_port(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_scheme(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_https(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static void ngx_http_variable_set_args(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_is_args(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_document_root(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_realpath_root(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_request_filename(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_server_name(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_request_method(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_remote_user(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_bytes_sent(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_body_bytes_sent(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_pipe(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_request_completion(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_request_body(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_request_body_file(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_request_length(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_request_time(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_request_id(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_status(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + +static ngx_int_t ngx_http_variable_sent_content_type(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_sent_content_length(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_sent_location(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_sent_last_modified(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_sent_connection(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_sent_keep_alive(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_sent_transfer_encoding(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + +static ngx_int_t ngx_http_variable_connection(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_connection_requests(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + +static ngx_int_t ngx_http_variable_nginx_version(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_hostname(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_pid(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_msec(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_time_iso8601(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_time_local(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + +/* + * TODO: + * Apache CGI: AUTH_TYPE, PATH_INFO (null), PATH_TRANSLATED + * REMOTE_HOST (null), REMOTE_IDENT (null), + * SERVER_SOFTWARE + * + * Apache SSI: DOCUMENT_NAME, LAST_MODIFIED, USER_NAME (file owner) + */ + +/* + * the $http_host, $http_user_agent, $http_referer, and $http_via + * variables may be handled by generic + * ngx_http_variable_unknown_header_in(), but for performance reasons + * they are handled using dedicated entries + */ + +static ngx_http_variable_t ngx_http_core_variables[] = { + + { ngx_string("http_host"), NULL, ngx_http_variable_header, + offsetof(ngx_http_request_t, headers_in.host), 0, 0 }, + + { ngx_string("http_user_agent"), NULL, ngx_http_variable_header, + offsetof(ngx_http_request_t, headers_in.user_agent), 0, 0 }, + + { ngx_string("http_referer"), NULL, ngx_http_variable_header, + offsetof(ngx_http_request_t, headers_in.referer), 0, 0 }, + +#if (NGX_HTTP_GZIP) + { ngx_string("http_via"), NULL, ngx_http_variable_header, + offsetof(ngx_http_request_t, headers_in.via), 0, 0 }, +#endif + +#if (NGX_HTTP_X_FORWARDED_FOR) + { ngx_string("http_x_forwarded_for"), NULL, ngx_http_variable_headers, + offsetof(ngx_http_request_t, headers_in.x_forwarded_for), 0, 0 }, +#endif + + { ngx_string("http_cookie"), NULL, ngx_http_variable_cookies, + offsetof(ngx_http_request_t, headers_in.cookies), 0, 0 }, + + { ngx_string("content_length"), NULL, ngx_http_variable_content_length, + 0, 0, 0 }, + + { ngx_string("content_type"), NULL, ngx_http_variable_header, + offsetof(ngx_http_request_t, headers_in.content_type), 0, 0 }, + + { ngx_string("host"), NULL, ngx_http_variable_host, 0, 0, 0 }, + + { ngx_string("binary_remote_addr"), NULL, + ngx_http_variable_binary_remote_addr, 0, 0, 0 }, + + { ngx_string("remote_addr"), NULL, ngx_http_variable_remote_addr, 0, 0, 0 }, + + { ngx_string("remote_port"), NULL, ngx_http_variable_remote_port, 0, 0, 0 }, + + { ngx_string("proxy_protocol_addr"), NULL, + ngx_http_variable_proxy_protocol_addr, 0, 0, 0 }, + + { ngx_string("proxy_protocol_port"), NULL, + ngx_http_variable_proxy_protocol_port, 0, 0, 0 }, + + { ngx_string("server_addr"), NULL, ngx_http_variable_server_addr, 0, 0, 0 }, + + { ngx_string("server_port"), NULL, ngx_http_variable_server_port, 0, 0, 0 }, + + { ngx_string("server_protocol"), NULL, ngx_http_variable_request, + offsetof(ngx_http_request_t, http_protocol), 0, 0 }, + + { ngx_string("scheme"), NULL, ngx_http_variable_scheme, 0, 0, 0 }, + + { ngx_string("https"), NULL, ngx_http_variable_https, 0, 0, 0 }, + + { ngx_string("request_uri"), NULL, ngx_http_variable_request, + offsetof(ngx_http_request_t, unparsed_uri), 0, 0 }, + + { ngx_string("uri"), NULL, ngx_http_variable_request, + offsetof(ngx_http_request_t, uri), + NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("document_uri"), NULL, ngx_http_variable_request, + offsetof(ngx_http_request_t, uri), + NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("request"), NULL, ngx_http_variable_request_line, 0, 0, 0 }, + + { ngx_string("document_root"), NULL, + ngx_http_variable_document_root, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("realpath_root"), NULL, + ngx_http_variable_realpath_root, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("query_string"), NULL, ngx_http_variable_request, + offsetof(ngx_http_request_t, args), + NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("args"), + ngx_http_variable_set_args, + ngx_http_variable_request, + offsetof(ngx_http_request_t, args), + NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("is_args"), NULL, ngx_http_variable_is_args, + 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("request_filename"), NULL, + ngx_http_variable_request_filename, 0, + NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("server_name"), NULL, ngx_http_variable_server_name, 0, 0, 0 }, + + { ngx_string("request_method"), NULL, + ngx_http_variable_request_method, 0, + NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("remote_user"), NULL, ngx_http_variable_remote_user, 0, 0, 0 }, + + { ngx_string("bytes_sent"), NULL, ngx_http_variable_bytes_sent, + 0, 0, 0 }, + + { ngx_string("body_bytes_sent"), NULL, ngx_http_variable_body_bytes_sent, + 0, 0, 0 }, + + { ngx_string("pipe"), NULL, ngx_http_variable_pipe, + 0, 0, 0 }, + + { ngx_string("request_completion"), NULL, + ngx_http_variable_request_completion, + 0, 0, 0 }, + + { ngx_string("request_body"), NULL, + ngx_http_variable_request_body, + 0, 0, 0 }, + + { ngx_string("request_body_file"), NULL, + ngx_http_variable_request_body_file, + 0, 0, 0 }, + + { ngx_string("request_length"), NULL, ngx_http_variable_request_length, + 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("request_time"), NULL, ngx_http_variable_request_time, + 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("request_id"), NULL, + ngx_http_variable_request_id, + 0, 0, 0 }, + + { ngx_string("status"), NULL, + ngx_http_variable_status, 0, + NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("sent_http_content_type"), NULL, + ngx_http_variable_sent_content_type, 0, 0, 0 }, + + { ngx_string("sent_http_content_length"), NULL, + ngx_http_variable_sent_content_length, 0, 0, 0 }, + + { ngx_string("sent_http_location"), NULL, + ngx_http_variable_sent_location, 0, 0, 0 }, + + { ngx_string("sent_http_last_modified"), NULL, + ngx_http_variable_sent_last_modified, 0, 0, 0 }, + + { ngx_string("sent_http_connection"), NULL, + ngx_http_variable_sent_connection, 0, 0, 0 }, + + { ngx_string("sent_http_keep_alive"), NULL, + ngx_http_variable_sent_keep_alive, 0, 0, 0 }, + + { ngx_string("sent_http_transfer_encoding"), NULL, + ngx_http_variable_sent_transfer_encoding, 0, 0, 0 }, + + { ngx_string("sent_http_cache_control"), NULL, ngx_http_variable_headers, + offsetof(ngx_http_request_t, headers_out.cache_control), 0, 0 }, + + { ngx_string("limit_rate"), ngx_http_variable_request_set_size, + ngx_http_variable_request_get_size, + offsetof(ngx_http_request_t, limit_rate), + NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("connection"), NULL, + ngx_http_variable_connection, 0, 0, 0 }, + + { ngx_string("connection_requests"), NULL, + ngx_http_variable_connection_requests, 0, 0, 0 }, + + { ngx_string("nginx_version"), NULL, ngx_http_variable_nginx_version, + 0, 0, 0 }, + + { ngx_string("hostname"), NULL, ngx_http_variable_hostname, + 0, 0, 0 }, + + { ngx_string("pid"), NULL, ngx_http_variable_pid, + 0, 0, 0 }, + + { ngx_string("msec"), NULL, ngx_http_variable_msec, + 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("time_iso8601"), NULL, ngx_http_variable_time_iso8601, + 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("time_local"), NULL, ngx_http_variable_time_local, + 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, + +#if (NGX_HAVE_TCP_INFO) + { ngx_string("tcpinfo_rtt"), NULL, ngx_http_variable_tcpinfo, + 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("tcpinfo_rttvar"), NULL, ngx_http_variable_tcpinfo, + 1, NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("tcpinfo_snd_cwnd"), NULL, ngx_http_variable_tcpinfo, + 2, NGX_HTTP_VAR_NOCACHEABLE, 0 }, + + { ngx_string("tcpinfo_rcv_space"), NULL, ngx_http_variable_tcpinfo, + 3, NGX_HTTP_VAR_NOCACHEABLE, 0 }, +#endif + + { ngx_string("http_"), NULL, ngx_http_variable_unknown_header_in, + 0, NGX_HTTP_VAR_PREFIX, 0 }, + + { ngx_string("sent_http_"), NULL, ngx_http_variable_unknown_header_out, + 0, NGX_HTTP_VAR_PREFIX, 0 }, + + { ngx_string("cookie_"), NULL, ngx_http_variable_cookie, + 0, NGX_HTTP_VAR_PREFIX, 0 }, + + { ngx_string("arg_"), NULL, ngx_http_variable_argument, + 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_PREFIX, 0 }, + + { ngx_null_string, NULL, NULL, 0, 0, 0 } +}; + + +ngx_http_variable_value_t ngx_http_variable_null_value = + ngx_http_variable(""); +ngx_http_variable_value_t ngx_http_variable_true_value = + ngx_http_variable("1"); + + +static ngx_uint_t ngx_http_variable_depth = 100; + + +ngx_http_variable_t * +ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags) +{ + ngx_int_t rc; + ngx_uint_t i; + ngx_hash_key_t *key; + ngx_http_variable_t *v; + ngx_http_core_main_conf_t *cmcf; + + if (name->len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid variable name \"$\""); + return NULL; + } + + if (flags & NGX_HTTP_VAR_PREFIX) { + return ngx_http_add_prefix_variable(cf, name, flags); + } + + cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); + + key = cmcf->variables_keys->keys.elts; + for (i = 0; i < cmcf->variables_keys->keys.nelts; i++) { + if (name->len != key[i].key.len + || ngx_strncasecmp(name->data, key[i].key.data, name->len) != 0) + { + continue; + } + + v = key[i].value; + + if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the duplicate \"%V\" variable", name); + return NULL; + } + + v->flags &= flags | ~NGX_HTTP_VAR_WEAK; + + return v; + } + + v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t)); + if (v == NULL) { + return NULL; + } + + v->name.len = name->len; + v->name.data = ngx_pnalloc(cf->pool, name->len); + if (v->name.data == NULL) { + return NULL; + } + + ngx_strlow(v->name.data, name->data, name->len); + + v->set_handler = NULL; + v->get_handler = NULL; + v->data = 0; + v->flags = flags; + v->index = 0; + + rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, 0); + + if (rc == NGX_ERROR) { + return NULL; + } + + if (rc == NGX_BUSY) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "conflicting variable name \"%V\"", name); + return NULL; + } + + return v; +} + + +static ngx_http_variable_t * +ngx_http_add_prefix_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags) +{ + ngx_uint_t i; + ngx_http_variable_t *v; + ngx_http_core_main_conf_t *cmcf; + + cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); + + v = cmcf->prefix_variables.elts; + for (i = 0; i < cmcf->prefix_variables.nelts; i++) { + if (name->len != v[i].name.len + || ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0) + { + continue; + } + + v = &v[i]; + + if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the duplicate \"%V\" variable", name); + return NULL; + } + + v->flags &= flags | ~NGX_HTTP_VAR_WEAK; + + return v; + } + + v = ngx_array_push(&cmcf->prefix_variables); + if (v == NULL) { + return NULL; + } + + v->name.len = name->len; + v->name.data = ngx_pnalloc(cf->pool, name->len); + if (v->name.data == NULL) { + return NULL; + } + + ngx_strlow(v->name.data, name->data, name->len); + + v->set_handler = NULL; + v->get_handler = NULL; + v->data = 0; + v->flags = flags; + v->index = 0; + + return v; +} + + +ngx_int_t +ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name) +{ + ngx_uint_t i; + ngx_http_variable_t *v; + ngx_http_core_main_conf_t *cmcf; + + if (name->len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid variable name \"$\""); + return NGX_ERROR; + } + + cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); + + v = cmcf->variables.elts; + + if (v == NULL) { + if (ngx_array_init(&cmcf->variables, cf->pool, 4, + sizeof(ngx_http_variable_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + } else { + for (i = 0; i < cmcf->variables.nelts; i++) { + if (name->len != v[i].name.len + || ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0) + { + continue; + } + + return i; + } + } + + v = ngx_array_push(&cmcf->variables); + if (v == NULL) { + return NGX_ERROR; + } + + v->name.len = name->len; + v->name.data = ngx_pnalloc(cf->pool, name->len); + if (v->name.data == NULL) { + return NGX_ERROR; + } + + ngx_strlow(v->name.data, name->data, name->len); + + v->set_handler = NULL; + v->get_handler = NULL; + v->data = 0; + v->flags = 0; + v->index = cmcf->variables.nelts - 1; + + return v->index; +} + + +ngx_http_variable_value_t * +ngx_http_get_indexed_variable(ngx_http_request_t *r, ngx_uint_t index) +{ + ngx_http_variable_t *v; + ngx_http_core_main_conf_t *cmcf; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + if (cmcf->variables.nelts <= index) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "unknown variable index: %ui", index); + return NULL; + } + + if (r->variables[index].not_found || r->variables[index].valid) { + return &r->variables[index]; + } + + v = cmcf->variables.elts; + + if (ngx_http_variable_depth == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "cycle while evaluating variable \"%V\"", + &v[index].name); + return NULL; + } + + ngx_http_variable_depth--; + + if (v[index].get_handler(r, &r->variables[index], v[index].data) + == NGX_OK) + { + ngx_http_variable_depth++; + + if (v[index].flags & NGX_HTTP_VAR_NOCACHEABLE) { + r->variables[index].no_cacheable = 1; + } + + return &r->variables[index]; + } + + ngx_http_variable_depth++; + + r->variables[index].valid = 0; + r->variables[index].not_found = 1; + + return NULL; +} + + +ngx_http_variable_value_t * +ngx_http_get_flushed_variable(ngx_http_request_t *r, ngx_uint_t index) +{ + ngx_http_variable_value_t *v; + + v = &r->variables[index]; + + if (v->valid || v->not_found) { + if (!v->no_cacheable) { + return v; + } + + v->valid = 0; + v->not_found = 0; + } + + return ngx_http_get_indexed_variable(r, index); +} + + +ngx_http_variable_value_t * +ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key) +{ + size_t len; + ngx_uint_t i, n; + ngx_http_variable_t *v; + ngx_http_variable_value_t *vv; + ngx_http_core_main_conf_t *cmcf; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + v = ngx_hash_find(&cmcf->variables_hash, key, name->data, name->len); + + if (v) { + if (v->flags & NGX_HTTP_VAR_INDEXED) { + return ngx_http_get_flushed_variable(r, v->index); + } + + if (ngx_http_variable_depth == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "cycle while evaluating variable \"%V\"", name); + return NULL; + } + + ngx_http_variable_depth--; + + vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t)); + + if (vv && v->get_handler(r, vv, v->data) == NGX_OK) { + ngx_http_variable_depth++; + return vv; + } + + ngx_http_variable_depth++; + return NULL; + } + + vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t)); + if (vv == NULL) { + return NULL; + } + + len = 0; + + v = cmcf->prefix_variables.elts; + n = cmcf->prefix_variables.nelts; + + for (i = 0; i < cmcf->prefix_variables.nelts; i++) { + if (name->len >= v[i].name.len && name->len > len + && ngx_strncmp(name->data, v[i].name.data, v[i].name.len) == 0) + { + len = v[i].name.len; + n = i; + } + } + + if (n != cmcf->prefix_variables.nelts) { + if (v[n].get_handler(r, vv, (uintptr_t) name) == NGX_OK) { + return vv; + } + + return NULL; + } + + vv->not_found = 1; + + return vv; +} + + +static ngx_int_t +ngx_http_variable_request(ngx_http_request_t *r, ngx_http_variable_value_t *v, + uintptr_t data) +{ + ngx_str_t *s; + + s = (ngx_str_t *) ((char *) r + data); + + if (s->data) { + v->len = s->len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = s->data; + + } else { + v->not_found = 1; + } + + return NGX_OK; +} + + +#if 0 + +static void +ngx_http_variable_request_set(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_str_t *s; + + s = (ngx_str_t *) ((char *) r + data); + + s->len = v->len; + s->data = v->data; +} + +#endif + + +static ngx_int_t +ngx_http_variable_request_get_size(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + size_t *sp; + + sp = (size_t *) ((char *) r + data); + + v->data = ngx_pnalloc(r->pool, NGX_SIZE_T_LEN); + if (v->data == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(v->data, "%uz", *sp) - v->data; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + + +static void +ngx_http_variable_request_set_size(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ssize_t s, *sp; + ngx_str_t val; + + val.len = v->len; + val.data = v->data; + + s = ngx_parse_size(&val); + + if (s == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "invalid size \"%V\"", &val); + return; + } + + sp = (ssize_t *) ((char *) r + data); + + *sp = s; + + return; +} + + +static ngx_int_t +ngx_http_variable_header(ngx_http_request_t *r, ngx_http_variable_value_t *v, + uintptr_t data) +{ + ngx_table_elt_t *h; + + h = *(ngx_table_elt_t **) ((char *) r + data); + + if (h) { + v->len = h->value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = h->value.data; + + } else { + v->not_found = 1; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_cookies(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + return ngx_http_variable_headers_internal(r, v, data, ';'); +} + + +static ngx_int_t +ngx_http_variable_headers(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + return ngx_http_variable_headers_internal(r, v, data, ','); +} + + +static ngx_int_t +ngx_http_variable_headers_internal(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data, u_char sep) +{ + size_t len; + u_char *p, *end; + ngx_uint_t i, n; + ngx_array_t *a; + ngx_table_elt_t **h; + + a = (ngx_array_t *) ((char *) r + data); + + n = a->nelts; + h = a->elts; + + len = 0; + + for (i = 0; i < n; i++) { + + if (h[i]->hash == 0) { + continue; + } + + len += h[i]->value.len + 2; + } + + if (len == 0) { + v->not_found = 1; + return NGX_OK; + } + + len -= 2; + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + if (n == 1) { + v->len = (*h)->value.len; + v->data = (*h)->value.data; + + return NGX_OK; + } + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = len; + v->data = p; + + end = p + len; + + for (i = 0; /* void */ ; i++) { + + if (h[i]->hash == 0) { + continue; + } + + p = ngx_copy(p, h[i]->value.data, h[i]->value.len); + + if (p == end) { + break; + } + + *p++ = sep; *p++ = ' '; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_unknown_header_in(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + return ngx_http_variable_unknown_header(v, (ngx_str_t *) data, + &r->headers_in.headers.part, + sizeof("http_") - 1); +} + + +static ngx_int_t +ngx_http_variable_unknown_header_out(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + return ngx_http_variable_unknown_header(v, (ngx_str_t *) data, + &r->headers_out.headers.part, + sizeof("sent_http_") - 1); +} + + +ngx_int_t +ngx_http_variable_unknown_header(ngx_http_variable_value_t *v, ngx_str_t *var, + ngx_list_part_t *part, size_t prefix) +{ + u_char ch; + ngx_uint_t i, n; + ngx_table_elt_t *header; + + header = part->elts; + + for (i = 0; /* void */ ; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + for (n = 0; n + prefix < var->len && n < header[i].key.len; n++) { + ch = header[i].key.data[n]; + + if (ch >= 'A' && ch <= 'Z') { + ch |= 0x20; + + } else if (ch == '-') { + ch = '_'; + } + + if (var->data[n + prefix] != ch) { + break; + } + } + + if (n + prefix == var->len && n == header[i].key.len) { + v->len = header[i].value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = header[i].value.data; + + return NGX_OK; + } + } + + v->not_found = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_request_line(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p, *s; + + s = r->request_line.data; + + if (s == NULL) { + s = r->request_start; + + if (s == NULL) { + v->not_found = 1; + return NGX_OK; + } + + for (p = s; p < r->header_in->last; p++) { + if (*p == CR || *p == LF) { + break; + } + } + + r->request_line.len = p - s; + r->request_line.data = s; + } + + v->len = r->request_line.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = s; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_cookie(ngx_http_request_t *r, ngx_http_variable_value_t *v, + uintptr_t data) +{ + ngx_str_t *name = (ngx_str_t *) data; + + ngx_str_t cookie, s; + + s.len = name->len - (sizeof("cookie_") - 1); + s.data = name->data + sizeof("cookie_") - 1; + + if (ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &s, &cookie) + == NGX_DECLINED) + { + v->not_found = 1; + return NGX_OK; + } + + v->len = cookie.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = cookie.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_argument(ngx_http_request_t *r, ngx_http_variable_value_t *v, + uintptr_t data) +{ + ngx_str_t *name = (ngx_str_t *) data; + + u_char *arg; + size_t len; + ngx_str_t value; + + len = name->len - (sizeof("arg_") - 1); + arg = name->data + sizeof("arg_") - 1; + + if (ngx_http_arg(r, arg, len, &value) != NGX_OK) { + v->not_found = 1; + return NGX_OK; + } + + v->data = value.data; + v->len = value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + + +#if (NGX_HAVE_TCP_INFO) + +static ngx_int_t +ngx_http_variable_tcpinfo(ngx_http_request_t *r, ngx_http_variable_value_t *v, + uintptr_t data) +{ + struct tcp_info ti; + socklen_t len; + uint32_t value; + + len = sizeof(struct tcp_info); + if (getsockopt(r->connection->fd, IPPROTO_TCP, TCP_INFO, &ti, &len) == -1) { + v->not_found = 1; + return NGX_OK; + } + + v->data = ngx_pnalloc(r->pool, NGX_INT32_LEN); + if (v->data == NULL) { + return NGX_ERROR; + } + + switch (data) { + case 0: + value = ti.tcpi_rtt; + break; + + case 1: + value = ti.tcpi_rttvar; + break; + + case 2: + value = ti.tcpi_snd_cwnd; + break; + + case 3: + value = ti.tcpi_rcv_space; + break; + + /* suppress warning */ + default: + value = 0; + break; + } + + v->len = ngx_sprintf(v->data, "%uD", value) - v->data; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + +#endif + + +static ngx_int_t +ngx_http_variable_content_length(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + if (r->headers_in.content_length) { + v->len = r->headers_in.content_length->value.len; + v->data = r->headers_in.content_length->value.data; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + } else if (r->reading_body) { + v->not_found = 1; + v->no_cacheable = 1; + + } else if (r->headers_in.content_length_n >= 0) { + p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(p, "%O", r->headers_in.content_length_n) - p; + v->data = p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + } else { + v->not_found = 1; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_host(ngx_http_request_t *r, ngx_http_variable_value_t *v, + uintptr_t data) +{ + ngx_http_core_srv_conf_t *cscf; + + if (r->headers_in.server.len) { + v->len = r->headers_in.server.len; + v->data = r->headers_in.server.data; + + } else { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + v->len = cscf->server_name.len; + v->data = cscf->server_name.data; + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_binary_remote_addr(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + switch (r->connection->sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) r->connection->sockaddr; + + v->len = sizeof(struct in6_addr); + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = sin6->sin6_addr.s6_addr; + + break; +#endif + + default: /* AF_INET */ + sin = (struct sockaddr_in *) r->connection->sockaddr; + + v->len = sizeof(in_addr_t); + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) &sin->sin_addr; + + break; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_remote_addr(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + v->len = r->connection->addr_text.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = r->connection->addr_text.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_remote_port(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_uint_t port; + + 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(r->connection->sockaddr); + + if (port > 0 && port < 65536) { + v->len = ngx_sprintf(v->data, "%ui", port) - v->data; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_proxy_protocol_addr(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + v->len = r->connection->proxy_protocol_addr.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = r->connection->proxy_protocol_addr.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_proxy_protocol_port(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_uint_t port; + + 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 = r->connection->proxy_protocol_port; + + if (port > 0 && port < 65536) { + v->len = ngx_sprintf(v->data, "%ui", port) - v->data; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_server_addr(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_str_t s; + u_char addr[NGX_SOCKADDR_STRLEN]; + + s.len = NGX_SOCKADDR_STRLEN; + s.data = addr; + + if (ngx_connection_local_sockaddr(r->connection, &s, 0) != NGX_OK) { + return NGX_ERROR; + } + + s.data = ngx_pnalloc(r->pool, s.len); + if (s.data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(s.data, addr, s.len); + + v->len = s.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = s.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_server_port(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_uint_t port; + + v->len = 0; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + if (ngx_connection_local_sockaddr(r->connection, NULL, 0) != NGX_OK) { + return NGX_ERROR; + } + + v->data = ngx_pnalloc(r->pool, sizeof("65535") - 1); + if (v->data == NULL) { + return NGX_ERROR; + } + + port = ngx_inet_get_port(r->connection->local_sockaddr); + + if (port > 0 && port < 65536) { + v->len = ngx_sprintf(v->data, "%ui", port) - v->data; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_scheme(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ +#if (NGX_HTTP_SSL) + + if (r->connection->ssl) { + v->len = sizeof("https") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "https"; + + return NGX_OK; + } + +#endif + + v->len = sizeof("http") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "http"; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_https(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ +#if (NGX_HTTP_SSL) + + if (r->connection->ssl) { + v->len = sizeof("on") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "on"; + + return NGX_OK; + } + +#endif + + *v = ngx_http_variable_null_value; + + return NGX_OK; +} + + +static void +ngx_http_variable_set_args(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + r->args.len = v->len; + r->args.data = v->data; + r->valid_unparsed_uri = 0; +} + + +static ngx_int_t +ngx_http_variable_is_args(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + if (r->args.len == 0) { + v->len = 0; + v->data = NULL; + return NGX_OK; + } + + v->len = 1; + v->data = (u_char *) "?"; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_document_root(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_str_t path; + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->root_lengths == NULL) { + v->len = clcf->root.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = clcf->root.data; + + } else { + if (ngx_http_script_run(r, &path, clcf->root_lengths->elts, 0, + clcf->root_values->elts) + == NULL) + { + return NGX_ERROR; + } + + if (ngx_get_full_name(r->pool, (ngx_str_t *) &ngx_cycle->prefix, &path) + != NGX_OK) + { + return NGX_ERROR; + } + + v->len = path.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = path.data; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_realpath_root(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *real; + size_t len; + ngx_str_t path; + ngx_http_core_loc_conf_t *clcf; +#if (NGX_HAVE_MAX_PATH) + u_char buffer[NGX_MAX_PATH]; +#endif + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->root_lengths == NULL) { + path = clcf->root; + + } else { + if (ngx_http_script_run(r, &path, clcf->root_lengths->elts, 1, + clcf->root_values->elts) + == NULL) + { + return NGX_ERROR; + } + + path.data[path.len - 1] = '\0'; + + if (ngx_get_full_name(r->pool, (ngx_str_t *) &ngx_cycle->prefix, &path) + != NGX_OK) + { + return NGX_ERROR; + } + } + +#if (NGX_HAVE_MAX_PATH) + real = buffer; +#else + real = NULL; +#endif + + real = ngx_realpath(path.data, real); + + if (real == NULL) { + ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno, + ngx_realpath_n " \"%s\" failed", path.data); + return NGX_ERROR; + } + + len = ngx_strlen(real); + + v->data = ngx_pnalloc(r->pool, len); + if (v->data == NULL) { +#if !(NGX_HAVE_MAX_PATH) + ngx_free(real); +#endif + return NGX_ERROR; + } + + v->len = len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + ngx_memcpy(v->data, real, len); + +#if !(NGX_HAVE_MAX_PATH) + ngx_free(real); +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_request_filename(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + size_t root; + ngx_str_t path; + + if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) { + return NGX_ERROR; + } + + /* ngx_http_map_uri_to_path() allocates memory for terminating '\0' */ + + v->len = path.len - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = path.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_server_name(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_http_core_srv_conf_t *cscf; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + v->len = cscf->server_name.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = cscf->server_name.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_request_method(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->main->method_name.data) { + v->len = r->main->method_name.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = r->main->method_name.data; + + } else { + v->not_found = 1; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_remote_user(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_int_t rc; + + rc = ngx_http_auth_basic_user(r); + + if (rc == NGX_DECLINED) { + v->not_found = 1; + return NGX_OK; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + v->len = r->headers_in.user.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = r->headers_in.user.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_bytes_sent(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(p, "%O", r->connection->sent) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_body_bytes_sent(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + off_t sent; + u_char *p; + + sent = r->connection->sent - r->header_size; + + if (sent < 0) { + sent = 0; + } + + p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(p, "%O", sent) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_pipe(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + v->data = (u_char *) (r->pipeline ? "p" : "."); + v->len = 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_status(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_uint_t status; + + v->data = ngx_pnalloc(r->pool, NGX_INT_T_LEN); + if (v->data == NULL) { + return NGX_ERROR; + } + + 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; + } + + v->len = ngx_sprintf(v->data, "%03ui", status) - v->data; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_sent_content_type(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->headers_out.content_type.len) { + v->len = r->headers_out.content_type.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = r->headers_out.content_type.data; + + } else { + v->not_found = 1; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_sent_content_length(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + if (r->headers_out.content_length) { + v->len = r->headers_out.content_length->value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = r->headers_out.content_length->value.data; + + return NGX_OK; + } + + if (r->headers_out.content_length_n >= 0) { + p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(p, "%O", r->headers_out.content_length_n) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; + } + + v->not_found = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_sent_location(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_str_t name; + + if (r->headers_out.location) { + v->len = r->headers_out.location->value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = r->headers_out.location->value.data; + + return NGX_OK; + } + + ngx_str_set(&name, "sent_http_location"); + + return ngx_http_variable_unknown_header(v, &name, + &r->headers_out.headers.part, + sizeof("sent_http_") - 1); +} + + +static ngx_int_t +ngx_http_variable_sent_last_modified(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + if (r->headers_out.last_modified) { + v->len = r->headers_out.last_modified->value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = r->headers_out.last_modified->value.data; + + return NGX_OK; + } + + if (r->headers_out.last_modified_time >= 0) { + p = ngx_pnalloc(r->pool, sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = ngx_http_time(p, r->headers_out.last_modified_time) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; + } + + v->not_found = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_sent_connection(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + size_t len; + char *p; + + if (r->headers_out.status == NGX_HTTP_SWITCHING_PROTOCOLS) { + len = sizeof("upgrade") - 1; + p = "upgrade"; + + } else if (r->keepalive) { + len = sizeof("keep-alive") - 1; + p = "keep-alive"; + + } else { + len = sizeof("close") - 1; + p = "close"; + } + + v->len = len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_sent_keep_alive(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + ngx_http_core_loc_conf_t *clcf; + + if (r->keepalive) { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->keepalive_header) { + + p = ngx_pnalloc(r->pool, sizeof("timeout=") - 1 + NGX_TIME_T_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(p, "timeout=%T", clcf->keepalive_header) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; + } + } + + v->not_found = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_sent_transfer_encoding(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->chunked) { + v->len = sizeof("chunked") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "chunked"; + + } else { + v->not_found = 1; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_request_completion(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->request_complete) { + v->len = 2; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "OK"; + + return NGX_OK; + } + + v->len = 0; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) ""; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_request_body(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + size_t len; + ngx_buf_t *buf; + ngx_chain_t *cl; + + if (r->request_body == NULL + || r->request_body->bufs == NULL + || r->request_body->temp_file) + { + v->not_found = 1; + + return NGX_OK; + } + + cl = r->request_body->bufs; + buf = cl->buf; + + if (cl->next == NULL) { + v->len = buf->last - buf->pos; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = buf->pos; + + return NGX_OK; + } + + 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) { + return NGX_ERROR; + } + + v->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); + } + + v->len = len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_request_body_file(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->request_body == NULL || r->request_body->temp_file == NULL) { + v->not_found = 1; + + return NGX_OK; + } + + v->len = r->request_body->temp_file->file.name.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = r->request_body->temp_file->file.name.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_request_length(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(p, "%O", r->request_length) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_request_time(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + ngx_time_t *tp; + ngx_msec_int_t ms; + + p = ngx_pnalloc(r->pool, NGX_TIME_T_LEN + 4); + if (p == NULL) { + return NGX_ERROR; + } + + tp = ngx_timeofday(); + + ms = (ngx_msec_int_t) + ((tp->sec - r->start_sec) * 1000 + (tp->msec - r->start_msec)); + ms = ngx_max(ms, 0); + + v->len = ngx_sprintf(p, "%T.%03M", (time_t) ms / 1000, ms % 1000) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_request_id(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *id; + +#if (NGX_OPENSSL) + u_char random_bytes[16]; +#endif + + id = ngx_pnalloc(r->pool, 32); + if (id == NULL) { + return NGX_ERROR; + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + v->len = 32; + v->data = id; + +#if (NGX_OPENSSL) + + if (RAND_bytes(random_bytes, 16) == 1) { + ngx_hex_dump(id, random_bytes, 16); + return NGX_OK; + } + + ngx_ssl_error(NGX_LOG_ERR, r->connection->log, 0, "RAND_bytes() failed"); + +#endif + + ngx_sprintf(id, "%08xD%08xD%08xD%08xD", + (uint32_t) ngx_random(), (uint32_t) ngx_random(), + (uint32_t) ngx_random(), (uint32_t) ngx_random()); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_connection(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + p = ngx_pnalloc(r->pool, NGX_ATOMIC_T_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(p, "%uA", r->connection->number) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_connection_requests(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + p = ngx_pnalloc(r->pool, NGX_INT_T_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(p, "%ui", r->connection->requests) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_nginx_version(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + v->len = sizeof(NGINX_VERSION) - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) NGINX_VERSION; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_hostname(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + v->len = ngx_cycle->hostname.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = ngx_cycle->hostname.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_pid(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + p = ngx_pnalloc(r->pool, NGX_INT64_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(p, "%P", ngx_pid) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_msec(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + ngx_time_t *tp; + + p = ngx_pnalloc(r->pool, NGX_TIME_T_LEN + 4); + if (p == NULL) { + return NGX_ERROR; + } + + tp = ngx_timeofday(); + + v->len = ngx_sprintf(p, "%T.%03M", tp->sec, tp->msec) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_time_iso8601(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + p = ngx_pnalloc(r->pool, ngx_cached_http_log_iso8601.len); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, ngx_cached_http_log_iso8601.data, + ngx_cached_http_log_iso8601.len); + + v->len = ngx_cached_http_log_iso8601.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_variable_time_local(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + p = ngx_pnalloc(r->pool, ngx_cached_http_log_time.len); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, ngx_cached_http_log_time.data, ngx_cached_http_log_time.len); + + v->len = ngx_cached_http_log_time.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + +void * +ngx_http_map_find(ngx_http_request_t *r, ngx_http_map_t *map, ngx_str_t *match) +{ + void *value; + u_char *low; + size_t len; + ngx_uint_t key; + + len = match->len; + + if (len) { + low = ngx_pnalloc(r->pool, len); + if (low == NULL) { + return NULL; + } + + } else { + low = NULL; + } + + key = ngx_hash_strlow(low, match->data, len); + + value = ngx_hash_find_combined(&map->hash, key, low, len); + if (value) { + return value; + } + +#if (NGX_PCRE) + + if (len && map->nregex) { + ngx_int_t n; + ngx_uint_t i; + ngx_http_map_regex_t *reg; + + reg = map->regex; + + for (i = 0; i < map->nregex; i++) { + + n = ngx_http_regex_exec(r, reg[i].regex, match); + + if (n == NGX_OK) { + return reg[i].value; + } + + if (n == NGX_DECLINED) { + continue; + } + + /* NGX_ERROR */ + + return NULL; + } + } + +#endif + + return NULL; +} + + +#if (NGX_PCRE) + +static ngx_int_t +ngx_http_variable_not_found(ngx_http_request_t *r, ngx_http_variable_value_t *v, + uintptr_t data) +{ + v->not_found = 1; + return NGX_OK; +} + + +ngx_http_regex_t * +ngx_http_regex_compile(ngx_conf_t *cf, ngx_regex_compile_t *rc) +{ + u_char *p; + size_t size; + ngx_str_t name; + ngx_uint_t i, n; + ngx_http_variable_t *v; + ngx_http_regex_t *re; + ngx_http_regex_variable_t *rv; + ngx_http_core_main_conf_t *cmcf; + + rc->pool = cf->pool; + + if (ngx_regex_compile(rc) != NGX_OK) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc->err); + return NULL; + } + + re = ngx_pcalloc(cf->pool, sizeof(ngx_http_regex_t)); + if (re == NULL) { + return NULL; + } + + re->regex = rc->regex; + re->ncaptures = rc->captures; + re->name = rc->pattern; + + cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); + cmcf->ncaptures = ngx_max(cmcf->ncaptures, re->ncaptures); + + n = (ngx_uint_t) rc->named_captures; + + if (n == 0) { + return re; + } + + rv = ngx_palloc(rc->pool, n * sizeof(ngx_http_regex_variable_t)); + if (rv == NULL) { + return NULL; + } + + re->variables = rv; + re->nvariables = n; + + size = rc->name_size; + p = rc->names; + + for (i = 0; i < n; i++) { + rv[i].capture = 2 * ((p[0] << 8) + p[1]); + + name.data = &p[2]; + name.len = ngx_strlen(name.data); + + v = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE); + if (v == NULL) { + return NULL; + } + + rv[i].index = ngx_http_get_variable_index(cf, &name); + if (rv[i].index == NGX_ERROR) { + return NULL; + } + + v->get_handler = ngx_http_variable_not_found; + + p += size; + } + + return re; +} + + +ngx_int_t +ngx_http_regex_exec(ngx_http_request_t *r, ngx_http_regex_t *re, ngx_str_t *s) +{ + ngx_int_t rc, index; + ngx_uint_t i, n, len; + ngx_http_variable_value_t *vv; + ngx_http_core_main_conf_t *cmcf; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + if (re->ncaptures) { + len = cmcf->ncaptures; + + if (r->captures == NULL) { + r->captures = ngx_palloc(r->pool, len * sizeof(int)); + if (r->captures == NULL) { + return NGX_ERROR; + } + } + + } else { + len = 0; + } + + rc = ngx_regex_exec(re->regex, s, r->captures, len); + + if (rc == NGX_REGEX_NO_MATCHED) { + return NGX_DECLINED; + } + + if (rc < 0) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + ngx_regex_exec_n " failed: %i on \"%V\" using \"%V\"", + rc, s, &re->name); + return NGX_ERROR; + } + + for (i = 0; i < re->nvariables; i++) { + + n = re->variables[i].capture; + index = re->variables[i].index; + vv = &r->variables[index]; + + vv->len = r->captures[n + 1] - r->captures[n]; + vv->valid = 1; + vv->no_cacheable = 0; + vv->not_found = 0; + vv->data = &s->data[r->captures[n]]; + +#if (NGX_DEBUG) + { + ngx_http_variable_t *v; + + v = cmcf->variables.elts; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http regex set $%V to \"%v\"", &v[index].name, vv); + } +#endif + } + + r->ncaptures = rc * 2; + r->captures_data = s->data; + + return NGX_OK; +} + +#endif + + +ngx_int_t +ngx_http_variables_add_core_vars(ngx_conf_t *cf) +{ + ngx_http_variable_t *cv, *v; + ngx_http_core_main_conf_t *cmcf; + + cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); + + cmcf->variables_keys = ngx_pcalloc(cf->temp_pool, + sizeof(ngx_hash_keys_arrays_t)); + if (cmcf->variables_keys == NULL) { + return NGX_ERROR; + } + + cmcf->variables_keys->pool = cf->pool; + cmcf->variables_keys->temp_pool = cf->pool; + + if (ngx_hash_keys_array_init(cmcf->variables_keys, NGX_HASH_SMALL) + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_array_init(&cmcf->prefix_variables, cf->pool, 8, + sizeof(ngx_http_variable_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + for (cv = ngx_http_core_variables; cv->name.len; cv++) { + v = ngx_http_add_variable(cf, &cv->name, cv->flags); + if (v == NULL) { + return NGX_ERROR; + } + + *v = *cv; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_variables_init_vars(ngx_conf_t *cf) +{ + size_t len; + ngx_uint_t i, n; + ngx_hash_key_t *key; + ngx_hash_init_t hash; + ngx_http_variable_t *v, *av, *pv; + ngx_http_core_main_conf_t *cmcf; + + /* set the handlers for the indexed http variables */ + + cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); + + v = cmcf->variables.elts; + pv = cmcf->prefix_variables.elts; + key = cmcf->variables_keys->keys.elts; + + for (i = 0; i < cmcf->variables.nelts; i++) { + + for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) { + + av = key[n].value; + + if (v[i].name.len == key[n].key.len + && ngx_strncmp(v[i].name.data, key[n].key.data, v[i].name.len) + == 0) + { + v[i].get_handler = av->get_handler; + v[i].data = av->data; + + av->flags |= NGX_HTTP_VAR_INDEXED; + v[i].flags = av->flags; + + av->index = i; + + if (av->get_handler == NULL + || (av->flags & NGX_HTTP_VAR_WEAK)) + { + break; + } + + goto next; + } + } + + len = 0; + av = NULL; + + for (n = 0; n < cmcf->prefix_variables.nelts; n++) { + if (v[i].name.len >= pv[n].name.len && v[i].name.len > len + && ngx_strncmp(v[i].name.data, pv[n].name.data, pv[n].name.len) + == 0) + { + av = &pv[n]; + len = pv[n].name.len; + } + } + + if (av) { + v[i].get_handler = av->get_handler; + v[i].data = (uintptr_t) &v[i].name; + v[i].flags = av->flags; + + goto next; + } + + if (v[i].get_handler == NULL) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "unknown \"%V\" variable", &v[i].name); + + return NGX_ERROR; + } + + next: + continue; + } + + + for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) { + av = key[n].value; + + if (av->flags & NGX_HTTP_VAR_NOHASH) { + key[n].key.data = NULL; + } + } + + + hash.hash = &cmcf->variables_hash; + hash.key = ngx_hash_key; + hash.max_size = cmcf->variables_hash_max_size; + hash.bucket_size = cmcf->variables_hash_bucket_size; + hash.name = "variables_hash"; + hash.pool = cf->pool; + hash.temp_pool = NULL; + + if (ngx_hash_init(&hash, cmcf->variables_keys->keys.elts, + cmcf->variables_keys->keys.nelts) + != NGX_OK) + { + return NGX_ERROR; + } + + cmcf->variables_keys = NULL; + + return NGX_OK; +} diff --git a/app/nginx/src/http/ngx_http_variables.h b/app/nginx/src/http/ngx_http_variables.h new file mode 100644 index 0000000..df337de --- /dev/null +++ b/app/nginx/src/http/ngx_http_variables.h @@ -0,0 +1,114 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_VARIABLES_H_INCLUDED_ +#define _NGX_HTTP_VARIABLES_H_INCLUDED_ + + +#include +#include +#include + + +typedef ngx_variable_value_t ngx_http_variable_value_t; + +#define ngx_http_variable(v) { sizeof(v) - 1, 1, 0, 0, 0, (u_char *) v } + +typedef struct ngx_http_variable_s ngx_http_variable_t; + +typedef void (*ngx_http_set_variable_pt) (ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +typedef ngx_int_t (*ngx_http_get_variable_pt) (ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + + +#define NGX_HTTP_VAR_CHANGEABLE 1 +#define NGX_HTTP_VAR_NOCACHEABLE 2 +#define NGX_HTTP_VAR_INDEXED 4 +#define NGX_HTTP_VAR_NOHASH 8 +#define NGX_HTTP_VAR_WEAK 16 +#define NGX_HTTP_VAR_PREFIX 32 + + +struct ngx_http_variable_s { + ngx_str_t name; /* must be first to build the hash */ + ngx_http_set_variable_pt set_handler; + ngx_http_get_variable_pt get_handler; + uintptr_t data; + ngx_uint_t flags; + ngx_uint_t index; +}; + + +ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, + ngx_uint_t flags); +ngx_int_t ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name); +ngx_http_variable_value_t *ngx_http_get_indexed_variable(ngx_http_request_t *r, + ngx_uint_t index); +ngx_http_variable_value_t *ngx_http_get_flushed_variable(ngx_http_request_t *r, + ngx_uint_t index); + +ngx_http_variable_value_t *ngx_http_get_variable(ngx_http_request_t *r, + ngx_str_t *name, ngx_uint_t key); + +ngx_int_t ngx_http_variable_unknown_header(ngx_http_variable_value_t *v, + ngx_str_t *var, ngx_list_part_t *part, size_t prefix); + + +#if (NGX_PCRE) + +typedef struct { + ngx_uint_t capture; + ngx_int_t index; +} ngx_http_regex_variable_t; + + +typedef struct { + ngx_regex_t *regex; + ngx_uint_t ncaptures; + ngx_http_regex_variable_t *variables; + ngx_uint_t nvariables; + ngx_str_t name; +} ngx_http_regex_t; + + +typedef struct { + ngx_http_regex_t *regex; + void *value; +} ngx_http_map_regex_t; + + +ngx_http_regex_t *ngx_http_regex_compile(ngx_conf_t *cf, + ngx_regex_compile_t *rc); +ngx_int_t ngx_http_regex_exec(ngx_http_request_t *r, ngx_http_regex_t *re, + ngx_str_t *s); + +#endif + + +typedef struct { + ngx_hash_combined_t hash; +#if (NGX_PCRE) + ngx_http_map_regex_t *regex; + ngx_uint_t nregex; +#endif +} ngx_http_map_t; + + +void *ngx_http_map_find(ngx_http_request_t *r, ngx_http_map_t *map, + ngx_str_t *match); + + +ngx_int_t ngx_http_variables_add_core_vars(ngx_conf_t *cf); +ngx_int_t ngx_http_variables_init_vars(ngx_conf_t *cf); + + +extern ngx_http_variable_value_t ngx_http_variable_null_value; +extern ngx_http_variable_value_t ngx_http_variable_true_value; + + +#endif /* _NGX_HTTP_VARIABLES_H_INCLUDED_ */ diff --git a/app/nginx/src/http/ngx_http_write_filter_module.c b/app/nginx/src/http/ngx_http_write_filter_module.c new file mode 100644 index 0000000..0036231 --- /dev/null +++ b/app/nginx/src/http/ngx_http_write_filter_module.c @@ -0,0 +1,327 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static ngx_int_t ngx_http_write_filter_init(ngx_conf_t *cf); + + +static ngx_http_module_t ngx_http_write_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_write_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL, /* merge location configuration */ +}; + + +ngx_module_t ngx_http_write_filter_module = { + NGX_MODULE_V1, + &ngx_http_write_filter_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +ngx_int_t +ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + off_t size, sent, nsent, limit; + ngx_uint_t last, flush, sync; + ngx_msec_t delay; + ngx_chain_t *cl, *ln, **ll, *chain; + ngx_connection_t *c; + ngx_http_core_loc_conf_t *clcf; + + c = r->connection; + + if (c->error) { + return NGX_ERROR; + } + + size = 0; + flush = 0; + sync = 0; + last = 0; + ll = &r->out; + + /* find the size, the flush point and the last link of the saved chain */ + + for (cl = r->out; cl; cl = cl->next) { + ll = &cl->next; + + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, + "write old buf t:%d f:%d %p, pos %p, size: %z " + "file: %O, size: %O", + cl->buf->temporary, cl->buf->in_file, + cl->buf->start, cl->buf->pos, + cl->buf->last - cl->buf->pos, + cl->buf->file_pos, + cl->buf->file_last - cl->buf->file_pos); + +#if 1 + if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "zero size buf in writer " + "t:%d r:%d f:%d %p %p-%p %p %O-%O", + cl->buf->temporary, + cl->buf->recycled, + cl->buf->in_file, + cl->buf->start, + cl->buf->pos, + cl->buf->last, + cl->buf->file, + cl->buf->file_pos, + cl->buf->file_last); + + ngx_debug_point(); + return NGX_ERROR; + } +#endif + + size += ngx_buf_size(cl->buf); + + if (cl->buf->flush || cl->buf->recycled) { + flush = 1; + } + + if (cl->buf->sync) { + sync = 1; + } + + if (cl->buf->last_buf) { + last = 1; + } + } + + /* add the new chain to the existent one */ + + for (ln = in; ln; ln = ln->next) { + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = ln->buf; + *ll = cl; + ll = &cl->next; + + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, + "write new buf t:%d f:%d %p, pos %p, size: %z " + "file: %O, size: %O", + cl->buf->temporary, cl->buf->in_file, + cl->buf->start, cl->buf->pos, + cl->buf->last - cl->buf->pos, + cl->buf->file_pos, + cl->buf->file_last - cl->buf->file_pos); + +#if 1 + if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "zero size buf in writer " + "t:%d r:%d f:%d %p %p-%p %p %O-%O", + cl->buf->temporary, + cl->buf->recycled, + cl->buf->in_file, + cl->buf->start, + cl->buf->pos, + cl->buf->last, + cl->buf->file, + cl->buf->file_pos, + cl->buf->file_last); + + ngx_debug_point(); + return NGX_ERROR; + } +#endif + + size += ngx_buf_size(cl->buf); + + if (cl->buf->flush || cl->buf->recycled) { + flush = 1; + } + + if (cl->buf->sync) { + sync = 1; + } + + if (cl->buf->last_buf) { + last = 1; + } + } + + *ll = NULL; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http write filter: l:%ui f:%ui s:%O", last, flush, size); + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + /* + * avoid the output if there are no last buf, no flush point, + * there are the incoming bufs and the size of all bufs + * is smaller than "postpone_output" directive + */ + + if (!last && !flush && in && size < (off_t) clcf->postpone_output) { + return NGX_OK; + } + + if (c->write->delayed) { + c->buffered |= NGX_HTTP_WRITE_BUFFERED; + return NGX_AGAIN; + } + + if (size == 0 + && !(c->buffered & NGX_LOWLEVEL_BUFFERED) + && !(last && c->need_last_buf)) + { + if (last || flush || sync) { + for (cl = r->out; cl; /* void */) { + ln = cl; + cl = cl->next; + ngx_free_chain(r->pool, ln); + } + + r->out = NULL; + c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; + + return NGX_OK; + } + + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "the http output chain is empty"); + + ngx_debug_point(); + + return NGX_ERROR; + } + + if (r->limit_rate) { + if (r->limit_rate_after == 0) { + r->limit_rate_after = clcf->limit_rate_after; + } + + limit = (off_t) r->limit_rate * (ngx_time() - r->start_sec + 1) + - (c->sent - r->limit_rate_after); + + if (limit <= 0) { + c->write->delayed = 1; + delay = (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1); + ngx_add_timer(c->write, delay); + + c->buffered |= NGX_HTTP_WRITE_BUFFERED; + + return NGX_AGAIN; + } + + if (clcf->sendfile_max_chunk + && (off_t) clcf->sendfile_max_chunk < limit) + { + limit = clcf->sendfile_max_chunk; + } + + } else { + limit = clcf->sendfile_max_chunk; + } + + sent = c->sent; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http write filter limit %O", limit); + + chain = c->send_chain(c, r->out, limit); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http write filter %p", chain); + + if (chain == NGX_CHAIN_ERROR) { + c->error = 1; + return NGX_ERROR; + } + + if (r->limit_rate) { + + nsent = c->sent; + + if (r->limit_rate_after) { + + sent -= r->limit_rate_after; + if (sent < 0) { + sent = 0; + } + + nsent -= r->limit_rate_after; + if (nsent < 0) { + nsent = 0; + } + } + + delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate); + + if (delay > 0) { + limit = 0; + c->write->delayed = 1; + ngx_add_timer(c->write, delay); + } + } + + if (limit + && c->write->ready + && c->sent - sent >= limit - (off_t) (2 * ngx_pagesize)) + { + c->write->delayed = 1; + ngx_add_timer(c->write, 1); + } + + for (cl = r->out; cl && cl != chain; /* void */) { + ln = cl; + cl = cl->next; + ngx_free_chain(r->pool, ln); + } + + r->out = chain; + + if (chain) { + c->buffered |= NGX_HTTP_WRITE_BUFFERED; + return NGX_AGAIN; + } + + c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; + + if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) { + return NGX_AGAIN; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_write_filter_init(ngx_conf_t *cf) +{ + ngx_http_top_body_filter = ngx_http_write_filter; + + return NGX_OK; +} diff --git a/app/nginx/src/http/v2/ngx_http_v2.c b/app/nginx/src/http/v2/ngx_http_v2.c new file mode 100644 index 0000000..55db58e --- /dev/null +++ b/app/nginx/src/http/v2/ngx_http_v2.c @@ -0,0 +1,4484 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include + + +/* errors */ +#define NGX_HTTP_V2_NO_ERROR 0x0 +#define NGX_HTTP_V2_PROTOCOL_ERROR 0x1 +#define NGX_HTTP_V2_INTERNAL_ERROR 0x2 +#define NGX_HTTP_V2_FLOW_CTRL_ERROR 0x3 +#define NGX_HTTP_V2_SETTINGS_TIMEOUT 0x4 +#define NGX_HTTP_V2_STREAM_CLOSED 0x5 +#define NGX_HTTP_V2_SIZE_ERROR 0x6 +#define NGX_HTTP_V2_REFUSED_STREAM 0x7 +#define NGX_HTTP_V2_CANCEL 0x8 +#define NGX_HTTP_V2_COMP_ERROR 0x9 +#define NGX_HTTP_V2_CONNECT_ERROR 0xa +#define NGX_HTTP_V2_ENHANCE_YOUR_CALM 0xb +#define NGX_HTTP_V2_INADEQUATE_SECURITY 0xc +#define NGX_HTTP_V2_HTTP_1_1_REQUIRED 0xd + +/* frame sizes */ +#define NGX_HTTP_V2_RST_STREAM_SIZE 4 +#define NGX_HTTP_V2_PRIORITY_SIZE 5 +#define NGX_HTTP_V2_PING_SIZE 8 +#define NGX_HTTP_V2_GOAWAY_SIZE 8 +#define NGX_HTTP_V2_WINDOW_UPDATE_SIZE 4 + +#define NGX_HTTP_V2_STREAM_ID_SIZE 4 + +#define NGX_HTTP_V2_SETTINGS_PARAM_SIZE 6 + +/* settings fields */ +#define NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING 0x1 +#define NGX_HTTP_V2_MAX_STREAMS_SETTING 0x3 +#define NGX_HTTP_V2_INIT_WINDOW_SIZE_SETTING 0x4 +#define NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING 0x5 + +#define NGX_HTTP_V2_FRAME_BUFFER_SIZE 24 + +#define NGX_HTTP_V2_DEFAULT_FRAME_SIZE (1 << 14) + +#define NGX_HTTP_V2_ROOT (void *) -1 + + +static void ngx_http_v2_read_handler(ngx_event_t *rev); +static void ngx_http_v2_write_handler(ngx_event_t *wev); +static void ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c); + +static u_char *ngx_http_v2_state_proxy_protocol(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_preface(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_preface_end(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_head(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_data(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_header_block(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_field_len(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_field_huff(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_field_raw(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_field_skip(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_header_complete(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_handle_continuation(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end, ngx_http_v2_handler_pt handler); +static u_char *ngx_http_v2_state_priority(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_rst_stream(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_settings(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_push_promise(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_ping(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_goaway(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_window_update(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_continuation(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_complete(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_skip_padded(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_skip(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_save(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end, ngx_http_v2_handler_pt handler); +static u_char *ngx_http_v2_state_headers_save(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end, ngx_http_v2_handler_pt handler); +static u_char *ngx_http_v2_connection_error(ngx_http_v2_connection_t *h2c, + ngx_uint_t err); + +static ngx_int_t ngx_http_v2_parse_int(ngx_http_v2_connection_t *h2c, + u_char **pos, u_char *end, ngx_uint_t prefix); + +static ngx_http_v2_stream_t *ngx_http_v2_create_stream( + ngx_http_v2_connection_t *h2c); +static ngx_http_v2_node_t *ngx_http_v2_get_node_by_id( + ngx_http_v2_connection_t *h2c, ngx_uint_t sid, ngx_uint_t alloc); +static ngx_http_v2_node_t *ngx_http_v2_get_closed_node( + ngx_http_v2_connection_t *h2c); +#define ngx_http_v2_index_size(h2scf) (h2scf->streams_index_mask + 1) +#define ngx_http_v2_index(h2scf, sid) ((sid >> 1) & h2scf->streams_index_mask) + +static ngx_int_t ngx_http_v2_send_settings(ngx_http_v2_connection_t *h2c, + ngx_uint_t ack); +static ngx_int_t ngx_http_v2_settings_frame_handler( + ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); +static ngx_int_t ngx_http_v2_send_window_update(ngx_http_v2_connection_t *h2c, + ngx_uint_t sid, size_t window); +static ngx_int_t ngx_http_v2_send_rst_stream(ngx_http_v2_connection_t *h2c, + ngx_uint_t sid, ngx_uint_t status); +static ngx_int_t ngx_http_v2_send_goaway(ngx_http_v2_connection_t *h2c, + ngx_uint_t status); + +static ngx_http_v2_out_frame_t *ngx_http_v2_get_frame( + ngx_http_v2_connection_t *h2c, size_t length, ngx_uint_t type, + u_char flags, ngx_uint_t sid); +static ngx_int_t ngx_http_v2_frame_handler(ngx_http_v2_connection_t *h2c, + ngx_http_v2_out_frame_t *frame); + +static ngx_int_t ngx_http_v2_validate_header(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_pseudo_header(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_parse_path(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_parse_method(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_parse_scheme(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_parse_authority(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_construct_request_line(ngx_http_request_t *r); +static ngx_int_t ngx_http_v2_cookie(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_construct_cookie_header(ngx_http_request_t *r); +static void ngx_http_v2_run_request(ngx_http_request_t *r); +static ngx_int_t ngx_http_v2_process_request_body(ngx_http_request_t *r, + u_char *pos, size_t size, ngx_uint_t last); +static ngx_int_t ngx_http_v2_filter_request_body(ngx_http_request_t *r); +static void ngx_http_v2_read_client_request_body_handler(ngx_http_request_t *r); + +static ngx_int_t ngx_http_v2_terminate_stream(ngx_http_v2_connection_t *h2c, + ngx_http_v2_stream_t *stream, ngx_uint_t status); +static void ngx_http_v2_close_stream_handler(ngx_event_t *ev); +static void ngx_http_v2_handle_connection_handler(ngx_event_t *rev); +static void ngx_http_v2_idle_handler(ngx_event_t *rev); +static void ngx_http_v2_finalize_connection(ngx_http_v2_connection_t *h2c, + ngx_uint_t status); + +static ngx_int_t ngx_http_v2_adjust_windows(ngx_http_v2_connection_t *h2c, + ssize_t delta); +static void ngx_http_v2_set_dependency(ngx_http_v2_connection_t *h2c, + ngx_http_v2_node_t *node, ngx_uint_t depend, ngx_uint_t exclusive); +static void ngx_http_v2_node_children_update(ngx_http_v2_node_t *node); + +static void ngx_http_v2_pool_cleanup(void *data); + + +static ngx_http_v2_handler_pt ngx_http_v2_frame_states[] = { + ngx_http_v2_state_data, + ngx_http_v2_state_headers, + ngx_http_v2_state_priority, + ngx_http_v2_state_rst_stream, + ngx_http_v2_state_settings, + ngx_http_v2_state_push_promise, + ngx_http_v2_state_ping, + ngx_http_v2_state_goaway, + ngx_http_v2_state_window_update, + ngx_http_v2_state_continuation +}; + +#define NGX_HTTP_V2_FRAME_STATES \ + (sizeof(ngx_http_v2_frame_states) / sizeof(ngx_http_v2_handler_pt)) + + +void +ngx_http_v2_init(ngx_event_t *rev) +{ + ngx_connection_t *c; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_v2_srv_conf_t *h2scf; + ngx_http_v2_main_conf_t *h2mcf; + ngx_http_v2_connection_t *h2c; + + c = rev->data; + hc = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "init http2 connection"); + + c->log->action = "processing HTTP/2 connection"; + + h2mcf = ngx_http_get_module_main_conf(hc->conf_ctx, ngx_http_v2_module); + + if (h2mcf->recv_buffer == NULL) { + h2mcf->recv_buffer = ngx_palloc(ngx_cycle->pool, + h2mcf->recv_buffer_size); + if (h2mcf->recv_buffer == NULL) { + ngx_http_close_connection(c); + return; + } + } + + h2c = ngx_pcalloc(c->pool, sizeof(ngx_http_v2_connection_t)); + if (h2c == NULL) { + ngx_http_close_connection(c); + return; + } + + h2c->connection = c; + h2c->http_connection = hc; + + h2c->send_window = NGX_HTTP_V2_DEFAULT_WINDOW; + h2c->recv_window = NGX_HTTP_V2_MAX_WINDOW; + + h2c->init_window = NGX_HTTP_V2_DEFAULT_WINDOW; + + h2c->frame_size = NGX_HTTP_V2_DEFAULT_FRAME_SIZE; + + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); + + h2c->pool = ngx_create_pool(h2scf->pool_size, h2c->connection->log); + if (h2c->pool == NULL) { + ngx_http_close_connection(c); + return; + } + + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + ngx_http_close_connection(c); + return; + } + + cln->handler = ngx_http_v2_pool_cleanup; + cln->data = h2c; + + h2c->streams_index = ngx_pcalloc(c->pool, ngx_http_v2_index_size(h2scf) + * sizeof(ngx_http_v2_node_t *)); + if (h2c->streams_index == NULL) { + ngx_http_close_connection(c); + return; + } + + if (ngx_http_v2_send_settings(h2c, 0) == NGX_ERROR) { + ngx_http_close_connection(c); + return; + } + + if (ngx_http_v2_send_window_update(h2c, 0, NGX_HTTP_V2_MAX_WINDOW + - NGX_HTTP_V2_DEFAULT_WINDOW) + == NGX_ERROR) + { + ngx_http_close_connection(c); + return; + } + + h2c->state.handler = hc->proxy_protocol ? ngx_http_v2_state_proxy_protocol + : ngx_http_v2_state_preface; + + ngx_queue_init(&h2c->waiting); + ngx_queue_init(&h2c->dependencies); + ngx_queue_init(&h2c->closed); + + c->data = h2c; + + rev->handler = ngx_http_v2_read_handler; + c->write->handler = ngx_http_v2_write_handler; + + c->idle = 1; + + ngx_http_v2_read_handler(rev); +} + + +static void +ngx_http_v2_read_handler(ngx_event_t *rev) +{ + u_char *p, *end; + size_t available; + ssize_t n; + ngx_connection_t *c; + ngx_http_v2_main_conf_t *h2mcf; + ngx_http_v2_connection_t *h2c; + + c = rev->data; + h2c = c->data; + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 read handler"); + + h2c->blocked = 1; + + if (c->close) { + c->close = 0; + + if (!h2c->goaway) { + h2c->goaway = 1; + + if (ngx_http_v2_send_goaway(h2c, NGX_HTTP_V2_NO_ERROR) + == NGX_ERROR) + { + ngx_http_v2_finalize_connection(h2c, 0); + return; + } + + if (ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) { + ngx_http_v2_finalize_connection(h2c, 0); + return; + } + } + + h2c->blocked = 0; + + return; + } + + h2mcf = ngx_http_get_module_main_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + available = h2mcf->recv_buffer_size - 2 * NGX_HTTP_V2_STATE_BUFFER_SIZE; + + do { + p = h2mcf->recv_buffer; + + ngx_memcpy(p, h2c->state.buffer, NGX_HTTP_V2_STATE_BUFFER_SIZE); + end = p + h2c->state.buffer_used; + + n = c->recv(c, end, available); + + if (n == NGX_AGAIN) { + break; + } + + if (n == 0 && (h2c->state.incomplete || h2c->processing)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client prematurely closed connection"); + } + + if (n == 0 || n == NGX_ERROR) { + c->error = 1; + ngx_http_v2_finalize_connection(h2c, 0); + return; + } + + end += n; + + h2c->state.buffer_used = 0; + h2c->state.incomplete = 0; + + do { + p = h2c->state.handler(h2c, p, end); + + if (p == NULL) { + return; + } + + } while (p != end); + + } while (rev->ready); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + return; + } + + if (h2c->last_out && ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) { + ngx_http_v2_finalize_connection(h2c, 0); + return; + } + + h2c->blocked = 0; + + if (h2c->processing) { + if (rev->timer_set) { + ngx_del_timer(rev); + } + + return; + } + + ngx_http_v2_handle_connection(h2c); +} + + +static void +ngx_http_v2_write_handler(ngx_event_t *wev) +{ + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_v2_connection_t *h2c; + + c = wev->data; + h2c = c->data; + + if (wev->timedout) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http2 write event timed out"); + c->error = 1; + ngx_http_v2_finalize_connection(h2c, 0); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 write handler"); + + if (h2c->last_out == NULL && !c->buffered) { + + if (wev->timer_set) { + ngx_del_timer(wev); + } + + ngx_http_v2_handle_connection(h2c); + return; + } + + h2c->blocked = 1; + + rc = ngx_http_v2_send_output_queue(h2c); + + if (rc == NGX_ERROR) { + ngx_http_v2_finalize_connection(h2c, 0); + return; + } + + h2c->blocked = 0; + + if (rc == NGX_AGAIN) { + return; + } + + ngx_http_v2_handle_connection(h2c); +} + + +ngx_int_t +ngx_http_v2_send_output_queue(ngx_http_v2_connection_t *h2c) +{ + int tcp_nodelay; + ngx_chain_t *cl; + ngx_event_t *wev; + ngx_connection_t *c; + ngx_http_v2_out_frame_t *out, *frame, *fn; + ngx_http_core_loc_conf_t *clcf; + + c = h2c->connection; + + if (c->error) { + return NGX_ERROR; + } + + wev = c->write; + + if (!wev->ready) { + return NGX_AGAIN; + } + + cl = NULL; + out = NULL; + + for (frame = h2c->last_out; frame; frame = fn) { + frame->last->next = cl; + cl = frame->first; + + fn = frame->next; + frame->next = out; + out = frame; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http2 frame out: %p sid:%ui bl:%d len:%uz", + out, out->stream ? out->stream->node->id : 0, + out->blocked, out->length); + } + + cl = c->send_chain(c, cl, 0); + + if (cl == NGX_CHAIN_ERROR) { + goto error; + } + + clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx, + ngx_http_core_module); + + if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { + goto error; + } + + if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) { + if (ngx_tcp_push(c->fd) == -1) { + ngx_connection_error(c, ngx_socket_errno, ngx_tcp_push_n " failed"); + goto error; + } + + c->tcp_nopush = NGX_TCP_NOPUSH_UNSET; + tcp_nodelay = ngx_tcp_nodelay_and_tcp_nopush ? 1 : 0; + + } else { + tcp_nodelay = 1; + } + + if (tcp_nodelay + && clcf->tcp_nodelay + && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) + { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) + == -1) + { +#if (NGX_SOLARIS) + /* Solaris returns EINVAL if a socket has been shut down */ + c->log_error = NGX_ERROR_IGNORE_EINVAL; +#endif + + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + + c->log_error = NGX_ERROR_INFO; + goto error; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + for ( /* void */ ; out; out = fn) { + fn = out->next; + + if (out->handler(h2c, out) != NGX_OK) { + out->blocked = 1; + break; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http2 frame sent: %p sid:%ui bl:%d len:%uz", + out, out->stream ? out->stream->node->id : 0, + out->blocked, out->length); + } + + frame = NULL; + + for ( /* void */ ; out; out = fn) { + fn = out->next; + out->next = frame; + frame = out; + } + + h2c->last_out = frame; + + if (!wev->ready) { + ngx_add_timer(wev, clcf->send_timeout); + return NGX_AGAIN; + } + + if (wev->timer_set) { + ngx_del_timer(wev); + } + + return NGX_OK; + +error: + + c->error = 1; + + if (!h2c->blocked) { + ngx_post_event(wev, &ngx_posted_events); + } + + return NGX_ERROR; +} + + +static void +ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c) +{ + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_v2_srv_conf_t *h2scf; + + if (h2c->last_out || h2c->processing) { + return; + } + + c = h2c->connection; + + if (c->error) { + ngx_http_close_connection(c); + return; + } + + if (c->buffered) { + h2c->blocked = 1; + + rc = ngx_http_v2_send_output_queue(h2c); + + h2c->blocked = 0; + + if (rc == NGX_ERROR) { + ngx_http_close_connection(c); + return; + } + + if (rc == NGX_AGAIN) { + return; + } + + /* rc == NGX_OK */ + } + + if (h2c->goaway) { + ngx_http_close_connection(c); + return; + } + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + if (h2c->state.incomplete) { + ngx_add_timer(c->read, h2scf->recv_timeout); + return; + } + + ngx_destroy_pool(h2c->pool); + + h2c->pool = NULL; + h2c->free_frames = NULL; + h2c->free_fake_connections = NULL; + +#if (NGX_HTTP_SSL) + if (c->ssl) { + ngx_ssl_free_buffer(c); + } +#endif + + c->destroyed = 1; + ngx_reusable_connection(c, 1); + + c->write->handler = ngx_http_empty_handler; + c->read->handler = ngx_http_v2_idle_handler; + + if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + ngx_add_timer(c->read, h2scf->idle_timeout); +} + + +static u_char * +ngx_http_v2_state_proxy_protocol(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + ngx_log_t *log; + + log = h2c->connection->log; + log->action = "reading PROXY protocol"; + + pos = ngx_proxy_protocol_read(h2c->connection, pos, end); + + log->action = "processing HTTP/2 connection"; + + if (pos == NULL) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + + return ngx_http_v2_state_preface(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_preface(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + static const u_char preface[] = "PRI * HTTP/2.0\r\n"; + + if ((size_t) (end - pos) < sizeof(preface) - 1) { + return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_preface); + } + + if (ngx_memcmp(pos, preface, sizeof(preface) - 1) != 0) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "invalid http2 connection preface \"%*s\"", + sizeof(preface) - 1, pos); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + + return ngx_http_v2_state_preface_end(h2c, pos + sizeof(preface) - 1, end); +} + + +static u_char * +ngx_http_v2_state_preface_end(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + static const u_char preface[] = "\r\nSM\r\n\r\n"; + + if ((size_t) (end - pos) < sizeof(preface) - 1) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_preface_end); + } + + if (ngx_memcmp(pos, preface, sizeof(preface) - 1) != 0) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "invalid http2 connection preface \"%*s\"", + sizeof(preface) - 1, pos); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 preface verified"); + + return ngx_http_v2_state_head(h2c, pos + sizeof(preface) - 1, end); +} + + +static u_char * +ngx_http_v2_state_head(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) +{ + uint32_t head; + ngx_uint_t type; + + if (end - pos < NGX_HTTP_V2_FRAME_HEADER_SIZE) { + return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_head); + } + + head = ngx_http_v2_parse_uint32(pos); + + h2c->state.length = ngx_http_v2_parse_length(head); + h2c->state.flags = pos[4]; + + h2c->state.sid = ngx_http_v2_parse_sid(&pos[5]); + + pos += NGX_HTTP_V2_FRAME_HEADER_SIZE; + + type = ngx_http_v2_parse_type(head); + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "process http2 frame type:%ui f:%Xd l:%uz sid:%ui", + type, h2c->state.flags, h2c->state.length, h2c->state.sid); + + if (type >= NGX_HTTP_V2_FRAME_STATES) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 frame with unknown type %ui", type); + return ngx_http_v2_state_skip(h2c, pos, end); + } + + return ngx_http_v2_frame_states[type](h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_data(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) +{ + size_t size; + ngx_http_v2_node_t *node; + ngx_http_v2_stream_t *stream; + + size = h2c->state.length; + + if (h2c->state.flags & NGX_HTTP_V2_PADDED_FLAG) { + + if (h2c->state.length == 0) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent padded DATA frame " + "with incorrect length: 0"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (end - pos == 0) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_data); + } + + h2c->state.padding = *pos++; + + if (h2c->state.padding >= size) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent padded DATA frame " + "with incorrect length: %uz, padding: %uz", + size, h2c->state.padding); + + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_PROTOCOL_ERROR); + } + + h2c->state.length -= 1 + h2c->state.padding; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 DATA frame"); + + if (size > h2c->recv_window) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client violated connection flow control: " + "received DATA frame length %uz, available window %uz", + size, h2c->recv_window); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_FLOW_CTRL_ERROR); + } + + h2c->recv_window -= size; + + if (h2c->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4) { + + if (ngx_http_v2_send_window_update(h2c, 0, NGX_HTTP_V2_MAX_WINDOW + - h2c->recv_window) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + h2c->recv_window = NGX_HTTP_V2_MAX_WINDOW; + } + + node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0); + + if (node == NULL || node->stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "unknown http2 stream"); + + return ngx_http_v2_state_skip_padded(h2c, pos, end); + } + + stream = node->stream; + + if (size > stream->recv_window) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client violated flow control for stream %ui: " + "received DATA frame length %uz, available window %uz", + node->id, size, stream->recv_window); + + if (ngx_http_v2_terminate_stream(h2c, stream, + NGX_HTTP_V2_FLOW_CTRL_ERROR) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + return ngx_http_v2_state_skip_padded(h2c, pos, end); + } + + stream->recv_window -= size; + + if (stream->no_flow_control + && stream->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4) + { + if (ngx_http_v2_send_window_update(h2c, node->id, + NGX_HTTP_V2_MAX_WINDOW + - stream->recv_window) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + stream->recv_window = NGX_HTTP_V2_MAX_WINDOW; + } + + if (stream->in_closed) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent DATA frame for half-closed stream %ui", + node->id); + + if (ngx_http_v2_terminate_stream(h2c, stream, + NGX_HTTP_V2_STREAM_CLOSED) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + return ngx_http_v2_state_skip_padded(h2c, pos, end); + } + + h2c->state.stream = stream; + + return ngx_http_v2_state_read_data(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + size_t size; + ngx_buf_t *buf; + ngx_int_t rc; + ngx_http_request_t *r; + ngx_http_v2_stream_t *stream; + ngx_http_v2_srv_conf_t *h2scf; + + stream = h2c->state.stream; + + if (stream == NULL) { + return ngx_http_v2_state_skip_padded(h2c, pos, end); + } + + if (stream->skip_data) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "skipping http2 DATA frame"); + + return ngx_http_v2_state_skip_padded(h2c, pos, end); + } + + size = end - pos; + + if (size >= h2c->state.length) { + size = h2c->state.length; + stream->in_closed = h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG; + } + + r = stream->request; + + if (r->request_body) { + rc = ngx_http_v2_process_request_body(r, pos, size, stream->in_closed); + + if (rc != NGX_OK) { + stream->skip_data = 1; + ngx_http_finalize_request(r, rc); + } + + } else if (size) { + buf = stream->preread; + + if (buf == NULL) { + h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module); + + buf = ngx_create_temp_buf(r->pool, h2scf->preread_size); + if (buf == NULL) { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + stream->preread = buf; + } + + if (size > (size_t) (buf->end - buf->last)) { + ngx_log_error(NGX_LOG_ALERT, h2c->connection->log, 0, + "http2 preread buffer overflow"); + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + buf->last = ngx_cpymem(buf->last, pos, size); + } + + pos += size; + h2c->state.length -= size; + + if (h2c->state.length) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_read_data); + } + + if (h2c->state.padding) { + return ngx_http_v2_state_skip_padded(h2c, pos, end); + } + + return ngx_http_v2_state_complete(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + size_t size; + ngx_uint_t padded, priority, depend, dependency, excl, weight; + ngx_uint_t status; + ngx_http_v2_node_t *node; + ngx_http_v2_stream_t *stream; + ngx_http_v2_srv_conf_t *h2scf; + + padded = h2c->state.flags & NGX_HTTP_V2_PADDED_FLAG; + priority = h2c->state.flags & NGX_HTTP_V2_PRIORITY_FLAG; + + size = 0; + + if (padded) { + size++; + } + + if (priority) { + size += sizeof(uint32_t) + 1; + } + + if (h2c->state.length < size) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent HEADERS frame with incorrect length %uz", + h2c->state.length); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (h2c->state.length == size) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent HEADERS frame with empty header block"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (h2c->goaway) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "skipping http2 HEADERS frame"); + return ngx_http_v2_state_skip(h2c, pos, end); + } + + if ((size_t) (end - pos) < size) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_headers); + } + + h2c->state.length -= size; + + if (padded) { + h2c->state.padding = *pos++; + + if (h2c->state.padding > h2c->state.length) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent padded HEADERS frame " + "with incorrect length: %uz, padding: %uz", + h2c->state.length, h2c->state.padding); + + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_PROTOCOL_ERROR); + } + + h2c->state.length -= h2c->state.padding; + } + + depend = 0; + excl = 0; + weight = 16; + + if (priority) { + dependency = ngx_http_v2_parse_uint32(pos); + + depend = dependency & 0x7fffffff; + excl = dependency >> 31; + weight = pos[4] + 1; + + pos += sizeof(uint32_t) + 1; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 HEADERS frame sid:%ui on %ui excl:%ui weight:%ui", + h2c->state.sid, depend, excl, weight); + + if (h2c->state.sid % 2 == 0 || h2c->state.sid <= h2c->last_sid) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent HEADERS frame with incorrect identifier " + "%ui, the last was %ui", h2c->state.sid, h2c->last_sid); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + + h2c->last_sid = h2c->state.sid; + + h2c->state.pool = ngx_create_pool(1024, h2c->connection->log); + if (h2c->state.pool == NULL) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + if (depend == h2c->state.sid) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent HEADERS frame for stream %ui " + "with incorrect dependency", h2c->state.sid); + + status = NGX_HTTP_V2_PROTOCOL_ERROR; + goto rst_stream; + } + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + h2c->state.header_limit = h2scf->max_header_size; + + if (h2c->processing >= h2scf->concurrent_streams) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "concurrent streams exceeded %ui", h2c->processing); + + status = NGX_HTTP_V2_REFUSED_STREAM; + goto rst_stream; + } + + if (!h2c->settings_ack + && !(h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG) + && h2scf->preread_size < NGX_HTTP_V2_DEFAULT_WINDOW) + { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent stream with data " + "before settings were acknowledged"); + + status = NGX_HTTP_V2_REFUSED_STREAM; + goto rst_stream; + } + + node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 1); + + if (node == NULL) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + if (node->parent) { + ngx_queue_remove(&node->reuse); + h2c->closed_nodes--; + } + + stream = ngx_http_v2_create_stream(h2c); + if (stream == NULL) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + h2c->state.stream = stream; + + stream->pool = h2c->state.pool; + h2c->state.keep_pool = 1; + + stream->request->request_length = h2c->state.length; + + stream->in_closed = h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG; + stream->node = node; + + node->stream = stream; + + if (priority || node->parent == NULL) { + node->weight = weight; + ngx_http_v2_set_dependency(h2c, node, depend, excl); + } + + if (h2c->connection->requests >= h2scf->max_requests) { + h2c->goaway = 1; + + if (ngx_http_v2_send_goaway(h2c, NGX_HTTP_V2_NO_ERROR) == NGX_ERROR) { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + } + + return ngx_http_v2_state_header_block(h2c, pos, end); + +rst_stream: + + if (ngx_http_v2_send_rst_stream(h2c, h2c->state.sid, status) != NGX_OK) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + return ngx_http_v2_state_header_block(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_header_block(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + u_char ch; + ngx_int_t value; + ngx_uint_t indexed, size_update, prefix; + + if (end - pos < 1) { + return ngx_http_v2_state_headers_save(h2c, pos, end, + ngx_http_v2_state_header_block); + } + + if (!(h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) + && h2c->state.length < NGX_HTTP_V2_INT_OCTETS) + { + return ngx_http_v2_handle_continuation(h2c, pos, end, + ngx_http_v2_state_header_block); + } + + size_update = 0; + indexed = 0; + + ch = *pos; + + if (ch >= (1 << 7)) { + /* indexed header field */ + indexed = 1; + prefix = ngx_http_v2_prefix(7); + + } else if (ch >= (1 << 6)) { + /* literal header field with incremental indexing */ + h2c->state.index = 1; + prefix = ngx_http_v2_prefix(6); + + } else if (ch >= (1 << 5)) { + /* dynamic table size update */ + size_update = 1; + prefix = ngx_http_v2_prefix(5); + + } else if (ch >= (1 << 4)) { + /* literal header field never indexed */ + prefix = ngx_http_v2_prefix(4); + + } else { + /* literal header field without indexing */ + prefix = ngx_http_v2_prefix(4); + } + + value = ngx_http_v2_parse_int(h2c, &pos, end, prefix); + + if (value < 0) { + if (value == NGX_AGAIN) { + return ngx_http_v2_state_headers_save(h2c, pos, end, + ngx_http_v2_state_header_block); + } + + if (value == NGX_DECLINED) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent header block with too long %s value", + size_update ? "size update" : "header index"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); + } + + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent header block with incorrect length"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (indexed) { + if (ngx_http_v2_get_indexed_header(h2c, value, 0) != NGX_OK) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); + } + + return ngx_http_v2_state_process_header(h2c, pos, end); + } + + if (size_update) { + if (ngx_http_v2_table_size(h2c, value) != NGX_OK) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); + } + + return ngx_http_v2_state_header_complete(h2c, pos, end); + } + + if (value == 0) { + h2c->state.parse_name = 1; + + } else if (ngx_http_v2_get_indexed_header(h2c, value, 1) != NGX_OK) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); + } + + h2c->state.parse_value = 1; + + return ngx_http_v2_state_field_len(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_field_len(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + size_t alloc; + ngx_int_t len; + ngx_uint_t huff; + ngx_http_v2_srv_conf_t *h2scf; + + if (!(h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) + && h2c->state.length < NGX_HTTP_V2_INT_OCTETS) + { + return ngx_http_v2_handle_continuation(h2c, pos, end, + ngx_http_v2_state_field_len); + } + + if (h2c->state.length < 1) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent header block with incorrect length"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (end - pos < 1) { + return ngx_http_v2_state_headers_save(h2c, pos, end, + ngx_http_v2_state_field_len); + } + + huff = *pos >> 7; + len = ngx_http_v2_parse_int(h2c, &pos, end, ngx_http_v2_prefix(7)); + + if (len < 0) { + if (len == NGX_AGAIN) { + return ngx_http_v2_state_headers_save(h2c, pos, end, + ngx_http_v2_state_field_len); + } + + if (len == NGX_DECLINED) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent header field with too long length value"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); + } + + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent header block with incorrect length"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack %s string length: %i", + huff ? "encoded" : "raw", len); + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + if ((size_t) len > h2scf->max_field_size) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client exceeded http2_max_field_size limit"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_ENHANCE_YOUR_CALM); + } + + h2c->state.field_rest = len; + + if (h2c->state.stream == NULL && !h2c->state.index) { + return ngx_http_v2_state_field_skip(h2c, pos, end); + } + + alloc = (huff ? len * 8 / 5 : len) + 1; + + h2c->state.field_start = ngx_pnalloc(h2c->state.pool, alloc); + if (h2c->state.field_start == NULL) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + h2c->state.field_end = h2c->state.field_start; + + if (huff) { + return ngx_http_v2_state_field_huff(h2c, pos, end); + } + + return ngx_http_v2_state_field_raw(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_field_huff(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + size_t size; + + size = end - pos; + + if (size > h2c->state.field_rest) { + size = h2c->state.field_rest; + } + + if (size > h2c->state.length) { + size = h2c->state.length; + } + + h2c->state.length -= size; + h2c->state.field_rest -= size; + + if (ngx_http_v2_huff_decode(&h2c->state.field_state, pos, size, + &h2c->state.field_end, + h2c->state.field_rest == 0, + h2c->connection->log) + != NGX_OK) + { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent invalid encoded header field"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); + } + + pos += size; + + if (h2c->state.field_rest == 0) { + *h2c->state.field_end = '\0'; + return ngx_http_v2_state_process_header(h2c, pos, end); + } + + if (h2c->state.length) { + return ngx_http_v2_state_headers_save(h2c, pos, end, + ngx_http_v2_state_field_huff); + } + + if (h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent header field with incorrect length"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + return ngx_http_v2_handle_continuation(h2c, pos, end, + ngx_http_v2_state_field_huff); +} + + +static u_char * +ngx_http_v2_state_field_raw(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + size_t size; + + size = end - pos; + + if (size > h2c->state.field_rest) { + size = h2c->state.field_rest; + } + + if (size > h2c->state.length) { + size = h2c->state.length; + } + + h2c->state.length -= size; + h2c->state.field_rest -= size; + + h2c->state.field_end = ngx_cpymem(h2c->state.field_end, pos, size); + + pos += size; + + if (h2c->state.field_rest == 0) { + *h2c->state.field_end = '\0'; + return ngx_http_v2_state_process_header(h2c, pos, end); + } + + if (h2c->state.length) { + return ngx_http_v2_state_headers_save(h2c, pos, end, + ngx_http_v2_state_field_raw); + } + + if (h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent header field with incorrect length"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + return ngx_http_v2_handle_continuation(h2c, pos, end, + ngx_http_v2_state_field_raw); +} + + +static u_char * +ngx_http_v2_state_field_skip(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + size_t size; + + size = end - pos; + + if (size > h2c->state.field_rest) { + size = h2c->state.field_rest; + } + + if (size > h2c->state.length) { + size = h2c->state.length; + } + + h2c->state.length -= size; + h2c->state.field_rest -= size; + + pos += size; + + if (h2c->state.field_rest == 0) { + return ngx_http_v2_state_process_header(h2c, pos, end); + } + + if (h2c->state.length) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_field_skip); + } + + if (h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent header field with incorrect length"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + return ngx_http_v2_handle_continuation(h2c, pos, end, + ngx_http_v2_state_field_skip); +} + + +static u_char * +ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + size_t len; + ngx_int_t rc; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_request_t *r; + ngx_http_v2_header_t *header; + ngx_http_core_srv_conf_t *cscf; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + header = &h2c->state.header; + + if (h2c->state.parse_name) { + h2c->state.parse_name = 0; + + header->name.len = h2c->state.field_end - h2c->state.field_start; + header->name.data = h2c->state.field_start; + + return ngx_http_v2_state_field_len(h2c, pos, end); + } + + if (h2c->state.parse_value) { + h2c->state.parse_value = 0; + + header->value.len = h2c->state.field_end - h2c->state.field_start; + header->value.data = h2c->state.field_start; + } + + len = header->name.len + header->value.len; + + if (len > h2c->state.header_limit) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client exceeded http2_max_header_size limit"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_ENHANCE_YOUR_CALM); + } + + h2c->state.header_limit -= len; + + if (h2c->state.index) { + if (ngx_http_v2_add_header(h2c, header) != NGX_OK) { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + h2c->state.index = 0; + } + + if (h2c->state.stream == NULL) { + return ngx_http_v2_state_header_complete(h2c, pos, end); + } + + r = h2c->state.stream->request; + + /* TODO Optimization: validate headers while parsing. */ + if (ngx_http_v2_validate_header(r, header) != NGX_OK) { + if (ngx_http_v2_terminate_stream(h2c, h2c->state.stream, + NGX_HTTP_V2_PROTOCOL_ERROR) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + goto error; + } + + if (header->name.data[0] == ':') { + rc = ngx_http_v2_pseudo_header(r, header); + + if (rc == NGX_OK) { + return ngx_http_v2_state_header_complete(h2c, pos, end); + } + + if (rc == NGX_ABORT) { + goto error; + } + + if (rc == NGX_DECLINED) { + if (ngx_http_v2_terminate_stream(h2c, h2c->state.stream, + NGX_HTTP_V2_PROTOCOL_ERROR) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + goto error; + } + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + if (r->invalid_header) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (cscf->ignore_invalid_headers) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header: \"%V\"", &header->name); + + return ngx_http_v2_state_header_complete(h2c, pos, end); + } + } + + if (header->name.len == cookie.len + && ngx_memcmp(header->name.data, cookie.data, cookie.len) == 0) + { + if (ngx_http_v2_cookie(r, header) != NGX_OK) { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + return ngx_http_v2_state_header_complete(h2c, pos, end); + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + h->key.len = header->name.len; + h->key.data = header->name.data; + + /* TODO Optimization: precalculate hash and handler for indexed headers. */ + h->hash = ngx_hash_key(h->key.data, h->key.len); + + h->value.len = header->value.len; + h->value.data = header->value.data; + + h->lowcase_key = h->key.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { + goto error; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http2 http header: \"%V: %V\"", &h->key, &h->value); + + return ngx_http_v2_state_header_complete(h2c, pos, end); + +error: + + h2c->state.stream = NULL; + + return ngx_http_v2_state_header_complete(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_header_complete(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + ngx_http_v2_stream_t *stream; + + if (h2c->state.length) { + h2c->state.handler = ngx_http_v2_state_header_block; + return pos; + } + + if (!(h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG)) { + return ngx_http_v2_handle_continuation(h2c, pos, end, + ngx_http_v2_state_header_complete); + } + + stream = h2c->state.stream; + + if (stream) { + ngx_http_v2_run_request(stream->request); + } + + if (!h2c->state.keep_pool) { + ngx_destroy_pool(h2c->state.pool); + } + + h2c->state.pool = NULL; + h2c->state.keep_pool = 0; + + if (h2c->state.padding) { + return ngx_http_v2_state_skip_padded(h2c, pos, end); + } + + return ngx_http_v2_state_complete(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_handle_continuation(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end, ngx_http_v2_handler_pt handler) +{ + u_char *p; + size_t len, skip; + uint32_t head; + + len = h2c->state.length; + + if (h2c->state.padding && (size_t) (end - pos) > len) { + skip = ngx_min(h2c->state.padding, (end - pos) - len); + + h2c->state.padding -= skip; + + p = pos; + pos += skip; + ngx_memmove(pos, p, len); + } + + if ((size_t) (end - pos) < len + NGX_HTTP_V2_FRAME_HEADER_SIZE) { + return ngx_http_v2_state_headers_save(h2c, pos, end, handler); + } + + p = pos + len; + + head = ngx_http_v2_parse_uint32(p); + + if (ngx_http_v2_parse_type(head) != NGX_HTTP_V2_CONTINUATION_FRAME) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent inappropriate frame while CONTINUATION was expected"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + + h2c->state.flags |= p[4]; + + if (h2c->state.sid != ngx_http_v2_parse_sid(&p[5])) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent CONTINUATION frame with incorrect identifier"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + + p = pos; + pos += NGX_HTTP_V2_FRAME_HEADER_SIZE; + + ngx_memcpy(pos, p, len); + + len = ngx_http_v2_parse_length(head); + + h2c->state.length += len; + + if (h2c->state.stream) { + h2c->state.stream->request->request_length += len; + } + + h2c->state.handler = handler; + return pos; +} + + +static u_char * +ngx_http_v2_state_priority(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + ngx_uint_t depend, dependency, excl, weight; + ngx_http_v2_node_t *node; + + if (h2c->state.length != NGX_HTTP_V2_PRIORITY_SIZE) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent PRIORITY frame with incorrect length %uz", + h2c->state.length); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (end - pos < NGX_HTTP_V2_PRIORITY_SIZE) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_priority); + } + + dependency = ngx_http_v2_parse_uint32(pos); + + depend = dependency & 0x7fffffff; + excl = dependency >> 31; + weight = pos[4] + 1; + + pos += NGX_HTTP_V2_PRIORITY_SIZE; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 PRIORITY frame sid:%ui on %ui excl:%ui weight:%ui", + h2c->state.sid, depend, excl, weight); + + if (h2c->state.sid == 0) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent PRIORITY frame with incorrect identifier"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + + if (depend == h2c->state.sid) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent PRIORITY frame for stream %ui " + "with incorrect dependency", h2c->state.sid); + + node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0); + + if (node && node->stream) { + if (ngx_http_v2_terminate_stream(h2c, node->stream, + NGX_HTTP_V2_PROTOCOL_ERROR) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + } else { + if (ngx_http_v2_send_rst_stream(h2c, h2c->state.sid, + NGX_HTTP_V2_PROTOCOL_ERROR) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + } + + return ngx_http_v2_state_complete(h2c, pos, end); + } + + node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 1); + + if (node == NULL) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + node->weight = weight; + + if (node->stream == NULL) { + if (node->parent == NULL) { + h2c->closed_nodes++; + + } else { + ngx_queue_remove(&node->reuse); + } + + ngx_queue_insert_tail(&h2c->closed, &node->reuse); + } + + ngx_http_v2_set_dependency(h2c, node, depend, excl); + + return ngx_http_v2_state_complete(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_rst_stream(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + ngx_uint_t status; + ngx_event_t *ev; + ngx_connection_t *fc; + ngx_http_v2_node_t *node; + ngx_http_v2_stream_t *stream; + + if (h2c->state.length != NGX_HTTP_V2_RST_STREAM_SIZE) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent RST_STREAM frame with incorrect length %uz", + h2c->state.length); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (end - pos < NGX_HTTP_V2_RST_STREAM_SIZE) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_rst_stream); + } + + status = ngx_http_v2_parse_uint32(pos); + + pos += NGX_HTTP_V2_RST_STREAM_SIZE; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 RST_STREAM frame, sid:%ui status:%ui", + h2c->state.sid, status); + + if (h2c->state.sid == 0) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent RST_STREAM frame with incorrect identifier"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + + node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0); + + if (node == NULL || node->stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "unknown http2 stream"); + + return ngx_http_v2_state_complete(h2c, pos, end); + } + + stream = node->stream; + + stream->in_closed = 1; + stream->out_closed = 1; + + fc = stream->request->connection; + fc->error = 1; + + switch (status) { + + case NGX_HTTP_V2_CANCEL: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client canceled stream %ui", h2c->state.sid); + break; + + case NGX_HTTP_V2_INTERNAL_ERROR: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client terminated stream %ui due to internal error", + h2c->state.sid); + break; + + default: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client terminated stream %ui with status %ui", + h2c->state.sid, status); + break; + } + + ev = fc->read; + ev->handler(ev); + + return ngx_http_v2_state_complete(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_settings(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + if (h2c->state.flags == NGX_HTTP_V2_ACK_FLAG) { + + if (h2c->state.length != 0) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent SETTINGS frame with the ACK flag " + "and nonzero length"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + h2c->settings_ack = 1; + + return ngx_http_v2_state_complete(h2c, pos, end); + } + + if (h2c->state.length % NGX_HTTP_V2_SETTINGS_PARAM_SIZE) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent SETTINGS frame with incorrect length %uz", + h2c->state.length); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + ngx_http_v2_send_settings(h2c, 1); + + return ngx_http_v2_state_settings_params(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + ngx_uint_t id, value; + + while (h2c->state.length) { + if (end - pos < NGX_HTTP_V2_SETTINGS_PARAM_SIZE) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_settings_params); + } + + h2c->state.length -= NGX_HTTP_V2_SETTINGS_PARAM_SIZE; + + id = ngx_http_v2_parse_uint16(pos); + value = ngx_http_v2_parse_uint32(&pos[2]); + + switch (id) { + + case NGX_HTTP_V2_INIT_WINDOW_SIZE_SETTING: + + if (value > NGX_HTTP_V2_MAX_WINDOW) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent SETTINGS frame with incorrect " + "INITIAL_WINDOW_SIZE value %ui", value); + + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_FLOW_CTRL_ERROR); + } + + if (ngx_http_v2_adjust_windows(h2c, value - h2c->init_window) + != NGX_OK) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + h2c->init_window = value; + break; + + case NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING: + + if (value > NGX_HTTP_V2_MAX_FRAME_SIZE + || value < NGX_HTTP_V2_DEFAULT_FRAME_SIZE) + { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent SETTINGS frame with incorrect " + "MAX_FRAME_SIZE value %ui", value); + + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_PROTOCOL_ERROR); + } + + h2c->frame_size = value; + break; + + default: + break; + } + + pos += NGX_HTTP_V2_SETTINGS_PARAM_SIZE; + } + + return ngx_http_v2_state_complete(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_push_promise(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent PUSH_PROMISE frame"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); +} + + +static u_char * +ngx_http_v2_state_ping(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) +{ + ngx_buf_t *buf; + ngx_http_v2_out_frame_t *frame; + + if (h2c->state.length != NGX_HTTP_V2_PING_SIZE) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent PING frame with incorrect length %uz", + h2c->state.length); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (end - pos < NGX_HTTP_V2_PING_SIZE) { + return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_ping); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 PING frame, flags: %ud", h2c->state.flags); + + if (h2c->state.flags & NGX_HTTP_V2_ACK_FLAG) { + return ngx_http_v2_state_skip(h2c, pos, end); + } + + frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_PING_SIZE, + NGX_HTTP_V2_PING_FRAME, + NGX_HTTP_V2_ACK_FLAG, 0); + if (frame == NULL) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + buf = frame->first->buf; + + buf->last = ngx_cpymem(buf->last, pos, NGX_HTTP_V2_PING_SIZE); + + ngx_http_v2_queue_blocked_frame(h2c, frame); + + return ngx_http_v2_state_complete(h2c, pos + NGX_HTTP_V2_PING_SIZE, end); +} + + +static u_char * +ngx_http_v2_state_goaway(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ +#if (NGX_DEBUG) + ngx_uint_t last_sid, error; +#endif + + if (h2c->state.length < NGX_HTTP_V2_GOAWAY_SIZE) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent GOAWAY frame " + "with incorrect length %uz", h2c->state.length); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (end - pos < NGX_HTTP_V2_GOAWAY_SIZE) { + return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_goaway); + } + +#if (NGX_DEBUG) + h2c->state.length -= NGX_HTTP_V2_GOAWAY_SIZE; + + last_sid = ngx_http_v2_parse_sid(pos); + error = ngx_http_v2_parse_uint32(&pos[4]); + + pos += NGX_HTTP_V2_GOAWAY_SIZE; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 GOAWAY frame: last sid %ui, error %ui", + last_sid, error); +#endif + + return ngx_http_v2_state_skip(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_window_update(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + size_t window; + ngx_event_t *wev; + ngx_queue_t *q; + ngx_http_v2_node_t *node; + ngx_http_v2_stream_t *stream; + + if (h2c->state.length != NGX_HTTP_V2_WINDOW_UPDATE_SIZE) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent WINDOW_UPDATE frame " + "with incorrect length %uz", h2c->state.length); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (end - pos < NGX_HTTP_V2_WINDOW_UPDATE_SIZE) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_window_update); + } + + window = ngx_http_v2_parse_window(pos); + + pos += NGX_HTTP_V2_WINDOW_UPDATE_SIZE; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 WINDOW_UPDATE frame sid:%ui window:%uz", + h2c->state.sid, window); + + if (h2c->state.sid) { + node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0); + + if (node == NULL || node->stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "unknown http2 stream"); + + return ngx_http_v2_state_complete(h2c, pos, end); + } + + stream = node->stream; + + if (window > (size_t) (NGX_HTTP_V2_MAX_WINDOW - stream->send_window)) { + + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client violated flow control for stream %ui: " + "received WINDOW_UPDATE frame " + "with window increment %uz " + "not allowed for window %z", + h2c->state.sid, window, stream->send_window); + + if (ngx_http_v2_terminate_stream(h2c, stream, + NGX_HTTP_V2_FLOW_CTRL_ERROR) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + return ngx_http_v2_state_complete(h2c, pos, end); + } + + stream->send_window += window; + + if (stream->exhausted) { + stream->exhausted = 0; + + wev = stream->request->connection->write; + + wev->active = 0; + wev->ready = 1; + + if (!wev->delayed) { + wev->handler(wev); + } + } + + return ngx_http_v2_state_complete(h2c, pos, end); + } + + if (window > NGX_HTTP_V2_MAX_WINDOW - h2c->send_window) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client violated connection flow control: " + "received WINDOW_UPDATE frame " + "with window increment %uz " + "not allowed for window %uz", + window, h2c->send_window); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_FLOW_CTRL_ERROR); + } + + h2c->send_window += window; + + while (!ngx_queue_empty(&h2c->waiting)) { + q = ngx_queue_head(&h2c->waiting); + + ngx_queue_remove(q); + + stream = ngx_queue_data(q, ngx_http_v2_stream_t, queue); + + stream->waiting = 0; + + wev = stream->request->connection->write; + + wev->active = 0; + wev->ready = 1; + + if (!wev->delayed) { + wev->handler(wev); + + if (h2c->send_window == 0) { + break; + } + } + } + + return ngx_http_v2_state_complete(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_continuation(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent unexpected CONTINUATION frame"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); +} + + +static u_char * +ngx_http_v2_state_complete(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 frame complete pos:%p end:%p", pos, end); + + if (pos > end) { + ngx_log_error(NGX_LOG_ALERT, h2c->connection->log, 0, + "receive buffer overrun"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + h2c->state.stream = NULL; + h2c->state.handler = ngx_http_v2_state_head; + + return pos; +} + + +static u_char * +ngx_http_v2_state_skip_padded(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + h2c->state.length += h2c->state.padding; + h2c->state.padding = 0; + + return ngx_http_v2_state_skip(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_skip(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) +{ + size_t size; + + size = end - pos; + + if (size < h2c->state.length) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 frame skip %uz of %uz", size, h2c->state.length); + + h2c->state.length -= size; + return ngx_http_v2_state_save(h2c, end, end, ngx_http_v2_state_skip); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 frame skip %uz", h2c->state.length); + + return ngx_http_v2_state_complete(h2c, pos + h2c->state.length, end); +} + + +static u_char * +ngx_http_v2_state_save(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end, + ngx_http_v2_handler_pt handler) +{ + size_t size; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 frame state save pos:%p end:%p handler:%p", + pos, end, handler); + + size = end - pos; + + if (size > NGX_HTTP_V2_STATE_BUFFER_SIZE) { + ngx_log_error(NGX_LOG_ALERT, h2c->connection->log, 0, + "state buffer overflow: %uz bytes required", size); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + ngx_memcpy(h2c->state.buffer, pos, NGX_HTTP_V2_STATE_BUFFER_SIZE); + + h2c->state.buffer_used = size; + h2c->state.handler = handler; + h2c->state.incomplete = 1; + + return end; +} + + +static u_char * +ngx_http_v2_state_headers_save(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end, ngx_http_v2_handler_pt handler) +{ + ngx_event_t *rev; + ngx_http_request_t *r; + ngx_http_core_srv_conf_t *cscf; + + if (h2c->state.stream) { + r = h2c->state.stream->request; + rev = r->connection->read; + + if (!rev->timer_set) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); + } + } + + return ngx_http_v2_state_save(h2c, pos, end, handler); +} + + +static u_char * +ngx_http_v2_connection_error(ngx_http_v2_connection_t *h2c, + ngx_uint_t err) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 state connection error"); + + if (err == NGX_HTTP_V2_INTERNAL_ERROR) { + ngx_debug_point(); + } + + ngx_http_v2_finalize_connection(h2c, err); + + return NULL; +} + + +static ngx_int_t +ngx_http_v2_parse_int(ngx_http_v2_connection_t *h2c, u_char **pos, u_char *end, + ngx_uint_t prefix) +{ + u_char *start, *p; + ngx_uint_t value, octet, shift; + + start = *pos; + p = start; + + value = *p++ & prefix; + + if (value != prefix) { + if (h2c->state.length == 0) { + return NGX_ERROR; + } + + h2c->state.length--; + + *pos = p; + return value; + } + + if (end - start > NGX_HTTP_V2_INT_OCTETS) { + end = start + NGX_HTTP_V2_INT_OCTETS; + } + + for (shift = 0; p != end; shift += 7) { + octet = *p++; + + value += (octet & 0x7f) << shift; + + if (octet < 128) { + if ((size_t) (p - start) > h2c->state.length) { + return NGX_ERROR; + } + + h2c->state.length -= p - start; + + *pos = p; + return value; + } + } + + if ((size_t) (end - start) >= h2c->state.length) { + return NGX_ERROR; + } + + if (end == start + NGX_HTTP_V2_INT_OCTETS) { + return NGX_DECLINED; + } + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_v2_send_settings(ngx_http_v2_connection_t *h2c, ngx_uint_t ack) +{ + size_t len; + ngx_buf_t *buf; + ngx_chain_t *cl; + ngx_http_v2_srv_conf_t *h2scf; + ngx_http_v2_out_frame_t *frame; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 send SETTINGS frame ack:%ui", ack); + + frame = ngx_palloc(h2c->pool, sizeof(ngx_http_v2_out_frame_t)); + if (frame == NULL) { + return NGX_ERROR; + } + + cl = ngx_alloc_chain_link(h2c->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + len = ack ? 0 : (sizeof(uint16_t) + sizeof(uint32_t)) * 3; + + buf = ngx_create_temp_buf(h2c->pool, NGX_HTTP_V2_FRAME_HEADER_SIZE + len); + if (buf == NULL) { + return NGX_ERROR; + } + + buf->last_buf = 1; + + cl->buf = buf; + cl->next = NULL; + + frame->first = cl; + frame->last = cl; + frame->handler = ngx_http_v2_settings_frame_handler; + frame->stream = NULL; +#if (NGX_DEBUG) + frame->length = len; +#endif + frame->blocked = 0; + + buf->last = ngx_http_v2_write_len_and_type(buf->last, len, + NGX_HTTP_V2_SETTINGS_FRAME); + + *buf->last++ = ack ? NGX_HTTP_V2_ACK_FLAG : NGX_HTTP_V2_NO_FLAG; + + buf->last = ngx_http_v2_write_sid(buf->last, 0); + + if (!ack) { + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + buf->last = ngx_http_v2_write_uint16(buf->last, + NGX_HTTP_V2_MAX_STREAMS_SETTING); + buf->last = ngx_http_v2_write_uint32(buf->last, + h2scf->concurrent_streams); + + buf->last = ngx_http_v2_write_uint16(buf->last, + NGX_HTTP_V2_INIT_WINDOW_SIZE_SETTING); + buf->last = ngx_http_v2_write_uint32(buf->last, h2scf->preread_size); + + buf->last = ngx_http_v2_write_uint16(buf->last, + NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING); + buf->last = ngx_http_v2_write_uint32(buf->last, + NGX_HTTP_V2_MAX_FRAME_SIZE); + } + + ngx_http_v2_queue_blocked_frame(h2c, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_settings_frame_handler(ngx_http_v2_connection_t *h2c, + ngx_http_v2_out_frame_t *frame) +{ + ngx_buf_t *buf; + + buf = frame->first->buf; + + if (buf->pos != buf->last) { + return NGX_AGAIN; + } + + ngx_free_chain(h2c->pool, frame->first); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_send_window_update(ngx_http_v2_connection_t *h2c, ngx_uint_t sid, + size_t window) +{ + ngx_buf_t *buf; + ngx_http_v2_out_frame_t *frame; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 send WINDOW_UPDATE frame sid:%ui, window:%uz", + sid, window); + + frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_WINDOW_UPDATE_SIZE, + NGX_HTTP_V2_WINDOW_UPDATE_FRAME, + NGX_HTTP_V2_NO_FLAG, sid); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + buf->last = ngx_http_v2_write_uint32(buf->last, window); + + ngx_http_v2_queue_blocked_frame(h2c, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_send_rst_stream(ngx_http_v2_connection_t *h2c, ngx_uint_t sid, + ngx_uint_t status) +{ + ngx_buf_t *buf; + ngx_http_v2_out_frame_t *frame; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 send RST_STREAM frame sid:%ui, status:%ui", + sid, status); + + frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_RST_STREAM_SIZE, + NGX_HTTP_V2_RST_STREAM_FRAME, + NGX_HTTP_V2_NO_FLAG, sid); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + buf->last = ngx_http_v2_write_uint32(buf->last, status); + + ngx_http_v2_queue_blocked_frame(h2c, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_send_goaway(ngx_http_v2_connection_t *h2c, ngx_uint_t status) +{ + ngx_buf_t *buf; + ngx_http_v2_out_frame_t *frame; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 send GOAWAY frame: last sid %ui, error %ui", + h2c->last_sid, status); + + frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_GOAWAY_SIZE, + NGX_HTTP_V2_GOAWAY_FRAME, + NGX_HTTP_V2_NO_FLAG, 0); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + buf->last = ngx_http_v2_write_sid(buf->last, h2c->last_sid); + buf->last = ngx_http_v2_write_uint32(buf->last, status); + + ngx_http_v2_queue_blocked_frame(h2c, frame); + + return NGX_OK; +} + + +static ngx_http_v2_out_frame_t * +ngx_http_v2_get_frame(ngx_http_v2_connection_t *h2c, size_t length, + ngx_uint_t type, u_char flags, ngx_uint_t sid) +{ + ngx_buf_t *buf; + ngx_pool_t *pool; + ngx_http_v2_out_frame_t *frame; + + frame = h2c->free_frames; + + if (frame) { + h2c->free_frames = frame->next; + + buf = frame->first->buf; + buf->pos = buf->start; + + frame->blocked = 0; + + } else { + pool = h2c->pool ? h2c->pool : h2c->connection->pool; + + frame = ngx_pcalloc(pool, sizeof(ngx_http_v2_out_frame_t)); + if (frame == NULL) { + return NULL; + } + + frame->first = ngx_alloc_chain_link(pool); + if (frame->first == NULL) { + return NULL; + } + + buf = ngx_create_temp_buf(pool, NGX_HTTP_V2_FRAME_BUFFER_SIZE); + if (buf == NULL) { + return NULL; + } + + buf->last_buf = 1; + + frame->first->buf = buf; + frame->last = frame->first; + + frame->handler = ngx_http_v2_frame_handler; + } + +#if (NGX_DEBUG) + if (length > NGX_HTTP_V2_FRAME_BUFFER_SIZE - NGX_HTTP_V2_FRAME_HEADER_SIZE) + { + ngx_log_error(NGX_LOG_ALERT, h2c->connection->log, 0, + "requested control frame is too large: %uz", length); + return NULL; + } + + frame->length = length; +#endif + + buf->last = ngx_http_v2_write_len_and_type(buf->pos, length, type); + + *buf->last++ = flags; + + buf->last = ngx_http_v2_write_sid(buf->last, sid); + + return frame; +} + + +static ngx_int_t +ngx_http_v2_frame_handler(ngx_http_v2_connection_t *h2c, + ngx_http_v2_out_frame_t *frame) +{ + ngx_buf_t *buf; + + buf = frame->first->buf; + + if (buf->pos != buf->last) { + return NGX_AGAIN; + } + + frame->next = h2c->free_frames; + h2c->free_frames = frame; + + return NGX_OK; +} + + +static ngx_http_v2_stream_t * +ngx_http_v2_create_stream(ngx_http_v2_connection_t *h2c) +{ + ngx_log_t *log; + ngx_event_t *rev, *wev; + ngx_connection_t *fc; + ngx_http_log_ctx_t *ctx; + ngx_http_request_t *r; + ngx_http_v2_stream_t *stream; + ngx_http_v2_srv_conf_t *h2scf; + ngx_http_core_srv_conf_t *cscf; + + fc = h2c->free_fake_connections; + + if (fc) { + h2c->free_fake_connections = fc->data; + + rev = fc->read; + wev = fc->write; + log = fc->log; + ctx = log->data; + + } else { + fc = ngx_palloc(h2c->pool, sizeof(ngx_connection_t)); + if (fc == NULL) { + return NULL; + } + + rev = ngx_palloc(h2c->pool, sizeof(ngx_event_t)); + if (rev == NULL) { + return NULL; + } + + wev = ngx_palloc(h2c->pool, sizeof(ngx_event_t)); + if (wev == NULL) { + return NULL; + } + + log = ngx_palloc(h2c->pool, sizeof(ngx_log_t)); + if (log == NULL) { + return NULL; + } + + ctx = ngx_palloc(h2c->pool, sizeof(ngx_http_log_ctx_t)); + if (ctx == NULL) { + return NULL; + } + + ctx->connection = fc; + ctx->request = NULL; + ctx->current_request = NULL; + } + + ngx_memcpy(log, h2c->connection->log, sizeof(ngx_log_t)); + + log->data = ctx; + log->action = "reading client request headers"; + + ngx_memzero(rev, sizeof(ngx_event_t)); + + rev->data = fc; + rev->ready = 1; + rev->handler = ngx_http_v2_close_stream_handler; + rev->log = log; + + ngx_memcpy(wev, rev, sizeof(ngx_event_t)); + + wev->write = 1; + + ngx_memcpy(fc, h2c->connection, sizeof(ngx_connection_t)); + + fc->data = h2c->http_connection; + fc->read = rev; + fc->write = wev; + fc->sent = 0; + fc->log = log; + fc->buffered = 0; + fc->sndlowat = 1; + fc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; + + r = ngx_http_create_request(fc); + if (r == NULL) { + return NULL; + } + + ngx_str_set(&r->http_protocol, "HTTP/2.0"); + + r->http_version = NGX_HTTP_VERSION_20; + r->valid_location = 1; + + fc->data = r; + h2c->connection->requests++; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_in = ngx_create_temp_buf(r->pool, + cscf->client_header_buffer_size); + if (r->header_in == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + if (ngx_list_init(&r->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; + + stream = ngx_pcalloc(r->pool, sizeof(ngx_http_v2_stream_t)); + if (stream == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->stream = stream; + + stream->request = r; + stream->connection = h2c; + + h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module); + + stream->send_window = h2c->init_window; + stream->recv_window = h2scf->preread_size; + + h2c->processing++; + + return stream; +} + + +static ngx_http_v2_node_t * +ngx_http_v2_get_node_by_id(ngx_http_v2_connection_t *h2c, ngx_uint_t sid, + ngx_uint_t alloc) +{ + ngx_uint_t index; + ngx_http_v2_node_t *node; + ngx_http_v2_srv_conf_t *h2scf; + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + index = ngx_http_v2_index(h2scf, sid); + + for (node = h2c->streams_index[index]; node; node = node->index) { + + if (node->id == sid) { + return node; + } + } + + if (!alloc) { + return NULL; + } + + if (h2c->closed_nodes < 32) { + node = ngx_pcalloc(h2c->connection->pool, sizeof(ngx_http_v2_node_t)); + if (node == NULL) { + return NULL; + } + + } else { + node = ngx_http_v2_get_closed_node(h2c); + } + + node->id = sid; + + ngx_queue_init(&node->children); + + node->index = h2c->streams_index[index]; + h2c->streams_index[index] = node; + + return node; +} + + +static ngx_http_v2_node_t * +ngx_http_v2_get_closed_node(ngx_http_v2_connection_t *h2c) +{ + ngx_uint_t weight; + ngx_queue_t *q, *children; + ngx_http_v2_node_t *node, **next, *n, *parent, *child; + ngx_http_v2_srv_conf_t *h2scf; + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + h2c->closed_nodes--; + + q = ngx_queue_head(&h2c->closed); + + ngx_queue_remove(q); + + node = ngx_queue_data(q, ngx_http_v2_node_t, reuse); + + next = &h2c->streams_index[ngx_http_v2_index(h2scf, node->id)]; + + for ( ;; ) { + n = *next; + + if (n == node) { + *next = n->index; + break; + } + + next = &n->index; + } + + ngx_queue_remove(&node->queue); + + weight = 0; + + for (q = ngx_queue_head(&node->children); + q != ngx_queue_sentinel(&node->children); + q = ngx_queue_next(q)) + { + child = ngx_queue_data(q, ngx_http_v2_node_t, queue); + weight += child->weight; + } + + parent = node->parent; + + for (q = ngx_queue_head(&node->children); + q != ngx_queue_sentinel(&node->children); + q = ngx_queue_next(q)) + { + child = ngx_queue_data(q, ngx_http_v2_node_t, queue); + child->parent = parent; + child->weight = node->weight * child->weight / weight; + + if (child->weight == 0) { + child->weight = 1; + } + } + + if (parent == NGX_HTTP_V2_ROOT) { + node->rank = 0; + node->rel_weight = 1.0; + + children = &h2c->dependencies; + + } else { + node->rank = parent->rank; + node->rel_weight = parent->rel_weight; + + children = &parent->children; + } + + ngx_http_v2_node_children_update(node); + ngx_queue_add(children, &node->children); + + ngx_memzero(node, sizeof(ngx_http_v2_node_t)); + + return node; +} + + +static ngx_int_t +ngx_http_v2_validate_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) +{ + u_char ch; + ngx_uint_t i; + ngx_http_core_srv_conf_t *cscf; + + if (header->name.len == 0) { + return NGX_ERROR; + } + + r->invalid_header = 0; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + for (i = (header->name.data[0] == ':'); i != header->name.len; i++) { + ch = header->name.data[i]; + + if ((ch >= 'a' && ch <= 'z') + || (ch == '-') + || (ch >= '0' && ch <= '9') + || (ch == '_' && cscf->underscores_in_headers)) + { + continue; + } + + switch (ch) { + case '\0': + case LF: + case CR: + case ':': + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%V\"", + &header->name); + + return NGX_ERROR; + } + + if (ch >= 'A' && ch <= 'Z') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%V\"", + &header->name); + + return NGX_ERROR; + } + + r->invalid_header = 1; + } + + for (i = 0; i != header->value.len; i++) { + ch = header->value.data[i]; + + switch (ch) { + case '\0': + case LF: + case CR: + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%V\" with " + "invalid value: \"%V\"", + &header->name, &header->value); + + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_pseudo_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) +{ + header->name.len--; + header->name.data++; + + switch (header->name.len) { + case 4: + if (ngx_memcmp(header->name.data, "path", sizeof("path") - 1) + == 0) + { + return ngx_http_v2_parse_path(r, header); + } + + break; + + case 6: + if (ngx_memcmp(header->name.data, "method", sizeof("method") - 1) + == 0) + { + return ngx_http_v2_parse_method(r, header); + } + + if (ngx_memcmp(header->name.data, "scheme", sizeof("scheme") - 1) + == 0) + { + return ngx_http_v2_parse_scheme(r, header); + } + + break; + + case 9: + if (ngx_memcmp(header->name.data, "authority", sizeof("authority") - 1) + == 0) + { + return ngx_http_v2_parse_authority(r, header); + } + + break; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent unknown pseudo-header \":%V\"", + &header->name); + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_http_v2_parse_path(ngx_http_request_t *r, ngx_http_v2_header_t *header) +{ + if (r->unparsed_uri.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :path header"); + + return NGX_DECLINED; + } + + if (header->value.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :path header"); + + return NGX_DECLINED; + } + + r->uri_start = header->value.data; + r->uri_end = header->value.data + header->value.len; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid :path header: \"%V\"", + &header->value); + + return NGX_DECLINED; + } + + if (ngx_http_process_request_uri(r) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_request_uri() + */ + return NGX_ABORT; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_parse_method(ngx_http_request_t *r, ngx_http_v2_header_t *header) +{ + size_t k, len; + ngx_uint_t n; + const u_char *p, *m; + + /* + * This array takes less than 256 sequential bytes, + * and if typical CPU cache line size is 64 bytes, + * it is prefetched for 4 load operations. + */ + static const struct { + u_char len; + const u_char method[11]; + uint32_t value; + } tests[] = { + { 3, "GET", NGX_HTTP_GET }, + { 4, "POST", NGX_HTTP_POST }, + { 4, "HEAD", NGX_HTTP_HEAD }, + { 7, "OPTIONS", NGX_HTTP_OPTIONS }, + { 8, "PROPFIND", NGX_HTTP_PROPFIND }, + { 3, "PUT", NGX_HTTP_PUT }, + { 5, "MKCOL", NGX_HTTP_MKCOL }, + { 6, "DELETE", NGX_HTTP_DELETE }, + { 4, "COPY", NGX_HTTP_COPY }, + { 4, "MOVE", NGX_HTTP_MOVE }, + { 9, "PROPPATCH", NGX_HTTP_PROPPATCH }, + { 4, "LOCK", NGX_HTTP_LOCK }, + { 6, "UNLOCK", NGX_HTTP_UNLOCK }, + { 5, "PATCH", NGX_HTTP_PATCH }, + { 5, "TRACE", NGX_HTTP_TRACE } + }, *test; + + if (r->method_name.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :method header"); + + return NGX_DECLINED; + } + + if (header->value.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :method header"); + + return NGX_DECLINED; + } + + r->method_name.len = header->value.len; + r->method_name.data = header->value.data; + + len = r->method_name.len; + n = sizeof(tests) / sizeof(tests[0]); + test = tests; + + do { + if (len == test->len) { + p = r->method_name.data; + m = test->method; + k = len; + + do { + if (*p++ != *m++) { + goto next; + } + } while (--k); + + r->method = test->value; + return NGX_OK; + } + + next: + test++; + + } while (--n); + + p = r->method_name.data; + + do { + if ((*p < 'A' || *p > 'Z') && *p != '_' && *p != '-') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid method: \"%V\"", + &r->method_name); + + return NGX_DECLINED; + } + + p++; + + } while (--len); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_parse_scheme(ngx_http_request_t *r, ngx_http_v2_header_t *header) +{ + if (r->schema_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :scheme header"); + + return NGX_DECLINED; + } + + if (header->value.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :scheme header"); + + return NGX_DECLINED; + } + + r->schema_start = header->value.data; + r->schema_end = header->value.data + header->value.len; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_http_v2_header_t *header) +{ + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t host = ngx_string("host"); + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = ngx_hash_key(host.data, host.len); + + h->key.len = host.len; + h->key.data = host.data; + + h->value.len = header->value.len; + h->value.data = header->value.data; + + h->lowcase_key = host.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_host() + */ + return NGX_ABORT; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_construct_request_line(ngx_http_request_t *r) +{ + u_char *p; + + static const u_char ending[] = " HTTP/2.0"; + + if (r->method_name.len == 0 + || r->unparsed_uri.len == 0) + { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + r->request_line.len = r->method_name.len + 1 + + r->unparsed_uri.len + + sizeof(ending) - 1; + + p = ngx_pnalloc(r->pool, r->request_line.len + 1); + if (p == NULL) { + ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->request_line.data = p; + + p = ngx_cpymem(p, r->method_name.data, r->method_name.len); + + *p++ = ' '; + + p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len); + + ngx_memcpy(p, ending, sizeof(ending)); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http2 http request line: \"%V\"", &r->request_line); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_cookie(ngx_http_request_t *r, ngx_http_v2_header_t *header) +{ + ngx_str_t *val; + ngx_array_t *cookies; + + cookies = r->stream->cookies; + + if (cookies == NULL) { + cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t)); + if (cookies == NULL) { + return NGX_ERROR; + } + + r->stream->cookies = cookies; + } + + val = ngx_array_push(cookies); + if (val == NULL) { + return NGX_ERROR; + } + + val->len = header->value.len; + val->data = header->value.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_construct_cookie_header(ngx_http_request_t *r) +{ + u_char *buf, *p, *end; + size_t len; + ngx_str_t *vals; + ngx_uint_t i; + ngx_array_t *cookies; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + cookies = r->stream->cookies; + + if (cookies == NULL) { + return NGX_OK; + } + + vals = cookies->elts; + + i = 0; + len = 0; + + do { + len += vals[i].len + 2; + } while (++i != cookies->nelts); + + len -= 2; + + buf = ngx_pnalloc(r->pool, len + 1); + if (buf == NULL) { + ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + p = buf; + end = buf + len; + + for (i = 0; /* void */ ; i++) { + + p = ngx_cpymem(p, vals[i].data, vals[i].len); + + if (p == end) { + *p = '\0'; + break; + } + + *p++ = ';'; *p++ = ' '; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + h->hash = ngx_hash_key(cookie.data, cookie.len); + + h->key.len = cookie.len; + h->key.data = cookie.data; + + h->value.len = len; + h->value.data = buf; + + h->lowcase_key = cookie.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_multi_header_lines() + */ + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_http_v2_run_request(ngx_http_request_t *r) +{ + if (ngx_http_v2_construct_request_line(r) != NGX_OK) { + return; + } + + if (ngx_http_v2_construct_cookie_header(r) != NGX_OK) { + return; + } + + r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; + + if (ngx_http_process_request_header(r) != NGX_OK) { + return; + } + + if (r->headers_in.content_length_n > 0 && r->stream->in_closed) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream"); + + r->stream->skip_data = 1; + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return; + } + + if (r->headers_in.content_length_n == -1 && !r->stream->in_closed) { + r->headers_in.chunked = 1; + } + + ngx_http_process_request(r); +} + + +ngx_int_t +ngx_http_v2_read_request_body(ngx_http_request_t *r, + ngx_http_client_body_handler_pt post_handler) +{ + off_t len; + size_t size; + ngx_buf_t *buf; + ngx_int_t rc; + ngx_http_v2_stream_t *stream; + ngx_http_v2_srv_conf_t *h2scf; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + ngx_http_v2_connection_t *h2c; + + stream = r->stream; + + if (stream->skip_data) { + r->request_body_no_buffering = 0; + post_handler(r); + return NGX_OK; + } + + rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); + if (rb == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + /* + * set by ngx_pcalloc(): + * + * rb->bufs = NULL; + * rb->buf = NULL; + * rb->received = 0; + * rb->free = NULL; + * rb->busy = NULL; + */ + + rb->rest = 1; + rb->post_handler = post_handler; + + r->request_body = rb; + + h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module); + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + len = r->headers_in.content_length_n; + + if (r->request_body_no_buffering && !stream->in_closed) { + + if (len < 0 || len > (off_t) clcf->client_body_buffer_size) { + len = clcf->client_body_buffer_size; + } + + /* + * We need a room to store data up to the stream's initial window size, + * at least until this window will be exhausted. + */ + + if (len < (off_t) h2scf->preread_size) { + len = h2scf->preread_size; + } + + if (len > NGX_HTTP_V2_MAX_WINDOW) { + len = NGX_HTTP_V2_MAX_WINDOW; + } + + rb->buf = ngx_create_temp_buf(r->pool, (size_t) len); + + } else if (len >= 0 && len <= (off_t) clcf->client_body_buffer_size + && !r->request_body_in_file_only) + { + rb->buf = ngx_create_temp_buf(r->pool, (size_t) len); + + } else { + if (stream->preread) { + /* enforce writing preread buffer to file */ + r->request_body_in_file_only = 1; + } + + rb->buf = ngx_calloc_buf(r->pool); + + if (rb->buf != NULL) { + rb->buf->sync = 1; + } + } + + if (rb->buf == NULL) { + stream->skip_data = 1; + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + buf = stream->preread; + + if (stream->in_closed) { + r->request_body_no_buffering = 0; + + if (buf) { + rc = ngx_http_v2_process_request_body(r, buf->pos, + buf->last - buf->pos, 1); + ngx_pfree(r->pool, buf->start); + return rc; + } + + return ngx_http_v2_process_request_body(r, NULL, 0, 1); + } + + if (buf) { + rc = ngx_http_v2_process_request_body(r, buf->pos, + buf->last - buf->pos, 0); + + ngx_pfree(r->pool, buf->start); + + if (rc != NGX_OK) { + stream->skip_data = 1; + return rc; + } + } + + if (r->request_body_no_buffering) { + size = (size_t) len - h2scf->preread_size; + + } else { + stream->no_flow_control = 1; + size = NGX_HTTP_V2_MAX_WINDOW - stream->recv_window; + } + + if (size) { + if (ngx_http_v2_send_window_update(stream->connection, + stream->node->id, size) + == NGX_ERROR) + { + stream->skip_data = 1; + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + h2c = stream->connection; + + if (!h2c->blocked) { + if (ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) { + stream->skip_data = 1; + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + } + + stream->recv_window += size; + } + + if (!buf) { + ngx_add_timer(r->connection->read, clcf->client_body_timeout); + } + + r->read_event_handler = ngx_http_v2_read_client_request_body_handler; + r->write_event_handler = ngx_http_request_empty_handler; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_v2_process_request_body(ngx_http_request_t *r, u_char *pos, + size_t size, ngx_uint_t last) +{ + ngx_buf_t *buf; + ngx_int_t rc; + ngx_connection_t *fc; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + fc = r->connection; + rb = r->request_body; + buf = rb->buf; + + if (size) { + if (buf->sync) { + buf->pos = buf->start = pos; + buf->last = buf->end = pos + size; + + } else { + if (size > (size_t) (buf->end - buf->last)) { + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client intended to send body data " + "larger than declared"); + + return NGX_HTTP_BAD_REQUEST; + } + + buf->last = ngx_cpymem(buf->last, pos, size); + } + } + + if (last) { + rb->rest = 0; + + if (fc->read->timer_set) { + ngx_del_timer(fc->read); + } + + if (r->request_body_no_buffering) { + ngx_post_event(fc->read, &ngx_posted_events); + return NGX_OK; + } + + rc = ngx_http_v2_filter_request_body(r); + + if (rc != NGX_OK) { + return rc; + } + + if (buf->sync) { + /* prevent reusing this buffer in the upstream module */ + rb->buf = NULL; + } + + if (r->headers_in.chunked) { + r->headers_in.content_length_n = rb->received; + } + + r->read_event_handler = ngx_http_block_reading; + rb->post_handler(r); + + return NGX_OK; + } + + if (size == 0) { + return NGX_OK; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + ngx_add_timer(fc->read, clcf->client_body_timeout); + + if (r->request_body_no_buffering) { + ngx_post_event(fc->read, &ngx_posted_events); + return NGX_OK; + } + + if (buf->sync) { + return ngx_http_v2_filter_request_body(r); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_filter_request_body(ngx_http_request_t *r) +{ + ngx_buf_t *b, *buf; + ngx_int_t rc; + ngx_chain_t *cl; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + rb = r->request_body; + buf = rb->buf; + + if (buf->pos == buf->last && rb->rest) { + cl = NULL; + goto update; + } + + cl = ngx_chain_get_free_buf(r->pool, &rb->free); + if (cl == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b = cl->buf; + + ngx_memzero(b, sizeof(ngx_buf_t)); + + if (buf->pos != buf->last) { + r->request_length += buf->last - buf->pos; + rb->received += buf->last - buf->pos; + + if (r->headers_in.content_length_n != -1) { + if (rb->received > r->headers_in.content_length_n) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client intended to send body data " + "larger than declared"); + + return NGX_HTTP_BAD_REQUEST; + } + + } else { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->client_max_body_size + && rb->received > clcf->client_max_body_size) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client intended to send too large chunked body: " + "%O bytes", rb->received); + + return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; + } + } + + b->temporary = 1; + b->pos = buf->pos; + b->last = buf->last; + b->start = b->pos; + b->end = b->last; + + buf->pos = buf->last; + } + + if (!rb->rest) { + if (r->headers_in.content_length_n != -1 + && r->headers_in.content_length_n != rb->received) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream: " + "only %O out of %O bytes of request body received", + rb->received, r->headers_in.content_length_n); + + return NGX_HTTP_BAD_REQUEST; + } + + b->last_buf = 1; + } + + b->tag = (ngx_buf_tag_t) &ngx_http_v2_filter_request_body; + b->flush = r->request_body_no_buffering; + +update: + + rc = ngx_http_top_request_body_filter(r, cl); + + ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &cl, + (ngx_buf_tag_t) &ngx_http_v2_filter_request_body); + + return rc; +} + + +static void +ngx_http_v2_read_client_request_body_handler(ngx_http_request_t *r) +{ + ngx_connection_t *fc; + + fc = r->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 read client request body handler"); + + if (fc->read->timedout) { + ngx_log_error(NGX_LOG_INFO, fc->log, NGX_ETIMEDOUT, "client timed out"); + + fc->timedout = 1; + r->stream->skip_data = 1; + + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + if (fc->error) { + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client prematurely closed stream"); + + r->stream->skip_data = 1; + + ngx_http_finalize_request(r, NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } +} + + +ngx_int_t +ngx_http_v2_read_unbuffered_request_body(ngx_http_request_t *r) +{ + size_t window; + ngx_buf_t *buf; + ngx_int_t rc; + ngx_connection_t *fc; + ngx_http_v2_stream_t *stream; + ngx_http_v2_connection_t *h2c; + ngx_http_core_loc_conf_t *clcf; + + stream = r->stream; + fc = r->connection; + + if (fc->read->timedout) { + if (stream->recv_window) { + stream->skip_data = 1; + fc->timedout = 1; + + return NGX_HTTP_REQUEST_TIME_OUT; + } + + fc->read->timedout = 0; + } + + if (fc->error) { + stream->skip_data = 1; + return NGX_HTTP_BAD_REQUEST; + } + + rc = ngx_http_v2_filter_request_body(r); + + if (rc != NGX_OK) { + stream->skip_data = 1; + return rc; + } + + if (!r->request_body->rest) { + return NGX_OK; + } + + if (r->request_body->busy != NULL) { + return NGX_AGAIN; + } + + buf = r->request_body->buf; + + buf->pos = buf->start; + buf->last = buf->start; + + window = buf->end - buf->start; + h2c = stream->connection; + + if (h2c->state.stream == stream) { + window -= h2c->state.length; + } + + if (window <= stream->recv_window) { + if (window < stream->recv_window) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "http2 negative window update"); + stream->skip_data = 1; + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + + if (ngx_http_v2_send_window_update(h2c, stream->node->id, + window - stream->recv_window) + == NGX_ERROR) + { + stream->skip_data = 1; + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) { + stream->skip_data = 1; + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (stream->recv_window == 0) { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + ngx_add_timer(fc->read, clcf->client_body_timeout); + } + + stream->recv_window = window; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_v2_terminate_stream(ngx_http_v2_connection_t *h2c, + ngx_http_v2_stream_t *stream, ngx_uint_t status) +{ + ngx_event_t *rev; + ngx_connection_t *fc; + + if (stream->rst_sent) { + return NGX_OK; + } + + if (ngx_http_v2_send_rst_stream(h2c, stream->node->id, status) + == NGX_ERROR) + { + return NGX_ERROR; + } + + stream->rst_sent = 1; + stream->skip_data = 1; + + fc = stream->request->connection; + fc->error = 1; + + rev = fc->read; + rev->handler(rev); + + return NGX_OK; +} + + +void +ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc) +{ + ngx_pool_t *pool; + ngx_event_t *ev; + ngx_connection_t *fc; + ngx_http_v2_node_t *node; + ngx_http_v2_connection_t *h2c; + + h2c = stream->connection; + node = stream->node; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 close stream %ui, queued %ui, processing %ui", + node->id, stream->queued, h2c->processing); + + fc = stream->request->connection; + + if (stream->queued) { + fc->write->handler = ngx_http_v2_close_stream_handler; + fc->read->handler = ngx_http_empty_handler; + return; + } + + if (!stream->rst_sent && !h2c->connection->error) { + + if (!stream->out_closed) { + if (ngx_http_v2_send_rst_stream(h2c, node->id, + fc->timedout ? NGX_HTTP_V2_PROTOCOL_ERROR + : NGX_HTTP_V2_INTERNAL_ERROR) + != NGX_OK) + { + h2c->connection->error = 1; + } + + } else if (!stream->in_closed) { +#if 0 + if (ngx_http_v2_send_rst_stream(h2c, node->id, NGX_HTTP_V2_NO_ERROR) + != NGX_OK) + { + h2c->connection->error = 1; + } +#else + /* + * At the time of writing at least the latest versions of Chrome + * do not properly handle RST_STREAM with NO_ERROR status. + * + * See: https://bugs.chromium.org/p/chromium/issues/detail?id=603182 + * + * As a workaround, the stream window is maximized before closing + * the stream. This allows a client to send up to 2 GB of data + * before getting blocked on flow control. + */ + + if (stream->recv_window < NGX_HTTP_V2_MAX_WINDOW + && ngx_http_v2_send_window_update(h2c, node->id, + NGX_HTTP_V2_MAX_WINDOW + - stream->recv_window) + != NGX_OK) + { + h2c->connection->error = 1; + } +#endif + } + } + + if (h2c->state.stream == stream) { + h2c->state.stream = NULL; + } + + node->stream = NULL; + + ngx_queue_insert_tail(&h2c->closed, &node->reuse); + h2c->closed_nodes++; + + /* + * This pool keeps decoded request headers which can be used by log phase + * handlers in ngx_http_free_request(). + * + * The pointer is stored into local variable because the stream object + * will be destroyed after a call to ngx_http_free_request(). + */ + pool = stream->pool; + + ngx_http_free_request(stream->request, rc); + + if (pool != h2c->state.pool) { + ngx_destroy_pool(pool); + + } else { + /* pool will be destroyed when the complete header is parsed */ + h2c->state.keep_pool = 0; + } + + ev = fc->read; + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + ev = fc->write; + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + fc->data = h2c->free_fake_connections; + h2c->free_fake_connections = fc; + + h2c->processing--; + + if (h2c->processing || h2c->blocked) { + return; + } + + ev = h2c->connection->read; + + ev->handler = ngx_http_v2_handle_connection_handler; + ngx_post_event(ev, &ngx_posted_events); +} + + +static void +ngx_http_v2_close_stream_handler(ngx_event_t *ev) +{ + ngx_connection_t *fc; + ngx_http_request_t *r; + + fc = ev->data; + r = fc->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 close stream handler"); + + if (ev->timedout) { + ngx_log_error(NGX_LOG_INFO, fc->log, NGX_ETIMEDOUT, "client timed out"); + + fc->timedout = 1; + + ngx_http_v2_close_stream(r->stream, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + ngx_http_v2_close_stream(r->stream, 0); +} + + +static void +ngx_http_v2_handle_connection_handler(ngx_event_t *rev) +{ + ngx_connection_t *c; + ngx_http_v2_connection_t *h2c; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "http2 handle connection handler"); + + c = rev->data; + h2c = c->data; + + if (c->error) { + ngx_http_v2_finalize_connection(h2c, 0); + return; + } + + rev->handler = ngx_http_v2_read_handler; + + if (rev->ready) { + ngx_http_v2_read_handler(rev); + return; + } + + if (h2c->last_out && ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) { + ngx_http_v2_finalize_connection(h2c, 0); + return; + } + + ngx_http_v2_handle_connection(c->data); +} + + +static void +ngx_http_v2_idle_handler(ngx_event_t *rev) +{ + ngx_connection_t *c; + ngx_http_v2_srv_conf_t *h2scf; + ngx_http_v2_connection_t *h2c; + + c = rev->data; + h2c = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 idle handler"); + + if (rev->timedout || c->close) { + ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_NO_ERROR); + return; + } + +#if (NGX_HAVE_KQUEUE) + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { + if (rev->pending_eof) { + c->log->handler = NULL; + ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno, + "kevent() reported that client %V closed " + "idle connection", &c->addr_text); +#if (NGX_HTTP_SSL) + if (c->ssl) { + c->ssl->no_send_shutdown = 1; + } +#endif + ngx_http_close_connection(c); + return; + } + } + +#endif + + c->destroyed = 0; + ngx_reusable_connection(c, 0); + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + h2c->pool = ngx_create_pool(h2scf->pool_size, h2c->connection->log); + if (h2c->pool == NULL) { + ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + return; + } + + c->write->handler = ngx_http_v2_write_handler; + + rev->handler = ngx_http_v2_read_handler; + ngx_http_v2_read_handler(rev); +} + + +static void +ngx_http_v2_finalize_connection(ngx_http_v2_connection_t *h2c, + ngx_uint_t status) +{ + ngx_uint_t i, size; + ngx_event_t *ev; + ngx_connection_t *c, *fc; + ngx_http_request_t *r; + ngx_http_v2_node_t *node; + ngx_http_v2_stream_t *stream; + ngx_http_v2_srv_conf_t *h2scf; + + c = h2c->connection; + + h2c->blocked = 1; + + if (!c->error && !h2c->goaway) { + if (ngx_http_v2_send_goaway(h2c, status) != NGX_ERROR) { + (void) ngx_http_v2_send_output_queue(h2c); + } + } + + c->error = 1; + + if (!h2c->processing) { + ngx_http_close_connection(c); + return; + } + + c->read->handler = ngx_http_empty_handler; + c->write->handler = ngx_http_empty_handler; + + h2c->last_out = NULL; + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + size = ngx_http_v2_index_size(h2scf); + + for (i = 0; i < size; i++) { + + for (node = h2c->streams_index[i]; node; node = node->index) { + stream = node->stream; + + if (stream == NULL) { + continue; + } + + stream->waiting = 0; + + r = stream->request; + fc = r->connection; + + fc->error = 1; + + if (stream->queued) { + stream->queued = 0; + + ev = fc->write; + ev->active = 0; + ev->ready = 1; + + } else { + ev = fc->read; + } + + ev->eof = 1; + ev->handler(ev); + } + } + + h2c->blocked = 0; + + if (h2c->processing) { + return; + } + + ngx_http_close_connection(c); +} + + +static ngx_int_t +ngx_http_v2_adjust_windows(ngx_http_v2_connection_t *h2c, ssize_t delta) +{ + ngx_uint_t i, size; + ngx_event_t *wev; + ngx_http_v2_node_t *node; + ngx_http_v2_stream_t *stream; + ngx_http_v2_srv_conf_t *h2scf; + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + size = ngx_http_v2_index_size(h2scf); + + for (i = 0; i < size; i++) { + + for (node = h2c->streams_index[i]; node; node = node->index) { + stream = node->stream; + + if (stream == NULL) { + continue; + } + + if (delta > 0 + && stream->send_window + > (ssize_t) (NGX_HTTP_V2_MAX_WINDOW - delta)) + { + if (ngx_http_v2_terminate_stream(h2c, stream, + NGX_HTTP_V2_FLOW_CTRL_ERROR) + == NGX_ERROR) + { + return NGX_ERROR; + } + + continue; + } + + stream->send_window += delta; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2:%ui adjusted window: %z", + node->id, stream->send_window); + + if (stream->send_window > 0 && stream->exhausted) { + stream->exhausted = 0; + + wev = stream->request->connection->write; + + wev->active = 0; + wev->ready = 1; + + if (!wev->delayed) { + wev->handler(wev); + } + } + } + } + + return NGX_OK; +} + + +static void +ngx_http_v2_set_dependency(ngx_http_v2_connection_t *h2c, + ngx_http_v2_node_t *node, ngx_uint_t depend, ngx_uint_t exclusive) +{ + ngx_queue_t *children, *q; + ngx_http_v2_node_t *parent, *child, *next; + + parent = depend ? ngx_http_v2_get_node_by_id(h2c, depend, 0) : NULL; + + if (parent == NULL) { + parent = NGX_HTTP_V2_ROOT; + + if (depend != 0) { + exclusive = 0; + } + + node->rank = 1; + node->rel_weight = (1.0 / 256) * node->weight; + + children = &h2c->dependencies; + + } else { + if (node->parent != NULL) { + + for (next = parent->parent; + next != NGX_HTTP_V2_ROOT && next->rank >= node->rank; + next = next->parent) + { + if (next != node) { + continue; + } + + ngx_queue_remove(&parent->queue); + ngx_queue_insert_after(&node->queue, &parent->queue); + + parent->parent = node->parent; + + if (node->parent == NGX_HTTP_V2_ROOT) { + parent->rank = 1; + parent->rel_weight = (1.0 / 256) * parent->weight; + + } else { + parent->rank = node->parent->rank + 1; + parent->rel_weight = (node->parent->rel_weight / 256) + * parent->weight; + } + + if (!exclusive) { + ngx_http_v2_node_children_update(parent); + } + + break; + } + } + + node->rank = parent->rank + 1; + node->rel_weight = (parent->rel_weight / 256) * node->weight; + + if (parent->stream == NULL) { + ngx_queue_remove(&parent->reuse); + ngx_queue_insert_tail(&h2c->closed, &parent->reuse); + } + + children = &parent->children; + } + + if (exclusive) { + for (q = ngx_queue_head(children); + q != ngx_queue_sentinel(children); + q = ngx_queue_next(q)) + { + child = ngx_queue_data(q, ngx_http_v2_node_t, queue); + child->parent = node; + } + + ngx_queue_add(&node->children, children); + ngx_queue_init(children); + } + + if (node->parent != NULL) { + ngx_queue_remove(&node->queue); + } + + ngx_queue_insert_tail(children, &node->queue); + + node->parent = parent; + + ngx_http_v2_node_children_update(node); +} + + +static void +ngx_http_v2_node_children_update(ngx_http_v2_node_t *node) +{ + ngx_queue_t *q; + ngx_http_v2_node_t *child; + + for (q = ngx_queue_head(&node->children); + q != ngx_queue_sentinel(&node->children); + q = ngx_queue_next(q)) + { + child = ngx_queue_data(q, ngx_http_v2_node_t, queue); + + child->rank = node->rank + 1; + child->rel_weight = (node->rel_weight / 256) * child->weight; + + ngx_http_v2_node_children_update(child); + } +} + + +static void +ngx_http_v2_pool_cleanup(void *data) +{ + ngx_http_v2_connection_t *h2c = data; + + if (h2c->state.pool) { + ngx_destroy_pool(h2c->state.pool); + } + + if (h2c->pool) { + ngx_destroy_pool(h2c->pool); + } +} diff --git a/app/nginx/src/http/v2/ngx_http_v2.h b/app/nginx/src/http/v2/ngx_http_v2.h new file mode 100644 index 0000000..7d2a2ea --- /dev/null +++ b/app/nginx/src/http/v2/ngx_http_v2.h @@ -0,0 +1,342 @@ +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#ifndef _NGX_HTTP_V2_H_INCLUDED_ +#define _NGX_HTTP_V2_H_INCLUDED_ + + +#include +#include +#include + + +#define NGX_HTTP_V2_ALPN_ADVERTISE "\x02h2" +#define NGX_HTTP_V2_NPN_ADVERTISE NGX_HTTP_V2_ALPN_ADVERTISE + +#define NGX_HTTP_V2_STATE_BUFFER_SIZE 16 + +#define NGX_HTTP_V2_MAX_FRAME_SIZE ((1 << 24) - 1) + +#define NGX_HTTP_V2_INT_OCTETS 4 +#define NGX_HTTP_V2_MAX_FIELD \ + (127 + (1 << (NGX_HTTP_V2_INT_OCTETS - 1) * 7) - 1) + +#define NGX_HTTP_V2_FRAME_HEADER_SIZE 9 + +/* frame types */ +#define NGX_HTTP_V2_DATA_FRAME 0x0 +#define NGX_HTTP_V2_HEADERS_FRAME 0x1 +#define NGX_HTTP_V2_PRIORITY_FRAME 0x2 +#define NGX_HTTP_V2_RST_STREAM_FRAME 0x3 +#define NGX_HTTP_V2_SETTINGS_FRAME 0x4 +#define NGX_HTTP_V2_PUSH_PROMISE_FRAME 0x5 +#define NGX_HTTP_V2_PING_FRAME 0x6 +#define NGX_HTTP_V2_GOAWAY_FRAME 0x7 +#define NGX_HTTP_V2_WINDOW_UPDATE_FRAME 0x8 +#define NGX_HTTP_V2_CONTINUATION_FRAME 0x9 + +/* frame flags */ +#define NGX_HTTP_V2_NO_FLAG 0x00 +#define NGX_HTTP_V2_ACK_FLAG 0x01 +#define NGX_HTTP_V2_END_STREAM_FLAG 0x01 +#define NGX_HTTP_V2_END_HEADERS_FLAG 0x04 +#define NGX_HTTP_V2_PADDED_FLAG 0x08 +#define NGX_HTTP_V2_PRIORITY_FLAG 0x20 + +#define NGX_HTTP_V2_MAX_WINDOW ((1U << 31) - 1) +#define NGX_HTTP_V2_DEFAULT_WINDOW 65535 + + +typedef struct ngx_http_v2_connection_s ngx_http_v2_connection_t; +typedef struct ngx_http_v2_node_s ngx_http_v2_node_t; +typedef struct ngx_http_v2_out_frame_s ngx_http_v2_out_frame_t; + + +typedef u_char *(*ngx_http_v2_handler_pt) (ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); + + +typedef struct { + ngx_str_t name; + ngx_str_t value; +} ngx_http_v2_header_t; + + +typedef struct { + ngx_uint_t sid; + size_t length; + size_t padding; + unsigned flags:8; + + unsigned incomplete:1; + unsigned keep_pool:1; + + /* HPACK */ + unsigned parse_name:1; + unsigned parse_value:1; + unsigned index:1; + ngx_http_v2_header_t header; + size_t header_limit; + u_char field_state; + u_char *field_start; + u_char *field_end; + size_t field_rest; + ngx_pool_t *pool; + + ngx_http_v2_stream_t *stream; + + u_char buffer[NGX_HTTP_V2_STATE_BUFFER_SIZE]; + size_t buffer_used; + ngx_http_v2_handler_pt handler; +} ngx_http_v2_state_t; + + + +typedef struct { + ngx_http_v2_header_t **entries; + + ngx_uint_t added; + ngx_uint_t deleted; + ngx_uint_t reused; + ngx_uint_t allocated; + + size_t size; + size_t free; + u_char *storage; + u_char *pos; +} ngx_http_v2_hpack_t; + + +struct ngx_http_v2_connection_s { + ngx_connection_t *connection; + ngx_http_connection_t *http_connection; + + ngx_uint_t processing; + + size_t send_window; + size_t recv_window; + size_t init_window; + + size_t frame_size; + + ngx_queue_t waiting; + + ngx_http_v2_state_t state; + + ngx_http_v2_hpack_t hpack; + + ngx_pool_t *pool; + + ngx_http_v2_out_frame_t *free_frames; + ngx_connection_t *free_fake_connections; + + ngx_http_v2_node_t **streams_index; + + ngx_http_v2_out_frame_t *last_out; + + ngx_queue_t dependencies; + ngx_queue_t closed; + + ngx_uint_t last_sid; + + unsigned closed_nodes:8; + unsigned settings_ack:1; + unsigned blocked:1; + unsigned goaway:1; +}; + + +struct ngx_http_v2_node_s { + ngx_uint_t id; + ngx_http_v2_node_t *index; + ngx_http_v2_node_t *parent; + ngx_queue_t queue; + ngx_queue_t children; + ngx_queue_t reuse; + ngx_uint_t rank; + ngx_uint_t weight; + double rel_weight; + ngx_http_v2_stream_t *stream; +}; + + +struct ngx_http_v2_stream_s { + ngx_http_request_t *request; + ngx_http_v2_connection_t *connection; + ngx_http_v2_node_t *node; + + ngx_uint_t queued; + + /* + * A change to SETTINGS_INITIAL_WINDOW_SIZE could cause the + * send_window to become negative, hence it's signed. + */ + ssize_t send_window; + size_t recv_window; + + ngx_buf_t *preread; + + ngx_http_v2_out_frame_t *free_frames; + ngx_chain_t *free_frame_headers; + ngx_chain_t *free_bufs; + + ngx_queue_t queue; + + ngx_array_t *cookies; + + size_t header_limit; + + ngx_pool_t *pool; + + unsigned waiting:1; + unsigned blocked:1; + unsigned exhausted:1; + unsigned in_closed:1; + unsigned out_closed:1; + unsigned rst_sent:1; + unsigned no_flow_control:1; + unsigned skip_data:1; +}; + + +struct ngx_http_v2_out_frame_s { + ngx_http_v2_out_frame_t *next; + ngx_chain_t *first; + ngx_chain_t *last; + ngx_int_t (*handler)(ngx_http_v2_connection_t *h2c, + ngx_http_v2_out_frame_t *frame); + + ngx_http_v2_stream_t *stream; + size_t length; + + unsigned blocked:1; + unsigned fin:1; +}; + + +static ngx_inline void +ngx_http_v2_queue_frame(ngx_http_v2_connection_t *h2c, + ngx_http_v2_out_frame_t *frame) +{ + ngx_http_v2_out_frame_t **out; + + for (out = &h2c->last_out; *out; out = &(*out)->next) { + + if ((*out)->blocked || (*out)->stream == NULL) { + break; + } + + if ((*out)->stream->node->rank < frame->stream->node->rank + || ((*out)->stream->node->rank == frame->stream->node->rank + && (*out)->stream->node->rel_weight + >= frame->stream->node->rel_weight)) + { + break; + } + } + + frame->next = *out; + *out = frame; +} + + +static ngx_inline void +ngx_http_v2_queue_blocked_frame(ngx_http_v2_connection_t *h2c, + ngx_http_v2_out_frame_t *frame) +{ + ngx_http_v2_out_frame_t **out; + + for (out = &h2c->last_out; *out; out = &(*out)->next) { + + if ((*out)->blocked || (*out)->stream == NULL) { + break; + } + } + + frame->next = *out; + *out = frame; +} + + +void ngx_http_v2_init(ngx_event_t *rev); +void ngx_http_v2_request_headers_init(void); + +ngx_int_t ngx_http_v2_read_request_body(ngx_http_request_t *r, + ngx_http_client_body_handler_pt post_handler); +ngx_int_t ngx_http_v2_read_unbuffered_request_body(ngx_http_request_t *r); + +void ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc); + +ngx_int_t ngx_http_v2_send_output_queue(ngx_http_v2_connection_t *h2c); + + +ngx_int_t ngx_http_v2_get_indexed_header(ngx_http_v2_connection_t *h2c, + ngx_uint_t index, ngx_uint_t name_only); +ngx_int_t ngx_http_v2_add_header(ngx_http_v2_connection_t *h2c, + ngx_http_v2_header_t *header); +ngx_int_t ngx_http_v2_table_size(ngx_http_v2_connection_t *h2c, size_t size); + + +ngx_int_t ngx_http_v2_huff_decode(u_char *state, u_char *src, size_t len, + u_char **dst, ngx_uint_t last, ngx_log_t *log); +size_t ngx_http_v2_huff_encode(u_char *src, size_t len, u_char *dst, + ngx_uint_t lower); + + +#define ngx_http_v2_prefix(bits) ((1 << (bits)) - 1) + + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_http_v2_parse_uint16(p) ntohs(*(uint16_t *) (p)) +#define ngx_http_v2_parse_uint32(p) ntohl(*(uint32_t *) (p)) + +#else + +#define ngx_http_v2_parse_uint16(p) ((p)[0] << 8 | (p)[1]) +#define ngx_http_v2_parse_uint32(p) \ + ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) + +#endif + +#define ngx_http_v2_parse_length(p) ((p) >> 8) +#define ngx_http_v2_parse_type(p) ((p) & 0xff) +#define ngx_http_v2_parse_sid(p) (ngx_http_v2_parse_uint32(p) & 0x7fffffff) +#define ngx_http_v2_parse_window(p) (ngx_http_v2_parse_uint32(p) & 0x7fffffff) + + +#define ngx_http_v2_write_uint16_aligned(p, s) \ + (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) +#define ngx_http_v2_write_uint32_aligned(p, s) \ + (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_http_v2_write_uint16 ngx_http_v2_write_uint16_aligned +#define ngx_http_v2_write_uint32 ngx_http_v2_write_uint32_aligned + +#else + +#define ngx_http_v2_write_uint16(p, s) \ + ((p)[0] = (u_char) ((s) >> 8), \ + (p)[1] = (u_char) (s), \ + (p) + sizeof(uint16_t)) + +#define ngx_http_v2_write_uint32(p, s) \ + ((p)[0] = (u_char) ((s) >> 24), \ + (p)[1] = (u_char) ((s) >> 16), \ + (p)[2] = (u_char) ((s) >> 8), \ + (p)[3] = (u_char) (s), \ + (p) + sizeof(uint32_t)) + +#endif + +#define ngx_http_v2_write_len_and_type(p, l, t) \ + ngx_http_v2_write_uint32_aligned(p, (l) << 8 | (t)) + +#define ngx_http_v2_write_sid ngx_http_v2_write_uint32 + +#endif /* _NGX_HTTP_V2_H_INCLUDED_ */ diff --git a/app/nginx/src/http/v2/ngx_http_v2_filter_module.c b/app/nginx/src/http/v2/ngx_http_v2_filter_module.c new file mode 100644 index 0000000..dac5046 --- /dev/null +++ b/app/nginx/src/http/v2/ngx_http_v2_filter_module.c @@ -0,0 +1,1448 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include +#include + + +/* + * This returns precise number of octets for values in range 0..253 + * and estimate number for the rest, but not smaller than required. + */ + +#define ngx_http_v2_integer_octets(v) (1 + (v) / 127) + +#define ngx_http_v2_literal_size(h) \ + (ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1) + +#define ngx_http_v2_indexed(i) (128 + (i)) +#define ngx_http_v2_inc_indexed(i) (64 + (i)) + +#define ngx_http_v2_write_name(dst, src, len, tmp) \ + ngx_http_v2_string_encode(dst, src, len, tmp, 1) +#define ngx_http_v2_write_value(dst, src, len, tmp) \ + ngx_http_v2_string_encode(dst, src, len, tmp, 0) + +#define NGX_HTTP_V2_ENCODE_RAW 0 +#define NGX_HTTP_V2_ENCODE_HUFF 0x80 + +#define NGX_HTTP_V2_STATUS_INDEX 8 +#define NGX_HTTP_V2_STATUS_200_INDEX 8 +#define NGX_HTTP_V2_STATUS_204_INDEX 9 +#define NGX_HTTP_V2_STATUS_206_INDEX 10 +#define NGX_HTTP_V2_STATUS_304_INDEX 11 +#define NGX_HTTP_V2_STATUS_400_INDEX 12 +#define NGX_HTTP_V2_STATUS_404_INDEX 13 +#define NGX_HTTP_V2_STATUS_500_INDEX 14 + +#define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 +#define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 +#define NGX_HTTP_V2_DATE_INDEX 33 +#define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 +#define NGX_HTTP_V2_LOCATION_INDEX 46 +#define NGX_HTTP_V2_SERVER_INDEX 54 +#define NGX_HTTP_V2_VARY_INDEX 59 + + +static u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, + u_char *tmp, ngx_uint_t lower); +static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, + ngx_uint_t value); +static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame( + ngx_http_request_t *r, u_char *pos, u_char *end); + +static ngx_chain_t *ngx_http_v2_send_chain(ngx_connection_t *fc, + ngx_chain_t *in, off_t limit); + +static ngx_chain_t *ngx_http_v2_filter_get_shadow( + ngx_http_v2_stream_t *stream, ngx_buf_t *buf, off_t offset, off_t size); +static ngx_http_v2_out_frame_t *ngx_http_v2_filter_get_data_frame( + ngx_http_v2_stream_t *stream, size_t len, ngx_chain_t *first, + ngx_chain_t *last); + +static ngx_inline ngx_int_t ngx_http_v2_flow_control( + ngx_http_v2_connection_t *h2c, ngx_http_v2_stream_t *stream); +static void ngx_http_v2_waiting_queue(ngx_http_v2_connection_t *h2c, + ngx_http_v2_stream_t *stream); + +static ngx_inline ngx_int_t ngx_http_v2_filter_send( + ngx_connection_t *fc, ngx_http_v2_stream_t *stream); + +static ngx_int_t ngx_http_v2_headers_frame_handler( + ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); +static ngx_int_t ngx_http_v2_data_frame_handler( + ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); +static ngx_inline void ngx_http_v2_handle_frame( + ngx_http_v2_stream_t *stream, ngx_http_v2_out_frame_t *frame); +static ngx_inline void ngx_http_v2_handle_stream( + ngx_http_v2_connection_t *h2c, ngx_http_v2_stream_t *stream); + +static void ngx_http_v2_filter_cleanup(void *data); + +static ngx_int_t ngx_http_v2_filter_init(ngx_conf_t *cf); + + +static ngx_http_module_t ngx_http_v2_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_v2_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_v2_filter_module = { + NGX_MODULE_V1, + &ngx_http_v2_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_v2_header_filter(ngx_http_request_t *r) +{ + u_char status, *pos, *start, *p, *tmp; + size_t len, tmp_len; + ngx_str_t host, location; + ngx_uint_t i, port; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_connection_t *fc; + ngx_http_cleanup_t *cln; + ngx_http_v2_out_frame_t *frame; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + u_char addr[NGX_SOCKADDR_STRLEN]; + + static const u_char nginx[5] = "\x84\xaa\x63\x55\xe7"; +#if (NGX_HTTP_GZIP) + static const u_char accept_encoding[12] = + "\x8b\x84\x84\x2d\x69\x5b\x05\x44\x3c\x86\xaa\x6f"; +#endif + + static size_t nginx_ver_len = ngx_http_v2_literal_size(NGINX_VER); + static u_char nginx_ver[ngx_http_v2_literal_size(NGINX_VER)]; + + static size_t nginx_ver_build_len = + ngx_http_v2_literal_size(NGINX_VER_BUILD); + static u_char nginx_ver_build[ngx_http_v2_literal_size(NGINX_VER_BUILD)]; + + if (!r->stream) { + return ngx_http_next_header_filter(r); + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http2 header filter"); + + if (r->header_sent) { + return NGX_OK; + } + + r->header_sent = 1; + + if (r != r->main) { + return NGX_OK; + } + + fc = r->connection; + + if (fc->error) { + return NGX_ERROR; + } + + if (r->method == NGX_HTTP_HEAD) { + r->header_only = 1; + } + + switch (r->headers_out.status) { + + case NGX_HTTP_OK: + status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_200_INDEX); + break; + + case NGX_HTTP_NO_CONTENT: + r->header_only = 1; + + ngx_str_null(&r->headers_out.content_type); + + r->headers_out.content_length = NULL; + r->headers_out.content_length_n = -1; + + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + + status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_204_INDEX); + break; + + case NGX_HTTP_PARTIAL_CONTENT: + status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_206_INDEX); + break; + + case NGX_HTTP_NOT_MODIFIED: + r->header_only = 1; + status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_304_INDEX); + break; + + default: + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + + switch (r->headers_out.status) { + + case NGX_HTTP_BAD_REQUEST: + status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_400_INDEX); + break; + + case NGX_HTTP_NOT_FOUND: + status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_404_INDEX); + break; + + case NGX_HTTP_INTERNAL_SERVER_ERROR: + status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_500_INDEX); + break; + + default: + status = 0; + } + } + + len = status ? 1 : 1 + ngx_http_v2_literal_size("418"); + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->headers_out.server == NULL) { + + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + len += 1 + nginx_ver_len; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + len += 1 + nginx_ver_build_len; + + } else { + len += 1 + sizeof(nginx); + } + } + + if (r->headers_out.date == NULL) { + len += 1 + ngx_http_v2_literal_size("Wed, 31 Dec 1986 18:00:00 GMT"); + } + + if (r->headers_out.content_type.len) { + len += 1 + NGX_HTTP_V2_INT_OCTETS + r->headers_out.content_type.len; + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + len += sizeof("; charset=") - 1 + r->headers_out.charset.len; + } + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + len += 1 + ngx_http_v2_integer_octets(NGX_OFF_T_LEN) + NGX_OFF_T_LEN; + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + len += 1 + ngx_http_v2_literal_size("Wed, 31 Dec 1986 18:00:00 GMT"); + } + + if (r->headers_out.location && r->headers_out.location->value.len) { + + if (r->headers_out.location->value.data[0] == '/' + && clcf->absolute_redirect) + { + if (clcf->server_name_in_redirect) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + host = cscf->server_name; + + } else if (r->headers_in.server.len) { + host = r->headers_in.server; + + } else { + host.len = NGX_SOCKADDR_STRLEN; + host.data = addr; + + if (ngx_connection_local_sockaddr(fc, &host, 0) != NGX_OK) { + return NGX_ERROR; + } + } + + port = ngx_inet_get_port(fc->local_sockaddr); + + location.len = sizeof("https://") - 1 + host.len + + r->headers_out.location->value.len; + + if (clcf->port_in_redirect) { + +#if (NGX_HTTP_SSL) + if (fc->ssl) + port = (port == 443) ? 0 : port; + else +#endif + port = (port == 80) ? 0 : port; + + } else { + port = 0; + } + + if (port) { + location.len += sizeof(":65535") - 1; + } + + location.data = ngx_pnalloc(r->pool, location.len); + if (location.data == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(location.data, "http", sizeof("http") - 1); + +#if (NGX_HTTP_SSL) + if (fc->ssl) { + *p++ = 's'; + } +#endif + + *p++ = ':'; *p++ = '/'; *p++ = '/'; + p = ngx_cpymem(p, host.data, host.len); + + if (port) { + p = ngx_sprintf(p, ":%ui", port); + } + + p = ngx_cpymem(p, r->headers_out.location->value.data, + r->headers_out.location->value.len); + + /* update r->headers_out.location->value for possible logging */ + + r->headers_out.location->value.len = p - location.data; + r->headers_out.location->value.data = location.data; + ngx_str_set(&r->headers_out.location->key, "Location"); + } + + r->headers_out.location->hash = 0; + + len += 1 + NGX_HTTP_V2_INT_OCTETS + r->headers_out.location->value.len; + } + + tmp_len = len; + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + if (clcf->gzip_vary) { + len += 1 + sizeof(accept_encoding); + + } else { + r->gzip_vary = 0; + } + } +#endif + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + if (header[i].key.len > NGX_HTTP_V2_MAX_FIELD) { + ngx_log_error(NGX_LOG_CRIT, fc->log, 0, + "too long response header name: \"%V\"", + &header[i].key); + return NGX_ERROR; + } + + if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) { + ngx_log_error(NGX_LOG_CRIT, fc->log, 0, + "too long response header value: \"%V: %V\"", + &header[i].key, &header[i].value); + return NGX_ERROR; + } + + len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len + + NGX_HTTP_V2_INT_OCTETS + header[i].value.len; + + if (header[i].key.len > tmp_len) { + tmp_len = header[i].key.len; + } + + if (header[i].value.len > tmp_len) { + tmp_len = header[i].value.len; + } + } + + tmp = ngx_palloc(r->pool, tmp_len); + pos = ngx_pnalloc(r->pool, len); + + if (pos == NULL || tmp == NULL) { + return NGX_ERROR; + } + + start = pos; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 output header: \":status: %03ui\"", + r->headers_out.status); + + if (status) { + *pos++ = status; + + } else { + *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX); + *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3; + pos = ngx_sprintf(pos, "%03ui", r->headers_out.status); + } + + if (r->headers_out.server == NULL) { + + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 output header: \"server: %s\"", + NGINX_VER); + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 output header: \"server: %s\"", + NGINX_VER_BUILD); + + } else { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 output header: \"server: nginx\""); + } + + *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX); + + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + if (nginx_ver[0] == '\0') { + p = ngx_http_v2_write_value(nginx_ver, (u_char *) NGINX_VER, + sizeof(NGINX_VER) - 1, tmp); + nginx_ver_len = p - nginx_ver; + } + + pos = ngx_cpymem(pos, nginx_ver, nginx_ver_len); + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + if (nginx_ver_build[0] == '\0') { + p = ngx_http_v2_write_value(nginx_ver_build, + (u_char *) NGINX_VER_BUILD, + sizeof(NGINX_VER_BUILD) - 1, tmp); + nginx_ver_build_len = p - nginx_ver_build; + } + + pos = ngx_cpymem(pos, nginx_ver_build, nginx_ver_build_len); + + } else { + pos = ngx_cpymem(pos, nginx, sizeof(nginx)); + } + } + + if (r->headers_out.date == NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 output header: \"date: %V\"", + &ngx_cached_http_time); + + *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX); + pos = ngx_http_v2_write_value(pos, ngx_cached_http_time.data, + ngx_cached_http_time.len, tmp); + } + + if (r->headers_out.content_type.len) { + *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX); + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + len = r->headers_out.content_type.len + sizeof("; charset=") - 1 + + r->headers_out.charset.len; + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(p, r->headers_out.content_type.data, + r->headers_out.content_type.len); + + p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1); + + p = ngx_cpymem(p, r->headers_out.charset.data, + r->headers_out.charset.len); + + /* updated r->headers_out.content_type is also needed for logging */ + + r->headers_out.content_type.len = len; + r->headers_out.content_type.data = p - len; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 output header: \"content-type: %V\"", + &r->headers_out.content_type); + + pos = ngx_http_v2_write_value(pos, r->headers_out.content_type.data, + r->headers_out.content_type.len, tmp); + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 output header: \"content-length: %O\"", + r->headers_out.content_length_n); + + *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX); + + p = pos; + pos = ngx_sprintf(pos + 1, "%O", r->headers_out.content_length_n); + *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1); + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX); + + ngx_http_time(pos, r->headers_out.last_modified_time); + len = sizeof("Wed, 31 Dec 1986 18:00:00 GMT") - 1; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 output header: \"last-modified: %*s\"", + len, pos); + + /* + * Date will always be encoded using huffman in the temporary buffer, + * so it's safe here to use src and dst pointing to the same address. + */ + pos = ngx_http_v2_write_value(pos, pos, len, tmp); + } + + if (r->headers_out.location && r->headers_out.location->value.len) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 output header: \"location: %V\"", + &r->headers_out.location->value); + + *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX); + pos = ngx_http_v2_write_value(pos, r->headers_out.location->value.data, + r->headers_out.location->value.len, tmp); + } + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 output header: \"vary: Accept-Encoding\""); + + *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX); + pos = ngx_cpymem(pos, accept_encoding, sizeof(accept_encoding)); + } +#endif + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + +#if (NGX_DEBUG) + if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) { + ngx_strlow(tmp, header[i].key.data, header[i].key.len); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 output header: \"%*s: %V\"", + header[i].key.len, tmp, &header[i].value); + } +#endif + + *pos++ = 0; + + pos = ngx_http_v2_write_name(pos, header[i].key.data, + header[i].key.len, tmp); + + pos = ngx_http_v2_write_value(pos, header[i].value.data, + header[i].value.len, tmp); + } + + frame = ngx_http_v2_create_headers_frame(r, start, pos); + if (frame == NULL) { + return NGX_ERROR; + } + + ngx_http_v2_queue_blocked_frame(r->stream->connection, frame); + + cln = ngx_http_cleanup_add(r, 0); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_http_v2_filter_cleanup; + cln->data = r->stream; + + r->stream->queued = 1; + + fc->send_chain = ngx_http_v2_send_chain; + fc->need_last_buf = 1; + + return ngx_http_v2_filter_send(fc, r->stream); +} + + +static u_char * +ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp, + ngx_uint_t lower) +{ + size_t hlen; + + hlen = ngx_http_v2_huff_encode(src, len, tmp, lower); + + if (hlen > 0) { + *dst = NGX_HTTP_V2_ENCODE_HUFF; + dst = ngx_http_v2_write_int(dst, ngx_http_v2_prefix(7), hlen); + return ngx_cpymem(dst, tmp, hlen); + } + + *dst = NGX_HTTP_V2_ENCODE_RAW; + dst = ngx_http_v2_write_int(dst, ngx_http_v2_prefix(7), len); + + if (lower) { + ngx_strlow(dst, src, len); + return dst + len; + } + + return ngx_cpymem(dst, src, len); +} + + +static u_char * +ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value) +{ + if (value < prefix) { + *pos++ |= value; + return pos; + } + + *pos++ |= prefix; + value -= prefix; + + while (value >= 128) { + *pos++ = value % 128 + 128; + value /= 128; + } + + *pos++ = (u_char) value; + + return pos; +} + + +static ngx_http_v2_out_frame_t * +ngx_http_v2_create_headers_frame(ngx_http_request_t *r, u_char *pos, + u_char *end) +{ + u_char type, flags; + size_t rest, frame_size; + ngx_buf_t *b; + ngx_chain_t *cl, **ll; + ngx_http_v2_stream_t *stream; + ngx_http_v2_out_frame_t *frame; + + stream = r->stream; + rest = end - pos; + + frame = ngx_palloc(r->pool, sizeof(ngx_http_v2_out_frame_t)); + if (frame == NULL) { + return NULL; + } + + frame->handler = ngx_http_v2_headers_frame_handler; + frame->stream = stream; + frame->length = rest; + frame->blocked = 1; + frame->fin = r->header_only; + + ll = &frame->first; + + type = NGX_HTTP_V2_HEADERS_FRAME; + flags = r->header_only ? NGX_HTTP_V2_END_STREAM_FLAG : NGX_HTTP_V2_NO_FLAG; + frame_size = stream->connection->frame_size; + + for ( ;; ) { + if (rest <= frame_size) { + frame_size = rest; + flags |= NGX_HTTP_V2_END_HEADERS_FLAG; + } + + b = ngx_create_temp_buf(r->pool, NGX_HTTP_V2_FRAME_HEADER_SIZE); + if (b == NULL) { + return NULL; + } + + b->last = ngx_http_v2_write_len_and_type(b->last, frame_size, type); + *b->last++ = flags; + b->last = ngx_http_v2_write_sid(b->last, stream->node->id); + + b->tag = (ngx_buf_tag_t) &ngx_http_v2_module; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = b; + + *ll = cl; + ll = &cl->next; + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NULL; + } + + b->pos = pos; + + pos += frame_size; + + b->last = pos; + b->start = b->pos; + b->end = b->last; + b->temporary = 1; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = b; + + *ll = cl; + ll = &cl->next; + + rest -= frame_size; + + if (rest) { + frame->length += NGX_HTTP_V2_FRAME_HEADER_SIZE; + + type = NGX_HTTP_V2_CONTINUATION_FRAME; + flags = NGX_HTTP_V2_NO_FLAG; + continue; + } + + b->last_buf = r->header_only; + cl->next = NULL; + frame->last = cl; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http2:%ui create HEADERS frame %p: len:%uz", + stream->node->id, frame, frame->length); + + return frame; + } +} + + +static ngx_chain_t * +ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit) +{ + off_t size, offset; + size_t rest, frame_size; + ngx_chain_t *cl, *out, **ln; + ngx_http_request_t *r; + ngx_http_v2_stream_t *stream; + ngx_http_v2_loc_conf_t *h2lcf; + ngx_http_v2_out_frame_t *frame; + ngx_http_v2_connection_t *h2c; + + r = fc->data; + stream = r->stream; + +#if (NGX_SUPPRESS_WARN) + size = 0; +#endif + + while (in) { + size = ngx_buf_size(in->buf); + + if (size || in->buf->last_buf) { + break; + } + + in = in->next; + } + + if (in == NULL) { + + if (stream->queued) { + fc->write->active = 1; + fc->write->ready = 0; + + } else { + fc->buffered &= ~NGX_HTTP_V2_BUFFERED; + } + + return NULL; + } + + h2c = stream->connection; + + if (size && ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) { + fc->write->active = 1; + fc->write->ready = 0; + return in; + } + + if (in->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) { + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf = in->buf; + in->buf = cl->buf->shadow; + + offset = ngx_buf_in_memory(in->buf) + ? (cl->buf->pos - in->buf->pos) + : (cl->buf->file_pos - in->buf->file_pos); + + cl->next = stream->free_bufs; + stream->free_bufs = cl; + + } else { + offset = 0; + } + + if (limit == 0 || limit > (off_t) h2c->send_window) { + limit = h2c->send_window; + } + + if (limit > stream->send_window) { + limit = (stream->send_window > 0) ? stream->send_window : 0; + } + + h2lcf = ngx_http_get_module_loc_conf(r, ngx_http_v2_module); + + frame_size = (h2lcf->chunk_size < h2c->frame_size) + ? h2lcf->chunk_size : h2c->frame_size; + +#if (NGX_SUPPRESS_WARN) + cl = NULL; +#endif + + for ( ;; ) { + if ((off_t) frame_size > limit) { + frame_size = (size_t) limit; + } + + ln = &out; + rest = frame_size; + + while ((off_t) rest >= size) { + + if (offset) { + cl = ngx_http_v2_filter_get_shadow(stream, in->buf, + offset, size); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + offset = 0; + + } else { + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf = in->buf; + } + + *ln = cl; + ln = &cl->next; + + rest -= (size_t) size; + in = in->next; + + if (in == NULL) { + frame_size -= rest; + rest = 0; + break; + } + + size = ngx_buf_size(in->buf); + } + + if (rest) { + cl = ngx_http_v2_filter_get_shadow(stream, in->buf, offset, rest); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf->flush = 0; + cl->buf->last_buf = 0; + + *ln = cl; + + offset += rest; + size -= rest; + } + + frame = ngx_http_v2_filter_get_data_frame(stream, frame_size, out, cl); + if (frame == NULL) { + return NGX_CHAIN_ERROR; + } + + ngx_http_v2_queue_frame(h2c, frame); + + h2c->send_window -= frame_size; + + stream->send_window -= frame_size; + stream->queued++; + + if (in == NULL) { + break; + } + + limit -= frame_size; + + if (limit == 0) { + break; + } + } + + if (offset) { + cl = ngx_http_v2_filter_get_shadow(stream, in->buf, offset, size); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + in->buf = cl->buf; + ngx_free_chain(r->pool, cl); + } + + if (ngx_http_v2_filter_send(fc, stream) == NGX_ERROR) { + return NGX_CHAIN_ERROR; + } + + if (in && ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) { + fc->write->active = 1; + fc->write->ready = 0; + } + + return in; +} + + +static ngx_chain_t * +ngx_http_v2_filter_get_shadow(ngx_http_v2_stream_t *stream, ngx_buf_t *buf, + off_t offset, off_t size) +{ + ngx_buf_t *chunk; + ngx_chain_t *cl; + + cl = ngx_chain_get_free_buf(stream->request->pool, &stream->free_bufs); + if (cl == NULL) { + return NULL; + } + + chunk = cl->buf; + + ngx_memcpy(chunk, buf, sizeof(ngx_buf_t)); + + chunk->tag = (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow; + chunk->shadow = buf; + + if (ngx_buf_in_memory(chunk)) { + chunk->pos += offset; + chunk->last = chunk->pos + size; + } + + if (chunk->in_file) { + chunk->file_pos += offset; + chunk->file_last = chunk->file_pos + size; + } + + return cl; +} + + +static ngx_http_v2_out_frame_t * +ngx_http_v2_filter_get_data_frame(ngx_http_v2_stream_t *stream, + size_t len, ngx_chain_t *first, ngx_chain_t *last) +{ + u_char flags; + ngx_buf_t *buf; + ngx_chain_t *cl; + ngx_http_v2_out_frame_t *frame; + + frame = stream->free_frames; + + if (frame) { + stream->free_frames = frame->next; + + } else { + frame = ngx_palloc(stream->request->pool, + sizeof(ngx_http_v2_out_frame_t)); + if (frame == NULL) { + return NULL; + } + } + + flags = last->buf->last_buf ? NGX_HTTP_V2_END_STREAM_FLAG : 0; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0, + "http2:%ui create DATA frame %p: len:%uz flags:%ui", + stream->node->id, frame, len, (ngx_uint_t) flags); + + cl = ngx_chain_get_free_buf(stream->request->pool, + &stream->free_frame_headers); + if (cl == NULL) { + return NULL; + } + + buf = cl->buf; + + if (buf->start == NULL) { + buf->start = ngx_palloc(stream->request->pool, + NGX_HTTP_V2_FRAME_HEADER_SIZE); + if (buf->start == NULL) { + return NULL; + } + + buf->end = buf->start + NGX_HTTP_V2_FRAME_HEADER_SIZE; + buf->last = buf->end; + + buf->tag = (ngx_buf_tag_t) &ngx_http_v2_module; + buf->memory = 1; + } + + buf->pos = buf->start; + buf->last = buf->pos; + + buf->last = ngx_http_v2_write_len_and_type(buf->last, len, + NGX_HTTP_V2_DATA_FRAME); + *buf->last++ = flags; + + buf->last = ngx_http_v2_write_sid(buf->last, stream->node->id); + + cl->next = first; + first = cl; + + last->buf->flush = 1; + + frame->first = first; + frame->last = last; + frame->handler = ngx_http_v2_data_frame_handler; + frame->stream = stream; + frame->length = len; + frame->blocked = 0; + frame->fin = last->buf->last_buf; + + return frame; +} + + +static ngx_inline ngx_int_t +ngx_http_v2_filter_send(ngx_connection_t *fc, ngx_http_v2_stream_t *stream) +{ + stream->blocked = 1; + + if (ngx_http_v2_send_output_queue(stream->connection) == NGX_ERROR) { + fc->error = 1; + return NGX_ERROR; + } + + stream->blocked = 0; + + if (stream->queued) { + fc->buffered |= NGX_HTTP_V2_BUFFERED; + fc->write->active = 1; + fc->write->ready = 0; + return NGX_AGAIN; + } + + fc->buffered &= ~NGX_HTTP_V2_BUFFERED; + + return NGX_OK; +} + + +static ngx_inline ngx_int_t +ngx_http_v2_flow_control(ngx_http_v2_connection_t *h2c, + ngx_http_v2_stream_t *stream) +{ + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2:%ui available windows: conn:%uz stream:%z", + stream->node->id, h2c->send_window, stream->send_window); + + if (stream->send_window <= 0) { + stream->exhausted = 1; + return NGX_DECLINED; + } + + if (h2c->send_window == 0) { + ngx_http_v2_waiting_queue(h2c, stream); + return NGX_DECLINED; + } + + return NGX_OK; +} + + +static void +ngx_http_v2_waiting_queue(ngx_http_v2_connection_t *h2c, + ngx_http_v2_stream_t *stream) +{ + ngx_queue_t *q; + ngx_http_v2_stream_t *s; + + if (stream->waiting) { + return; + } + + stream->waiting = 1; + + for (q = ngx_queue_last(&h2c->waiting); + q != ngx_queue_sentinel(&h2c->waiting); + q = ngx_queue_prev(q)) + { + s = ngx_queue_data(q, ngx_http_v2_stream_t, queue); + + if (s->node->rank < stream->node->rank + || (s->node->rank == stream->node->rank + && s->node->rel_weight >= stream->node->rel_weight)) + { + break; + } + } + + ngx_queue_insert_after(q, &stream->queue); +} + + + +static ngx_int_t +ngx_http_v2_headers_frame_handler(ngx_http_v2_connection_t *h2c, + ngx_http_v2_out_frame_t *frame) +{ + ngx_chain_t *cl, *ln; + ngx_http_v2_stream_t *stream; + + stream = frame->stream; + cl = frame->first; + + for ( ;; ) { + if (cl->buf->pos != cl->buf->last) { + frame->first = cl; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2:%ui HEADERS frame %p was sent partially", + stream->node->id, frame); + + return NGX_AGAIN; + } + + ln = cl->next; + + if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_module) { + cl->next = stream->free_frame_headers; + stream->free_frame_headers = cl; + + } else { + cl->next = stream->free_bufs; + stream->free_bufs = cl; + } + + if (cl == frame->last) { + break; + } + + cl = ln; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2:%ui HEADERS frame %p was sent", + stream->node->id, frame); + + stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE + + frame->length; + + ngx_http_v2_handle_frame(stream, frame); + + ngx_http_v2_handle_stream(h2c, stream); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_data_frame_handler(ngx_http_v2_connection_t *h2c, + ngx_http_v2_out_frame_t *frame) +{ + ngx_buf_t *buf; + ngx_chain_t *cl, *ln; + ngx_http_v2_stream_t *stream; + + stream = frame->stream; + cl = frame->first; + + if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_module) { + + if (cl->buf->pos != cl->buf->last) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2:%ui DATA frame %p was sent partially", + stream->node->id, frame); + + return NGX_AGAIN; + } + + ln = cl->next; + + cl->next = stream->free_frame_headers; + stream->free_frame_headers = cl; + + if (cl == frame->last) { + goto done; + } + + cl = ln; + } + + for ( ;; ) { + if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) { + buf = cl->buf->shadow; + + if (ngx_buf_in_memory(buf)) { + buf->pos = cl->buf->pos; + } + + if (buf->in_file) { + buf->file_pos = cl->buf->file_pos; + } + } + + if (ngx_buf_size(cl->buf) != 0) { + + if (cl != frame->first) { + frame->first = cl; + ngx_http_v2_handle_stream(h2c, stream); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2:%ui DATA frame %p was sent partially", + stream->node->id, frame); + + return NGX_AGAIN; + } + + ln = cl->next; + + if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) { + cl->next = stream->free_bufs; + stream->free_bufs = cl; + + } else { + ngx_free_chain(stream->request->pool, cl); + } + + if (cl == frame->last) { + goto done; + } + + cl = ln; + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2:%ui DATA frame %p was sent", + stream->node->id, frame); + + stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE; + + ngx_http_v2_handle_frame(stream, frame); + + ngx_http_v2_handle_stream(h2c, stream); + + return NGX_OK; +} + + +static ngx_inline void +ngx_http_v2_handle_frame(ngx_http_v2_stream_t *stream, + ngx_http_v2_out_frame_t *frame) +{ + ngx_http_request_t *r; + + r = stream->request; + + r->connection->sent += NGX_HTTP_V2_FRAME_HEADER_SIZE + frame->length; + + if (frame->fin) { + stream->out_closed = 1; + } + + frame->next = stream->free_frames; + stream->free_frames = frame; + + stream->queued--; +} + + +static ngx_inline void +ngx_http_v2_handle_stream(ngx_http_v2_connection_t *h2c, + ngx_http_v2_stream_t *stream) +{ + ngx_event_t *wev; + ngx_connection_t *fc; + + if (stream->waiting || stream->blocked) { + return; + } + + fc = stream->request->connection; + + if (!fc->error && stream->exhausted) { + return; + } + + wev = fc->write; + + wev->active = 0; + wev->ready = 1; + + if (!fc->error && wev->delayed) { + return; + } + + ngx_post_event(wev, &ngx_posted_events); +} + + +static void +ngx_http_v2_filter_cleanup(void *data) +{ + ngx_http_v2_stream_t *stream = data; + + size_t window; + ngx_event_t *wev; + ngx_queue_t *q; + ngx_http_v2_out_frame_t *frame, **fn; + ngx_http_v2_connection_t *h2c; + + if (stream->waiting) { + stream->waiting = 0; + ngx_queue_remove(&stream->queue); + } + + if (stream->queued == 0) { + return; + } + + window = 0; + h2c = stream->connection; + fn = &h2c->last_out; + + for ( ;; ) { + frame = *fn; + + if (frame == NULL) { + break; + } + + if (frame->stream == stream && !frame->blocked) { + *fn = frame->next; + + window += frame->length; + + if (--stream->queued == 0) { + break; + } + + continue; + } + + fn = &frame->next; + } + + if (h2c->send_window == 0 && window) { + + while (!ngx_queue_empty(&h2c->waiting)) { + q = ngx_queue_head(&h2c->waiting); + + ngx_queue_remove(q); + + stream = ngx_queue_data(q, ngx_http_v2_stream_t, queue); + + stream->waiting = 0; + + wev = stream->request->connection->write; + + wev->active = 0; + wev->ready = 1; + + if (!wev->delayed) { + ngx_post_event(wev, &ngx_posted_events); + } + } + } + + h2c->send_window += window; +} + + +static ngx_int_t +ngx_http_v2_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_v2_header_filter; + + return NGX_OK; +} diff --git a/app/nginx/src/http/v2/ngx_http_v2_huff_decode.c b/app/nginx/src/http/v2/ngx_http_v2_huff_decode.c new file mode 100644 index 0000000..49ca576 --- /dev/null +++ b/app/nginx/src/http/v2/ngx_http_v2_huff_decode.c @@ -0,0 +1,2714 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include + + +typedef struct { + u_char next; + u_char emit; + u_char sym; + u_char ending; +} ngx_http_v2_huff_decode_code_t; + + +static ngx_inline ngx_int_t ngx_http_v2_huff_decode_bits(u_char *state, + u_char *ending, ngx_uint_t bits, u_char **dst); + + +static ngx_http_v2_huff_decode_code_t ngx_http_v2_huff_decode_codes[256][16] = +{ + /* 0 */ + { + {0x04, 0x00, 0x00, 0x00}, {0x05, 0x00, 0x00, 0x00}, + {0x07, 0x00, 0x00, 0x00}, {0x08, 0x00, 0x00, 0x00}, + {0x0b, 0x00, 0x00, 0x00}, {0x0c, 0x00, 0x00, 0x00}, + {0x10, 0x00, 0x00, 0x00}, {0x13, 0x00, 0x00, 0x00}, + {0x19, 0x00, 0x00, 0x00}, {0x1c, 0x00, 0x00, 0x00}, + {0x20, 0x00, 0x00, 0x00}, {0x23, 0x00, 0x00, 0x00}, + {0x2a, 0x00, 0x00, 0x00}, {0x31, 0x00, 0x00, 0x00}, + {0x39, 0x00, 0x00, 0x00}, {0x40, 0x00, 0x00, 0x01} + }, + { + {0x00, 0x01, 0x30, 0x01}, {0x00, 0x01, 0x31, 0x01}, + {0x00, 0x01, 0x32, 0x01}, {0x00, 0x01, 0x61, 0x01}, + {0x00, 0x01, 0x63, 0x01}, {0x00, 0x01, 0x65, 0x01}, + {0x00, 0x01, 0x69, 0x01}, {0x00, 0x01, 0x6f, 0x01}, + {0x00, 0x01, 0x73, 0x01}, {0x00, 0x01, 0x74, 0x01}, + {0x0d, 0x00, 0x00, 0x00}, {0x0e, 0x00, 0x00, 0x00}, + {0x11, 0x00, 0x00, 0x00}, {0x12, 0x00, 0x00, 0x00}, + {0x14, 0x00, 0x00, 0x00}, {0x15, 0x00, 0x00, 0x00} + }, + { + {0x01, 0x01, 0x30, 0x00}, {0x16, 0x01, 0x30, 0x01}, + {0x01, 0x01, 0x31, 0x00}, {0x16, 0x01, 0x31, 0x01}, + {0x01, 0x01, 0x32, 0x00}, {0x16, 0x01, 0x32, 0x01}, + {0x01, 0x01, 0x61, 0x00}, {0x16, 0x01, 0x61, 0x01}, + {0x01, 0x01, 0x63, 0x00}, {0x16, 0x01, 0x63, 0x01}, + {0x01, 0x01, 0x65, 0x00}, {0x16, 0x01, 0x65, 0x01}, + {0x01, 0x01, 0x69, 0x00}, {0x16, 0x01, 0x69, 0x01}, + {0x01, 0x01, 0x6f, 0x00}, {0x16, 0x01, 0x6f, 0x01} + }, + { + {0x02, 0x01, 0x30, 0x00}, {0x09, 0x01, 0x30, 0x00}, + {0x17, 0x01, 0x30, 0x00}, {0x28, 0x01, 0x30, 0x01}, + {0x02, 0x01, 0x31, 0x00}, {0x09, 0x01, 0x31, 0x00}, + {0x17, 0x01, 0x31, 0x00}, {0x28, 0x01, 0x31, 0x01}, + {0x02, 0x01, 0x32, 0x00}, {0x09, 0x01, 0x32, 0x00}, + {0x17, 0x01, 0x32, 0x00}, {0x28, 0x01, 0x32, 0x01}, + {0x02, 0x01, 0x61, 0x00}, {0x09, 0x01, 0x61, 0x00}, + {0x17, 0x01, 0x61, 0x00}, {0x28, 0x01, 0x61, 0x01} + }, + { + {0x03, 0x01, 0x30, 0x00}, {0x06, 0x01, 0x30, 0x00}, + {0x0a, 0x01, 0x30, 0x00}, {0x0f, 0x01, 0x30, 0x00}, + {0x18, 0x01, 0x30, 0x00}, {0x1f, 0x01, 0x30, 0x00}, + {0x29, 0x01, 0x30, 0x00}, {0x38, 0x01, 0x30, 0x01}, + {0x03, 0x01, 0x31, 0x00}, {0x06, 0x01, 0x31, 0x00}, + {0x0a, 0x01, 0x31, 0x00}, {0x0f, 0x01, 0x31, 0x00}, + {0x18, 0x01, 0x31, 0x00}, {0x1f, 0x01, 0x31, 0x00}, + {0x29, 0x01, 0x31, 0x00}, {0x38, 0x01, 0x31, 0x01} + }, + /* 5 */ + { + {0x03, 0x01, 0x32, 0x00}, {0x06, 0x01, 0x32, 0x00}, + {0x0a, 0x01, 0x32, 0x00}, {0x0f, 0x01, 0x32, 0x00}, + {0x18, 0x01, 0x32, 0x00}, {0x1f, 0x01, 0x32, 0x00}, + {0x29, 0x01, 0x32, 0x00}, {0x38, 0x01, 0x32, 0x01}, + {0x03, 0x01, 0x61, 0x00}, {0x06, 0x01, 0x61, 0x00}, + {0x0a, 0x01, 0x61, 0x00}, {0x0f, 0x01, 0x61, 0x00}, + {0x18, 0x01, 0x61, 0x00}, {0x1f, 0x01, 0x61, 0x00}, + {0x29, 0x01, 0x61, 0x00}, {0x38, 0x01, 0x61, 0x01} + }, + { + {0x02, 0x01, 0x63, 0x00}, {0x09, 0x01, 0x63, 0x00}, + {0x17, 0x01, 0x63, 0x00}, {0x28, 0x01, 0x63, 0x01}, + {0x02, 0x01, 0x65, 0x00}, {0x09, 0x01, 0x65, 0x00}, + {0x17, 0x01, 0x65, 0x00}, {0x28, 0x01, 0x65, 0x01}, + {0x02, 0x01, 0x69, 0x00}, {0x09, 0x01, 0x69, 0x00}, + {0x17, 0x01, 0x69, 0x00}, {0x28, 0x01, 0x69, 0x01}, + {0x02, 0x01, 0x6f, 0x00}, {0x09, 0x01, 0x6f, 0x00}, + {0x17, 0x01, 0x6f, 0x00}, {0x28, 0x01, 0x6f, 0x01} + }, + { + {0x03, 0x01, 0x63, 0x00}, {0x06, 0x01, 0x63, 0x00}, + {0x0a, 0x01, 0x63, 0x00}, {0x0f, 0x01, 0x63, 0x00}, + {0x18, 0x01, 0x63, 0x00}, {0x1f, 0x01, 0x63, 0x00}, + {0x29, 0x01, 0x63, 0x00}, {0x38, 0x01, 0x63, 0x01}, + {0x03, 0x01, 0x65, 0x00}, {0x06, 0x01, 0x65, 0x00}, + {0x0a, 0x01, 0x65, 0x00}, {0x0f, 0x01, 0x65, 0x00}, + {0x18, 0x01, 0x65, 0x00}, {0x1f, 0x01, 0x65, 0x00}, + {0x29, 0x01, 0x65, 0x00}, {0x38, 0x01, 0x65, 0x01} + }, + { + {0x03, 0x01, 0x69, 0x00}, {0x06, 0x01, 0x69, 0x00}, + {0x0a, 0x01, 0x69, 0x00}, {0x0f, 0x01, 0x69, 0x00}, + {0x18, 0x01, 0x69, 0x00}, {0x1f, 0x01, 0x69, 0x00}, + {0x29, 0x01, 0x69, 0x00}, {0x38, 0x01, 0x69, 0x01}, + {0x03, 0x01, 0x6f, 0x00}, {0x06, 0x01, 0x6f, 0x00}, + {0x0a, 0x01, 0x6f, 0x00}, {0x0f, 0x01, 0x6f, 0x00}, + {0x18, 0x01, 0x6f, 0x00}, {0x1f, 0x01, 0x6f, 0x00}, + {0x29, 0x01, 0x6f, 0x00}, {0x38, 0x01, 0x6f, 0x01} + }, + { + {0x01, 0x01, 0x73, 0x00}, {0x16, 0x01, 0x73, 0x01}, + {0x01, 0x01, 0x74, 0x00}, {0x16, 0x01, 0x74, 0x01}, + {0x00, 0x01, 0x20, 0x01}, {0x00, 0x01, 0x25, 0x01}, + {0x00, 0x01, 0x2d, 0x01}, {0x00, 0x01, 0x2e, 0x01}, + {0x00, 0x01, 0x2f, 0x01}, {0x00, 0x01, 0x33, 0x01}, + {0x00, 0x01, 0x34, 0x01}, {0x00, 0x01, 0x35, 0x01}, + {0x00, 0x01, 0x36, 0x01}, {0x00, 0x01, 0x37, 0x01}, + {0x00, 0x01, 0x38, 0x01}, {0x00, 0x01, 0x39, 0x01} + }, + /* 10 */ + { + {0x02, 0x01, 0x73, 0x00}, {0x09, 0x01, 0x73, 0x00}, + {0x17, 0x01, 0x73, 0x00}, {0x28, 0x01, 0x73, 0x01}, + {0x02, 0x01, 0x74, 0x00}, {0x09, 0x01, 0x74, 0x00}, + {0x17, 0x01, 0x74, 0x00}, {0x28, 0x01, 0x74, 0x01}, + {0x01, 0x01, 0x20, 0x00}, {0x16, 0x01, 0x20, 0x01}, + {0x01, 0x01, 0x25, 0x00}, {0x16, 0x01, 0x25, 0x01}, + {0x01, 0x01, 0x2d, 0x00}, {0x16, 0x01, 0x2d, 0x01}, + {0x01, 0x01, 0x2e, 0x00}, {0x16, 0x01, 0x2e, 0x01} + }, + { + {0x03, 0x01, 0x73, 0x00}, {0x06, 0x01, 0x73, 0x00}, + {0x0a, 0x01, 0x73, 0x00}, {0x0f, 0x01, 0x73, 0x00}, + {0x18, 0x01, 0x73, 0x00}, {0x1f, 0x01, 0x73, 0x00}, + {0x29, 0x01, 0x73, 0x00}, {0x38, 0x01, 0x73, 0x01}, + {0x03, 0x01, 0x74, 0x00}, {0x06, 0x01, 0x74, 0x00}, + {0x0a, 0x01, 0x74, 0x00}, {0x0f, 0x01, 0x74, 0x00}, + {0x18, 0x01, 0x74, 0x00}, {0x1f, 0x01, 0x74, 0x00}, + {0x29, 0x01, 0x74, 0x00}, {0x38, 0x01, 0x74, 0x01} + }, + { + {0x02, 0x01, 0x20, 0x00}, {0x09, 0x01, 0x20, 0x00}, + {0x17, 0x01, 0x20, 0x00}, {0x28, 0x01, 0x20, 0x01}, + {0x02, 0x01, 0x25, 0x00}, {0x09, 0x01, 0x25, 0x00}, + {0x17, 0x01, 0x25, 0x00}, {0x28, 0x01, 0x25, 0x01}, + {0x02, 0x01, 0x2d, 0x00}, {0x09, 0x01, 0x2d, 0x00}, + {0x17, 0x01, 0x2d, 0x00}, {0x28, 0x01, 0x2d, 0x01}, + {0x02, 0x01, 0x2e, 0x00}, {0x09, 0x01, 0x2e, 0x00}, + {0x17, 0x01, 0x2e, 0x00}, {0x28, 0x01, 0x2e, 0x01} + }, + { + {0x03, 0x01, 0x20, 0x00}, {0x06, 0x01, 0x20, 0x00}, + {0x0a, 0x01, 0x20, 0x00}, {0x0f, 0x01, 0x20, 0x00}, + {0x18, 0x01, 0x20, 0x00}, {0x1f, 0x01, 0x20, 0x00}, + {0x29, 0x01, 0x20, 0x00}, {0x38, 0x01, 0x20, 0x01}, + {0x03, 0x01, 0x25, 0x00}, {0x06, 0x01, 0x25, 0x00}, + {0x0a, 0x01, 0x25, 0x00}, {0x0f, 0x01, 0x25, 0x00}, + {0x18, 0x01, 0x25, 0x00}, {0x1f, 0x01, 0x25, 0x00}, + {0x29, 0x01, 0x25, 0x00}, {0x38, 0x01, 0x25, 0x01} + }, + { + {0x03, 0x01, 0x2d, 0x00}, {0x06, 0x01, 0x2d, 0x00}, + {0x0a, 0x01, 0x2d, 0x00}, {0x0f, 0x01, 0x2d, 0x00}, + {0x18, 0x01, 0x2d, 0x00}, {0x1f, 0x01, 0x2d, 0x00}, + {0x29, 0x01, 0x2d, 0x00}, {0x38, 0x01, 0x2d, 0x01}, + {0x03, 0x01, 0x2e, 0x00}, {0x06, 0x01, 0x2e, 0x00}, + {0x0a, 0x01, 0x2e, 0x00}, {0x0f, 0x01, 0x2e, 0x00}, + {0x18, 0x01, 0x2e, 0x00}, {0x1f, 0x01, 0x2e, 0x00}, + {0x29, 0x01, 0x2e, 0x00}, {0x38, 0x01, 0x2e, 0x01} + }, + /* 15 */ + { + {0x01, 0x01, 0x2f, 0x00}, {0x16, 0x01, 0x2f, 0x01}, + {0x01, 0x01, 0x33, 0x00}, {0x16, 0x01, 0x33, 0x01}, + {0x01, 0x01, 0x34, 0x00}, {0x16, 0x01, 0x34, 0x01}, + {0x01, 0x01, 0x35, 0x00}, {0x16, 0x01, 0x35, 0x01}, + {0x01, 0x01, 0x36, 0x00}, {0x16, 0x01, 0x36, 0x01}, + {0x01, 0x01, 0x37, 0x00}, {0x16, 0x01, 0x37, 0x01}, + {0x01, 0x01, 0x38, 0x00}, {0x16, 0x01, 0x38, 0x01}, + {0x01, 0x01, 0x39, 0x00}, {0x16, 0x01, 0x39, 0x01} + }, + { + {0x02, 0x01, 0x2f, 0x00}, {0x09, 0x01, 0x2f, 0x00}, + {0x17, 0x01, 0x2f, 0x00}, {0x28, 0x01, 0x2f, 0x01}, + {0x02, 0x01, 0x33, 0x00}, {0x09, 0x01, 0x33, 0x00}, + {0x17, 0x01, 0x33, 0x00}, {0x28, 0x01, 0x33, 0x01}, + {0x02, 0x01, 0x34, 0x00}, {0x09, 0x01, 0x34, 0x00}, + {0x17, 0x01, 0x34, 0x00}, {0x28, 0x01, 0x34, 0x01}, + {0x02, 0x01, 0x35, 0x00}, {0x09, 0x01, 0x35, 0x00}, + {0x17, 0x01, 0x35, 0x00}, {0x28, 0x01, 0x35, 0x01} + }, + { + {0x03, 0x01, 0x2f, 0x00}, {0x06, 0x01, 0x2f, 0x00}, + {0x0a, 0x01, 0x2f, 0x00}, {0x0f, 0x01, 0x2f, 0x00}, + {0x18, 0x01, 0x2f, 0x00}, {0x1f, 0x01, 0x2f, 0x00}, + {0x29, 0x01, 0x2f, 0x00}, {0x38, 0x01, 0x2f, 0x01}, + {0x03, 0x01, 0x33, 0x00}, {0x06, 0x01, 0x33, 0x00}, + {0x0a, 0x01, 0x33, 0x00}, {0x0f, 0x01, 0x33, 0x00}, + {0x18, 0x01, 0x33, 0x00}, {0x1f, 0x01, 0x33, 0x00}, + {0x29, 0x01, 0x33, 0x00}, {0x38, 0x01, 0x33, 0x01} + }, + { + {0x03, 0x01, 0x34, 0x00}, {0x06, 0x01, 0x34, 0x00}, + {0x0a, 0x01, 0x34, 0x00}, {0x0f, 0x01, 0x34, 0x00}, + {0x18, 0x01, 0x34, 0x00}, {0x1f, 0x01, 0x34, 0x00}, + {0x29, 0x01, 0x34, 0x00}, {0x38, 0x01, 0x34, 0x01}, + {0x03, 0x01, 0x35, 0x00}, {0x06, 0x01, 0x35, 0x00}, + {0x0a, 0x01, 0x35, 0x00}, {0x0f, 0x01, 0x35, 0x00}, + {0x18, 0x01, 0x35, 0x00}, {0x1f, 0x01, 0x35, 0x00}, + {0x29, 0x01, 0x35, 0x00}, {0x38, 0x01, 0x35, 0x01} + }, + { + {0x02, 0x01, 0x36, 0x00}, {0x09, 0x01, 0x36, 0x00}, + {0x17, 0x01, 0x36, 0x00}, {0x28, 0x01, 0x36, 0x01}, + {0x02, 0x01, 0x37, 0x00}, {0x09, 0x01, 0x37, 0x00}, + {0x17, 0x01, 0x37, 0x00}, {0x28, 0x01, 0x37, 0x01}, + {0x02, 0x01, 0x38, 0x00}, {0x09, 0x01, 0x38, 0x00}, + {0x17, 0x01, 0x38, 0x00}, {0x28, 0x01, 0x38, 0x01}, + {0x02, 0x01, 0x39, 0x00}, {0x09, 0x01, 0x39, 0x00}, + {0x17, 0x01, 0x39, 0x00}, {0x28, 0x01, 0x39, 0x01} + }, + /* 20 */ + { + {0x03, 0x01, 0x36, 0x00}, {0x06, 0x01, 0x36, 0x00}, + {0x0a, 0x01, 0x36, 0x00}, {0x0f, 0x01, 0x36, 0x00}, + {0x18, 0x01, 0x36, 0x00}, {0x1f, 0x01, 0x36, 0x00}, + {0x29, 0x01, 0x36, 0x00}, {0x38, 0x01, 0x36, 0x01}, + {0x03, 0x01, 0x37, 0x00}, {0x06, 0x01, 0x37, 0x00}, + {0x0a, 0x01, 0x37, 0x00}, {0x0f, 0x01, 0x37, 0x00}, + {0x18, 0x01, 0x37, 0x00}, {0x1f, 0x01, 0x37, 0x00}, + {0x29, 0x01, 0x37, 0x00}, {0x38, 0x01, 0x37, 0x01} + }, + { + {0x03, 0x01, 0x38, 0x00}, {0x06, 0x01, 0x38, 0x00}, + {0x0a, 0x01, 0x38, 0x00}, {0x0f, 0x01, 0x38, 0x00}, + {0x18, 0x01, 0x38, 0x00}, {0x1f, 0x01, 0x38, 0x00}, + {0x29, 0x01, 0x38, 0x00}, {0x38, 0x01, 0x38, 0x01}, + {0x03, 0x01, 0x39, 0x00}, {0x06, 0x01, 0x39, 0x00}, + {0x0a, 0x01, 0x39, 0x00}, {0x0f, 0x01, 0x39, 0x00}, + {0x18, 0x01, 0x39, 0x00}, {0x1f, 0x01, 0x39, 0x00}, + {0x29, 0x01, 0x39, 0x00}, {0x38, 0x01, 0x39, 0x01} + }, + { + {0x1a, 0x00, 0x00, 0x00}, {0x1b, 0x00, 0x00, 0x00}, + {0x1d, 0x00, 0x00, 0x00}, {0x1e, 0x00, 0x00, 0x00}, + {0x21, 0x00, 0x00, 0x00}, {0x22, 0x00, 0x00, 0x00}, + {0x24, 0x00, 0x00, 0x00}, {0x25, 0x00, 0x00, 0x00}, + {0x2b, 0x00, 0x00, 0x00}, {0x2e, 0x00, 0x00, 0x00}, + {0x32, 0x00, 0x00, 0x00}, {0x35, 0x00, 0x00, 0x00}, + {0x3a, 0x00, 0x00, 0x00}, {0x3d, 0x00, 0x00, 0x00}, + {0x41, 0x00, 0x00, 0x00}, {0x44, 0x00, 0x00, 0x01} + }, + { + {0x00, 0x01, 0x3d, 0x01}, {0x00, 0x01, 0x41, 0x01}, + {0x00, 0x01, 0x5f, 0x01}, {0x00, 0x01, 0x62, 0x01}, + {0x00, 0x01, 0x64, 0x01}, {0x00, 0x01, 0x66, 0x01}, + {0x00, 0x01, 0x67, 0x01}, {0x00, 0x01, 0x68, 0x01}, + {0x00, 0x01, 0x6c, 0x01}, {0x00, 0x01, 0x6d, 0x01}, + {0x00, 0x01, 0x6e, 0x01}, {0x00, 0x01, 0x70, 0x01}, + {0x00, 0x01, 0x72, 0x01}, {0x00, 0x01, 0x75, 0x01}, + {0x26, 0x00, 0x00, 0x00}, {0x27, 0x00, 0x00, 0x00} + }, + { + {0x01, 0x01, 0x3d, 0x00}, {0x16, 0x01, 0x3d, 0x01}, + {0x01, 0x01, 0x41, 0x00}, {0x16, 0x01, 0x41, 0x01}, + {0x01, 0x01, 0x5f, 0x00}, {0x16, 0x01, 0x5f, 0x01}, + {0x01, 0x01, 0x62, 0x00}, {0x16, 0x01, 0x62, 0x01}, + {0x01, 0x01, 0x64, 0x00}, {0x16, 0x01, 0x64, 0x01}, + {0x01, 0x01, 0x66, 0x00}, {0x16, 0x01, 0x66, 0x01}, + {0x01, 0x01, 0x67, 0x00}, {0x16, 0x01, 0x67, 0x01}, + {0x01, 0x01, 0x68, 0x00}, {0x16, 0x01, 0x68, 0x01} + }, + /* 25 */ + { + {0x02, 0x01, 0x3d, 0x00}, {0x09, 0x01, 0x3d, 0x00}, + {0x17, 0x01, 0x3d, 0x00}, {0x28, 0x01, 0x3d, 0x01}, + {0x02, 0x01, 0x41, 0x00}, {0x09, 0x01, 0x41, 0x00}, + {0x17, 0x01, 0x41, 0x00}, {0x28, 0x01, 0x41, 0x01}, + {0x02, 0x01, 0x5f, 0x00}, {0x09, 0x01, 0x5f, 0x00}, + {0x17, 0x01, 0x5f, 0x00}, {0x28, 0x01, 0x5f, 0x01}, + {0x02, 0x01, 0x62, 0x00}, {0x09, 0x01, 0x62, 0x00}, + {0x17, 0x01, 0x62, 0x00}, {0x28, 0x01, 0x62, 0x01} + }, + { + {0x03, 0x01, 0x3d, 0x00}, {0x06, 0x01, 0x3d, 0x00}, + {0x0a, 0x01, 0x3d, 0x00}, {0x0f, 0x01, 0x3d, 0x00}, + {0x18, 0x01, 0x3d, 0x00}, {0x1f, 0x01, 0x3d, 0x00}, + {0x29, 0x01, 0x3d, 0x00}, {0x38, 0x01, 0x3d, 0x01}, + {0x03, 0x01, 0x41, 0x00}, {0x06, 0x01, 0x41, 0x00}, + {0x0a, 0x01, 0x41, 0x00}, {0x0f, 0x01, 0x41, 0x00}, + {0x18, 0x01, 0x41, 0x00}, {0x1f, 0x01, 0x41, 0x00}, + {0x29, 0x01, 0x41, 0x00}, {0x38, 0x01, 0x41, 0x01} + }, + { + {0x03, 0x01, 0x5f, 0x00}, {0x06, 0x01, 0x5f, 0x00}, + {0x0a, 0x01, 0x5f, 0x00}, {0x0f, 0x01, 0x5f, 0x00}, + {0x18, 0x01, 0x5f, 0x00}, {0x1f, 0x01, 0x5f, 0x00}, + {0x29, 0x01, 0x5f, 0x00}, {0x38, 0x01, 0x5f, 0x01}, + {0x03, 0x01, 0x62, 0x00}, {0x06, 0x01, 0x62, 0x00}, + {0x0a, 0x01, 0x62, 0x00}, {0x0f, 0x01, 0x62, 0x00}, + {0x18, 0x01, 0x62, 0x00}, {0x1f, 0x01, 0x62, 0x00}, + {0x29, 0x01, 0x62, 0x00}, {0x38, 0x01, 0x62, 0x01} + }, + { + {0x02, 0x01, 0x64, 0x00}, {0x09, 0x01, 0x64, 0x00}, + {0x17, 0x01, 0x64, 0x00}, {0x28, 0x01, 0x64, 0x01}, + {0x02, 0x01, 0x66, 0x00}, {0x09, 0x01, 0x66, 0x00}, + {0x17, 0x01, 0x66, 0x00}, {0x28, 0x01, 0x66, 0x01}, + {0x02, 0x01, 0x67, 0x00}, {0x09, 0x01, 0x67, 0x00}, + {0x17, 0x01, 0x67, 0x00}, {0x28, 0x01, 0x67, 0x01}, + {0x02, 0x01, 0x68, 0x00}, {0x09, 0x01, 0x68, 0x00}, + {0x17, 0x01, 0x68, 0x00}, {0x28, 0x01, 0x68, 0x01} + }, + { + {0x03, 0x01, 0x64, 0x00}, {0x06, 0x01, 0x64, 0x00}, + {0x0a, 0x01, 0x64, 0x00}, {0x0f, 0x01, 0x64, 0x00}, + {0x18, 0x01, 0x64, 0x00}, {0x1f, 0x01, 0x64, 0x00}, + {0x29, 0x01, 0x64, 0x00}, {0x38, 0x01, 0x64, 0x01}, + {0x03, 0x01, 0x66, 0x00}, {0x06, 0x01, 0x66, 0x00}, + {0x0a, 0x01, 0x66, 0x00}, {0x0f, 0x01, 0x66, 0x00}, + {0x18, 0x01, 0x66, 0x00}, {0x1f, 0x01, 0x66, 0x00}, + {0x29, 0x01, 0x66, 0x00}, {0x38, 0x01, 0x66, 0x01} + }, + /* 30 */ + { + {0x03, 0x01, 0x67, 0x00}, {0x06, 0x01, 0x67, 0x00}, + {0x0a, 0x01, 0x67, 0x00}, {0x0f, 0x01, 0x67, 0x00}, + {0x18, 0x01, 0x67, 0x00}, {0x1f, 0x01, 0x67, 0x00}, + {0x29, 0x01, 0x67, 0x00}, {0x38, 0x01, 0x67, 0x01}, + {0x03, 0x01, 0x68, 0x00}, {0x06, 0x01, 0x68, 0x00}, + {0x0a, 0x01, 0x68, 0x00}, {0x0f, 0x01, 0x68, 0x00}, + {0x18, 0x01, 0x68, 0x00}, {0x1f, 0x01, 0x68, 0x00}, + {0x29, 0x01, 0x68, 0x00}, {0x38, 0x01, 0x68, 0x01} + }, + { + {0x01, 0x01, 0x6c, 0x00}, {0x16, 0x01, 0x6c, 0x01}, + {0x01, 0x01, 0x6d, 0x00}, {0x16, 0x01, 0x6d, 0x01}, + {0x01, 0x01, 0x6e, 0x00}, {0x16, 0x01, 0x6e, 0x01}, + {0x01, 0x01, 0x70, 0x00}, {0x16, 0x01, 0x70, 0x01}, + {0x01, 0x01, 0x72, 0x00}, {0x16, 0x01, 0x72, 0x01}, + {0x01, 0x01, 0x75, 0x00}, {0x16, 0x01, 0x75, 0x01}, + {0x00, 0x01, 0x3a, 0x01}, {0x00, 0x01, 0x42, 0x01}, + {0x00, 0x01, 0x43, 0x01}, {0x00, 0x01, 0x44, 0x01} + }, + { + {0x02, 0x01, 0x6c, 0x00}, {0x09, 0x01, 0x6c, 0x00}, + {0x17, 0x01, 0x6c, 0x00}, {0x28, 0x01, 0x6c, 0x01}, + {0x02, 0x01, 0x6d, 0x00}, {0x09, 0x01, 0x6d, 0x00}, + {0x17, 0x01, 0x6d, 0x00}, {0x28, 0x01, 0x6d, 0x01}, + {0x02, 0x01, 0x6e, 0x00}, {0x09, 0x01, 0x6e, 0x00}, + {0x17, 0x01, 0x6e, 0x00}, {0x28, 0x01, 0x6e, 0x01}, + {0x02, 0x01, 0x70, 0x00}, {0x09, 0x01, 0x70, 0x00}, + {0x17, 0x01, 0x70, 0x00}, {0x28, 0x01, 0x70, 0x01} + }, + { + {0x03, 0x01, 0x6c, 0x00}, {0x06, 0x01, 0x6c, 0x00}, + {0x0a, 0x01, 0x6c, 0x00}, {0x0f, 0x01, 0x6c, 0x00}, + {0x18, 0x01, 0x6c, 0x00}, {0x1f, 0x01, 0x6c, 0x00}, + {0x29, 0x01, 0x6c, 0x00}, {0x38, 0x01, 0x6c, 0x01}, + {0x03, 0x01, 0x6d, 0x00}, {0x06, 0x01, 0x6d, 0x00}, + {0x0a, 0x01, 0x6d, 0x00}, {0x0f, 0x01, 0x6d, 0x00}, + {0x18, 0x01, 0x6d, 0x00}, {0x1f, 0x01, 0x6d, 0x00}, + {0x29, 0x01, 0x6d, 0x00}, {0x38, 0x01, 0x6d, 0x01} + }, + { + {0x03, 0x01, 0x6e, 0x00}, {0x06, 0x01, 0x6e, 0x00}, + {0x0a, 0x01, 0x6e, 0x00}, {0x0f, 0x01, 0x6e, 0x00}, + {0x18, 0x01, 0x6e, 0x00}, {0x1f, 0x01, 0x6e, 0x00}, + {0x29, 0x01, 0x6e, 0x00}, {0x38, 0x01, 0x6e, 0x01}, + {0x03, 0x01, 0x70, 0x00}, {0x06, 0x01, 0x70, 0x00}, + {0x0a, 0x01, 0x70, 0x00}, {0x0f, 0x01, 0x70, 0x00}, + {0x18, 0x01, 0x70, 0x00}, {0x1f, 0x01, 0x70, 0x00}, + {0x29, 0x01, 0x70, 0x00}, {0x38, 0x01, 0x70, 0x01} + }, + /* 35 */ + { + {0x02, 0x01, 0x72, 0x00}, {0x09, 0x01, 0x72, 0x00}, + {0x17, 0x01, 0x72, 0x00}, {0x28, 0x01, 0x72, 0x01}, + {0x02, 0x01, 0x75, 0x00}, {0x09, 0x01, 0x75, 0x00}, + {0x17, 0x01, 0x75, 0x00}, {0x28, 0x01, 0x75, 0x01}, + {0x01, 0x01, 0x3a, 0x00}, {0x16, 0x01, 0x3a, 0x01}, + {0x01, 0x01, 0x42, 0x00}, {0x16, 0x01, 0x42, 0x01}, + {0x01, 0x01, 0x43, 0x00}, {0x16, 0x01, 0x43, 0x01}, + {0x01, 0x01, 0x44, 0x00}, {0x16, 0x01, 0x44, 0x01} + }, + { + {0x03, 0x01, 0x72, 0x00}, {0x06, 0x01, 0x72, 0x00}, + {0x0a, 0x01, 0x72, 0x00}, {0x0f, 0x01, 0x72, 0x00}, + {0x18, 0x01, 0x72, 0x00}, {0x1f, 0x01, 0x72, 0x00}, + {0x29, 0x01, 0x72, 0x00}, {0x38, 0x01, 0x72, 0x01}, + {0x03, 0x01, 0x75, 0x00}, {0x06, 0x01, 0x75, 0x00}, + {0x0a, 0x01, 0x75, 0x00}, {0x0f, 0x01, 0x75, 0x00}, + {0x18, 0x01, 0x75, 0x00}, {0x1f, 0x01, 0x75, 0x00}, + {0x29, 0x01, 0x75, 0x00}, {0x38, 0x01, 0x75, 0x01} + }, + { + {0x02, 0x01, 0x3a, 0x00}, {0x09, 0x01, 0x3a, 0x00}, + {0x17, 0x01, 0x3a, 0x00}, {0x28, 0x01, 0x3a, 0x01}, + {0x02, 0x01, 0x42, 0x00}, {0x09, 0x01, 0x42, 0x00}, + {0x17, 0x01, 0x42, 0x00}, {0x28, 0x01, 0x42, 0x01}, + {0x02, 0x01, 0x43, 0x00}, {0x09, 0x01, 0x43, 0x00}, + {0x17, 0x01, 0x43, 0x00}, {0x28, 0x01, 0x43, 0x01}, + {0x02, 0x01, 0x44, 0x00}, {0x09, 0x01, 0x44, 0x00}, + {0x17, 0x01, 0x44, 0x00}, {0x28, 0x01, 0x44, 0x01} + }, + { + {0x03, 0x01, 0x3a, 0x00}, {0x06, 0x01, 0x3a, 0x00}, + {0x0a, 0x01, 0x3a, 0x00}, {0x0f, 0x01, 0x3a, 0x00}, + {0x18, 0x01, 0x3a, 0x00}, {0x1f, 0x01, 0x3a, 0x00}, + {0x29, 0x01, 0x3a, 0x00}, {0x38, 0x01, 0x3a, 0x01}, + {0x03, 0x01, 0x42, 0x00}, {0x06, 0x01, 0x42, 0x00}, + {0x0a, 0x01, 0x42, 0x00}, {0x0f, 0x01, 0x42, 0x00}, + {0x18, 0x01, 0x42, 0x00}, {0x1f, 0x01, 0x42, 0x00}, + {0x29, 0x01, 0x42, 0x00}, {0x38, 0x01, 0x42, 0x01} + }, + { + {0x03, 0x01, 0x43, 0x00}, {0x06, 0x01, 0x43, 0x00}, + {0x0a, 0x01, 0x43, 0x00}, {0x0f, 0x01, 0x43, 0x00}, + {0x18, 0x01, 0x43, 0x00}, {0x1f, 0x01, 0x43, 0x00}, + {0x29, 0x01, 0x43, 0x00}, {0x38, 0x01, 0x43, 0x01}, + {0x03, 0x01, 0x44, 0x00}, {0x06, 0x01, 0x44, 0x00}, + {0x0a, 0x01, 0x44, 0x00}, {0x0f, 0x01, 0x44, 0x00}, + {0x18, 0x01, 0x44, 0x00}, {0x1f, 0x01, 0x44, 0x00}, + {0x29, 0x01, 0x44, 0x00}, {0x38, 0x01, 0x44, 0x01} + }, + /* 40 */ + { + {0x2c, 0x00, 0x00, 0x00}, {0x2d, 0x00, 0x00, 0x00}, + {0x2f, 0x00, 0x00, 0x00}, {0x30, 0x00, 0x00, 0x00}, + {0x33, 0x00, 0x00, 0x00}, {0x34, 0x00, 0x00, 0x00}, + {0x36, 0x00, 0x00, 0x00}, {0x37, 0x00, 0x00, 0x00}, + {0x3b, 0x00, 0x00, 0x00}, {0x3c, 0x00, 0x00, 0x00}, + {0x3e, 0x00, 0x00, 0x00}, {0x3f, 0x00, 0x00, 0x00}, + {0x42, 0x00, 0x00, 0x00}, {0x43, 0x00, 0x00, 0x00}, + {0x45, 0x00, 0x00, 0x00}, {0x48, 0x00, 0x00, 0x01} + }, + { + {0x00, 0x01, 0x45, 0x01}, {0x00, 0x01, 0x46, 0x01}, + {0x00, 0x01, 0x47, 0x01}, {0x00, 0x01, 0x48, 0x01}, + {0x00, 0x01, 0x49, 0x01}, {0x00, 0x01, 0x4a, 0x01}, + {0x00, 0x01, 0x4b, 0x01}, {0x00, 0x01, 0x4c, 0x01}, + {0x00, 0x01, 0x4d, 0x01}, {0x00, 0x01, 0x4e, 0x01}, + {0x00, 0x01, 0x4f, 0x01}, {0x00, 0x01, 0x50, 0x01}, + {0x00, 0x01, 0x51, 0x01}, {0x00, 0x01, 0x52, 0x01}, + {0x00, 0x01, 0x53, 0x01}, {0x00, 0x01, 0x54, 0x01} + }, + { + {0x01, 0x01, 0x45, 0x00}, {0x16, 0x01, 0x45, 0x01}, + {0x01, 0x01, 0x46, 0x00}, {0x16, 0x01, 0x46, 0x01}, + {0x01, 0x01, 0x47, 0x00}, {0x16, 0x01, 0x47, 0x01}, + {0x01, 0x01, 0x48, 0x00}, {0x16, 0x01, 0x48, 0x01}, + {0x01, 0x01, 0x49, 0x00}, {0x16, 0x01, 0x49, 0x01}, + {0x01, 0x01, 0x4a, 0x00}, {0x16, 0x01, 0x4a, 0x01}, + {0x01, 0x01, 0x4b, 0x00}, {0x16, 0x01, 0x4b, 0x01}, + {0x01, 0x01, 0x4c, 0x00}, {0x16, 0x01, 0x4c, 0x01} + }, + { + {0x02, 0x01, 0x45, 0x00}, {0x09, 0x01, 0x45, 0x00}, + {0x17, 0x01, 0x45, 0x00}, {0x28, 0x01, 0x45, 0x01}, + {0x02, 0x01, 0x46, 0x00}, {0x09, 0x01, 0x46, 0x00}, + {0x17, 0x01, 0x46, 0x00}, {0x28, 0x01, 0x46, 0x01}, + {0x02, 0x01, 0x47, 0x00}, {0x09, 0x01, 0x47, 0x00}, + {0x17, 0x01, 0x47, 0x00}, {0x28, 0x01, 0x47, 0x01}, + {0x02, 0x01, 0x48, 0x00}, {0x09, 0x01, 0x48, 0x00}, + {0x17, 0x01, 0x48, 0x00}, {0x28, 0x01, 0x48, 0x01} + }, + { + {0x03, 0x01, 0x45, 0x00}, {0x06, 0x01, 0x45, 0x00}, + {0x0a, 0x01, 0x45, 0x00}, {0x0f, 0x01, 0x45, 0x00}, + {0x18, 0x01, 0x45, 0x00}, {0x1f, 0x01, 0x45, 0x00}, + {0x29, 0x01, 0x45, 0x00}, {0x38, 0x01, 0x45, 0x01}, + {0x03, 0x01, 0x46, 0x00}, {0x06, 0x01, 0x46, 0x00}, + {0x0a, 0x01, 0x46, 0x00}, {0x0f, 0x01, 0x46, 0x00}, + {0x18, 0x01, 0x46, 0x00}, {0x1f, 0x01, 0x46, 0x00}, + {0x29, 0x01, 0x46, 0x00}, {0x38, 0x01, 0x46, 0x01} + }, + /* 45 */ + { + {0x03, 0x01, 0x47, 0x00}, {0x06, 0x01, 0x47, 0x00}, + {0x0a, 0x01, 0x47, 0x00}, {0x0f, 0x01, 0x47, 0x00}, + {0x18, 0x01, 0x47, 0x00}, {0x1f, 0x01, 0x47, 0x00}, + {0x29, 0x01, 0x47, 0x00}, {0x38, 0x01, 0x47, 0x01}, + {0x03, 0x01, 0x48, 0x00}, {0x06, 0x01, 0x48, 0x00}, + {0x0a, 0x01, 0x48, 0x00}, {0x0f, 0x01, 0x48, 0x00}, + {0x18, 0x01, 0x48, 0x00}, {0x1f, 0x01, 0x48, 0x00}, + {0x29, 0x01, 0x48, 0x00}, {0x38, 0x01, 0x48, 0x01} + }, + { + {0x02, 0x01, 0x49, 0x00}, {0x09, 0x01, 0x49, 0x00}, + {0x17, 0x01, 0x49, 0x00}, {0x28, 0x01, 0x49, 0x01}, + {0x02, 0x01, 0x4a, 0x00}, {0x09, 0x01, 0x4a, 0x00}, + {0x17, 0x01, 0x4a, 0x00}, {0x28, 0x01, 0x4a, 0x01}, + {0x02, 0x01, 0x4b, 0x00}, {0x09, 0x01, 0x4b, 0x00}, + {0x17, 0x01, 0x4b, 0x00}, {0x28, 0x01, 0x4b, 0x01}, + {0x02, 0x01, 0x4c, 0x00}, {0x09, 0x01, 0x4c, 0x00}, + {0x17, 0x01, 0x4c, 0x00}, {0x28, 0x01, 0x4c, 0x01} + }, + { + {0x03, 0x01, 0x49, 0x00}, {0x06, 0x01, 0x49, 0x00}, + {0x0a, 0x01, 0x49, 0x00}, {0x0f, 0x01, 0x49, 0x00}, + {0x18, 0x01, 0x49, 0x00}, {0x1f, 0x01, 0x49, 0x00}, + {0x29, 0x01, 0x49, 0x00}, {0x38, 0x01, 0x49, 0x01}, + {0x03, 0x01, 0x4a, 0x00}, {0x06, 0x01, 0x4a, 0x00}, + {0x0a, 0x01, 0x4a, 0x00}, {0x0f, 0x01, 0x4a, 0x00}, + {0x18, 0x01, 0x4a, 0x00}, {0x1f, 0x01, 0x4a, 0x00}, + {0x29, 0x01, 0x4a, 0x00}, {0x38, 0x01, 0x4a, 0x01} + }, + { + {0x03, 0x01, 0x4b, 0x00}, {0x06, 0x01, 0x4b, 0x00}, + {0x0a, 0x01, 0x4b, 0x00}, {0x0f, 0x01, 0x4b, 0x00}, + {0x18, 0x01, 0x4b, 0x00}, {0x1f, 0x01, 0x4b, 0x00}, + {0x29, 0x01, 0x4b, 0x00}, {0x38, 0x01, 0x4b, 0x01}, + {0x03, 0x01, 0x4c, 0x00}, {0x06, 0x01, 0x4c, 0x00}, + {0x0a, 0x01, 0x4c, 0x00}, {0x0f, 0x01, 0x4c, 0x00}, + {0x18, 0x01, 0x4c, 0x00}, {0x1f, 0x01, 0x4c, 0x00}, + {0x29, 0x01, 0x4c, 0x00}, {0x38, 0x01, 0x4c, 0x01} + }, + { + {0x01, 0x01, 0x4d, 0x00}, {0x16, 0x01, 0x4d, 0x01}, + {0x01, 0x01, 0x4e, 0x00}, {0x16, 0x01, 0x4e, 0x01}, + {0x01, 0x01, 0x4f, 0x00}, {0x16, 0x01, 0x4f, 0x01}, + {0x01, 0x01, 0x50, 0x00}, {0x16, 0x01, 0x50, 0x01}, + {0x01, 0x01, 0x51, 0x00}, {0x16, 0x01, 0x51, 0x01}, + {0x01, 0x01, 0x52, 0x00}, {0x16, 0x01, 0x52, 0x01}, + {0x01, 0x01, 0x53, 0x00}, {0x16, 0x01, 0x53, 0x01}, + {0x01, 0x01, 0x54, 0x00}, {0x16, 0x01, 0x54, 0x01} + }, + /* 50 */ + { + {0x02, 0x01, 0x4d, 0x00}, {0x09, 0x01, 0x4d, 0x00}, + {0x17, 0x01, 0x4d, 0x00}, {0x28, 0x01, 0x4d, 0x01}, + {0x02, 0x01, 0x4e, 0x00}, {0x09, 0x01, 0x4e, 0x00}, + {0x17, 0x01, 0x4e, 0x00}, {0x28, 0x01, 0x4e, 0x01}, + {0x02, 0x01, 0x4f, 0x00}, {0x09, 0x01, 0x4f, 0x00}, + {0x17, 0x01, 0x4f, 0x00}, {0x28, 0x01, 0x4f, 0x01}, + {0x02, 0x01, 0x50, 0x00}, {0x09, 0x01, 0x50, 0x00}, + {0x17, 0x01, 0x50, 0x00}, {0x28, 0x01, 0x50, 0x01} + }, + { + {0x03, 0x01, 0x4d, 0x00}, {0x06, 0x01, 0x4d, 0x00}, + {0x0a, 0x01, 0x4d, 0x00}, {0x0f, 0x01, 0x4d, 0x00}, + {0x18, 0x01, 0x4d, 0x00}, {0x1f, 0x01, 0x4d, 0x00}, + {0x29, 0x01, 0x4d, 0x00}, {0x38, 0x01, 0x4d, 0x01}, + {0x03, 0x01, 0x4e, 0x00}, {0x06, 0x01, 0x4e, 0x00}, + {0x0a, 0x01, 0x4e, 0x00}, {0x0f, 0x01, 0x4e, 0x00}, + {0x18, 0x01, 0x4e, 0x00}, {0x1f, 0x01, 0x4e, 0x00}, + {0x29, 0x01, 0x4e, 0x00}, {0x38, 0x01, 0x4e, 0x01} + }, + { + {0x03, 0x01, 0x4f, 0x00}, {0x06, 0x01, 0x4f, 0x00}, + {0x0a, 0x01, 0x4f, 0x00}, {0x0f, 0x01, 0x4f, 0x00}, + {0x18, 0x01, 0x4f, 0x00}, {0x1f, 0x01, 0x4f, 0x00}, + {0x29, 0x01, 0x4f, 0x00}, {0x38, 0x01, 0x4f, 0x01}, + {0x03, 0x01, 0x50, 0x00}, {0x06, 0x01, 0x50, 0x00}, + {0x0a, 0x01, 0x50, 0x00}, {0x0f, 0x01, 0x50, 0x00}, + {0x18, 0x01, 0x50, 0x00}, {0x1f, 0x01, 0x50, 0x00}, + {0x29, 0x01, 0x50, 0x00}, {0x38, 0x01, 0x50, 0x01} + }, + { + {0x02, 0x01, 0x51, 0x00}, {0x09, 0x01, 0x51, 0x00}, + {0x17, 0x01, 0x51, 0x00}, {0x28, 0x01, 0x51, 0x01}, + {0x02, 0x01, 0x52, 0x00}, {0x09, 0x01, 0x52, 0x00}, + {0x17, 0x01, 0x52, 0x00}, {0x28, 0x01, 0x52, 0x01}, + {0x02, 0x01, 0x53, 0x00}, {0x09, 0x01, 0x53, 0x00}, + {0x17, 0x01, 0x53, 0x00}, {0x28, 0x01, 0x53, 0x01}, + {0x02, 0x01, 0x54, 0x00}, {0x09, 0x01, 0x54, 0x00}, + {0x17, 0x01, 0x54, 0x00}, {0x28, 0x01, 0x54, 0x01} + }, + { + {0x03, 0x01, 0x51, 0x00}, {0x06, 0x01, 0x51, 0x00}, + {0x0a, 0x01, 0x51, 0x00}, {0x0f, 0x01, 0x51, 0x00}, + {0x18, 0x01, 0x51, 0x00}, {0x1f, 0x01, 0x51, 0x00}, + {0x29, 0x01, 0x51, 0x00}, {0x38, 0x01, 0x51, 0x01}, + {0x03, 0x01, 0x52, 0x00}, {0x06, 0x01, 0x52, 0x00}, + {0x0a, 0x01, 0x52, 0x00}, {0x0f, 0x01, 0x52, 0x00}, + {0x18, 0x01, 0x52, 0x00}, {0x1f, 0x01, 0x52, 0x00}, + {0x29, 0x01, 0x52, 0x00}, {0x38, 0x01, 0x52, 0x01} + }, + /* 55 */ + { + {0x03, 0x01, 0x53, 0x00}, {0x06, 0x01, 0x53, 0x00}, + {0x0a, 0x01, 0x53, 0x00}, {0x0f, 0x01, 0x53, 0x00}, + {0x18, 0x01, 0x53, 0x00}, {0x1f, 0x01, 0x53, 0x00}, + {0x29, 0x01, 0x53, 0x00}, {0x38, 0x01, 0x53, 0x01}, + {0x03, 0x01, 0x54, 0x00}, {0x06, 0x01, 0x54, 0x00}, + {0x0a, 0x01, 0x54, 0x00}, {0x0f, 0x01, 0x54, 0x00}, + {0x18, 0x01, 0x54, 0x00}, {0x1f, 0x01, 0x54, 0x00}, + {0x29, 0x01, 0x54, 0x00}, {0x38, 0x01, 0x54, 0x01} + }, + { + {0x00, 0x01, 0x55, 0x01}, {0x00, 0x01, 0x56, 0x01}, + {0x00, 0x01, 0x57, 0x01}, {0x00, 0x01, 0x59, 0x01}, + {0x00, 0x01, 0x6a, 0x01}, {0x00, 0x01, 0x6b, 0x01}, + {0x00, 0x01, 0x71, 0x01}, {0x00, 0x01, 0x76, 0x01}, + {0x00, 0x01, 0x77, 0x01}, {0x00, 0x01, 0x78, 0x01}, + {0x00, 0x01, 0x79, 0x01}, {0x00, 0x01, 0x7a, 0x01}, + {0x46, 0x00, 0x00, 0x00}, {0x47, 0x00, 0x00, 0x00}, + {0x49, 0x00, 0x00, 0x00}, {0x4a, 0x00, 0x00, 0x01} + }, + { + {0x01, 0x01, 0x55, 0x00}, {0x16, 0x01, 0x55, 0x01}, + {0x01, 0x01, 0x56, 0x00}, {0x16, 0x01, 0x56, 0x01}, + {0x01, 0x01, 0x57, 0x00}, {0x16, 0x01, 0x57, 0x01}, + {0x01, 0x01, 0x59, 0x00}, {0x16, 0x01, 0x59, 0x01}, + {0x01, 0x01, 0x6a, 0x00}, {0x16, 0x01, 0x6a, 0x01}, + {0x01, 0x01, 0x6b, 0x00}, {0x16, 0x01, 0x6b, 0x01}, + {0x01, 0x01, 0x71, 0x00}, {0x16, 0x01, 0x71, 0x01}, + {0x01, 0x01, 0x76, 0x00}, {0x16, 0x01, 0x76, 0x01} + }, + { + {0x02, 0x01, 0x55, 0x00}, {0x09, 0x01, 0x55, 0x00}, + {0x17, 0x01, 0x55, 0x00}, {0x28, 0x01, 0x55, 0x01}, + {0x02, 0x01, 0x56, 0x00}, {0x09, 0x01, 0x56, 0x00}, + {0x17, 0x01, 0x56, 0x00}, {0x28, 0x01, 0x56, 0x01}, + {0x02, 0x01, 0x57, 0x00}, {0x09, 0x01, 0x57, 0x00}, + {0x17, 0x01, 0x57, 0x00}, {0x28, 0x01, 0x57, 0x01}, + {0x02, 0x01, 0x59, 0x00}, {0x09, 0x01, 0x59, 0x00}, + {0x17, 0x01, 0x59, 0x00}, {0x28, 0x01, 0x59, 0x01} + }, + { + {0x03, 0x01, 0x55, 0x00}, {0x06, 0x01, 0x55, 0x00}, + {0x0a, 0x01, 0x55, 0x00}, {0x0f, 0x01, 0x55, 0x00}, + {0x18, 0x01, 0x55, 0x00}, {0x1f, 0x01, 0x55, 0x00}, + {0x29, 0x01, 0x55, 0x00}, {0x38, 0x01, 0x55, 0x01}, + {0x03, 0x01, 0x56, 0x00}, {0x06, 0x01, 0x56, 0x00}, + {0x0a, 0x01, 0x56, 0x00}, {0x0f, 0x01, 0x56, 0x00}, + {0x18, 0x01, 0x56, 0x00}, {0x1f, 0x01, 0x56, 0x00}, + {0x29, 0x01, 0x56, 0x00}, {0x38, 0x01, 0x56, 0x01} + }, + /* 60 */ + { + {0x03, 0x01, 0x57, 0x00}, {0x06, 0x01, 0x57, 0x00}, + {0x0a, 0x01, 0x57, 0x00}, {0x0f, 0x01, 0x57, 0x00}, + {0x18, 0x01, 0x57, 0x00}, {0x1f, 0x01, 0x57, 0x00}, + {0x29, 0x01, 0x57, 0x00}, {0x38, 0x01, 0x57, 0x01}, + {0x03, 0x01, 0x59, 0x00}, {0x06, 0x01, 0x59, 0x00}, + {0x0a, 0x01, 0x59, 0x00}, {0x0f, 0x01, 0x59, 0x00}, + {0x18, 0x01, 0x59, 0x00}, {0x1f, 0x01, 0x59, 0x00}, + {0x29, 0x01, 0x59, 0x00}, {0x38, 0x01, 0x59, 0x01} + }, + { + {0x02, 0x01, 0x6a, 0x00}, {0x09, 0x01, 0x6a, 0x00}, + {0x17, 0x01, 0x6a, 0x00}, {0x28, 0x01, 0x6a, 0x01}, + {0x02, 0x01, 0x6b, 0x00}, {0x09, 0x01, 0x6b, 0x00}, + {0x17, 0x01, 0x6b, 0x00}, {0x28, 0x01, 0x6b, 0x01}, + {0x02, 0x01, 0x71, 0x00}, {0x09, 0x01, 0x71, 0x00}, + {0x17, 0x01, 0x71, 0x00}, {0x28, 0x01, 0x71, 0x01}, + {0x02, 0x01, 0x76, 0x00}, {0x09, 0x01, 0x76, 0x00}, + {0x17, 0x01, 0x76, 0x00}, {0x28, 0x01, 0x76, 0x01} + }, + { + {0x03, 0x01, 0x6a, 0x00}, {0x06, 0x01, 0x6a, 0x00}, + {0x0a, 0x01, 0x6a, 0x00}, {0x0f, 0x01, 0x6a, 0x00}, + {0x18, 0x01, 0x6a, 0x00}, {0x1f, 0x01, 0x6a, 0x00}, + {0x29, 0x01, 0x6a, 0x00}, {0x38, 0x01, 0x6a, 0x01}, + {0x03, 0x01, 0x6b, 0x00}, {0x06, 0x01, 0x6b, 0x00}, + {0x0a, 0x01, 0x6b, 0x00}, {0x0f, 0x01, 0x6b, 0x00}, + {0x18, 0x01, 0x6b, 0x00}, {0x1f, 0x01, 0x6b, 0x00}, + {0x29, 0x01, 0x6b, 0x00}, {0x38, 0x01, 0x6b, 0x01} + }, + { + {0x03, 0x01, 0x71, 0x00}, {0x06, 0x01, 0x71, 0x00}, + {0x0a, 0x01, 0x71, 0x00}, {0x0f, 0x01, 0x71, 0x00}, + {0x18, 0x01, 0x71, 0x00}, {0x1f, 0x01, 0x71, 0x00}, + {0x29, 0x01, 0x71, 0x00}, {0x38, 0x01, 0x71, 0x01}, + {0x03, 0x01, 0x76, 0x00}, {0x06, 0x01, 0x76, 0x00}, + {0x0a, 0x01, 0x76, 0x00}, {0x0f, 0x01, 0x76, 0x00}, + {0x18, 0x01, 0x76, 0x00}, {0x1f, 0x01, 0x76, 0x00}, + {0x29, 0x01, 0x76, 0x00}, {0x38, 0x01, 0x76, 0x01} + }, + { + {0x01, 0x01, 0x77, 0x00}, {0x16, 0x01, 0x77, 0x01}, + {0x01, 0x01, 0x78, 0x00}, {0x16, 0x01, 0x78, 0x01}, + {0x01, 0x01, 0x79, 0x00}, {0x16, 0x01, 0x79, 0x01}, + {0x01, 0x01, 0x7a, 0x00}, {0x16, 0x01, 0x7a, 0x01}, + {0x00, 0x01, 0x26, 0x01}, {0x00, 0x01, 0x2a, 0x01}, + {0x00, 0x01, 0x2c, 0x01}, {0x00, 0x01, 0x3b, 0x01}, + {0x00, 0x01, 0x58, 0x01}, {0x00, 0x01, 0x5a, 0x01}, + {0x4b, 0x00, 0x00, 0x00}, {0x4e, 0x00, 0x00, 0x01} + }, + /* 65 */ + { + {0x02, 0x01, 0x77, 0x00}, {0x09, 0x01, 0x77, 0x00}, + {0x17, 0x01, 0x77, 0x00}, {0x28, 0x01, 0x77, 0x01}, + {0x02, 0x01, 0x78, 0x00}, {0x09, 0x01, 0x78, 0x00}, + {0x17, 0x01, 0x78, 0x00}, {0x28, 0x01, 0x78, 0x01}, + {0x02, 0x01, 0x79, 0x00}, {0x09, 0x01, 0x79, 0x00}, + {0x17, 0x01, 0x79, 0x00}, {0x28, 0x01, 0x79, 0x01}, + {0x02, 0x01, 0x7a, 0x00}, {0x09, 0x01, 0x7a, 0x00}, + {0x17, 0x01, 0x7a, 0x00}, {0x28, 0x01, 0x7a, 0x01} + }, + { + {0x03, 0x01, 0x77, 0x00}, {0x06, 0x01, 0x77, 0x00}, + {0x0a, 0x01, 0x77, 0x00}, {0x0f, 0x01, 0x77, 0x00}, + {0x18, 0x01, 0x77, 0x00}, {0x1f, 0x01, 0x77, 0x00}, + {0x29, 0x01, 0x77, 0x00}, {0x38, 0x01, 0x77, 0x01}, + {0x03, 0x01, 0x78, 0x00}, {0x06, 0x01, 0x78, 0x00}, + {0x0a, 0x01, 0x78, 0x00}, {0x0f, 0x01, 0x78, 0x00}, + {0x18, 0x01, 0x78, 0x00}, {0x1f, 0x01, 0x78, 0x00}, + {0x29, 0x01, 0x78, 0x00}, {0x38, 0x01, 0x78, 0x01} + }, + { + {0x03, 0x01, 0x79, 0x00}, {0x06, 0x01, 0x79, 0x00}, + {0x0a, 0x01, 0x79, 0x00}, {0x0f, 0x01, 0x79, 0x00}, + {0x18, 0x01, 0x79, 0x00}, {0x1f, 0x01, 0x79, 0x00}, + {0x29, 0x01, 0x79, 0x00}, {0x38, 0x01, 0x79, 0x01}, + {0x03, 0x01, 0x7a, 0x00}, {0x06, 0x01, 0x7a, 0x00}, + {0x0a, 0x01, 0x7a, 0x00}, {0x0f, 0x01, 0x7a, 0x00}, + {0x18, 0x01, 0x7a, 0x00}, {0x1f, 0x01, 0x7a, 0x00}, + {0x29, 0x01, 0x7a, 0x00}, {0x38, 0x01, 0x7a, 0x01} + }, + { + {0x01, 0x01, 0x26, 0x00}, {0x16, 0x01, 0x26, 0x01}, + {0x01, 0x01, 0x2a, 0x00}, {0x16, 0x01, 0x2a, 0x01}, + {0x01, 0x01, 0x2c, 0x00}, {0x16, 0x01, 0x2c, 0x01}, + {0x01, 0x01, 0x3b, 0x00}, {0x16, 0x01, 0x3b, 0x01}, + {0x01, 0x01, 0x58, 0x00}, {0x16, 0x01, 0x58, 0x01}, + {0x01, 0x01, 0x5a, 0x00}, {0x16, 0x01, 0x5a, 0x01}, + {0x4c, 0x00, 0x00, 0x00}, {0x4d, 0x00, 0x00, 0x00}, + {0x4f, 0x00, 0x00, 0x00}, {0x51, 0x00, 0x00, 0x01} + }, + { + {0x02, 0x01, 0x26, 0x00}, {0x09, 0x01, 0x26, 0x00}, + {0x17, 0x01, 0x26, 0x00}, {0x28, 0x01, 0x26, 0x01}, + {0x02, 0x01, 0x2a, 0x00}, {0x09, 0x01, 0x2a, 0x00}, + {0x17, 0x01, 0x2a, 0x00}, {0x28, 0x01, 0x2a, 0x01}, + {0x02, 0x01, 0x2c, 0x00}, {0x09, 0x01, 0x2c, 0x00}, + {0x17, 0x01, 0x2c, 0x00}, {0x28, 0x01, 0x2c, 0x01}, + {0x02, 0x01, 0x3b, 0x00}, {0x09, 0x01, 0x3b, 0x00}, + {0x17, 0x01, 0x3b, 0x00}, {0x28, 0x01, 0x3b, 0x01} + }, + /* 70 */ + { + {0x03, 0x01, 0x26, 0x00}, {0x06, 0x01, 0x26, 0x00}, + {0x0a, 0x01, 0x26, 0x00}, {0x0f, 0x01, 0x26, 0x00}, + {0x18, 0x01, 0x26, 0x00}, {0x1f, 0x01, 0x26, 0x00}, + {0x29, 0x01, 0x26, 0x00}, {0x38, 0x01, 0x26, 0x01}, + {0x03, 0x01, 0x2a, 0x00}, {0x06, 0x01, 0x2a, 0x00}, + {0x0a, 0x01, 0x2a, 0x00}, {0x0f, 0x01, 0x2a, 0x00}, + {0x18, 0x01, 0x2a, 0x00}, {0x1f, 0x01, 0x2a, 0x00}, + {0x29, 0x01, 0x2a, 0x00}, {0x38, 0x01, 0x2a, 0x01} + }, + { + {0x03, 0x01, 0x2c, 0x00}, {0x06, 0x01, 0x2c, 0x00}, + {0x0a, 0x01, 0x2c, 0x00}, {0x0f, 0x01, 0x2c, 0x00}, + {0x18, 0x01, 0x2c, 0x00}, {0x1f, 0x01, 0x2c, 0x00}, + {0x29, 0x01, 0x2c, 0x00}, {0x38, 0x01, 0x2c, 0x01}, + {0x03, 0x01, 0x3b, 0x00}, {0x06, 0x01, 0x3b, 0x00}, + {0x0a, 0x01, 0x3b, 0x00}, {0x0f, 0x01, 0x3b, 0x00}, + {0x18, 0x01, 0x3b, 0x00}, {0x1f, 0x01, 0x3b, 0x00}, + {0x29, 0x01, 0x3b, 0x00}, {0x38, 0x01, 0x3b, 0x01} + }, + { + {0x02, 0x01, 0x58, 0x00}, {0x09, 0x01, 0x58, 0x00}, + {0x17, 0x01, 0x58, 0x00}, {0x28, 0x01, 0x58, 0x01}, + {0x02, 0x01, 0x5a, 0x00}, {0x09, 0x01, 0x5a, 0x00}, + {0x17, 0x01, 0x5a, 0x00}, {0x28, 0x01, 0x5a, 0x01}, + {0x00, 0x01, 0x21, 0x01}, {0x00, 0x01, 0x22, 0x01}, + {0x00, 0x01, 0x28, 0x01}, {0x00, 0x01, 0x29, 0x01}, + {0x00, 0x01, 0x3f, 0x01}, {0x50, 0x00, 0x00, 0x00}, + {0x52, 0x00, 0x00, 0x00}, {0x54, 0x00, 0x00, 0x01} + }, + { + {0x03, 0x01, 0x58, 0x00}, {0x06, 0x01, 0x58, 0x00}, + {0x0a, 0x01, 0x58, 0x00}, {0x0f, 0x01, 0x58, 0x00}, + {0x18, 0x01, 0x58, 0x00}, {0x1f, 0x01, 0x58, 0x00}, + {0x29, 0x01, 0x58, 0x00}, {0x38, 0x01, 0x58, 0x01}, + {0x03, 0x01, 0x5a, 0x00}, {0x06, 0x01, 0x5a, 0x00}, + {0x0a, 0x01, 0x5a, 0x00}, {0x0f, 0x01, 0x5a, 0x00}, + {0x18, 0x01, 0x5a, 0x00}, {0x1f, 0x01, 0x5a, 0x00}, + {0x29, 0x01, 0x5a, 0x00}, {0x38, 0x01, 0x5a, 0x01} + }, + { + {0x01, 0x01, 0x21, 0x00}, {0x16, 0x01, 0x21, 0x01}, + {0x01, 0x01, 0x22, 0x00}, {0x16, 0x01, 0x22, 0x01}, + {0x01, 0x01, 0x28, 0x00}, {0x16, 0x01, 0x28, 0x01}, + {0x01, 0x01, 0x29, 0x00}, {0x16, 0x01, 0x29, 0x01}, + {0x01, 0x01, 0x3f, 0x00}, {0x16, 0x01, 0x3f, 0x01}, + {0x00, 0x01, 0x27, 0x01}, {0x00, 0x01, 0x2b, 0x01}, + {0x00, 0x01, 0x7c, 0x01}, {0x53, 0x00, 0x00, 0x00}, + {0x55, 0x00, 0x00, 0x00}, {0x58, 0x00, 0x00, 0x01} + }, + /* 75 */ + { + {0x02, 0x01, 0x21, 0x00}, {0x09, 0x01, 0x21, 0x00}, + {0x17, 0x01, 0x21, 0x00}, {0x28, 0x01, 0x21, 0x01}, + {0x02, 0x01, 0x22, 0x00}, {0x09, 0x01, 0x22, 0x00}, + {0x17, 0x01, 0x22, 0x00}, {0x28, 0x01, 0x22, 0x01}, + {0x02, 0x01, 0x28, 0x00}, {0x09, 0x01, 0x28, 0x00}, + {0x17, 0x01, 0x28, 0x00}, {0x28, 0x01, 0x28, 0x01}, + {0x02, 0x01, 0x29, 0x00}, {0x09, 0x01, 0x29, 0x00}, + {0x17, 0x01, 0x29, 0x00}, {0x28, 0x01, 0x29, 0x01} + }, + { + {0x03, 0x01, 0x21, 0x00}, {0x06, 0x01, 0x21, 0x00}, + {0x0a, 0x01, 0x21, 0x00}, {0x0f, 0x01, 0x21, 0x00}, + {0x18, 0x01, 0x21, 0x00}, {0x1f, 0x01, 0x21, 0x00}, + {0x29, 0x01, 0x21, 0x00}, {0x38, 0x01, 0x21, 0x01}, + {0x03, 0x01, 0x22, 0x00}, {0x06, 0x01, 0x22, 0x00}, + {0x0a, 0x01, 0x22, 0x00}, {0x0f, 0x01, 0x22, 0x00}, + {0x18, 0x01, 0x22, 0x00}, {0x1f, 0x01, 0x22, 0x00}, + {0x29, 0x01, 0x22, 0x00}, {0x38, 0x01, 0x22, 0x01} + }, + { + {0x03, 0x01, 0x28, 0x00}, {0x06, 0x01, 0x28, 0x00}, + {0x0a, 0x01, 0x28, 0x00}, {0x0f, 0x01, 0x28, 0x00}, + {0x18, 0x01, 0x28, 0x00}, {0x1f, 0x01, 0x28, 0x00}, + {0x29, 0x01, 0x28, 0x00}, {0x38, 0x01, 0x28, 0x01}, + {0x03, 0x01, 0x29, 0x00}, {0x06, 0x01, 0x29, 0x00}, + {0x0a, 0x01, 0x29, 0x00}, {0x0f, 0x01, 0x29, 0x00}, + {0x18, 0x01, 0x29, 0x00}, {0x1f, 0x01, 0x29, 0x00}, + {0x29, 0x01, 0x29, 0x00}, {0x38, 0x01, 0x29, 0x01} + }, + { + {0x02, 0x01, 0x3f, 0x00}, {0x09, 0x01, 0x3f, 0x00}, + {0x17, 0x01, 0x3f, 0x00}, {0x28, 0x01, 0x3f, 0x01}, + {0x01, 0x01, 0x27, 0x00}, {0x16, 0x01, 0x27, 0x01}, + {0x01, 0x01, 0x2b, 0x00}, {0x16, 0x01, 0x2b, 0x01}, + {0x01, 0x01, 0x7c, 0x00}, {0x16, 0x01, 0x7c, 0x01}, + {0x00, 0x01, 0x23, 0x01}, {0x00, 0x01, 0x3e, 0x01}, + {0x56, 0x00, 0x00, 0x00}, {0x57, 0x00, 0x00, 0x00}, + {0x59, 0x00, 0x00, 0x00}, {0x5a, 0x00, 0x00, 0x01} + }, + { + {0x03, 0x01, 0x3f, 0x00}, {0x06, 0x01, 0x3f, 0x00}, + {0x0a, 0x01, 0x3f, 0x00}, {0x0f, 0x01, 0x3f, 0x00}, + {0x18, 0x01, 0x3f, 0x00}, {0x1f, 0x01, 0x3f, 0x00}, + {0x29, 0x01, 0x3f, 0x00}, {0x38, 0x01, 0x3f, 0x01}, + {0x02, 0x01, 0x27, 0x00}, {0x09, 0x01, 0x27, 0x00}, + {0x17, 0x01, 0x27, 0x00}, {0x28, 0x01, 0x27, 0x01}, + {0x02, 0x01, 0x2b, 0x00}, {0x09, 0x01, 0x2b, 0x00}, + {0x17, 0x01, 0x2b, 0x00}, {0x28, 0x01, 0x2b, 0x01} + }, + /* 80 */ + { + {0x03, 0x01, 0x27, 0x00}, {0x06, 0x01, 0x27, 0x00}, + {0x0a, 0x01, 0x27, 0x00}, {0x0f, 0x01, 0x27, 0x00}, + {0x18, 0x01, 0x27, 0x00}, {0x1f, 0x01, 0x27, 0x00}, + {0x29, 0x01, 0x27, 0x00}, {0x38, 0x01, 0x27, 0x01}, + {0x03, 0x01, 0x2b, 0x00}, {0x06, 0x01, 0x2b, 0x00}, + {0x0a, 0x01, 0x2b, 0x00}, {0x0f, 0x01, 0x2b, 0x00}, + {0x18, 0x01, 0x2b, 0x00}, {0x1f, 0x01, 0x2b, 0x00}, + {0x29, 0x01, 0x2b, 0x00}, {0x38, 0x01, 0x2b, 0x01} + }, + { + {0x02, 0x01, 0x7c, 0x00}, {0x09, 0x01, 0x7c, 0x00}, + {0x17, 0x01, 0x7c, 0x00}, {0x28, 0x01, 0x7c, 0x01}, + {0x01, 0x01, 0x23, 0x00}, {0x16, 0x01, 0x23, 0x01}, + {0x01, 0x01, 0x3e, 0x00}, {0x16, 0x01, 0x3e, 0x01}, + {0x00, 0x01, 0x00, 0x01}, {0x00, 0x01, 0x24, 0x01}, + {0x00, 0x01, 0x40, 0x01}, {0x00, 0x01, 0x5b, 0x01}, + {0x00, 0x01, 0x5d, 0x01}, {0x00, 0x01, 0x7e, 0x01}, + {0x5b, 0x00, 0x00, 0x00}, {0x5c, 0x00, 0x00, 0x01} + }, + { + {0x03, 0x01, 0x7c, 0x00}, {0x06, 0x01, 0x7c, 0x00}, + {0x0a, 0x01, 0x7c, 0x00}, {0x0f, 0x01, 0x7c, 0x00}, + {0x18, 0x01, 0x7c, 0x00}, {0x1f, 0x01, 0x7c, 0x00}, + {0x29, 0x01, 0x7c, 0x00}, {0x38, 0x01, 0x7c, 0x01}, + {0x02, 0x01, 0x23, 0x00}, {0x09, 0x01, 0x23, 0x00}, + {0x17, 0x01, 0x23, 0x00}, {0x28, 0x01, 0x23, 0x01}, + {0x02, 0x01, 0x3e, 0x00}, {0x09, 0x01, 0x3e, 0x00}, + {0x17, 0x01, 0x3e, 0x00}, {0x28, 0x01, 0x3e, 0x01} + }, + { + {0x03, 0x01, 0x23, 0x00}, {0x06, 0x01, 0x23, 0x00}, + {0x0a, 0x01, 0x23, 0x00}, {0x0f, 0x01, 0x23, 0x00}, + {0x18, 0x01, 0x23, 0x00}, {0x1f, 0x01, 0x23, 0x00}, + {0x29, 0x01, 0x23, 0x00}, {0x38, 0x01, 0x23, 0x01}, + {0x03, 0x01, 0x3e, 0x00}, {0x06, 0x01, 0x3e, 0x00}, + {0x0a, 0x01, 0x3e, 0x00}, {0x0f, 0x01, 0x3e, 0x00}, + {0x18, 0x01, 0x3e, 0x00}, {0x1f, 0x01, 0x3e, 0x00}, + {0x29, 0x01, 0x3e, 0x00}, {0x38, 0x01, 0x3e, 0x01} + }, + { + {0x01, 0x01, 0x00, 0x00}, {0x16, 0x01, 0x00, 0x01}, + {0x01, 0x01, 0x24, 0x00}, {0x16, 0x01, 0x24, 0x01}, + {0x01, 0x01, 0x40, 0x00}, {0x16, 0x01, 0x40, 0x01}, + {0x01, 0x01, 0x5b, 0x00}, {0x16, 0x01, 0x5b, 0x01}, + {0x01, 0x01, 0x5d, 0x00}, {0x16, 0x01, 0x5d, 0x01}, + {0x01, 0x01, 0x7e, 0x00}, {0x16, 0x01, 0x7e, 0x01}, + {0x00, 0x01, 0x5e, 0x01}, {0x00, 0x01, 0x7d, 0x01}, + {0x5d, 0x00, 0x00, 0x00}, {0x5e, 0x00, 0x00, 0x01} + }, + /* 85 */ + { + {0x02, 0x01, 0x00, 0x00}, {0x09, 0x01, 0x00, 0x00}, + {0x17, 0x01, 0x00, 0x00}, {0x28, 0x01, 0x00, 0x01}, + {0x02, 0x01, 0x24, 0x00}, {0x09, 0x01, 0x24, 0x00}, + {0x17, 0x01, 0x24, 0x00}, {0x28, 0x01, 0x24, 0x01}, + {0x02, 0x01, 0x40, 0x00}, {0x09, 0x01, 0x40, 0x00}, + {0x17, 0x01, 0x40, 0x00}, {0x28, 0x01, 0x40, 0x01}, + {0x02, 0x01, 0x5b, 0x00}, {0x09, 0x01, 0x5b, 0x00}, + {0x17, 0x01, 0x5b, 0x00}, {0x28, 0x01, 0x5b, 0x01} + }, + { + {0x03, 0x01, 0x00, 0x00}, {0x06, 0x01, 0x00, 0x00}, + {0x0a, 0x01, 0x00, 0x00}, {0x0f, 0x01, 0x00, 0x00}, + {0x18, 0x01, 0x00, 0x00}, {0x1f, 0x01, 0x00, 0x00}, + {0x29, 0x01, 0x00, 0x00}, {0x38, 0x01, 0x00, 0x01}, + {0x03, 0x01, 0x24, 0x00}, {0x06, 0x01, 0x24, 0x00}, + {0x0a, 0x01, 0x24, 0x00}, {0x0f, 0x01, 0x24, 0x00}, + {0x18, 0x01, 0x24, 0x00}, {0x1f, 0x01, 0x24, 0x00}, + {0x29, 0x01, 0x24, 0x00}, {0x38, 0x01, 0x24, 0x01} + }, + { + {0x03, 0x01, 0x40, 0x00}, {0x06, 0x01, 0x40, 0x00}, + {0x0a, 0x01, 0x40, 0x00}, {0x0f, 0x01, 0x40, 0x00}, + {0x18, 0x01, 0x40, 0x00}, {0x1f, 0x01, 0x40, 0x00}, + {0x29, 0x01, 0x40, 0x00}, {0x38, 0x01, 0x40, 0x01}, + {0x03, 0x01, 0x5b, 0x00}, {0x06, 0x01, 0x5b, 0x00}, + {0x0a, 0x01, 0x5b, 0x00}, {0x0f, 0x01, 0x5b, 0x00}, + {0x18, 0x01, 0x5b, 0x00}, {0x1f, 0x01, 0x5b, 0x00}, + {0x29, 0x01, 0x5b, 0x00}, {0x38, 0x01, 0x5b, 0x01} + }, + { + {0x02, 0x01, 0x5d, 0x00}, {0x09, 0x01, 0x5d, 0x00}, + {0x17, 0x01, 0x5d, 0x00}, {0x28, 0x01, 0x5d, 0x01}, + {0x02, 0x01, 0x7e, 0x00}, {0x09, 0x01, 0x7e, 0x00}, + {0x17, 0x01, 0x7e, 0x00}, {0x28, 0x01, 0x7e, 0x01}, + {0x01, 0x01, 0x5e, 0x00}, {0x16, 0x01, 0x5e, 0x01}, + {0x01, 0x01, 0x7d, 0x00}, {0x16, 0x01, 0x7d, 0x01}, + {0x00, 0x01, 0x3c, 0x01}, {0x00, 0x01, 0x60, 0x01}, + {0x00, 0x01, 0x7b, 0x01}, {0x5f, 0x00, 0x00, 0x01} + }, + { + {0x03, 0x01, 0x5d, 0x00}, {0x06, 0x01, 0x5d, 0x00}, + {0x0a, 0x01, 0x5d, 0x00}, {0x0f, 0x01, 0x5d, 0x00}, + {0x18, 0x01, 0x5d, 0x00}, {0x1f, 0x01, 0x5d, 0x00}, + {0x29, 0x01, 0x5d, 0x00}, {0x38, 0x01, 0x5d, 0x01}, + {0x03, 0x01, 0x7e, 0x00}, {0x06, 0x01, 0x7e, 0x00}, + {0x0a, 0x01, 0x7e, 0x00}, {0x0f, 0x01, 0x7e, 0x00}, + {0x18, 0x01, 0x7e, 0x00}, {0x1f, 0x01, 0x7e, 0x00}, + {0x29, 0x01, 0x7e, 0x00}, {0x38, 0x01, 0x7e, 0x01} + }, + /* 90 */ + { + {0x02, 0x01, 0x5e, 0x00}, {0x09, 0x01, 0x5e, 0x00}, + {0x17, 0x01, 0x5e, 0x00}, {0x28, 0x01, 0x5e, 0x01}, + {0x02, 0x01, 0x7d, 0x00}, {0x09, 0x01, 0x7d, 0x00}, + {0x17, 0x01, 0x7d, 0x00}, {0x28, 0x01, 0x7d, 0x01}, + {0x01, 0x01, 0x3c, 0x00}, {0x16, 0x01, 0x3c, 0x01}, + {0x01, 0x01, 0x60, 0x00}, {0x16, 0x01, 0x60, 0x01}, + {0x01, 0x01, 0x7b, 0x00}, {0x16, 0x01, 0x7b, 0x01}, + {0x60, 0x00, 0x00, 0x00}, {0x6e, 0x00, 0x00, 0x01} + }, + { + {0x03, 0x01, 0x5e, 0x00}, {0x06, 0x01, 0x5e, 0x00}, + {0x0a, 0x01, 0x5e, 0x00}, {0x0f, 0x01, 0x5e, 0x00}, + {0x18, 0x01, 0x5e, 0x00}, {0x1f, 0x01, 0x5e, 0x00}, + {0x29, 0x01, 0x5e, 0x00}, {0x38, 0x01, 0x5e, 0x01}, + {0x03, 0x01, 0x7d, 0x00}, {0x06, 0x01, 0x7d, 0x00}, + {0x0a, 0x01, 0x7d, 0x00}, {0x0f, 0x01, 0x7d, 0x00}, + {0x18, 0x01, 0x7d, 0x00}, {0x1f, 0x01, 0x7d, 0x00}, + {0x29, 0x01, 0x7d, 0x00}, {0x38, 0x01, 0x7d, 0x01} + }, + { + {0x02, 0x01, 0x3c, 0x00}, {0x09, 0x01, 0x3c, 0x00}, + {0x17, 0x01, 0x3c, 0x00}, {0x28, 0x01, 0x3c, 0x01}, + {0x02, 0x01, 0x60, 0x00}, {0x09, 0x01, 0x60, 0x00}, + {0x17, 0x01, 0x60, 0x00}, {0x28, 0x01, 0x60, 0x01}, + {0x02, 0x01, 0x7b, 0x00}, {0x09, 0x01, 0x7b, 0x00}, + {0x17, 0x01, 0x7b, 0x00}, {0x28, 0x01, 0x7b, 0x01}, + {0x61, 0x00, 0x00, 0x00}, {0x65, 0x00, 0x00, 0x00}, + {0x6f, 0x00, 0x00, 0x00}, {0x85, 0x00, 0x00, 0x01} + }, + { + {0x03, 0x01, 0x3c, 0x00}, {0x06, 0x01, 0x3c, 0x00}, + {0x0a, 0x01, 0x3c, 0x00}, {0x0f, 0x01, 0x3c, 0x00}, + {0x18, 0x01, 0x3c, 0x00}, {0x1f, 0x01, 0x3c, 0x00}, + {0x29, 0x01, 0x3c, 0x00}, {0x38, 0x01, 0x3c, 0x01}, + {0x03, 0x01, 0x60, 0x00}, {0x06, 0x01, 0x60, 0x00}, + {0x0a, 0x01, 0x60, 0x00}, {0x0f, 0x01, 0x60, 0x00}, + {0x18, 0x01, 0x60, 0x00}, {0x1f, 0x01, 0x60, 0x00}, + {0x29, 0x01, 0x60, 0x00}, {0x38, 0x01, 0x60, 0x01} + }, + { + {0x03, 0x01, 0x7b, 0x00}, {0x06, 0x01, 0x7b, 0x00}, + {0x0a, 0x01, 0x7b, 0x00}, {0x0f, 0x01, 0x7b, 0x00}, + {0x18, 0x01, 0x7b, 0x00}, {0x1f, 0x01, 0x7b, 0x00}, + {0x29, 0x01, 0x7b, 0x00}, {0x38, 0x01, 0x7b, 0x01}, + {0x62, 0x00, 0x00, 0x00}, {0x63, 0x00, 0x00, 0x00}, + {0x66, 0x00, 0x00, 0x00}, {0x69, 0x00, 0x00, 0x00}, + {0x70, 0x00, 0x00, 0x00}, {0x77, 0x00, 0x00, 0x00}, + {0x86, 0x00, 0x00, 0x00}, {0x99, 0x00, 0x00, 0x01} + }, + /* 95 */ + { + {0x00, 0x01, 0x5c, 0x01}, {0x00, 0x01, 0xc3, 0x01}, + {0x00, 0x01, 0xd0, 0x01}, {0x64, 0x00, 0x00, 0x00}, + {0x67, 0x00, 0x00, 0x00}, {0x68, 0x00, 0x00, 0x00}, + {0x6a, 0x00, 0x00, 0x00}, {0x6b, 0x00, 0x00, 0x00}, + {0x71, 0x00, 0x00, 0x00}, {0x74, 0x00, 0x00, 0x00}, + {0x78, 0x00, 0x00, 0x00}, {0x7e, 0x00, 0x00, 0x00}, + {0x87, 0x00, 0x00, 0x00}, {0x8e, 0x00, 0x00, 0x00}, + {0x9a, 0x00, 0x00, 0x00}, {0xa9, 0x00, 0x00, 0x01} + }, + { + {0x01, 0x01, 0x5c, 0x00}, {0x16, 0x01, 0x5c, 0x01}, + {0x01, 0x01, 0xc3, 0x00}, {0x16, 0x01, 0xc3, 0x01}, + {0x01, 0x01, 0xd0, 0x00}, {0x16, 0x01, 0xd0, 0x01}, + {0x00, 0x01, 0x80, 0x01}, {0x00, 0x01, 0x82, 0x01}, + {0x00, 0x01, 0x83, 0x01}, {0x00, 0x01, 0xa2, 0x01}, + {0x00, 0x01, 0xb8, 0x01}, {0x00, 0x01, 0xc2, 0x01}, + {0x00, 0x01, 0xe0, 0x01}, {0x00, 0x01, 0xe2, 0x01}, + {0x6c, 0x00, 0x00, 0x00}, {0x6d, 0x00, 0x00, 0x00} + }, + { + {0x02, 0x01, 0x5c, 0x00}, {0x09, 0x01, 0x5c, 0x00}, + {0x17, 0x01, 0x5c, 0x00}, {0x28, 0x01, 0x5c, 0x01}, + {0x02, 0x01, 0xc3, 0x00}, {0x09, 0x01, 0xc3, 0x00}, + {0x17, 0x01, 0xc3, 0x00}, {0x28, 0x01, 0xc3, 0x01}, + {0x02, 0x01, 0xd0, 0x00}, {0x09, 0x01, 0xd0, 0x00}, + {0x17, 0x01, 0xd0, 0x00}, {0x28, 0x01, 0xd0, 0x01}, + {0x01, 0x01, 0x80, 0x00}, {0x16, 0x01, 0x80, 0x01}, + {0x01, 0x01, 0x82, 0x00}, {0x16, 0x01, 0x82, 0x01} + }, + { + {0x03, 0x01, 0x5c, 0x00}, {0x06, 0x01, 0x5c, 0x00}, + {0x0a, 0x01, 0x5c, 0x00}, {0x0f, 0x01, 0x5c, 0x00}, + {0x18, 0x01, 0x5c, 0x00}, {0x1f, 0x01, 0x5c, 0x00}, + {0x29, 0x01, 0x5c, 0x00}, {0x38, 0x01, 0x5c, 0x01}, + {0x03, 0x01, 0xc3, 0x00}, {0x06, 0x01, 0xc3, 0x00}, + {0x0a, 0x01, 0xc3, 0x00}, {0x0f, 0x01, 0xc3, 0x00}, + {0x18, 0x01, 0xc3, 0x00}, {0x1f, 0x01, 0xc3, 0x00}, + {0x29, 0x01, 0xc3, 0x00}, {0x38, 0x01, 0xc3, 0x01} + }, + { + {0x03, 0x01, 0xd0, 0x00}, {0x06, 0x01, 0xd0, 0x00}, + {0x0a, 0x01, 0xd0, 0x00}, {0x0f, 0x01, 0xd0, 0x00}, + {0x18, 0x01, 0xd0, 0x00}, {0x1f, 0x01, 0xd0, 0x00}, + {0x29, 0x01, 0xd0, 0x00}, {0x38, 0x01, 0xd0, 0x01}, + {0x02, 0x01, 0x80, 0x00}, {0x09, 0x01, 0x80, 0x00}, + {0x17, 0x01, 0x80, 0x00}, {0x28, 0x01, 0x80, 0x01}, + {0x02, 0x01, 0x82, 0x00}, {0x09, 0x01, 0x82, 0x00}, + {0x17, 0x01, 0x82, 0x00}, {0x28, 0x01, 0x82, 0x01} + }, + /* 100 */ + { + {0x03, 0x01, 0x80, 0x00}, {0x06, 0x01, 0x80, 0x00}, + {0x0a, 0x01, 0x80, 0x00}, {0x0f, 0x01, 0x80, 0x00}, + {0x18, 0x01, 0x80, 0x00}, {0x1f, 0x01, 0x80, 0x00}, + {0x29, 0x01, 0x80, 0x00}, {0x38, 0x01, 0x80, 0x01}, + {0x03, 0x01, 0x82, 0x00}, {0x06, 0x01, 0x82, 0x00}, + {0x0a, 0x01, 0x82, 0x00}, {0x0f, 0x01, 0x82, 0x00}, + {0x18, 0x01, 0x82, 0x00}, {0x1f, 0x01, 0x82, 0x00}, + {0x29, 0x01, 0x82, 0x00}, {0x38, 0x01, 0x82, 0x01} + }, + { + {0x01, 0x01, 0x83, 0x00}, {0x16, 0x01, 0x83, 0x01}, + {0x01, 0x01, 0xa2, 0x00}, {0x16, 0x01, 0xa2, 0x01}, + {0x01, 0x01, 0xb8, 0x00}, {0x16, 0x01, 0xb8, 0x01}, + {0x01, 0x01, 0xc2, 0x00}, {0x16, 0x01, 0xc2, 0x01}, + {0x01, 0x01, 0xe0, 0x00}, {0x16, 0x01, 0xe0, 0x01}, + {0x01, 0x01, 0xe2, 0x00}, {0x16, 0x01, 0xe2, 0x01}, + {0x00, 0x01, 0x99, 0x01}, {0x00, 0x01, 0xa1, 0x01}, + {0x00, 0x01, 0xa7, 0x01}, {0x00, 0x01, 0xac, 0x01} + }, + { + {0x02, 0x01, 0x83, 0x00}, {0x09, 0x01, 0x83, 0x00}, + {0x17, 0x01, 0x83, 0x00}, {0x28, 0x01, 0x83, 0x01}, + {0x02, 0x01, 0xa2, 0x00}, {0x09, 0x01, 0xa2, 0x00}, + {0x17, 0x01, 0xa2, 0x00}, {0x28, 0x01, 0xa2, 0x01}, + {0x02, 0x01, 0xb8, 0x00}, {0x09, 0x01, 0xb8, 0x00}, + {0x17, 0x01, 0xb8, 0x00}, {0x28, 0x01, 0xb8, 0x01}, + {0x02, 0x01, 0xc2, 0x00}, {0x09, 0x01, 0xc2, 0x00}, + {0x17, 0x01, 0xc2, 0x00}, {0x28, 0x01, 0xc2, 0x01} + }, + { + {0x03, 0x01, 0x83, 0x00}, {0x06, 0x01, 0x83, 0x00}, + {0x0a, 0x01, 0x83, 0x00}, {0x0f, 0x01, 0x83, 0x00}, + {0x18, 0x01, 0x83, 0x00}, {0x1f, 0x01, 0x83, 0x00}, + {0x29, 0x01, 0x83, 0x00}, {0x38, 0x01, 0x83, 0x01}, + {0x03, 0x01, 0xa2, 0x00}, {0x06, 0x01, 0xa2, 0x00}, + {0x0a, 0x01, 0xa2, 0x00}, {0x0f, 0x01, 0xa2, 0x00}, + {0x18, 0x01, 0xa2, 0x00}, {0x1f, 0x01, 0xa2, 0x00}, + {0x29, 0x01, 0xa2, 0x00}, {0x38, 0x01, 0xa2, 0x01} + }, + { + {0x03, 0x01, 0xb8, 0x00}, {0x06, 0x01, 0xb8, 0x00}, + {0x0a, 0x01, 0xb8, 0x00}, {0x0f, 0x01, 0xb8, 0x00}, + {0x18, 0x01, 0xb8, 0x00}, {0x1f, 0x01, 0xb8, 0x00}, + {0x29, 0x01, 0xb8, 0x00}, {0x38, 0x01, 0xb8, 0x01}, + {0x03, 0x01, 0xc2, 0x00}, {0x06, 0x01, 0xc2, 0x00}, + {0x0a, 0x01, 0xc2, 0x00}, {0x0f, 0x01, 0xc2, 0x00}, + {0x18, 0x01, 0xc2, 0x00}, {0x1f, 0x01, 0xc2, 0x00}, + {0x29, 0x01, 0xc2, 0x00}, {0x38, 0x01, 0xc2, 0x01} + }, + /* 105 */ + { + {0x02, 0x01, 0xe0, 0x00}, {0x09, 0x01, 0xe0, 0x00}, + {0x17, 0x01, 0xe0, 0x00}, {0x28, 0x01, 0xe0, 0x01}, + {0x02, 0x01, 0xe2, 0x00}, {0x09, 0x01, 0xe2, 0x00}, + {0x17, 0x01, 0xe2, 0x00}, {0x28, 0x01, 0xe2, 0x01}, + {0x01, 0x01, 0x99, 0x00}, {0x16, 0x01, 0x99, 0x01}, + {0x01, 0x01, 0xa1, 0x00}, {0x16, 0x01, 0xa1, 0x01}, + {0x01, 0x01, 0xa7, 0x00}, {0x16, 0x01, 0xa7, 0x01}, + {0x01, 0x01, 0xac, 0x00}, {0x16, 0x01, 0xac, 0x01} + }, + { + {0x03, 0x01, 0xe0, 0x00}, {0x06, 0x01, 0xe0, 0x00}, + {0x0a, 0x01, 0xe0, 0x00}, {0x0f, 0x01, 0xe0, 0x00}, + {0x18, 0x01, 0xe0, 0x00}, {0x1f, 0x01, 0xe0, 0x00}, + {0x29, 0x01, 0xe0, 0x00}, {0x38, 0x01, 0xe0, 0x01}, + {0x03, 0x01, 0xe2, 0x00}, {0x06, 0x01, 0xe2, 0x00}, + {0x0a, 0x01, 0xe2, 0x00}, {0x0f, 0x01, 0xe2, 0x00}, + {0x18, 0x01, 0xe2, 0x00}, {0x1f, 0x01, 0xe2, 0x00}, + {0x29, 0x01, 0xe2, 0x00}, {0x38, 0x01, 0xe2, 0x01} + }, + { + {0x02, 0x01, 0x99, 0x00}, {0x09, 0x01, 0x99, 0x00}, + {0x17, 0x01, 0x99, 0x00}, {0x28, 0x01, 0x99, 0x01}, + {0x02, 0x01, 0xa1, 0x00}, {0x09, 0x01, 0xa1, 0x00}, + {0x17, 0x01, 0xa1, 0x00}, {0x28, 0x01, 0xa1, 0x01}, + {0x02, 0x01, 0xa7, 0x00}, {0x09, 0x01, 0xa7, 0x00}, + {0x17, 0x01, 0xa7, 0x00}, {0x28, 0x01, 0xa7, 0x01}, + {0x02, 0x01, 0xac, 0x00}, {0x09, 0x01, 0xac, 0x00}, + {0x17, 0x01, 0xac, 0x00}, {0x28, 0x01, 0xac, 0x01} + }, + { + {0x03, 0x01, 0x99, 0x00}, {0x06, 0x01, 0x99, 0x00}, + {0x0a, 0x01, 0x99, 0x00}, {0x0f, 0x01, 0x99, 0x00}, + {0x18, 0x01, 0x99, 0x00}, {0x1f, 0x01, 0x99, 0x00}, + {0x29, 0x01, 0x99, 0x00}, {0x38, 0x01, 0x99, 0x01}, + {0x03, 0x01, 0xa1, 0x00}, {0x06, 0x01, 0xa1, 0x00}, + {0x0a, 0x01, 0xa1, 0x00}, {0x0f, 0x01, 0xa1, 0x00}, + {0x18, 0x01, 0xa1, 0x00}, {0x1f, 0x01, 0xa1, 0x00}, + {0x29, 0x01, 0xa1, 0x00}, {0x38, 0x01, 0xa1, 0x01} + }, + { + {0x03, 0x01, 0xa7, 0x00}, {0x06, 0x01, 0xa7, 0x00}, + {0x0a, 0x01, 0xa7, 0x00}, {0x0f, 0x01, 0xa7, 0x00}, + {0x18, 0x01, 0xa7, 0x00}, {0x1f, 0x01, 0xa7, 0x00}, + {0x29, 0x01, 0xa7, 0x00}, {0x38, 0x01, 0xa7, 0x01}, + {0x03, 0x01, 0xac, 0x00}, {0x06, 0x01, 0xac, 0x00}, + {0x0a, 0x01, 0xac, 0x00}, {0x0f, 0x01, 0xac, 0x00}, + {0x18, 0x01, 0xac, 0x00}, {0x1f, 0x01, 0xac, 0x00}, + {0x29, 0x01, 0xac, 0x00}, {0x38, 0x01, 0xac, 0x01} + }, + /* 110 */ + { + {0x72, 0x00, 0x00, 0x00}, {0x73, 0x00, 0x00, 0x00}, + {0x75, 0x00, 0x00, 0x00}, {0x76, 0x00, 0x00, 0x00}, + {0x79, 0x00, 0x00, 0x00}, {0x7b, 0x00, 0x00, 0x00}, + {0x7f, 0x00, 0x00, 0x00}, {0x82, 0x00, 0x00, 0x00}, + {0x88, 0x00, 0x00, 0x00}, {0x8b, 0x00, 0x00, 0x00}, + {0x8f, 0x00, 0x00, 0x00}, {0x92, 0x00, 0x00, 0x00}, + {0x9b, 0x00, 0x00, 0x00}, {0xa2, 0x00, 0x00, 0x00}, + {0xaa, 0x00, 0x00, 0x00}, {0xb4, 0x00, 0x00, 0x01} + }, + { + {0x00, 0x01, 0xb0, 0x01}, {0x00, 0x01, 0xb1, 0x01}, + {0x00, 0x01, 0xb3, 0x01}, {0x00, 0x01, 0xd1, 0x01}, + {0x00, 0x01, 0xd8, 0x01}, {0x00, 0x01, 0xd9, 0x01}, + {0x00, 0x01, 0xe3, 0x01}, {0x00, 0x01, 0xe5, 0x01}, + {0x00, 0x01, 0xe6, 0x01}, {0x7a, 0x00, 0x00, 0x00}, + {0x7c, 0x00, 0x00, 0x00}, {0x7d, 0x00, 0x00, 0x00}, + {0x80, 0x00, 0x00, 0x00}, {0x81, 0x00, 0x00, 0x00}, + {0x83, 0x00, 0x00, 0x00}, {0x84, 0x00, 0x00, 0x00} + }, + { + {0x01, 0x01, 0xb0, 0x00}, {0x16, 0x01, 0xb0, 0x01}, + {0x01, 0x01, 0xb1, 0x00}, {0x16, 0x01, 0xb1, 0x01}, + {0x01, 0x01, 0xb3, 0x00}, {0x16, 0x01, 0xb3, 0x01}, + {0x01, 0x01, 0xd1, 0x00}, {0x16, 0x01, 0xd1, 0x01}, + {0x01, 0x01, 0xd8, 0x00}, {0x16, 0x01, 0xd8, 0x01}, + {0x01, 0x01, 0xd9, 0x00}, {0x16, 0x01, 0xd9, 0x01}, + {0x01, 0x01, 0xe3, 0x00}, {0x16, 0x01, 0xe3, 0x01}, + {0x01, 0x01, 0xe5, 0x00}, {0x16, 0x01, 0xe5, 0x01} + }, + { + {0x02, 0x01, 0xb0, 0x00}, {0x09, 0x01, 0xb0, 0x00}, + {0x17, 0x01, 0xb0, 0x00}, {0x28, 0x01, 0xb0, 0x01}, + {0x02, 0x01, 0xb1, 0x00}, {0x09, 0x01, 0xb1, 0x00}, + {0x17, 0x01, 0xb1, 0x00}, {0x28, 0x01, 0xb1, 0x01}, + {0x02, 0x01, 0xb3, 0x00}, {0x09, 0x01, 0xb3, 0x00}, + {0x17, 0x01, 0xb3, 0x00}, {0x28, 0x01, 0xb3, 0x01}, + {0x02, 0x01, 0xd1, 0x00}, {0x09, 0x01, 0xd1, 0x00}, + {0x17, 0x01, 0xd1, 0x00}, {0x28, 0x01, 0xd1, 0x01} + }, + { + {0x03, 0x01, 0xb0, 0x00}, {0x06, 0x01, 0xb0, 0x00}, + {0x0a, 0x01, 0xb0, 0x00}, {0x0f, 0x01, 0xb0, 0x00}, + {0x18, 0x01, 0xb0, 0x00}, {0x1f, 0x01, 0xb0, 0x00}, + {0x29, 0x01, 0xb0, 0x00}, {0x38, 0x01, 0xb0, 0x01}, + {0x03, 0x01, 0xb1, 0x00}, {0x06, 0x01, 0xb1, 0x00}, + {0x0a, 0x01, 0xb1, 0x00}, {0x0f, 0x01, 0xb1, 0x00}, + {0x18, 0x01, 0xb1, 0x00}, {0x1f, 0x01, 0xb1, 0x00}, + {0x29, 0x01, 0xb1, 0x00}, {0x38, 0x01, 0xb1, 0x01} + }, + /* 115 */ + { + {0x03, 0x01, 0xb3, 0x00}, {0x06, 0x01, 0xb3, 0x00}, + {0x0a, 0x01, 0xb3, 0x00}, {0x0f, 0x01, 0xb3, 0x00}, + {0x18, 0x01, 0xb3, 0x00}, {0x1f, 0x01, 0xb3, 0x00}, + {0x29, 0x01, 0xb3, 0x00}, {0x38, 0x01, 0xb3, 0x01}, + {0x03, 0x01, 0xd1, 0x00}, {0x06, 0x01, 0xd1, 0x00}, + {0x0a, 0x01, 0xd1, 0x00}, {0x0f, 0x01, 0xd1, 0x00}, + {0x18, 0x01, 0xd1, 0x00}, {0x1f, 0x01, 0xd1, 0x00}, + {0x29, 0x01, 0xd1, 0x00}, {0x38, 0x01, 0xd1, 0x01} + }, + { + {0x02, 0x01, 0xd8, 0x00}, {0x09, 0x01, 0xd8, 0x00}, + {0x17, 0x01, 0xd8, 0x00}, {0x28, 0x01, 0xd8, 0x01}, + {0x02, 0x01, 0xd9, 0x00}, {0x09, 0x01, 0xd9, 0x00}, + {0x17, 0x01, 0xd9, 0x00}, {0x28, 0x01, 0xd9, 0x01}, + {0x02, 0x01, 0xe3, 0x00}, {0x09, 0x01, 0xe3, 0x00}, + {0x17, 0x01, 0xe3, 0x00}, {0x28, 0x01, 0xe3, 0x01}, + {0x02, 0x01, 0xe5, 0x00}, {0x09, 0x01, 0xe5, 0x00}, + {0x17, 0x01, 0xe5, 0x00}, {0x28, 0x01, 0xe5, 0x01} + }, + { + {0x03, 0x01, 0xd8, 0x00}, {0x06, 0x01, 0xd8, 0x00}, + {0x0a, 0x01, 0xd8, 0x00}, {0x0f, 0x01, 0xd8, 0x00}, + {0x18, 0x01, 0xd8, 0x00}, {0x1f, 0x01, 0xd8, 0x00}, + {0x29, 0x01, 0xd8, 0x00}, {0x38, 0x01, 0xd8, 0x01}, + {0x03, 0x01, 0xd9, 0x00}, {0x06, 0x01, 0xd9, 0x00}, + {0x0a, 0x01, 0xd9, 0x00}, {0x0f, 0x01, 0xd9, 0x00}, + {0x18, 0x01, 0xd9, 0x00}, {0x1f, 0x01, 0xd9, 0x00}, + {0x29, 0x01, 0xd9, 0x00}, {0x38, 0x01, 0xd9, 0x01} + }, + { + {0x03, 0x01, 0xe3, 0x00}, {0x06, 0x01, 0xe3, 0x00}, + {0x0a, 0x01, 0xe3, 0x00}, {0x0f, 0x01, 0xe3, 0x00}, + {0x18, 0x01, 0xe3, 0x00}, {0x1f, 0x01, 0xe3, 0x00}, + {0x29, 0x01, 0xe3, 0x00}, {0x38, 0x01, 0xe3, 0x01}, + {0x03, 0x01, 0xe5, 0x00}, {0x06, 0x01, 0xe5, 0x00}, + {0x0a, 0x01, 0xe5, 0x00}, {0x0f, 0x01, 0xe5, 0x00}, + {0x18, 0x01, 0xe5, 0x00}, {0x1f, 0x01, 0xe5, 0x00}, + {0x29, 0x01, 0xe5, 0x00}, {0x38, 0x01, 0xe5, 0x01} + }, + { + {0x01, 0x01, 0xe6, 0x00}, {0x16, 0x01, 0xe6, 0x01}, + {0x00, 0x01, 0x81, 0x01}, {0x00, 0x01, 0x84, 0x01}, + {0x00, 0x01, 0x85, 0x01}, {0x00, 0x01, 0x86, 0x01}, + {0x00, 0x01, 0x88, 0x01}, {0x00, 0x01, 0x92, 0x01}, + {0x00, 0x01, 0x9a, 0x01}, {0x00, 0x01, 0x9c, 0x01}, + {0x00, 0x01, 0xa0, 0x01}, {0x00, 0x01, 0xa3, 0x01}, + {0x00, 0x01, 0xa4, 0x01}, {0x00, 0x01, 0xa9, 0x01}, + {0x00, 0x01, 0xaa, 0x01}, {0x00, 0x01, 0xad, 0x01} + }, + /* 120 */ + { + {0x02, 0x01, 0xe6, 0x00}, {0x09, 0x01, 0xe6, 0x00}, + {0x17, 0x01, 0xe6, 0x00}, {0x28, 0x01, 0xe6, 0x01}, + {0x01, 0x01, 0x81, 0x00}, {0x16, 0x01, 0x81, 0x01}, + {0x01, 0x01, 0x84, 0x00}, {0x16, 0x01, 0x84, 0x01}, + {0x01, 0x01, 0x85, 0x00}, {0x16, 0x01, 0x85, 0x01}, + {0x01, 0x01, 0x86, 0x00}, {0x16, 0x01, 0x86, 0x01}, + {0x01, 0x01, 0x88, 0x00}, {0x16, 0x01, 0x88, 0x01}, + {0x01, 0x01, 0x92, 0x00}, {0x16, 0x01, 0x92, 0x01} + }, + { + {0x03, 0x01, 0xe6, 0x00}, {0x06, 0x01, 0xe6, 0x00}, + {0x0a, 0x01, 0xe6, 0x00}, {0x0f, 0x01, 0xe6, 0x00}, + {0x18, 0x01, 0xe6, 0x00}, {0x1f, 0x01, 0xe6, 0x00}, + {0x29, 0x01, 0xe6, 0x00}, {0x38, 0x01, 0xe6, 0x01}, + {0x02, 0x01, 0x81, 0x00}, {0x09, 0x01, 0x81, 0x00}, + {0x17, 0x01, 0x81, 0x00}, {0x28, 0x01, 0x81, 0x01}, + {0x02, 0x01, 0x84, 0x00}, {0x09, 0x01, 0x84, 0x00}, + {0x17, 0x01, 0x84, 0x00}, {0x28, 0x01, 0x84, 0x01} + }, + { + {0x03, 0x01, 0x81, 0x00}, {0x06, 0x01, 0x81, 0x00}, + {0x0a, 0x01, 0x81, 0x00}, {0x0f, 0x01, 0x81, 0x00}, + {0x18, 0x01, 0x81, 0x00}, {0x1f, 0x01, 0x81, 0x00}, + {0x29, 0x01, 0x81, 0x00}, {0x38, 0x01, 0x81, 0x01}, + {0x03, 0x01, 0x84, 0x00}, {0x06, 0x01, 0x84, 0x00}, + {0x0a, 0x01, 0x84, 0x00}, {0x0f, 0x01, 0x84, 0x00}, + {0x18, 0x01, 0x84, 0x00}, {0x1f, 0x01, 0x84, 0x00}, + {0x29, 0x01, 0x84, 0x00}, {0x38, 0x01, 0x84, 0x01} + }, + { + {0x02, 0x01, 0x85, 0x00}, {0x09, 0x01, 0x85, 0x00}, + {0x17, 0x01, 0x85, 0x00}, {0x28, 0x01, 0x85, 0x01}, + {0x02, 0x01, 0x86, 0x00}, {0x09, 0x01, 0x86, 0x00}, + {0x17, 0x01, 0x86, 0x00}, {0x28, 0x01, 0x86, 0x01}, + {0x02, 0x01, 0x88, 0x00}, {0x09, 0x01, 0x88, 0x00}, + {0x17, 0x01, 0x88, 0x00}, {0x28, 0x01, 0x88, 0x01}, + {0x02, 0x01, 0x92, 0x00}, {0x09, 0x01, 0x92, 0x00}, + {0x17, 0x01, 0x92, 0x00}, {0x28, 0x01, 0x92, 0x01} + }, + { + {0x03, 0x01, 0x85, 0x00}, {0x06, 0x01, 0x85, 0x00}, + {0x0a, 0x01, 0x85, 0x00}, {0x0f, 0x01, 0x85, 0x00}, + {0x18, 0x01, 0x85, 0x00}, {0x1f, 0x01, 0x85, 0x00}, + {0x29, 0x01, 0x85, 0x00}, {0x38, 0x01, 0x85, 0x01}, + {0x03, 0x01, 0x86, 0x00}, {0x06, 0x01, 0x86, 0x00}, + {0x0a, 0x01, 0x86, 0x00}, {0x0f, 0x01, 0x86, 0x00}, + {0x18, 0x01, 0x86, 0x00}, {0x1f, 0x01, 0x86, 0x00}, + {0x29, 0x01, 0x86, 0x00}, {0x38, 0x01, 0x86, 0x01} + }, + /* 125 */ + { + {0x03, 0x01, 0x88, 0x00}, {0x06, 0x01, 0x88, 0x00}, + {0x0a, 0x01, 0x88, 0x00}, {0x0f, 0x01, 0x88, 0x00}, + {0x18, 0x01, 0x88, 0x00}, {0x1f, 0x01, 0x88, 0x00}, + {0x29, 0x01, 0x88, 0x00}, {0x38, 0x01, 0x88, 0x01}, + {0x03, 0x01, 0x92, 0x00}, {0x06, 0x01, 0x92, 0x00}, + {0x0a, 0x01, 0x92, 0x00}, {0x0f, 0x01, 0x92, 0x00}, + {0x18, 0x01, 0x92, 0x00}, {0x1f, 0x01, 0x92, 0x00}, + {0x29, 0x01, 0x92, 0x00}, {0x38, 0x01, 0x92, 0x01} + }, + { + {0x01, 0x01, 0x9a, 0x00}, {0x16, 0x01, 0x9a, 0x01}, + {0x01, 0x01, 0x9c, 0x00}, {0x16, 0x01, 0x9c, 0x01}, + {0x01, 0x01, 0xa0, 0x00}, {0x16, 0x01, 0xa0, 0x01}, + {0x01, 0x01, 0xa3, 0x00}, {0x16, 0x01, 0xa3, 0x01}, + {0x01, 0x01, 0xa4, 0x00}, {0x16, 0x01, 0xa4, 0x01}, + {0x01, 0x01, 0xa9, 0x00}, {0x16, 0x01, 0xa9, 0x01}, + {0x01, 0x01, 0xaa, 0x00}, {0x16, 0x01, 0xaa, 0x01}, + {0x01, 0x01, 0xad, 0x00}, {0x16, 0x01, 0xad, 0x01} + }, + { + {0x02, 0x01, 0x9a, 0x00}, {0x09, 0x01, 0x9a, 0x00}, + {0x17, 0x01, 0x9a, 0x00}, {0x28, 0x01, 0x9a, 0x01}, + {0x02, 0x01, 0x9c, 0x00}, {0x09, 0x01, 0x9c, 0x00}, + {0x17, 0x01, 0x9c, 0x00}, {0x28, 0x01, 0x9c, 0x01}, + {0x02, 0x01, 0xa0, 0x00}, {0x09, 0x01, 0xa0, 0x00}, + {0x17, 0x01, 0xa0, 0x00}, {0x28, 0x01, 0xa0, 0x01}, + {0x02, 0x01, 0xa3, 0x00}, {0x09, 0x01, 0xa3, 0x00}, + {0x17, 0x01, 0xa3, 0x00}, {0x28, 0x01, 0xa3, 0x01} + }, + { + {0x03, 0x01, 0x9a, 0x00}, {0x06, 0x01, 0x9a, 0x00}, + {0x0a, 0x01, 0x9a, 0x00}, {0x0f, 0x01, 0x9a, 0x00}, + {0x18, 0x01, 0x9a, 0x00}, {0x1f, 0x01, 0x9a, 0x00}, + {0x29, 0x01, 0x9a, 0x00}, {0x38, 0x01, 0x9a, 0x01}, + {0x03, 0x01, 0x9c, 0x00}, {0x06, 0x01, 0x9c, 0x00}, + {0x0a, 0x01, 0x9c, 0x00}, {0x0f, 0x01, 0x9c, 0x00}, + {0x18, 0x01, 0x9c, 0x00}, {0x1f, 0x01, 0x9c, 0x00}, + {0x29, 0x01, 0x9c, 0x00}, {0x38, 0x01, 0x9c, 0x01} + }, + { + {0x03, 0x01, 0xa0, 0x00}, {0x06, 0x01, 0xa0, 0x00}, + {0x0a, 0x01, 0xa0, 0x00}, {0x0f, 0x01, 0xa0, 0x00}, + {0x18, 0x01, 0xa0, 0x00}, {0x1f, 0x01, 0xa0, 0x00}, + {0x29, 0x01, 0xa0, 0x00}, {0x38, 0x01, 0xa0, 0x01}, + {0x03, 0x01, 0xa3, 0x00}, {0x06, 0x01, 0xa3, 0x00}, + {0x0a, 0x01, 0xa3, 0x00}, {0x0f, 0x01, 0xa3, 0x00}, + {0x18, 0x01, 0xa3, 0x00}, {0x1f, 0x01, 0xa3, 0x00}, + {0x29, 0x01, 0xa3, 0x00}, {0x38, 0x01, 0xa3, 0x01} + }, + /* 130 */ + { + {0x02, 0x01, 0xa4, 0x00}, {0x09, 0x01, 0xa4, 0x00}, + {0x17, 0x01, 0xa4, 0x00}, {0x28, 0x01, 0xa4, 0x01}, + {0x02, 0x01, 0xa9, 0x00}, {0x09, 0x01, 0xa9, 0x00}, + {0x17, 0x01, 0xa9, 0x00}, {0x28, 0x01, 0xa9, 0x01}, + {0x02, 0x01, 0xaa, 0x00}, {0x09, 0x01, 0xaa, 0x00}, + {0x17, 0x01, 0xaa, 0x00}, {0x28, 0x01, 0xaa, 0x01}, + {0x02, 0x01, 0xad, 0x00}, {0x09, 0x01, 0xad, 0x00}, + {0x17, 0x01, 0xad, 0x00}, {0x28, 0x01, 0xad, 0x01} + }, + { + {0x03, 0x01, 0xa4, 0x00}, {0x06, 0x01, 0xa4, 0x00}, + {0x0a, 0x01, 0xa4, 0x00}, {0x0f, 0x01, 0xa4, 0x00}, + {0x18, 0x01, 0xa4, 0x00}, {0x1f, 0x01, 0xa4, 0x00}, + {0x29, 0x01, 0xa4, 0x00}, {0x38, 0x01, 0xa4, 0x01}, + {0x03, 0x01, 0xa9, 0x00}, {0x06, 0x01, 0xa9, 0x00}, + {0x0a, 0x01, 0xa9, 0x00}, {0x0f, 0x01, 0xa9, 0x00}, + {0x18, 0x01, 0xa9, 0x00}, {0x1f, 0x01, 0xa9, 0x00}, + {0x29, 0x01, 0xa9, 0x00}, {0x38, 0x01, 0xa9, 0x01} + }, + { + {0x03, 0x01, 0xaa, 0x00}, {0x06, 0x01, 0xaa, 0x00}, + {0x0a, 0x01, 0xaa, 0x00}, {0x0f, 0x01, 0xaa, 0x00}, + {0x18, 0x01, 0xaa, 0x00}, {0x1f, 0x01, 0xaa, 0x00}, + {0x29, 0x01, 0xaa, 0x00}, {0x38, 0x01, 0xaa, 0x01}, + {0x03, 0x01, 0xad, 0x00}, {0x06, 0x01, 0xad, 0x00}, + {0x0a, 0x01, 0xad, 0x00}, {0x0f, 0x01, 0xad, 0x00}, + {0x18, 0x01, 0xad, 0x00}, {0x1f, 0x01, 0xad, 0x00}, + {0x29, 0x01, 0xad, 0x00}, {0x38, 0x01, 0xad, 0x01} + }, + { + {0x89, 0x00, 0x00, 0x00}, {0x8a, 0x00, 0x00, 0x00}, + {0x8c, 0x00, 0x00, 0x00}, {0x8d, 0x00, 0x00, 0x00}, + {0x90, 0x00, 0x00, 0x00}, {0x91, 0x00, 0x00, 0x00}, + {0x93, 0x00, 0x00, 0x00}, {0x96, 0x00, 0x00, 0x00}, + {0x9c, 0x00, 0x00, 0x00}, {0x9f, 0x00, 0x00, 0x00}, + {0xa3, 0x00, 0x00, 0x00}, {0xa6, 0x00, 0x00, 0x00}, + {0xab, 0x00, 0x00, 0x00}, {0xae, 0x00, 0x00, 0x00}, + {0xb5, 0x00, 0x00, 0x00}, {0xbe, 0x00, 0x00, 0x01} + }, + { + {0x00, 0x01, 0xb2, 0x01}, {0x00, 0x01, 0xb5, 0x01}, + {0x00, 0x01, 0xb9, 0x01}, {0x00, 0x01, 0xba, 0x01}, + {0x00, 0x01, 0xbb, 0x01}, {0x00, 0x01, 0xbd, 0x01}, + {0x00, 0x01, 0xbe, 0x01}, {0x00, 0x01, 0xc4, 0x01}, + {0x00, 0x01, 0xc6, 0x01}, {0x00, 0x01, 0xe4, 0x01}, + {0x00, 0x01, 0xe8, 0x01}, {0x00, 0x01, 0xe9, 0x01}, + {0x94, 0x00, 0x00, 0x00}, {0x95, 0x00, 0x00, 0x00}, + {0x97, 0x00, 0x00, 0x00}, {0x98, 0x00, 0x00, 0x00} + }, + /* 135 */ + { + {0x01, 0x01, 0xb2, 0x00}, {0x16, 0x01, 0xb2, 0x01}, + {0x01, 0x01, 0xb5, 0x00}, {0x16, 0x01, 0xb5, 0x01}, + {0x01, 0x01, 0xb9, 0x00}, {0x16, 0x01, 0xb9, 0x01}, + {0x01, 0x01, 0xba, 0x00}, {0x16, 0x01, 0xba, 0x01}, + {0x01, 0x01, 0xbb, 0x00}, {0x16, 0x01, 0xbb, 0x01}, + {0x01, 0x01, 0xbd, 0x00}, {0x16, 0x01, 0xbd, 0x01}, + {0x01, 0x01, 0xbe, 0x00}, {0x16, 0x01, 0xbe, 0x01}, + {0x01, 0x01, 0xc4, 0x00}, {0x16, 0x01, 0xc4, 0x01} + }, + { + {0x02, 0x01, 0xb2, 0x00}, {0x09, 0x01, 0xb2, 0x00}, + {0x17, 0x01, 0xb2, 0x00}, {0x28, 0x01, 0xb2, 0x01}, + {0x02, 0x01, 0xb5, 0x00}, {0x09, 0x01, 0xb5, 0x00}, + {0x17, 0x01, 0xb5, 0x00}, {0x28, 0x01, 0xb5, 0x01}, + {0x02, 0x01, 0xb9, 0x00}, {0x09, 0x01, 0xb9, 0x00}, + {0x17, 0x01, 0xb9, 0x00}, {0x28, 0x01, 0xb9, 0x01}, + {0x02, 0x01, 0xba, 0x00}, {0x09, 0x01, 0xba, 0x00}, + {0x17, 0x01, 0xba, 0x00}, {0x28, 0x01, 0xba, 0x01} + }, + { + {0x03, 0x01, 0xb2, 0x00}, {0x06, 0x01, 0xb2, 0x00}, + {0x0a, 0x01, 0xb2, 0x00}, {0x0f, 0x01, 0xb2, 0x00}, + {0x18, 0x01, 0xb2, 0x00}, {0x1f, 0x01, 0xb2, 0x00}, + {0x29, 0x01, 0xb2, 0x00}, {0x38, 0x01, 0xb2, 0x01}, + {0x03, 0x01, 0xb5, 0x00}, {0x06, 0x01, 0xb5, 0x00}, + {0x0a, 0x01, 0xb5, 0x00}, {0x0f, 0x01, 0xb5, 0x00}, + {0x18, 0x01, 0xb5, 0x00}, {0x1f, 0x01, 0xb5, 0x00}, + {0x29, 0x01, 0xb5, 0x00}, {0x38, 0x01, 0xb5, 0x01} + }, + { + {0x03, 0x01, 0xb9, 0x00}, {0x06, 0x01, 0xb9, 0x00}, + {0x0a, 0x01, 0xb9, 0x00}, {0x0f, 0x01, 0xb9, 0x00}, + {0x18, 0x01, 0xb9, 0x00}, {0x1f, 0x01, 0xb9, 0x00}, + {0x29, 0x01, 0xb9, 0x00}, {0x38, 0x01, 0xb9, 0x01}, + {0x03, 0x01, 0xba, 0x00}, {0x06, 0x01, 0xba, 0x00}, + {0x0a, 0x01, 0xba, 0x00}, {0x0f, 0x01, 0xba, 0x00}, + {0x18, 0x01, 0xba, 0x00}, {0x1f, 0x01, 0xba, 0x00}, + {0x29, 0x01, 0xba, 0x00}, {0x38, 0x01, 0xba, 0x01} + }, + { + {0x02, 0x01, 0xbb, 0x00}, {0x09, 0x01, 0xbb, 0x00}, + {0x17, 0x01, 0xbb, 0x00}, {0x28, 0x01, 0xbb, 0x01}, + {0x02, 0x01, 0xbd, 0x00}, {0x09, 0x01, 0xbd, 0x00}, + {0x17, 0x01, 0xbd, 0x00}, {0x28, 0x01, 0xbd, 0x01}, + {0x02, 0x01, 0xbe, 0x00}, {0x09, 0x01, 0xbe, 0x00}, + {0x17, 0x01, 0xbe, 0x00}, {0x28, 0x01, 0xbe, 0x01}, + {0x02, 0x01, 0xc4, 0x00}, {0x09, 0x01, 0xc4, 0x00}, + {0x17, 0x01, 0xc4, 0x00}, {0x28, 0x01, 0xc4, 0x01} + }, + /* 140 */ + { + {0x03, 0x01, 0xbb, 0x00}, {0x06, 0x01, 0xbb, 0x00}, + {0x0a, 0x01, 0xbb, 0x00}, {0x0f, 0x01, 0xbb, 0x00}, + {0x18, 0x01, 0xbb, 0x00}, {0x1f, 0x01, 0xbb, 0x00}, + {0x29, 0x01, 0xbb, 0x00}, {0x38, 0x01, 0xbb, 0x01}, + {0x03, 0x01, 0xbd, 0x00}, {0x06, 0x01, 0xbd, 0x00}, + {0x0a, 0x01, 0xbd, 0x00}, {0x0f, 0x01, 0xbd, 0x00}, + {0x18, 0x01, 0xbd, 0x00}, {0x1f, 0x01, 0xbd, 0x00}, + {0x29, 0x01, 0xbd, 0x00}, {0x38, 0x01, 0xbd, 0x01} + }, + { + {0x03, 0x01, 0xbe, 0x00}, {0x06, 0x01, 0xbe, 0x00}, + {0x0a, 0x01, 0xbe, 0x00}, {0x0f, 0x01, 0xbe, 0x00}, + {0x18, 0x01, 0xbe, 0x00}, {0x1f, 0x01, 0xbe, 0x00}, + {0x29, 0x01, 0xbe, 0x00}, {0x38, 0x01, 0xbe, 0x01}, + {0x03, 0x01, 0xc4, 0x00}, {0x06, 0x01, 0xc4, 0x00}, + {0x0a, 0x01, 0xc4, 0x00}, {0x0f, 0x01, 0xc4, 0x00}, + {0x18, 0x01, 0xc4, 0x00}, {0x1f, 0x01, 0xc4, 0x00}, + {0x29, 0x01, 0xc4, 0x00}, {0x38, 0x01, 0xc4, 0x01} + }, + { + {0x01, 0x01, 0xc6, 0x00}, {0x16, 0x01, 0xc6, 0x01}, + {0x01, 0x01, 0xe4, 0x00}, {0x16, 0x01, 0xe4, 0x01}, + {0x01, 0x01, 0xe8, 0x00}, {0x16, 0x01, 0xe8, 0x01}, + {0x01, 0x01, 0xe9, 0x00}, {0x16, 0x01, 0xe9, 0x01}, + {0x00, 0x01, 0x01, 0x01}, {0x00, 0x01, 0x87, 0x01}, + {0x00, 0x01, 0x89, 0x01}, {0x00, 0x01, 0x8a, 0x01}, + {0x00, 0x01, 0x8b, 0x01}, {0x00, 0x01, 0x8c, 0x01}, + {0x00, 0x01, 0x8d, 0x01}, {0x00, 0x01, 0x8f, 0x01} + }, + { + {0x02, 0x01, 0xc6, 0x00}, {0x09, 0x01, 0xc6, 0x00}, + {0x17, 0x01, 0xc6, 0x00}, {0x28, 0x01, 0xc6, 0x01}, + {0x02, 0x01, 0xe4, 0x00}, {0x09, 0x01, 0xe4, 0x00}, + {0x17, 0x01, 0xe4, 0x00}, {0x28, 0x01, 0xe4, 0x01}, + {0x02, 0x01, 0xe8, 0x00}, {0x09, 0x01, 0xe8, 0x00}, + {0x17, 0x01, 0xe8, 0x00}, {0x28, 0x01, 0xe8, 0x01}, + {0x02, 0x01, 0xe9, 0x00}, {0x09, 0x01, 0xe9, 0x00}, + {0x17, 0x01, 0xe9, 0x00}, {0x28, 0x01, 0xe9, 0x01} + }, + { + {0x03, 0x01, 0xc6, 0x00}, {0x06, 0x01, 0xc6, 0x00}, + {0x0a, 0x01, 0xc6, 0x00}, {0x0f, 0x01, 0xc6, 0x00}, + {0x18, 0x01, 0xc6, 0x00}, {0x1f, 0x01, 0xc6, 0x00}, + {0x29, 0x01, 0xc6, 0x00}, {0x38, 0x01, 0xc6, 0x01}, + {0x03, 0x01, 0xe4, 0x00}, {0x06, 0x01, 0xe4, 0x00}, + {0x0a, 0x01, 0xe4, 0x00}, {0x0f, 0x01, 0xe4, 0x00}, + {0x18, 0x01, 0xe4, 0x00}, {0x1f, 0x01, 0xe4, 0x00}, + {0x29, 0x01, 0xe4, 0x00}, {0x38, 0x01, 0xe4, 0x01} + }, + /* 145 */ + { + {0x03, 0x01, 0xe8, 0x00}, {0x06, 0x01, 0xe8, 0x00}, + {0x0a, 0x01, 0xe8, 0x00}, {0x0f, 0x01, 0xe8, 0x00}, + {0x18, 0x01, 0xe8, 0x00}, {0x1f, 0x01, 0xe8, 0x00}, + {0x29, 0x01, 0xe8, 0x00}, {0x38, 0x01, 0xe8, 0x01}, + {0x03, 0x01, 0xe9, 0x00}, {0x06, 0x01, 0xe9, 0x00}, + {0x0a, 0x01, 0xe9, 0x00}, {0x0f, 0x01, 0xe9, 0x00}, + {0x18, 0x01, 0xe9, 0x00}, {0x1f, 0x01, 0xe9, 0x00}, + {0x29, 0x01, 0xe9, 0x00}, {0x38, 0x01, 0xe9, 0x01} + }, + { + {0x01, 0x01, 0x01, 0x00}, {0x16, 0x01, 0x01, 0x01}, + {0x01, 0x01, 0x87, 0x00}, {0x16, 0x01, 0x87, 0x01}, + {0x01, 0x01, 0x89, 0x00}, {0x16, 0x01, 0x89, 0x01}, + {0x01, 0x01, 0x8a, 0x00}, {0x16, 0x01, 0x8a, 0x01}, + {0x01, 0x01, 0x8b, 0x00}, {0x16, 0x01, 0x8b, 0x01}, + {0x01, 0x01, 0x8c, 0x00}, {0x16, 0x01, 0x8c, 0x01}, + {0x01, 0x01, 0x8d, 0x00}, {0x16, 0x01, 0x8d, 0x01}, + {0x01, 0x01, 0x8f, 0x00}, {0x16, 0x01, 0x8f, 0x01} + }, + { + {0x02, 0x01, 0x01, 0x00}, {0x09, 0x01, 0x01, 0x00}, + {0x17, 0x01, 0x01, 0x00}, {0x28, 0x01, 0x01, 0x01}, + {0x02, 0x01, 0x87, 0x00}, {0x09, 0x01, 0x87, 0x00}, + {0x17, 0x01, 0x87, 0x00}, {0x28, 0x01, 0x87, 0x01}, + {0x02, 0x01, 0x89, 0x00}, {0x09, 0x01, 0x89, 0x00}, + {0x17, 0x01, 0x89, 0x00}, {0x28, 0x01, 0x89, 0x01}, + {0x02, 0x01, 0x8a, 0x00}, {0x09, 0x01, 0x8a, 0x00}, + {0x17, 0x01, 0x8a, 0x00}, {0x28, 0x01, 0x8a, 0x01} + }, + { + {0x03, 0x01, 0x01, 0x00}, {0x06, 0x01, 0x01, 0x00}, + {0x0a, 0x01, 0x01, 0x00}, {0x0f, 0x01, 0x01, 0x00}, + {0x18, 0x01, 0x01, 0x00}, {0x1f, 0x01, 0x01, 0x00}, + {0x29, 0x01, 0x01, 0x00}, {0x38, 0x01, 0x01, 0x01}, + {0x03, 0x01, 0x87, 0x00}, {0x06, 0x01, 0x87, 0x00}, + {0x0a, 0x01, 0x87, 0x00}, {0x0f, 0x01, 0x87, 0x00}, + {0x18, 0x01, 0x87, 0x00}, {0x1f, 0x01, 0x87, 0x00}, + {0x29, 0x01, 0x87, 0x00}, {0x38, 0x01, 0x87, 0x01} + }, + { + {0x03, 0x01, 0x89, 0x00}, {0x06, 0x01, 0x89, 0x00}, + {0x0a, 0x01, 0x89, 0x00}, {0x0f, 0x01, 0x89, 0x00}, + {0x18, 0x01, 0x89, 0x00}, {0x1f, 0x01, 0x89, 0x00}, + {0x29, 0x01, 0x89, 0x00}, {0x38, 0x01, 0x89, 0x01}, + {0x03, 0x01, 0x8a, 0x00}, {0x06, 0x01, 0x8a, 0x00}, + {0x0a, 0x01, 0x8a, 0x00}, {0x0f, 0x01, 0x8a, 0x00}, + {0x18, 0x01, 0x8a, 0x00}, {0x1f, 0x01, 0x8a, 0x00}, + {0x29, 0x01, 0x8a, 0x00}, {0x38, 0x01, 0x8a, 0x01} + }, + /* 150 */ + { + {0x02, 0x01, 0x8b, 0x00}, {0x09, 0x01, 0x8b, 0x00}, + {0x17, 0x01, 0x8b, 0x00}, {0x28, 0x01, 0x8b, 0x01}, + {0x02, 0x01, 0x8c, 0x00}, {0x09, 0x01, 0x8c, 0x00}, + {0x17, 0x01, 0x8c, 0x00}, {0x28, 0x01, 0x8c, 0x01}, + {0x02, 0x01, 0x8d, 0x00}, {0x09, 0x01, 0x8d, 0x00}, + {0x17, 0x01, 0x8d, 0x00}, {0x28, 0x01, 0x8d, 0x01}, + {0x02, 0x01, 0x8f, 0x00}, {0x09, 0x01, 0x8f, 0x00}, + {0x17, 0x01, 0x8f, 0x00}, {0x28, 0x01, 0x8f, 0x01} + }, + { + {0x03, 0x01, 0x8b, 0x00}, {0x06, 0x01, 0x8b, 0x00}, + {0x0a, 0x01, 0x8b, 0x00}, {0x0f, 0x01, 0x8b, 0x00}, + {0x18, 0x01, 0x8b, 0x00}, {0x1f, 0x01, 0x8b, 0x00}, + {0x29, 0x01, 0x8b, 0x00}, {0x38, 0x01, 0x8b, 0x01}, + {0x03, 0x01, 0x8c, 0x00}, {0x06, 0x01, 0x8c, 0x00}, + {0x0a, 0x01, 0x8c, 0x00}, {0x0f, 0x01, 0x8c, 0x00}, + {0x18, 0x01, 0x8c, 0x00}, {0x1f, 0x01, 0x8c, 0x00}, + {0x29, 0x01, 0x8c, 0x00}, {0x38, 0x01, 0x8c, 0x01} + }, + { + {0x03, 0x01, 0x8d, 0x00}, {0x06, 0x01, 0x8d, 0x00}, + {0x0a, 0x01, 0x8d, 0x00}, {0x0f, 0x01, 0x8d, 0x00}, + {0x18, 0x01, 0x8d, 0x00}, {0x1f, 0x01, 0x8d, 0x00}, + {0x29, 0x01, 0x8d, 0x00}, {0x38, 0x01, 0x8d, 0x01}, + {0x03, 0x01, 0x8f, 0x00}, {0x06, 0x01, 0x8f, 0x00}, + {0x0a, 0x01, 0x8f, 0x00}, {0x0f, 0x01, 0x8f, 0x00}, + {0x18, 0x01, 0x8f, 0x00}, {0x1f, 0x01, 0x8f, 0x00}, + {0x29, 0x01, 0x8f, 0x00}, {0x38, 0x01, 0x8f, 0x01} + }, + { + {0x9d, 0x00, 0x00, 0x00}, {0x9e, 0x00, 0x00, 0x00}, + {0xa0, 0x00, 0x00, 0x00}, {0xa1, 0x00, 0x00, 0x00}, + {0xa4, 0x00, 0x00, 0x00}, {0xa5, 0x00, 0x00, 0x00}, + {0xa7, 0x00, 0x00, 0x00}, {0xa8, 0x00, 0x00, 0x00}, + {0xac, 0x00, 0x00, 0x00}, {0xad, 0x00, 0x00, 0x00}, + {0xaf, 0x00, 0x00, 0x00}, {0xb1, 0x00, 0x00, 0x00}, + {0xb6, 0x00, 0x00, 0x00}, {0xb9, 0x00, 0x00, 0x00}, + {0xbf, 0x00, 0x00, 0x00}, {0xcf, 0x00, 0x00, 0x01} + }, + { + {0x00, 0x01, 0x93, 0x01}, {0x00, 0x01, 0x95, 0x01}, + {0x00, 0x01, 0x96, 0x01}, {0x00, 0x01, 0x97, 0x01}, + {0x00, 0x01, 0x98, 0x01}, {0x00, 0x01, 0x9b, 0x01}, + {0x00, 0x01, 0x9d, 0x01}, {0x00, 0x01, 0x9e, 0x01}, + {0x00, 0x01, 0xa5, 0x01}, {0x00, 0x01, 0xa6, 0x01}, + {0x00, 0x01, 0xa8, 0x01}, {0x00, 0x01, 0xae, 0x01}, + {0x00, 0x01, 0xaf, 0x01}, {0x00, 0x01, 0xb4, 0x01}, + {0x00, 0x01, 0xb6, 0x01}, {0x00, 0x01, 0xb7, 0x01} + }, + /* 155 */ + { + {0x01, 0x01, 0x93, 0x00}, {0x16, 0x01, 0x93, 0x01}, + {0x01, 0x01, 0x95, 0x00}, {0x16, 0x01, 0x95, 0x01}, + {0x01, 0x01, 0x96, 0x00}, {0x16, 0x01, 0x96, 0x01}, + {0x01, 0x01, 0x97, 0x00}, {0x16, 0x01, 0x97, 0x01}, + {0x01, 0x01, 0x98, 0x00}, {0x16, 0x01, 0x98, 0x01}, + {0x01, 0x01, 0x9b, 0x00}, {0x16, 0x01, 0x9b, 0x01}, + {0x01, 0x01, 0x9d, 0x00}, {0x16, 0x01, 0x9d, 0x01}, + {0x01, 0x01, 0x9e, 0x00}, {0x16, 0x01, 0x9e, 0x01} + }, + { + {0x02, 0x01, 0x93, 0x00}, {0x09, 0x01, 0x93, 0x00}, + {0x17, 0x01, 0x93, 0x00}, {0x28, 0x01, 0x93, 0x01}, + {0x02, 0x01, 0x95, 0x00}, {0x09, 0x01, 0x95, 0x00}, + {0x17, 0x01, 0x95, 0x00}, {0x28, 0x01, 0x95, 0x01}, + {0x02, 0x01, 0x96, 0x00}, {0x09, 0x01, 0x96, 0x00}, + {0x17, 0x01, 0x96, 0x00}, {0x28, 0x01, 0x96, 0x01}, + {0x02, 0x01, 0x97, 0x00}, {0x09, 0x01, 0x97, 0x00}, + {0x17, 0x01, 0x97, 0x00}, {0x28, 0x01, 0x97, 0x01} + }, + { + {0x03, 0x01, 0x93, 0x00}, {0x06, 0x01, 0x93, 0x00}, + {0x0a, 0x01, 0x93, 0x00}, {0x0f, 0x01, 0x93, 0x00}, + {0x18, 0x01, 0x93, 0x00}, {0x1f, 0x01, 0x93, 0x00}, + {0x29, 0x01, 0x93, 0x00}, {0x38, 0x01, 0x93, 0x01}, + {0x03, 0x01, 0x95, 0x00}, {0x06, 0x01, 0x95, 0x00}, + {0x0a, 0x01, 0x95, 0x00}, {0x0f, 0x01, 0x95, 0x00}, + {0x18, 0x01, 0x95, 0x00}, {0x1f, 0x01, 0x95, 0x00}, + {0x29, 0x01, 0x95, 0x00}, {0x38, 0x01, 0x95, 0x01} + }, + { + {0x03, 0x01, 0x96, 0x00}, {0x06, 0x01, 0x96, 0x00}, + {0x0a, 0x01, 0x96, 0x00}, {0x0f, 0x01, 0x96, 0x00}, + {0x18, 0x01, 0x96, 0x00}, {0x1f, 0x01, 0x96, 0x00}, + {0x29, 0x01, 0x96, 0x00}, {0x38, 0x01, 0x96, 0x01}, + {0x03, 0x01, 0x97, 0x00}, {0x06, 0x01, 0x97, 0x00}, + {0x0a, 0x01, 0x97, 0x00}, {0x0f, 0x01, 0x97, 0x00}, + {0x18, 0x01, 0x97, 0x00}, {0x1f, 0x01, 0x97, 0x00}, + {0x29, 0x01, 0x97, 0x00}, {0x38, 0x01, 0x97, 0x01} + }, + { + {0x02, 0x01, 0x98, 0x00}, {0x09, 0x01, 0x98, 0x00}, + {0x17, 0x01, 0x98, 0x00}, {0x28, 0x01, 0x98, 0x01}, + {0x02, 0x01, 0x9b, 0x00}, {0x09, 0x01, 0x9b, 0x00}, + {0x17, 0x01, 0x9b, 0x00}, {0x28, 0x01, 0x9b, 0x01}, + {0x02, 0x01, 0x9d, 0x00}, {0x09, 0x01, 0x9d, 0x00}, + {0x17, 0x01, 0x9d, 0x00}, {0x28, 0x01, 0x9d, 0x01}, + {0x02, 0x01, 0x9e, 0x00}, {0x09, 0x01, 0x9e, 0x00}, + {0x17, 0x01, 0x9e, 0x00}, {0x28, 0x01, 0x9e, 0x01} + }, + /* 160 */ + { + {0x03, 0x01, 0x98, 0x00}, {0x06, 0x01, 0x98, 0x00}, + {0x0a, 0x01, 0x98, 0x00}, {0x0f, 0x01, 0x98, 0x00}, + {0x18, 0x01, 0x98, 0x00}, {0x1f, 0x01, 0x98, 0x00}, + {0x29, 0x01, 0x98, 0x00}, {0x38, 0x01, 0x98, 0x01}, + {0x03, 0x01, 0x9b, 0x00}, {0x06, 0x01, 0x9b, 0x00}, + {0x0a, 0x01, 0x9b, 0x00}, {0x0f, 0x01, 0x9b, 0x00}, + {0x18, 0x01, 0x9b, 0x00}, {0x1f, 0x01, 0x9b, 0x00}, + {0x29, 0x01, 0x9b, 0x00}, {0x38, 0x01, 0x9b, 0x01} + }, + { + {0x03, 0x01, 0x9d, 0x00}, {0x06, 0x01, 0x9d, 0x00}, + {0x0a, 0x01, 0x9d, 0x00}, {0x0f, 0x01, 0x9d, 0x00}, + {0x18, 0x01, 0x9d, 0x00}, {0x1f, 0x01, 0x9d, 0x00}, + {0x29, 0x01, 0x9d, 0x00}, {0x38, 0x01, 0x9d, 0x01}, + {0x03, 0x01, 0x9e, 0x00}, {0x06, 0x01, 0x9e, 0x00}, + {0x0a, 0x01, 0x9e, 0x00}, {0x0f, 0x01, 0x9e, 0x00}, + {0x18, 0x01, 0x9e, 0x00}, {0x1f, 0x01, 0x9e, 0x00}, + {0x29, 0x01, 0x9e, 0x00}, {0x38, 0x01, 0x9e, 0x01} + }, + { + {0x01, 0x01, 0xa5, 0x00}, {0x16, 0x01, 0xa5, 0x01}, + {0x01, 0x01, 0xa6, 0x00}, {0x16, 0x01, 0xa6, 0x01}, + {0x01, 0x01, 0xa8, 0x00}, {0x16, 0x01, 0xa8, 0x01}, + {0x01, 0x01, 0xae, 0x00}, {0x16, 0x01, 0xae, 0x01}, + {0x01, 0x01, 0xaf, 0x00}, {0x16, 0x01, 0xaf, 0x01}, + {0x01, 0x01, 0xb4, 0x00}, {0x16, 0x01, 0xb4, 0x01}, + {0x01, 0x01, 0xb6, 0x00}, {0x16, 0x01, 0xb6, 0x01}, + {0x01, 0x01, 0xb7, 0x00}, {0x16, 0x01, 0xb7, 0x01} + }, + { + {0x02, 0x01, 0xa5, 0x00}, {0x09, 0x01, 0xa5, 0x00}, + {0x17, 0x01, 0xa5, 0x00}, {0x28, 0x01, 0xa5, 0x01}, + {0x02, 0x01, 0xa6, 0x00}, {0x09, 0x01, 0xa6, 0x00}, + {0x17, 0x01, 0xa6, 0x00}, {0x28, 0x01, 0xa6, 0x01}, + {0x02, 0x01, 0xa8, 0x00}, {0x09, 0x01, 0xa8, 0x00}, + {0x17, 0x01, 0xa8, 0x00}, {0x28, 0x01, 0xa8, 0x01}, + {0x02, 0x01, 0xae, 0x00}, {0x09, 0x01, 0xae, 0x00}, + {0x17, 0x01, 0xae, 0x00}, {0x28, 0x01, 0xae, 0x01} + }, + { + {0x03, 0x01, 0xa5, 0x00}, {0x06, 0x01, 0xa5, 0x00}, + {0x0a, 0x01, 0xa5, 0x00}, {0x0f, 0x01, 0xa5, 0x00}, + {0x18, 0x01, 0xa5, 0x00}, {0x1f, 0x01, 0xa5, 0x00}, + {0x29, 0x01, 0xa5, 0x00}, {0x38, 0x01, 0xa5, 0x01}, + {0x03, 0x01, 0xa6, 0x00}, {0x06, 0x01, 0xa6, 0x00}, + {0x0a, 0x01, 0xa6, 0x00}, {0x0f, 0x01, 0xa6, 0x00}, + {0x18, 0x01, 0xa6, 0x00}, {0x1f, 0x01, 0xa6, 0x00}, + {0x29, 0x01, 0xa6, 0x00}, {0x38, 0x01, 0xa6, 0x01} + }, + /* 165 */ + { + {0x03, 0x01, 0xa8, 0x00}, {0x06, 0x01, 0xa8, 0x00}, + {0x0a, 0x01, 0xa8, 0x00}, {0x0f, 0x01, 0xa8, 0x00}, + {0x18, 0x01, 0xa8, 0x00}, {0x1f, 0x01, 0xa8, 0x00}, + {0x29, 0x01, 0xa8, 0x00}, {0x38, 0x01, 0xa8, 0x01}, + {0x03, 0x01, 0xae, 0x00}, {0x06, 0x01, 0xae, 0x00}, + {0x0a, 0x01, 0xae, 0x00}, {0x0f, 0x01, 0xae, 0x00}, + {0x18, 0x01, 0xae, 0x00}, {0x1f, 0x01, 0xae, 0x00}, + {0x29, 0x01, 0xae, 0x00}, {0x38, 0x01, 0xae, 0x01} + }, + { + {0x02, 0x01, 0xaf, 0x00}, {0x09, 0x01, 0xaf, 0x00}, + {0x17, 0x01, 0xaf, 0x00}, {0x28, 0x01, 0xaf, 0x01}, + {0x02, 0x01, 0xb4, 0x00}, {0x09, 0x01, 0xb4, 0x00}, + {0x17, 0x01, 0xb4, 0x00}, {0x28, 0x01, 0xb4, 0x01}, + {0x02, 0x01, 0xb6, 0x00}, {0x09, 0x01, 0xb6, 0x00}, + {0x17, 0x01, 0xb6, 0x00}, {0x28, 0x01, 0xb6, 0x01}, + {0x02, 0x01, 0xb7, 0x00}, {0x09, 0x01, 0xb7, 0x00}, + {0x17, 0x01, 0xb7, 0x00}, {0x28, 0x01, 0xb7, 0x01} + }, + { + {0x03, 0x01, 0xaf, 0x00}, {0x06, 0x01, 0xaf, 0x00}, + {0x0a, 0x01, 0xaf, 0x00}, {0x0f, 0x01, 0xaf, 0x00}, + {0x18, 0x01, 0xaf, 0x00}, {0x1f, 0x01, 0xaf, 0x00}, + {0x29, 0x01, 0xaf, 0x00}, {0x38, 0x01, 0xaf, 0x01}, + {0x03, 0x01, 0xb4, 0x00}, {0x06, 0x01, 0xb4, 0x00}, + {0x0a, 0x01, 0xb4, 0x00}, {0x0f, 0x01, 0xb4, 0x00}, + {0x18, 0x01, 0xb4, 0x00}, {0x1f, 0x01, 0xb4, 0x00}, + {0x29, 0x01, 0xb4, 0x00}, {0x38, 0x01, 0xb4, 0x01} + }, + { + {0x03, 0x01, 0xb6, 0x00}, {0x06, 0x01, 0xb6, 0x00}, + {0x0a, 0x01, 0xb6, 0x00}, {0x0f, 0x01, 0xb6, 0x00}, + {0x18, 0x01, 0xb6, 0x00}, {0x1f, 0x01, 0xb6, 0x00}, + {0x29, 0x01, 0xb6, 0x00}, {0x38, 0x01, 0xb6, 0x01}, + {0x03, 0x01, 0xb7, 0x00}, {0x06, 0x01, 0xb7, 0x00}, + {0x0a, 0x01, 0xb7, 0x00}, {0x0f, 0x01, 0xb7, 0x00}, + {0x18, 0x01, 0xb7, 0x00}, {0x1f, 0x01, 0xb7, 0x00}, + {0x29, 0x01, 0xb7, 0x00}, {0x38, 0x01, 0xb7, 0x01} + }, + { + {0x00, 0x01, 0xbc, 0x01}, {0x00, 0x01, 0xbf, 0x01}, + {0x00, 0x01, 0xc5, 0x01}, {0x00, 0x01, 0xe7, 0x01}, + {0x00, 0x01, 0xef, 0x01}, {0xb0, 0x00, 0x00, 0x00}, + {0xb2, 0x00, 0x00, 0x00}, {0xb3, 0x00, 0x00, 0x00}, + {0xb7, 0x00, 0x00, 0x00}, {0xb8, 0x00, 0x00, 0x00}, + {0xba, 0x00, 0x00, 0x00}, {0xbb, 0x00, 0x00, 0x00}, + {0xc0, 0x00, 0x00, 0x00}, {0xc7, 0x00, 0x00, 0x00}, + {0xd0, 0x00, 0x00, 0x00}, {0xdf, 0x00, 0x00, 0x01} + }, + /* 170 */ + { + {0x01, 0x01, 0xbc, 0x00}, {0x16, 0x01, 0xbc, 0x01}, + {0x01, 0x01, 0xbf, 0x00}, {0x16, 0x01, 0xbf, 0x01}, + {0x01, 0x01, 0xc5, 0x00}, {0x16, 0x01, 0xc5, 0x01}, + {0x01, 0x01, 0xe7, 0x00}, {0x16, 0x01, 0xe7, 0x01}, + {0x01, 0x01, 0xef, 0x00}, {0x16, 0x01, 0xef, 0x01}, + {0x00, 0x01, 0x09, 0x01}, {0x00, 0x01, 0x8e, 0x01}, + {0x00, 0x01, 0x90, 0x01}, {0x00, 0x01, 0x91, 0x01}, + {0x00, 0x01, 0x94, 0x01}, {0x00, 0x01, 0x9f, 0x01} + }, + { + {0x02, 0x01, 0xbc, 0x00}, {0x09, 0x01, 0xbc, 0x00}, + {0x17, 0x01, 0xbc, 0x00}, {0x28, 0x01, 0xbc, 0x01}, + {0x02, 0x01, 0xbf, 0x00}, {0x09, 0x01, 0xbf, 0x00}, + {0x17, 0x01, 0xbf, 0x00}, {0x28, 0x01, 0xbf, 0x01}, + {0x02, 0x01, 0xc5, 0x00}, {0x09, 0x01, 0xc5, 0x00}, + {0x17, 0x01, 0xc5, 0x00}, {0x28, 0x01, 0xc5, 0x01}, + {0x02, 0x01, 0xe7, 0x00}, {0x09, 0x01, 0xe7, 0x00}, + {0x17, 0x01, 0xe7, 0x00}, {0x28, 0x01, 0xe7, 0x01} + }, + { + {0x03, 0x01, 0xbc, 0x00}, {0x06, 0x01, 0xbc, 0x00}, + {0x0a, 0x01, 0xbc, 0x00}, {0x0f, 0x01, 0xbc, 0x00}, + {0x18, 0x01, 0xbc, 0x00}, {0x1f, 0x01, 0xbc, 0x00}, + {0x29, 0x01, 0xbc, 0x00}, {0x38, 0x01, 0xbc, 0x01}, + {0x03, 0x01, 0xbf, 0x00}, {0x06, 0x01, 0xbf, 0x00}, + {0x0a, 0x01, 0xbf, 0x00}, {0x0f, 0x01, 0xbf, 0x00}, + {0x18, 0x01, 0xbf, 0x00}, {0x1f, 0x01, 0xbf, 0x00}, + {0x29, 0x01, 0xbf, 0x00}, {0x38, 0x01, 0xbf, 0x01} + }, + { + {0x03, 0x01, 0xc5, 0x00}, {0x06, 0x01, 0xc5, 0x00}, + {0x0a, 0x01, 0xc5, 0x00}, {0x0f, 0x01, 0xc5, 0x00}, + {0x18, 0x01, 0xc5, 0x00}, {0x1f, 0x01, 0xc5, 0x00}, + {0x29, 0x01, 0xc5, 0x00}, {0x38, 0x01, 0xc5, 0x01}, + {0x03, 0x01, 0xe7, 0x00}, {0x06, 0x01, 0xe7, 0x00}, + {0x0a, 0x01, 0xe7, 0x00}, {0x0f, 0x01, 0xe7, 0x00}, + {0x18, 0x01, 0xe7, 0x00}, {0x1f, 0x01, 0xe7, 0x00}, + {0x29, 0x01, 0xe7, 0x00}, {0x38, 0x01, 0xe7, 0x01} + }, + { + {0x02, 0x01, 0xef, 0x00}, {0x09, 0x01, 0xef, 0x00}, + {0x17, 0x01, 0xef, 0x00}, {0x28, 0x01, 0xef, 0x01}, + {0x01, 0x01, 0x09, 0x00}, {0x16, 0x01, 0x09, 0x01}, + {0x01, 0x01, 0x8e, 0x00}, {0x16, 0x01, 0x8e, 0x01}, + {0x01, 0x01, 0x90, 0x00}, {0x16, 0x01, 0x90, 0x01}, + {0x01, 0x01, 0x91, 0x00}, {0x16, 0x01, 0x91, 0x01}, + {0x01, 0x01, 0x94, 0x00}, {0x16, 0x01, 0x94, 0x01}, + {0x01, 0x01, 0x9f, 0x00}, {0x16, 0x01, 0x9f, 0x01} + }, + /* 175 */ + { + {0x03, 0x01, 0xef, 0x00}, {0x06, 0x01, 0xef, 0x00}, + {0x0a, 0x01, 0xef, 0x00}, {0x0f, 0x01, 0xef, 0x00}, + {0x18, 0x01, 0xef, 0x00}, {0x1f, 0x01, 0xef, 0x00}, + {0x29, 0x01, 0xef, 0x00}, {0x38, 0x01, 0xef, 0x01}, + {0x02, 0x01, 0x09, 0x00}, {0x09, 0x01, 0x09, 0x00}, + {0x17, 0x01, 0x09, 0x00}, {0x28, 0x01, 0x09, 0x01}, + {0x02, 0x01, 0x8e, 0x00}, {0x09, 0x01, 0x8e, 0x00}, + {0x17, 0x01, 0x8e, 0x00}, {0x28, 0x01, 0x8e, 0x01} + }, + { + {0x03, 0x01, 0x09, 0x00}, {0x06, 0x01, 0x09, 0x00}, + {0x0a, 0x01, 0x09, 0x00}, {0x0f, 0x01, 0x09, 0x00}, + {0x18, 0x01, 0x09, 0x00}, {0x1f, 0x01, 0x09, 0x00}, + {0x29, 0x01, 0x09, 0x00}, {0x38, 0x01, 0x09, 0x01}, + {0x03, 0x01, 0x8e, 0x00}, {0x06, 0x01, 0x8e, 0x00}, + {0x0a, 0x01, 0x8e, 0x00}, {0x0f, 0x01, 0x8e, 0x00}, + {0x18, 0x01, 0x8e, 0x00}, {0x1f, 0x01, 0x8e, 0x00}, + {0x29, 0x01, 0x8e, 0x00}, {0x38, 0x01, 0x8e, 0x01} + }, + { + {0x02, 0x01, 0x90, 0x00}, {0x09, 0x01, 0x90, 0x00}, + {0x17, 0x01, 0x90, 0x00}, {0x28, 0x01, 0x90, 0x01}, + {0x02, 0x01, 0x91, 0x00}, {0x09, 0x01, 0x91, 0x00}, + {0x17, 0x01, 0x91, 0x00}, {0x28, 0x01, 0x91, 0x01}, + {0x02, 0x01, 0x94, 0x00}, {0x09, 0x01, 0x94, 0x00}, + {0x17, 0x01, 0x94, 0x00}, {0x28, 0x01, 0x94, 0x01}, + {0x02, 0x01, 0x9f, 0x00}, {0x09, 0x01, 0x9f, 0x00}, + {0x17, 0x01, 0x9f, 0x00}, {0x28, 0x01, 0x9f, 0x01} + }, + { + {0x03, 0x01, 0x90, 0x00}, {0x06, 0x01, 0x90, 0x00}, + {0x0a, 0x01, 0x90, 0x00}, {0x0f, 0x01, 0x90, 0x00}, + {0x18, 0x01, 0x90, 0x00}, {0x1f, 0x01, 0x90, 0x00}, + {0x29, 0x01, 0x90, 0x00}, {0x38, 0x01, 0x90, 0x01}, + {0x03, 0x01, 0x91, 0x00}, {0x06, 0x01, 0x91, 0x00}, + {0x0a, 0x01, 0x91, 0x00}, {0x0f, 0x01, 0x91, 0x00}, + {0x18, 0x01, 0x91, 0x00}, {0x1f, 0x01, 0x91, 0x00}, + {0x29, 0x01, 0x91, 0x00}, {0x38, 0x01, 0x91, 0x01} + }, + { + {0x03, 0x01, 0x94, 0x00}, {0x06, 0x01, 0x94, 0x00}, + {0x0a, 0x01, 0x94, 0x00}, {0x0f, 0x01, 0x94, 0x00}, + {0x18, 0x01, 0x94, 0x00}, {0x1f, 0x01, 0x94, 0x00}, + {0x29, 0x01, 0x94, 0x00}, {0x38, 0x01, 0x94, 0x01}, + {0x03, 0x01, 0x9f, 0x00}, {0x06, 0x01, 0x9f, 0x00}, + {0x0a, 0x01, 0x9f, 0x00}, {0x0f, 0x01, 0x9f, 0x00}, + {0x18, 0x01, 0x9f, 0x00}, {0x1f, 0x01, 0x9f, 0x00}, + {0x29, 0x01, 0x9f, 0x00}, {0x38, 0x01, 0x9f, 0x01} + }, + /* 180 */ + { + {0x00, 0x01, 0xab, 0x01}, {0x00, 0x01, 0xce, 0x01}, + {0x00, 0x01, 0xd7, 0x01}, {0x00, 0x01, 0xe1, 0x01}, + {0x00, 0x01, 0xec, 0x01}, {0x00, 0x01, 0xed, 0x01}, + {0xbc, 0x00, 0x00, 0x00}, {0xbd, 0x00, 0x00, 0x00}, + {0xc1, 0x00, 0x00, 0x00}, {0xc4, 0x00, 0x00, 0x00}, + {0xc8, 0x00, 0x00, 0x00}, {0xcb, 0x00, 0x00, 0x00}, + {0xd1, 0x00, 0x00, 0x00}, {0xd8, 0x00, 0x00, 0x00}, + {0xe0, 0x00, 0x00, 0x00}, {0xee, 0x00, 0x00, 0x01} + }, + { + {0x01, 0x01, 0xab, 0x00}, {0x16, 0x01, 0xab, 0x01}, + {0x01, 0x01, 0xce, 0x00}, {0x16, 0x01, 0xce, 0x01}, + {0x01, 0x01, 0xd7, 0x00}, {0x16, 0x01, 0xd7, 0x01}, + {0x01, 0x01, 0xe1, 0x00}, {0x16, 0x01, 0xe1, 0x01}, + {0x01, 0x01, 0xec, 0x00}, {0x16, 0x01, 0xec, 0x01}, + {0x01, 0x01, 0xed, 0x00}, {0x16, 0x01, 0xed, 0x01}, + {0x00, 0x01, 0xc7, 0x01}, {0x00, 0x01, 0xcf, 0x01}, + {0x00, 0x01, 0xea, 0x01}, {0x00, 0x01, 0xeb, 0x01} + }, + { + {0x02, 0x01, 0xab, 0x00}, {0x09, 0x01, 0xab, 0x00}, + {0x17, 0x01, 0xab, 0x00}, {0x28, 0x01, 0xab, 0x01}, + {0x02, 0x01, 0xce, 0x00}, {0x09, 0x01, 0xce, 0x00}, + {0x17, 0x01, 0xce, 0x00}, {0x28, 0x01, 0xce, 0x01}, + {0x02, 0x01, 0xd7, 0x00}, {0x09, 0x01, 0xd7, 0x00}, + {0x17, 0x01, 0xd7, 0x00}, {0x28, 0x01, 0xd7, 0x01}, + {0x02, 0x01, 0xe1, 0x00}, {0x09, 0x01, 0xe1, 0x00}, + {0x17, 0x01, 0xe1, 0x00}, {0x28, 0x01, 0xe1, 0x01} + }, + { + {0x03, 0x01, 0xab, 0x00}, {0x06, 0x01, 0xab, 0x00}, + {0x0a, 0x01, 0xab, 0x00}, {0x0f, 0x01, 0xab, 0x00}, + {0x18, 0x01, 0xab, 0x00}, {0x1f, 0x01, 0xab, 0x00}, + {0x29, 0x01, 0xab, 0x00}, {0x38, 0x01, 0xab, 0x01}, + {0x03, 0x01, 0xce, 0x00}, {0x06, 0x01, 0xce, 0x00}, + {0x0a, 0x01, 0xce, 0x00}, {0x0f, 0x01, 0xce, 0x00}, + {0x18, 0x01, 0xce, 0x00}, {0x1f, 0x01, 0xce, 0x00}, + {0x29, 0x01, 0xce, 0x00}, {0x38, 0x01, 0xce, 0x01} + }, + { + {0x03, 0x01, 0xd7, 0x00}, {0x06, 0x01, 0xd7, 0x00}, + {0x0a, 0x01, 0xd7, 0x00}, {0x0f, 0x01, 0xd7, 0x00}, + {0x18, 0x01, 0xd7, 0x00}, {0x1f, 0x01, 0xd7, 0x00}, + {0x29, 0x01, 0xd7, 0x00}, {0x38, 0x01, 0xd7, 0x01}, + {0x03, 0x01, 0xe1, 0x00}, {0x06, 0x01, 0xe1, 0x00}, + {0x0a, 0x01, 0xe1, 0x00}, {0x0f, 0x01, 0xe1, 0x00}, + {0x18, 0x01, 0xe1, 0x00}, {0x1f, 0x01, 0xe1, 0x00}, + {0x29, 0x01, 0xe1, 0x00}, {0x38, 0x01, 0xe1, 0x01} + }, + /* 185 */ + { + {0x02, 0x01, 0xec, 0x00}, {0x09, 0x01, 0xec, 0x00}, + {0x17, 0x01, 0xec, 0x00}, {0x28, 0x01, 0xec, 0x01}, + {0x02, 0x01, 0xed, 0x00}, {0x09, 0x01, 0xed, 0x00}, + {0x17, 0x01, 0xed, 0x00}, {0x28, 0x01, 0xed, 0x01}, + {0x01, 0x01, 0xc7, 0x00}, {0x16, 0x01, 0xc7, 0x01}, + {0x01, 0x01, 0xcf, 0x00}, {0x16, 0x01, 0xcf, 0x01}, + {0x01, 0x01, 0xea, 0x00}, {0x16, 0x01, 0xea, 0x01}, + {0x01, 0x01, 0xeb, 0x00}, {0x16, 0x01, 0xeb, 0x01} + }, + { + {0x03, 0x01, 0xec, 0x00}, {0x06, 0x01, 0xec, 0x00}, + {0x0a, 0x01, 0xec, 0x00}, {0x0f, 0x01, 0xec, 0x00}, + {0x18, 0x01, 0xec, 0x00}, {0x1f, 0x01, 0xec, 0x00}, + {0x29, 0x01, 0xec, 0x00}, {0x38, 0x01, 0xec, 0x01}, + {0x03, 0x01, 0xed, 0x00}, {0x06, 0x01, 0xed, 0x00}, + {0x0a, 0x01, 0xed, 0x00}, {0x0f, 0x01, 0xed, 0x00}, + {0x18, 0x01, 0xed, 0x00}, {0x1f, 0x01, 0xed, 0x00}, + {0x29, 0x01, 0xed, 0x00}, {0x38, 0x01, 0xed, 0x01} + }, + { + {0x02, 0x01, 0xc7, 0x00}, {0x09, 0x01, 0xc7, 0x00}, + {0x17, 0x01, 0xc7, 0x00}, {0x28, 0x01, 0xc7, 0x01}, + {0x02, 0x01, 0xcf, 0x00}, {0x09, 0x01, 0xcf, 0x00}, + {0x17, 0x01, 0xcf, 0x00}, {0x28, 0x01, 0xcf, 0x01}, + {0x02, 0x01, 0xea, 0x00}, {0x09, 0x01, 0xea, 0x00}, + {0x17, 0x01, 0xea, 0x00}, {0x28, 0x01, 0xea, 0x01}, + {0x02, 0x01, 0xeb, 0x00}, {0x09, 0x01, 0xeb, 0x00}, + {0x17, 0x01, 0xeb, 0x00}, {0x28, 0x01, 0xeb, 0x01} + }, + { + {0x03, 0x01, 0xc7, 0x00}, {0x06, 0x01, 0xc7, 0x00}, + {0x0a, 0x01, 0xc7, 0x00}, {0x0f, 0x01, 0xc7, 0x00}, + {0x18, 0x01, 0xc7, 0x00}, {0x1f, 0x01, 0xc7, 0x00}, + {0x29, 0x01, 0xc7, 0x00}, {0x38, 0x01, 0xc7, 0x01}, + {0x03, 0x01, 0xcf, 0x00}, {0x06, 0x01, 0xcf, 0x00}, + {0x0a, 0x01, 0xcf, 0x00}, {0x0f, 0x01, 0xcf, 0x00}, + {0x18, 0x01, 0xcf, 0x00}, {0x1f, 0x01, 0xcf, 0x00}, + {0x29, 0x01, 0xcf, 0x00}, {0x38, 0x01, 0xcf, 0x01} + }, + { + {0x03, 0x01, 0xea, 0x00}, {0x06, 0x01, 0xea, 0x00}, + {0x0a, 0x01, 0xea, 0x00}, {0x0f, 0x01, 0xea, 0x00}, + {0x18, 0x01, 0xea, 0x00}, {0x1f, 0x01, 0xea, 0x00}, + {0x29, 0x01, 0xea, 0x00}, {0x38, 0x01, 0xea, 0x01}, + {0x03, 0x01, 0xeb, 0x00}, {0x06, 0x01, 0xeb, 0x00}, + {0x0a, 0x01, 0xeb, 0x00}, {0x0f, 0x01, 0xeb, 0x00}, + {0x18, 0x01, 0xeb, 0x00}, {0x1f, 0x01, 0xeb, 0x00}, + {0x29, 0x01, 0xeb, 0x00}, {0x38, 0x01, 0xeb, 0x01} + }, + /* 190 */ + { + {0xc2, 0x00, 0x00, 0x00}, {0xc3, 0x00, 0x00, 0x00}, + {0xc5, 0x00, 0x00, 0x00}, {0xc6, 0x00, 0x00, 0x00}, + {0xc9, 0x00, 0x00, 0x00}, {0xca, 0x00, 0x00, 0x00}, + {0xcc, 0x00, 0x00, 0x00}, {0xcd, 0x00, 0x00, 0x00}, + {0xd2, 0x00, 0x00, 0x00}, {0xd5, 0x00, 0x00, 0x00}, + {0xd9, 0x00, 0x00, 0x00}, {0xdc, 0x00, 0x00, 0x00}, + {0xe1, 0x00, 0x00, 0x00}, {0xe7, 0x00, 0x00, 0x00}, + {0xef, 0x00, 0x00, 0x00}, {0xf6, 0x00, 0x00, 0x01} + }, + { + {0x00, 0x01, 0xc0, 0x01}, {0x00, 0x01, 0xc1, 0x01}, + {0x00, 0x01, 0xc8, 0x01}, {0x00, 0x01, 0xc9, 0x01}, + {0x00, 0x01, 0xca, 0x01}, {0x00, 0x01, 0xcd, 0x01}, + {0x00, 0x01, 0xd2, 0x01}, {0x00, 0x01, 0xd5, 0x01}, + {0x00, 0x01, 0xda, 0x01}, {0x00, 0x01, 0xdb, 0x01}, + {0x00, 0x01, 0xee, 0x01}, {0x00, 0x01, 0xf0, 0x01}, + {0x00, 0x01, 0xf2, 0x01}, {0x00, 0x01, 0xf3, 0x01}, + {0x00, 0x01, 0xff, 0x01}, {0xce, 0x00, 0x00, 0x00} + }, + { + {0x01, 0x01, 0xc0, 0x00}, {0x16, 0x01, 0xc0, 0x01}, + {0x01, 0x01, 0xc1, 0x00}, {0x16, 0x01, 0xc1, 0x01}, + {0x01, 0x01, 0xc8, 0x00}, {0x16, 0x01, 0xc8, 0x01}, + {0x01, 0x01, 0xc9, 0x00}, {0x16, 0x01, 0xc9, 0x01}, + {0x01, 0x01, 0xca, 0x00}, {0x16, 0x01, 0xca, 0x01}, + {0x01, 0x01, 0xcd, 0x00}, {0x16, 0x01, 0xcd, 0x01}, + {0x01, 0x01, 0xd2, 0x00}, {0x16, 0x01, 0xd2, 0x01}, + {0x01, 0x01, 0xd5, 0x00}, {0x16, 0x01, 0xd5, 0x01} + }, + { + {0x02, 0x01, 0xc0, 0x00}, {0x09, 0x01, 0xc0, 0x00}, + {0x17, 0x01, 0xc0, 0x00}, {0x28, 0x01, 0xc0, 0x01}, + {0x02, 0x01, 0xc1, 0x00}, {0x09, 0x01, 0xc1, 0x00}, + {0x17, 0x01, 0xc1, 0x00}, {0x28, 0x01, 0xc1, 0x01}, + {0x02, 0x01, 0xc8, 0x00}, {0x09, 0x01, 0xc8, 0x00}, + {0x17, 0x01, 0xc8, 0x00}, {0x28, 0x01, 0xc8, 0x01}, + {0x02, 0x01, 0xc9, 0x00}, {0x09, 0x01, 0xc9, 0x00}, + {0x17, 0x01, 0xc9, 0x00}, {0x28, 0x01, 0xc9, 0x01} + }, + { + {0x03, 0x01, 0xc0, 0x00}, {0x06, 0x01, 0xc0, 0x00}, + {0x0a, 0x01, 0xc0, 0x00}, {0x0f, 0x01, 0xc0, 0x00}, + {0x18, 0x01, 0xc0, 0x00}, {0x1f, 0x01, 0xc0, 0x00}, + {0x29, 0x01, 0xc0, 0x00}, {0x38, 0x01, 0xc0, 0x01}, + {0x03, 0x01, 0xc1, 0x00}, {0x06, 0x01, 0xc1, 0x00}, + {0x0a, 0x01, 0xc1, 0x00}, {0x0f, 0x01, 0xc1, 0x00}, + {0x18, 0x01, 0xc1, 0x00}, {0x1f, 0x01, 0xc1, 0x00}, + {0x29, 0x01, 0xc1, 0x00}, {0x38, 0x01, 0xc1, 0x01} + }, + /* 195 */ + { + {0x03, 0x01, 0xc8, 0x00}, {0x06, 0x01, 0xc8, 0x00}, + {0x0a, 0x01, 0xc8, 0x00}, {0x0f, 0x01, 0xc8, 0x00}, + {0x18, 0x01, 0xc8, 0x00}, {0x1f, 0x01, 0xc8, 0x00}, + {0x29, 0x01, 0xc8, 0x00}, {0x38, 0x01, 0xc8, 0x01}, + {0x03, 0x01, 0xc9, 0x00}, {0x06, 0x01, 0xc9, 0x00}, + {0x0a, 0x01, 0xc9, 0x00}, {0x0f, 0x01, 0xc9, 0x00}, + {0x18, 0x01, 0xc9, 0x00}, {0x1f, 0x01, 0xc9, 0x00}, + {0x29, 0x01, 0xc9, 0x00}, {0x38, 0x01, 0xc9, 0x01} + }, + { + {0x02, 0x01, 0xca, 0x00}, {0x09, 0x01, 0xca, 0x00}, + {0x17, 0x01, 0xca, 0x00}, {0x28, 0x01, 0xca, 0x01}, + {0x02, 0x01, 0xcd, 0x00}, {0x09, 0x01, 0xcd, 0x00}, + {0x17, 0x01, 0xcd, 0x00}, {0x28, 0x01, 0xcd, 0x01}, + {0x02, 0x01, 0xd2, 0x00}, {0x09, 0x01, 0xd2, 0x00}, + {0x17, 0x01, 0xd2, 0x00}, {0x28, 0x01, 0xd2, 0x01}, + {0x02, 0x01, 0xd5, 0x00}, {0x09, 0x01, 0xd5, 0x00}, + {0x17, 0x01, 0xd5, 0x00}, {0x28, 0x01, 0xd5, 0x01} + }, + { + {0x03, 0x01, 0xca, 0x00}, {0x06, 0x01, 0xca, 0x00}, + {0x0a, 0x01, 0xca, 0x00}, {0x0f, 0x01, 0xca, 0x00}, + {0x18, 0x01, 0xca, 0x00}, {0x1f, 0x01, 0xca, 0x00}, + {0x29, 0x01, 0xca, 0x00}, {0x38, 0x01, 0xca, 0x01}, + {0x03, 0x01, 0xcd, 0x00}, {0x06, 0x01, 0xcd, 0x00}, + {0x0a, 0x01, 0xcd, 0x00}, {0x0f, 0x01, 0xcd, 0x00}, + {0x18, 0x01, 0xcd, 0x00}, {0x1f, 0x01, 0xcd, 0x00}, + {0x29, 0x01, 0xcd, 0x00}, {0x38, 0x01, 0xcd, 0x01} + }, + { + {0x03, 0x01, 0xd2, 0x00}, {0x06, 0x01, 0xd2, 0x00}, + {0x0a, 0x01, 0xd2, 0x00}, {0x0f, 0x01, 0xd2, 0x00}, + {0x18, 0x01, 0xd2, 0x00}, {0x1f, 0x01, 0xd2, 0x00}, + {0x29, 0x01, 0xd2, 0x00}, {0x38, 0x01, 0xd2, 0x01}, + {0x03, 0x01, 0xd5, 0x00}, {0x06, 0x01, 0xd5, 0x00}, + {0x0a, 0x01, 0xd5, 0x00}, {0x0f, 0x01, 0xd5, 0x00}, + {0x18, 0x01, 0xd5, 0x00}, {0x1f, 0x01, 0xd5, 0x00}, + {0x29, 0x01, 0xd5, 0x00}, {0x38, 0x01, 0xd5, 0x01} + }, + { + {0x01, 0x01, 0xda, 0x00}, {0x16, 0x01, 0xda, 0x01}, + {0x01, 0x01, 0xdb, 0x00}, {0x16, 0x01, 0xdb, 0x01}, + {0x01, 0x01, 0xee, 0x00}, {0x16, 0x01, 0xee, 0x01}, + {0x01, 0x01, 0xf0, 0x00}, {0x16, 0x01, 0xf0, 0x01}, + {0x01, 0x01, 0xf2, 0x00}, {0x16, 0x01, 0xf2, 0x01}, + {0x01, 0x01, 0xf3, 0x00}, {0x16, 0x01, 0xf3, 0x01}, + {0x01, 0x01, 0xff, 0x00}, {0x16, 0x01, 0xff, 0x01}, + {0x00, 0x01, 0xcb, 0x01}, {0x00, 0x01, 0xcc, 0x01} + }, + /* 200 */ + { + {0x02, 0x01, 0xda, 0x00}, {0x09, 0x01, 0xda, 0x00}, + {0x17, 0x01, 0xda, 0x00}, {0x28, 0x01, 0xda, 0x01}, + {0x02, 0x01, 0xdb, 0x00}, {0x09, 0x01, 0xdb, 0x00}, + {0x17, 0x01, 0xdb, 0x00}, {0x28, 0x01, 0xdb, 0x01}, + {0x02, 0x01, 0xee, 0x00}, {0x09, 0x01, 0xee, 0x00}, + {0x17, 0x01, 0xee, 0x00}, {0x28, 0x01, 0xee, 0x01}, + {0x02, 0x01, 0xf0, 0x00}, {0x09, 0x01, 0xf0, 0x00}, + {0x17, 0x01, 0xf0, 0x00}, {0x28, 0x01, 0xf0, 0x01} + }, + { + {0x03, 0x01, 0xda, 0x00}, {0x06, 0x01, 0xda, 0x00}, + {0x0a, 0x01, 0xda, 0x00}, {0x0f, 0x01, 0xda, 0x00}, + {0x18, 0x01, 0xda, 0x00}, {0x1f, 0x01, 0xda, 0x00}, + {0x29, 0x01, 0xda, 0x00}, {0x38, 0x01, 0xda, 0x01}, + {0x03, 0x01, 0xdb, 0x00}, {0x06, 0x01, 0xdb, 0x00}, + {0x0a, 0x01, 0xdb, 0x00}, {0x0f, 0x01, 0xdb, 0x00}, + {0x18, 0x01, 0xdb, 0x00}, {0x1f, 0x01, 0xdb, 0x00}, + {0x29, 0x01, 0xdb, 0x00}, {0x38, 0x01, 0xdb, 0x01} + }, + { + {0x03, 0x01, 0xee, 0x00}, {0x06, 0x01, 0xee, 0x00}, + {0x0a, 0x01, 0xee, 0x00}, {0x0f, 0x01, 0xee, 0x00}, + {0x18, 0x01, 0xee, 0x00}, {0x1f, 0x01, 0xee, 0x00}, + {0x29, 0x01, 0xee, 0x00}, {0x38, 0x01, 0xee, 0x01}, + {0x03, 0x01, 0xf0, 0x00}, {0x06, 0x01, 0xf0, 0x00}, + {0x0a, 0x01, 0xf0, 0x00}, {0x0f, 0x01, 0xf0, 0x00}, + {0x18, 0x01, 0xf0, 0x00}, {0x1f, 0x01, 0xf0, 0x00}, + {0x29, 0x01, 0xf0, 0x00}, {0x38, 0x01, 0xf0, 0x01} + }, + { + {0x02, 0x01, 0xf2, 0x00}, {0x09, 0x01, 0xf2, 0x00}, + {0x17, 0x01, 0xf2, 0x00}, {0x28, 0x01, 0xf2, 0x01}, + {0x02, 0x01, 0xf3, 0x00}, {0x09, 0x01, 0xf3, 0x00}, + {0x17, 0x01, 0xf3, 0x00}, {0x28, 0x01, 0xf3, 0x01}, + {0x02, 0x01, 0xff, 0x00}, {0x09, 0x01, 0xff, 0x00}, + {0x17, 0x01, 0xff, 0x00}, {0x28, 0x01, 0xff, 0x01}, + {0x01, 0x01, 0xcb, 0x00}, {0x16, 0x01, 0xcb, 0x01}, + {0x01, 0x01, 0xcc, 0x00}, {0x16, 0x01, 0xcc, 0x01} + }, + { + {0x03, 0x01, 0xf2, 0x00}, {0x06, 0x01, 0xf2, 0x00}, + {0x0a, 0x01, 0xf2, 0x00}, {0x0f, 0x01, 0xf2, 0x00}, + {0x18, 0x01, 0xf2, 0x00}, {0x1f, 0x01, 0xf2, 0x00}, + {0x29, 0x01, 0xf2, 0x00}, {0x38, 0x01, 0xf2, 0x01}, + {0x03, 0x01, 0xf3, 0x00}, {0x06, 0x01, 0xf3, 0x00}, + {0x0a, 0x01, 0xf3, 0x00}, {0x0f, 0x01, 0xf3, 0x00}, + {0x18, 0x01, 0xf3, 0x00}, {0x1f, 0x01, 0xf3, 0x00}, + {0x29, 0x01, 0xf3, 0x00}, {0x38, 0x01, 0xf3, 0x01} + }, + /* 205 */ + { + {0x03, 0x01, 0xff, 0x00}, {0x06, 0x01, 0xff, 0x00}, + {0x0a, 0x01, 0xff, 0x00}, {0x0f, 0x01, 0xff, 0x00}, + {0x18, 0x01, 0xff, 0x00}, {0x1f, 0x01, 0xff, 0x00}, + {0x29, 0x01, 0xff, 0x00}, {0x38, 0x01, 0xff, 0x01}, + {0x02, 0x01, 0xcb, 0x00}, {0x09, 0x01, 0xcb, 0x00}, + {0x17, 0x01, 0xcb, 0x00}, {0x28, 0x01, 0xcb, 0x01}, + {0x02, 0x01, 0xcc, 0x00}, {0x09, 0x01, 0xcc, 0x00}, + {0x17, 0x01, 0xcc, 0x00}, {0x28, 0x01, 0xcc, 0x01} + }, + { + {0x03, 0x01, 0xcb, 0x00}, {0x06, 0x01, 0xcb, 0x00}, + {0x0a, 0x01, 0xcb, 0x00}, {0x0f, 0x01, 0xcb, 0x00}, + {0x18, 0x01, 0xcb, 0x00}, {0x1f, 0x01, 0xcb, 0x00}, + {0x29, 0x01, 0xcb, 0x00}, {0x38, 0x01, 0xcb, 0x01}, + {0x03, 0x01, 0xcc, 0x00}, {0x06, 0x01, 0xcc, 0x00}, + {0x0a, 0x01, 0xcc, 0x00}, {0x0f, 0x01, 0xcc, 0x00}, + {0x18, 0x01, 0xcc, 0x00}, {0x1f, 0x01, 0xcc, 0x00}, + {0x29, 0x01, 0xcc, 0x00}, {0x38, 0x01, 0xcc, 0x01} + }, + { + {0xd3, 0x00, 0x00, 0x00}, {0xd4, 0x00, 0x00, 0x00}, + {0xd6, 0x00, 0x00, 0x00}, {0xd7, 0x00, 0x00, 0x00}, + {0xda, 0x00, 0x00, 0x00}, {0xdb, 0x00, 0x00, 0x00}, + {0xdd, 0x00, 0x00, 0x00}, {0xde, 0x00, 0x00, 0x00}, + {0xe2, 0x00, 0x00, 0x00}, {0xe4, 0x00, 0x00, 0x00}, + {0xe8, 0x00, 0x00, 0x00}, {0xeb, 0x00, 0x00, 0x00}, + {0xf0, 0x00, 0x00, 0x00}, {0xf3, 0x00, 0x00, 0x00}, + {0xf7, 0x00, 0x00, 0x00}, {0xfa, 0x00, 0x00, 0x01} + }, + { + {0x00, 0x01, 0xd3, 0x01}, {0x00, 0x01, 0xd4, 0x01}, + {0x00, 0x01, 0xd6, 0x01}, {0x00, 0x01, 0xdd, 0x01}, + {0x00, 0x01, 0xde, 0x01}, {0x00, 0x01, 0xdf, 0x01}, + {0x00, 0x01, 0xf1, 0x01}, {0x00, 0x01, 0xf4, 0x01}, + {0x00, 0x01, 0xf5, 0x01}, {0x00, 0x01, 0xf6, 0x01}, + {0x00, 0x01, 0xf7, 0x01}, {0x00, 0x01, 0xf8, 0x01}, + {0x00, 0x01, 0xfa, 0x01}, {0x00, 0x01, 0xfb, 0x01}, + {0x00, 0x01, 0xfc, 0x01}, {0x00, 0x01, 0xfd, 0x01} + }, + { + {0x01, 0x01, 0xd3, 0x00}, {0x16, 0x01, 0xd3, 0x01}, + {0x01, 0x01, 0xd4, 0x00}, {0x16, 0x01, 0xd4, 0x01}, + {0x01, 0x01, 0xd6, 0x00}, {0x16, 0x01, 0xd6, 0x01}, + {0x01, 0x01, 0xdd, 0x00}, {0x16, 0x01, 0xdd, 0x01}, + {0x01, 0x01, 0xde, 0x00}, {0x16, 0x01, 0xde, 0x01}, + {0x01, 0x01, 0xdf, 0x00}, {0x16, 0x01, 0xdf, 0x01}, + {0x01, 0x01, 0xf1, 0x00}, {0x16, 0x01, 0xf1, 0x01}, + {0x01, 0x01, 0xf4, 0x00}, {0x16, 0x01, 0xf4, 0x01} + }, + /* 210 */ + { + {0x02, 0x01, 0xd3, 0x00}, {0x09, 0x01, 0xd3, 0x00}, + {0x17, 0x01, 0xd3, 0x00}, {0x28, 0x01, 0xd3, 0x01}, + {0x02, 0x01, 0xd4, 0x00}, {0x09, 0x01, 0xd4, 0x00}, + {0x17, 0x01, 0xd4, 0x00}, {0x28, 0x01, 0xd4, 0x01}, + {0x02, 0x01, 0xd6, 0x00}, {0x09, 0x01, 0xd6, 0x00}, + {0x17, 0x01, 0xd6, 0x00}, {0x28, 0x01, 0xd6, 0x01}, + {0x02, 0x01, 0xdd, 0x00}, {0x09, 0x01, 0xdd, 0x00}, + {0x17, 0x01, 0xdd, 0x00}, {0x28, 0x01, 0xdd, 0x01} + }, + { + {0x03, 0x01, 0xd3, 0x00}, {0x06, 0x01, 0xd3, 0x00}, + {0x0a, 0x01, 0xd3, 0x00}, {0x0f, 0x01, 0xd3, 0x00}, + {0x18, 0x01, 0xd3, 0x00}, {0x1f, 0x01, 0xd3, 0x00}, + {0x29, 0x01, 0xd3, 0x00}, {0x38, 0x01, 0xd3, 0x01}, + {0x03, 0x01, 0xd4, 0x00}, {0x06, 0x01, 0xd4, 0x00}, + {0x0a, 0x01, 0xd4, 0x00}, {0x0f, 0x01, 0xd4, 0x00}, + {0x18, 0x01, 0xd4, 0x00}, {0x1f, 0x01, 0xd4, 0x00}, + {0x29, 0x01, 0xd4, 0x00}, {0x38, 0x01, 0xd4, 0x01} + }, + { + {0x03, 0x01, 0xd6, 0x00}, {0x06, 0x01, 0xd6, 0x00}, + {0x0a, 0x01, 0xd6, 0x00}, {0x0f, 0x01, 0xd6, 0x00}, + {0x18, 0x01, 0xd6, 0x00}, {0x1f, 0x01, 0xd6, 0x00}, + {0x29, 0x01, 0xd6, 0x00}, {0x38, 0x01, 0xd6, 0x01}, + {0x03, 0x01, 0xdd, 0x00}, {0x06, 0x01, 0xdd, 0x00}, + {0x0a, 0x01, 0xdd, 0x00}, {0x0f, 0x01, 0xdd, 0x00}, + {0x18, 0x01, 0xdd, 0x00}, {0x1f, 0x01, 0xdd, 0x00}, + {0x29, 0x01, 0xdd, 0x00}, {0x38, 0x01, 0xdd, 0x01} + }, + { + {0x02, 0x01, 0xde, 0x00}, {0x09, 0x01, 0xde, 0x00}, + {0x17, 0x01, 0xde, 0x00}, {0x28, 0x01, 0xde, 0x01}, + {0x02, 0x01, 0xdf, 0x00}, {0x09, 0x01, 0xdf, 0x00}, + {0x17, 0x01, 0xdf, 0x00}, {0x28, 0x01, 0xdf, 0x01}, + {0x02, 0x01, 0xf1, 0x00}, {0x09, 0x01, 0xf1, 0x00}, + {0x17, 0x01, 0xf1, 0x00}, {0x28, 0x01, 0xf1, 0x01}, + {0x02, 0x01, 0xf4, 0x00}, {0x09, 0x01, 0xf4, 0x00}, + {0x17, 0x01, 0xf4, 0x00}, {0x28, 0x01, 0xf4, 0x01} + }, + { + {0x03, 0x01, 0xde, 0x00}, {0x06, 0x01, 0xde, 0x00}, + {0x0a, 0x01, 0xde, 0x00}, {0x0f, 0x01, 0xde, 0x00}, + {0x18, 0x01, 0xde, 0x00}, {0x1f, 0x01, 0xde, 0x00}, + {0x29, 0x01, 0xde, 0x00}, {0x38, 0x01, 0xde, 0x01}, + {0x03, 0x01, 0xdf, 0x00}, {0x06, 0x01, 0xdf, 0x00}, + {0x0a, 0x01, 0xdf, 0x00}, {0x0f, 0x01, 0xdf, 0x00}, + {0x18, 0x01, 0xdf, 0x00}, {0x1f, 0x01, 0xdf, 0x00}, + {0x29, 0x01, 0xdf, 0x00}, {0x38, 0x01, 0xdf, 0x01} + }, + /* 215 */ + { + {0x03, 0x01, 0xf1, 0x00}, {0x06, 0x01, 0xf1, 0x00}, + {0x0a, 0x01, 0xf1, 0x00}, {0x0f, 0x01, 0xf1, 0x00}, + {0x18, 0x01, 0xf1, 0x00}, {0x1f, 0x01, 0xf1, 0x00}, + {0x29, 0x01, 0xf1, 0x00}, {0x38, 0x01, 0xf1, 0x01}, + {0x03, 0x01, 0xf4, 0x00}, {0x06, 0x01, 0xf4, 0x00}, + {0x0a, 0x01, 0xf4, 0x00}, {0x0f, 0x01, 0xf4, 0x00}, + {0x18, 0x01, 0xf4, 0x00}, {0x1f, 0x01, 0xf4, 0x00}, + {0x29, 0x01, 0xf4, 0x00}, {0x38, 0x01, 0xf4, 0x01} + }, + { + {0x01, 0x01, 0xf5, 0x00}, {0x16, 0x01, 0xf5, 0x01}, + {0x01, 0x01, 0xf6, 0x00}, {0x16, 0x01, 0xf6, 0x01}, + {0x01, 0x01, 0xf7, 0x00}, {0x16, 0x01, 0xf7, 0x01}, + {0x01, 0x01, 0xf8, 0x00}, {0x16, 0x01, 0xf8, 0x01}, + {0x01, 0x01, 0xfa, 0x00}, {0x16, 0x01, 0xfa, 0x01}, + {0x01, 0x01, 0xfb, 0x00}, {0x16, 0x01, 0xfb, 0x01}, + {0x01, 0x01, 0xfc, 0x00}, {0x16, 0x01, 0xfc, 0x01}, + {0x01, 0x01, 0xfd, 0x00}, {0x16, 0x01, 0xfd, 0x01} + }, + { + {0x02, 0x01, 0xf5, 0x00}, {0x09, 0x01, 0xf5, 0x00}, + {0x17, 0x01, 0xf5, 0x00}, {0x28, 0x01, 0xf5, 0x01}, + {0x02, 0x01, 0xf6, 0x00}, {0x09, 0x01, 0xf6, 0x00}, + {0x17, 0x01, 0xf6, 0x00}, {0x28, 0x01, 0xf6, 0x01}, + {0x02, 0x01, 0xf7, 0x00}, {0x09, 0x01, 0xf7, 0x00}, + {0x17, 0x01, 0xf7, 0x00}, {0x28, 0x01, 0xf7, 0x01}, + {0x02, 0x01, 0xf8, 0x00}, {0x09, 0x01, 0xf8, 0x00}, + {0x17, 0x01, 0xf8, 0x00}, {0x28, 0x01, 0xf8, 0x01} + }, + { + {0x03, 0x01, 0xf5, 0x00}, {0x06, 0x01, 0xf5, 0x00}, + {0x0a, 0x01, 0xf5, 0x00}, {0x0f, 0x01, 0xf5, 0x00}, + {0x18, 0x01, 0xf5, 0x00}, {0x1f, 0x01, 0xf5, 0x00}, + {0x29, 0x01, 0xf5, 0x00}, {0x38, 0x01, 0xf5, 0x01}, + {0x03, 0x01, 0xf6, 0x00}, {0x06, 0x01, 0xf6, 0x00}, + {0x0a, 0x01, 0xf6, 0x00}, {0x0f, 0x01, 0xf6, 0x00}, + {0x18, 0x01, 0xf6, 0x00}, {0x1f, 0x01, 0xf6, 0x00}, + {0x29, 0x01, 0xf6, 0x00}, {0x38, 0x01, 0xf6, 0x01} + }, + { + {0x03, 0x01, 0xf7, 0x00}, {0x06, 0x01, 0xf7, 0x00}, + {0x0a, 0x01, 0xf7, 0x00}, {0x0f, 0x01, 0xf7, 0x00}, + {0x18, 0x01, 0xf7, 0x00}, {0x1f, 0x01, 0xf7, 0x00}, + {0x29, 0x01, 0xf7, 0x00}, {0x38, 0x01, 0xf7, 0x01}, + {0x03, 0x01, 0xf8, 0x00}, {0x06, 0x01, 0xf8, 0x00}, + {0x0a, 0x01, 0xf8, 0x00}, {0x0f, 0x01, 0xf8, 0x00}, + {0x18, 0x01, 0xf8, 0x00}, {0x1f, 0x01, 0xf8, 0x00}, + {0x29, 0x01, 0xf8, 0x00}, {0x38, 0x01, 0xf8, 0x01} + }, + /* 220 */ + { + {0x02, 0x01, 0xfa, 0x00}, {0x09, 0x01, 0xfa, 0x00}, + {0x17, 0x01, 0xfa, 0x00}, {0x28, 0x01, 0xfa, 0x01}, + {0x02, 0x01, 0xfb, 0x00}, {0x09, 0x01, 0xfb, 0x00}, + {0x17, 0x01, 0xfb, 0x00}, {0x28, 0x01, 0xfb, 0x01}, + {0x02, 0x01, 0xfc, 0x00}, {0x09, 0x01, 0xfc, 0x00}, + {0x17, 0x01, 0xfc, 0x00}, {0x28, 0x01, 0xfc, 0x01}, + {0x02, 0x01, 0xfd, 0x00}, {0x09, 0x01, 0xfd, 0x00}, + {0x17, 0x01, 0xfd, 0x00}, {0x28, 0x01, 0xfd, 0x01} + }, + { + {0x03, 0x01, 0xfa, 0x00}, {0x06, 0x01, 0xfa, 0x00}, + {0x0a, 0x01, 0xfa, 0x00}, {0x0f, 0x01, 0xfa, 0x00}, + {0x18, 0x01, 0xfa, 0x00}, {0x1f, 0x01, 0xfa, 0x00}, + {0x29, 0x01, 0xfa, 0x00}, {0x38, 0x01, 0xfa, 0x01}, + {0x03, 0x01, 0xfb, 0x00}, {0x06, 0x01, 0xfb, 0x00}, + {0x0a, 0x01, 0xfb, 0x00}, {0x0f, 0x01, 0xfb, 0x00}, + {0x18, 0x01, 0xfb, 0x00}, {0x1f, 0x01, 0xfb, 0x00}, + {0x29, 0x01, 0xfb, 0x00}, {0x38, 0x01, 0xfb, 0x01} + }, + { + {0x03, 0x01, 0xfc, 0x00}, {0x06, 0x01, 0xfc, 0x00}, + {0x0a, 0x01, 0xfc, 0x00}, {0x0f, 0x01, 0xfc, 0x00}, + {0x18, 0x01, 0xfc, 0x00}, {0x1f, 0x01, 0xfc, 0x00}, + {0x29, 0x01, 0xfc, 0x00}, {0x38, 0x01, 0xfc, 0x01}, + {0x03, 0x01, 0xfd, 0x00}, {0x06, 0x01, 0xfd, 0x00}, + {0x0a, 0x01, 0xfd, 0x00}, {0x0f, 0x01, 0xfd, 0x00}, + {0x18, 0x01, 0xfd, 0x00}, {0x1f, 0x01, 0xfd, 0x00}, + {0x29, 0x01, 0xfd, 0x00}, {0x38, 0x01, 0xfd, 0x01} + }, + { + {0x00, 0x01, 0xfe, 0x01}, {0xe3, 0x00, 0x00, 0x00}, + {0xe5, 0x00, 0x00, 0x00}, {0xe6, 0x00, 0x00, 0x00}, + {0xe9, 0x00, 0x00, 0x00}, {0xea, 0x00, 0x00, 0x00}, + {0xec, 0x00, 0x00, 0x00}, {0xed, 0x00, 0x00, 0x00}, + {0xf1, 0x00, 0x00, 0x00}, {0xf2, 0x00, 0x00, 0x00}, + {0xf4, 0x00, 0x00, 0x00}, {0xf5, 0x00, 0x00, 0x00}, + {0xf8, 0x00, 0x00, 0x00}, {0xf9, 0x00, 0x00, 0x00}, + {0xfb, 0x00, 0x00, 0x00}, {0xfc, 0x00, 0x00, 0x01} + }, + { + {0x01, 0x01, 0xfe, 0x00}, {0x16, 0x01, 0xfe, 0x01}, + {0x00, 0x01, 0x02, 0x01}, {0x00, 0x01, 0x03, 0x01}, + {0x00, 0x01, 0x04, 0x01}, {0x00, 0x01, 0x05, 0x01}, + {0x00, 0x01, 0x06, 0x01}, {0x00, 0x01, 0x07, 0x01}, + {0x00, 0x01, 0x08, 0x01}, {0x00, 0x01, 0x0b, 0x01}, + {0x00, 0x01, 0x0c, 0x01}, {0x00, 0x01, 0x0e, 0x01}, + {0x00, 0x01, 0x0f, 0x01}, {0x00, 0x01, 0x10, 0x01}, + {0x00, 0x01, 0x11, 0x01}, {0x00, 0x01, 0x12, 0x01} + }, + /* 225 */ + { + {0x02, 0x01, 0xfe, 0x00}, {0x09, 0x01, 0xfe, 0x00}, + {0x17, 0x01, 0xfe, 0x00}, {0x28, 0x01, 0xfe, 0x01}, + {0x01, 0x01, 0x02, 0x00}, {0x16, 0x01, 0x02, 0x01}, + {0x01, 0x01, 0x03, 0x00}, {0x16, 0x01, 0x03, 0x01}, + {0x01, 0x01, 0x04, 0x00}, {0x16, 0x01, 0x04, 0x01}, + {0x01, 0x01, 0x05, 0x00}, {0x16, 0x01, 0x05, 0x01}, + {0x01, 0x01, 0x06, 0x00}, {0x16, 0x01, 0x06, 0x01}, + {0x01, 0x01, 0x07, 0x00}, {0x16, 0x01, 0x07, 0x01} + }, + { + {0x03, 0x01, 0xfe, 0x00}, {0x06, 0x01, 0xfe, 0x00}, + {0x0a, 0x01, 0xfe, 0x00}, {0x0f, 0x01, 0xfe, 0x00}, + {0x18, 0x01, 0xfe, 0x00}, {0x1f, 0x01, 0xfe, 0x00}, + {0x29, 0x01, 0xfe, 0x00}, {0x38, 0x01, 0xfe, 0x01}, + {0x02, 0x01, 0x02, 0x00}, {0x09, 0x01, 0x02, 0x00}, + {0x17, 0x01, 0x02, 0x00}, {0x28, 0x01, 0x02, 0x01}, + {0x02, 0x01, 0x03, 0x00}, {0x09, 0x01, 0x03, 0x00}, + {0x17, 0x01, 0x03, 0x00}, {0x28, 0x01, 0x03, 0x01} + }, + { + {0x03, 0x01, 0x02, 0x00}, {0x06, 0x01, 0x02, 0x00}, + {0x0a, 0x01, 0x02, 0x00}, {0x0f, 0x01, 0x02, 0x00}, + {0x18, 0x01, 0x02, 0x00}, {0x1f, 0x01, 0x02, 0x00}, + {0x29, 0x01, 0x02, 0x00}, {0x38, 0x01, 0x02, 0x01}, + {0x03, 0x01, 0x03, 0x00}, {0x06, 0x01, 0x03, 0x00}, + {0x0a, 0x01, 0x03, 0x00}, {0x0f, 0x01, 0x03, 0x00}, + {0x18, 0x01, 0x03, 0x00}, {0x1f, 0x01, 0x03, 0x00}, + {0x29, 0x01, 0x03, 0x00}, {0x38, 0x01, 0x03, 0x01} + }, + { + {0x02, 0x01, 0x04, 0x00}, {0x09, 0x01, 0x04, 0x00}, + {0x17, 0x01, 0x04, 0x00}, {0x28, 0x01, 0x04, 0x01}, + {0x02, 0x01, 0x05, 0x00}, {0x09, 0x01, 0x05, 0x00}, + {0x17, 0x01, 0x05, 0x00}, {0x28, 0x01, 0x05, 0x01}, + {0x02, 0x01, 0x06, 0x00}, {0x09, 0x01, 0x06, 0x00}, + {0x17, 0x01, 0x06, 0x00}, {0x28, 0x01, 0x06, 0x01}, + {0x02, 0x01, 0x07, 0x00}, {0x09, 0x01, 0x07, 0x00}, + {0x17, 0x01, 0x07, 0x00}, {0x28, 0x01, 0x07, 0x01} + }, + { + {0x03, 0x01, 0x04, 0x00}, {0x06, 0x01, 0x04, 0x00}, + {0x0a, 0x01, 0x04, 0x00}, {0x0f, 0x01, 0x04, 0x00}, + {0x18, 0x01, 0x04, 0x00}, {0x1f, 0x01, 0x04, 0x00}, + {0x29, 0x01, 0x04, 0x00}, {0x38, 0x01, 0x04, 0x01}, + {0x03, 0x01, 0x05, 0x00}, {0x06, 0x01, 0x05, 0x00}, + {0x0a, 0x01, 0x05, 0x00}, {0x0f, 0x01, 0x05, 0x00}, + {0x18, 0x01, 0x05, 0x00}, {0x1f, 0x01, 0x05, 0x00}, + {0x29, 0x01, 0x05, 0x00}, {0x38, 0x01, 0x05, 0x01} + }, + /* 230 */ + { + {0x03, 0x01, 0x06, 0x00}, {0x06, 0x01, 0x06, 0x00}, + {0x0a, 0x01, 0x06, 0x00}, {0x0f, 0x01, 0x06, 0x00}, + {0x18, 0x01, 0x06, 0x00}, {0x1f, 0x01, 0x06, 0x00}, + {0x29, 0x01, 0x06, 0x00}, {0x38, 0x01, 0x06, 0x01}, + {0x03, 0x01, 0x07, 0x00}, {0x06, 0x01, 0x07, 0x00}, + {0x0a, 0x01, 0x07, 0x00}, {0x0f, 0x01, 0x07, 0x00}, + {0x18, 0x01, 0x07, 0x00}, {0x1f, 0x01, 0x07, 0x00}, + {0x29, 0x01, 0x07, 0x00}, {0x38, 0x01, 0x07, 0x01} + }, + { + {0x01, 0x01, 0x08, 0x00}, {0x16, 0x01, 0x08, 0x01}, + {0x01, 0x01, 0x0b, 0x00}, {0x16, 0x01, 0x0b, 0x01}, + {0x01, 0x01, 0x0c, 0x00}, {0x16, 0x01, 0x0c, 0x01}, + {0x01, 0x01, 0x0e, 0x00}, {0x16, 0x01, 0x0e, 0x01}, + {0x01, 0x01, 0x0f, 0x00}, {0x16, 0x01, 0x0f, 0x01}, + {0x01, 0x01, 0x10, 0x00}, {0x16, 0x01, 0x10, 0x01}, + {0x01, 0x01, 0x11, 0x00}, {0x16, 0x01, 0x11, 0x01}, + {0x01, 0x01, 0x12, 0x00}, {0x16, 0x01, 0x12, 0x01} + }, + { + {0x02, 0x01, 0x08, 0x00}, {0x09, 0x01, 0x08, 0x00}, + {0x17, 0x01, 0x08, 0x00}, {0x28, 0x01, 0x08, 0x01}, + {0x02, 0x01, 0x0b, 0x00}, {0x09, 0x01, 0x0b, 0x00}, + {0x17, 0x01, 0x0b, 0x00}, {0x28, 0x01, 0x0b, 0x01}, + {0x02, 0x01, 0x0c, 0x00}, {0x09, 0x01, 0x0c, 0x00}, + {0x17, 0x01, 0x0c, 0x00}, {0x28, 0x01, 0x0c, 0x01}, + {0x02, 0x01, 0x0e, 0x00}, {0x09, 0x01, 0x0e, 0x00}, + {0x17, 0x01, 0x0e, 0x00}, {0x28, 0x01, 0x0e, 0x01} + }, + { + {0x03, 0x01, 0x08, 0x00}, {0x06, 0x01, 0x08, 0x00}, + {0x0a, 0x01, 0x08, 0x00}, {0x0f, 0x01, 0x08, 0x00}, + {0x18, 0x01, 0x08, 0x00}, {0x1f, 0x01, 0x08, 0x00}, + {0x29, 0x01, 0x08, 0x00}, {0x38, 0x01, 0x08, 0x01}, + {0x03, 0x01, 0x0b, 0x00}, {0x06, 0x01, 0x0b, 0x00}, + {0x0a, 0x01, 0x0b, 0x00}, {0x0f, 0x01, 0x0b, 0x00}, + {0x18, 0x01, 0x0b, 0x00}, {0x1f, 0x01, 0x0b, 0x00}, + {0x29, 0x01, 0x0b, 0x00}, {0x38, 0x01, 0x0b, 0x01} + }, + { + {0x03, 0x01, 0x0c, 0x00}, {0x06, 0x01, 0x0c, 0x00}, + {0x0a, 0x01, 0x0c, 0x00}, {0x0f, 0x01, 0x0c, 0x00}, + {0x18, 0x01, 0x0c, 0x00}, {0x1f, 0x01, 0x0c, 0x00}, + {0x29, 0x01, 0x0c, 0x00}, {0x38, 0x01, 0x0c, 0x01}, + {0x03, 0x01, 0x0e, 0x00}, {0x06, 0x01, 0x0e, 0x00}, + {0x0a, 0x01, 0x0e, 0x00}, {0x0f, 0x01, 0x0e, 0x00}, + {0x18, 0x01, 0x0e, 0x00}, {0x1f, 0x01, 0x0e, 0x00}, + {0x29, 0x01, 0x0e, 0x00}, {0x38, 0x01, 0x0e, 0x01} + }, + /* 235 */ + { + {0x02, 0x01, 0x0f, 0x00}, {0x09, 0x01, 0x0f, 0x00}, + {0x17, 0x01, 0x0f, 0x00}, {0x28, 0x01, 0x0f, 0x01}, + {0x02, 0x01, 0x10, 0x00}, {0x09, 0x01, 0x10, 0x00}, + {0x17, 0x01, 0x10, 0x00}, {0x28, 0x01, 0x10, 0x01}, + {0x02, 0x01, 0x11, 0x00}, {0x09, 0x01, 0x11, 0x00}, + {0x17, 0x01, 0x11, 0x00}, {0x28, 0x01, 0x11, 0x01}, + {0x02, 0x01, 0x12, 0x00}, {0x09, 0x01, 0x12, 0x00}, + {0x17, 0x01, 0x12, 0x00}, {0x28, 0x01, 0x12, 0x01} + }, + { + {0x03, 0x01, 0x0f, 0x00}, {0x06, 0x01, 0x0f, 0x00}, + {0x0a, 0x01, 0x0f, 0x00}, {0x0f, 0x01, 0x0f, 0x00}, + {0x18, 0x01, 0x0f, 0x00}, {0x1f, 0x01, 0x0f, 0x00}, + {0x29, 0x01, 0x0f, 0x00}, {0x38, 0x01, 0x0f, 0x01}, + {0x03, 0x01, 0x10, 0x00}, {0x06, 0x01, 0x10, 0x00}, + {0x0a, 0x01, 0x10, 0x00}, {0x0f, 0x01, 0x10, 0x00}, + {0x18, 0x01, 0x10, 0x00}, {0x1f, 0x01, 0x10, 0x00}, + {0x29, 0x01, 0x10, 0x00}, {0x38, 0x01, 0x10, 0x01} + }, + { + {0x03, 0x01, 0x11, 0x00}, {0x06, 0x01, 0x11, 0x00}, + {0x0a, 0x01, 0x11, 0x00}, {0x0f, 0x01, 0x11, 0x00}, + {0x18, 0x01, 0x11, 0x00}, {0x1f, 0x01, 0x11, 0x00}, + {0x29, 0x01, 0x11, 0x00}, {0x38, 0x01, 0x11, 0x01}, + {0x03, 0x01, 0x12, 0x00}, {0x06, 0x01, 0x12, 0x00}, + {0x0a, 0x01, 0x12, 0x00}, {0x0f, 0x01, 0x12, 0x00}, + {0x18, 0x01, 0x12, 0x00}, {0x1f, 0x01, 0x12, 0x00}, + {0x29, 0x01, 0x12, 0x00}, {0x38, 0x01, 0x12, 0x01} + }, + { + {0x00, 0x01, 0x13, 0x01}, {0x00, 0x01, 0x14, 0x01}, + {0x00, 0x01, 0x15, 0x01}, {0x00, 0x01, 0x17, 0x01}, + {0x00, 0x01, 0x18, 0x01}, {0x00, 0x01, 0x19, 0x01}, + {0x00, 0x01, 0x1a, 0x01}, {0x00, 0x01, 0x1b, 0x01}, + {0x00, 0x01, 0x1c, 0x01}, {0x00, 0x01, 0x1d, 0x01}, + {0x00, 0x01, 0x1e, 0x01}, {0x00, 0x01, 0x1f, 0x01}, + {0x00, 0x01, 0x7f, 0x01}, {0x00, 0x01, 0xdc, 0x01}, + {0x00, 0x01, 0xf9, 0x01}, {0xfd, 0x00, 0x00, 0x01} + }, + { + {0x01, 0x01, 0x13, 0x00}, {0x16, 0x01, 0x13, 0x01}, + {0x01, 0x01, 0x14, 0x00}, {0x16, 0x01, 0x14, 0x01}, + {0x01, 0x01, 0x15, 0x00}, {0x16, 0x01, 0x15, 0x01}, + {0x01, 0x01, 0x17, 0x00}, {0x16, 0x01, 0x17, 0x01}, + {0x01, 0x01, 0x18, 0x00}, {0x16, 0x01, 0x18, 0x01}, + {0x01, 0x01, 0x19, 0x00}, {0x16, 0x01, 0x19, 0x01}, + {0x01, 0x01, 0x1a, 0x00}, {0x16, 0x01, 0x1a, 0x01}, + {0x01, 0x01, 0x1b, 0x00}, {0x16, 0x01, 0x1b, 0x01} + }, + /* 240 */ + { + {0x02, 0x01, 0x13, 0x00}, {0x09, 0x01, 0x13, 0x00}, + {0x17, 0x01, 0x13, 0x00}, {0x28, 0x01, 0x13, 0x01}, + {0x02, 0x01, 0x14, 0x00}, {0x09, 0x01, 0x14, 0x00}, + {0x17, 0x01, 0x14, 0x00}, {0x28, 0x01, 0x14, 0x01}, + {0x02, 0x01, 0x15, 0x00}, {0x09, 0x01, 0x15, 0x00}, + {0x17, 0x01, 0x15, 0x00}, {0x28, 0x01, 0x15, 0x01}, + {0x02, 0x01, 0x17, 0x00}, {0x09, 0x01, 0x17, 0x00}, + {0x17, 0x01, 0x17, 0x00}, {0x28, 0x01, 0x17, 0x01} + }, + { + {0x03, 0x01, 0x13, 0x00}, {0x06, 0x01, 0x13, 0x00}, + {0x0a, 0x01, 0x13, 0x00}, {0x0f, 0x01, 0x13, 0x00}, + {0x18, 0x01, 0x13, 0x00}, {0x1f, 0x01, 0x13, 0x00}, + {0x29, 0x01, 0x13, 0x00}, {0x38, 0x01, 0x13, 0x01}, + {0x03, 0x01, 0x14, 0x00}, {0x06, 0x01, 0x14, 0x00}, + {0x0a, 0x01, 0x14, 0x00}, {0x0f, 0x01, 0x14, 0x00}, + {0x18, 0x01, 0x14, 0x00}, {0x1f, 0x01, 0x14, 0x00}, + {0x29, 0x01, 0x14, 0x00}, {0x38, 0x01, 0x14, 0x01} + }, + { + {0x03, 0x01, 0x15, 0x00}, {0x06, 0x01, 0x15, 0x00}, + {0x0a, 0x01, 0x15, 0x00}, {0x0f, 0x01, 0x15, 0x00}, + {0x18, 0x01, 0x15, 0x00}, {0x1f, 0x01, 0x15, 0x00}, + {0x29, 0x01, 0x15, 0x00}, {0x38, 0x01, 0x15, 0x01}, + {0x03, 0x01, 0x17, 0x00}, {0x06, 0x01, 0x17, 0x00}, + {0x0a, 0x01, 0x17, 0x00}, {0x0f, 0x01, 0x17, 0x00}, + {0x18, 0x01, 0x17, 0x00}, {0x1f, 0x01, 0x17, 0x00}, + {0x29, 0x01, 0x17, 0x00}, {0x38, 0x01, 0x17, 0x01} + }, + { + {0x02, 0x01, 0x18, 0x00}, {0x09, 0x01, 0x18, 0x00}, + {0x17, 0x01, 0x18, 0x00}, {0x28, 0x01, 0x18, 0x01}, + {0x02, 0x01, 0x19, 0x00}, {0x09, 0x01, 0x19, 0x00}, + {0x17, 0x01, 0x19, 0x00}, {0x28, 0x01, 0x19, 0x01}, + {0x02, 0x01, 0x1a, 0x00}, {0x09, 0x01, 0x1a, 0x00}, + {0x17, 0x01, 0x1a, 0x00}, {0x28, 0x01, 0x1a, 0x01}, + {0x02, 0x01, 0x1b, 0x00}, {0x09, 0x01, 0x1b, 0x00}, + {0x17, 0x01, 0x1b, 0x00}, {0x28, 0x01, 0x1b, 0x01} + }, + { + {0x03, 0x01, 0x18, 0x00}, {0x06, 0x01, 0x18, 0x00}, + {0x0a, 0x01, 0x18, 0x00}, {0x0f, 0x01, 0x18, 0x00}, + {0x18, 0x01, 0x18, 0x00}, {0x1f, 0x01, 0x18, 0x00}, + {0x29, 0x01, 0x18, 0x00}, {0x38, 0x01, 0x18, 0x01}, + {0x03, 0x01, 0x19, 0x00}, {0x06, 0x01, 0x19, 0x00}, + {0x0a, 0x01, 0x19, 0x00}, {0x0f, 0x01, 0x19, 0x00}, + {0x18, 0x01, 0x19, 0x00}, {0x1f, 0x01, 0x19, 0x00}, + {0x29, 0x01, 0x19, 0x00}, {0x38, 0x01, 0x19, 0x01} + }, + /* 245 */ + { + {0x03, 0x01, 0x1a, 0x00}, {0x06, 0x01, 0x1a, 0x00}, + {0x0a, 0x01, 0x1a, 0x00}, {0x0f, 0x01, 0x1a, 0x00}, + {0x18, 0x01, 0x1a, 0x00}, {0x1f, 0x01, 0x1a, 0x00}, + {0x29, 0x01, 0x1a, 0x00}, {0x38, 0x01, 0x1a, 0x01}, + {0x03, 0x01, 0x1b, 0x00}, {0x06, 0x01, 0x1b, 0x00}, + {0x0a, 0x01, 0x1b, 0x00}, {0x0f, 0x01, 0x1b, 0x00}, + {0x18, 0x01, 0x1b, 0x00}, {0x1f, 0x01, 0x1b, 0x00}, + {0x29, 0x01, 0x1b, 0x00}, {0x38, 0x01, 0x1b, 0x01} + }, + { + {0x01, 0x01, 0x1c, 0x00}, {0x16, 0x01, 0x1c, 0x01}, + {0x01, 0x01, 0x1d, 0x00}, {0x16, 0x01, 0x1d, 0x01}, + {0x01, 0x01, 0x1e, 0x00}, {0x16, 0x01, 0x1e, 0x01}, + {0x01, 0x01, 0x1f, 0x00}, {0x16, 0x01, 0x1f, 0x01}, + {0x01, 0x01, 0x7f, 0x00}, {0x16, 0x01, 0x7f, 0x01}, + {0x01, 0x01, 0xdc, 0x00}, {0x16, 0x01, 0xdc, 0x01}, + {0x01, 0x01, 0xf9, 0x00}, {0x16, 0x01, 0xf9, 0x01}, + {0xfe, 0x00, 0x00, 0x00}, {0xff, 0x00, 0x00, 0x01} + }, + { + {0x02, 0x01, 0x1c, 0x00}, {0x09, 0x01, 0x1c, 0x00}, + {0x17, 0x01, 0x1c, 0x00}, {0x28, 0x01, 0x1c, 0x01}, + {0x02, 0x01, 0x1d, 0x00}, {0x09, 0x01, 0x1d, 0x00}, + {0x17, 0x01, 0x1d, 0x00}, {0x28, 0x01, 0x1d, 0x01}, + {0x02, 0x01, 0x1e, 0x00}, {0x09, 0x01, 0x1e, 0x00}, + {0x17, 0x01, 0x1e, 0x00}, {0x28, 0x01, 0x1e, 0x01}, + {0x02, 0x01, 0x1f, 0x00}, {0x09, 0x01, 0x1f, 0x00}, + {0x17, 0x01, 0x1f, 0x00}, {0x28, 0x01, 0x1f, 0x01} + }, + { + {0x03, 0x01, 0x1c, 0x00}, {0x06, 0x01, 0x1c, 0x00}, + {0x0a, 0x01, 0x1c, 0x00}, {0x0f, 0x01, 0x1c, 0x00}, + {0x18, 0x01, 0x1c, 0x00}, {0x1f, 0x01, 0x1c, 0x00}, + {0x29, 0x01, 0x1c, 0x00}, {0x38, 0x01, 0x1c, 0x01}, + {0x03, 0x01, 0x1d, 0x00}, {0x06, 0x01, 0x1d, 0x00}, + {0x0a, 0x01, 0x1d, 0x00}, {0x0f, 0x01, 0x1d, 0x00}, + {0x18, 0x01, 0x1d, 0x00}, {0x1f, 0x01, 0x1d, 0x00}, + {0x29, 0x01, 0x1d, 0x00}, {0x38, 0x01, 0x1d, 0x01} + }, + { + {0x03, 0x01, 0x1e, 0x00}, {0x06, 0x01, 0x1e, 0x00}, + {0x0a, 0x01, 0x1e, 0x00}, {0x0f, 0x01, 0x1e, 0x00}, + {0x18, 0x01, 0x1e, 0x00}, {0x1f, 0x01, 0x1e, 0x00}, + {0x29, 0x01, 0x1e, 0x00}, {0x38, 0x01, 0x1e, 0x01}, + {0x03, 0x01, 0x1f, 0x00}, {0x06, 0x01, 0x1f, 0x00}, + {0x0a, 0x01, 0x1f, 0x00}, {0x0f, 0x01, 0x1f, 0x00}, + {0x18, 0x01, 0x1f, 0x00}, {0x1f, 0x01, 0x1f, 0x00}, + {0x29, 0x01, 0x1f, 0x00}, {0x38, 0x01, 0x1f, 0x01} + }, + /* 250 */ + { + {0x02, 0x01, 0x7f, 0x00}, {0x09, 0x01, 0x7f, 0x00}, + {0x17, 0x01, 0x7f, 0x00}, {0x28, 0x01, 0x7f, 0x01}, + {0x02, 0x01, 0xdc, 0x00}, {0x09, 0x01, 0xdc, 0x00}, + {0x17, 0x01, 0xdc, 0x00}, {0x28, 0x01, 0xdc, 0x01}, + {0x02, 0x01, 0xf9, 0x00}, {0x09, 0x01, 0xf9, 0x00}, + {0x17, 0x01, 0xf9, 0x00}, {0x28, 0x01, 0xf9, 0x01}, + {0x00, 0x01, 0x0a, 0x01}, {0x00, 0x01, 0x0d, 0x01}, + {0x00, 0x01, 0x16, 0x01}, {0xfa, 0x00, 0x00, 0x00} + }, + { + {0x03, 0x01, 0x7f, 0x00}, {0x06, 0x01, 0x7f, 0x00}, + {0x0a, 0x01, 0x7f, 0x00}, {0x0f, 0x01, 0x7f, 0x00}, + {0x18, 0x01, 0x7f, 0x00}, {0x1f, 0x01, 0x7f, 0x00}, + {0x29, 0x01, 0x7f, 0x00}, {0x38, 0x01, 0x7f, 0x01}, + {0x03, 0x01, 0xdc, 0x00}, {0x06, 0x01, 0xdc, 0x00}, + {0x0a, 0x01, 0xdc, 0x00}, {0x0f, 0x01, 0xdc, 0x00}, + {0x18, 0x01, 0xdc, 0x00}, {0x1f, 0x01, 0xdc, 0x00}, + {0x29, 0x01, 0xdc, 0x00}, {0x38, 0x01, 0xdc, 0x01} + }, + { + {0x03, 0x01, 0xf9, 0x00}, {0x06, 0x01, 0xf9, 0x00}, + {0x0a, 0x01, 0xf9, 0x00}, {0x0f, 0x01, 0xf9, 0x00}, + {0x18, 0x01, 0xf9, 0x00}, {0x1f, 0x01, 0xf9, 0x00}, + {0x29, 0x01, 0xf9, 0x00}, {0x38, 0x01, 0xf9, 0x01}, + {0x01, 0x01, 0x0a, 0x00}, {0x16, 0x01, 0x0a, 0x01}, + {0x01, 0x01, 0x0d, 0x00}, {0x16, 0x01, 0x0d, 0x01}, + {0x01, 0x01, 0x16, 0x00}, {0x16, 0x01, 0x16, 0x01}, + {0xfc, 0x00, 0x00, 0x00}, {0xfc, 0x00, 0x00, 0x00} + }, + { + {0x02, 0x01, 0x0a, 0x00}, {0x09, 0x01, 0x0a, 0x00}, + {0x17, 0x01, 0x0a, 0x00}, {0x28, 0x01, 0x0a, 0x01}, + {0x02, 0x01, 0x0d, 0x00}, {0x09, 0x01, 0x0d, 0x00}, + {0x17, 0x01, 0x0d, 0x00}, {0x28, 0x01, 0x0d, 0x01}, + {0x02, 0x01, 0x16, 0x00}, {0x09, 0x01, 0x16, 0x00}, + {0x17, 0x01, 0x16, 0x00}, {0x28, 0x01, 0x16, 0x01}, + {0xfd, 0x00, 0x00, 0x00}, {0xfd, 0x00, 0x00, 0x00}, + {0xfd, 0x00, 0x00, 0x00}, {0xfd, 0x00, 0x00, 0x00} + }, + { + {0x03, 0x01, 0x0a, 0x00}, {0x06, 0x01, 0x0a, 0x00}, + {0x0a, 0x01, 0x0a, 0x00}, {0x0f, 0x01, 0x0a, 0x00}, + {0x18, 0x01, 0x0a, 0x00}, {0x1f, 0x01, 0x0a, 0x00}, + {0x29, 0x01, 0x0a, 0x00}, {0x38, 0x01, 0x0a, 0x01}, + {0x03, 0x01, 0x0d, 0x00}, {0x06, 0x01, 0x0d, 0x00}, + {0x0a, 0x01, 0x0d, 0x00}, {0x0f, 0x01, 0x0d, 0x00}, + {0x18, 0x01, 0x0d, 0x00}, {0x1f, 0x01, 0x0d, 0x00}, + {0x29, 0x01, 0x0d, 0x00}, {0x38, 0x01, 0x0d, 0x01} + }, + /* 255 */ + { + {0x03, 0x01, 0x16, 0x00}, {0x06, 0x01, 0x16, 0x00}, + {0x0a, 0x01, 0x16, 0x00}, {0x0f, 0x01, 0x16, 0x00}, + {0x18, 0x01, 0x16, 0x00}, {0x1f, 0x01, 0x16, 0x00}, + {0x29, 0x01, 0x16, 0x00}, {0x38, 0x01, 0x16, 0x01}, + {0xff, 0x00, 0x00, 0x00}, {0xff, 0x00, 0x00, 0x00}, + {0xff, 0x00, 0x00, 0x00}, {0xff, 0x00, 0x00, 0x00}, + {0xff, 0x00, 0x00, 0x00}, {0xff, 0x00, 0x00, 0x00}, + {0xff, 0x00, 0x00, 0x00}, {0xff, 0x00, 0x00, 0x00} + } +}; + + +ngx_int_t +ngx_http_v2_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, + ngx_uint_t last, ngx_log_t *log) +{ + u_char *end, ch, ending; + + ch = 0; + ending = 1; + + end = src + len; + + while (src != end) { + ch = *src++; + + if (ngx_http_v2_huff_decode_bits(state, &ending, ch >> 4, dst) + != NGX_OK) + { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, + "http2 huffman decoding error at state %d: " + "bad code 0x%Xd", *state, ch >> 4); + + return NGX_ERROR; + } + + if (ngx_http_v2_huff_decode_bits(state, &ending, ch & 0xf, dst) + != NGX_OK) + { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, + "http2 huffman decoding error at state %d: " + "bad code 0x%Xd", *state, ch & 0xf); + + return NGX_ERROR; + } + } + + if (last) { + if (!ending) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, + "http2 huffman decoding error: " + "incomplete code 0x%Xd", ch); + + return NGX_ERROR; + } + + *state = 0; + } + + return NGX_OK; +} + + + +static ngx_inline ngx_int_t +ngx_http_v2_huff_decode_bits(u_char *state, u_char *ending, ngx_uint_t bits, + u_char **dst) +{ + ngx_http_v2_huff_decode_code_t code; + + code = ngx_http_v2_huff_decode_codes[*state][bits]; + + if (code.next == *state) { + return NGX_ERROR; + } + + if (code.emit) { + *(*dst)++ = code.sym; + } + + *ending = code.ending; + *state = code.next; + + return NGX_OK; +} diff --git a/app/nginx/src/http/v2/ngx_http_v2_huff_encode.c b/app/nginx/src/http/v2/ngx_http_v2_huff_encode.c new file mode 100644 index 0000000..3f822cd --- /dev/null +++ b/app/nginx/src/http/v2/ngx_http_v2_huff_encode.c @@ -0,0 +1,254 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + * Copyright (C) 2015 Vlad Krasnov + */ + + +#include +#include +#include + + +typedef struct { + uint32_t code; + uint32_t len; +} ngx_http_v2_huff_encode_code_t; + + +static ngx_http_v2_huff_encode_code_t ngx_http_v2_huff_encode_table[256] = +{ + {0x00001ff8, 13}, {0x007fffd8, 23}, {0x0fffffe2, 28}, {0x0fffffe3, 28}, + {0x0fffffe4, 28}, {0x0fffffe5, 28}, {0x0fffffe6, 28}, {0x0fffffe7, 28}, + {0x0fffffe8, 28}, {0x00ffffea, 24}, {0x3ffffffc, 30}, {0x0fffffe9, 28}, + {0x0fffffea, 28}, {0x3ffffffd, 30}, {0x0fffffeb, 28}, {0x0fffffec, 28}, + {0x0fffffed, 28}, {0x0fffffee, 28}, {0x0fffffef, 28}, {0x0ffffff0, 28}, + {0x0ffffff1, 28}, {0x0ffffff2, 28}, {0x3ffffffe, 30}, {0x0ffffff3, 28}, + {0x0ffffff4, 28}, {0x0ffffff5, 28}, {0x0ffffff6, 28}, {0x0ffffff7, 28}, + {0x0ffffff8, 28}, {0x0ffffff9, 28}, {0x0ffffffa, 28}, {0x0ffffffb, 28}, + {0x00000014, 6}, {0x000003f8, 10}, {0x000003f9, 10}, {0x00000ffa, 12}, + {0x00001ff9, 13}, {0x00000015, 6}, {0x000000f8, 8}, {0x000007fa, 11}, + {0x000003fa, 10}, {0x000003fb, 10}, {0x000000f9, 8}, {0x000007fb, 11}, + {0x000000fa, 8}, {0x00000016, 6}, {0x00000017, 6}, {0x00000018, 6}, + {0x00000000, 5}, {0x00000001, 5}, {0x00000002, 5}, {0x00000019, 6}, + {0x0000001a, 6}, {0x0000001b, 6}, {0x0000001c, 6}, {0x0000001d, 6}, + {0x0000001e, 6}, {0x0000001f, 6}, {0x0000005c, 7}, {0x000000fb, 8}, + {0x00007ffc, 15}, {0x00000020, 6}, {0x00000ffb, 12}, {0x000003fc, 10}, + {0x00001ffa, 13}, {0x00000021, 6}, {0x0000005d, 7}, {0x0000005e, 7}, + {0x0000005f, 7}, {0x00000060, 7}, {0x00000061, 7}, {0x00000062, 7}, + {0x00000063, 7}, {0x00000064, 7}, {0x00000065, 7}, {0x00000066, 7}, + {0x00000067, 7}, {0x00000068, 7}, {0x00000069, 7}, {0x0000006a, 7}, + {0x0000006b, 7}, {0x0000006c, 7}, {0x0000006d, 7}, {0x0000006e, 7}, + {0x0000006f, 7}, {0x00000070, 7}, {0x00000071, 7}, {0x00000072, 7}, + {0x000000fc, 8}, {0x00000073, 7}, {0x000000fd, 8}, {0x00001ffb, 13}, + {0x0007fff0, 19}, {0x00001ffc, 13}, {0x00003ffc, 14}, {0x00000022, 6}, + {0x00007ffd, 15}, {0x00000003, 5}, {0x00000023, 6}, {0x00000004, 5}, + {0x00000024, 6}, {0x00000005, 5}, {0x00000025, 6}, {0x00000026, 6}, + {0x00000027, 6}, {0x00000006, 5}, {0x00000074, 7}, {0x00000075, 7}, + {0x00000028, 6}, {0x00000029, 6}, {0x0000002a, 6}, {0x00000007, 5}, + {0x0000002b, 6}, {0x00000076, 7}, {0x0000002c, 6}, {0x00000008, 5}, + {0x00000009, 5}, {0x0000002d, 6}, {0x00000077, 7}, {0x00000078, 7}, + {0x00000079, 7}, {0x0000007a, 7}, {0x0000007b, 7}, {0x00007ffe, 15}, + {0x000007fc, 11}, {0x00003ffd, 14}, {0x00001ffd, 13}, {0x0ffffffc, 28}, + {0x000fffe6, 20}, {0x003fffd2, 22}, {0x000fffe7, 20}, {0x000fffe8, 20}, + {0x003fffd3, 22}, {0x003fffd4, 22}, {0x003fffd5, 22}, {0x007fffd9, 23}, + {0x003fffd6, 22}, {0x007fffda, 23}, {0x007fffdb, 23}, {0x007fffdc, 23}, + {0x007fffdd, 23}, {0x007fffde, 23}, {0x00ffffeb, 24}, {0x007fffdf, 23}, + {0x00ffffec, 24}, {0x00ffffed, 24}, {0x003fffd7, 22}, {0x007fffe0, 23}, + {0x00ffffee, 24}, {0x007fffe1, 23}, {0x007fffe2, 23}, {0x007fffe3, 23}, + {0x007fffe4, 23}, {0x001fffdc, 21}, {0x003fffd8, 22}, {0x007fffe5, 23}, + {0x003fffd9, 22}, {0x007fffe6, 23}, {0x007fffe7, 23}, {0x00ffffef, 24}, + {0x003fffda, 22}, {0x001fffdd, 21}, {0x000fffe9, 20}, {0x003fffdb, 22}, + {0x003fffdc, 22}, {0x007fffe8, 23}, {0x007fffe9, 23}, {0x001fffde, 21}, + {0x007fffea, 23}, {0x003fffdd, 22}, {0x003fffde, 22}, {0x00fffff0, 24}, + {0x001fffdf, 21}, {0x003fffdf, 22}, {0x007fffeb, 23}, {0x007fffec, 23}, + {0x001fffe0, 21}, {0x001fffe1, 21}, {0x003fffe0, 22}, {0x001fffe2, 21}, + {0x007fffed, 23}, {0x003fffe1, 22}, {0x007fffee, 23}, {0x007fffef, 23}, + {0x000fffea, 20}, {0x003fffe2, 22}, {0x003fffe3, 22}, {0x003fffe4, 22}, + {0x007ffff0, 23}, {0x003fffe5, 22}, {0x003fffe6, 22}, {0x007ffff1, 23}, + {0x03ffffe0, 26}, {0x03ffffe1, 26}, {0x000fffeb, 20}, {0x0007fff1, 19}, + {0x003fffe7, 22}, {0x007ffff2, 23}, {0x003fffe8, 22}, {0x01ffffec, 25}, + {0x03ffffe2, 26}, {0x03ffffe3, 26}, {0x03ffffe4, 26}, {0x07ffffde, 27}, + {0x07ffffdf, 27}, {0x03ffffe5, 26}, {0x00fffff1, 24}, {0x01ffffed, 25}, + {0x0007fff2, 19}, {0x001fffe3, 21}, {0x03ffffe6, 26}, {0x07ffffe0, 27}, + {0x07ffffe1, 27}, {0x03ffffe7, 26}, {0x07ffffe2, 27}, {0x00fffff2, 24}, + {0x001fffe4, 21}, {0x001fffe5, 21}, {0x03ffffe8, 26}, {0x03ffffe9, 26}, + {0x0ffffffd, 28}, {0x07ffffe3, 27}, {0x07ffffe4, 27}, {0x07ffffe5, 27}, + {0x000fffec, 20}, {0x00fffff3, 24}, {0x000fffed, 20}, {0x001fffe6, 21}, + {0x003fffe9, 22}, {0x001fffe7, 21}, {0x001fffe8, 21}, {0x007ffff3, 23}, + {0x003fffea, 22}, {0x003fffeb, 22}, {0x01ffffee, 25}, {0x01ffffef, 25}, + {0x00fffff4, 24}, {0x00fffff5, 24}, {0x03ffffea, 26}, {0x007ffff4, 23}, + {0x03ffffeb, 26}, {0x07ffffe6, 27}, {0x03ffffec, 26}, {0x03ffffed, 26}, + {0x07ffffe7, 27}, {0x07ffffe8, 27}, {0x07ffffe9, 27}, {0x07ffffea, 27}, + {0x07ffffeb, 27}, {0x0ffffffe, 28}, {0x07ffffec, 27}, {0x07ffffed, 27}, + {0x07ffffee, 27}, {0x07ffffef, 27}, {0x07fffff0, 27}, {0x03ffffee, 26} +}; + + +/* same as above, but embeds lowercase transformation */ +static ngx_http_v2_huff_encode_code_t ngx_http_v2_huff_encode_table_lc[256] = +{ + {0x00001ff8, 13}, {0x007fffd8, 23}, {0x0fffffe2, 28}, {0x0fffffe3, 28}, + {0x0fffffe4, 28}, {0x0fffffe5, 28}, {0x0fffffe6, 28}, {0x0fffffe7, 28}, + {0x0fffffe8, 28}, {0x00ffffea, 24}, {0x3ffffffc, 30}, {0x0fffffe9, 28}, + {0x0fffffea, 28}, {0x3ffffffd, 30}, {0x0fffffeb, 28}, {0x0fffffec, 28}, + {0x0fffffed, 28}, {0x0fffffee, 28}, {0x0fffffef, 28}, {0x0ffffff0, 28}, + {0x0ffffff1, 28}, {0x0ffffff2, 28}, {0x3ffffffe, 30}, {0x0ffffff3, 28}, + {0x0ffffff4, 28}, {0x0ffffff5, 28}, {0x0ffffff6, 28}, {0x0ffffff7, 28}, + {0x0ffffff8, 28}, {0x0ffffff9, 28}, {0x0ffffffa, 28}, {0x0ffffffb, 28}, + {0x00000014, 6}, {0x000003f8, 10}, {0x000003f9, 10}, {0x00000ffa, 12}, + {0x00001ff9, 13}, {0x00000015, 6}, {0x000000f8, 8}, {0x000007fa, 11}, + {0x000003fa, 10}, {0x000003fb, 10}, {0x000000f9, 8}, {0x000007fb, 11}, + {0x000000fa, 8}, {0x00000016, 6}, {0x00000017, 6}, {0x00000018, 6}, + {0x00000000, 5}, {0x00000001, 5}, {0x00000002, 5}, {0x00000019, 6}, + {0x0000001a, 6}, {0x0000001b, 6}, {0x0000001c, 6}, {0x0000001d, 6}, + {0x0000001e, 6}, {0x0000001f, 6}, {0x0000005c, 7}, {0x000000fb, 8}, + {0x00007ffc, 15}, {0x00000020, 6}, {0x00000ffb, 12}, {0x000003fc, 10}, + {0x00001ffa, 13}, {0x00000003, 5}, {0x00000023, 6}, {0x00000004, 5}, + {0x00000024, 6}, {0x00000005, 5}, {0x00000025, 6}, {0x00000026, 6}, + {0x00000027, 6}, {0x00000006, 5}, {0x00000074, 7}, {0x00000075, 7}, + {0x00000028, 6}, {0x00000029, 6}, {0x0000002a, 6}, {0x00000007, 5}, + {0x0000002b, 6}, {0x00000076, 7}, {0x0000002c, 6}, {0x00000008, 5}, + {0x00000009, 5}, {0x0000002d, 6}, {0x00000077, 7}, {0x00000078, 7}, + {0x00000079, 7}, {0x0000007a, 7}, {0x0000007b, 7}, {0x00001ffb, 13}, + {0x0007fff0, 19}, {0x00001ffc, 13}, {0x00003ffc, 14}, {0x00000022, 6}, + {0x00007ffd, 15}, {0x00000003, 5}, {0x00000023, 6}, {0x00000004, 5}, + {0x00000024, 6}, {0x00000005, 5}, {0x00000025, 6}, {0x00000026, 6}, + {0x00000027, 6}, {0x00000006, 5}, {0x00000074, 7}, {0x00000075, 7}, + {0x00000028, 6}, {0x00000029, 6}, {0x0000002a, 6}, {0x00000007, 5}, + {0x0000002b, 6}, {0x00000076, 7}, {0x0000002c, 6}, {0x00000008, 5}, + {0x00000009, 5}, {0x0000002d, 6}, {0x00000077, 7}, {0x00000078, 7}, + {0x00000079, 7}, {0x0000007a, 7}, {0x0000007b, 7}, {0x00007ffe, 15}, + {0x000007fc, 11}, {0x00003ffd, 14}, {0x00001ffd, 13}, {0x0ffffffc, 28}, + {0x000fffe6, 20}, {0x003fffd2, 22}, {0x000fffe7, 20}, {0x000fffe8, 20}, + {0x003fffd3, 22}, {0x003fffd4, 22}, {0x003fffd5, 22}, {0x007fffd9, 23}, + {0x003fffd6, 22}, {0x007fffda, 23}, {0x007fffdb, 23}, {0x007fffdc, 23}, + {0x007fffdd, 23}, {0x007fffde, 23}, {0x00ffffeb, 24}, {0x007fffdf, 23}, + {0x00ffffec, 24}, {0x00ffffed, 24}, {0x003fffd7, 22}, {0x007fffe0, 23}, + {0x00ffffee, 24}, {0x007fffe1, 23}, {0x007fffe2, 23}, {0x007fffe3, 23}, + {0x007fffe4, 23}, {0x001fffdc, 21}, {0x003fffd8, 22}, {0x007fffe5, 23}, + {0x003fffd9, 22}, {0x007fffe6, 23}, {0x007fffe7, 23}, {0x00ffffef, 24}, + {0x003fffda, 22}, {0x001fffdd, 21}, {0x000fffe9, 20}, {0x003fffdb, 22}, + {0x003fffdc, 22}, {0x007fffe8, 23}, {0x007fffe9, 23}, {0x001fffde, 21}, + {0x007fffea, 23}, {0x003fffdd, 22}, {0x003fffde, 22}, {0x00fffff0, 24}, + {0x001fffdf, 21}, {0x003fffdf, 22}, {0x007fffeb, 23}, {0x007fffec, 23}, + {0x001fffe0, 21}, {0x001fffe1, 21}, {0x003fffe0, 22}, {0x001fffe2, 21}, + {0x007fffed, 23}, {0x003fffe1, 22}, {0x007fffee, 23}, {0x007fffef, 23}, + {0x000fffea, 20}, {0x003fffe2, 22}, {0x003fffe3, 22}, {0x003fffe4, 22}, + {0x007ffff0, 23}, {0x003fffe5, 22}, {0x003fffe6, 22}, {0x007ffff1, 23}, + {0x03ffffe0, 26}, {0x03ffffe1, 26}, {0x000fffeb, 20}, {0x0007fff1, 19}, + {0x003fffe7, 22}, {0x007ffff2, 23}, {0x003fffe8, 22}, {0x01ffffec, 25}, + {0x03ffffe2, 26}, {0x03ffffe3, 26}, {0x03ffffe4, 26}, {0x07ffffde, 27}, + {0x07ffffdf, 27}, {0x03ffffe5, 26}, {0x00fffff1, 24}, {0x01ffffed, 25}, + {0x0007fff2, 19}, {0x001fffe3, 21}, {0x03ffffe6, 26}, {0x07ffffe0, 27}, + {0x07ffffe1, 27}, {0x03ffffe7, 26}, {0x07ffffe2, 27}, {0x00fffff2, 24}, + {0x001fffe4, 21}, {0x001fffe5, 21}, {0x03ffffe8, 26}, {0x03ffffe9, 26}, + {0x0ffffffd, 28}, {0x07ffffe3, 27}, {0x07ffffe4, 27}, {0x07ffffe5, 27}, + {0x000fffec, 20}, {0x00fffff3, 24}, {0x000fffed, 20}, {0x001fffe6, 21}, + {0x003fffe9, 22}, {0x001fffe7, 21}, {0x001fffe8, 21}, {0x007ffff3, 23}, + {0x003fffea, 22}, {0x003fffeb, 22}, {0x01ffffee, 25}, {0x01ffffef, 25}, + {0x00fffff4, 24}, {0x00fffff5, 24}, {0x03ffffea, 26}, {0x007ffff4, 23}, + {0x03ffffeb, 26}, {0x07ffffe6, 27}, {0x03ffffec, 26}, {0x03ffffed, 26}, + {0x07ffffe7, 27}, {0x07ffffe8, 27}, {0x07ffffe9, 27}, {0x07ffffea, 27}, + {0x07ffffeb, 27}, {0x0ffffffe, 28}, {0x07ffffec, 27}, {0x07ffffed, 27}, + {0x07ffffee, 27}, {0x07ffffef, 27}, {0x07fffff0, 27}, {0x03ffffee, 26} +}; + + +#if (NGX_PTR_SIZE == 8) + +#if (NGX_HAVE_LITTLE_ENDIAN) + +#if (NGX_HAVE_GCC_BSWAP64) +#define ngx_http_v2_huff_encode_buf(dst, buf) \ + (*(uint64_t *) (dst) = __builtin_bswap64(buf)) +#else +#define ngx_http_v2_huff_encode_buf(dst, buf) \ + ((dst)[0] = (u_char) ((buf) >> 56), \ + (dst)[1] = (u_char) ((buf) >> 48), \ + (dst)[2] = (u_char) ((buf) >> 40), \ + (dst)[3] = (u_char) ((buf) >> 32), \ + (dst)[4] = (u_char) ((buf) >> 24), \ + (dst)[5] = (u_char) ((buf) >> 16), \ + (dst)[6] = (u_char) ((buf) >> 8), \ + (dst)[7] = (u_char) (buf)) +#endif + +#else /* !NGX_HAVE_LITTLE_ENDIAN */ +#define ngx_http_v2_huff_encode_buf(dst, buf) \ + (*(uint64_t *) (dst) = (buf)) +#endif + +#else /* NGX_PTR_SIZE == 4 */ + +#define ngx_http_v2_huff_encode_buf(dst, buf) \ + (*(uint32_t *) (dst) = htonl(buf)) + +#endif + + +size_t +ngx_http_v2_huff_encode(u_char *src, size_t len, u_char *dst, ngx_uint_t lower) +{ + u_char *end; + size_t hlen; + ngx_uint_t buf, pending, code; + ngx_http_v2_huff_encode_code_t *table, *next; + + table = lower ? ngx_http_v2_huff_encode_table_lc + : ngx_http_v2_huff_encode_table; + hlen = 0; + buf = 0; + pending = 0; + + end = src + len; + + while (src != end) { + next = &table[*src++]; + + code = next->code; + pending += next->len; + + /* accumulate bits */ + if (pending < sizeof(buf) * 8) { + buf |= code << (sizeof(buf) * 8 - pending); + continue; + } + + if (hlen + sizeof(buf) >= len) { + return 0; + } + + pending -= sizeof(buf) * 8; + + buf |= code >> pending; + + ngx_http_v2_huff_encode_buf(&dst[hlen], buf); + + hlen += sizeof(buf); + + buf = pending ? code << (sizeof(buf) * 8 - pending) : 0; + } + + if (pending == 0) { + return hlen; + } + + buf |= (ngx_uint_t) -1 >> pending; + + pending = ngx_align(pending, 8); + + if (hlen + pending / 8 >= len) { + return 0; + } + + buf >>= sizeof(buf) * 8 - pending; + + do { + pending -= 8; + dst[hlen++] = (u_char) (buf >> pending); + } while (pending); + + return hlen; +} diff --git a/app/nginx/src/http/v2/ngx_http_v2_module.c b/app/nginx/src/http/v2/ngx_http_v2_module.c new file mode 100644 index 0000000..032abcb --- /dev/null +++ b/app/nginx/src/http/v2/ngx_http_v2_module.c @@ -0,0 +1,509 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include + + +static ngx_int_t ngx_http_v2_add_variables(ngx_conf_t *cf); + +static ngx_int_t ngx_http_v2_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + +static ngx_int_t ngx_http_v2_module_init(ngx_cycle_t *cycle); + +static void *ngx_http_v2_create_main_conf(ngx_conf_t *cf); +static char *ngx_http_v2_init_main_conf(ngx_conf_t *cf, void *conf); +static void *ngx_http_v2_create_srv_conf(ngx_conf_t *cf); +static char *ngx_http_v2_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static void *ngx_http_v2_create_loc_conf(ngx_conf_t *cf); +static char *ngx_http_v2_merge_loc_conf(ngx_conf_t *cf, void *parent, + void *child); + +static char *ngx_http_v2_recv_buffer_size(ngx_conf_t *cf, void *post, + void *data); +static char *ngx_http_v2_pool_size(ngx_conf_t *cf, void *post, void *data); +static char *ngx_http_v2_preread_size(ngx_conf_t *cf, void *post, void *data); +static char *ngx_http_v2_streams_index_mask(ngx_conf_t *cf, void *post, + void *data); +static char *ngx_http_v2_chunk_size(ngx_conf_t *cf, void *post, void *data); +static char *ngx_http_v2_spdy_deprecated(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +static ngx_conf_post_t ngx_http_v2_recv_buffer_size_post = + { ngx_http_v2_recv_buffer_size }; +static ngx_conf_post_t ngx_http_v2_pool_size_post = + { ngx_http_v2_pool_size }; +static ngx_conf_post_t ngx_http_v2_preread_size_post = + { ngx_http_v2_preread_size }; +static ngx_conf_post_t ngx_http_v2_streams_index_mask_post = + { ngx_http_v2_streams_index_mask }; +static ngx_conf_post_t ngx_http_v2_chunk_size_post = + { ngx_http_v2_chunk_size }; + + +static ngx_command_t ngx_http_v2_commands[] = { + + { ngx_string("http2_recv_buffer_size"), + NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_MAIN_CONF_OFFSET, + offsetof(ngx_http_v2_main_conf_t, recv_buffer_size), + &ngx_http_v2_recv_buffer_size_post }, + + { ngx_string("http2_pool_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v2_srv_conf_t, pool_size), + &ngx_http_v2_pool_size_post }, + + { ngx_string("http2_max_concurrent_streams"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v2_srv_conf_t, concurrent_streams), + NULL }, + + { ngx_string("http2_max_requests"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v2_srv_conf_t, max_requests), + NULL }, + + { ngx_string("http2_max_field_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_v2_srv_conf_t, max_field_size), + NULL }, + + { ngx_string("http2_max_header_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_v2_srv_conf_t, max_header_size), + NULL }, + + { ngx_string("http2_body_preread_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_v2_srv_conf_t, preread_size), + &ngx_http_v2_preread_size_post }, + + { ngx_string("http2_streams_index_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v2_srv_conf_t, streams_index_mask), + &ngx_http_v2_streams_index_mask_post }, + + { ngx_string("http2_recv_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v2_srv_conf_t, recv_timeout), + NULL }, + + { ngx_string("http2_idle_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v2_srv_conf_t, idle_timeout), + NULL }, + + { ngx_string("http2_chunk_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_v2_loc_conf_t, chunk_size), + &ngx_http_v2_chunk_size_post }, + + { ngx_string("spdy_recv_buffer_size"), + NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_http_v2_spdy_deprecated, + NGX_HTTP_MAIN_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("spdy_pool_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_v2_spdy_deprecated, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("spdy_max_concurrent_streams"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_v2_spdy_deprecated, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("spdy_streams_index_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_v2_spdy_deprecated, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("spdy_recv_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_v2_spdy_deprecated, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("spdy_keepalive_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_v2_spdy_deprecated, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("spdy_headers_comp"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_v2_spdy_deprecated, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("spdy_chunk_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_http_v2_spdy_deprecated, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_v2_module_ctx = { + ngx_http_v2_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + ngx_http_v2_create_main_conf, /* create main configuration */ + ngx_http_v2_init_main_conf, /* init main configuration */ + + ngx_http_v2_create_srv_conf, /* create server configuration */ + ngx_http_v2_merge_srv_conf, /* merge server configuration */ + + ngx_http_v2_create_loc_conf, /* create location configuration */ + ngx_http_v2_merge_loc_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_v2_module = { + NGX_MODULE_V1, + &ngx_http_v2_module_ctx, /* module context */ + ngx_http_v2_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + ngx_http_v2_module_init, /* 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_v2_vars[] = { + + { ngx_string("http2"), NULL, + ngx_http_v2_variable, 0, 0, 0 }, + + { ngx_null_string, NULL, NULL, 0, 0, 0 } +}; + + +static ngx_int_t +ngx_http_v2_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_v2_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_v2_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + + if (r->stream) { +#if (NGX_HTTP_SSL) + + if (r->connection->ssl) { + v->len = sizeof("h2") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "h2"; + + return NGX_OK; + } + +#endif + v->len = sizeof("h2c") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "h2c"; + + return NGX_OK; + } + + *v = ngx_http_variable_null_value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_module_init(ngx_cycle_t *cycle) +{ + return NGX_OK; +} + + +static void * +ngx_http_v2_create_main_conf(ngx_conf_t *cf) +{ + ngx_http_v2_main_conf_t *h2mcf; + + h2mcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v2_main_conf_t)); + if (h2mcf == NULL) { + return NULL; + } + + h2mcf->recv_buffer_size = NGX_CONF_UNSET_SIZE; + + return h2mcf; +} + + +static char * +ngx_http_v2_init_main_conf(ngx_conf_t *cf, void *conf) +{ + ngx_http_v2_main_conf_t *h2mcf = conf; + + ngx_conf_init_size_value(h2mcf->recv_buffer_size, 256 * 1024); + + return NGX_CONF_OK; +} + + +static void * +ngx_http_v2_create_srv_conf(ngx_conf_t *cf) +{ + ngx_http_v2_srv_conf_t *h2scf; + + h2scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v2_srv_conf_t)); + if (h2scf == NULL) { + return NULL; + } + + h2scf->pool_size = NGX_CONF_UNSET_SIZE; + + h2scf->concurrent_streams = NGX_CONF_UNSET_UINT; + h2scf->max_requests = NGX_CONF_UNSET_UINT; + + h2scf->max_field_size = NGX_CONF_UNSET_SIZE; + h2scf->max_header_size = NGX_CONF_UNSET_SIZE; + + h2scf->preread_size = NGX_CONF_UNSET_SIZE; + + h2scf->streams_index_mask = NGX_CONF_UNSET_UINT; + + h2scf->recv_timeout = NGX_CONF_UNSET_MSEC; + h2scf->idle_timeout = NGX_CONF_UNSET_MSEC; + + return h2scf; +} + + +static char * +ngx_http_v2_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_v2_srv_conf_t *prev = parent; + ngx_http_v2_srv_conf_t *conf = child; + + ngx_conf_merge_size_value(conf->pool_size, prev->pool_size, 4096); + + ngx_conf_merge_uint_value(conf->concurrent_streams, + prev->concurrent_streams, 128); + ngx_conf_merge_uint_value(conf->max_requests, prev->max_requests, 1000); + + ngx_conf_merge_size_value(conf->max_field_size, prev->max_field_size, + 4096); + ngx_conf_merge_size_value(conf->max_header_size, prev->max_header_size, + 16384); + + ngx_conf_merge_size_value(conf->preread_size, prev->preread_size, 65536); + + ngx_conf_merge_uint_value(conf->streams_index_mask, + prev->streams_index_mask, 32 - 1); + + ngx_conf_merge_msec_value(conf->recv_timeout, + prev->recv_timeout, 30000); + ngx_conf_merge_msec_value(conf->idle_timeout, + prev->idle_timeout, 180000); + + return NGX_CONF_OK; +} + + +static void * +ngx_http_v2_create_loc_conf(ngx_conf_t *cf) +{ + ngx_http_v2_loc_conf_t *h2lcf; + + h2lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v2_loc_conf_t)); + if (h2lcf == NULL) { + return NULL; + } + + h2lcf->chunk_size = NGX_CONF_UNSET_SIZE; + + return h2lcf; +} + + +static char * +ngx_http_v2_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_v2_loc_conf_t *prev = parent; + ngx_http_v2_loc_conf_t *conf = child; + + ngx_conf_merge_size_value(conf->chunk_size, prev->chunk_size, 8 * 1024); + + return NGX_CONF_OK; +} + + +static char * +ngx_http_v2_recv_buffer_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp <= 2 * NGX_HTTP_V2_STATE_BUFFER_SIZE) { + return "value is too small"; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_v2_pool_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp < NGX_MIN_POOL_SIZE) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the pool size must be no less than %uz", + NGX_MIN_POOL_SIZE); + + return NGX_CONF_ERROR; + } + + if (*sp % NGX_POOL_ALIGNMENT) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the pool size must be a multiple of %uz", + NGX_POOL_ALIGNMENT); + + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_v2_preread_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp > NGX_HTTP_V2_MAX_WINDOW) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the maximum body preread buffer size is %uz", + NGX_HTTP_V2_MAX_WINDOW); + + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_v2_streams_index_mask(ngx_conf_t *cf, void *post, void *data) +{ + ngx_uint_t *np = data; + + ngx_uint_t mask; + + mask = *np - 1; + + if (*np == 0 || (*np & mask)) { + return "must be a power of two"; + } + + *np = mask; + + return NGX_CONF_OK; +} + + +static char * +ngx_http_v2_chunk_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the http2 chunk size cannot be zero"); + + return NGX_CONF_ERROR; + } + + if (*sp > NGX_HTTP_V2_MAX_FRAME_SIZE) { + *sp = NGX_HTTP_V2_MAX_FRAME_SIZE; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_v2_spdy_deprecated(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "invalid directive \"%V\": ngx_http_spdy_module " + "was superseded by ngx_http_v2_module", &cmd->name); + + return NGX_CONF_OK; +} diff --git a/app/nginx/src/http/v2/ngx_http_v2_module.h b/app/nginx/src/http/v2/ngx_http_v2_module.h new file mode 100644 index 0000000..540f826 --- /dev/null +++ b/app/nginx/src/http/v2/ngx_http_v2_module.h @@ -0,0 +1,44 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#ifndef _NGX_HTTP_V2_MODULE_H_INCLUDED_ +#define _NGX_HTTP_V2_MODULE_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + size_t recv_buffer_size; + u_char *recv_buffer; +} ngx_http_v2_main_conf_t; + + +typedef struct { + size_t pool_size; + ngx_uint_t concurrent_streams; + ngx_uint_t max_requests; + size_t max_field_size; + size_t max_header_size; + size_t preread_size; + ngx_uint_t streams_index_mask; + ngx_msec_t recv_timeout; + ngx_msec_t idle_timeout; +} ngx_http_v2_srv_conf_t; + + +typedef struct { + size_t chunk_size; +} ngx_http_v2_loc_conf_t; + + +extern ngx_module_t ngx_http_v2_module; + + +#endif /* _NGX_HTTP_V2_MODULE_H_INCLUDED_ */ diff --git a/app/nginx/src/http/v2/ngx_http_v2_table.c b/app/nginx/src/http/v2/ngx_http_v2_table.c new file mode 100644 index 0000000..a73748a --- /dev/null +++ b/app/nginx/src/http/v2/ngx_http_v2_table.c @@ -0,0 +1,349 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include + + +#define NGX_HTTP_V2_TABLE_SIZE 4096 + + +static ngx_int_t ngx_http_v2_table_account(ngx_http_v2_connection_t *h2c, + size_t size); + + +static ngx_http_v2_header_t ngx_http_v2_static_table[] = { + { ngx_string(":authority"), ngx_string("") }, + { ngx_string(":method"), ngx_string("GET") }, + { ngx_string(":method"), ngx_string("POST") }, + { ngx_string(":path"), ngx_string("/") }, + { ngx_string(":path"), ngx_string("/index.html") }, + { ngx_string(":scheme"), ngx_string("http") }, + { ngx_string(":scheme"), ngx_string("https") }, + { ngx_string(":status"), ngx_string("200") }, + { ngx_string(":status"), ngx_string("204") }, + { ngx_string(":status"), ngx_string("206") }, + { ngx_string(":status"), ngx_string("304") }, + { ngx_string(":status"), ngx_string("400") }, + { ngx_string(":status"), ngx_string("404") }, + { ngx_string(":status"), ngx_string("500") }, + { ngx_string("accept-charset"), ngx_string("") }, + { ngx_string("accept-encoding"), ngx_string("gzip, deflate") }, + { ngx_string("accept-language"), ngx_string("") }, + { ngx_string("accept-ranges"), ngx_string("") }, + { ngx_string("accept"), ngx_string("") }, + { ngx_string("access-control-allow-origin"), ngx_string("") }, + { ngx_string("age"), ngx_string("") }, + { ngx_string("allow"), ngx_string("") }, + { ngx_string("authorization"), ngx_string("") }, + { ngx_string("cache-control"), ngx_string("") }, + { ngx_string("content-disposition"), ngx_string("") }, + { ngx_string("content-encoding"), ngx_string("") }, + { ngx_string("content-language"), ngx_string("") }, + { ngx_string("content-length"), ngx_string("") }, + { ngx_string("content-location"), ngx_string("") }, + { ngx_string("content-range"), ngx_string("") }, + { ngx_string("content-type"), ngx_string("") }, + { ngx_string("cookie"), ngx_string("") }, + { ngx_string("date"), ngx_string("") }, + { ngx_string("etag"), ngx_string("") }, + { ngx_string("expect"), ngx_string("") }, + { ngx_string("expires"), ngx_string("") }, + { ngx_string("from"), ngx_string("") }, + { ngx_string("host"), ngx_string("") }, + { ngx_string("if-match"), ngx_string("") }, + { ngx_string("if-modified-since"), ngx_string("") }, + { ngx_string("if-none-match"), ngx_string("") }, + { ngx_string("if-range"), ngx_string("") }, + { ngx_string("if-unmodified-since"), ngx_string("") }, + { ngx_string("last-modified"), ngx_string("") }, + { ngx_string("link"), ngx_string("") }, + { ngx_string("location"), ngx_string("") }, + { ngx_string("max-forwards"), ngx_string("") }, + { ngx_string("proxy-authenticate"), ngx_string("") }, + { ngx_string("proxy-authorization"), ngx_string("") }, + { ngx_string("range"), ngx_string("") }, + { ngx_string("referer"), ngx_string("") }, + { ngx_string("refresh"), ngx_string("") }, + { ngx_string("retry-after"), ngx_string("") }, + { ngx_string("server"), ngx_string("") }, + { ngx_string("set-cookie"), ngx_string("") }, + { ngx_string("strict-transport-security"), ngx_string("") }, + { ngx_string("transfer-encoding"), ngx_string("") }, + { ngx_string("user-agent"), ngx_string("") }, + { ngx_string("vary"), ngx_string("") }, + { ngx_string("via"), ngx_string("") }, + { ngx_string("www-authenticate"), ngx_string("") }, +}; + +#define NGX_HTTP_V2_STATIC_TABLE_ENTRIES \ + (sizeof(ngx_http_v2_static_table) \ + / sizeof(ngx_http_v2_header_t)) + + +ngx_int_t +ngx_http_v2_get_indexed_header(ngx_http_v2_connection_t *h2c, ngx_uint_t index, + ngx_uint_t name_only) +{ + u_char *p; + size_t rest; + ngx_http_v2_header_t *entry; + + if (index == 0) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent invalid hpack table index 0"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 get indexed %s: %ui", + name_only ? "header" : "header name", index); + + index--; + + if (index < NGX_HTTP_V2_STATIC_TABLE_ENTRIES) { + h2c->state.header = ngx_http_v2_static_table[index]; + return NGX_OK; + } + + index -= NGX_HTTP_V2_STATIC_TABLE_ENTRIES; + + if (index < h2c->hpack.added - h2c->hpack.deleted) { + index = (h2c->hpack.added - index - 1) % h2c->hpack.allocated; + entry = h2c->hpack.entries[index]; + + p = ngx_pnalloc(h2c->state.pool, entry->name.len + 1); + if (p == NULL) { + return NGX_ERROR; + } + + h2c->state.header.name.len = entry->name.len; + h2c->state.header.name.data = p; + + rest = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - entry->name.data; + + if (entry->name.len > rest) { + p = ngx_cpymem(p, entry->name.data, rest); + p = ngx_cpymem(p, h2c->hpack.storage, entry->name.len - rest); + + } else { + p = ngx_cpymem(p, entry->name.data, entry->name.len); + } + + *p = '\0'; + + if (name_only) { + return NGX_OK; + } + + p = ngx_pnalloc(h2c->state.pool, entry->value.len + 1); + if (p == NULL) { + return NGX_ERROR; + } + + h2c->state.header.value.len = entry->value.len; + h2c->state.header.value.data = p; + + rest = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - entry->value.data; + + if (entry->value.len > rest) { + p = ngx_cpymem(p, entry->value.data, rest); + p = ngx_cpymem(p, h2c->hpack.storage, entry->value.len - rest); + + } else { + p = ngx_cpymem(p, entry->value.data, entry->value.len); + } + + *p = '\0'; + + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent out of bound hpack table index: %ui", index); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v2_add_header(ngx_http_v2_connection_t *h2c, + ngx_http_v2_header_t *header) +{ + size_t avail; + ngx_uint_t index; + ngx_http_v2_header_t *entry, **entries; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 add header to hpack table: \"%V: %V\"", + &header->name, &header->value); + + if (h2c->hpack.entries == NULL) { + h2c->hpack.allocated = 64; + h2c->hpack.size = NGX_HTTP_V2_TABLE_SIZE; + h2c->hpack.free = NGX_HTTP_V2_TABLE_SIZE; + + h2c->hpack.entries = ngx_palloc(h2c->connection->pool, + sizeof(ngx_http_v2_header_t *) + * h2c->hpack.allocated); + if (h2c->hpack.entries == NULL) { + return NGX_ERROR; + } + + h2c->hpack.storage = ngx_palloc(h2c->connection->pool, + h2c->hpack.free); + if (h2c->hpack.storage == NULL) { + return NGX_ERROR; + } + + h2c->hpack.pos = h2c->hpack.storage; + } + + if (ngx_http_v2_table_account(h2c, header->name.len + header->value.len) + != NGX_OK) + { + return NGX_OK; + } + + if (h2c->hpack.reused == h2c->hpack.deleted) { + entry = ngx_palloc(h2c->connection->pool, sizeof(ngx_http_v2_header_t)); + if (entry == NULL) { + return NGX_ERROR; + } + + } else { + entry = h2c->hpack.entries[h2c->hpack.reused++ % h2c->hpack.allocated]; + } + + avail = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - h2c->hpack.pos; + + entry->name.len = header->name.len; + entry->name.data = h2c->hpack.pos; + + if (avail >= header->name.len) { + h2c->hpack.pos = ngx_cpymem(h2c->hpack.pos, header->name.data, + header->name.len); + } else { + ngx_memcpy(h2c->hpack.pos, header->name.data, avail); + h2c->hpack.pos = ngx_cpymem(h2c->hpack.storage, + header->name.data + avail, + header->name.len - avail); + avail = NGX_HTTP_V2_TABLE_SIZE; + } + + avail -= header->name.len; + + entry->value.len = header->value.len; + entry->value.data = h2c->hpack.pos; + + if (avail >= header->value.len) { + h2c->hpack.pos = ngx_cpymem(h2c->hpack.pos, header->value.data, + header->value.len); + } else { + ngx_memcpy(h2c->hpack.pos, header->value.data, avail); + h2c->hpack.pos = ngx_cpymem(h2c->hpack.storage, + header->value.data + avail, + header->value.len - avail); + } + + if (h2c->hpack.allocated == h2c->hpack.added - h2c->hpack.deleted) { + + entries = ngx_palloc(h2c->connection->pool, + sizeof(ngx_http_v2_header_t *) + * (h2c->hpack.allocated + 64)); + if (entries == NULL) { + return NGX_ERROR; + } + + index = h2c->hpack.deleted % h2c->hpack.allocated; + + ngx_memcpy(entries, &h2c->hpack.entries[index], + (h2c->hpack.allocated - index) + * sizeof(ngx_http_v2_header_t *)); + + ngx_memcpy(&entries[h2c->hpack.allocated - index], h2c->hpack.entries, + index * sizeof(ngx_http_v2_header_t *)); + + (void) ngx_pfree(h2c->connection->pool, h2c->hpack.entries); + + h2c->hpack.entries = entries; + + h2c->hpack.added = h2c->hpack.allocated; + h2c->hpack.deleted = 0; + h2c->hpack.reused = 0; + h2c->hpack.allocated += 64; + } + + h2c->hpack.entries[h2c->hpack.added++ % h2c->hpack.allocated] = entry; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_table_account(ngx_http_v2_connection_t *h2c, size_t size) +{ + ngx_http_v2_header_t *entry; + + size += 32; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack table account: %uz free:%uz", + size, h2c->hpack.free); + + if (size <= h2c->hpack.free) { + h2c->hpack.free -= size; + return NGX_OK; + } + + if (size > h2c->hpack.size) { + h2c->hpack.deleted = h2c->hpack.added; + h2c->hpack.free = h2c->hpack.size; + return NGX_DECLINED; + } + + do { + entry = h2c->hpack.entries[h2c->hpack.deleted++ % h2c->hpack.allocated]; + h2c->hpack.free += 32 + entry->name.len + entry->value.len; + } while (size > h2c->hpack.free); + + h2c->hpack.free -= size; + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v2_table_size(ngx_http_v2_connection_t *h2c, size_t size) +{ + ssize_t needed; + ngx_http_v2_header_t *entry; + + if (size > NGX_HTTP_V2_TABLE_SIZE) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent invalid table size update: %uz", size); + + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 new hpack table size: %uz was:%uz", + size, h2c->hpack.size); + + needed = h2c->hpack.size - size; + + while (needed > (ssize_t) h2c->hpack.free) { + entry = h2c->hpack.entries[h2c->hpack.deleted++ % h2c->hpack.allocated]; + h2c->hpack.free += 32 + entry->name.len + entry->value.len; + } + + h2c->hpack.size = size; + h2c->hpack.free -= needed; + + return NGX_OK; +} -- cgit 1.2.3-korg