This commit introduced the basic URI rewrite. It allows users to change request URI. Note the "rewrite" option ignores the contained query if any and the query from the request is preserverd.
An example:
"routes": [
{
"match": {
"uri": "/v1/test"
},
"action": {
"return": 200
}
},
{
"action": {
"rewrite": "/v1$uri",
"pass": "routes"
}
}
]
Reviewed-by: Alejandro Colomar <alx@nginx.com>
1276 lines
30 KiB
C
1276 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, const u_char *end);
|
|
static nxt_int_t nxt_http_parse_request_line(nxt_http_request_parse_t *rp,
|
|
u_char **pos, const u_char *end);
|
|
static nxt_int_t nxt_http_parse_field_name(nxt_http_request_parse_t *rp,
|
|
u_char **pos, const u_char *end);
|
|
static nxt_int_t nxt_http_parse_field_value(nxt_http_request_parse_t *rp,
|
|
u_char **pos, const u_char *end);
|
|
static u_char *nxt_http_lookup_field_end(u_char *p, const u_char *end);
|
|
static nxt_int_t nxt_http_parse_field_end(nxt_http_request_parse_t *rp,
|
|
u_char **pos, const u_char *end);
|
|
|
|
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, const 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,
|
|
const 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 (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
|
|
|| (memcmp(ver.str, "HTTP/1.", 7) == 0
|
|
&& ver.s.minor >= '0' && ver.s.minor <= '9')))
|
|
{
|
|
rp->version.ui64 = ver.ui64;
|
|
|
|
p += 9;
|
|
if (nxt_fast_path(*p == '\r')) {
|
|
|
|
if (nxt_slow_path(p + 1 == end)) {
|
|
return NXT_AGAIN;
|
|
}
|
|
|
|
if (nxt_slow_path(p[1] != '\n')) {
|
|
return NXT_HTTP_PARSE_INVALID;
|
|
}
|
|
|
|
*pos = p + 2;
|
|
|
|
} else {
|
|
*pos = p + 1;
|
|
}
|
|
|
|
rp->request_line_end = p;
|
|
|
|
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 (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,
|
|
const 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,
|
|
const 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,
|
|
const 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, const 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,
|
|
const 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 */
|
|
};
|
|
|
|
|
|
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;
|
|
}
|