Newer
Older
ngx_http_header_inspect / ngx_http_header_inspect.c
Andreas Jaggi on 24 Sep 2011 91 KB Inspect Cache-Control header, fixes #11
/*
 * ngx_http_header_inspect - Inspect HTTP headers
 *
 * Copyright (c) 2011, Andreas Jaggi <andreas.jaggi@waterwave.ch>
 */

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_array.h>



typedef struct {
	ngx_flag_t inspect;
	ngx_flag_t log;
	ngx_flag_t log_uninspected;
	ngx_flag_t block;

	ngx_uint_t range_max_byteranges;
} 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, ngx_uint_t *len);
static ngx_int_t ngx_header_inspect_parse_base64(char* header, ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, u_char *data, ngx_uint_t maxlen);
static ngx_int_t ngx_header_inspect_parse_entity_tag(u_char *data, ngx_uint_t maxlen, ngx_uint_t *len);
static ngx_int_t ngx_header_inspect_parse_languagerange(u_char *data, ngx_uint_t maxlen, ngx_uint_t *len);
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_acceptencoding_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_contentencoding_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_acceptlanguage_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_contentlanguage_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_acceptcharset_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_digit_header(char* header, ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_ifmatch_header(char* header, ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_allow_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_host_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_accept_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_connection_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_contentrange_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_useragent_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_upgrade_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_via_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_from_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_pragma_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_contenttype_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_contentmd5_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_authorization_header(char* header, ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_expect_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_warning_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_trailer_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_transferencoding_header(char* header, ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_referer_header(char* header, ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value);
static ngx_int_t ngx_header_inspect_cachecontrol_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, 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_log_uninspected"),
		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_uninspected),
		NULL
	},
	{
		ngx_string("inspect_headers_range_max_byteranges"),
		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_byteranges),
		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_byteranges) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: Range header contains more than %d byteranges", conf->range_max_byteranges);
			}
			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 *len) {
	ngx_uint_t i = 0;
	enum http_date_type {RFC1123, RFC850, ASCTIME} type;

	if ( maxlen < 24 ) {
		*len = i;
		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] != ',')
				) {
					*len = i;
					return NGX_ERROR;
				}
				i = 7;
				break;
			default:
				*len = i;
				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] != ',')
				) {
					*len = i;
					return NGX_ERROR;
				}
				i = 8;
				break;
			default:
				*len = i;
				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] != ',')
				) {
					*len = i;
					return NGX_ERROR;
				}
				i = 10;
				break;
			default:
				*len = i;
				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] != ',')
				) {
					*len = i;
					return NGX_ERROR;
				}
				i = 9;
				break;
			default:
				*len = i;
				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] != ',')
				) {
					*len = i;
					return NGX_ERROR;
				}
				i = 7;
				break;
			default:
				*len = i;
				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] != ',')
				) {
					*len = i;
					return NGX_ERROR;
				}
				i = 9;
				break;
			default:
				*len = i;
				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] != ',')
				) {
					*len = i;
					return NGX_ERROR;
				}
				i = 7;
				break;
			default:
				*len = i;
				return NGX_ERROR;
		}
	} else {
		*len = i;
		return NGX_ERROR;
	}

	switch (type) {
		case RFC1123:
			if (maxlen < 29) {
				*len = i;
				return NGX_ERROR;
			}
			break;
		case RFC850:
			if (maxlen < 30) {
				*len = i;
				return NGX_ERROR;
			}
			break;
		case ASCTIME:
			if (maxlen < 24) {
				*len = i;
				return NGX_ERROR;
			}
			break;
		default:
			*len = i;
			return NGX_ERROR;
	}

	if (data[i] != ' ') {
		*len = i;
		return NGX_ERROR;
	}
	i++;

	if (type == RFC1123) {
	/* rfc1123: day */
		if ((data[i] < '0') || (data[i] > '9')) {
			*len = i;
			return NGX_ERROR;
		}
		i++;
		if ((data[i] < '0') || (data[i] > '9')) {
			*len = i;
			return NGX_ERROR;
		}
		i++;
		if (data[i] != ' ') {
			*len = i;
			return NGX_ERROR;
		}
		i++;
	} else if (type == RFC850) {
	/* rfc850: day */
		if ((data[i] < '0') || (data[i] > '9')) {
			*len = i;
			return NGX_ERROR;
		}
		i++;
		if ((data[i] < '0') || (data[i] > '9')) {
			*len = i;
			return NGX_ERROR;
		}
		i++;
		if (data[i] != '-') {
			*len = 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 {
		*len = i;
		return NGX_ERROR;
	}

	if (type == RFC1123) {
	/* rfc1123: year */
		if (data[i] != ' ') {
			*len = i;
			return NGX_ERROR;
		}
		i++;
		if ((data[i] < '0') || (data[i] > '9')) {
			*len = i;
			return NGX_ERROR;
		}
		i++;
		if ((data[i] < '0') || (data[i] > '9')) {
			*len = i;
			return NGX_ERROR;
		}
		i++;
		if ((data[i] < '0') || (data[i] > '9')) {
			*len = i;
			return NGX_ERROR;
		}
		i++;
	} else if (type == RFC850) {
	/* rfc850: year */
		if (data[i] != '-') {
			*len = i;
			return NGX_ERROR;
		}
		i++;
		if ((data[i] < '0') || (data[i] > '9')) {
			*len = i;
			return NGX_ERROR;
		}
		i++;
	} else if (type == ASCTIME) {
	/* asctime: day */
		if (data[i] != ' ') {
			*len = i;
			return NGX_ERROR;
		}
		i++;
		if ((data[i] != ' ') || (data[i] < '0') || (data[i] > '9')) {
			*len = i;
			return NGX_ERROR;
		}
		i++;
	}
	if ((data[i] < '0') || (data[i] > '9')) {
		*len = i;
		return NGX_ERROR;
	}
	i++;
	if (data[i] != ' ') {
		*len = 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] != ':')
	) {
		*len = i;
		return NGX_ERROR;
	}
	i += 3;
	if (
		(data[i] < '0') || (data[i] > '9') ||
		(data[i+1] < '0') || (data[i+1] > '9') ||
		(data[i+2] != ':')
	) {
		*len = i;
		return NGX_ERROR;
	}
	i += 3;
	if (
		(data[i] < '0') || (data[i] > '9') ||
		(data[i+1] < '0') || (data[i+1] > '9') ||
		(data[i+2] != ' ')
	) {
		*len = i;
		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')
		) {
			*len = i;
			return NGX_ERROR;
		}
		i += 4;
	} else {
		/* GMT */
		if ((data[i] != 'G') || (data[i+1] != 'M') || (data[i+2] != 'T')) {
			*len = i;
			return NGX_ERROR;
		}
		i += 3;
	}

	*len = i;
	return NGX_OK;
}

static ngx_int_t ngx_header_inspect_parse_entity_tag(u_char *data, ngx_uint_t maxlen, ngx_uint_t *len) {
	ngx_uint_t i = 0;

	if ( maxlen < 2 ) {
		*len = 0;
		return NGX_ERROR;
	}

	if ( data[0] == 'W' ) {
		if ( data[1] != '/' ) {
			*len = 2;
			return NGX_ERROR;
		}
		i = 2;
	}

	if ( i+1 >= maxlen ) {
		*len = i;
		return NGX_ERROR;
	}

	if ( data[i] != '"' ) {
		*len = i+1;
		return NGX_ERROR;
	}
	i++;

	for ( ; i < maxlen-1 ; i++ ) {
		if ( data[i] == '"' ) {
			*len = i+1;
			return NGX_OK;
		}
	}

	*len = maxlen;
	return NGX_ERROR;
}

static ngx_int_t ngx_header_inspect_parse_qvalue(u_char *data, ngx_uint_t maxlen, ngx_uint_t *len) {

	*len = 0;

	if ((maxlen < 3) || (data[0] != 'q') || (data[1] != '=')) {
		return NGX_ERROR;
	}

	if (data[2] == '0') {
		if ((maxlen == 3) || (data[3] != '.')) {
			*len = 3;
			return NGX_OK;
		}
		if ((data[4] < '0') || (data[4] > '9')) {
			*len = 4;
			return NGX_OK;
		}
		if ((data[5] < '0') || (data[5] > '9')) {
			*len = 5;
			return NGX_OK;
		}
		if ((data[6] < '0') || (data[6] > '9')) {
			*len = 6;
		} else {
			*len = 7;
		}
		return NGX_OK;
	} else if (data[2] == '1') {
		if ((maxlen == 3) || (data[3] != '.')) {
			*len = 3;
			return NGX_OK;
		}
		if (data[4] != '0') {
			*len = 4;
			return NGX_OK;
		}
		if (data[5] != '0') {
			*len = 5;
			return NGX_OK;
		}
		if (data[6] != '0') {
			*len = 6;
		} else {
			*len = 7;
		}
		return NGX_OK;
	} else {
		*len = 2;
		return NGX_ERROR;
	}
}

