/* * 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; }