Initial Commit
This commit is contained in:
124
headers-more-nginx-module-0.32/src/ddebug.h
Normal file
124
headers-more-nginx-module-0.32/src/ddebug.h
Normal file
@@ -0,0 +1,124 @@
|
||||
#ifndef DDEBUG_H
|
||||
#define DDEBUG_H
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_http.h>
|
||||
#include <nginx.h>
|
||||
|
||||
|
||||
#if defined(DDEBUG) && (DDEBUG)
|
||||
|
||||
# if (NGX_HAVE_VARIADIC_MACROS)
|
||||
|
||||
# define dd(...) fprintf(stderr, "headers-more *** %s: ", __func__); \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
fprintf(stderr, " at %s line %d.\n", __FILE__, __LINE__)
|
||||
|
||||
# else
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
static ngx_inline void
|
||||
dd(const char * fmt, ...) {
|
||||
}
|
||||
|
||||
# endif
|
||||
|
||||
# if DDEBUG > 1
|
||||
|
||||
# define dd_enter() dd_enter_helper(r, __func__)
|
||||
|
||||
# if defined(nginx_version) && nginx_version >= 8011
|
||||
# define dd_main_req_count r->main->count
|
||||
# else
|
||||
# define dd_main_req_count 0
|
||||
# endif
|
||||
|
||||
static ngx_inline void
|
||||
dd_enter_helper(ngx_http_request_t *r, const char *func)
|
||||
{
|
||||
ngx_http_posted_request_t *pr;
|
||||
|
||||
fprintf(stderr, "headers-more *** enter %s %.*s %.*s?%.*s c:%d m:%p r:%p ar:%p pr:%p",
|
||||
func,
|
||||
(int) r->method_name.len, r->method_name.data,
|
||||
(int) r->uri.len, r->uri.data,
|
||||
(int) r->args.len, r->args.data,
|
||||
(int) dd_main_req_count, r->main,
|
||||
r, r->connection->data, r->parent);
|
||||
|
||||
if (r->posted_requests) {
|
||||
fprintf(stderr, " posted:");
|
||||
|
||||
for (pr = r->posted_requests; pr; pr = pr->next) {
|
||||
fprintf(stderr, "%p,", pr);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
# else
|
||||
|
||||
# define dd_enter()
|
||||
|
||||
# endif
|
||||
|
||||
#else
|
||||
|
||||
# if (NGX_HAVE_VARIADIC_MACROS)
|
||||
|
||||
# define dd(...)
|
||||
|
||||
# define dd_enter()
|
||||
|
||||
# else
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
static ngx_inline void
|
||||
dd(const char * fmt, ...) {
|
||||
}
|
||||
|
||||
static ngx_inline void
|
||||
dd_enter() {
|
||||
}
|
||||
|
||||
# endif
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(DDEBUG) && (DDEBUG)
|
||||
|
||||
#define dd_check_read_event_handler(r) \
|
||||
dd("r->read_event_handler = %s", \
|
||||
r->read_event_handler == ngx_http_block_reading ? \
|
||||
"ngx_http_block_reading" : \
|
||||
r->read_event_handler == ngx_http_test_reading ? \
|
||||
"ngx_http_test_reading" : \
|
||||
r->read_event_handler == ngx_http_request_empty_handler ? \
|
||||
"ngx_http_request_empty_handler" : "UNKNOWN")
|
||||
|
||||
#define dd_check_write_event_handler(r) \
|
||||
dd("r->write_event_handler = %s", \
|
||||
r->write_event_handler == ngx_http_handler ? \
|
||||
"ngx_http_handler" : \
|
||||
r->write_event_handler == ngx_http_core_run_phases ? \
|
||||
"ngx_http_core_run_phases" : \
|
||||
r->write_event_handler == ngx_http_request_empty_handler ? \
|
||||
"ngx_http_request_empty_handler" : "UNKNOWN")
|
||||
|
||||
#else
|
||||
|
||||
#define dd_check_read_event_handler(r)
|
||||
#define dd_check_write_event_handler(r)
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* DDEBUG_H */
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Yichun Zhang (agentzh)
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DDEBUG
|
||||
#define DDEBUG 0
|
||||
#endif
|
||||
#include "ddebug.h"
|
||||
|
||||
|
||||
#include "ngx_http_headers_more_filter_module.h"
|
||||
#include "ngx_http_headers_more_headers_out.h"
|
||||
#include "ngx_http_headers_more_headers_in.h"
|
||||
#include "ngx_http_headers_more_util.h"
|
||||
#include <ngx_config.h>
|
||||
|
||||
|
||||
/* config handlers */
|
||||
|
||||
static void *ngx_http_headers_more_create_loc_conf(ngx_conf_t *cf);
|
||||
static char *ngx_http_headers_more_merge_loc_conf(ngx_conf_t *cf,
|
||||
void *parent, void *child);
|
||||
static void *ngx_http_headers_more_create_main_conf(ngx_conf_t *cf);
|
||||
static ngx_int_t ngx_http_headers_more_post_config(ngx_conf_t *cf);
|
||||
|
||||
/* post-read-phase handler */
|
||||
|
||||
static ngx_int_t ngx_http_headers_more_handler(ngx_http_request_t *r);
|
||||
|
||||
/* filter handlers */
|
||||
|
||||
static ngx_int_t ngx_http_headers_more_filter_init(ngx_conf_t *cf);
|
||||
|
||||
ngx_uint_t ngx_http_headers_more_location_hash = 0;
|
||||
|
||||
|
||||
static ngx_command_t ngx_http_headers_more_filter_commands[] = {
|
||||
|
||||
{ ngx_string("more_set_headers"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
|
||||
|NGX_CONF_1MORE,
|
||||
ngx_http_headers_more_set_headers,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
0,
|
||||
NULL},
|
||||
|
||||
{ ngx_string("more_clear_headers"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
|
||||
|NGX_CONF_1MORE,
|
||||
ngx_http_headers_more_clear_headers,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
0,
|
||||
NULL},
|
||||
|
||||
{ ngx_string("more_set_input_headers"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
|
||||
|NGX_CONF_1MORE,
|
||||
ngx_http_headers_more_set_input_headers,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
0,
|
||||
NULL},
|
||||
|
||||
{ ngx_string("more_clear_input_headers"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
|
||||
|NGX_CONF_1MORE,
|
||||
ngx_http_headers_more_clear_input_headers,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
0,
|
||||
NULL},
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_http_module_t ngx_http_headers_more_filter_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
ngx_http_headers_more_post_config, /* postconfiguration */
|
||||
|
||||
ngx_http_headers_more_create_main_conf, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
|
||||
NULL, /* create server configuration */
|
||||
NULL, /* merge server configuration */
|
||||
|
||||
ngx_http_headers_more_create_loc_conf, /* create location configuration */
|
||||
ngx_http_headers_more_merge_loc_conf /* merge location configuration */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_http_headers_more_filter_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_http_headers_more_filter_module_ctx, /* module context */
|
||||
ngx_http_headers_more_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 volatile ngx_cycle_t *ngx_http_headers_more_prev_cycle = NULL;
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_headers_more_filter(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
ngx_uint_t i;
|
||||
ngx_http_headers_more_loc_conf_t *conf;
|
||||
ngx_http_headers_more_cmd_t *cmd;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
||||
"headers more header filter, uri \"%V\"", &r->uri);
|
||||
|
||||
conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_more_filter_module);
|
||||
|
||||
if (conf->cmds) {
|
||||
cmd = conf->cmds->elts;
|
||||
for (i = 0; i < conf->cmds->nelts; i++) {
|
||||
if (cmd[i].is_input) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rc = ngx_http_headers_more_exec_cmd(r, &cmd[i]);
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ngx_http_next_header_filter(r);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_headers_more_filter_init(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_http_next_header_filter = ngx_http_top_header_filter;
|
||||
ngx_http_top_header_filter = ngx_http_headers_more_filter;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
ngx_http_headers_more_create_loc_conf(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_http_headers_more_loc_conf_t *conf;
|
||||
|
||||
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_headers_more_loc_conf_t));
|
||||
if (conf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* set by ngx_pcalloc():
|
||||
*
|
||||
* conf->cmds = NULL;
|
||||
*/
|
||||
|
||||
return conf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_http_headers_more_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
|
||||
{
|
||||
ngx_uint_t i;
|
||||
ngx_uint_t orig_len;
|
||||
ngx_http_headers_more_cmd_t *prev_cmd, *cmd;
|
||||
ngx_http_headers_more_loc_conf_t *prev = parent;
|
||||
ngx_http_headers_more_loc_conf_t *conf = child;
|
||||
|
||||
if (conf->cmds == NULL || conf->cmds->nelts == 0) {
|
||||
conf->cmds = prev->cmds;
|
||||
|
||||
} else if (prev->cmds && prev->cmds->nelts) {
|
||||
orig_len = conf->cmds->nelts;
|
||||
|
||||
(void) ngx_array_push_n(conf->cmds, prev->cmds->nelts);
|
||||
|
||||
cmd = conf->cmds->elts;
|
||||
|
||||
for (i = 0; i < orig_len; i++) {
|
||||
cmd[conf->cmds->nelts - 1 - i] = cmd[orig_len - 1 - i];
|
||||
}
|
||||
|
||||
prev_cmd = prev->cmds->elts;
|
||||
|
||||
for (i = 0; i < prev->cmds->nelts; i++) {
|
||||
cmd[i] = prev_cmd[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_headers_more_post_config(ngx_conf_t *cf)
|
||||
{
|
||||
int multi_http_blocks;
|
||||
ngx_int_t rc;
|
||||
ngx_http_handler_pt *h;
|
||||
ngx_http_core_main_conf_t *cmcf;
|
||||
|
||||
ngx_http_headers_more_main_conf_t *hmcf;
|
||||
|
||||
ngx_http_headers_more_location_hash =
|
||||
ngx_http_headers_more_hash_literal("location");
|
||||
|
||||
hmcf = ngx_http_conf_get_module_main_conf(cf,
|
||||
ngx_http_headers_more_filter_module);
|
||||
|
||||
if (ngx_http_headers_more_prev_cycle != ngx_cycle) {
|
||||
ngx_http_headers_more_prev_cycle = ngx_cycle;
|
||||
multi_http_blocks = 0;
|
||||
|
||||
} else {
|
||||
multi_http_blocks = 1;
|
||||
}
|
||||
|
||||
if (multi_http_blocks || hmcf->requires_filter) {
|
||||
rc = ngx_http_headers_more_filter_init(cf);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hmcf->requires_handler) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
|
||||
|
||||
h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);
|
||||
if (h == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
*h = ngx_http_headers_more_handler;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_headers_more_handler(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
ngx_uint_t i;
|
||||
ngx_http_headers_more_loc_conf_t *conf;
|
||||
ngx_http_headers_more_main_conf_t *hmcf;
|
||||
ngx_http_headers_more_cmd_t *cmd;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
||||
"headers more rewrite handler, uri \"%V\"", &r->uri);
|
||||
|
||||
hmcf = ngx_http_get_module_main_conf(r,
|
||||
ngx_http_headers_more_filter_module);
|
||||
|
||||
if (!hmcf->postponed_to_phase_end) {
|
||||
ngx_http_core_main_conf_t *cmcf;
|
||||
ngx_http_phase_handler_t tmp;
|
||||
ngx_http_phase_handler_t *ph;
|
||||
ngx_http_phase_handler_t *cur_ph;
|
||||
ngx_http_phase_handler_t *last_ph;
|
||||
|
||||
hmcf->postponed_to_phase_end = 1;
|
||||
|
||||
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
|
||||
|
||||
ph = cmcf->phase_engine.handlers;
|
||||
cur_ph = &ph[r->phase_handler];
|
||||
last_ph = &ph[cur_ph->next - 1];
|
||||
|
||||
if (cur_ph < last_ph) {
|
||||
dd("swaping the contents of cur_ph and last_ph...");
|
||||
|
||||
tmp = *cur_ph;
|
||||
|
||||
memmove(cur_ph, cur_ph + 1,
|
||||
(last_ph - cur_ph) * sizeof (ngx_http_phase_handler_t));
|
||||
|
||||
*last_ph = tmp;
|
||||
|
||||
r->phase_handler--; /* redo the current ph */
|
||||
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
}
|
||||
|
||||
dd("running phase handler...");
|
||||
|
||||
conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_more_filter_module);
|
||||
|
||||
if (conf->cmds) {
|
||||
if (r->http_version < NGX_HTTP_VERSION_10) {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
cmd = conf->cmds->elts;
|
||||
for (i = 0; i < conf->cmds->nelts; i++) {
|
||||
if (!cmd[i].is_input) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rc = ngx_http_headers_more_exec_input_cmd(r, &cmd[i]);
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
ngx_http_headers_more_create_main_conf(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_http_headers_more_main_conf_t *hmcf;
|
||||
|
||||
hmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_headers_more_main_conf_t));
|
||||
if (hmcf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* set by ngx_pcalloc:
|
||||
* hmcf->postponed_to_phase_end = 0;
|
||||
* hmcf->requires_filter = 0;
|
||||
* hmcf->requires_handler = 0;
|
||||
*/
|
||||
|
||||
return hmcf;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) Yichun Zhang (agentzh)
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NGX_HTTP_HEADERS_MORE_FILTER_MODULE_H
|
||||
#define NGX_HTTP_HEADERS_MORE_FILTER_MODULE_H
|
||||
|
||||
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_http.h>
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
typedef enum {
|
||||
ngx_http_headers_more_opcode_set,
|
||||
ngx_http_headers_more_opcode_clear
|
||||
} ngx_http_headers_more_opcode_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_array_t *types; /* of ngx_str_t */
|
||||
ngx_array_t *statuses; /* of ngx_uint_t */
|
||||
ngx_array_t *headers; /* of ngx_http_header_val_t */
|
||||
ngx_flag_t is_input;
|
||||
} ngx_http_headers_more_cmd_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_array_t *cmds; /* of ngx_http_headers_more_cmd_t */
|
||||
} ngx_http_headers_more_loc_conf_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_int_t postponed_to_phase_end;
|
||||
ngx_int_t requires_filter;
|
||||
ngx_int_t requires_handler;
|
||||
} ngx_http_headers_more_main_conf_t;
|
||||
|
||||
|
||||
typedef struct ngx_http_headers_more_header_val_s
|
||||
ngx_http_headers_more_header_val_t;
|
||||
|
||||
|
||||
typedef ngx_int_t (*ngx_http_headers_more_set_header_pt)(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t name;
|
||||
ngx_uint_t offset;
|
||||
ngx_http_headers_more_set_header_pt handler;
|
||||
} ngx_http_headers_more_set_header_t;
|
||||
|
||||
|
||||
struct ngx_http_headers_more_header_val_s {
|
||||
ngx_http_complex_value_t value;
|
||||
ngx_uint_t hash;
|
||||
ngx_str_t key;
|
||||
ngx_http_headers_more_set_header_pt handler;
|
||||
ngx_uint_t offset;
|
||||
ngx_flag_t replace;
|
||||
ngx_flag_t wildcard;
|
||||
};
|
||||
|
||||
|
||||
extern ngx_module_t ngx_http_headers_more_filter_module;
|
||||
|
||||
|
||||
#ifndef ngx_str_set
|
||||
#define ngx_str_set(str, text) \
|
||||
(str)->len = sizeof(text) - 1; (str)->data = (u_char *) text
|
||||
#endif
|
||||
|
||||
|
||||
#define ngx_http_headers_more_assert(a) assert(a)
|
||||
|
||||
|
||||
#endif /* NGX_HTTP_HEADERS_MORE_FILTER_MODULE_H */
|
||||
@@ -0,0 +1,882 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Yichun Zhang (agentzh)
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DDEBUG
|
||||
#define DDEBUG 0
|
||||
#endif
|
||||
#include "ddebug.h"
|
||||
|
||||
|
||||
#include "ngx_http_headers_more_headers_in.h"
|
||||
#include "ngx_http_headers_more_util.h"
|
||||
#include <ctype.h>
|
||||
|
||||
|
||||
static char *ngx_http_headers_more_parse_directive(ngx_conf_t *cf,
|
||||
ngx_command_t *ngx_cmd, void *conf,
|
||||
ngx_http_headers_more_opcode_t opcode);
|
||||
static int ngx_http_headers_more_check_type(ngx_http_request_t *r,
|
||||
ngx_array_t *types);
|
||||
static ngx_int_t ngx_http_set_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_set_header_helper(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value,
|
||||
ngx_table_elt_t **output_header);
|
||||
static ngx_int_t ngx_http_set_builtin_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_set_user_agent_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_set_content_length_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_clear_builtin_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_clear_content_length_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_set_host_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_set_connection_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_set_builtin_multi_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_headers_more_validate_host(ngx_str_t *host,
|
||||
ngx_pool_t *pool, ngx_uint_t alloc);
|
||||
|
||||
|
||||
static ngx_http_headers_more_set_header_t ngx_http_headers_more_set_handlers[]
|
||||
= {
|
||||
|
||||
{ ngx_string("Host"),
|
||||
offsetof(ngx_http_headers_in_t, host),
|
||||
ngx_http_set_host_header },
|
||||
|
||||
{ ngx_string("Connection"),
|
||||
offsetof(ngx_http_headers_in_t, connection),
|
||||
ngx_http_set_connection_header },
|
||||
|
||||
{ ngx_string("If-Modified-Since"),
|
||||
offsetof(ngx_http_headers_in_t, if_modified_since),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
#if defined(nginx_version) && nginx_version >= 9002
|
||||
{ ngx_string("If-Unmodified-Since"),
|
||||
offsetof(ngx_http_headers_in_t, if_unmodified_since),
|
||||
ngx_http_set_builtin_header },
|
||||
#endif
|
||||
|
||||
#if defined(nginx_version) && nginx_version >= 1003003
|
||||
{ ngx_string("If-Match"),
|
||||
offsetof(ngx_http_headers_in_t, if_match),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("If-None-Match"),
|
||||
offsetof(ngx_http_headers_in_t, if_none_match),
|
||||
ngx_http_set_builtin_header },
|
||||
#endif
|
||||
|
||||
{ ngx_string("User-Agent"),
|
||||
offsetof(ngx_http_headers_in_t, user_agent),
|
||||
ngx_http_set_user_agent_header },
|
||||
|
||||
{ ngx_string("Referer"),
|
||||
offsetof(ngx_http_headers_in_t, referer),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Content-Length"),
|
||||
offsetof(ngx_http_headers_in_t, content_length),
|
||||
ngx_http_set_content_length_header },
|
||||
|
||||
{ ngx_string("Content-Type"),
|
||||
offsetof(ngx_http_headers_in_t, content_type),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Range"),
|
||||
offsetof(ngx_http_headers_in_t, range),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("If-Range"),
|
||||
offsetof(ngx_http_headers_in_t, if_range),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Transfer-Encoding"),
|
||||
offsetof(ngx_http_headers_in_t, transfer_encoding),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Expect"),
|
||||
offsetof(ngx_http_headers_in_t, expect),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
#if defined(nginx_version) && nginx_version >= 1003013
|
||||
{ ngx_string("Upgrade"),
|
||||
offsetof(ngx_http_headers_in_t, upgrade),
|
||||
ngx_http_set_builtin_header },
|
||||
#endif
|
||||
|
||||
#if (NGX_HTTP_GZIP)
|
||||
{ ngx_string("Accept-Encoding"),
|
||||
offsetof(ngx_http_headers_in_t, accept_encoding),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Via"), offsetof(ngx_http_headers_in_t, via),
|
||||
ngx_http_set_builtin_header },
|
||||
#endif
|
||||
|
||||
{ ngx_string("Authorization"),
|
||||
offsetof(ngx_http_headers_in_t, authorization),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Keep-Alive"),
|
||||
offsetof(ngx_http_headers_in_t, keep_alive),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
#if (NGX_HTTP_X_FORWARDED_FOR)
|
||||
{ ngx_string("X-Forwarded-For"),
|
||||
offsetof(ngx_http_headers_in_t, x_forwarded_for),
|
||||
ngx_http_set_builtin_multi_header },
|
||||
|
||||
#endif
|
||||
|
||||
#if (NGX_HTTP_REALIP)
|
||||
{ ngx_string("X-Real-IP"),
|
||||
offsetof(ngx_http_headers_in_t, x_real_ip),
|
||||
ngx_http_set_builtin_header },
|
||||
#endif
|
||||
|
||||
#if (NGX_HTTP_DAV)
|
||||
{ ngx_string("Depth"), offsetof(ngx_http_headers_in_t, depth),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Destination"), offsetof(ngx_http_headers_in_t, destination),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Overwrite"), offsetof(ngx_http_headers_in_t, overwrite),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Date"), offsetof(ngx_http_headers_in_t, date),
|
||||
ngx_http_set_builtin_header },
|
||||
#endif
|
||||
|
||||
{ ngx_string("Cookie"),
|
||||
offsetof(ngx_http_headers_in_t, cookies),
|
||||
ngx_http_set_builtin_multi_header },
|
||||
|
||||
{ ngx_null_string, 0, ngx_http_set_header }
|
||||
};
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_http_headers_more_exec_input_cmd(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_cmd_t *cmd)
|
||||
{
|
||||
ngx_str_t value;
|
||||
ngx_http_headers_more_header_val_t *h;
|
||||
ngx_uint_t i;
|
||||
|
||||
if (!cmd->headers) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (cmd->types && !ngx_http_headers_more_check_type(r, cmd->types)) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
h = cmd->headers->elts;
|
||||
for (i = 0; i < cmd->headers->nelts; i++) {
|
||||
|
||||
if (ngx_http_complex_value(r, &h[i].value, &value) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (value.len) {
|
||||
value.len--; /* remove the trailing '\0' added by
|
||||
ngx_http_headers_more_parse_header */
|
||||
}
|
||||
|
||||
if (h[i].handler(r, &h[i], &value) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_set_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
return ngx_http_set_header_helper(r, hv, value, NULL);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_set_header_helper(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value,
|
||||
ngx_table_elt_t **output_header)
|
||||
{
|
||||
ngx_table_elt_t *h, *matched;
|
||||
ngx_list_part_t *part;
|
||||
ngx_uint_t i;
|
||||
ngx_uint_t rc;
|
||||
|
||||
dd_enter();
|
||||
|
||||
matched = NULL;
|
||||
|
||||
retry:
|
||||
|
||||
part = &r->headers_in.headers.part;
|
||||
h = part->elts;
|
||||
|
||||
for (i = 0; /* void */; i++) {
|
||||
dd("i: %d, part: %p", (int) i, part);
|
||||
|
||||
if (i >= part->nelts) {
|
||||
if (part->next == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
part = part->next;
|
||||
h = part->elts;
|
||||
i = 0;
|
||||
}
|
||||
|
||||
if (h[i].key.len == hv->key.len
|
||||
&& ngx_strncasecmp(h[i].key.data, hv->key.data,
|
||||
h[i].key.len) == 0)
|
||||
{
|
||||
if (value->len == 0 || (matched && matched != &h[i])) {
|
||||
h[i].hash = 0;
|
||||
|
||||
rc = ngx_http_headers_more_rm_header_helper(
|
||||
&r->headers_in.headers, part, i);
|
||||
|
||||
ngx_http_headers_more_assert(
|
||||
!(r->headers_in.headers.part.next == NULL
|
||||
&& r->headers_in.headers.last
|
||||
!= &r->headers_in.headers.part));
|
||||
|
||||
if (rc == NGX_OK) {
|
||||
if (output_header) {
|
||||
*output_header = NULL;
|
||||
}
|
||||
|
||||
goto retry;
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
h[i].value = *value;
|
||||
|
||||
if (output_header) {
|
||||
*output_header = &h[i];
|
||||
dd("setting existing builtin input header");
|
||||
}
|
||||
|
||||
if (matched == NULL) {
|
||||
matched = &h[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (value->len == 0 || hv->replace) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (r->headers_in.headers.last == NULL) {
|
||||
/* must be 400 bad request */
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
h = ngx_list_push(&r->headers_in.headers);
|
||||
|
||||
if (h == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
dd("created new header for %.*s", (int) hv->key.len, hv->key.data);
|
||||
|
||||
if (value->len == 0) {
|
||||
h->hash = 0;
|
||||
|
||||
} else {
|
||||
h->hash = hv->hash;
|
||||
}
|
||||
|
||||
h->key = hv->key;
|
||||
h->value = *value;
|
||||
|
||||
h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
|
||||
if (h->lowcase_key == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
|
||||
|
||||
if (output_header) {
|
||||
*output_header = h;
|
||||
|
||||
while (r != r->main) {
|
||||
r->parent->headers_in = r->headers_in;
|
||||
r = r->parent;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_set_builtin_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
ngx_table_elt_t *h, **old;
|
||||
|
||||
dd("entered set_builtin_header (input)");
|
||||
|
||||
if (hv->offset) {
|
||||
old = (ngx_table_elt_t **) ((char *) &r->headers_in + hv->offset);
|
||||
|
||||
} else {
|
||||
old = NULL;
|
||||
}
|
||||
|
||||
dd("old builtin ptr ptr: %p", old);
|
||||
if (old) {
|
||||
dd("old builtin ptr: %p", *old);
|
||||
}
|
||||
|
||||
if (old == NULL || *old == NULL) {
|
||||
dd("set normal header");
|
||||
return ngx_http_set_header_helper(r, hv, value, old);
|
||||
}
|
||||
|
||||
h = *old;
|
||||
|
||||
if (value->len == 0) {
|
||||
h->hash = 0;
|
||||
h->value = *value;
|
||||
|
||||
return ngx_http_set_header_helper(r, hv, value, old);
|
||||
}
|
||||
|
||||
h->hash = hv->hash;
|
||||
h->value = *value;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_set_host_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
ngx_str_t host;
|
||||
|
||||
if (value->len) {
|
||||
host= *value;
|
||||
|
||||
if (ngx_http_headers_more_validate_host(&host, r->pool, 0) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
r->headers_in.server = host;
|
||||
|
||||
} else {
|
||||
r->headers_in.server = *value;
|
||||
}
|
||||
|
||||
return ngx_http_set_builtin_header(r, hv, value);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_set_content_length_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
off_t len;
|
||||
|
||||
if (value->len == 0) {
|
||||
return ngx_http_clear_content_length_header(r, hv, value);
|
||||
}
|
||||
|
||||
len = ngx_atosz(value->data, value->len);
|
||||
if (len == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
dd("reset headers_in.content_length_n to %d", (int) len);
|
||||
|
||||
r->headers_in.content_length_n = len;
|
||||
|
||||
return ngx_http_set_builtin_header(r, hv, value);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_clear_content_length_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
r->headers_in.content_length_n = -1;
|
||||
|
||||
return ngx_http_clear_builtin_header(r, hv, value);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_clear_builtin_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
value->len = 0;
|
||||
return ngx_http_set_builtin_header(r, hv, value);
|
||||
}
|
||||
|
||||
|
||||
char *
|
||||
ngx_http_headers_more_set_input_headers(ngx_conf_t *cf,
|
||||
ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
return ngx_http_headers_more_parse_directive(cf, cmd, conf,
|
||||
ngx_http_headers_more_opcode_set);
|
||||
}
|
||||
|
||||
|
||||
char *
|
||||
ngx_http_headers_more_clear_input_headers(ngx_conf_t *cf,
|
||||
ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
return ngx_http_headers_more_parse_directive(cf, cmd, conf,
|
||||
ngx_http_headers_more_opcode_clear);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
ngx_http_headers_more_check_type(ngx_http_request_t *r, ngx_array_t *types)
|
||||
{
|
||||
ngx_uint_t i;
|
||||
ngx_str_t *t;
|
||||
ngx_str_t actual_type;
|
||||
|
||||
if (r->headers_in.content_type == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
actual_type = r->headers_in.content_type->value;
|
||||
if (actual_type.len == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
dd("headers_in->content_type: %.*s",
|
||||
(int) actual_type.len,
|
||||
actual_type.data);
|
||||
|
||||
t = types->elts;
|
||||
for (i = 0; i < types->nelts; i++) {
|
||||
dd("...comparing with type [%.*s]", (int) t[i].len, t[i].data);
|
||||
|
||||
if (actual_type.len == t[i].len
|
||||
&& ngx_strncmp(actual_type.data, t[i].data, t[i].len) == 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_http_headers_more_parse_directive(ngx_conf_t *cf, ngx_command_t *ngx_cmd,
|
||||
void *conf, ngx_http_headers_more_opcode_t opcode)
|
||||
{
|
||||
ngx_http_headers_more_loc_conf_t *hlcf = conf;
|
||||
|
||||
ngx_uint_t i;
|
||||
ngx_http_headers_more_cmd_t *cmd;
|
||||
ngx_str_t *arg;
|
||||
ngx_flag_t ignore_next_arg;
|
||||
ngx_str_t *cmd_name;
|
||||
ngx_int_t rc;
|
||||
ngx_flag_t replace = 0;
|
||||
ngx_http_headers_more_header_val_t *h;
|
||||
|
||||
ngx_http_headers_more_main_conf_t *hmcf;
|
||||
|
||||
if (hlcf->cmds == NULL) {
|
||||
hlcf->cmds = ngx_array_create(cf->pool, 1,
|
||||
sizeof(ngx_http_headers_more_cmd_t));
|
||||
|
||||
if (hlcf->cmds == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
cmd = ngx_array_push(hlcf->cmds);
|
||||
|
||||
if (cmd == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
cmd->headers = ngx_array_create(cf->pool, 1,
|
||||
sizeof(ngx_http_headers_more_header_val_t));
|
||||
|
||||
if (cmd->headers == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
cmd->types = ngx_array_create(cf->pool, 1, sizeof(ngx_str_t));
|
||||
if (cmd->types == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
cmd->statuses = NULL;
|
||||
|
||||
arg = cf->args->elts;
|
||||
|
||||
cmd_name = &arg[0];
|
||||
|
||||
ignore_next_arg = 0;
|
||||
|
||||
for (i = 1; i < cf->args->nelts; i++) {
|
||||
if (ignore_next_arg) {
|
||||
ignore_next_arg = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg[i].len == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg[i].data[0] != '-') {
|
||||
rc = ngx_http_headers_more_parse_header(cf, cmd_name,
|
||||
&arg[i], cmd->headers,
|
||||
opcode,
|
||||
ngx_http_headers_more_set_handlers);
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg[i].len == 2) {
|
||||
if (arg[i].data[1] == 't') {
|
||||
if (i == cf->args->nelts - 1) {
|
||||
ngx_log_error(NGX_LOG_ERR, cf->log, 0,
|
||||
"%V: option -t takes an argument.",
|
||||
cmd_name);
|
||||
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
rc = ngx_http_headers_more_parse_types(cf->log, cmd_name,
|
||||
&arg[i + 1],
|
||||
cmd->types);
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
ignore_next_arg = 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg[i].data[1] == 'r') {
|
||||
dd("Found replace flag");
|
||||
replace = 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ERR, cf->log, 0,
|
||||
"%V: invalid option name: \"%V\"", cmd_name, &arg[i]);
|
||||
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
dd("Found %d types, and %d headers",
|
||||
(int) cmd->types->nelts,
|
||||
(int) cmd->headers->nelts);
|
||||
|
||||
if (cmd->headers->nelts == 0) {
|
||||
ngx_pfree(cf->pool, cmd->headers);
|
||||
cmd->headers = NULL;
|
||||
|
||||
} else {
|
||||
h = cmd->headers->elts;
|
||||
for (i = 0; i < cmd->headers->nelts; i++) {
|
||||
h[i].replace = replace;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd->types->nelts == 0) {
|
||||
ngx_pfree(cf->pool, cmd->types);
|
||||
cmd->types = NULL;
|
||||
}
|
||||
|
||||
cmd->is_input = 1;
|
||||
|
||||
hmcf = ngx_http_conf_get_module_main_conf(cf,
|
||||
ngx_http_headers_more_filter_module);
|
||||
|
||||
hmcf->requires_handler = 1;
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
/* borrowed the code from ngx_http_request.c:ngx_http_process_user_agent */
|
||||
static ngx_int_t
|
||||
ngx_http_set_user_agent_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
u_char *user_agent, *msie;
|
||||
|
||||
/* clear existing settings */
|
||||
|
||||
r->headers_in.msie = 0;
|
||||
r->headers_in.msie6 = 0;
|
||||
r->headers_in.opera = 0;
|
||||
r->headers_in.gecko = 0;
|
||||
r->headers_in.chrome = 0;
|
||||
r->headers_in.safari = 0;
|
||||
r->headers_in.konqueror = 0;
|
||||
|
||||
if (value->len == 0) {
|
||||
return ngx_http_set_builtin_header(r, hv, value);
|
||||
}
|
||||
|
||||
/* check some widespread browsers */
|
||||
|
||||
user_agent = value->data;
|
||||
|
||||
msie = ngx_strstrn(user_agent, "MSIE ", 5 - 1);
|
||||
|
||||
if (msie && msie + 7 < user_agent + value->len) {
|
||||
|
||||
r->headers_in.msie = 1;
|
||||
|
||||
if (msie[6] == '.') {
|
||||
|
||||
switch (msie[5]) {
|
||||
case '4':
|
||||
case '5':
|
||||
r->headers_in.msie6 = 1;
|
||||
break;
|
||||
case '6':
|
||||
if (ngx_strstrn(msie + 8, "SV1", 3 - 1) == NULL) {
|
||||
r->headers_in.msie6 = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ngx_strstrn(user_agent, "Opera", 5 - 1)) {
|
||||
r->headers_in.opera = 1;
|
||||
r->headers_in.msie = 0;
|
||||
r->headers_in.msie6 = 0;
|
||||
}
|
||||
|
||||
if (!r->headers_in.msie && !r->headers_in.opera) {
|
||||
|
||||
if (ngx_strstrn(user_agent, "Gecko/", 6 - 1)) {
|
||||
r->headers_in.gecko = 1;
|
||||
|
||||
} else if (ngx_strstrn(user_agent, "Chrome/", 7 - 1)) {
|
||||
r->headers_in.chrome = 1;
|
||||
|
||||
} else if (ngx_strstrn(user_agent, "Safari/", 7 - 1)
|
||||
&& ngx_strstrn(user_agent, "Mac OS X", 8 - 1))
|
||||
{
|
||||
r->headers_in.safari = 1;
|
||||
|
||||
} else if (ngx_strstrn(user_agent, "Konqueror", 9 - 1)) {
|
||||
r->headers_in.konqueror = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ngx_http_set_builtin_header(r, hv, value);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_set_connection_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
r->headers_in.connection_type = 0;
|
||||
|
||||
if (value->len == 0) {
|
||||
return ngx_http_set_builtin_header(r, hv, value);
|
||||
}
|
||||
|
||||
if (ngx_strcasestrn(value->data, "close", 5 - 1)) {
|
||||
r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
|
||||
r->headers_in.keep_alive_n = -1;
|
||||
|
||||
} else if (ngx_strcasestrn(value->data, "keep-alive", 10 - 1)) {
|
||||
r->headers_in.connection_type = NGX_HTTP_CONNECTION_KEEP_ALIVE;
|
||||
}
|
||||
|
||||
return ngx_http_set_builtin_header(r, hv, value);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_set_builtin_multi_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
ngx_array_t *headers;
|
||||
ngx_table_elt_t **v, *h;
|
||||
|
||||
if (r->headers_out.status == 400 || r->headers_in.headers.last == NULL) {
|
||||
/* must be a 400 Bad Request */
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
headers = (ngx_array_t *) ((char *) &r->headers_in + hv->offset);
|
||||
|
||||
if (headers->nelts > 0) {
|
||||
ngx_array_destroy(headers);
|
||||
|
||||
if (ngx_array_init(headers, r->pool, 2,
|
||||
sizeof(ngx_table_elt_t *))
|
||||
!= NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
dd("clear multi-value headers: %d", (int) headers->nelts);
|
||||
}
|
||||
|
||||
#if 1
|
||||
if (headers->nalloc == 0) {
|
||||
if (ngx_array_init(headers, r->pool, 2,
|
||||
sizeof(ngx_table_elt_t *))
|
||||
!= NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
h = NULL;
|
||||
if (ngx_http_set_header_helper(r, hv, value, &h) == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (value->len == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
dd("new cookie header: %p", h);
|
||||
|
||||
v = ngx_array_push(headers);
|
||||
if (v == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
*v = h;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_headers_more_validate_host(ngx_str_t *host, ngx_pool_t *pool,
|
||||
ngx_uint_t alloc)
|
||||
{
|
||||
u_char *h, ch;
|
||||
size_t i, dot_pos, host_len;
|
||||
|
||||
enum {
|
||||
sw_usual = 0,
|
||||
sw_literal,
|
||||
sw_rest
|
||||
} state;
|
||||
|
||||
dot_pos = host->len;
|
||||
host_len = host->len;
|
||||
|
||||
h = host->data;
|
||||
|
||||
state = sw_usual;
|
||||
|
||||
for (i = 0; i < host->len; i++) {
|
||||
ch = h[i];
|
||||
|
||||
switch (ch) {
|
||||
|
||||
case '.':
|
||||
if (dot_pos == i - 1) {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
dot_pos = i;
|
||||
break;
|
||||
|
||||
case ':':
|
||||
if (state == sw_usual) {
|
||||
host_len = i;
|
||||
state = sw_rest;
|
||||
}
|
||||
break;
|
||||
|
||||
case '[':
|
||||
if (i == 0) {
|
||||
state = sw_literal;
|
||||
}
|
||||
break;
|
||||
|
||||
case ']':
|
||||
if (state == sw_literal) {
|
||||
host_len = i + 1;
|
||||
state = sw_rest;
|
||||
}
|
||||
break;
|
||||
|
||||
case '\0':
|
||||
return NGX_DECLINED;
|
||||
|
||||
default:
|
||||
|
||||
if (ngx_path_separator(ch)) {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
if (ch >= 'A' && ch <= 'Z') {
|
||||
alloc = 1;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dot_pos == host_len - 1) {
|
||||
host_len--;
|
||||
}
|
||||
|
||||
if (host_len == 0) {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
if (alloc) {
|
||||
host->data = ngx_pnalloc(pool, host_len);
|
||||
if (host->data == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_strlow(host->data, h, host_len);
|
||||
}
|
||||
|
||||
host->len = host_len;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) Yichun Zhang (agentzh)
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NGX_HTTP_HEADERS_MORE_INPUT_HEADERS_H
|
||||
#define NGX_HTTP_HEADERS_MORE_INPUT_HEADERS_H
|
||||
|
||||
|
||||
#include "ngx_http_headers_more_filter_module.h"
|
||||
|
||||
|
||||
/* output header setters and clearers */
|
||||
|
||||
ngx_int_t ngx_http_headers_more_exec_input_cmd(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_cmd_t *cmd);
|
||||
|
||||
char *ngx_http_headers_more_set_input_headers(ngx_conf_t *cf,
|
||||
ngx_command_t *cmd, void *conf);
|
||||
|
||||
char *ngx_http_headers_more_clear_input_headers(ngx_conf_t *cf,
|
||||
ngx_command_t *cmd, void *conf);
|
||||
|
||||
|
||||
#endif /* NGX_HTTP_HEADERS_MORE_INPUT_HEADERS_H */
|
||||
@@ -0,0 +1,716 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Yichun Zhang (agentzh)
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DDEBUG
|
||||
#define DDEBUG 0
|
||||
#endif
|
||||
#include "ddebug.h"
|
||||
|
||||
|
||||
#include "ngx_http_headers_more_headers_out.h"
|
||||
#include "ngx_http_headers_more_util.h"
|
||||
#include <ctype.h>
|
||||
|
||||
|
||||
static char *
|
||||
ngx_http_headers_more_parse_directive(ngx_conf_t *cf, ngx_command_t *ngx_cmd,
|
||||
void *conf, ngx_http_headers_more_opcode_t opcode);
|
||||
static ngx_flag_t ngx_http_headers_more_check_type(ngx_http_request_t *r,
|
||||
ngx_array_t *types);
|
||||
static ngx_flag_t ngx_http_headers_more_check_status(ngx_http_request_t *r,
|
||||
ngx_array_t *statuses);
|
||||
static ngx_int_t ngx_http_set_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_set_header_helper(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value,
|
||||
ngx_table_elt_t **output_header, ngx_flag_t no_create);
|
||||
static ngx_int_t ngx_http_set_builtin_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_set_accept_ranges_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_set_content_length_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_set_content_type_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_clear_builtin_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_clear_content_length_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
static ngx_int_t ngx_http_set_builtin_multi_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value);
|
||||
|
||||
|
||||
static ngx_http_headers_more_set_header_t ngx_http_headers_more_set_handlers[]
|
||||
= {
|
||||
|
||||
{ ngx_string("Server"),
|
||||
offsetof(ngx_http_headers_out_t, server),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Date"),
|
||||
offsetof(ngx_http_headers_out_t, date),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Content-Encoding"),
|
||||
offsetof(ngx_http_headers_out_t, content_encoding),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Location"),
|
||||
offsetof(ngx_http_headers_out_t, location),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Refresh"),
|
||||
offsetof(ngx_http_headers_out_t, refresh),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Last-Modified"),
|
||||
offsetof(ngx_http_headers_out_t, last_modified),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Content-Range"),
|
||||
offsetof(ngx_http_headers_out_t, content_range),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Accept-Ranges"),
|
||||
offsetof(ngx_http_headers_out_t, accept_ranges),
|
||||
ngx_http_set_accept_ranges_header },
|
||||
|
||||
{ ngx_string("WWW-Authenticate"),
|
||||
offsetof(ngx_http_headers_out_t, www_authenticate),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Expires"),
|
||||
offsetof(ngx_http_headers_out_t, expires),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("E-Tag"),
|
||||
offsetof(ngx_http_headers_out_t, etag),
|
||||
ngx_http_set_builtin_header },
|
||||
|
||||
{ ngx_string("Content-Length"),
|
||||
offsetof(ngx_http_headers_out_t, content_length),
|
||||
ngx_http_set_content_length_header },
|
||||
|
||||
{ ngx_string("Content-Type"),
|
||||
0,
|
||||
ngx_http_set_content_type_header },
|
||||
|
||||
{ ngx_string("Cache-Control"),
|
||||
offsetof(ngx_http_headers_out_t, cache_control),
|
||||
ngx_http_set_builtin_multi_header },
|
||||
|
||||
{ ngx_null_string, 0, ngx_http_set_header }
|
||||
};
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_http_headers_more_exec_cmd(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_cmd_t *cmd)
|
||||
{
|
||||
ngx_str_t value;
|
||||
ngx_http_headers_more_header_val_t *h;
|
||||
ngx_uint_t i;
|
||||
|
||||
if (!cmd->headers) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (cmd->types && !ngx_http_headers_more_check_type(r, cmd->types)) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (cmd->statuses
|
||||
&& !ngx_http_headers_more_check_status(r, cmd->statuses))
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
h = cmd->headers->elts;
|
||||
for (i = 0; i < cmd->headers->nelts; i++) {
|
||||
|
||||
if (ngx_http_complex_value(r, &h[i].value, &value) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (value.len) {
|
||||
value.len--; /* remove the trailing '\0' added by
|
||||
ngx_http_headers_more_parse_header */
|
||||
}
|
||||
|
||||
if (h[i].handler(r, &h[i], &value) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_set_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
return ngx_http_set_header_helper(r, hv, value, NULL, 0);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_set_header_helper(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value,
|
||||
ngx_table_elt_t **output_header, ngx_flag_t no_create)
|
||||
{
|
||||
ngx_table_elt_t *h;
|
||||
ngx_list_part_t *part;
|
||||
ngx_uint_t i;
|
||||
ngx_flag_t matched = 0;
|
||||
|
||||
dd_enter();
|
||||
|
||||
#if 1
|
||||
if (r->headers_out.location
|
||||
&& r->headers_out.location->value.len
|
||||
&& r->headers_out.location->value.data[0] == '/')
|
||||
{
|
||||
/* XXX ngx_http_core_find_config_phase, for example,
|
||||
* may not initialize the "key" and "hash" fields
|
||||
* for a nasty optimization purpose, and
|
||||
* we have to work-around it here */
|
||||
|
||||
r->headers_out.location->hash = ngx_http_headers_more_location_hash;
|
||||
ngx_str_set(&r->headers_out.location->key, "Location");
|
||||
}
|
||||
#endif
|
||||
|
||||
part = &r->headers_out.headers.part;
|
||||
h = part->elts;
|
||||
|
||||
for (i = 0; /* void */; i++) {
|
||||
|
||||
if (i >= part->nelts) {
|
||||
if (part->next == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
part = part->next;
|
||||
h = part->elts;
|
||||
i = 0;
|
||||
}
|
||||
|
||||
if (h[i].hash == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!hv->wildcard
|
||||
&& h[i].key.len == hv->key.len
|
||||
&& ngx_strncasecmp(h[i].key.data, hv->key.data,
|
||||
h[i].key.len) == 0)
|
||||
{
|
||||
goto matched;
|
||||
}
|
||||
|
||||
if (hv->wildcard
|
||||
&& h[i].key.len >= hv->key.len - 1
|
||||
&& ngx_strncasecmp(h[i].key.data, hv->key.data,
|
||||
hv->key.len - 1) == 0)
|
||||
{
|
||||
goto matched;
|
||||
}
|
||||
|
||||
/* not matched */
|
||||
continue;
|
||||
|
||||
matched:
|
||||
|
||||
if (value->len == 0 || matched) {
|
||||
dd("clearing normal header for %.*s", (int) hv->key.len,
|
||||
hv->key.data);
|
||||
|
||||
h[i].value.len = 0;
|
||||
h[i].hash = 0;
|
||||
|
||||
} else {
|
||||
h[i].value = *value;
|
||||
h[i].hash = hv->hash;
|
||||
}
|
||||
|
||||
if (output_header) {
|
||||
*output_header = &h[i];
|
||||
}
|
||||
|
||||
matched = 1;
|
||||
}
|
||||
|
||||
if (matched){
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if ((hv->wildcard || no_create) && value->len == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/* XXX we still need to create header slot even if the value
|
||||
* is empty because some builtin headers like Last-Modified
|
||||
* relies on this to get cleared */
|
||||
|
||||
h = ngx_list_push(&r->headers_out.headers);
|
||||
if (h == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (value->len == 0) {
|
||||
h->hash = 0;
|
||||
|
||||
} else {
|
||||
h->hash = hv->hash;
|
||||
}
|
||||
|
||||
h->key = hv->key;
|
||||
h->value = *value;
|
||||
|
||||
h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
|
||||
if (h->lowcase_key == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
|
||||
|
||||
if (output_header) {
|
||||
*output_header = h;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_set_builtin_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
ngx_table_elt_t *h, **old;
|
||||
|
||||
dd_enter();
|
||||
|
||||
if (hv->offset) {
|
||||
old = (ngx_table_elt_t **) ((char *) &r->headers_out + hv->offset);
|
||||
|
||||
} else {
|
||||
old = NULL;
|
||||
}
|
||||
|
||||
if (old == NULL || *old == NULL) {
|
||||
return ngx_http_set_header_helper(r, hv, value, old, 0);
|
||||
}
|
||||
|
||||
h = *old;
|
||||
|
||||
if (value->len == 0) {
|
||||
dd("clearing the builtin header");
|
||||
|
||||
h->hash = 0;
|
||||
h->value = *value;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
h->hash = hv->hash;
|
||||
h->key = hv->key;
|
||||
h->value = *value;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_set_builtin_multi_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
ngx_array_t *pa;
|
||||
ngx_table_elt_t *ho, **ph;
|
||||
ngx_uint_t i;
|
||||
|
||||
pa = (ngx_array_t *) ((char *) &r->headers_out + hv->offset);
|
||||
|
||||
if (pa->elts == NULL) {
|
||||
if (ngx_array_init(pa, r->pool, 2, sizeof(ngx_table_elt_t *))
|
||||
!= NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/* override old values (if any) */
|
||||
|
||||
if (pa->nelts > 0) {
|
||||
ph = pa->elts;
|
||||
for (i = 1; i < pa->nelts; i++) {
|
||||
ph[i]->hash = 0;
|
||||
ph[i]->value.len = 0;
|
||||
}
|
||||
|
||||
ph[0]->value = *value;
|
||||
|
||||
if (value->len == 0) {
|
||||
ph[0]->hash = 0;
|
||||
|
||||
} else {
|
||||
ph[0]->hash = hv->hash;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
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->value = *value;
|
||||
ho->hash = hv->hash;
|
||||
ngx_str_set(&ho->key, "Cache-Control");
|
||||
*ph = ho;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_set_content_type_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
u_char *p, *last, *end;
|
||||
|
||||
r->headers_out.content_type_len = value->len;
|
||||
r->headers_out.content_type = *value;
|
||||
r->headers_out.content_type_hash = hv->hash;
|
||||
r->headers_out.content_type_lowcase = NULL;
|
||||
|
||||
p = value->data;
|
||||
end = p + value->len;
|
||||
|
||||
for (; p != end; p++) {
|
||||
|
||||
if (*p != ';') {
|
||||
continue;
|
||||
}
|
||||
|
||||
last = p;
|
||||
|
||||
while (*++p == ' ') { /* void */ }
|
||||
|
||||
if (p == end) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ngx_strncasecmp(p, (u_char *) "charset=", 8) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
p += 8;
|
||||
|
||||
r->headers_out.content_type_len = last - value->data;
|
||||
|
||||
if (*p == '"') {
|
||||
p++;
|
||||
}
|
||||
|
||||
last = end;
|
||||
|
||||
if (*(last - 1) == '"') {
|
||||
last--;
|
||||
}
|
||||
|
||||
r->headers_out.charset.len = last - p;
|
||||
r->headers_out.charset.data = p;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
value->len = 0;
|
||||
|
||||
return ngx_http_set_header_helper(r, hv, value, NULL, 1);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_set_content_length_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
off_t len;
|
||||
|
||||
if (value->len == 0) {
|
||||
return ngx_http_clear_content_length_header(r, hv, value);
|
||||
}
|
||||
|
||||
len = ngx_atosz(value->data, value->len);
|
||||
if (len == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
r->headers_out.content_length_n = len;
|
||||
|
||||
return ngx_http_set_builtin_header(r, hv, value);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_set_accept_ranges_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
if (value->len == 0) {
|
||||
r->allow_ranges = 0;
|
||||
}
|
||||
|
||||
return ngx_http_set_builtin_header(r, hv, value);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_clear_content_length_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
r->headers_out.content_length_n = -1;
|
||||
|
||||
return ngx_http_clear_builtin_header(r, hv, value);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_clear_builtin_header(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_header_val_t *hv, ngx_str_t *value)
|
||||
{
|
||||
dd_enter();
|
||||
|
||||
value->len = 0;
|
||||
|
||||
return ngx_http_set_builtin_header(r, hv, value);
|
||||
}
|
||||
|
||||
|
||||
char *
|
||||
ngx_http_headers_more_set_headers(ngx_conf_t *cf,
|
||||
ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
return ngx_http_headers_more_parse_directive(cf, cmd, conf,
|
||||
ngx_http_headers_more_opcode_set);
|
||||
}
|
||||
|
||||
|
||||
char *
|
||||
ngx_http_headers_more_clear_headers(ngx_conf_t *cf,
|
||||
ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
return ngx_http_headers_more_parse_directive(cf, cmd, conf,
|
||||
ngx_http_headers_more_opcode_clear);
|
||||
}
|
||||
|
||||
|
||||
static ngx_flag_t
|
||||
ngx_http_headers_more_check_type(ngx_http_request_t *r, ngx_array_t *types)
|
||||
{
|
||||
ngx_uint_t i;
|
||||
ngx_str_t *t;
|
||||
|
||||
dd("headers_out->content_type: %.*s (len %d)",
|
||||
(int) r->headers_out.content_type.len,
|
||||
r->headers_out.content_type.data,
|
||||
(int) r->headers_out.content_type.len);
|
||||
|
||||
t = types->elts;
|
||||
|
||||
for (i = 0; i < types->nelts; i++) {
|
||||
dd("...comparing with type [%.*s]", (int) t[i].len, t[i].data);
|
||||
|
||||
if (r->headers_out.content_type_len == t[i].len
|
||||
&& ngx_strncmp(r->headers_out.content_type.data,
|
||||
t[i].data, t[i].len) == 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static ngx_flag_t
|
||||
ngx_http_headers_more_check_status(ngx_http_request_t *r, ngx_array_t *statuses)
|
||||
{
|
||||
ngx_uint_t i;
|
||||
ngx_uint_t *status;
|
||||
|
||||
dd("headers_out.status = %d", (int) r->headers_out.status);
|
||||
|
||||
status = statuses->elts;
|
||||
for (i = 0; i < statuses->nelts; i++) {
|
||||
dd("...comparing with specified status %d", (int) status[i]);
|
||||
|
||||
if (r->headers_out.status == status[i]) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_http_headers_more_parse_directive(ngx_conf_t *cf, ngx_command_t *ngx_cmd,
|
||||
void *conf, ngx_http_headers_more_opcode_t opcode)
|
||||
{
|
||||
ngx_http_headers_more_loc_conf_t *hlcf = conf;
|
||||
|
||||
ngx_uint_t i;
|
||||
ngx_http_headers_more_cmd_t *cmd;
|
||||
ngx_str_t *arg;
|
||||
ngx_flag_t ignore_next_arg;
|
||||
ngx_str_t *cmd_name;
|
||||
ngx_int_t rc;
|
||||
|
||||
ngx_http_headers_more_main_conf_t *hmcf;
|
||||
|
||||
if (hlcf->cmds == NULL) {
|
||||
hlcf->cmds = ngx_array_create(cf->pool, 1,
|
||||
sizeof(ngx_http_headers_more_cmd_t));
|
||||
|
||||
if (hlcf->cmds == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
cmd = ngx_array_push(hlcf->cmds);
|
||||
if (cmd == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
cmd->headers =
|
||||
ngx_array_create(cf->pool, 1,
|
||||
sizeof(ngx_http_headers_more_header_val_t));
|
||||
if (cmd->headers == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
cmd->types = ngx_array_create(cf->pool, 1, sizeof(ngx_str_t));
|
||||
if (cmd->types == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
cmd->statuses = ngx_array_create(cf->pool, 1, sizeof(ngx_uint_t));
|
||||
if (cmd->statuses == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
arg = cf->args->elts;
|
||||
|
||||
cmd_name = &arg[0];
|
||||
|
||||
ignore_next_arg = 0;
|
||||
|
||||
for (i = 1; i < cf->args->nelts; i++) {
|
||||
|
||||
if (ignore_next_arg) {
|
||||
ignore_next_arg = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg[i].len == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg[i].data[0] != '-') {
|
||||
rc = ngx_http_headers_more_parse_header(cf, cmd_name,
|
||||
&arg[i], cmd->headers,
|
||||
opcode,
|
||||
ngx_http_headers_more_set_handlers);
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg[i].len == 2) {
|
||||
if (arg[i].data[1] == 't') {
|
||||
if (i == cf->args->nelts - 1) {
|
||||
ngx_log_error(NGX_LOG_ERR, cf->log, 0,
|
||||
"%V: option -t takes an argument.",
|
||||
cmd_name);
|
||||
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
rc = ngx_http_headers_more_parse_types(cf->log, cmd_name,
|
||||
&arg[i + 1],
|
||||
cmd->types);
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
ignore_next_arg = 1;
|
||||
|
||||
continue;
|
||||
|
||||
} else if (arg[i].data[1] == 's') {
|
||||
|
||||
if (i == cf->args->nelts - 1) {
|
||||
ngx_log_error(NGX_LOG_ERR, cf->log, 0,
|
||||
"%V: option -s takes an argument.",
|
||||
cmd_name);
|
||||
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
rc = ngx_http_headers_more_parse_statuses(cf->log, cmd_name,
|
||||
&arg[i + 1],
|
||||
cmd->statuses);
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
ignore_next_arg = 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ERR, cf->log, 0,
|
||||
"%V: invalid option name: \"%V\"", cmd_name, &arg[i]);
|
||||
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
dd("Found %d statuses, %d types, and %d headers",
|
||||
(int) cmd->statuses->nelts, (int) cmd->types->nelts,
|
||||
(int) cmd->headers->nelts);
|
||||
|
||||
if (cmd->headers->nelts == 0) {
|
||||
cmd->headers = NULL;
|
||||
}
|
||||
|
||||
if (cmd->types->nelts == 0) {
|
||||
cmd->types = NULL;
|
||||
}
|
||||
|
||||
if (cmd->statuses->nelts == 0) {
|
||||
cmd->statuses = NULL;
|
||||
}
|
||||
|
||||
cmd->is_input = 0;
|
||||
|
||||
hmcf = ngx_http_conf_get_module_main_conf(cf,
|
||||
ngx_http_headers_more_filter_module);
|
||||
|
||||
hmcf->requires_filter = 1;
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) Yichun Zhang (agentzh)
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NGX_HTTP_HEADERS_MORE_OUTPUT_HEADERS_H
|
||||
#define NGX_HTTP_HEADERS_MORE_OUTPUT_HEADERS_H
|
||||
|
||||
|
||||
#include "ngx_http_headers_more_filter_module.h"
|
||||
|
||||
|
||||
/* output header setters and clearers */
|
||||
|
||||
ngx_int_t ngx_http_headers_more_exec_cmd(ngx_http_request_t *r,
|
||||
ngx_http_headers_more_cmd_t *cmd);
|
||||
|
||||
char *ngx_http_headers_more_set_headers(ngx_conf_t *cf,
|
||||
ngx_command_t *cmd, void *conf);
|
||||
|
||||
char *ngx_http_headers_more_clear_headers(ngx_conf_t *cf,
|
||||
ngx_command_t *cmd, void *conf);
|
||||
|
||||
|
||||
#endif /* NGX_HTTP_HEADERS_MORE_OUTPUT_HEADERS_H */
|
||||
380
headers-more-nginx-module-0.32/src/ngx_http_headers_more_util.c
Normal file
380
headers-more-nginx-module-0.32/src/ngx_http_headers_more_util.c
Normal file
@@ -0,0 +1,380 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) Yichun Zhang (agentzh)
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DDEBUG
|
||||
#define DDEBUG 0
|
||||
#endif
|
||||
#include "ddebug.h"
|
||||
|
||||
|
||||
#include "ngx_http_headers_more_util.h"
|
||||
#include <ctype.h>
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_http_headers_more_parse_header(ngx_conf_t *cf, ngx_str_t *cmd_name,
|
||||
ngx_str_t *raw_header, ngx_array_t *headers,
|
||||
ngx_http_headers_more_opcode_t opcode,
|
||||
ngx_http_headers_more_set_header_t *handlers)
|
||||
{
|
||||
ngx_http_headers_more_header_val_t *hv;
|
||||
|
||||
ngx_uint_t i;
|
||||
ngx_str_t key = ngx_null_string;
|
||||
ngx_str_t value = ngx_null_string;
|
||||
ngx_flag_t seen_end_of_key;
|
||||
ngx_http_compile_complex_value_t ccv;
|
||||
u_char *p;
|
||||
|
||||
hv = ngx_array_push(headers);
|
||||
if (hv == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
seen_end_of_key = 0;
|
||||
for (i = 0; i < raw_header->len; i++) {
|
||||
if (key.len == 0) {
|
||||
if (isspace(raw_header->data[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
key.data = raw_header->data;
|
||||
key.len = 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!seen_end_of_key) {
|
||||
if (raw_header->data[i] == ':'
|
||||
|| isspace(raw_header->data[i]))
|
||||
{
|
||||
seen_end_of_key = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
key.len++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value.len == 0) {
|
||||
if (raw_header->data[i] == ':'
|
||||
|| isspace(raw_header->data[i]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
value.data = &raw_header->data[i];
|
||||
value.len = 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
value.len++;
|
||||
}
|
||||
|
||||
if (key.len == 0) {
|
||||
ngx_log_error(NGX_LOG_ERR, cf->log, 0,
|
||||
"%V: no key found in the header argument: %V",
|
||||
cmd_name, raw_header);
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
hv->wildcard = (key.data[key.len - 1] == '*');
|
||||
if (hv->wildcard && key.len<2){
|
||||
ngx_log_error(NGX_LOG_ERR, cf->log, 0,
|
||||
"%V: wildcard key too short: %V",
|
||||
cmd_name, raw_header);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
hv->hash = ngx_hash_key_lc(key.data, key.len);
|
||||
hv->key = key;
|
||||
|
||||
hv->offset = 0;
|
||||
|
||||
for (i = 0; handlers[i].name.len; i++) {
|
||||
if (hv->key.len != handlers[i].name.len
|
||||
|| ngx_strncasecmp(hv->key.data, handlers[i].name.data,
|
||||
handlers[i].name.len) != 0)
|
||||
{
|
||||
dd("hv key comparison: %s <> %s", handlers[i].name.data,
|
||||
hv->key.data);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
hv->offset = handlers[i].offset;
|
||||
hv->handler = handlers[i].handler;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (handlers[i].name.len == 0 && handlers[i].handler) {
|
||||
hv->offset = handlers[i].offset;
|
||||
hv->handler = handlers[i].handler;
|
||||
}
|
||||
|
||||
if (opcode == ngx_http_headers_more_opcode_clear) {
|
||||
value.len = 0;
|
||||
}
|
||||
|
||||
if (value.len == 0) {
|
||||
ngx_memzero(&hv->value, sizeof(ngx_http_complex_value_t));
|
||||
return NGX_OK;
|
||||
|
||||
}
|
||||
|
||||
/* Nginx request header value requires to be a null-terminated
|
||||
* C string */
|
||||
|
||||
p = ngx_palloc(cf->pool, value.len + 1);
|
||||
if (p == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_memcpy(p, value.data, value.len);
|
||||
p[value.len] = '\0';
|
||||
value.data = p;
|
||||
value.len++; /* we should also compile the trailing '\0' */
|
||||
|
||||
/* compile the header value as a complex value */
|
||||
|
||||
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
|
||||
|
||||
ccv.cf = cf;
|
||||
ccv.value = &value;
|
||||
ccv.complex_value = &hv->value;
|
||||
|
||||
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_http_headers_more_parse_statuses(ngx_log_t *log, ngx_str_t *cmd_name,
|
||||
ngx_str_t *value, ngx_array_t *statuses)
|
||||
{
|
||||
u_char *p, *last;
|
||||
ngx_uint_t *s = NULL;
|
||||
|
||||
p = value->data;
|
||||
last = p + value->len;
|
||||
|
||||
for (; p != last; p++) {
|
||||
if (s == NULL) {
|
||||
if (isspace(*p)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
s = ngx_array_push(statuses);
|
||||
if (s == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (*p >= '0' && *p <= '9') {
|
||||
*s = *p - '0';
|
||||
|
||||
} else {
|
||||
ngx_log_error(NGX_LOG_ERR, log, 0,
|
||||
"%V: invalid digit \"%c\" found in "
|
||||
"the status code list \"%V\"",
|
||||
cmd_name, *p, value);
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isspace(*p)) {
|
||||
dd("Parsed status %d", (int) *s);
|
||||
|
||||
s = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*p >= '0' && *p <= '9') {
|
||||
*s *= 10;
|
||||
*s += *p - '0';
|
||||
|
||||
} else {
|
||||
ngx_log_error(NGX_LOG_ERR, log, 0,
|
||||
"%V: invalid digit \"%c\" found in "
|
||||
"the status code list \"%V\"",
|
||||
cmd_name, *p, value);
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (s) {
|
||||
dd("Parsed status %d", (int) *s);
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_http_headers_more_parse_types(ngx_log_t *log, ngx_str_t *cmd_name,
|
||||
ngx_str_t *value, ngx_array_t *types)
|
||||
{
|
||||
u_char *p, *last;
|
||||
ngx_str_t *t = NULL;
|
||||
|
||||
p = value->data;
|
||||
last = p + value->len;
|
||||
|
||||
for (; p != last; p++) {
|
||||
if (t == NULL) {
|
||||
if (isspace(*p) || *p == ';') {
|
||||
continue;
|
||||
}
|
||||
|
||||
t = ngx_array_push(types);
|
||||
if (t == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
t->len = 1;
|
||||
t->data = p;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isspace(*p) || *p == ';') {
|
||||
t = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
t->len++;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_http_headers_more_rm_header_helper(ngx_list_t *l, ngx_list_part_t *cur,
|
||||
ngx_uint_t i)
|
||||
{
|
||||
ngx_table_elt_t *data;
|
||||
ngx_list_part_t *new, *part;
|
||||
|
||||
dd("list rm item: part %p, i %d, nalloc %d", cur, (int) i,
|
||||
(int) l->nalloc);
|
||||
|
||||
data = cur->elts;
|
||||
|
||||
dd("cur: nelts %d, nalloc %d", (int) cur->nelts,
|
||||
(int) l->nalloc);
|
||||
|
||||
if (i == 0) {
|
||||
cur->elts = (char *) cur->elts + l->size;
|
||||
cur->nelts--;
|
||||
|
||||
if (cur == l->last) {
|
||||
if (cur->nelts == 0) {
|
||||
#if 1
|
||||
part = &l->part;
|
||||
|
||||
if (part == cur) {
|
||||
cur->elts = (char *) cur->elts - l->size;
|
||||
/* do nothing */
|
||||
|
||||
} else {
|
||||
while (part->next != cur) {
|
||||
if (part->next == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
part = part->next;
|
||||
}
|
||||
|
||||
l->last = part;
|
||||
part->next = NULL;
|
||||
dd("part nelts: %d", (int) part->nelts);
|
||||
l->nalloc = part->nelts;
|
||||
}
|
||||
#endif
|
||||
|
||||
} else {
|
||||
l->nalloc--;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (cur->nelts == 0) {
|
||||
part = &l->part;
|
||||
|
||||
if (part == cur) {
|
||||
ngx_http_headers_more_assert(cur->next != NULL);
|
||||
|
||||
dd("remove 'cur' from the list by rewriting 'cur': "
|
||||
"l->last: %p, cur: %p, cur->next: %p, part: %p",
|
||||
l->last, cur, cur->next, part);
|
||||
|
||||
if (l->last == cur->next) {
|
||||
dd("last is cur->next");
|
||||
l->part = *(cur->next);
|
||||
l->last = part;
|
||||
l->nalloc = part->nelts;
|
||||
|
||||
} else {
|
||||
l->part = *(cur->next);
|
||||
}
|
||||
|
||||
} else {
|
||||
dd("remove 'cur' from the list");
|
||||
while (part->next != cur) {
|
||||
if (part->next == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
part = part->next;
|
||||
}
|
||||
|
||||
part->next = cur->next;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (i == cur->nelts - 1) {
|
||||
cur->nelts--;
|
||||
|
||||
if (cur == l->last) {
|
||||
l->nalloc = cur->nelts;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
new = ngx_palloc(l->pool, sizeof(ngx_list_part_t));
|
||||
if (new == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
new->elts = &data[i + 1];
|
||||
new->nelts = cur->nelts - i - 1;
|
||||
new->next = cur->next;
|
||||
|
||||
cur->nelts = i;
|
||||
cur->next = new;
|
||||
if (cur == l->last) {
|
||||
l->last = new;
|
||||
l->nalloc = new->nelts;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) Yichun Zhang (agentzh)
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NGX_HTTP_HEADERS_MORE_UTIL_H
|
||||
#define NGX_HTTP_HEADERS_MORE_UTIL_H
|
||||
|
||||
|
||||
#include "ngx_http_headers_more_filter_module.h"
|
||||
|
||||
|
||||
#define ngx_http_headers_more_hash_literal(s) \
|
||||
ngx_http_headers_more_hash_str((u_char *) s, sizeof(s) - 1)
|
||||
|
||||
|
||||
static ngx_inline ngx_uint_t
|
||||
ngx_http_headers_more_hash_str(u_char *src, size_t n)
|
||||
{
|
||||
ngx_uint_t key;
|
||||
|
||||
key = 0;
|
||||
|
||||
while (n--) {
|
||||
key = ngx_hash(key, *src);
|
||||
src++;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
extern ngx_uint_t ngx_http_headers_more_location_hash;
|
||||
|
||||
|
||||
ngx_int_t ngx_http_headers_more_parse_header(ngx_conf_t *cf,
|
||||
ngx_str_t *cmd_name, ngx_str_t *raw_header, ngx_array_t *headers,
|
||||
ngx_http_headers_more_opcode_t opcode,
|
||||
ngx_http_headers_more_set_header_t *handlers);
|
||||
|
||||
ngx_int_t ngx_http_headers_more_parse_statuses(ngx_log_t *log,
|
||||
ngx_str_t *cmd_name, ngx_str_t *value, ngx_array_t *statuses);
|
||||
|
||||
ngx_int_t ngx_http_headers_more_parse_types(ngx_log_t *log,
|
||||
ngx_str_t *cmd_name, ngx_str_t *value, ngx_array_t *types);
|
||||
|
||||
ngx_int_t ngx_http_headers_more_rm_header_helper(ngx_list_t *l,
|
||||
ngx_list_part_t *cur, ngx_uint_t i);
|
||||
|
||||
|
||||
#endif /* NGX_HTTP_HEADERS_MORE_UTIL_H */
|
||||
Reference in New Issue
Block a user