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:
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);
}