static ngx_int_t ngx_header_inspect_parse_contentcoding(u_char *data, ngx_uint_t maxlen, ngx_uint_t *len) {

	if (maxlen < 1) {
		*len = 0;
		return NGX_ERROR;
	}
	*len = 1;

	switch (data[0]) {
		case '*':
			return NGX_OK;
			break;
		case 'c':
			if ( (maxlen < 8) || (ngx_strncmp("compress", data, 8) != 0)) {
				return NGX_ERROR;
			}
			*len = 8;
			break;
		case 'd':
			if ( (maxlen < 7) || (ngx_strncmp("deflate", data, 7) != 0)) {
				return NGX_ERROR;
			}
			*len = 7;
			break;
		case 'e':
			if ( (maxlen < 3) || (ngx_strncmp("exi", data, 3) != 0)) {
				return NGX_ERROR;
			}
			*len = 3;
			break;
		case 'g':
			if ( (maxlen < 4) || (ngx_strncmp("gzip", data, 4) != 0)) {
				return NGX_ERROR;
			}
			*len = 4;
			break;
		case 'i':
			if ( (maxlen < 8) || (ngx_strncmp("identity", data, 8) != 0)) {
				return NGX_ERROR;
			}
			*len = 8;
			break;
		case 'p':
			if ( (maxlen < 12) || (ngx_strncmp("pack200-gzip", data, 12) != 0)) {
				return NGX_ERROR;
			}
			*len = 12;
			break;
		default:
			return NGX_ERROR;
	}

	return NGX_OK;
}

static ngx_int_t ngx_header_inspect_parse_mediatype(u_char *data, ngx_uint_t maxlen, ngx_uint_t *len) {
	ngx_uint_t i = 0;
	u_char d;
	ngx_uint_t secondpart = 0;
	ngx_uint_t parameter = 0;

	if (maxlen < 1) {
		*len = 0;
		return NGX_ERROR;
	}

	*len = 1;
	while ( i < maxlen ) {
		d = data[i];
		if ( d == '/' ) {
			if ( i < 1 ) {
				*len = 1;
				return NGX_ERROR;
			} else {
				if ( secondpart == 0 ) {
					secondpart = 1;
					i++;
					continue;
				} else {
					*len = i;
					return NGX_ERROR;
				}
			}
		}

		if (
			((d < '0') || (d > '9')) &&
			((d < 'a') || (d > 'z')) &&
			((d < 'A') || (d > 'Z')) &&
			(d != '-') && (d != '_') &&
			(d != '+') && (d != '.') &&
			(d != ':') && (d != '*')
			/* TODO: check with RFC which chars are valid */
		) {
			*len = i;
			if (secondpart == 0) {
				return NGX_ERROR;
			} else {
				if ( d == ';' ) {
					parameter = 1;
					break;
				} else {
					return NGX_OK;
				}
			}
		}
		i++;
	}

	if ( parameter ) {
		if ( i+4 > maxlen ) {
			return NGX_ERROR;
		}
		while ( i < maxlen ) {
			if ( data[i] != ';' ) {
				*len = i;
				return NGX_OK;
			}
			i++;

			while ( (i < maxlen) && (data[i] == ' ') ) { i++; }
			if ( i == maxlen ) {
				*len = i;
				return NGX_ERROR;
			}

			/* attribute */
			while ( i < maxlen ) {
				d = data[i];

				if ( d == '=' ) {
					break;
				}

				if (
					((d < '0') || (d > '9')) &&
					((d < 'a') || (d > 'z')) &&
					((d < 'A') || (d > 'Z')) &&
					(d != '-') && (d != '_') &&
					(d != '+') && (d != '.') &&
					(d != ':') && (d != '*')
				) {
					*len = i;
					return NGX_ERROR;
				}
				i++;
			}
			if ( i == maxlen ) {
				*len = i;
				return NGX_ERROR;
			}
			i++;

			/* value */
			/* TODO: what if value is double-quoted? */
			while ( i < maxlen ) {
				d = data[i];

				if ( d == ';' ) {
					break;
				}

				if (
					((d < '0') || (d > '9')) &&
					((d < 'a') || (d > 'z')) &&
					((d < 'A') || (d > 'Z')) &&
					(d != '-') && (d != '_') &&
					(d != '+') && (d != '.') &&
					(d != ':') && (d != '*')
				) {
					*len = i;
					return NGX_OK;
				}
				i++;
			}
			while ( (i < maxlen) && (data[i] == ' ') ) { i++; }
		}
	}

	*len = i;
	if (secondpart == 0) {
		return NGX_ERROR;
	} else {
		return NGX_OK;
	}
}

static ngx_int_t ngx_header_inspect_parse_charset(u_char *data, ngx_uint_t maxlen, ngx_uint_t *len) {
	ngx_uint_t i;
	u_char d;
	ngx_uint_t alphacount = 0;

	if (maxlen < 1) {
		*len = 0;
		return NGX_ERROR;
	}

	if (data[0] == '*') {
		*len = 1;
		return NGX_OK;
	}

	*len = 1;
	for ( i = 0; i < maxlen; i++ ) {
		d = data[i];
		if (
			(d == '-') ||
			(d == '_') ||
			(d == '+') ||
			(d == '.') ||
			(d == ':')
		) {
			if (alphacount == 0) {
				*len = i;
				return NGX_ERROR;
			}
			alphacount = 0;
			continue;
		}
		if (
			((d < '0') || (d > '9')) &&
			((d < 'a') || (d > 'z')) &&
			((d < 'A') || (d > 'Z'))
		) {
			*len = i;
			if (alphacount == 0) {
				return NGX_ERROR;
			} else {
				return NGX_OK;
			}
		}
		alphacount++;
	}

	*len = i;
	if (alphacount == 0) {
		return NGX_ERROR;
	} else {
		return NGX_OK;
	}
}

static ngx_int_t ngx_header_inspect_parse_languagerange(u_char *data, ngx_uint_t maxlen, ngx_uint_t *len) {
	ngx_uint_t i;
	u_char d;
	ngx_uint_t alphacount = 0;

	if (maxlen < 1) {
		*len = 0;
		return NGX_ERROR;
	}

	if (data[0] == '*') {
		*len = 1;
		return NGX_OK;
	}

	*len = 1;
	for ( i = 0; i < maxlen; i++ ) {
		d = data[i];
		if (d == '-') {
			if (alphacount == 0) {
				*len = i;
				return NGX_ERROR;
			}
			alphacount = 0;
			continue;
		}
		if (
			((d < 'a') || (d > 'z')) &&
			((d < 'A') || (d > 'Z'))
		) {
			*len = i;
			if (alphacount == 0) {
				return NGX_ERROR;
			} else {
				return NGX_OK;
			}
		}
		if (alphacount == 8) {
			*len = i;
			return NGX_ERROR;
		}
		alphacount++;
	}

	*len = i;
	if (alphacount == 0) {
		return NGX_ERROR;
	} else {
		return NGX_OK;
	}
}

static ngx_int_t ngx_header_inspect_ifmatch_header(char* header, ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_int_t rc = NGX_AGAIN;
	ngx_uint_t i = 0;
	ngx_uint_t v;

	if ( (value.len == 1) && (value.data[0] == '*') ) {
		return NGX_OK;
	}

	if ( value.len < 2 ) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: %s header \"%s\" too short", header, value.data);
		}
		return NGX_ERROR;
	}

	while ( i < value.len ) {
		if ( ngx_header_inspect_parse_entity_tag(&(value.data[i]), value.len-i, &v) != NGX_OK ) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid entity-tag at position %d in %s header \"%s\"", i, header, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i += v;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
		if (i == value.len) {
			rc = NGX_OK;
			break;
		}
		if (value.data[i] != ',') {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal char at position %d in %s header \"%s\"", i, header, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i++;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
	}

	if (rc == NGX_AGAIN) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of %s header \"%s\"", header, value.data);
		}
		rc = NGX_ERROR;
	}

	return rc;
}

static ngx_int_t ngx_header_inspect_digit_header(char* header, ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_uint_t i = 0;

	if ( value.len <= 0 ) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: %s header \"%s\" is empty", header, value.data);
		}
		return NGX_ERROR;
	}

	for ( i = 0; i < value.len; i++ ) {
		if ( (value.data[i] < '0') || (value.data[i] > '9') ) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid digit at position %d in %s header \"%s\"", i, header, value.data);
			}
			return NGX_ERROR;
		}
	}

	return NGX_OK;
}

static ngx_int_t ngx_header_inspect_acceptcharset_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_int_t rc = NGX_AGAIN;
	ngx_uint_t i = 0;
	ngx_uint_t v;

	if ((value.len == 0) || ((value.len == 1) && (value.data[0] == '*'))) {
		return NGX_OK;
	}

	while ( i < value.len ) {
		if (ngx_header_inspect_parse_charset(&(value.data[i]), value.len-i, &v) != NGX_OK) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid charset at position %d in Accept-Charset header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i += v;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
		if (i == value.len) {
			rc = NGX_OK;
			break;
		}
		if (value.data[i] == ';') {
			i++;
			if (i >= value.len) {
				if ( conf->log ) {
					ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Accept-Charset header \"%s\"", value.data);
				}
				rc = NGX_ERROR;
				break;
			}
			if ((value.data[i] == ' ') && (i < value.len)) {
				i++;
			}
			if (ngx_header_inspect_parse_qvalue(&(value.data[i]), value.len-i, &v) != NGX_OK) {
				if ( conf->log ) {
					ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid qvalue at position %d in Accept-Charset header \"%s\"", i, value.data);
				}
				rc = NGX_ERROR;
				break;
			}
			i += v;
			if ((value.data[i] == ' ') && (i < value.len)) {
				i++;
			}
			if (i == value.len) {
				rc = NGX_OK;
				break;
			}
		}
		if (value.data[i] != ',') {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal char at position %d in Accept-Charset header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i++;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
	}

	if (rc == NGX_AGAIN) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Accept-Charset header \"%s\"", value.data);
		}
		rc = NGX_ERROR;
	}

	return rc;
}

