From fe43ed09aceedb380ae663cc31b0af7c1dfd924c Mon Sep 17 00:00:00 2001
From: Frederic Cambus <fred@statdns.com>
Date: Tue, 3 Mar 2020 12:53:18 +0100
Subject: [PATCH] Add the http_pretty_error configuration option.

When enabled, Kore returns HTML error pages for status codes 4xx and
5xx instead of empty content.
---
 include/kore/http.h |  1 +
 src/config.c        | 17 +++++++++++++++++
 src/http.c          | 37 ++++++++++++++++++++++++++++++++++++-
 3 files changed, 54 insertions(+), 1 deletion(-)

diff --git a/include/kore/http.h b/include/kore/http.h
index d31c51e4..20df6ca0 100644
--- a/include/kore/http.h
+++ b/include/kore/http.h
@@ -327,6 +327,7 @@ extern u_int16_t	http_keepalive_time;
 extern u_int32_t	http_request_limit;
 extern u_int32_t	http_request_count;
 extern u_int64_t	http_body_disk_offload;
+extern int		http_pretty_error;
 extern char		*http_body_disk_path;
 extern struct kore_pool	http_header_pool;
 
diff --git a/src/config.c b/src/config.c
index 3e36fd19..3593eec0 100644
--- a/src/config.c
+++ b/src/config.c
@@ -130,6 +130,7 @@ static int		configure_http_request_limit(char *);
 static int		configure_http_body_disk_offload(char *);
 static int		configure_http_body_disk_path(char *);
 static int		configure_http_server_version(char *);
+static int		configure_http_pretty_error(char *);
 static int		configure_validator(char *);
 static int		configure_params(char *);
 static int		configure_validate(char *);
@@ -258,6 +259,7 @@ static struct {
 	{ "http_body_disk_offload",	configure_http_body_disk_offload },
 	{ "http_body_disk_path",	configure_http_body_disk_path },
 	{ "http_server_version",	configure_http_server_version },
+	{ "http_pretty_error",		configure_http_pretty_error },
 	{ "websocket_maxframe",		configure_websocket_maxframe },
 	{ "websocket_timeout",		configure_websocket_timeout },
 #endif
@@ -1334,6 +1336,21 @@ configure_http_server_version(char *version)
 	return (KORE_RESULT_OK);
 }
 
+static int
+configure_http_pretty_error(char *yesno)
+{
+	if (!strcmp(yesno, "no")) {
+		http_pretty_error = 0;
+	} else if (!strcmp(yesno, "yes")) {
+		http_pretty_error = 1;
+	} else {
+		printf("invalid '%s' for yes|no http_pretty_error option\n", yesno);
+		return (KORE_RESULT_ERROR);
+	}
+
+	return (KORE_RESULT_OK);
+}
+
 static int
 configure_http_hsts_enable(char *option)
 {
diff --git a/src/http.c b/src/http.c
index 8e1b3282..9c321963 100644
--- a/src/http.c
+++ b/src/http.c
@@ -140,6 +140,7 @@ static int	multipart_parse_headers(struct http_request *,
 static struct http_request	*http_request_new(struct connection *,
 				    const char *, const char *, char *,
 				    const char *);
+static struct kore_buf		*http_pretty_error_page(int);
 
 static struct kore_buf			*header_buf;
 static struct kore_buf			*ckhdr_buf;
@@ -168,6 +169,8 @@ size_t		http_body_max = HTTP_BODY_MAX_LEN;
 char		*http_body_disk_path = HTTP_BODY_DISK_PATH;
 u_int64_t	http_body_disk_offload = HTTP_BODY_DISK_OFFLOAD;
 
+int		http_pretty_error = 0;
+
 void
 http_parent_init(void)
 {
@@ -2077,6 +2080,8 @@ http_response_normal(struct http_request *req, struct connection *c,
 	const char		*conn;
 	char			version;
 	int			connection_close;
+	int			send_body = 1;
+	u_int8_t		*error_page = NULL;
 
 	kore_buf_reset(header_buf);
 
@@ -2129,6 +2134,12 @@ http_response_normal(struct http_request *req, struct connection *c,
 		    http_hsts_enable);
 	}
 
+	if (http_pretty_error && d == NULL && status >= 400) {
+		struct kore_buf *page = http_pretty_error_page(status);
+		error_page = kore_buf_release(page, &len);
+		d = error_page;
+	}
+
 	if (req != NULL) {
 		TAILQ_FOREACH(ck, &(req->resp_cookies), list)
 			http_write_response_cookie(ck);
@@ -2153,7 +2164,10 @@ http_response_normal(struct http_request *req, struct connection *c,
 	kore_buf_append(header_buf, "\r\n", 2);
 	net_send_queue(c, header_buf->data, header_buf->offset);
 
-	if (d != NULL && req != NULL && req->method != HTTP_METHOD_HEAD)
+	if (req != NULL && req->method == HTTP_METHOD_HEAD)
+		send_body = 0;
+
+	if (d != NULL && send_body)
 		net_send_queue(c, d, len);
 
 	if (!(c->flags & CONN_CLOSE_EMPTY) && !(c->flags & CONN_IS_BUSY))
@@ -2161,6 +2175,9 @@ http_response_normal(struct http_request *req, struct connection *c,
 
 	if (req != NULL)
 		req->content_length = len;
+
+	if (error_page)
+		kore_free(error_page);
 }
 
 static void
@@ -2481,3 +2498,21 @@ http_validate_header(char *header)
 
 	return (value);
 }
+
+static struct kore_buf *
+http_pretty_error_page(int status)
+{
+	const char *error_template =
+	"<html>\n<head>\n\t<title>%d %s</title>"
+	"</head>\n<body>\n\t"
+	"<h1>%d %s</h1>\n"
+	"</body>\n</html>\n";
+
+	const char *status_text = http_status_text(status);
+
+	struct kore_buf *r = kore_buf_alloc(128);
+	kore_buf_appendf(r, error_template,
+	    status, status_text, status, status_text);
+
+	return r;
+}
