diff --git a/README b/README new file mode 100644 index 0000000..fde41b9 --- /dev/null +++ b/README @@ -0,0 +1,55 @@ +Name + ngx_http_header_inspect - Inspect HTTP headers + +Synopsis + location /foo { + inspect_headers on; + inspect_headers_log_violations on; + inspect_headers_block_violations on; + + # only allow 3 range definitions in Range header + inspect_headers_range_max_bytesets 3; + } + +Limitations + Currently only inspects the following headers: Range, If-Range, + If-Unmodified-Since, If-Modified-Since, Date + +Report Bugs + Create a ticket on the issue tracking interface of GitHub: + http://github.com/x-way/ngx_http_header_inspect/issues + +Source Repository + Available on GitHub: + http://github.com/x-way/ngx_http_header_inspect + +Author + Andreas Jaggi + +Copyright & License + Copyright (c) 2011 Andreas Jaggi + + This module is licensed under the terms of the BSD license. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/config b/config new file mode 100644 index 0000000..06d8bb4 --- /dev/null +++ b/config @@ -0,0 +1,3 @@ +ngx_addon_name=ngx_http_header_inspect +HTTP_MODULES="$HTTP_MODULES ngx_http_header_inspect_module" +NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_header_inspect.c" diff --git a/ngx_http_header_inspect.c b/ngx_http_header_inspect.c new file mode 100644 index 0000000..2ee97a1 --- /dev/null +++ b/ngx_http_header_inspect.c @@ -0,0 +1,752 @@ +/* + * ngx_http_header_inspect - Inspect HTTP headers + * + * Copyright (c) 2011, Andreas Jaggi + */ + +#include +#include +#include +#include + + + +typedef struct { + ngx_flag_t inspect; + ngx_flag_t log; + ngx_flag_t block; + + ngx_uint_t range_max_bytesets; +} ngx_header_inspect_loc_conf_t; + + + +static ngx_int_t ngx_header_inspect_init(ngx_conf_t *cf); +static ngx_int_t ngx_header_inspect_http_date(u_char *data, ngx_uint_t maxlen); +static ngx_int_t ngx_header_inspect_entity_tag(u_char *data, ngx_uint_t maxlen); +static ngx_int_t ngx_header_inspect_range_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value); +static ngx_int_t ngx_header_inspect_ifrange_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value); +static ngx_int_t ngx_header_inspect_date_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, char *header, ngx_str_t value); +static ngx_int_t ngx_header_inspect_process_request(ngx_http_request_t *r); + +static void *ngx_header_inspect_create_conf(ngx_conf_t *cf); +static char *ngx_header_inspect_merge_conf(ngx_conf_t *cf, void *parent, void *child); + + + +static ngx_command_t ngx_header_inspect_commands[] = { + { + ngx_string("inspect_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_header_inspect_loc_conf_t, inspect), + NULL + }, + { + ngx_string("inspect_headers_log_violations"), + 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_header_inspect_loc_conf_t, log), + NULL + }, + { + ngx_string("inspect_headers_block_violations"), + 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_header_inspect_loc_conf_t, block), + NULL + }, + { + ngx_string("inspect_headers_range_max_bytesets"), + 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_header_inspect_loc_conf_t, range_max_bytesets), + NULL + }, + ngx_null_command +}; + +static ngx_http_module_t ngx_header_inspect_module_ctx = { + NULL, /* preconfiguration */ + ngx_header_inspect_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_header_inspect_create_conf, /* create location configuration */ + ngx_header_inspect_merge_conf, /* merge location configuration */ +}; + +ngx_module_t ngx_http_header_inspect_module = { + NGX_MODULE_V1, + &ngx_header_inspect_module_ctx, /* module context */ + ngx_header_inspect_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_header_inspect_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_REWRITE_PHASE].handlers); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_header_inspect_process_request; + + return NGX_OK; +} + +static ngx_int_t ngx_header_inspect_range_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) { + ngx_uint_t i,a,b,setcount; + ngx_int_t rc = NGX_OK; + enum range_header_states {RHS_NEWSET,RHS_NUM1,DELIM,RHS_NUM2,RHS_SUFDELIM,RHS_SUFNUM} state; + + if ( (value.len < 6) || (ngx_strncmp("bytes=", value.data, 6) != 0) ) { + if ( conf->log ) { + ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: Range header does not start with \"bytes=\""); + } + rc = NGX_ERROR; + } + + setcount = 1; + a = 0; + b = 0; + state = RHS_NEWSET; + + i = 6; /* start after bytes= */ + for ( ; i < value.len ; i++ ) { + + switch (value.data[i]) { + case ',': + if ( (state != DELIM) && (state != RHS_NUM2) && (state != RHS_SUFNUM) ) { + if ( conf->log ) { + ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected ',' at position %d in Range header \"%s\"", i, value.data); + } + rc = NGX_ERROR; + } + if ( state == RHS_NUM2 ) { + /* verify a <= b in 'a-b' sets */ + if ( a > b ) { + if ( conf->log ) { + ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid range definition at position %d in Range header \"%s\"", i, value.data); + } + rc = NGX_ERROR; + } + } + setcount++; + a = 0; + b = 0; + state = RHS_NEWSET; + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if ((state == RHS_NEWSET) || (state == RHS_NUM1)) { + a = a*10 + (value.data[i] - '0'); + state = RHS_NUM1; + } else if ((state == DELIM) || (state == RHS_NUM2)) { + b = b*10 + (value.data[i] - '0'); + state = RHS_NUM2; + } else if ((state == RHS_SUFDELIM) || (state == RHS_SUFNUM)) { + state = RHS_SUFNUM; + } else { + if ( conf->log ) { + ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected digit at position %d in Range header \"%s\"", i, value.data); + } + rc = NGX_ERROR; + } + break; + + case '-': + if (state == RHS_NEWSET) { + state = RHS_SUFDELIM; + } else if (state == RHS_NUM1) { + state = DELIM; + } else { + if ( conf->log ) { + ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected '-' at position %d in Range header \"%s\"", i, value.data); + } + rc = NGX_ERROR; + } + break; + + default: + if ( conf->log ) { + ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal char at position %d in Range header \"%s\"", i, value.data); + } + rc = NGX_ERROR; + } + + if (setcount > conf->range_max_bytesets) { + if ( conf->log ) { + ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: Range header contains more than %d bytesets", conf->range_max_bytesets); + } + return NGX_ERROR; + break; + } + } + + if ((state != DELIM) && (state != RHS_NUM2) && (state != RHS_SUFNUM)) { + if ( conf->log ) { + ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: Range header \"%s\" contains incomplete byteset definition", value.data); + } + rc = NGX_ERROR; + } + if ( state == RHS_NUM2 ) { + /* verify a <= b in 'a-b' sets */ + if ( a > b ) { + if ( conf->log ) { + ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid range definition at position %d in Range header \"%s\"", i, value.data); + } + rc = NGX_ERROR; + } + } + + return rc; +} + +static ngx_int_t ngx_header_inspect_http_date(u_char *data, ngx_uint_t maxlen) { + ngx_uint_t i; + enum http_date_type {RFC1123, RFC850, ASCTIME} type; + + if ( maxlen < 24 ) { + return NGX_ERROR; + } + + if ((data[0] == 'M') && (data[1] == 'o') && (data[2] == 'n')) { + /* Mon(day) */ + switch (data[3]) { + case ',': + type = RFC1123; + i = 4; + break; + case ' ': + type = ASCTIME; + i = 3; + break; + case 'd': + type = RFC850; + if ( + (data[4] != 'a') || + (data[5] != 'y') || + (data[6] != ',') + ) { + return NGX_ERROR; + } + i = 7; + break; + default: + return NGX_ERROR; + } + } else if ((data[0] == 'T') && (data[1] == 'u') && (data[2] == 'e')) { + /* Tue(sday) */ + switch (data[3]) { + case ',': + type = RFC1123; + i = 4; + break; + case ' ': + type = ASCTIME; + i = 3; + break; + case 's': + type = RFC850; + if ( + (data[4] != 'd') || + (data[5] != 'a') || + (data[6] != 'y') || + (data[7] != ',') + ) { + return NGX_ERROR; + } + i = 8; + break; + default: + return NGX_ERROR; + } + } else if ((data[0] == 'W') && (data[1] == 'e') && (data[2] == 'd')) { + /* Wed(nesday) */ + switch (data[3]) { + case ',': + type = RFC1123; + i = 4; + break; + case ' ': + type = ASCTIME; + i = 3; + break; + case 'n': + type = RFC850; + if ( + (data[4] != 'e') || + (data[5] != 's') || + (data[6] != 'd') || + (data[7] != 'a') || + (data[8] != 'y') || + (data[9] != ',') + ) { + return NGX_ERROR; + } + i = 10; + break; + default: + return NGX_ERROR; + } + } else if ((data[0] == 'T') && (data[1] == 'h') && (data[2] == 'u')) { + /* Thu(rsday) */ + switch (data[3]) { + case ',': + type = RFC1123; + i = 4; + break; + case ' ': + type = ASCTIME; + i = 3; + break; + case 'r': + type = RFC850; + if ( + (data[4] != 's') || + (data[5] != 'd') || + (data[6] != 'a') || + (data[7] != 'y') || + (data[8] != ',') + ) { + return NGX_ERROR; + } + i = 9; + break; + default: + return NGX_ERROR; + } + } else if ((data[0] == 'F') && (data[1] == 'r') && (data[2] == 'i')) { + /* Fri(day) */ + switch (data[3]) { + case ',': + type = RFC1123; + i = 4; + break; + case ' ': + type = ASCTIME; + i = 3; + break; + case 'd': + type = RFC850; + if ( + (data[4] != 'a') || + (data[5] != 'y') || + (data[6] != ',') + ) { + return NGX_ERROR; + } + i = 7; + break; + default: + return NGX_ERROR; + } + } else if ((data[0] == 'S') && (data[1] == 'a') && (data[2] == 't')) { + /* Sat(urday) */ + switch (data[3]) { + case ',': + type = RFC1123; + i = 4; + break; + case ' ': + type = ASCTIME; + i = 3; + break; + case 'u': + type = RFC850; + if ( + (data[4] != 'r') || + (data[5] != 'd') || + (data[6] != 'a') || + (data[7] != 'y') || + (data[8] != ',') + ) { + return NGX_ERROR; + } + i = 9; + break; + default: + return NGX_ERROR; + } + } else if ((data[0] == 'S') && (data[1] == 'u') && (data[2] == 'n')) { + /* Sun(day) */ + switch (data[3]) { + case ',': + type = RFC1123; + i = 4; + break; + case ' ': + type = ASCTIME; + i = 3; + break; + case 'd': + type = RFC850; + if ( + (data[4] != 'a') || + (data[5] != 'y') || + (data[6] != ',') + ) { + return NGX_ERROR; + } + i = 7; + break; + default: + return NGX_ERROR; + } + } else { + return NGX_ERROR; + } + + switch (type) { + case RFC1123: + if (maxlen != 29) { + return NGX_ERROR; + } + break; + case RFC850: + if (maxlen != 30) { + return NGX_ERROR; + } + break; + case ASCTIME: + if (maxlen != 24) { + return NGX_ERROR; + } + break; + default: + return NGX_ERROR; + } + + if (data[i] != ' ') { + return NGX_ERROR; + } + i++; + + if (type == RFC1123) { + /* rfc1123: day */ + if ((data[i] < '0') || (data[i] > '9')) { + return NGX_ERROR; + } + i++; + if ((data[i] < '0') || (data[i] > '9')) { + return NGX_ERROR; + } + i++; + if (data[i] != ' ') { + return NGX_ERROR; + } + i++; + } else if (type == RFC850) { + /* rfc850: day */ + if ((data[i] < '0') || (data[i] > '9')) { + return NGX_ERROR; + } + i++; + if ((data[i] < '0') || (data[i] > '9')) { + return NGX_ERROR; + } + i++; + if (data[i] != '-') { + return NGX_ERROR; + } + i++; + } + + /* month: Nov */ + if ( + ((data[i] == 'J') && (data[i+1] == 'a') && (data[i+2] == 'n')) || + ((data[i] == 'F') && (data[i+1] == 'e') && (data[i+2] == 'b')) || + ((data[i] == 'M') && (data[i+1] == 'a') && (data[i+2] == 'r')) || + ((data[i] == 'A') && (data[i+1] == 'p') && (data[i+2] == 'r')) || + ((data[i] == 'M') && (data[i+1] == 'a') && (data[i+2] == 'y')) || + ((data[i] == 'J') && (data[i+1] == 'u') && (data[i+2] == 'n')) || + ((data[i] == 'J') && (data[i+1] == 'u') && (data[i+2] == 'l')) || + ((data[i] == 'A') && (data[i+1] == 'u') && (data[i+2] == 'g')) || + ((data[i] == 'S') && (data[i+1] == 'e') && (data[i+2] == 'p')) || + ((data[i] == 'O') && (data[i+1] == 'c') && (data[i+2] == 't')) || + ((data[i] == 'N') && (data[i+1] == 'o') && (data[i+2] == 'v')) || + ((data[i] == 'D') && (data[i+1] == 'e') && (data[i+2] == 'c')) + ) { + i += 3; + } else { + return NGX_ERROR; + } + + if (type == RFC1123) { + /* rfc1123: year */ + if (data[i] != ' ') { + return NGX_ERROR; + } + i++; + if ((data[i] < '0') || (data[i] > '9')) { + return NGX_ERROR; + } + i++; + if ((data[i] < '0') || (data[i] > '9')) { + return NGX_ERROR; + } + i++; + if ((data[i] < '0') || (data[i] > '9')) { + return NGX_ERROR; + } + i++; + } else if (type == RFC850) { + /* rfc850: year */ + if (data[i] != '-') { + return NGX_ERROR; + } + i++; + if ((data[i] < '0') || (data[i] > '9')) { + return NGX_ERROR; + } + i++; + } else if (type == ASCTIME) { + /* asctime: day */ + if (data[i] != ' ') { + return NGX_ERROR; + } + i++; + if ((data[i] != ' ') || (data[i] < '0') || (data[i] > '9')) { + return NGX_ERROR; + } + i++; + } + if ((data[i] < '0') || (data[i] > '9')) { + return NGX_ERROR; + } + i++; + if (data[i] != ' ') { + return NGX_ERROR; + } + i++; + + /* time 08:49:37 */ + if ( + (data[i] < '0') || (data[i] > '9') || + (data[i+1] < '0') || (data[i+1] > '9') || + (data[i+2] != ':') + ) { + return NGX_ERROR; + } + i += 3; + if ( + (data[i] < '0') || (data[i] > '9') || + (data[i+1] < '0') || (data[i+1] > '9') || + (data[i+2] != ':') + ) { + return NGX_ERROR; + } + i += 3; + if ( + (data[i] < '0') || (data[i] > '9') || + (data[i+1] < '0') || (data[i+1] > '9') || + (data[i+2] != ' ') + ) { + return NGX_ERROR; + } + i += 3; + + if (type == ASCTIME) { + /* asctime: year: 1994 */ + if ( + (data[i] < '0') || (data[i] > '9') || + (data[i+1] < '0') || (data[i+1] > '9') || + (data[i+2] < '0') || (data[i+2] > '9') || + (data[i+3] < '0') || (data[i+3] > '9') + ) { + return NGX_ERROR; + } + i += 4; + } else { + /* GMT */ + if ((data[i] != 'G') || (data[i+1] != 'M') || (data[i+2] != 'T')) { + return NGX_ERROR; + } + i += 3; + } + + if ( i != maxlen ) { + return NGX_ERROR; + } + + return NGX_OK; +} + +static ngx_int_t ngx_header_inspect_entity_tag(u_char *data, ngx_uint_t maxlen) { + ngx_uint_t i = 0; + + if ( maxlen < 2 ) { + return NGX_ERROR; + } + + if ( data[0] == 'W' ) { + if ( data[1] != '/' ) { + return NGX_ERROR; + } + i = 2; + } + + if ( i+1 >= maxlen ) { + return NGX_ERROR; + } + + if ( data[i] != '"' ) { + return NGX_ERROR; + } + i++; + + for ( ; i < maxlen-1 ; i++ ) { + if ( data[i] == '"' ) { + return NGX_ERROR; + } + } + + if ( data[maxlen-1] != '"' ) { + return NGX_ERROR; + } + + return NGX_OK; +} + +static ngx_int_t ngx_header_inspect_ifrange_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) { + if (((value.data[0] == 'W') && (value.data[1] == '/'))|| (value.data[0] == '"')) { + /* 1. entity-tag */ + if ( ngx_header_inspect_entity_tag(value.data, value.len) != NGX_OK ) { + if ( conf->log ) { + ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid entity-tag in If-Range header \"%s\"", value.data); + } + return NGX_ERROR; + } + } else { + /* 2. HTTP-date */ + return ngx_header_inspect_date_header(conf, log, "If-Range", value); + } + + return NGX_OK; +} + +static ngx_int_t ngx_header_inspect_date_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, char *header, ngx_str_t value) { + + /* HTTP-date */ + if ( ngx_header_inspect_http_date(value.data, value.len) != NGX_OK ) { + if ( conf->log ) { + ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid HTTP-date in \"%s\" header \"%s\"", header, value.data); + } + return NGX_ERROR; + } + + return NGX_OK; +} + +static ngx_int_t ngx_header_inspect_process_request(ngx_http_request_t *r) { + ngx_header_inspect_loc_conf_t *conf; + ngx_table_elt_t *h; + ngx_list_part_t *part; + ngx_uint_t i; + ngx_int_t rc; + + conf = ngx_http_get_module_loc_conf(r, ngx_http_header_inspect_module); + + if (conf->inspect) { + part = &r->headers_in.headers.part; + do { + h = part->elts; + for (i = 0; i < part->nelts; i++) { + if ((h[i].key.len == 5) && (ngx_strcmp("Range", h[i].key.data) == 0)) { + rc = ngx_header_inspect_range_header(conf, r->connection->log, h[i].value); + if ((rc != NGX_OK) && conf->block) { + return NGX_HTTP_BAD_REQUEST; + } + } else if ((h[i].key.len == 8) && (ngx_strcmp("If-Range", h[i].key.data) == 0) ) { + rc = ngx_header_inspect_ifrange_header(conf, r->connection->log, h[i].value); + if ((rc != NGX_OK) && conf->block) { + return NGX_HTTP_BAD_REQUEST; + } + } else if ((h[i].key.len == 19) && (ngx_strcmp("If-Unmodified-Since", h[i].key.data) == 0) ) { + rc = ngx_header_inspect_date_header(conf, r->connection->log, "If-Unmodified-Since", h[i].value); + if ((rc != NGX_OK) && conf->block) { + return NGX_HTTP_BAD_REQUEST; + } + } else if ((h[i].key.len == 17) && (ngx_strcmp("If-Modified-Since", h[i].key.data) == 0) ) { + rc = ngx_header_inspect_date_header(conf, r->connection->log, "If-Modified-Since", h[i].value); + if ((rc != NGX_OK) && conf->block) { + return NGX_HTTP_BAD_REQUEST; + } + } else if ((h[i].key.len == 4) && (ngx_strcmp("Date", h[i].key.data) == 0) ) { + rc = ngx_header_inspect_date_header(conf, r->connection->log, "Date", h[i].value); + if ((rc != NGX_OK) && conf->block) { + return NGX_HTTP_BAD_REQUEST; + } + } + /* TODO: support for other headers */ + } + part = part->next; + } while ( part != NULL ); + } + + return NGX_DECLINED; +} + + + +static void *ngx_header_inspect_create_conf(ngx_conf_t *cf) { + ngx_header_inspect_loc_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_header_inspect_loc_conf_t)); + if (conf == NULL) { + return NGX_CONF_ERROR; + } + + conf->inspect = NGX_CONF_UNSET; + conf->log = NGX_CONF_UNSET; + conf->block = NGX_CONF_UNSET; + + conf->range_max_bytesets = NGX_CONF_UNSET_UINT; + + return conf; +} + +static char *ngx_header_inspect_merge_conf(ngx_conf_t *cf, void *parent, void *child) { + ngx_header_inspect_loc_conf_t *prev = parent; + ngx_header_inspect_loc_conf_t *conf = child; + + ngx_conf_merge_off_value(conf->inspect, prev->inspect, 0); + ngx_conf_merge_off_value(conf->log, prev->log, 1); + ngx_conf_merge_off_value(conf->block, prev->block, 0); + + ngx_conf_merge_uint_value(conf->range_max_bytesets, prev->range_max_bytesets, 5); + + return NGX_CONF_OK; +}