static ngx_int_t ngx_header_inspect_contentlanguage_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_int_t rc = NGX_AGAIN;
	ngx_uint_t i = 0;
	ngx_uint_t v;

	if ((value.len == 0) || ((value.len == 1) && (value.data[0] == '*'))) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: Content-Language header \"%s\" too short", value.data);
		}
		return NGX_ERROR;
	}

	while ( i < value.len ) {
		if (value.data[i] == '*') {
		/* hack, to prevent parse_languagerange from matching '*' */
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal char at position %d in Content-Language header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		if (ngx_header_inspect_parse_languagerange(&(value.data[i]), value.len-i, &v) != NGX_OK) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid language-range at position %d in Content-Language header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i += v;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
		if (i == value.len) {
			rc = NGX_OK;
			break;
		}
		if (value.data[i] != ',') {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal char at position %d in Content-Language header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i++;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
	}

	if (rc == NGX_AGAIN) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Content-Language header \"%s\"", value.data);
		}
		rc = NGX_ERROR;
	}

	return rc;

}

static ngx_int_t ngx_header_inspect_acceptlanguage_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_int_t rc = NGX_AGAIN;
	ngx_uint_t i = 0;
	ngx_uint_t v;

	if ((value.len == 0) || ((value.len == 1) && (value.data[0] == '*'))) {
		return NGX_OK;
	}

	while ( i < value.len ) {
		if (ngx_header_inspect_parse_languagerange(&(value.data[i]), value.len-i, &v) != NGX_OK) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid language-range at position %d in Accept-Language header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i += v;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
		if (i == value.len) {
			rc = NGX_OK;
			break;
		}
		if (value.data[i] == ';') {
			i++;
			if (i >= value.len) {
				if ( conf->log ) {
					ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Accept-Language header \"%s\"", value.data);
				}
				rc = NGX_ERROR;
				break;
			}
			if ((value.data[i] == ' ') && (i < value.len)) {
				i++;
			}
			if (ngx_header_inspect_parse_qvalue(&(value.data[i]), value.len-i, &v) != NGX_OK) {
				if ( conf->log ) {
					ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid qvalue at position %d in Accept-Language header \"%s\"", i, value.data);
				}
				rc = NGX_ERROR;
				break;
			}
			i += v;
			if ((value.data[i] == ' ') && (i < value.len)) {
				i++;
			}
			if (i == value.len) {
				rc = NGX_OK;
				break;
			}
		}
		if (value.data[i] != ',') {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal char at position %d in Accept-Language header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i++;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
	}

	if (rc == NGX_AGAIN) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Accept-Language header \"%s\"", value.data);
		}
		rc = NGX_ERROR;
	}

	return rc;
}

static ngx_int_t ngx_header_inspect_contentencoding_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_int_t rc = NGX_AGAIN;
	ngx_uint_t i = 0;
	ngx_uint_t v;

	if ((value.len == 0) || ((value.len == 1) && (value.data[0] == '*'))) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: Content-Encoding header \"%s\" too short", value.data);
		}
		return NGX_ERROR;
	}

	while ( i < value.len) {
		if (value.data[i] == '*') {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal char at position %d in Content-Encoding header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		if (ngx_header_inspect_parse_contentcoding(&(value.data[i]), value.len-i, &v) != NGX_OK) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid content-coding at position %d in Content-Encoding header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i += v;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
		if (i == value.len) {
			rc = NGX_OK;
			break;
		}
		if (value.data[i] != ',') {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal char at position %d in Content-Encoding header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i++;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
	}

	if (rc == NGX_AGAIN) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Content-Encoding header \"%s\"", value.data);
		}
		rc = NGX_ERROR;
	}

	return rc;

}

static ngx_int_t ngx_header_inspect_acceptencoding_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_int_t rc = NGX_AGAIN;
	ngx_uint_t i = 0;
	ngx_uint_t v;

	if ((value.len == 0) || ((value.len == 1) && (value.data[0] == '*'))) {
		return NGX_OK;
	}

	while ( i < value.len) {
		if (ngx_header_inspect_parse_contentcoding(&(value.data[i]), value.len-i, &v) != NGX_OK) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid content-coding at position %d in Accept-Encoding header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i += v;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
		if (i == value.len) {
			rc = NGX_OK;
			break;
		}
		if (value.data[i] == ';') {
			i++;
			if (i >= value.len) {
				if ( conf->log ) {
					ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Accept-Encoding header \"%s\"", value.data);
				}
				rc = NGX_ERROR;
				break;
			}
			if ((value.data[i] == ' ') && (i < value.len)) {
				i++;
			}
			if (ngx_header_inspect_parse_qvalue(&(value.data[i]), value.len-i, &v) != NGX_OK) {
				if ( conf->log ) {
					ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid qvalue at position %d in Accept-Encoding header \"%s\"", i, value.data);
				}
				rc = NGX_ERROR;
				break;
			}
			i += v;
			if ((value.data[i] == ' ') && (i < value.len)) {
				i++;
			}
			if (i == value.len) {
				rc = NGX_OK;
				break;
			}
		}
		if (value.data[i] != ',') {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal char at position %d in Accept-Encoding header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i++;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
	}

	if (rc == NGX_AGAIN) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Accept-Encoding header \"%s\"", value.data);
		}
		rc = NGX_ERROR;
	}

	return rc;
}

static ngx_int_t ngx_header_inspect_parse_cache_directive(u_char *data, ngx_uint_t maxlen, ngx_uint_t *len) {
	ngx_uint_t i = 0;
	if ( (maxlen >= 8) && (ngx_strncmp("no-cache", data, 8) == 0) ) {
		*len = 8;
		return NGX_OK;
	}
	if ( (maxlen >= 8) && (ngx_strncmp("no-store", data, 8) == 0) ) {
		*len = 8;
		return NGX_OK;
	}
	if ( (maxlen >= 12) && (ngx_strncmp("no-transform", data, 12) == 0) ) {
		*len = 12;
		return NGX_OK;
	}
	if ( (maxlen >= 14) && (ngx_strncmp("only-if-cached", data, 14) == 0) ) {
		*len = 14;
		return NGX_OK;
	}
	if ( (maxlen >= 9) && (ngx_strncmp("max-stale", data, 9) == 0) ) {
		*len = 9;
		if ( maxlen >= 11 ) {
			if ( (data[9] == '=') && (data[10] >= '0') && (data[10] <= '9') ) {
				i = 11;
				while ( (i <= maxlen) && (data[i] >= '0') && (data[i] <= '9') ) {
					i++;
				}
				*len = i;
			}
		}
		return NGX_OK;
	}
	if ( (maxlen >= 9) && (ngx_strncmp("max-age=", data, 8) == 0) && (data[8] >= '0') && (data[8] <= '9') ) {
		i = 9;
		while ( (i < maxlen) && (data[i] >= '0') &&(data[i] <= '9') ) {
			i++;
		}
		*len = i;
		return NGX_OK;
	}
	if ( (maxlen >= 11) && (ngx_strncmp("min-fresh=", data, 10) == 0) && (data[10] >= '0') && (data[10] <= '9') ) {
		i = 11;
		while ( (i < maxlen) && (data[i] >= '0') &&(data[i] <= '9') ) {
			i++;
		}
		*len = i;
		return NGX_OK;
	}

	return NGX_ERROR;
}

static ngx_int_t ngx_header_inspect_cachecontrol_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_int_t rc = NGX_AGAIN;
	ngx_uint_t i = 0;
	ngx_uint_t v;

	if (value.len < 1) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: Cache-Control header \"%s\" too short", value.data);
		}
		return NGX_ERROR;
	}

	while ( i < value.len ) {
		if ( ngx_header_inspect_parse_cache_directive(&(value.data[i]), value.len-i, &v) != NGX_OK ) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid cache-directive at position %d in Cache-Control header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i += v;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
		if (i == value.len) {
			rc = NGX_OK;
			break;
		}
		if (value.data[i] != ',') {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal char at position %d in Cache-Control header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i++;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
	}
	if ( rc == NGX_AGAIN ) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Cache-Control header \"%s\"", value.data);
		}
		rc = NGX_ERROR;
	}

	return rc;
}

