Previously, all requests that contained in header field names characters other than alphanumeric, or "-", or "_" were rejected with a 400 "Bad Request" error response. Now, the parser allows the same set of characters as specified in RFC 7230, including: "!", "#", "$", "%", "&", "'", "*", "+", ".", "^", "`", "|", and "~". Header field names that contain only these characters are considered valid. Also, there's a new option introduced: "discard_unsafe_fields". It accepts boolean value and it is set to "true" by default. When this option is "true", all header field names that contain characters in valid range, but other than alphanumeric or "-" are skipped during parsing. When the option is "false", these header fields aren't skipped. Requests with non-valid characters in header field names according to RFC 7230 are rejected regardless of "discard_unsafe_fields" setting. This closes #422 issue on GitHub.
1277 lines
30 KiB
C
1277 lines
30 KiB
C
|
|
/*
|
|
* Copyright (C) NGINX, Inc.
|
|
* Copyright (C) Valentin V. Bartenev
|
|
*/
|
|
|
|
#include <nxt_main.h>
|
|
|
|
|
|
static nxt_int_t nxt_http_parse_unusual_target(nxt_http_request_parse_t *rp,
|
|
u_char **pos, u_char *end);
|
|
static nxt_int_t nxt_http_parse_request_line(nxt_http_request_parse_t *rp,
|
|
u_char **pos, u_char *end);
|
|
static nxt_int_t nxt_http_parse_field_name(nxt_http_request_parse_t *rp,
|
|
u_char **pos, u_char *end);
|
|
static nxt_int_t nxt_http_parse_field_value(nxt_http_request_parse_t *rp,
|
|
u_char **pos, u_char *end);
|
|
static u_char *nxt_http_lookup_field_end(u_char *p, u_char *end);
|
|
static nxt_int_t nxt_http_parse_field_end(nxt_http_request_parse_t *rp,
|
|
u_char **pos, u_char *end);
|
|
|
|
static nxt_int_t nxt_http_parse_complex_target(nxt_http_request_parse_t *rp);
|
|
|
|
static nxt_int_t nxt_http_field_hash_test(nxt_lvlhsh_query_t *lhq, void *data);
|
|
|
|
static nxt_int_t nxt_http_field_hash_collision(nxt_lvlhsh_query_t *lhq,
|
|
void *data);
|
|
|
|
|
|
#define NXT_HTTP_MAX_FIELD_NAME 0xFF
|
|
#define NXT_HTTP_MAX_FIELD_VALUE NXT_INT32_T_MAX
|
|
|
|
#define NXT_HTTP_FIELD_LVLHSH_SHIFT 5
|
|
|
|
|
|
typedef enum {
|
|
NXT_HTTP_TARGET_SPACE = 1, /* \s */
|
|
NXT_HTTP_TARGET_HASH, /* # */
|
|
NXT_HTTP_TARGET_AGAIN,
|
|
NXT_HTTP_TARGET_BAD, /* \0\r\n */
|
|
|
|
/* traps below are used for extended check only */
|
|
|
|
NXT_HTTP_TARGET_SLASH = 5, /* / */
|
|
NXT_HTTP_TARGET_DOT, /* . */
|
|
NXT_HTTP_TARGET_ARGS_MARK, /* ? */
|
|
NXT_HTTP_TARGET_QUOTE_MARK, /* % */
|
|
} nxt_http_target_traps_e;
|
|
|
|
|
|
static const uint8_t nxt_http_target_chars[256] nxt_aligned(64) = {
|
|
/* \0 \n \r */
|
|
4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 4, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
|
|
/* \s ! " # $ % & ' ( ) * + , - . / */
|
|
1, 0, 0, 2, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 6, 5,
|
|
|
|
/* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7,
|
|
};
|
|
|
|
|
|
nxt_inline nxt_http_target_traps_e
|
|
nxt_http_parse_target(u_char **pos, u_char *end)
|
|
{
|
|
u_char *p;
|
|
nxt_uint_t trap;
|
|
|
|
p = *pos;
|
|
|
|
while (nxt_fast_path(end - p >= 10)) {
|
|
|
|
#define nxt_target_test_char(ch) \
|
|
\
|
|
trap = nxt_http_target_chars[ch]; \
|
|
\
|
|
if (nxt_slow_path(trap != 0)) { \
|
|
*pos = &(ch); \
|
|
return trap; \
|
|
}
|
|
|
|
/* enddef */
|
|
|
|
nxt_target_test_char(p[0]);
|
|
nxt_target_test_char(p[1]);
|
|
nxt_target_test_char(p[2]);
|
|
nxt_target_test_char(p[3]);
|
|
|
|
nxt_target_test_char(p[4]);
|
|
nxt_target_test_char(p[5]);
|
|
nxt_target_test_char(p[6]);
|
|
nxt_target_test_char(p[7]);
|
|
|
|
nxt_target_test_char(p[8]);
|
|
nxt_target_test_char(p[9]);
|
|
|
|
p += 10;
|
|
}
|
|
|
|
while (p != end) {
|
|
nxt_target_test_char(*p); p++;
|
|
}
|
|
|
|
return NXT_HTTP_TARGET_AGAIN;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_http_parse_request_init(nxt_http_request_parse_t *rp, nxt_mp_t *mp)
|
|
{
|
|
rp->mem_pool = mp;
|
|
|
|
rp->fields = nxt_list_create(mp, 8, sizeof(nxt_http_field_t));
|
|
if (nxt_slow_path(rp->fields == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
rp->field_hash = NXT_HTTP_FIELD_HASH_INIT;
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_http_parse_request(nxt_http_request_parse_t *rp, nxt_buf_mem_t *b)
|
|
{
|
|
nxt_int_t rc;
|
|
|
|
if (rp->handler == NULL) {
|
|
rp->handler = &nxt_http_parse_request_line;
|
|
}
|
|
|
|
do {
|
|
rc = rp->handler(rp, &b->pos, b->free);
|
|
} while (rc == NXT_OK);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_http_parse_fields(nxt_http_request_parse_t *rp, nxt_buf_mem_t *b)
|
|
{
|
|
nxt_int_t rc;
|
|
|
|
if (rp->handler == NULL) {
|
|
rp->handler = &nxt_http_parse_field_name;
|
|
}
|
|
|
|
do {
|
|
rc = rp->handler(rp, &b->pos, b->free);
|
|
} while (rc == NXT_OK);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_http_parse_request_line(nxt_http_request_parse_t *rp, u_char **pos,
|
|
u_char *end)
|
|
{
|
|
u_char *p, ch, *after_slash, *args;
|
|
nxt_int_t rc;
|
|
nxt_bool_t rest;
|
|
nxt_http_ver_t ver;
|
|
nxt_http_target_traps_e trap;
|
|
|
|
static const nxt_http_ver_t http11 = { "HTTP/1.1" };
|
|
static const nxt_http_ver_t http10 = { "HTTP/1.0" };
|
|
|
|
p = *pos;
|
|
|
|
rp->method.start = p;
|
|
|
|
for ( ;; ) {
|
|
|
|
while (nxt_fast_path(end - p >= 8)) {
|
|
|
|
#define nxt_method_test_char(ch) \
|
|
\
|
|
if (nxt_slow_path((ch) < 'A' || (ch) > 'Z')) { \
|
|
p = &(ch); \
|
|
goto method_unusual_char; \
|
|
}
|
|
|
|
/* enddef */
|
|
|
|
nxt_method_test_char(p[0]);
|
|
nxt_method_test_char(p[1]);
|
|
nxt_method_test_char(p[2]);
|
|
nxt_method_test_char(p[3]);
|
|
|
|
nxt_method_test_char(p[4]);
|
|
nxt_method_test_char(p[5]);
|
|
nxt_method_test_char(p[6]);
|
|
nxt_method_test_char(p[7]);
|
|
|
|
p += 8;
|
|
}
|
|
|
|
while (p != end) {
|
|
nxt_method_test_char(*p); p++;
|
|
}
|
|
|
|
rp->method.length = p - rp->method.start;
|
|
|
|
return NXT_AGAIN;
|
|
|
|
method_unusual_char:
|
|
|
|
ch = *p;
|
|
|
|
if (nxt_fast_path(ch == ' ')) {
|
|
rp->method.length = p - rp->method.start;
|
|
break;
|
|
}
|
|
|
|
if (ch == '_' || ch == '-') {
|
|
p++;
|
|
continue;
|
|
}
|
|
|
|
if (rp->method.start == p && (ch == '\r' || ch == '\n')) {
|
|
rp->method.start++;
|
|
p++;
|
|
continue;
|
|
}
|
|
|
|
rp->method.length = p - rp->method.start;
|
|
|
|
return NXT_HTTP_PARSE_INVALID;
|
|
}
|
|
|
|
p++;
|
|
|
|
if (nxt_slow_path(p == end)) {
|
|
return NXT_AGAIN;
|
|
}
|
|
|
|
/* target */
|
|
|
|
ch = *p;
|
|
|
|
if (nxt_slow_path(ch != '/')) {
|
|
rc = nxt_http_parse_unusual_target(rp, &p, end);
|
|
|
|
if (nxt_slow_path(rc != NXT_OK)) {
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
rp->target_start = p;
|
|
|
|
after_slash = p + 1;
|
|
args = NULL;
|
|
rest = 0;
|
|
|
|
continue_target:
|
|
|
|
for ( ;; ) {
|
|
p++;
|
|
|
|
trap = nxt_http_parse_target(&p, end);
|
|
|
|
switch (trap) {
|
|
case NXT_HTTP_TARGET_SLASH:
|
|
if (nxt_slow_path(after_slash == p)) {
|
|
rp->complex_target = 1;
|
|
goto rest_of_target;
|
|
}
|
|
|
|
after_slash = p + 1;
|
|
continue;
|
|
|
|
case NXT_HTTP_TARGET_DOT:
|
|
if (nxt_slow_path(after_slash == p)) {
|
|
rp->complex_target = 1;
|
|
goto rest_of_target;
|
|
}
|
|
|
|
continue;
|
|
|
|
case NXT_HTTP_TARGET_ARGS_MARK:
|
|
args = p + 1;
|
|
goto rest_of_target;
|
|
|
|
case NXT_HTTP_TARGET_SPACE:
|
|
rp->target_end = p;
|
|
goto space_after_target;
|
|
#if 0
|
|
case NXT_HTTP_TARGET_QUOTE_MARK:
|
|
rp->quoted_target = 1;
|
|
goto rest_of_target;
|
|
#else
|
|
case NXT_HTTP_TARGET_QUOTE_MARK:
|
|
#endif
|
|
case NXT_HTTP_TARGET_HASH:
|
|
rp->complex_target = 1;
|
|
goto rest_of_target;
|
|
|
|
case NXT_HTTP_TARGET_AGAIN:
|
|
rp->target_end = p;
|
|
return NXT_AGAIN;
|
|
|
|
case NXT_HTTP_TARGET_BAD:
|
|
rp->target_end = p;
|
|
return NXT_HTTP_PARSE_INVALID;
|
|
}
|
|
|
|
nxt_unreachable();
|
|
}
|
|
|
|
rest_of_target:
|
|
|
|
rest = 1;
|
|
|
|
for ( ;; ) {
|
|
p++;
|
|
|
|
trap = nxt_http_parse_target(&p, end);
|
|
|
|
switch (trap) {
|
|
case NXT_HTTP_TARGET_SPACE:
|
|
rp->target_end = p;
|
|
goto space_after_target;
|
|
|
|
case NXT_HTTP_TARGET_HASH:
|
|
rp->complex_target = 1;
|
|
continue;
|
|
|
|
case NXT_HTTP_TARGET_AGAIN:
|
|
rp->target_end = p;
|
|
return NXT_AGAIN;
|
|
|
|
case NXT_HTTP_TARGET_BAD:
|
|
rp->target_end = p;
|
|
return NXT_HTTP_PARSE_INVALID;
|
|
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
nxt_unreachable();
|
|
}
|
|
|
|
space_after_target:
|
|
|
|
if (nxt_slow_path(end - p < 10)) {
|
|
|
|
do {
|
|
p++;
|
|
|
|
if (p == end) {
|
|
return NXT_AGAIN;
|
|
}
|
|
|
|
} while (*p == ' ');
|
|
|
|
if (nxt_memcmp(p, "HTTP/", nxt_min(end - p, 5)) == 0) {
|
|
|
|
switch (end - p) {
|
|
case 8:
|
|
if (p[7] < '0' || p[7] > '9') {
|
|
break;
|
|
}
|
|
/* Fall through. */
|
|
case 7:
|
|
if (p[6] != '.') {
|
|
break;
|
|
}
|
|
/* Fall through. */
|
|
case 6:
|
|
if (p[5] < '0' || p[5] > '9') {
|
|
break;
|
|
}
|
|
/* Fall through. */
|
|
default:
|
|
return NXT_AGAIN;
|
|
}
|
|
}
|
|
|
|
//rp->space_in_target = 1;
|
|
|
|
if (rest) {
|
|
goto rest_of_target;
|
|
}
|
|
|
|
goto continue_target;
|
|
}
|
|
|
|
/* " HTTP/1.1\r\n" or " HTTP/1.1\n" */
|
|
|
|
if (nxt_slow_path(p[9] != '\r' && p[9] != '\n')) {
|
|
|
|
if (p[1] == ' ') {
|
|
/* surplus space after tartet */
|
|
p++;
|
|
goto space_after_target;
|
|
}
|
|
|
|
//rp->space_in_target = 1;
|
|
|
|
if (rest) {
|
|
goto rest_of_target;
|
|
}
|
|
|
|
goto continue_target;
|
|
}
|
|
|
|
nxt_memcpy(ver.str, &p[1], 8);
|
|
|
|
if (nxt_fast_path(ver.ui64 == http11.ui64
|
|
|| ver.ui64 == http10.ui64
|
|
|| (nxt_memcmp(ver.str, "HTTP/1.", 7) == 0
|
|
&& ver.s.minor >= '0' && ver.s.minor <= '9')))
|
|
{
|
|
rp->version.ui64 = ver.ui64;
|
|
|
|
if (nxt_fast_path(p[9] == '\r')) {
|
|
p += 10;
|
|
|
|
if (nxt_slow_path(p == end)) {
|
|
return NXT_AGAIN;
|
|
}
|
|
|
|
if (nxt_slow_path(*p != '\n')) {
|
|
return NXT_HTTP_PARSE_INVALID;
|
|
}
|
|
|
|
*pos = p + 1;
|
|
|
|
} else {
|
|
*pos = p + 10;
|
|
}
|
|
|
|
if (rp->complex_target != 0
|
|
#if 0
|
|
|| rp->quoted_target != 0
|
|
#endif
|
|
)
|
|
{
|
|
rc = nxt_http_parse_complex_target(rp);
|
|
|
|
if (nxt_slow_path(rc != NXT_OK)) {
|
|
return rc;
|
|
}
|
|
|
|
return nxt_http_parse_field_name(rp, pos, end);
|
|
}
|
|
|
|
rp->path.start = rp->target_start;
|
|
|
|
if (args != NULL) {
|
|
rp->path.length = args - rp->target_start - 1;
|
|
|
|
rp->args.length = rp->target_end - args;
|
|
rp->args.start = args;
|
|
|
|
} else {
|
|
rp->path.length = rp->target_end - rp->target_start;
|
|
}
|
|
|
|
return nxt_http_parse_field_name(rp, pos, end);
|
|
}
|
|
|
|
if (nxt_memcmp(ver.s.prefix, "HTTP/", 5) == 0
|
|
&& ver.s.major >= '0' && ver.s.major <= '9'
|
|
&& ver.s.point == '.'
|
|
&& ver.s.minor >= '0' && ver.s.minor <= '9')
|
|
{
|
|
rp->version.ui64 = ver.ui64;
|
|
return NXT_HTTP_PARSE_UNSUPPORTED_VERSION;
|
|
}
|
|
|
|
return NXT_HTTP_PARSE_INVALID;
|
|
}
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_http_parse_unusual_target(nxt_http_request_parse_t *rp, u_char **pos,
|
|
u_char *end)
|
|
{
|
|
u_char *p, ch;
|
|
|
|
p = *pos;
|
|
|
|
ch = *p;
|
|
|
|
if (ch == ' ') {
|
|
/* skip surplus spaces before target */
|
|
|
|
do {
|
|
p++;
|
|
|
|
if (nxt_slow_path(p == end)) {
|
|
return NXT_AGAIN;
|
|
}
|
|
|
|
ch = *p;
|
|
|
|
} while (ch == ' ');
|
|
|
|
if (ch == '/') {
|
|
*pos = p;
|
|
return NXT_OK;
|
|
}
|
|
}
|
|
|
|
/* absolute path or '*' */
|
|
|
|
/* TODO */
|
|
|
|
return NXT_HTTP_PARSE_INVALID;
|
|
}
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_http_parse_field_name(nxt_http_request_parse_t *rp, u_char **pos,
|
|
u_char *end)
|
|
{
|
|
u_char *p, c;
|
|
size_t len;
|
|
uint32_t hash;
|
|
|
|
static const u_char normal[256] nxt_aligned(64) =
|
|
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
|
/* \s ! " # $ % & ' ( ) * + , . / : ; < = > ? */
|
|
"\0\1\0\1\1\1\1\1\0\0\1\1\0" "-" "\1\0" "0123456789" "\0\0\0\0\0\0"
|
|
|
|
/* @ [ \ ] ^ _ */
|
|
"\0" "abcdefghijklmnopqrstuvwxyz" "\0\0\0\1\1"
|
|
/* ` { | } ~ */
|
|
"\1" "abcdefghijklmnopqrstuvwxyz" "\0\1\0\1\0"
|
|
|
|
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
|
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
|
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
|
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
|
|
|
|
p = *pos + rp->field_name.length;
|
|
hash = rp->field_hash;
|
|
|
|
while (nxt_fast_path(end - p >= 8)) {
|
|
|
|
#define nxt_field_name_test_char(ch) \
|
|
\
|
|
c = normal[ch]; \
|
|
\
|
|
if (nxt_slow_path(c <= '\1')) { \
|
|
if (c == '\0') { \
|
|
p = &(ch); \
|
|
goto name_end; \
|
|
} \
|
|
\
|
|
rp->skip_field = rp->discard_unsafe_fields; \
|
|
c = ch; \
|
|
} \
|
|
\
|
|
hash = nxt_http_field_hash_char(hash, c);
|
|
|
|
/* enddef */
|
|
|
|
nxt_field_name_test_char(p[0]);
|
|
nxt_field_name_test_char(p[1]);
|
|
nxt_field_name_test_char(p[2]);
|
|
nxt_field_name_test_char(p[3]);
|
|
|
|
nxt_field_name_test_char(p[4]);
|
|
nxt_field_name_test_char(p[5]);
|
|
nxt_field_name_test_char(p[6]);
|
|
nxt_field_name_test_char(p[7]);
|
|
|
|
p += 8;
|
|
}
|
|
|
|
while (nxt_fast_path(p != end)) {
|
|
nxt_field_name_test_char(*p); p++;
|
|
}
|
|
|
|
len = p - *pos;
|
|
|
|
if (nxt_slow_path(len > NXT_HTTP_MAX_FIELD_NAME)) {
|
|
return NXT_HTTP_PARSE_TOO_LARGE_FIELD;
|
|
}
|
|
|
|
rp->field_hash = hash;
|
|
rp->field_name.length = len;
|
|
|
|
rp->handler = &nxt_http_parse_field_name;
|
|
|
|
return NXT_AGAIN;
|
|
|
|
name_end:
|
|
|
|
if (nxt_fast_path(*p == ':')) {
|
|
if (nxt_slow_path(p == *pos)) {
|
|
return NXT_HTTP_PARSE_INVALID;
|
|
}
|
|
|
|
len = p - *pos;
|
|
|
|
if (nxt_slow_path(len > NXT_HTTP_MAX_FIELD_NAME)) {
|
|
return NXT_HTTP_PARSE_TOO_LARGE_FIELD;
|
|
}
|
|
|
|
rp->field_hash = hash;
|
|
|
|
rp->field_name.length = len;
|
|
rp->field_name.start = *pos;
|
|
|
|
*pos = p + 1;
|
|
|
|
return nxt_http_parse_field_value(rp, pos, end);
|
|
}
|
|
|
|
if (nxt_slow_path(p != *pos)) {
|
|
return NXT_HTTP_PARSE_INVALID;
|
|
}
|
|
|
|
return nxt_http_parse_field_end(rp, pos, end);
|
|
}
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_http_parse_field_value(nxt_http_request_parse_t *rp, u_char **pos,
|
|
u_char *end)
|
|
{
|
|
u_char *p, *start, ch;
|
|
size_t len;
|
|
|
|
p = *pos;
|
|
|
|
for ( ;; ) {
|
|
if (nxt_slow_path(p == end)) {
|
|
*pos = p;
|
|
rp->handler = &nxt_http_parse_field_value;
|
|
return NXT_AGAIN;
|
|
}
|
|
|
|
ch = *p;
|
|
|
|
if (ch != ' ' && ch != '\t') {
|
|
break;
|
|
}
|
|
|
|
p++;
|
|
}
|
|
|
|
start = p;
|
|
|
|
p += rp->field_value.length;
|
|
|
|
for ( ;; ) {
|
|
p = nxt_http_lookup_field_end(p, end);
|
|
|
|
if (nxt_slow_path(p == end)) {
|
|
*pos = start;
|
|
|
|
len = p - start;
|
|
|
|
if (nxt_slow_path(len > NXT_HTTP_MAX_FIELD_VALUE)) {
|
|
return NXT_HTTP_PARSE_TOO_LARGE_FIELD;
|
|
}
|
|
|
|
rp->field_value.length = len;
|
|
rp->handler = &nxt_http_parse_field_value;
|
|
return NXT_AGAIN;
|
|
}
|
|
|
|
ch = *p;
|
|
|
|
if (nxt_fast_path(ch == '\r' || ch == '\n')) {
|
|
break;
|
|
}
|
|
|
|
if (ch != '\t') {
|
|
return NXT_HTTP_PARSE_INVALID;
|
|
}
|
|
|
|
p++;
|
|
}
|
|
|
|
*pos = p;
|
|
|
|
if (nxt_fast_path(p != start)) {
|
|
|
|
while (p[-1] == ' ' || p[-1] == '\t') {
|
|
p--;
|
|
}
|
|
}
|
|
|
|
len = p - start;
|
|
|
|
if (nxt_slow_path(len > NXT_HTTP_MAX_FIELD_VALUE)) {
|
|
return NXT_HTTP_PARSE_TOO_LARGE_FIELD;
|
|
}
|
|
|
|
rp->field_value.length = len;
|
|
rp->field_value.start = start;
|
|
|
|
return nxt_http_parse_field_end(rp, pos, end);
|
|
}
|
|
|
|
|
|
static u_char *
|
|
nxt_http_lookup_field_end(u_char *p, u_char *end)
|
|
{
|
|
while (nxt_fast_path(end - p >= 16)) {
|
|
|
|
#define nxt_field_end_test_char(ch) \
|
|
\
|
|
if (nxt_slow_path((ch) < 0x20)) { \
|
|
return &(ch); \
|
|
}
|
|
|
|
/* enddef */
|
|
|
|
nxt_field_end_test_char(p[0]);
|
|
nxt_field_end_test_char(p[1]);
|
|
nxt_field_end_test_char(p[2]);
|
|
nxt_field_end_test_char(p[3]);
|
|
|
|
nxt_field_end_test_char(p[4]);
|
|
nxt_field_end_test_char(p[5]);
|
|
nxt_field_end_test_char(p[6]);
|
|
nxt_field_end_test_char(p[7]);
|
|
|
|
nxt_field_end_test_char(p[8]);
|
|
nxt_field_end_test_char(p[9]);
|
|
nxt_field_end_test_char(p[10]);
|
|
nxt_field_end_test_char(p[11]);
|
|
|
|
nxt_field_end_test_char(p[12]);
|
|
nxt_field_end_test_char(p[13]);
|
|
nxt_field_end_test_char(p[14]);
|
|
nxt_field_end_test_char(p[15]);
|
|
|
|
p += 16;
|
|
}
|
|
|
|
while (nxt_fast_path(end - p >= 4)) {
|
|
|
|
nxt_field_end_test_char(p[0]);
|
|
nxt_field_end_test_char(p[1]);
|
|
nxt_field_end_test_char(p[2]);
|
|
nxt_field_end_test_char(p[3]);
|
|
|
|
p += 4;
|
|
}
|
|
|
|
switch (end - p) {
|
|
case 3:
|
|
nxt_field_end_test_char(*p); p++;
|
|
/* Fall through. */
|
|
case 2:
|
|
nxt_field_end_test_char(*p); p++;
|
|
/* Fall through. */
|
|
case 1:
|
|
nxt_field_end_test_char(*p); p++;
|
|
/* Fall through. */
|
|
case 0:
|
|
break;
|
|
default:
|
|
nxt_unreachable();
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_http_parse_field_end(nxt_http_request_parse_t *rp, u_char **pos,
|
|
u_char *end)
|
|
{
|
|
u_char *p;
|
|
nxt_http_field_t *field;
|
|
|
|
p = *pos;
|
|
|
|
if (nxt_fast_path(*p == '\r')) {
|
|
p++;
|
|
|
|
if (nxt_slow_path(p == end)) {
|
|
rp->handler = &nxt_http_parse_field_end;
|
|
return NXT_AGAIN;
|
|
}
|
|
}
|
|
|
|
if (nxt_fast_path(*p == '\n')) {
|
|
*pos = p + 1;
|
|
|
|
if (rp->field_name.length != 0) {
|
|
if (rp->skip_field) {
|
|
rp->skip_field = 0;
|
|
|
|
} else {
|
|
field = nxt_list_add(rp->fields);
|
|
|
|
if (nxt_slow_path(field == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
field->hash = nxt_http_field_hash_end(rp->field_hash);
|
|
field->skip = 0;
|
|
field->hopbyhop = 0;
|
|
|
|
field->name_length = rp->field_name.length;
|
|
field->value_length = rp->field_value.length;
|
|
field->name = rp->field_name.start;
|
|
field->value = rp->field_value.start;
|
|
}
|
|
|
|
rp->field_hash = NXT_HTTP_FIELD_HASH_INIT;
|
|
|
|
rp->field_name.length = 0;
|
|
rp->field_value.length = 0;
|
|
|
|
rp->handler = &nxt_http_parse_field_name;
|
|
return NXT_OK;
|
|
}
|
|
|
|
return NXT_DONE;
|
|
}
|
|
|
|
return NXT_HTTP_PARSE_INVALID;
|
|
}
|
|
|
|
|
|
#define \
|
|
nxt_http_is_normal(c) \
|
|
(nxt_fast_path((nxt_http_normal[c / 8] & (1 << (c & 7))) != 0))
|
|
|
|
|
|
static const uint8_t nxt_http_normal[32] nxt_aligned(32) = {
|
|
|
|
/* \0 \r \n */
|
|
0xFE, 0xDB, 0xFF, 0xFF, /* 1111 1110 1101 1011 1111 1111 1111 1111 */
|
|
|
|
/* '&%$ #"! /.-, |*)( 7654 3210 ?>=< ;:98 */
|
|
0xD6, 0x37, 0xFF, 0x7F, /* 1101 0110 0011 0111 1111 1111 0111 1111 */
|
|
|
|
/* GFED CBA@ ONML KJIH WVUT SRQP _^]\ [ZYX */
|
|
0xFF, 0xFF, 0xFF, 0xFF, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
|
|
|
/* gfed cba` onml kjih wvut srqp ~}| {zyx */
|
|
0xFF, 0xFF, 0xFF, 0xFF, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
|
0xFF, 0xFF, 0xFF, 0xFF, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
|
0xFF, 0xFF, 0xFF, 0xFF, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
|
0xFF, 0xFF, 0xFF, 0xFF, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
|
|
};
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_http_parse_complex_target(nxt_http_request_parse_t *rp)
|
|
{
|
|
u_char *p, *u, c, ch, high, *args;
|
|
|
|
enum {
|
|
sw_normal = 0,
|
|
sw_slash,
|
|
sw_dot,
|
|
sw_dot_dot,
|
|
sw_quoted,
|
|
sw_quoted_second,
|
|
} state, saved_state;
|
|
|
|
nxt_prefetch(nxt_http_normal);
|
|
|
|
state = sw_normal;
|
|
saved_state = sw_normal;
|
|
p = rp->target_start;
|
|
|
|
u = nxt_mp_alloc(rp->mem_pool, rp->target_end - p + 1);
|
|
if (nxt_slow_path(u == NULL)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
rp->path.length = 0;
|
|
rp->path.start = u;
|
|
|
|
high = '\0';
|
|
args = NULL;
|
|
|
|
while (p < rp->target_end) {
|
|
|
|
ch = *p++;
|
|
|
|
again:
|
|
|
|
switch (state) {
|
|
|
|
case sw_normal:
|
|
|
|
if (nxt_http_is_normal(ch)) {
|
|
*u++ = ch;
|
|
continue;
|
|
}
|
|
|
|
switch (ch) {
|
|
case '/':
|
|
state = sw_slash;
|
|
*u++ = ch;
|
|
continue;
|
|
case '%':
|
|
saved_state = state;
|
|
state = sw_quoted;
|
|
continue;
|
|
case '?':
|
|
args = p;
|
|
goto args;
|
|
case '#':
|
|
goto done;
|
|
default:
|
|
*u++ = ch;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
|
|
case sw_slash:
|
|
|
|
if (nxt_http_is_normal(ch)) {
|
|
state = sw_normal;
|
|
*u++ = ch;
|
|
continue;
|
|
}
|
|
|
|
switch (ch) {
|
|
case '/':
|
|
continue;
|
|
case '.':
|
|
state = sw_dot;
|
|
*u++ = ch;
|
|
continue;
|
|
case '%':
|
|
saved_state = state;
|
|
state = sw_quoted;
|
|
continue;
|
|
case '?':
|
|
args = p;
|
|
goto args;
|
|
case '#':
|
|
goto done;
|
|
default:
|
|
state = sw_normal;
|
|
*u++ = ch;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
|
|
case sw_dot:
|
|
|
|
if (nxt_http_is_normal(ch)) {
|
|
state = sw_normal;
|
|
*u++ = ch;
|
|
continue;
|
|
}
|
|
|
|
switch (ch) {
|
|
case '/':
|
|
state = sw_slash;
|
|
u--;
|
|
continue;
|
|
case '.':
|
|
state = sw_dot_dot;
|
|
*u++ = ch;
|
|
continue;
|
|
case '%':
|
|
saved_state = state;
|
|
state = sw_quoted;
|
|
continue;
|
|
case '?':
|
|
u--;
|
|
args = p;
|
|
goto args;
|
|
case '#':
|
|
u--;
|
|
goto done;
|
|
default:
|
|
state = sw_normal;
|
|
*u++ = ch;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
|
|
case sw_dot_dot:
|
|
|
|
if (nxt_http_is_normal(ch)) {
|
|
state = sw_normal;
|
|
*u++ = ch;
|
|
continue;
|
|
}
|
|
|
|
switch (ch) {
|
|
|
|
case '/':
|
|
case '?':
|
|
case '#':
|
|
u -= 5;
|
|
|
|
for ( ;; ) {
|
|
if (u < rp->path.start) {
|
|
return NXT_HTTP_PARSE_INVALID;
|
|
}
|
|
|
|
if (*u == '/') {
|
|
u++;
|
|
break;
|
|
}
|
|
|
|
u--;
|
|
}
|
|
|
|
if (ch == '?') {
|
|
args = p;
|
|
goto args;
|
|
}
|
|
|
|
if (ch == '#') {
|
|
goto done;
|
|
}
|
|
|
|
state = sw_slash;
|
|
break;
|
|
|
|
case '%':
|
|
saved_state = state;
|
|
state = sw_quoted;
|
|
continue;
|
|
|
|
default:
|
|
state = sw_normal;
|
|
*u++ = ch;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
|
|
case sw_quoted:
|
|
//rp->quoted_target = 1;
|
|
|
|
if (ch >= '0' && ch <= '9') {
|
|
high = (u_char) (ch - '0');
|
|
state = sw_quoted_second;
|
|
continue;
|
|
}
|
|
|
|
c = (u_char) (ch | 0x20);
|
|
if (c >= 'a' && c <= 'f') {
|
|
high = (u_char) (c - 'a' + 10);
|
|
state = sw_quoted_second;
|
|
continue;
|
|
}
|
|
|
|
return NXT_HTTP_PARSE_INVALID;
|
|
|
|
case sw_quoted_second:
|
|
if (ch >= '0' && ch <= '9') {
|
|
ch = (u_char) ((high << 4) + ch - '0');
|
|
|
|
if (ch == '%') {
|
|
state = sw_normal;
|
|
*u++ = '%';
|
|
|
|
if (rp->encoded_slashes) {
|
|
*u++ = '2';
|
|
*u++ = '5';
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (ch == '#') {
|
|
state = sw_normal;
|
|
*u++ = '#';
|
|
continue;
|
|
}
|
|
|
|
if (ch == '\0') {
|
|
return NXT_HTTP_PARSE_INVALID;
|
|
}
|
|
|
|
state = saved_state;
|
|
goto again;
|
|
}
|
|
|
|
c = (u_char) (ch | 0x20);
|
|
if (c >= 'a' && c <= 'f') {
|
|
ch = (u_char) ((high << 4) + c - 'a' + 10);
|
|
|
|
if (ch == '?') {
|
|
state = sw_normal;
|
|
*u++ = ch;
|
|
continue;
|
|
}
|
|
|
|
if (ch == '/' && rp->encoded_slashes) {
|
|
state = sw_normal;
|
|
*u++ = '%';
|
|
*u++ = '2';
|
|
*u++ = p[-1]; /* 'f' or 'F' */
|
|
continue;
|
|
}
|
|
|
|
state = saved_state;
|
|
goto again;
|
|
}
|
|
|
|
return NXT_HTTP_PARSE_INVALID;
|
|
}
|
|
}
|
|
|
|
if (state >= sw_dot) {
|
|
if (state >= sw_quoted) {
|
|
return NXT_HTTP_PARSE_INVALID;
|
|
}
|
|
|
|
/* "/." and "/.." must be normalized similar to "/./" and "/../". */
|
|
ch = '/';
|
|
goto again;
|
|
}
|
|
|
|
args:
|
|
|
|
for (/* void */; p < rp->target_end; p++) {
|
|
if (*p == '#') {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (args != NULL) {
|
|
rp->args.length = p - args;
|
|
rp->args.start = args;
|
|
}
|
|
|
|
done:
|
|
|
|
rp->path.length = u - rp->path.start;
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
const nxt_lvlhsh_proto_t nxt_http_fields_hash_proto nxt_aligned(64) = {
|
|
NXT_LVLHSH_BUCKET_SIZE(64),
|
|
{ NXT_HTTP_FIELD_LVLHSH_SHIFT, 0, 0, 0, 0, 0, 0, 0 },
|
|
nxt_http_field_hash_test,
|
|
nxt_lvlhsh_alloc,
|
|
nxt_lvlhsh_free,
|
|
};
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_http_field_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
|
|
{
|
|
nxt_http_field_proc_t *field;
|
|
|
|
field = data;
|
|
|
|
if (nxt_strcasestr_eq(&lhq->key, &field->name)) {
|
|
return NXT_OK;
|
|
}
|
|
|
|
return NXT_DECLINED;
|
|
}
|
|
|
|
|
|
static nxt_int_t
|
|
nxt_http_field_hash_collision(nxt_lvlhsh_query_t *lhq, void *data)
|
|
{
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_http_fields_hash(nxt_lvlhsh_t *hash,
|
|
nxt_http_field_proc_t items[], nxt_uint_t count)
|
|
{
|
|
u_char ch;
|
|
uint32_t key;
|
|
nxt_str_t *name;
|
|
nxt_int_t ret;
|
|
nxt_uint_t i, j;
|
|
nxt_lvlhsh_query_t lhq;
|
|
|
|
lhq.replace = 0;
|
|
lhq.proto = &nxt_http_fields_hash_proto;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
key = NXT_HTTP_FIELD_HASH_INIT;
|
|
name = &items[i].name;
|
|
|
|
for (j = 0; j < name->length; j++) {
|
|
ch = nxt_lowcase(name->start[j]);
|
|
key = nxt_http_field_hash_char(key, ch);
|
|
}
|
|
|
|
lhq.key_hash = nxt_http_field_hash_end(key) & 0xFFFF;
|
|
lhq.key = *name;
|
|
lhq.value = &items[i];
|
|
|
|
ret = nxt_lvlhsh_insert(hash, &lhq);
|
|
|
|
if (nxt_slow_path(ret != NXT_OK)) {
|
|
return NXT_ERROR;
|
|
}
|
|
}
|
|
|
|
return NXT_OK;
|
|
}
|
|
|
|
|
|
nxt_uint_t
|
|
nxt_http_fields_hash_collisions(nxt_lvlhsh_t *hash,
|
|
nxt_http_field_proc_t items[], nxt_uint_t count, nxt_bool_t level)
|
|
{
|
|
u_char ch;
|
|
uint32_t key, mask;
|
|
nxt_str_t *name;
|
|
nxt_uint_t colls, i, j;
|
|
nxt_lvlhsh_proto_t proto;
|
|
nxt_lvlhsh_query_t lhq;
|
|
|
|
proto = nxt_http_fields_hash_proto;
|
|
proto.test = nxt_http_field_hash_collision;
|
|
|
|
lhq.replace = 0;
|
|
lhq.proto = &proto;
|
|
|
|
mask = level ? (1 << NXT_HTTP_FIELD_LVLHSH_SHIFT) - 1 : 0xFFFF;
|
|
|
|
colls = 0;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
key = NXT_HTTP_FIELD_HASH_INIT;
|
|
name = &items[i].name;
|
|
|
|
for (j = 0; j < name->length; j++) {
|
|
ch = nxt_lowcase(name->start[j]);
|
|
key = nxt_http_field_hash_char(key, ch);
|
|
}
|
|
|
|
lhq.key_hash = nxt_http_field_hash_end(key) & mask;
|
|
lhq.value = &items[i];
|
|
|
|
if (nxt_lvlhsh_insert(hash, &lhq) == NXT_DECLINED) {
|
|
colls++;
|
|
}
|
|
}
|
|
|
|
return colls;
|
|
}
|
|
|
|
|
|
nxt_int_t
|
|
nxt_http_fields_process(nxt_list_t *fields, nxt_lvlhsh_t *hash, void *ctx)
|
|
{
|
|
nxt_int_t ret;
|
|
nxt_http_field_t *field;
|
|
|
|
nxt_list_each(field, fields) {
|
|
|
|
ret = nxt_http_field_process(field, hash, ctx);
|
|
if (nxt_slow_path(ret != NXT_OK)) {
|
|
return ret;
|
|
}
|
|
|
|
} nxt_list_loop;
|
|
|
|
return NXT_OK;
|
|
}
|