#include #include "lnm/common.h" #include "lnm/http/loop_internal.h" lnm_err lnm_http_router_init(lnm_http_router **out) { lnm_http_router *router = calloc(1, sizeof(lnm_http_router)); if (router == NULL) { return lnm_err_failed_alloc; } *out = router; return lnm_err_ok; } void lnm_http_router_free(lnm_http_router *router) { if (router == NULL) { return; } for (size_t i = 0; i < 128; i++) { lnm_http_router_free(router->exact_children[i]); } lnm_http_router_free(router->single_segment_child); for (size_t i = 0; i < lnm_http_method_total; i++) { lnm_http_route_free(router->routes[i]); lnm_http_route_free(router->multi_segment_routes[i]); } free(router); } static bool is_ascii(const char *s) { while (*s != '\0') { if (*s < 0) { return false; } s++; } return true; } lnm_err lnm_http_router_add(lnm_http_route **out, lnm_http_router *http_router, lnm_http_method method, const char *path) { if (path[0] != '/' || !is_ascii(path)) { return lnm_err_invalid_route; } lnm_http_route *route; LNM_RES(lnm_http_route_init(&route)); size_t key_segments_count = 0; lnm_err res = lnm_err_ok; while (*path != '\0') { unsigned char c = *path; switch (c) { case ':': { const char *next_slash_ptr = strchr(path + 1, '/'); const char *new_path = next_slash_ptr == NULL ? strchr(path + 1, '\0') : next_slash_ptr; size_t key_len = new_path - (path + 1); if (key_len == 0) { res = lnm_err_invalid_route; goto end; } res = lnm_http_route_key_segment_insert(route, path + 1, key_len, key_segments_count); if (res != lnm_err_ok) { goto end; } key_segments_count++; if (http_router->single_segment_child == NULL) { res = lnm_http_router_init(&http_router->single_segment_child); if (res != lnm_err_ok) { goto end; } } http_router = http_router->single_segment_child; path = new_path; } break; case '*': { const char *next_slash_ptr = strchr(path + 1, '/'); // Star match should be at end of route if (next_slash_ptr != NULL) { res = lnm_err_invalid_route; goto end; } const char *end = strchr(path + 1, '\0'); size_t key_len = end - (path + 1); if (key_len == 0) { res = lnm_err_invalid_route; goto end; } res = lnm_http_route_key_segment_insert(route, path + 1, key_len, key_segments_count); if (res != lnm_err_ok) { goto end; } key_segments_count++; if (http_router->multi_segment_routes[method] != NULL) { res = lnm_err_overlapping_route; } http_router->multi_segment_routes[method] = route; goto end; } break; default: if (http_router->exact_children[c] == NULL) { res = lnm_http_router_init(&http_router->exact_children[c]); if (res != lnm_err_ok) { goto end; } } http_router = http_router->exact_children[c]; path++; break; } } if (http_router->routes[method] != NULL) { res = lnm_err_overlapping_route; goto end; } http_router->routes[method] = route; http_router->represents_route = true; end: if (res != lnm_err_ok) { lnm_http_route_free(route); return res; } if (out != NULL) { *out = route; } return lnm_err_ok; } static lnm_http_route_err __lnm_http_router_route(lnm_http_route_match *out, const lnm_http_router *router, lnm_http_method method, const char *path, size_t path_index, size_t matched_key_segments) { if (path[path_index] == '\0') { if (!router->represents_route) { return lnm_http_route_err_unknown_route; } if (out != NULL) { out->route = router->routes[method]; } return router->routes[method] == NULL ? lnm_http_route_err_unknown_method : lnm_http_route_err_match; } lnm_http_route_err res = lnm_http_route_err_unknown_route; const lnm_http_router *exact_router = router->exact_children[(unsigned char)path[path_index]]; if (exact_router != NULL) { lnm_http_route_err sub_res = __lnm_http_router_route( out, exact_router, method, path, path_index + 1, matched_key_segments); if (sub_res == lnm_http_route_err_match) { return lnm_http_route_err_match; } res = LNM_MAX(res, sub_res); } const lnm_http_router *single_segment_router = router->single_segment_child; if (single_segment_router != NULL) { const char *next_slash_ptr = strchr(path + path_index, '/'); const char *new_path = next_slash_ptr == NULL ? strchr(path + path_index, '\0') : next_slash_ptr; size_t segment_len = new_path - (path + path_index); if (segment_len > 0) { lnm_http_route_err sub_res = __lnm_http_router_route( out, single_segment_router, method, path, path_index + segment_len, matched_key_segments + 1); if (sub_res == lnm_http_route_err_match) { if (out != NULL) { out->key_segments[matched_key_segments].start = path_index; out->key_segments[matched_key_segments].len = segment_len; } return lnm_http_route_err_match; } res = LNM_MAX(res, sub_res); } } const lnm_http_route *multi_segment_route = router->multi_segment_routes[method]; if (multi_segment_route != NULL) { const char *end_ptr = strchr(path + path_index, '\0'); size_t segment_len = end_ptr - (path + path_index); // TODO do 405's make sense for star matches? if (segment_len > 0) { if (out != NULL) { out->route = multi_segment_route; out->key_segments[matched_key_segments].start = path_index; out->key_segments[matched_key_segments].len = segment_len; } return lnm_http_route_err_match; } } return res; } lnm_http_route_err lnm_http_router_route(lnm_http_route_match *out, const lnm_http_router *router, lnm_http_method method, const char *path) { if (!is_ascii(path)) { return lnm_http_route_err_unknown_route; } return __lnm_http_router_route(out, router, method, path, 0, 0); } const lnm_http_route_match_segment * lnm_http_route_match_get(lnm_http_route_match *match, const char *key) { if (match->route->key_segments == NULL) { return NULL; } lnm_http_route_segment_trie *trie = match->route->key_segments; while (*key != '\0') { trie = trie->children[(unsigned char)*key]; if (trie == NULL) { return NULL; } key++; } if (!trie->represents_segment) { return NULL; } return &match->key_segments[trie->index]; } bool lnm_http_router_conflicts(const lnm_http_router *r1, const lnm_http_router *r2) { // First check the literal routes for the routers for (lnm_http_method m = 0; m < lnm_http_method_total; m++) { if ((r1->routes[m] != NULL && r2->routes[m] != NULL) || (r1->multi_segment_routes[m] != NULL && r2->multi_segment_routes[m] != NULL)) { return true; } } if ((r1->single_segment_child != NULL && r2->single_segment_child != NULL) && lnm_http_router_conflicts(r1->single_segment_child, r2->single_segment_child)) { return true; } for (unsigned char c = 0; c < 128; c++) { if ((r1->exact_children[c] != NULL && r2->exact_children[c] != NULL) && lnm_http_router_conflicts(r1->exact_children[c], r2->exact_children[c])) { return true; } } return false; } void __lnm_http_router_merge(lnm_http_router *r1, lnm_http_router *r2) { for (lnm_http_method m = 0; m < lnm_http_method_total; m++) { if (r2->routes[m] != NULL) { r1->routes[m] = r2->routes[m]; } if (r2->multi_segment_routes[m] != NULL) { r1->multi_segment_routes[m] = r2->multi_segment_routes[m]; } } if (r1->single_segment_child != NULL && r2->single_segment_child != NULL) { __lnm_http_router_merge(r1->single_segment_child, r2->single_segment_child); } else if (r2->single_segment_child != NULL) { r1->single_segment_child = r2->single_segment_child; } for (unsigned char c = 0; c < 128; c++) { if (r1->exact_children[c] != NULL && r2->exact_children[c] != NULL) { __lnm_http_router_merge(r1->exact_children[c], r2->exact_children[c]); } else if (r2->exact_children[c] != NULL) { r1->exact_children[c] = r2->exact_children[c]; } } r1->represents_route = r1->represents_route || r2->represents_route; free(r2); } lnm_err lnm_http_router_merge(lnm_http_router *r1, lnm_http_router *r2) { if (lnm_http_router_conflicts(r1, r2)) { return lnm_err_overlapping_route; } __lnm_http_router_merge(r1, r2); return lnm_err_ok; } lnm_err lnm_http_router_nest(lnm_http_router *parent, lnm_http_router *child, const char *prefix) { if (!is_ascii(prefix) || prefix[0] != '/') { return lnm_err_invalid_route; } lnm_http_router *router = parent; // The child router's routes are also mounted on '/', so we stop one earlier // as we need to merge it with the router representing the '/' while (*prefix != '\0') { unsigned char c = *prefix; if (router->exact_children[c] == NULL) { LNM_RES(lnm_http_router_init(&router->exact_children[c])); } router = router->exact_children[c]; prefix++; } return lnm_http_router_merge(router, child); }