static ngx_int_t ngx_header_inspect_referer_header(char* header, ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	enum referer_header_states { RS_START, RS_SCHEME, RS_COLON, RS_SLASH1, RS_SLASH2, RS_HOST, RS_BR1, RS_IP6, RS_BR2, RS_COLON2, RS_PORT, RS_PATH } state;
	ngx_uint_t i;
	ngx_int_t rc = NGX_OK;
	u_char d;

	if ( value.len < 1 ) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: %s header \"%s\" too short", header, value.data);
		}
		return NGX_ERROR;
	}

	switch ( value.data[0] ) {
		case '/':
			/* relativePath */
			return NGX_OK;
			break;
		case 'h':
		case 'f':
			/* absoluteURI */
			state = RS_START;
			for ( i = 0; i < value.len ; i++ ) {
				d = value.data[i];

				if (
					((d >= 'g') && (d <= 'z')) ||
					((d >= 'G') && (d <= 'Z')) ||
					(d == '.')
				) {
					switch ( state ) {
						case RS_START:
							if (
								!(
								((value.len > 4) && (ngx_strncmp("http:", value.data, 5) == 0)) ||
								((value.len > 5) && (ngx_strncmp("https:", value.data, 6) == 0)) ||
								((value.len > 3) && (ngx_strncmp("ftp:", value.data, 4) == 0)) ||
								((value.len > 4) && (ngx_strncmp("ftps:", value.data, 5) == 0))
								)
							) {
								if ( conf->log ) {
									ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unknown scheme at begin of %s header \"%s\"", header, value.data);
								}
								return NGX_ERROR;
							}
							state = RS_SCHEME;
							break;
						case RS_SLASH2:
							state = RS_HOST;
							break;
						case RS_HOST:
						case RS_PATH:
						case RS_SCHEME:
							break;
						default:
							rc = NGX_ERROR;
					}
				} else if (
					((d >= 'a') && (d <= 'f')) ||
					((d >= 'A') && (d <= 'F'))
				) {
					switch ( state ) {
						case RS_START:
							if (
								!(
								((value.len > 4) && (ngx_strncmp("http:", value.data, 5) == 0)) ||
								((value.len > 5) && (ngx_strncmp("https:", value.data, 6) == 0)) ||
								((value.len > 3) && (ngx_strncmp("ftp:", value.data, 4) == 0)) ||
								((value.len > 4) && (ngx_strncmp("ftps:", value.data, 5) == 0))
								)
							) {
								if ( conf->log ) {
									ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unknown scheme at begin of %s header \"%s\"", header, value.data);
								}
								return NGX_ERROR;
							}
							state = RS_SCHEME;
							break;
						case RS_SLASH2:
							state = RS_HOST;
							break;
						case RS_BR1:
							state = RS_IP6;
							break;
						case RS_HOST:
						case RS_PATH:
						case RS_SCHEME:
						case RS_IP6:
							break;
						default:
							rc = NGX_ERROR;
					}
				} else if ( (d >= '0') && (d <= '9') ) {
					switch ( state ) {
						case RS_SLASH2:
							state = RS_HOST;
							break;
						case RS_COLON2:
							state = RS_PORT;
							break;
						case RS_BR1:
							state = RS_IP6;
							break;
						case RS_PORT:
						case RS_HOST:
						case RS_PATH:
						case RS_IP6:
							break;
						default:
							rc = NGX_ERROR;
					}
				} else if ( d == '/' ) {
					switch ( state ) {
						case RS_COLON:
							state = RS_SLASH1;
							break;
						case RS_SLASH1:
							state = RS_SLASH2;
							break;
						case RS_PORT:
						case RS_HOST:
						case RS_BR2:
							state = RS_PATH;
							break;
						case RS_PATH:
							break;
						default:
							rc = NGX_ERROR;
					}
				} else if ( d == ':' ) {
					switch ( state ) {
						case RS_SCHEME:
							state = RS_COLON;
							break;
						case RS_HOST:
						case RS_BR2:
							state = RS_COLON2;
							break;
						case RS_BR1:
							state = RS_IP6;
							break;
						case RS_PATH:
						case RS_IP6:
							break;
						default:
							rc = NGX_ERROR;
					}
				} else if ( d == '[' ) {
					switch ( state ) {
						case RS_SLASH2:
							state = RS_BR1;
							break;
						case RS_PATH:
							break;
						default:
							rc = NGX_ERROR;
					}
				} else if ( d == ']' ) {
					switch ( state ) {
						case RS_IP6:
							state = RS_BR2;
							break;
						case RS_PATH:
							break;
						default:
							rc = NGX_ERROR;
					}
				} else {
					switch ( state ) {
						case RS_PATH:
							break;
						default:
							rc = NGX_ERROR;
					}
				}
				if ( rc == NGX_ERROR ) {
					if ( conf->log ) {
						ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal character at position %d of %s header \"%s\"", i, header, value.data);
					}
					return NGX_ERROR;
				}
			}
			switch ( state ) {
				case RS_PATH:
				case RS_PORT:
				case RS_HOST:
				case RS_BR2:
					return NGX_OK;
				default:
					if ( conf->log ) {
						ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of %s header \"%s\"", header, value.data);
					}
					return NGX_ERROR;
			}
			break;
		default:
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal character at begin of %s header \"%s\"", header, value.data);
			}
			return NGX_ERROR;
	}
}

static ngx_int_t ngx_header_inspect_transferencoding_header(char* header, ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_uint_t i;
	ngx_int_t rc = NGX_OK;
	enum transferencoding_header_states { TS_START, TS_FIELD, TS_PARDELIM, TS_PARKEY, TS_PAREQ, TS_PARVAL, TS_PARVALQ, TS_PARVALQE, TS_DELIM, TS_SPACE } state;
	u_char d;
	ngx_uint_t te_header = 0;

	if ( ngx_strncmp("TE", header, 2) == 0 ) {
		te_header = 1;
	}

	state = TS_START;
	for ( i = 0; i < value.len; i++ ) {
		d = value.data[i];

		if (
			((d >= 'a') && (d <= 'z')) ||
			((d >= 'A') && (d <= 'Z')) ||
			((d >= '0') && (d <= '9'))
		) {
			switch ( state ) {
				case TS_START:
				case TS_SPACE:
					/* ensure transfer-codings is one of chunked, compress, deflate, gzip or identity */
					state = TS_FIELD;
					if (
						!(
							((value.len-i>=7) && (ngx_strncmp("chunked", &(value.data[i]),7) == 0) && ((value.data[i+7] == ',')||(value.data[i+7] == ';')||(value.data[i+7] == '\0'))) ||
							((value.len-i>=8) && (ngx_strncmp("compress", &(value.data[i]),8) == 0) && ((value.data[i+8] == ',')||(value.data[i+8] == ';')||(value.data[i+8] == '\0'))) ||
							((value.len-i>=7) && (ngx_strncmp("deflate", &(value.data[i]),7) == 0) && ((value.data[i+7] == ',')||(value.data[i+7] == ';')||(value.data[i+7] == '\0'))) ||
							((value.len-i>=4) && (ngx_strncmp("gzip", &(value.data[i]),4) == 0) && ((value.data[i+4] == ',')||(value.data[i+4] == ';')||(value.data[i+4] == '\0'))) ||
							((value.len-i>=8) && (ngx_strncmp("identity", &(value.data[i]),8) == 0) && ((value.data[i+8] == ',')||(value.data[i+8] == ';')||(value.data[i+8] == '\0'))) ||
							((te_header == 1) && (value.len-i>=8) && (ngx_strncmp("trailers", &(value.data[i]),8) == 0) && ((value.data[i+8] == ',')||(value.data[i+8] == ';')||(value.data[i+8] == '\0')))
						)
					) {
						if ( conf->log ) {
							ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal field at position %d in %s header \"%s\"", i, header, value.data);
						}
						return NGX_ERROR;
					}
					break;
				case TS_PARDELIM:
					/* TODO: if parkey is 'q', validate q-value */
					state = TS_PARKEY;
					break;
				case TS_PAREQ:
					state = TS_PARVAL;
					break;
				case TS_FIELD:
				case TS_PARKEY:
				case TS_PARVAL:
				case TS_PARVALQ:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == '.' ) {
			switch ( state ) {
				case TS_PARVAL:
				case TS_PARVALQ:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == ',' ) {
			switch ( state ) {
				case TS_FIELD:
				case TS_PARVAL:
				case TS_PARVALQE:
					state = TS_DELIM;
					break;
				case TS_PARVALQ:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == ' ' ) {
			switch ( state ) {
				case TS_DELIM:
					state = TS_SPACE;
					break;
				case TS_PARVAL:
				case TS_PARVALQ:
				case TS_PARDELIM:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == ';' ) {
			switch ( state ) {
				case TS_FIELD:
				case TS_PARVAL:
				case TS_PARVALQE:
					state = TS_PARDELIM;
					break;
				case TS_PARVALQ:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == '=' ) {
			switch ( state ) {
				case TS_PARKEY:
					state = TS_PAREQ;
					break;
				case TS_PARVALQ:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == '"' ) {
			switch ( state ) {
				case TS_PAREQ:
					state = TS_PARVALQ;
					break;
				case TS_PARVALQ:
					state = TS_PARVALQE;
					break;
				default:
					rc = NGX_ERROR;
			}
		} else {
			switch ( state ) {
				case TS_PARVALQ:
					break;
				default:
					rc = NGX_ERROR;
			}
		}
		if ( rc == NGX_ERROR ) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal char at position %d in %s header \"%s\"", i, header, value.data);
			}
			return NGX_ERROR;
		}
	}
	switch ( state ) {
		case TS_FIELD:
		case TS_PARVAL:
		case TS_PARVALQE:
			break;
		case TS_START:
			if ( te_header == 1 ) {
				break;
			}
		default:
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of %s header \"%s\"", header, value.data);
			}
			return NGX_ERROR;
	}

	return NGX_OK;
}

