/* * Copyright (C) Igor Sysoev * Copyright (C) NGINX, Inc. */ #include #include typedef enum { NXT_HTTP_ROUTE_TABLE = 0, NXT_HTTP_ROUTE_STRING, NXT_HTTP_ROUTE_STRING_PTR, NXT_HTTP_ROUTE_HEADER, NXT_HTTP_ROUTE_ARGUMENT, NXT_HTTP_ROUTE_COOKIE, NXT_HTTP_ROUTE_SCHEME, } nxt_http_route_object_t; typedef enum { NXT_HTTP_ROUTE_PATTERN_EXACT = 0, NXT_HTTP_ROUTE_PATTERN_BEGIN, NXT_HTTP_ROUTE_PATTERN_MIDDLE, NXT_HTTP_ROUTE_PATTERN_END, NXT_HTTP_ROUTE_PATTERN_SUBSTRING, } nxt_http_route_pattern_type_t; typedef enum { NXT_HTTP_ROUTE_PATTERN_NOCASE = 0, NXT_HTTP_ROUTE_PATTERN_LOWCASE, NXT_HTTP_ROUTE_PATTERN_UPCASE, } nxt_http_route_pattern_case_t; typedef struct { nxt_conf_value_t *host; nxt_conf_value_t *uri; nxt_conf_value_t *method; nxt_conf_value_t *headers; nxt_conf_value_t *arguments; nxt_conf_value_t *cookies; nxt_conf_value_t *scheme; } nxt_http_route_match_conf_t; typedef struct { u_char *start1; u_char *start2; uint32_t length1; uint32_t length2; uint32_t min_length; nxt_http_route_pattern_type_t type:8; uint8_t case_sensitive; /* 1 bit */ uint8_t negative; /* 1 bit */ uint8_t any; /* 1 bit */ } nxt_http_route_pattern_t; typedef struct { uint16_t hash; uint16_t name_length; uint32_t value_length; u_char *name; u_char *value; } nxt_http_name_value_t; typedef struct { uint16_t hash; uint16_t name_length; uint32_t value_length; u_char *name; u_char *value; } nxt_http_cookie_t; typedef struct { /* The object must be the first field. */ nxt_http_route_object_t object:8; uint32_t items; union { uintptr_t offset; struct { u_char *start; uint16_t hash; uint16_t length; } name; } u; nxt_http_route_pattern_t pattern[0]; } nxt_http_route_rule_t; typedef struct { uint32_t items; nxt_http_route_rule_t *rule[0]; } nxt_http_route_ruleset_t; typedef struct { /* The object must be the first field. */ nxt_http_route_object_t object:8; uint32_t items; nxt_http_route_ruleset_t *ruleset[0]; } nxt_http_route_table_t; typedef union { nxt_http_route_rule_t *rule; nxt_http_route_table_t *table; } nxt_http_route_test_t; typedef struct { uint32_t items; nxt_http_pass_t pass; nxt_http_route_test_t test[0]; } nxt_http_route_match_t; struct nxt_http_route_s { nxt_str_t name; uint32_t items; nxt_http_route_match_t *match[0]; }; struct nxt_http_routes_s { uint32_t items; nxt_http_route_t *route[0]; }; #define NJS_COOKIE_HASH \ (nxt_http_field_hash_end( \ nxt_http_field_hash_char( \ nxt_http_field_hash_char( \ nxt_http_field_hash_char( \ nxt_http_field_hash_char( \ nxt_http_field_hash_char( \ nxt_http_field_hash_char(NXT_HTTP_FIELD_HASH_INIT, \ 'c'), 'o'), 'o'), 'k'), 'i'), 'e')) & 0xFFFF) static nxt_http_route_t *nxt_http_route_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv); static nxt_http_route_match_t *nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv); static nxt_http_route_table_t *nxt_http_route_table_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *table_cv, nxt_http_route_object_t object, nxt_bool_t case_sensitive); static nxt_http_route_ruleset_t *nxt_http_route_ruleset_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *ruleset_cv, nxt_http_route_object_t object, nxt_bool_t case_sensitive); static nxt_http_route_rule_t *nxt_http_route_rule_name_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *rule_cv, nxt_str_t *name, nxt_bool_t case_sensitive); static nxt_http_route_rule_t *nxt_http_route_rule_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *cv, nxt_bool_t case_sensitive, nxt_http_route_pattern_case_t pattern_case); static int nxt_http_pattern_compare(const void *one, const void *two); static nxt_int_t nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *cv, nxt_http_route_pattern_t *pattern, nxt_http_route_pattern_case_t pattern_case); static u_char *nxt_http_route_pattern_copy(nxt_mp_t *mp, nxt_str_t *test, nxt_http_route_pattern_case_t pattern_case); static void nxt_http_route_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_http_route_t *route); static void nxt_http_pass_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_http_pass_t *pass); static nxt_http_route_t *nxt_http_route_find(nxt_http_routes_t *routes, nxt_str_t *name); static void nxt_http_route_cleanup(nxt_task_t *task, nxt_http_route_t *routes); static nxt_http_pass_t *nxt_http_route_pass(nxt_task_t *task, nxt_http_request_t *r, nxt_http_pass_t *start); static nxt_http_pass_t *nxt_http_route_match(nxt_http_request_t *r, nxt_http_route_match_t *match); static nxt_int_t nxt_http_route_table(nxt_http_request_t *r, nxt_http_route_table_t *table); static nxt_int_t nxt_http_route_ruleset(nxt_http_request_t *r, nxt_http_route_ruleset_t *ruleset); static nxt_int_t nxt_http_route_rule(nxt_http_request_t *r, nxt_http_route_rule_t *rule); static nxt_int_t nxt_http_route_header(nxt_http_request_t *r, nxt_http_route_rule_t *rule); static nxt_int_t nxt_http_route_arguments(nxt_http_request_t *r, nxt_http_route_rule_t *rule); static nxt_array_t *nxt_http_route_arguments_parse(nxt_http_request_t *r); static nxt_http_name_value_t *nxt_http_route_argument(nxt_array_t *array, u_char *name, size_t name_length, uint32_t hash, u_char *start, u_char *end); static nxt_int_t nxt_http_route_test_argument(nxt_http_request_t *r, nxt_http_route_rule_t *rule, nxt_array_t *array); static nxt_int_t nxt_http_route_scheme(nxt_http_request_t *r, nxt_http_route_rule_t *rule); static nxt_int_t nxt_http_route_cookies(nxt_http_request_t *r, nxt_http_route_rule_t *rule); static nxt_array_t *nxt_http_route_cookies_parse(nxt_http_request_t *r); static nxt_int_t nxt_http_route_cookie_parse(nxt_array_t *cookies, u_char *start, u_char *end); static nxt_http_name_value_t *nxt_http_route_cookie(nxt_array_t *array, u_char *name, size_t name_length, u_char *start, u_char *end); static nxt_int_t nxt_http_route_test_cookie(nxt_http_request_t *r, nxt_http_route_rule_t *rule, nxt_array_t *array); static nxt_int_t nxt_http_route_test_rule(nxt_http_request_t *r, nxt_http_route_rule_t *rule, u_char *start, size_t length); static nxt_int_t nxt_http_route_pattern(nxt_http_request_t *r, nxt_http_route_pattern_t *pattern, u_char *start, size_t length); static nxt_int_t nxt_http_route_memcmp(u_char *start, u_char *test, size_t length, nxt_bool_t case_sensitive); nxt_http_routes_t * nxt_http_routes_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *routes_conf) { size_t size; uint32_t i, n, next; nxt_mp_t *mp; nxt_str_t name, *string; nxt_bool_t object; nxt_conf_value_t *route_conf; nxt_http_route_t *route; nxt_http_routes_t *routes; object = (nxt_conf_type(routes_conf) == NXT_CONF_OBJECT); n = object ? nxt_conf_object_members_count(routes_conf) : 1; size = sizeof(nxt_http_routes_t) + n * sizeof(nxt_http_route_t *); mp = tmcf->router_conf->mem_pool; routes = nxt_mp_alloc(mp, size); if (nxt_slow_path(routes == NULL)) { return NULL; } routes->items = n; if (object) { next = 0; for (i = 0; i < n; i++) { route_conf = nxt_conf_next_object_member(routes_conf, &name, &next); route = nxt_http_route_create(task, tmcf, route_conf); if (nxt_slow_path(route == NULL)) { return NULL; } routes->route[i] = route; string = nxt_str_dup(mp, &route->name, &name); if (nxt_slow_path(string == NULL)) { return NULL; } } } else { route = nxt_http_route_create(task, tmcf, routes_conf); if (nxt_slow_path(route == NULL)) { return NULL; } routes->route[0] = route; route->name.length = 0; route->name.start = NULL; } return routes; } static nxt_conf_map_t nxt_http_route_match_conf[] = { { nxt_string("scheme"), NXT_CONF_MAP_PTR, offsetof(nxt_http_route_match_conf_t, scheme) }, { nxt_string("host"), NXT_CONF_MAP_PTR, offsetof(nxt_http_route_match_conf_t, host), }, { nxt_string("uri"), NXT_CONF_MAP_PTR, offsetof(nxt_http_route_match_conf_t, uri), }, { nxt_string("method"), NXT_CONF_MAP_PTR, offsetof(nxt_http_route_match_conf_t, method), }, { nxt_string("headers"), NXT_CONF_MAP_PTR, offsetof(nxt_http_route_match_conf_t, headers), }, { nxt_string("arguments"), NXT_CONF_MAP_PTR, offsetof(nxt_http_route_match_conf_t, arguments), }, { nxt_string("cookies"), NXT_CONF_MAP_PTR, offsetof(nxt_http_route_match_conf_t, cookies), }, }; static nxt_http_route_t * nxt_http_route_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv) { size_t size; uint32_t i, n; nxt_conf_value_t *value; nxt_http_route_t *route; nxt_http_route_match_t *match, **m; n = nxt_conf_array_elements_count(cv); size = sizeof(nxt_http_route_t) + n * sizeof(nxt_http_route_match_t *); route = nxt_mp_alloc(tmcf->router_conf->mem_pool, size); if (nxt_slow_path(route == NULL)) { return NULL; } route->items = n; m = &route->match[0]; for (i = 0; i < n; i++) { value = nxt_conf_get_array_element(cv, i); match = nxt_http_route_match_create(task, tmcf, value); if (match == NULL) { return NULL; } *m++ = match; } return route; } static nxt_http_route_match_t * nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv) { size_t size; uint32_t n; nxt_mp_t *mp; nxt_int_t ret; nxt_str_t pass, *string; nxt_conf_value_t *match_conf, *pass_conf; nxt_http_route_test_t *test; nxt_http_route_rule_t *rule; nxt_http_route_table_t *table; nxt_http_route_match_t *match; nxt_http_route_match_conf_t mtcf; static nxt_str_t pass_path = nxt_string("/action/pass"); static nxt_str_t share_path = nxt_string("/action/share"); static nxt_str_t match_path = nxt_string("/match"); match_conf = nxt_conf_get_path(cv, &match_path); n = (match_conf != NULL) ? nxt_conf_object_members_count(match_conf) : 0; size = sizeof(nxt_http_route_match_t) + n * sizeof(nxt_http_route_rule_t *); mp = tmcf->router_conf->mem_pool; match = nxt_mp_alloc(mp, size); if (nxt_slow_path(match == NULL)) { return NULL; } match->pass.u.route = NULL; match->pass.handler = NULL; match->items = n; pass_conf = nxt_conf_get_path(cv, &pass_path); if (pass_conf == NULL) { pass_conf = nxt_conf_get_path(cv, &share_path); if (nxt_slow_path(pass_conf == NULL)) { return NULL; } match->pass.handler = nxt_http_static_handler; } nxt_conf_get_string(pass_conf, &pass); string = nxt_str_dup(mp, &match->pass.name, &pass); if (nxt_slow_path(string == NULL)) { return NULL; } if (n == 0) { return match; } nxt_memzero(&mtcf, sizeof(mtcf)); ret = nxt_conf_map_object(tmcf->mem_pool, match_conf, nxt_http_route_match_conf, nxt_nitems(nxt_http_route_match_conf), &mtcf); if (ret != NXT_OK) { return NULL; } test = &match->test[0]; if (mtcf.scheme != NULL) { rule = nxt_http_route_rule_create(task, mp, mtcf.scheme, 1, NXT_HTTP_ROUTE_PATTERN_NOCASE); if (rule == NULL) { return NULL; } rule->object = NXT_HTTP_ROUTE_SCHEME; test->rule = rule; test++; } if (mtcf.host != NULL) { rule = nxt_http_route_rule_create(task, mp, mtcf.host, 1, NXT_HTTP_ROUTE_PATTERN_LOWCASE); if (rule == NULL) { return NULL; } rule->u.offset = offsetof(nxt_http_request_t, host); rule->object = NXT_HTTP_ROUTE_STRING; test->rule = rule; test++; } if (mtcf.uri != NULL) { rule = nxt_http_route_rule_create(task, mp, mtcf.uri, 1, NXT_HTTP_ROUTE_PATTERN_NOCASE); if (rule == NULL) { return NULL; } rule->u.offset = offsetof(nxt_http_request_t, path); rule->object = NXT_HTTP_ROUTE_STRING_PTR; test->rule = rule; test++; } if (mtcf.method != NULL) { rule = nxt_http_route_rule_create(task, mp, mtcf.method, 1, NXT_HTTP_ROUTE_PATTERN_UPCASE); if (rule == NULL) { return NULL; } rule->u.offset = offsetof(nxt_http_request_t, method); rule->object = NXT_HTTP_ROUTE_STRING_PTR; test->rule = rule; test++; } if (mtcf.headers != NULL) { table = nxt_http_route_table_create(task, mp, mtcf.headers, NXT_HTTP_ROUTE_HEADER, 0); if (table == NULL) { return NULL; } test->table = table; test++; } if (mtcf.arguments != NULL) { table = nxt_http_route_table_create(task, mp, mtcf.arguments, NXT_HTTP_ROUTE_ARGUMENT, 1); if (table == NULL) { return NULL; } test->table = table; test++; } if (mtcf.cookies != NULL) { table = nxt_http_route_table_create(task, mp, mtcf.cookies, NXT_HTTP_ROUTE_COOKIE, 1); if (table == NULL) { return NULL; } test->table = table; test++; } return match; } static nxt_http_route_table_t * nxt_http_route_table_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *table_cv, nxt_http_route_object_t object, nxt_bool_t case_sensitive) { size_t size; uint32_t i, n; nxt_bool_t array; nxt_conf_value_t *ruleset_cv; nxt_http_route_table_t *table; nxt_http_route_ruleset_t *ruleset; array = (nxt_conf_type(table_cv) == NXT_CONF_ARRAY); n = array ? nxt_conf_array_elements_count(table_cv) : 1; size = sizeof(nxt_http_route_table_t) + n * sizeof(nxt_http_route_ruleset_t *); table = nxt_mp_alloc(mp, size); if (nxt_slow_path(table == NULL)) { return NULL; } table->items = n; table->object = NXT_HTTP_ROUTE_TABLE; if (!array) { ruleset = nxt_http_route_ruleset_create(task, mp, table_cv, object, case_sensitive); if (nxt_slow_path(ruleset == NULL)) { return NULL; } table->ruleset[0] = ruleset; return table; } for (i = 0; i < n; i++) { ruleset_cv = nxt_conf_get_array_element(table_cv, i); ruleset = nxt_http_route_ruleset_create(task, mp, ruleset_cv, object, case_sensitive); if (nxt_slow_path(ruleset == NULL)) { return NULL; } table->ruleset[i] = ruleset; } return table; } static nxt_http_route_ruleset_t * nxt_http_route_ruleset_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *ruleset_cv, nxt_http_route_object_t object, nxt_bool_t case_sensitive) { size_t size; uint32_t i, n, next; nxt_str_t name; nxt_conf_value_t *rule_cv; nxt_http_route_rule_t *rule; nxt_http_route_ruleset_t *ruleset; n = nxt_conf_object_members_count(ruleset_cv); size = sizeof(nxt_http_route_ruleset_t) + n * sizeof(nxt_http_route_rule_t *); ruleset = nxt_mp_alloc(mp, size); if (nxt_slow_path(ruleset == NULL)) { return NULL; } ruleset->items = n; next = 0; for (i = 0; i < n; i++) { rule_cv = nxt_conf_next_object_member(ruleset_cv, &name, &next); rule = nxt_http_route_rule_name_create(task, mp, rule_cv, &name, case_sensitive); if (nxt_slow_path(rule == NULL)) { return NULL; } rule->object = object; ruleset->rule[i] = rule; } return ruleset; } static nxt_http_route_rule_t * nxt_http_route_rule_name_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *rule_cv, nxt_str_t *name, nxt_bool_t case_sensitive) { u_char c, *p; uint32_t hash; nxt_uint_t i; nxt_http_route_rule_t *rule; rule = nxt_http_route_rule_create(task, mp, rule_cv, case_sensitive, NXT_HTTP_ROUTE_PATTERN_NOCASE); if (nxt_slow_path(rule == NULL)) { return NULL; } rule->u.name.length = name->length; p = nxt_mp_nget(mp, name->length); if (nxt_slow_path(p == NULL)) { return NULL; } rule->u.name.start = p; hash = NXT_HTTP_FIELD_HASH_INIT; for (i = 0; i < name->length; i++) { c = name->start[i]; *p++ = c; c = case_sensitive ? c : nxt_lowcase(c); hash = nxt_http_field_hash_char(hash, c); } rule->u.name.hash = nxt_http_field_hash_end(hash) & 0xFFFF; return rule; } static nxt_http_route_rule_t * nxt_http_route_rule_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *cv, nxt_bool_t case_sensitive, nxt_http_route_pattern_case_t pattern_case) { size_t size; uint32_t i, n; nxt_int_t ret; nxt_bool_t string; nxt_conf_value_t *value; nxt_http_route_rule_t *rule; nxt_http_route_pattern_t *pattern; string = (nxt_conf_type(cv) != NXT_CONF_ARRAY); n = string ? 1 : nxt_conf_array_elements_count(cv); size = sizeof(nxt_http_route_rule_t) + n * sizeof(nxt_http_route_pattern_t); rule = nxt_mp_alloc(mp, size); if (nxt_slow_path(rule == NULL)) { return NULL; } rule->items = n; pattern = &rule->pattern[0]; if (string) { pattern[0].case_sensitive = case_sensitive; ret = nxt_http_route_pattern_create(task, mp, cv, &pattern[0], pattern_case); if (nxt_slow_path(ret != NXT_OK)) { return NULL; } return rule; } nxt_conf_array_qsort(cv, nxt_http_pattern_compare); for (i = 0; i < n; i++) { pattern[i].case_sensitive = case_sensitive; value = nxt_conf_get_array_element(cv, i); ret = nxt_http_route_pattern_create(task, mp, value, &pattern[i], pattern_case); if (nxt_slow_path(ret != NXT_OK)) { return NULL; } } return rule; } static int nxt_http_pattern_compare(const void *one, const void *two) { nxt_str_t test; nxt_bool_t negative1, negative2; nxt_conf_value_t *value; value = (nxt_conf_value_t *) one; nxt_conf_get_string(value, &test); negative1 = (test.length != 0 && test.start[0] == '!'); value = (nxt_conf_value_t *) two; nxt_conf_get_string(value, &test); negative2 = (test.length != 0 && test.start[0] == '!'); return (negative2 - negative1); } static nxt_int_t nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *cv, nxt_http_route_pattern_t *pattern, nxt_http_route_pattern_case_t pattern_case) { u_char *start; nxt_str_t test; nxt_uint_t n, length; nxt_http_route_pattern_type_t type; /* Suppress warning about uninitialized variable. */ length = 0; type = NXT_HTTP_ROUTE_PATTERN_EXACT; nxt_conf_get_string(cv, &test); pattern->negative = 0; pattern->any = 1; if (test.length != 0) { if (test.start[0] == '!') { test.start++; test.length--; pattern->negative = 1; pattern->any = 0; } if (test.length != 0) { if (test.start[0] == '*') { test.start++; test.length--; if (test.length != 0) { if (test.start[test.length - 1] == '*') { test.length--; type = NXT_HTTP_ROUTE_PATTERN_SUBSTRING; } else { type = NXT_HTTP_ROUTE_PATTERN_END; } } else { type = NXT_HTTP_ROUTE_PATTERN_BEGIN; } } else if (test.start[test.length - 1] == '*') { test.length--; type = NXT_HTTP_ROUTE_PATTERN_BEGIN; } else { length = test.length - 1; for (n = 1; n < length; n++) { if (test.start[n] == '*') { test.length = n; type = NXT_HTTP_ROUTE_PATTERN_MIDDLE; break; } } } } } pattern->type = type; pattern->min_length = test.length; pattern->length1 = test.length; start = nxt_http_route_pattern_copy(mp, &test, pattern_case); if (nxt_slow_path(start == NULL)) { return NXT_ERROR; } pattern->start1 = start; if (type == NXT_HTTP_ROUTE_PATTERN_MIDDLE) { length -= test.length; pattern->length2 = length; pattern->min_length += length; test.start = &test.start[test.length + 1]; test.length = length; start = nxt_http_route_pattern_copy(mp, &test, pattern_case); if (nxt_slow_path(start == NULL)) { return NXT_ERROR; } pattern->start2 = start; } return NXT_OK; } static u_char * nxt_http_route_pattern_copy(nxt_mp_t *mp, nxt_str_t *test, nxt_http_route_pattern_case_t pattern_case) { u_char *start; start = nxt_mp_nget(mp, test->length); if (nxt_slow_path(start == NULL)) { return start; } switch (pattern_case) { case NXT_HTTP_ROUTE_PATTERN_UPCASE: nxt_memcpy_upcase(start, test->start, test->length); break; case NXT_HTTP_ROUTE_PATTERN_LOWCASE: nxt_memcpy_lowcase(start, test->start, test->length); break; case NXT_HTTP_ROUTE_PATTERN_NOCASE: nxt_memcpy(start, test->start, test->length); break; } return start; } void nxt_http_routes_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf) { nxt_http_route_t **route, **end; nxt_http_routes_t *routes; routes = tmcf->router_conf->routes; if (routes != NULL) { route = &routes->route[0]; end = route + routes->items; while (route < end) { nxt_http_route_resolve(task, tmcf, *route); route++; } } } static void nxt_http_route_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_http_route_t *route) { nxt_http_pass_t *pass; nxt_http_route_match_t **match, **end; match = &route->match[0]; end = match + route->items; while (match < end) { pass = &(*match)->pass; if (pass->handler == NULL) { nxt_http_pass_resolve(task, tmcf, &(*match)->pass); } match++; } } static void nxt_http_pass_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_http_pass_t *pass) { nxt_str_t name; name = pass->name; if (nxt_str_start(&name, "applications/", 13)) { name.length -= 13; name.start += 13; pass->u.application = nxt_router_listener_application(tmcf, &name); nxt_router_app_use(task, pass->u.application, 1); pass->handler = nxt_http_request_application; } else if (nxt_str_start(&name, "routes", 6)) { if (name.length == 6) { name.length = 0; name.start = NULL; } else if (name.start[6] == '/') { name.length -= 7; name.start += 7; } pass->u.route = nxt_http_route_find(tmcf->router_conf->routes, &name); pass->handler = nxt_http_route_pass; } } static nxt_http_route_t * nxt_http_route_find(nxt_http_routes_t *routes, nxt_str_t *name) { nxt_http_route_t **route, **end; route = &routes->route[0]; end = route + routes->items; while (route < end) { if (nxt_strstr_eq(&(*route)->name, name)) { return *route; } route++; } return NULL; } nxt_http_pass_t * nxt_http_pass_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_str_t *name) { nxt_http_pass_t *pass; pass = nxt_mp_alloc(tmcf->router_conf->mem_pool, sizeof(nxt_http_pass_t)); if (nxt_slow_path(pass == NULL)) { return NULL; } pass->name = *name; nxt_http_pass_resolve(task, tmcf, pass); return pass; } /* COMPATIBILITY: listener application. */ nxt_http_pass_t * nxt_http_pass_application(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_str_t *name) { nxt_http_pass_t *pass; pass = nxt_mp_alloc(tmcf->router_conf->mem_pool, sizeof(nxt_http_pass_t)); if (nxt_slow_path(pass == NULL)) { return NULL; } pass->name = *name; pass->u.application = nxt_router_listener_application(tmcf, name); nxt_router_app_use(task, pass->u.application, 1); pass->handler = nxt_http_request_application; return pass; } void nxt_http_routes_cleanup(nxt_task_t *task, nxt_http_routes_t *routes) { nxt_http_route_t **route, **end; if (routes != NULL) { route = &routes->route[0]; end = route + routes->items; while (route < end) { nxt_http_route_cleanup(task, *route); route++; } } } static void nxt_http_route_cleanup(nxt_task_t *task, nxt_http_route_t *route) { nxt_http_route_match_t **match, **end; match = &route->match[0]; end = match + route->items; while (match < end) { nxt_http_pass_cleanup(task, &(*match)->pass); match++; } } void nxt_http_pass_cleanup(nxt_task_t *task, nxt_http_pass_t *pass) { if (pass->handler == nxt_http_request_application) { nxt_router_app_use(task, pass->u.application, -1); } } static nxt_http_pass_t * nxt_http_route_pass(nxt_task_t *task, nxt_http_request_t *r, nxt_http_pass_t *start) { nxt_http_pass_t *pass; nxt_http_route_t *route; nxt_http_route_match_t **match, **end; route = start->u.route; match = &route->match[0]; end = match + route->items; while (match < end) { pass = nxt_http_route_match(r, *match); if (pass != NULL) { return pass; } match++; } nxt_http_request_error(task, r, NXT_HTTP_NOT_FOUND); return NULL; } static nxt_http_pass_t * nxt_http_route_match(nxt_http_request_t *r, nxt_http_route_match_t *match) { nxt_int_t ret; nxt_http_route_test_t *test, *end; test = &match->test[0]; end = test + match->items; while (test < end) { if (test->rule->object != NXT_HTTP_ROUTE_TABLE) { ret = nxt_http_route_rule(r, test->rule); } else { ret = nxt_http_route_table(r, test->table); } if (ret <= 0) { /* 0 => NULL, -1 => NXT_HTTP_PASS_ERROR. */ return (nxt_http_pass_t *) (intptr_t) ret; } test++; } return &match->pass; } static nxt_int_t nxt_http_route_table(nxt_http_request_t *r, nxt_http_route_table_t *table) { nxt_int_t ret; nxt_http_route_ruleset_t **ruleset, **end; ret = 1; ruleset = &table->ruleset[0]; end = ruleset + table->items; while (ruleset < end) { ret = nxt_http_route_ruleset(r, *ruleset); if (ret != 0) { return ret; } ruleset++; } return ret; } static nxt_int_t nxt_http_route_ruleset(nxt_http_request_t *r, nxt_http_route_ruleset_t *ruleset) { nxt_int_t ret; nxt_http_route_rule_t **rule, **end; rule = &ruleset->rule[0]; end = rule + ruleset->items; while (rule < end) { ret = nxt_http_route_rule(r, *rule); if (ret <= 0) { return ret; } rule++; } return 1; } static nxt_int_t nxt_http_route_rule(nxt_http_request_t *r, nxt_http_route_rule_t *rule) { void *p, **pp; u_char *start; size_t length; nxt_str_t *s; switch (rule->object) { case NXT_HTTP_ROUTE_HEADER: return nxt_http_route_header(r, rule); case NXT_HTTP_ROUTE_ARGUMENT: return nxt_http_route_arguments(r, rule); case NXT_HTTP_ROUTE_COOKIE: return nxt_http_route_cookies(r, rule); case NXT_HTTP_ROUTE_SCHEME: return nxt_http_route_scheme(r, rule); default: break; } p = nxt_pointer_to(r, rule->u.offset); if (rule->object == NXT_HTTP_ROUTE_STRING) { s = p; } else { /* NXT_HTTP_ROUTE_STRING_PTR */ pp = p; s = *pp; if (s == NULL) { return 0; } } length = s->length; start = s->start; return nxt_http_route_test_rule(r, rule, start, length); } static nxt_int_t nxt_http_route_header(nxt_http_request_t *r, nxt_http_route_rule_t *rule) { nxt_int_t ret; nxt_http_field_t *f; ret = 0; nxt_list_each(f, r->fields) { if (rule->u.name.hash != f->hash || rule->u.name.length != f->name_length || nxt_strncasecmp(rule->u.name.start, f->name, f->name_length) != 0) { continue; } ret = nxt_http_route_test_rule(r, rule, f->value, f->value_length); if (ret == 0) { return ret; } } nxt_list_loop; return ret; } static nxt_int_t nxt_http_route_arguments(nxt_http_request_t *r, nxt_http_route_rule_t *rule) { nxt_array_t *arguments; if (r->args == NULL) { return 0; } arguments = nxt_http_route_arguments_parse(r); if (nxt_slow_path(arguments == NULL)) { return -1; } return nxt_http_route_test_argument(r, rule, arguments); } static nxt_array_t * nxt_http_route_arguments_parse(nxt_http_request_t *r) { size_t name_length; u_char c, *p, *start, *end, *name; uint32_t hash; nxt_bool_t valid; nxt_array_t *args; nxt_http_name_value_t *nv; if (r->arguments != NULL) { return r->arguments; } args = nxt_array_create(r->mem_pool, 2, sizeof(nxt_http_name_value_t)); if (nxt_slow_path(args == NULL)) { return NULL; } hash = NXT_HTTP_FIELD_HASH_INIT; valid = 1; name = NULL; name_length = 0; start = r->args->start; end = start + r->args->length; for (p = start; p < end; p++) { c = *p; if (c == '=') { name_length = p - start; name = start; start = p + 1; valid = (name_length != 0); } else if (c == '&') { if (valid) { nv = nxt_http_route_argument(args, name, name_length, hash, start, p); if (nxt_slow_path(nv == NULL)) { return NULL; } } hash = NXT_HTTP_FIELD_HASH_INIT; valid = 1; name = NULL; start = p + 1; } else if (name == NULL) { hash = nxt_http_field_hash_char(hash, c); } } if (valid) { nv = nxt_http_route_argument(args, name, name_length, hash, start, p); if (nxt_slow_path(nv == NULL)) { return NULL; } } r->arguments = args; return args; } static nxt_http_name_value_t * nxt_http_route_argument(nxt_array_t *array, u_char *name, size_t name_length, uint32_t hash, u_char *start, u_char *end) { size_t length; nxt_http_name_value_t *nv; nv = nxt_array_add(array); if (nxt_slow_path(nv == NULL)) { return NULL; } nv->hash = nxt_http_field_hash_end(hash) & 0xFFFF; length = end - start; if (name == NULL) { name_length = length; name = start; length = 0; } nv->name_length = name_length; nv->value_length = length; nv->name = name; nv->value = start; return nv; } static nxt_int_t nxt_http_route_test_argument(nxt_http_request_t *r, nxt_http_route_rule_t *rule, nxt_array_t *array) { nxt_bool_t ret; nxt_http_name_value_t *nv, *end; ret = 0; nv = array->elts; end = nv + array->nelts; while (nv < end) { if (rule->u.name.hash == nv->hash && rule->u.name.length == nv->name_length && nxt_memcmp(rule->u.name.start, nv->name, nv->name_length) == 0) { ret = nxt_http_route_test_rule(r, rule, nv->value, nv->value_length); if (ret == 0) { break; } } nv++; } return ret; } static nxt_int_t nxt_http_route_scheme(nxt_http_request_t *r, nxt_http_route_rule_t *rule) { nxt_bool_t tls, https; https = (rule->pattern[0].length1 == nxt_length("https")); tls = (r->tls != NULL); return (tls == https); } static nxt_int_t nxt_http_route_cookies(nxt_http_request_t *r, nxt_http_route_rule_t *rule) { nxt_array_t *cookies; cookies = nxt_http_route_cookies_parse(r); if (nxt_slow_path(cookies == NULL)) { return -1; } return nxt_http_route_test_cookie(r, rule, cookies); } static nxt_array_t * nxt_http_route_cookies_parse(nxt_http_request_t *r) { nxt_int_t ret; nxt_array_t *cookies; nxt_http_field_t *f; if (r->cookies != NULL) { return r->cookies; } cookies = nxt_array_create(r->mem_pool, 2, sizeof(nxt_http_name_value_t)); if (nxt_slow_path(cookies == NULL)) { return NULL; } nxt_list_each(f, r->fields) { if (f->hash != NJS_COOKIE_HASH || f->name_length != 6 || nxt_strncasecmp(f->name, (u_char *) "Cookie", 6) != 0) { continue; } ret = nxt_http_route_cookie_parse(cookies, f->value, f->value + f->value_length); if (ret != NXT_OK) { return NULL; } } nxt_list_loop; r->cookies = cookies; return cookies; } static nxt_int_t nxt_http_route_cookie_parse(nxt_array_t *cookies, u_char *start, u_char *end) { size_t name_length; u_char c, *p, *name; nxt_http_name_value_t *nv; name = NULL; name_length = 0; for (p = start; p < end; p++) { c = *p; if (c == '=') { while (start[0] == ' ') { start++; } name_length = p - start; if (name_length != 0) { name = start; } start = p + 1; } else if (c == ';') { if (name != NULL) { nv = nxt_http_route_cookie(cookies, name, name_length, start, p); if (nxt_slow_path(nv == NULL)) { return NXT_ERROR; } } name = NULL; start = p + 1; } } if (name != NULL) { nv = nxt_http_route_cookie(cookies, name, name_length, start, p); if (nxt_slow_path(nv == NULL)) { return NXT_ERROR; } } return NXT_OK; } static nxt_http_name_value_t * nxt_http_route_cookie(nxt_array_t *array, u_char *name, size_t name_length, u_char *start, u_char *end) { u_char c, *p; uint32_t hash; nxt_http_name_value_t *nv; nv = nxt_array_add(array); if (nxt_slow_path(nv == NULL)) { return NULL; } nv->name_length = name_length; nv->name = name; hash = NXT_HTTP_FIELD_HASH_INIT; for (p = name; p < name + name_length; p++) { c = *p; hash = nxt_http_field_hash_char(hash, c); } nv->hash = nxt_http_field_hash_end(hash) & 0xFFFF; while (start < end && end[-1] == ' ') { end--; } nv->value_length = end - start; nv->value = start; return nv; } static nxt_int_t nxt_http_route_test_cookie(nxt_http_request_t *r, nxt_http_route_rule_t *rule, nxt_array_t *array) { nxt_bool_t ret; nxt_http_name_value_t *nv, *end; ret = 0; nv = array->elts; end = nv + array->nelts; while (nv < end) { if (rule->u.name.hash == nv->hash && rule->u.name.length == nv->name_length && nxt_memcmp(rule->u.name.start, nv->name, nv->name_length) == 0) { ret = nxt_http_route_test_rule(r, rule, nv->value, nv->value_length); if (ret == 0) { break; } } nv++; } return ret; } static nxt_int_t nxt_http_route_test_rule(nxt_http_request_t *r, nxt_http_route_rule_t *rule, u_char *start, size_t length) { nxt_int_t ret; nxt_http_route_pattern_t *pattern, *end; ret = 1; pattern = &rule->pattern[0]; end = pattern + rule->items; while (pattern < end) { ret = nxt_http_route_pattern(r, pattern, start, length); /* nxt_http_route_pattern() returns either 1 or 0. */ ret ^= pattern->negative; if (pattern->any == ret) { return ret; } pattern++; } return ret; } static nxt_int_t nxt_http_route_pattern(nxt_http_request_t *r, nxt_http_route_pattern_t *pattern, u_char *start, size_t length) { u_char *p, *end, *test; size_t test_length; nxt_int_t ret; if (length < pattern->min_length) { return 0; } test = pattern->start1; test_length = pattern->length1; switch (pattern->type) { case NXT_HTTP_ROUTE_PATTERN_EXACT: if (length != test_length) { return 0; } break; case NXT_HTTP_ROUTE_PATTERN_BEGIN: break; case NXT_HTTP_ROUTE_PATTERN_MIDDLE: ret = nxt_http_route_memcmp(start, test, test_length, pattern->case_sensitive); if (!ret) { return ret; } test = pattern->start2; test_length = pattern->length2; /* Fall through. */ case NXT_HTTP_ROUTE_PATTERN_END: start += length - test_length; break; case NXT_HTTP_ROUTE_PATTERN_SUBSTRING: end = start + length; if (pattern->case_sensitive) { p = nxt_memstrn(start, end, (char *) test, test_length); } else { p = nxt_memcasestrn(start, end, (char *) test, test_length); } return (p != NULL); } return nxt_http_route_memcmp(start, test, test_length, pattern->case_sensitive); } static nxt_int_t nxt_http_route_memcmp(u_char *start, u_char *test, size_t test_length, nxt_bool_t case_sensitive) { nxt_int_t n; if (case_sensitive) { n = nxt_memcmp(start, test, test_length); } else { n = nxt_memcasecmp(start, test, test_length); } return (n == 0); }