kore

An easy to use, scalable and secure web application framework for writing web APIs in C.
Commits | Files | Refs | README | LICENSE | git clone https://git.kore.io/kore.git

commit fa2e8ef0b6e462af30869702db7976eb12b3c612
parent 6072828d8f78310b331c4fdcf50e5257b85ff801
Author: Joris Vink <joris@coders.se>
Date:   Fri,  7 Feb 2020 06:42:33 +0100

Add support for config based redirection.

Inside the domain contexts a 'redirect' rule will allow you to redirect
a request to another URI.

Ex:

Redirect all requests with a 301 to example.com

	redirect ^/.*$ 301 https://example.com

Using capture groups

	redirect ^/account/(.*)$ 301 https://example.com/account/$1

Adding the query string in the mix

	redirect ^/(.*)$ 301 https://example.com/$1?$qs

Diffstat:
conf/kore.conf.example | 24++++++++++++++++++++++--
include/kore/http.h | 10++++++++++
include/kore/kore.h | 4+++-
src/config.c | 33+++++++++++++++++++++++++++++++++
src/domain.c | 11++++++++++-
src/http.c | 115++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
src/module.c | 11+++--------
7 files changed, 174 insertions(+), 34 deletions(-)

diff --git a/conf/kore.conf.example b/conf/kore.conf.example @@ -10,8 +10,15 @@ #socket_backlog 5000 # Server configuration. -bind 127.0.0.1 443 -#bind_unix /var/run/kore.sock +server tls { + bind 127.0.0.1 443 + #bind_unix /var/run/kore.sock +} + +#server notls { +# bind 127.0.0.1 80 +# tls no +#} # The worker process root directory. If chrooting was not disabled # at startup the worker processes will chroot into this directory. @@ -276,6 +283,8 @@ authentication auth_example { # Example domain that responds to localhost. domain localhost { + attach tls + certfile cert/server.crt certkey cert/server.key accesslog /var/log/kore_access.log @@ -334,6 +343,17 @@ domain localhost { } } +# Example redirect 80->443. +#domain localhost { +# attach notls +# +# # specific redirect with a capture group and arguments +# redirect ^/account/(.*)$ 301 https://localhost/account/$1 +# +# # redirect the others back to root. +# redirect ^/.*$ 301 https://localhost +#} + #domain domain.com { # certfile cert/other/server.crt # certkey cert/other/server.key diff --git a/include/kore/http.h b/include/kore/http.h @@ -236,6 +236,13 @@ struct reqcall; struct kore_task; struct http_client; +struct http_redirect { + regex_t rctx; + int status; + char *target; + TAILQ_ENTRY(http_redirect) list; +}; + struct http_request { u_int8_t method; u_int8_t fsm_state; @@ -345,6 +352,9 @@ int http_media_register(const char *, const char *); int http_check_timeout(struct connection *, u_int64_t); ssize_t http_body_read(struct http_request *, void *, size_t); int http_body_digest(struct http_request *, char *, size_t); + +int http_redirect_add(struct kore_domain *, + const char *, int, const char *); void http_response(struct http_request *, int, const void *, size_t); void http_response_fileref(struct http_request *, int, struct kore_fileref *); diff --git a/include/kore/kore.h b/include/kore/kore.h @@ -134,6 +134,7 @@ extern int daemon(int, int); /* XXX hackish. */ #if !defined(KORE_NO_HTTP) struct http_request; +struct http_redirect; #endif #define KORE_FILEREF_SOFT_REMOVED 0x1000 @@ -323,6 +324,7 @@ struct kore_domain { int x509_verify_depth; #if !defined(KORE_NO_HTTP) TAILQ_HEAD(, kore_module_handle) handlers; + TAILQ_HEAD(, http_redirect) redirects; #endif TAILQ_ENTRY(kore_domain) list; }; @@ -927,7 +929,7 @@ int kore_module_handler_new(struct kore_domain *, const char *, const char *, const char *, int); void kore_module_handler_free(struct kore_module_handle *); struct kore_module_handle *kore_module_handler_find(struct http_request *, - const char *, const char *); + struct kore_domain *); #endif struct kore_runtime_call *kore_runtime_getcall(const char *); diff --git a/src/config.c b/src/config.c @@ -110,6 +110,7 @@ static int configure_client_verify_depth(char *); #if !defined(KORE_NO_HTTP) static int configure_route(char *); static int configure_filemap(char *); +static int configure_redirect(char *); static int configure_static_handler(char *); static int configure_dynamic_handler(char *); static int configure_restrict(char *); @@ -189,6 +190,7 @@ static struct { #if !defined(KORE_NO_HTTP) { "route", configure_route}, { "filemap", configure_filemap }, + { "redirect", configure_redirect }, { "static", configure_static_handler }, { "dynamic", configure_dynamic_handler }, { "accesslog", configure_accesslog }, @@ -1027,6 +1029,37 @@ configure_route(char *options) } static int +configure_redirect(char *options) +{ + char *argv[4]; + int elm, status, err; + + if (current_domain == NULL) { + printf("redirect outside of domain context\n"); + return (KORE_RESULT_ERROR); + } + + elm = kore_split_string(options, " ", argv, 4); + if (elm != 3) { + printf("missing parameters for redirect\n"); + return (KORE_RESULT_ERROR); + } + + status = kore_strtonum(argv[1], 10, 300, 399, &err); + if (err != KORE_RESULT_OK) { + printf("invalid status code on redirect (%s)\n", argv[1]); + return (KORE_RESULT_ERROR); + } + + if (!http_redirect_add(current_domain, argv[0], status, argv[2])) { + printf("invalid regex on redirect path\n"); + return (KORE_RESULT_ERROR); + } + + return (KORE_RESULT_OK); +} + +static int configure_filemap(char *options) { char *argv[3]; diff --git a/src/domain.c b/src/domain.c @@ -188,6 +188,7 @@ kore_domain_new(const char *domain) #if !defined(KORE_NO_HTTP) TAILQ_INIT(&(dom->handlers)); + TAILQ_INIT(&(dom->redirects)); #endif if (dom->id < KORE_DOMAIN_CACHE) { @@ -225,7 +226,8 @@ void kore_domain_free(struct kore_domain *dom) { #if !defined(KORE_NO_HTTP) - struct kore_module_handle *hdlr; + struct http_redirect *rdr; + struct kore_module_handle *hdlr; #endif if (dom == NULL) return; @@ -255,6 +257,13 @@ kore_domain_free(struct kore_domain *dom) TAILQ_REMOVE(&(dom->handlers), hdlr, list); kore_module_handler_free(hdlr); } + + while ((rdr = TAILQ_FIRST(&(dom->redirects))) != NULL) { + TAILQ_REMOVE(&(dom->redirects), rdr, list); + regfree(&rdr->rctx); + kore_free(rdr->target); + kore_free(rdr); + } #endif kore_free(dom); } diff --git a/src/http.c b/src/http.c @@ -123,6 +123,8 @@ static void http_error_response(struct connection *, int); static void http_write_response_cookie(struct http_cookie *); static void http_argument_add(struct http_request *, char *, char *, int, int); +static int http_check_redirect(struct http_request *, + struct kore_domain *); static void http_response_normal(struct http_request *, struct connection *, int, const void *, size_t); static void multipart_add_field(struct http_request *, struct kore_buf *, @@ -1482,13 +1484,82 @@ http_runlock_release(struct http_runlock *lock, struct http_request *req) } } +int +http_redirect_add(struct kore_domain *dom, const char *path, int status, + const char *target) +{ + struct http_redirect *rdr; + + rdr = kore_calloc(1, sizeof(*rdr)); + + if (regcomp(&(rdr->rctx), path, REG_EXTENDED)) { + kore_free(rdr); + return (KORE_RESULT_ERROR); + } + + rdr->status = status; + rdr->target = kore_strdup(target); + + TAILQ_INSERT_TAIL(&dom->redirects, rdr, list); + + return (KORE_RESULT_OK); +} + +static int +http_check_redirect(struct http_request *req, struct kore_domain *dom) +{ + int idx; + struct http_redirect *rdr; + const char *uri; + char key[4]; + struct kore_buf location; + + TAILQ_FOREACH(rdr, &dom->redirects, list) { + if (!regexec(&(rdr->rctx), req->path, + HTTP_CAPTURE_GROUPS, req->cgroups, 0)) + break; + } + + if (rdr == NULL) + return (KORE_RESULT_ERROR); + + kore_buf_init(&location, 128); + kore_buf_appendf(&location, "%s", rdr->target); + + if (req->query_string != NULL) { + kore_buf_replace_string(&location, "$qs", + req->query_string, strlen(req->query_string)); + } + + /* Starts at 1 to skip the full path. */ + for (idx = 1; idx < HTTP_CAPTURE_GROUPS - 1; idx++) { + if (req->cgroups[idx].rm_so == -1 || + req->cgroups[idx].rm_eo == -1) + break; + + (void)snprintf(key, sizeof(key), "$%d", idx); + + kore_buf_replace_string(&location, key, + req->path + req->cgroups[idx].rm_so, + req->cgroups[idx].rm_eo - req->cgroups[idx].rm_so); + } + + uri = kore_buf_stringify(&location, NULL); + + http_response_header(req, "location", uri); + http_response(req, rdr->status, NULL, 0); + + kore_buf_cleanup(&location); + + return (KORE_RESULT_OK); +} + static struct http_request * http_request_new(struct connection *c, const char *host, const char *method, char *path, const char *version) { struct kore_domain *dom; struct http_request *req; - struct kore_module_handle *hdlr; char *p, *hp; int m, flags; size_t hostlen, pathlen, qsoff; @@ -1523,7 +1594,6 @@ http_request_new(struct connection *c, const char *host, } if ((p = strchr(path, '?')) != NULL) { - *p = '\0'; qsoff = p - path; } else { qsoff = 0; @@ -1568,21 +1638,9 @@ http_request_new(struct connection *c, const char *host, return (NULL); } - req = kore_pool_get(&http_request_pool); - req->owner = c; - - if ((hdlr = kore_module_handler_find(req, host, path)) == NULL) { - kore_pool_put(&http_request_pool, req); - http_error_response(c, 404); - return (NULL); - } - if (hp != NULL) *hp = ':'; - if (p != NULL) - *p = '?'; - if (!strcasecmp(method, "get")) { m = HTTP_METHOD_GET; flags |= HTTP_REQUEST_COMPLETE; @@ -1605,7 +1663,6 @@ http_request_new(struct connection *c, const char *host, m = HTTP_METHOD_PATCH; flags |= HTTP_REQUEST_EXPECT_BODY; } else { - kore_pool_put(&http_request_pool, req); http_error_response(c, 400); return (NULL); } @@ -1613,17 +1670,12 @@ http_request_new(struct connection *c, const char *host, if (flags & HTTP_VERSION_1_0) { if (m != HTTP_METHOD_GET && m != HTTP_METHOD_POST && m != HTTP_METHOD_HEAD) { - kore_pool_put(&http_request_pool, req); http_error_response(c, HTTP_STATUS_METHOD_NOT_ALLOWED); return (NULL); } } - if (!(hdlr->methods & m)) { - kore_pool_put(&http_request_pool, req); - http_error_response(c, HTTP_STATUS_METHOD_NOT_ALLOWED); - return (NULL); - } + req = kore_pool_get(&http_request_pool); req->end = 0; req->total = 0; @@ -1631,7 +1683,6 @@ http_request_new(struct connection *c, const char *host, req->owner = c; req->status = 0; req->method = m; - req->hdlr = hdlr; req->agent = NULL; req->onfree = NULL; req->referer = NULL; @@ -1663,6 +1714,9 @@ http_request_new(struct connection *c, const char *host, req->query_string = NULL; } + /* Checked further down below if we need to 404. */ + req->hdlr = kore_module_handler_find(req, dom); + TAILQ_INIT(&(req->resp_headers)); TAILQ_INIT(&(req->req_headers)); TAILQ_INIT(&(req->resp_cookies)); @@ -1682,6 +1736,23 @@ http_request_new(struct connection *c, const char *host, TAILQ_INSERT_HEAD(&http_requests, req, list); TAILQ_INSERT_TAIL(&(c->http_requests), req, olist); + if (http_check_redirect(req, dom)) { + http_request_free(req); + return (NULL); + } + + if (req->hdlr == NULL) { + http_request_free(req); + http_error_response(c, HTTP_STATUS_NOT_FOUND); + return (NULL); + } + + if (!(req->hdlr->methods & m)) { + http_request_free(req); + http_error_response(c, HTTP_STATUS_METHOD_NOT_ALLOWED); + return (NULL); + } + return (req); } diff --git a/src/module.c b/src/module.c @@ -284,24 +284,19 @@ kore_module_handler_free(struct kore_module_handle *hdlr) } struct kore_module_handle * -kore_module_handler_find(struct http_request *req, const char *domain, - const char *path) +kore_module_handler_find(struct http_request *req, struct kore_domain *dom) { struct connection *c; - struct kore_domain *dom; struct kore_module_handle *hdlr; c = req->owner; - if ((dom = kore_domain_lookup(c->owner->server, domain)) == NULL) - return (NULL); - TAILQ_FOREACH(hdlr, &(dom->handlers), list) { if (hdlr->type == HANDLER_TYPE_STATIC) { - if (!strcmp(hdlr->path, path)) + if (!strcmp(hdlr->path, req->path)) return (hdlr); } else { - if (!regexec(&(hdlr->rctx), path, + if (!regexec(&(hdlr->rctx), req->path, HTTP_CAPTURE_GROUPS, req->cgroups, 0)) return (hdlr); }