static ngx_int_t ngx_header_inspect_trailer_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_uint_t i;
	ngx_int_t rc = NGX_OK;
	enum trail_header_states { TS_START, TS_FIELD, TS_DELIM, TS_SPACE } state;
	u_char d;

	state = TS_START;
	for ( i = 0; i < value.len; i++ ) {
		d = value.data[i];

		if (
			((d >= 'a') && (d <= 'z')) ||
			((d >= 'A') && (d <= 'Z')) ||
			((d >= '0') && (d <= '9')) ||
			(d == '-')
		) {
			switch ( state ) {
				case TS_START:
				case TS_SPACE:
					/* ensure field is not Transfer-Encondig, Content-Length or Trailer */
					if (
						(((value.len-i)>=17) && (ngx_strncmp("Transfer-Encoding", &(value.data[i]), 17) == 0) && ((value.data[i+17] == ',') || (value.data[i+17] == '\0'))) ||
						(((value.len-i)>=14) && (ngx_strncmp("Content-Length", &(value.data[i]), 14) == 0) && ((value.data[i+14] == ',') || (value.data[i+14] == '\0'))) ||
						(((value.len-i)>=7) && (ngx_strncmp("Trailer", &(value.data[i]), 7) == 0) && ((value.data[i+7] == ',') || (value.data[i+7] == '\0')))
					) {
						if ( conf->log ) {
							ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal field at position %d in Trailer header \"%s\"", i, value.data);
						}
						return NGX_ERROR;
					}
					state = TS_FIELD;
					break;
				case TS_FIELD:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == ',' ) {
			switch ( state ) {
				case TS_FIELD:
					state = TS_DELIM;
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == ' ' ) {
			switch ( state ) {
				case TS_DELIM:
					state = TS_SPACE;
					break;
				default:
					rc = NGX_ERROR;
			}
		} else {
			rc = NGX_ERROR;
		}
		if ( rc == NGX_ERROR ) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal character at position %d in Trailer header \"%s\"", i, value.data);
			}
			return NGX_ERROR;
		}
	}
	if ( state != TS_FIELD ) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Trailer header \"%s\"", value.data);
		}
		return NGX_ERROR;
	}

	return NGX_OK;
}

static ngx_int_t ngx_header_inspect_warning_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_uint_t i;
	ngx_uint_t v;
	ngx_int_t rc = NGX_OK;
	enum warn_header_states { WS_START, WS_CODE1, WS_CODE2, WS_CODE3, WS_SP1, WS_HOST, WS_COLON, WS_PORT, WS_SP2, WS_TXT, WS_TXTE, WS_SP3, WS_DATE, WS_DELIM, WS_SPACE } state;
	u_char d;

	state = WS_START;
	for ( i = 0; i < value.len ; i++ ) {
		d = value.data[i];

		if ( (d >= '0') && (d <= '9') ) {
			switch ( state ) {
				case WS_START:
				case WS_SPACE:
					state = WS_CODE1;
					break;
				case WS_CODE1:
					state = WS_CODE2;
					break;
				case WS_CODE2:
					state = WS_CODE3;
					break;
				case WS_SP1:
					state = WS_HOST;
					break;
				case WS_COLON:
					state = WS_PORT;
					break;
				case WS_HOST:
				case WS_PORT:
				case WS_TXT:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( ((d >= 'a') && (d <= 'z')) || ((d >= 'A') && (d <= 'Z')) ) {
			switch ( state ) {
				case WS_SP1:
					state = WS_HOST;
					break;
				case WS_HOST:
				case WS_TXT:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( (d == '-') || (d == '.') ) {
			switch ( state ) {
				case WS_HOST:
				case WS_TXT:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == ':' ) {
			switch ( state ) {
				case WS_HOST:
					state = WS_COLON;
					break;
				case WS_TXT:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == ',' ) {
			switch ( state ) {
				case WS_DATE:
				case WS_TXTE:
					state = WS_DELIM;
					break;
				case WS_TXT:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == ' ' ) {
			switch ( state ) {
				case WS_CODE3:
					state = WS_SP1;
					break;
				case WS_HOST:
				case WS_PORT:
					state = WS_SP2;
					break;
				case WS_TXTE:
					state = WS_SP3;
					break;
				case WS_DELIM:
					state = WS_SPACE;
					break;
				case WS_TXT:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == '"' ) {
			switch ( state ) {
				case WS_SP2:
					state = WS_TXT;
					break;
				case WS_TXT:
					state = WS_TXTE;
					break;
				case WS_SP3:
					state = WS_DATE;
					i++; /* skip qoute */
					if ( ngx_header_inspect_http_date(&(value.data[i]), value.len-i, &v) != NGX_OK ) {
						if ( conf->log ) {
							ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal date at position %d in Warning header \"%s\"", i, value.data);
						}
						return NGX_ERROR;
					}
					i += v;
					if ( i >= value.len ) {
						if ( conf->log ) {
							ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Warning header \"%s\"", value.data);
						}
						return NGX_ERROR;
					}
					if ( value.data[i] != '"' ) {
						rc = NGX_ERROR;
					}
					break;
				default:
					rc = NGX_ERROR;
			}
		} else {
			switch ( state ) {
				case WS_TXT:
					break;
				default:
					rc = NGX_ERROR;
			}
		}
		if ( rc == NGX_ERROR ) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal character at position %d in Warning header \"%s\"", i, value.data);
			}
			return NGX_ERROR;
		}
	}
	switch ( state ) {
		case WS_TXTE:
		case WS_DATE:
			break;
		default:
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Warning header \"%s\"", value.data);
			}
			return NGX_ERROR;
	}

	return NGX_OK;
}

static ngx_int_t ngx_header_inspect_expect_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {

	/* currently only the 'known' "100-continue" value is allowed */
	if ( (value.len == 12) && (ngx_strncasecmp((u_char *)"100-continue", value.data, 12) == 0) ) {
		return NGX_OK;
	} else {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unknown value in Expect header \"%s\"", value.data);
		}
		return NGX_ERROR;
	}
}

static ngx_int_t ngx_header_inspect_authorization_header(char* header, ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_uint_t i;
	ngx_int_t rc = NGX_OK;
	enum digest_header_states { DS_START, DS_KEY, DS_EQ, DS_VAL, DS_VALQ, DS_VALQE, DS_DELIM, DS_SPACE } state;
	u_char d;

	if ( value.len == 0 ) {
		return NGX_OK;
	}

	if ( (value.len >= 6) && (ngx_strncmp("Basic ", value.data, 6) == 0) ) {
		return ngx_header_inspect_parse_base64(header, conf, log, &(value.data[6]), value.len-6);
	}

	if ( (value.len >= 7) && (ngx_strncmp("Digest ", value.data, 7) == 0) ) {
		i = 7; /* start after "Digest " */
		state = DS_START;
		for ( ; i < value.len; i++ ) {
			d = value.data[i];

			if ( (d >= 'a') && (d <= 'z') ) {
				switch ( state ) {
					case DS_START:
					case DS_SPACE:
						state = DS_KEY;
						if (
							!(
								(((value.len-i) >= 9) && (ngx_strncmp("username=", &(value.data[i]), 9) == 0)) ||
								(((value.len-i) >= 6) && (ngx_strncmp("realm=", &(value.data[i]), 6) == 0)) ||
								(((value.len-i) >= 6) && (ngx_strncmp("nonce=", &(value.data[i]), 6) == 0)) ||
								(((value.len-i) >= 4) && (ngx_strncmp("uri=", &(value.data[i]), 4) == 0)) ||
								(((value.len-i) >= 9) && (ngx_strncmp("response=", &(value.data[i]), 9) == 0)) ||
								(((value.len-i) >= 10) && (ngx_strncmp("algorithm=", &(value.data[i]), 10) == 0)) ||
								(((value.len-i) >= 7) && (ngx_strncmp("cnonce=", &(value.data[i]), 7) == 0)) ||
								(((value.len-i) >= 7) && (ngx_strncmp("opaque=", &(value.data[i]), 7) == 0)) ||
								(((value.len-i) >= 4) && (ngx_strncmp("qop=", &(value.data[i]), 4) == 0)) ||
								(((value.len-i) >= 3) && (ngx_strncmp("nc=", &(value.data[i]), 3) == 0))
							)
						) {
							if ( conf->log ) {
								ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unknown auth-param at position %d in %s header \"%s\"", i, header, value.data);
							}
							return NGX_ERROR;
						}
						break;
					case DS_EQ:
						state = DS_VAL;
						break;
					case DS_VAL:
					case DS_VALQ:
					case DS_KEY:
						break;
					default:
						rc = NGX_ERROR;
				}
			} else if ( d == ',' ) {
				switch ( state ) {
					case DS_VAL:
					case DS_VALQE:
					case DS_EQ:
						state = DS_DELIM;
						break;
					case DS_VALQ:
						break;
					default:
						rc = NGX_ERROR;
				}
			} else if ( d == '=' ) {
				switch ( state ) {
					case DS_KEY:
						state = DS_EQ;
						break;
					case DS_VALQ:
						break;
					default:
						rc = NGX_ERROR;
				}

			} else if ( d == ' ' ) {
				switch ( state ) {
					case DS_DELIM:
						state = DS_SPACE;
						break;
					case DS_VALQ:
						break;
					default:
						rc = NGX_ERROR;
				}
			} else if ( d == '"' ) {
				switch ( state ) {
					case DS_EQ:
						state = DS_VALQ;
						break;
					case DS_VALQ:
						state = DS_VALQE;
						break;
					default:
						rc = NGX_ERROR;
				}
			} else if ( (d != '"') ) {
				switch ( state ) {
					case DS_VAL:
					case DS_VALQ:
						break;
					default:
						rc = NGX_ERROR;
				}
			} else {
				rc = NGX_ERROR;
			}
			if ( rc == NGX_ERROR ) {
				if ( conf->log ) {
					ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal character at position %d in %s header \"%s\"", i, header, value.data);
				}
				return NGX_ERROR;
			}
		}
		switch ( state ) {
			case DS_VALQE:
			case DS_VAL:
				break;
			default:
				if ( conf->log ) {
					ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of %s header \"%s\"", header, value.data);
				}
				return NGX_ERROR;
		}
		return NGX_OK;
	}

	if ( conf->log ) {
		ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unknown auth-scheme in %s header \"%s\"", header, value.data);
	}
	return NGX_ERROR;
}

static ngx_int_t ngx_header_inspect_contentmd5_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	return ngx_header_inspect_parse_base64("Content-MD5", conf, log, value.data, value.len);
}

static ngx_int_t ngx_header_inspect_parse_base64(char* header, ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, u_char *data, ngx_uint_t maxlen) {
	ngx_uint_t i;
	u_char d;

	if ( maxlen == 0 ) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: empty base64 value in %s header", header);
		}
		return NGX_ERROR;
	}

	for ( i = 0; i < maxlen; i++ ) {
		d = data[i];

		if ( (d >= '0') && (d <= '9') ) {
			continue;
		}
		if ( (d >= 'a') && (d <= 'z') ) {
			continue;
		}
		if ( (d >= 'A') && (d <= 'Z') ) {
			continue;
		}
		if ( (d == '+') || (d == '/') ) {
			continue;
		}
		if ( d == '=' ) {
			continue;
			i++;
			while ( i < maxlen ) {
				if ( data[i] != '=' ) {
					if ( conf->log ) {
						ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: trailing characters at position %d in base64 value \"%s\" of %s header", i, data, header);
					}
					return NGX_ERROR;
				}
				i++;
			}
			break;
		}
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal character at position %d in base64 value \"%s\" of %s header", i, data, header);
		}
		return NGX_ERROR;
	}

	return NGX_OK;
}

static ngx_int_t ngx_header_inspect_contenttype_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_uint_t v;

	if ( value.len < 3 ) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: Content-Type header \"%s\" too short", value.data);
		}
		return NGX_ERROR;
	}

	if (ngx_header_inspect_parse_mediatype(value.data, value.len, &v) != NGX_OK) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid media-type in Content-Type header \"%s\"", value.data);
		}
		return NGX_ERROR;
	}

	return NGX_OK;
}

static ngx_int_t ngx_header_inspect_pragma_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	/* currently only the 'known' "no-cache" value is allowed */
	if ( (value.len == 8) && (ngx_strncasecmp((u_char *)"no-cache", value.data, 8) == 0) ) {
		return NGX_OK;
	} else {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unknown value in Pragama header \"%s\"", value.data);
		}
		return NGX_ERROR;
	}
}

static ngx_int_t ngx_header_inspect_from_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_uint_t i = 0;
	u_char d;
	ngx_int_t rc = NGX_OK;
	enum from_header_states { FS_START, FS_LOCALPART, FS_AT, FS_DOMAIN, FS_DOT } state;

	if ( value.len < 3 ) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: From header \"%s\" too short", value.data);
		}
		return NGX_ERROR;
	}

	state = FS_START;
	for ( i = 0; i < value.len; i++ ) {
		d = value.data[i];
		if (
			((d >= '0') && (d <= '9')) ||
			((d >= 'a') && (d <= 'z')) ||
			((d >= 'A') && (d <= 'Z')) ||
			(d == '-')
		) {
			switch ( state ) {
				case FS_START:
					state = FS_LOCALPART;
					break;
				case FS_AT:
				case FS_DOT:
					state = FS_DOMAIN;
					break;
				case FS_LOCALPART:
				case FS_DOMAIN:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if (d == '+') {
			switch ( state ) {
				case FS_START:
					state = FS_LOCALPART;
					break;
				case FS_LOCALPART:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if (d == '.') {
			switch ( state ) {
				case FS_START:
					state = FS_LOCALPART;
					break;
				case FS_LOCALPART:
					break;
				case FS_DOMAIN:
					state = FS_DOT;
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if (d == '@') {
			switch ( state ) {
				case FS_LOCALPART:
					state = FS_AT;
					break;
				default:
					rc = NGX_ERROR;
			}
		} else {
			rc = NGX_ERROR;
		}
		if ( rc == NGX_ERROR ) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal character at position %d in From header \"%s\"", i, value.data);
			}
			return NGX_ERROR;
		}
	}
	if ( state != FS_DOMAIN ) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of From header \"%s\"", value.data);
		}
		return NGX_ERROR;
	}

	return NGX_OK;
}

static ngx_int_t ngx_header_inspect_via_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_uint_t i = 0;
	u_char d;
	ngx_int_t rc = NGX_OK;
	enum via_header_states { VS_START, VS_PROT, VS_SLASH, VS_VER, VS_SPACE1, VS_HOST, VS_COLON, VS_PORT, VS_DELIM, VS_SPACE2, VS_PAREN, VS_PARENEND, VS_SPACE3 } state;

	if ( value.len < 3 ) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: Via header \"%s\" too short", value.data);
		}
		return NGX_ERROR;
	}

	state = VS_START;
	for ( i = 0; i < value.len; i++ ) {
		d = value.data[i];
		if ( ((d >= '0') && (d <= '9')) ) {
			switch ( state ) {
				case VS_START:
				case VS_SPACE3:
					state = VS_PROT;
					break;
				case VS_SLASH:
					state = VS_VER;
					break;
				case VS_SPACE1:
					state = VS_HOST;
					break;
				case VS_COLON:
					state = VS_PORT;
					break;
				case VS_PROT:
				case VS_VER:
				case VS_PORT:
				case VS_HOST:
				case VS_PAREN:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if (
			((d >= 'a') && (d <= 'z')) ||
			((d >= 'A') && (d <= 'Z')) ||
			(d == '-') || (d == '.')
		) {
			switch ( state ) {
				case VS_START:
				case VS_SPACE3:
					state = VS_PROT;
					break;
				case VS_SLASH:
					state = VS_VER;
					break;
				case VS_SPACE1:
					state = VS_HOST;
					break;
				case VS_PROT:
				case VS_VER:
				case VS_HOST:
				case VS_PAREN:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if (d == ' ') {
			switch ( state ) {
				case VS_PROT:
				case VS_VER:
					state = VS_SPACE1;
					break;
				case VS_HOST:
				case VS_PORT:
					state = VS_SPACE2;
					break;
				case VS_DELIM:
					state = VS_SPACE3;
					break;
				case VS_SPACE1:
				case VS_SPACE2:
				case VS_SPACE3:
				case VS_PAREN:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if (d == '/') {
			switch ( state ) {
				case VS_PROT:
					state = VS_SLASH;
					break;
				case VS_PAREN:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if (d == ':') {
			switch ( state ) {
				case VS_HOST:
					state = VS_COLON;
					break;
				case VS_PAREN:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if (d == '(') {
			switch ( state ) {
				case VS_SPACE2:
					state = VS_PAREN;
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if (d == ')') {
			switch ( state ) {
				case VS_PAREN:
					state = VS_PARENEND;
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if (d == ',') {
			switch ( state ) {
				case VS_HOST:
				case VS_PORT:
				case VS_PARENEND:
					state = VS_DELIM;
					break;
				case VS_PAREN:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else {
			rc = NGX_ERROR;
		}
		if ( rc == NGX_ERROR ) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal character at position %d in Via header \"%s\"", i, value.data);
			}
			return NGX_ERROR;
		}
	}
	switch ( state ) {
		case VS_HOST:
		case VS_PORT:
		case VS_PARENEND:
			break;
		default:
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Via header \"%s\"", value.data);
			}
			return NGX_ERROR;
	}

	return NGX_OK;
}

static ngx_int_t ngx_header_inspect_upgrade_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_uint_t i = 0;
	u_char d;
	ngx_int_t rc = NGX_OK;
	enum upgrade_header_states { UPS_START, UPS_PROD, UPS_SLASH, UPS_VER, UPS_DELIM, UPS_SPACE } state;

	if ( value.len < 1 ) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: Upgrade header \"%s\" too short", value.data);
		}
		return NGX_ERROR;
	}

	state = UPS_START;
	for ( i = 0; i < value.len ; i++ ) {
		d = value.data[i];

		if (
			((d >= '0') && (d <= '9')) ||
			((d >= 'a') && (d <= 'z')) ||
			((d >= 'A') && (d <= 'Z')) ||
			(d == '-') || (d == '.')
		) {
			switch ( state ) {
				case UPS_START:
				case UPS_SPACE:
					state = UPS_PROD;
					break;
				case UPS_SLASH:
					state = UPS_VER;
					break;
				case UPS_PROD:
				case UPS_VER:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == '/' ) {
			switch ( state ) {
				case UPS_PROD:
					state = UPS_SLASH;
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == ' ' ) {
			switch ( state ) {
				case UPS_DELIM:
					state = UPS_SPACE;
					break;
				case UPS_SPACE:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == ',' ) {
			switch ( state ) {
				case UPS_PROD:
				case UPS_VER:
					state = UPS_DELIM;
					break;
				default:
					rc = NGX_ERROR;
			}
		} else {
			rc = NGX_ERROR;
		}
		if ( rc == NGX_ERROR ) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal character at position %d in Upgrade header \"%s\"", i, value.data);
			}
			return NGX_ERROR;
		}
	}
	switch ( state ) {
		case UPS_PROD:
		case UPS_VER:
			break;
		default:
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Upgrade header \"%s\"", value.data);
			}
			return NGX_ERROR;
	}

	return NGX_OK;
}

static ngx_int_t ngx_header_inspect_useragent_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_uint_t i = 0;
	u_char d;
	ngx_int_t rc = NGX_OK;
	enum useragent_header_states { UAS_START, UAS_PROD, UAS_SLASH, UAS_VER, UAS_SPACE, UAS_PAREN } state;


	if ( value.len < 1 ) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: User-Agent header \"%s\" too short", value.data);
		}
		return NGX_ERROR;
	}

	state = UAS_START;
	for ( i = 0; i < value.len ; i++ ) {
		d = value.data[i];
		if (
			((d >= '0') && (d <= '9')) ||
			((d >= 'a') && (d <= 'z')) ||
			((d >= 'A') && (d <= 'Z')) ||
			(d == '-') || (d == '.')
		) {
			switch ( state ) {
				case UAS_START:
				case UAS_SPACE:
					state = UAS_PROD;
					break;
				case UAS_PROD:
				case UAS_VER:
				case UAS_PAREN:
					break;
				case UAS_SLASH:
					state = UAS_VER;
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == '/' ) {
			switch ( state ) {
				case UAS_PROD:
					state = UAS_SLASH;
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == ' ' ) {
			switch ( state ) {
				case UAS_VER:
					state = UAS_SPACE;
					break;
				case UAS_SPACE:
				case UAS_PAREN:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == '(' ) {
			switch ( state ) {
				case UAS_SPACE:
					state = UAS_PAREN;
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if ( d == ')' ) {
			switch ( state ) {
				case UAS_PAREN:
					state = UAS_SPACE;
					break;
				default:
					rc = NGX_ERROR;
			}
		} else if (
			(d == ',') || (d == ':') || (d == ';') ||
			(d == '+') || (d == '_')
		) {
			switch ( state ) {
				case UAS_PAREN:
					break;
				default:
					rc = NGX_ERROR;
			}
		} else {
			rc = NGX_ERROR;
		}
		if ( rc == NGX_ERROR ) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal character at position %d in User-Agent header \"%s\"", i, value.data);
			}
			return NGX_ERROR;
		}
	}
	switch ( state ) {
		case UAS_SPACE:
		case UAS_PROD:
		case UAS_VER:
			break;
		default:
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of User-Agent header \"%s\"", value.data);
			}
			return NGX_ERROR;
	}

	return NGX_OK;
}

static ngx_int_t ngx_header_inspect_contentrange_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_uint_t i = 0;
	ngx_int_t rc = NGX_OK;
	ngx_int_t a,b,c;
	enum contentrange_header_states {RHS_START, RHS_STAR1, RHS_NUM1,DELIM,RHS_NUM2,RHS_SLASH,RHS_STAR2, RHS_NUM3} 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: Content-Range header \"%s\"  does not start with \"bytes \"", value.data);
		}
		return NGX_ERROR;
	}
	if ( value.len < 9 ) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: Content-Range header \"%s\" is too short", value.data);
		}
		return NGX_ERROR;
	}

	state = RHS_START;
	a = -1;
	b = -1;
	c = -1;

	i = 6; /* start after "bytes " */
	for ( ; i < value.len ; i++ ) {
		switch ( value.data[i] ) {
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				switch ( state ) {
					case RHS_START:
						state = RHS_NUM1;
						a = (value.data[i] - '0');
						break;
					case RHS_NUM1:
						a = a*10+(value.data[i] - '0');
						break;
					case RHS_NUM2:
						b = b*10+(value.data[i] - '0');
						break;
					case RHS_NUM3:
						c = c*10+(value.data[i] - '0');
						break;
					case DELIM:
						state = RHS_NUM2;
						b = (value.data[i] - '0');
						break;
					case RHS_SLASH:
						state = RHS_NUM3;
						c = (value.data[i] - '0');
						break;
					default:
						rc = NGX_ERROR;
				}
				break;
			case '*':
				switch ( state ) {
					case RHS_START:
						state = RHS_STAR1;
						break;
					case RHS_SLASH:
						state = RHS_STAR2;
						break;
					default:
						rc = NGX_ERROR;
				}
				break;
			case '/':
				switch ( state ) {
					case RHS_STAR1:
					case RHS_NUM2:
						state = RHS_SLASH;
						break;
					default:
						rc = NGX_ERROR;
				}
				break;
			case '-':
				switch ( state ) {
					case RHS_NUM1:
						state = DELIM;
						break;
					default:
						rc = NGX_ERROR;
				}
				break;
			default:
				rc = NGX_ERROR;
		}
		if ( rc == NGX_ERROR ) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal character at position %d in Content-Range header \"%s\"", i, value.data);
			}
			return NGX_ERROR;
		}
	}
	switch ( state ) {
		case RHS_NUM3:
		case RHS_STAR2:
			break;
		default:
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Content-Range header \"%s\"", value.data);
			}
			return NGX_ERROR;
	}

	/* in "a-b/c" ensure a < b and b < c if any of them are defined */
	if ( (a != -1) && (b != -1) ) {
		if ( (a >= b) || ((c != -1) && (b >= c)) ) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal range specification in Content-Range header \"%s\"", value.data);
			}
			return NGX_ERROR;
		}
	}

	return NGX_OK;
}

static ngx_int_t ngx_header_inspect_connection_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_uint_t i = 0;

	while ( i < value.len ) {
		/* as per 13.5.1 of RFC2616 only allow Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailer, Transfer-Encoding and Upgrade headers in Connection header */
		if ( ((i+5) <= value.len) && (ngx_strncmp("close", &(value.data[i]), 5) == 0 ) ) {
			i += 5;
		} else if ( ((i+10) <= value.len) && (ngx_strncmp("keep-alive", &(value.data[i]), 10) == 0 ) ) {
			i += 10;
		} else if ( ((i+10) <= value.len) && (ngx_strncmp("Keep-Alive", &(value.data[i]), 10) == 0 ) ) {
			i += 10;
		} else if ( ((i+18) <= value.len) && (ngx_strncmp("Proxy-Authenticate", &(value.data[i]), 18) == 0 ) ) {
			i += 18;
		} else if ( ((i+19) <= value.len) && (ngx_strncmp("Proxy-Authorization", &(value.data[i]), 19) == 0 ) ) {
			i += 19;
		} else if ( ((i+2) <= value.len) && (ngx_strncmp("TE", &(value.data[i]), 2) == 0 ) ) {
			i += 2;
		} else if ( ((i+7) <= value.len) && (ngx_strncmp("Trailer", &(value.data[i]), 7) == 0 ) ) {
			i += 7;
		} else if ( ((i+17) <= value.len) && (ngx_strncmp("Transfer-Encoding", &(value.data[i]), 17) == 0 ) ) {
			i += 17;
		} else if ( ((i+7) <= value.len) && (ngx_strncmp("Upgrade", &(value.data[i]), 7) == 0 ) ) {
			i += 7;
		} else {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal value at position %d in Connection header \"%s\"", i, value.data);
			}
			return NGX_ERROR;
		}

		if ( (i < value.len) && (value.data[i] == ' ') ) {
			i++;
		}

		if ( i == value.len ) {
			return NGX_OK;
		}

		if ( (i < value.len) && (value.data[i] != ',') ) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal character at position %d in Connection header \"%s\"", i, value.data);
			}
			return NGX_ERROR;
		}
		i++;

		if ( (i < value.len) && (value.data[i] == ' ') ) {
			i++;
		}
	}

	if ( conf->log ) {
		ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Connection header \"%s\"", value.data);
	}
	return NGX_ERROR;
}

static ngx_int_t ngx_header_inspect_accept_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_int_t rc = NGX_AGAIN;
	ngx_uint_t i = 0;
	ngx_uint_t v;

	if (value.len == 0) {
		return NGX_OK;
	}

	while ( i < value.len ) {
		if (ngx_header_inspect_parse_mediatype(&(value.data[i]), value.len-i, &v) != NGX_OK) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid media-type at position %d in Accept header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i += v;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
		if (i == value.len) {
			rc = NGX_OK;
			break;
		}
		if (value.data[i] == ';') {
			i++;
			if (i >= value.len) {
				if ( conf->log ) {
					ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Accept header \"%s\"", value.data);
				}
				rc = NGX_ERROR;
				break;
			}
			if ((value.data[i] == ' ') && (i < value.len)) {
				i++;
			}
			if (ngx_header_inspect_parse_qvalue(&(value.data[i]), value.len-i, &v) != NGX_OK) {
				if ( conf->log ) {
					ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: invalid qvalue at position %d in Accept header \"%s\"", i, value.data);
				}
				rc = NGX_ERROR;
				break;
			}
			/* TODO: parse additional parameters */
			i += v;
			if ((value.data[i] == ' ') && (i < value.len)) {
				i++;
			}
			if (i == value.len) {
				rc = NGX_OK;
				break;
			}
		}
		if (value.data[i] != ',') {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal char at position %d in Accept header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i++;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
	}

	if (rc == NGX_AGAIN) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Accept header \"%s\"", value.data);
		}
		rc = NGX_ERROR;
	}

	return rc;
}

static ngx_int_t ngx_header_inspect_host_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	u_char d = '\0';
	ngx_uint_t i = 0;

	if ( value.len == 0 ) {
		return NGX_OK;
	}

	if ( value.data[0] == '[' ) {
		i++;
		/* IPv6 address */
		while ( i < value.len ) {
			d = value.data[i];
			if (
				((d < '0') || (d > '9'))
				&& ((d < 'a') || (d > 'z'))
				&& ((d < 'A') || (d > 'Z'))
				&& (d != ':') && (d != '.')
				&& (d != ']')
			) {
				if ( conf->log ) {
					ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal char at position %d in Host header \"%s\"", i, value.data);
				}
				return NGX_ERROR;
			}
			if ( d == ']' ) {
				break;
			}
			i++;
		}
		if ( d != ']' ) {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Host header \"%s\"", value.data);
			}
			return NGX_ERROR;
		}
		if ( i+1 < value.len ) {
			d = value.data[i+1];
		}
		i++;
	} else {
		/* IPv4 address or domain name */
		while ( i < value.len ) {
			d = value.data[i];

			if ( 
				((d < '0') || (d > '9'))
				&& ((d < 'a') || (d > 'z'))
				&& ((d < 'A') || (d > 'Z'))
				&& (d != '.') && (d != '-')
				&& ((d != ':') || (i == 0))
			) {
				if ( conf->log ) {
					ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal char at position %d in Host header \"%s\"", i, value.data);
				}
				return NGX_ERROR;
			}
			if ( d == ':' ) {
				break;
			}
			i++;
		}
	}

	if ( (d == ':') && (i+1 < value.len) ) {
		i++;
		for ( ; i < value.len ; i++ ) {
			if ( (value.data[i] < '0') || (value.data[i] > '9') ) {
				if ( conf->log ) {
					ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal char at position %d in Host header \"%s\"", i, value.data);
				}
				return NGX_ERROR;
			}
		}
	}

	if ( i != value.len ) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Host header \"%s\"", value.data);
		}
		return NGX_ERROR;
	}

	return NGX_OK;
}

static ngx_int_t ngx_header_inspect_allow_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_int_t rc = NGX_AGAIN;
	ngx_uint_t i = 0;

	if ( value.len == 0 ) {
		return NGX_OK;
	}

	while ( i < value.len ) {
		if ( (i+3 <= value.len) && (ngx_strncmp("GET", &(value.data[i]), 3) == 0) ) {
			i += 3;
		} else if ( (i+4 <= value.len) && (ngx_strncmp("POST", &(value.data[i]), 4) == 0) ) {
			i += 4;
		} else if ( (i+3 <= value.len) && (ngx_strncmp("PUT", &(value.data[i]), 3) == 0) ) {
			i += 3;
		} else if ( (i+4 <= value.len) && (ngx_strncmp("HEAD", &(value.data[i]), 4) == 0) ) {
			i += 4;
		} else if ( (i+6 <= value.len) && (ngx_strncmp("DELETE", &(value.data[i]), 6) == 0) ) {
			i += 6;
		} else if ( (i+7 <= value.len) && (ngx_strncmp("OPTIONS", &(value.data[i]), 7) == 0) ) {
			i += 7;
		} else if ( (i+5 <= value.len) && (ngx_strncmp("TRACE", &(value.data[i]), 5) == 0) ) {
			i += 5;
		} else if ( (i+7 <= value.len) && (ngx_strncmp("CONNECT", &(value.data[i]), 7) == 0) ) {
			i += 7;
		} else {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal method at position %d in Allow header \"%s\"", i, value.data);
				rc = NGX_ERROR;
				break;
			}
		}
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
		if (i == value.len) {
			rc = NGX_OK;
			break;
		}
		if (value.data[i] != ',') {
			if ( conf->log ) {
				ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: illegal char at position %d in Allow header \"%s\"", i, value.data);
			}
			rc = NGX_ERROR;
			break;
		}
		i++;
		if ((value.data[i] == ' ') && (i < value.len)) {
			i++;
		}
	}
	if (rc == NGX_AGAIN) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: unexpected end of Allow header \"%s\"", value.data);
		}
		rc = NGX_ERROR;
	}

	return rc;
}

static ngx_int_t ngx_header_inspect_ifrange_header(ngx_header_inspect_loc_conf_t *conf, ngx_log_t *log, ngx_str_t value) {
	ngx_uint_t v = 0;

	if (((value.data[0] == 'W') && (value.data[1] == '/'))|| (value.data[0] == '"')) {
	/* 1. entity-tag */
		if ( (ngx_header_inspect_parse_entity_tag(value.data, value.len, &v) != NGX_OK) || (v != value.len) ) {
			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) {
	ngx_uint_t v;

	/* HTTP-date */
	if ( ngx_header_inspect_http_date(value.data, value.len, &v) != 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;
	}
	if ( value.len != v ) {
		if ( conf->log ) {
			ngx_log_error(NGX_LOG_ALERT, log, 0, "header_inspect: trailing characters 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;
					}
				} else if ((h[i].key.len == 7) && (ngx_strcmp("Expires", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_date_header(conf, r->connection->log, "Expires", h[i].value);
					if ((rc != NGX_OK) && conf->block) {
						return NGX_HTTP_BAD_REQUEST;
					}
				} else if ((h[i].key.len == 13) && (ngx_strcmp("Last-Modified", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_date_header(conf, r->connection->log, "Last-Modified", h[i].value);
					if ((rc != NGX_OK) && conf->block) {
						return NGX_HTTP_BAD_REQUEST;
					}
				} else if ((h[i].key.len == 16) && (ngx_strcmp("Content-Encoding", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_contentencoding_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 == 15) && (ngx_strcmp("Accept-Encoding", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_acceptencoding_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 == 16) && (ngx_strcmp("Content-Language", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_contentlanguage_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 == 15) && (ngx_strcmp("Accept-Language", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_acceptlanguage_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 == 14) && (ngx_strcmp("Accept-Charset", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_acceptcharset_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 == 14) && (ngx_strcmp("Content-Length", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_digit_header("Content-Length", conf, r->connection->log, h[i].value);
					if ((rc != NGX_OK) && conf->block) {
						return NGX_HTTP_BAD_REQUEST;
					}
				} else if ((h[i].key.len == 12) && (ngx_strcmp("Max-Forwards", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_digit_header("Max-Forwards", 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-Match", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_ifmatch_header("If-Match", conf, r->connection->log, h[i].value);
					if ((rc != NGX_OK) && conf->block) {
						return NGX_HTTP_BAD_REQUEST;
					}
				} else if ((h[i].key.len == 13) && (ngx_strcmp("If-None-Match", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_ifmatch_header("If-None-Match", conf, r->connection->log, h[i].value);
					if ((rc != NGX_OK) && conf->block) {
						return NGX_HTTP_BAD_REQUEST;
					}
				} else if ((h[i].key.len == 5) && (ngx_strcmp("Allow", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_allow_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 == 4) && (ngx_strcmp("Host", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_host_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 == 6) && (ngx_strcmp("Accept", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_accept_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 == 10) && (ngx_strcmp("Connection", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_connection_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 == 13) && (ngx_strcmp("Content-Range", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_contentrange_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 == 10) && (ngx_strcmp("User-Agent", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_useragent_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 == 7) && (ngx_strcmp("Upgrade", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_upgrade_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 == 3) && (ngx_strcmp("Via", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_via_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 == 4) && (ngx_strcmp("From", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_from_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 == 6) && (ngx_strcmp("Pragma", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_pragma_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 == 12) && (ngx_strcmp("Content-Type", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_contenttype_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 == 11) && (ngx_strcmp("Content-MD5", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_contentmd5_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 == 13) && (ngx_strcmp("Authorization", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_authorization_header("Authorization", 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("Proxy-Authorization", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_authorization_header("Proxy-Authorization", conf, r->connection->log, h[i].value);
					if ((rc != NGX_OK) && conf->block) {
						return NGX_HTTP_BAD_REQUEST;
					}
				} else if ((h[i].key.len == 6) && (ngx_strcmp("Expect", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_expect_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 == 7) && (ngx_strcmp("Warning", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_warning_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 == 7) && (ngx_strcmp("Trailer", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_trailer_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 == 17) && (ngx_strcmp("Transfer-Encoding", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_transferencoding_header("Transfer-Encoding", conf, r->connection->log, h[i].value);
					if ((rc != NGX_OK) && conf->block) {
						return NGX_HTTP_BAD_REQUEST;
					}
				} else if ((h[i].key.len == 2) && (ngx_strcmp("TE", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_transferencoding_header("TE", conf, r->connection->log, h[i].value);
					if ((rc != NGX_OK) && conf->block) {
						return NGX_HTTP_BAD_REQUEST;
					}
				} else if ((h[i].key.len == 7) && (ngx_strcmp("Referer", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_referer_header("Referer", conf, r->connection->log, h[i].value);
					if ((rc != NGX_OK) && conf->block) {
						return NGX_HTTP_BAD_REQUEST;
					}
				} else if ((h[i].key.len == 16) && (ngx_strcmp("Content-Location", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_referer_header("Content-Location", conf, r->connection->log, h[i].value);
					if ((rc != NGX_OK) && conf->block) {
						return NGX_HTTP_BAD_REQUEST;
					}
				} else if ((h[i].key.len == 13) && (ngx_strcmp("Cache-Control", h[i].key.data) == 0) ) {
					rc = ngx_header_inspect_cachecontrol_header(conf, r->connection->log, h[i].value);
					if ((rc != NGX_OK) && conf->block) {
						return NGX_HTTP_BAD_REQUEST;
					}
				} else {
					/* TODO: support for other headers */
					if (conf->log_uninspected) {
						ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "header_inspect: uninspected header \"%s: %s\"", h[i].key.data, h[i].value.data);
					}
				}
			}
			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->log_uninspected = NGX_CONF_UNSET;

	conf->range_max_byteranges = 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_off_value(conf->log_uninspected, prev->log_uninspected, 0);

	ngx_conf_merge_uint_value(conf->range_max_byteranges, prev->range_max_byteranges, 5);

	return NGX_CONF_OK;
}