From b490079b4705e81a6538d80a0e5ad4f271d02659 Mon Sep 17 00:00:00 2001
From: Andy Green <andy@warmcat.com>
Date: Wed, 7 Mar 2018 19:57:34 +0800
Subject: [PATCH] vhost: add 404 handler url option

This allows you to set a 404 handler URL on a vhost.

The necessary user code looks like...

    info.error_document_404 = "/404.html";

... at vhost-creation time.

In the existing lws_return_http_status() api, if it sees
the vhost has an "error_document_404" path set and that
we are trying to report a 404, it changes the action
instead to a redirect to the error_document_404 path.

The redirect target is returned using 404 status code.

If the redirect target doesn't exist, then it falls back
to just reporting the simple canned 404.
---
 lib/context.c                                      |  5 +++++
 lib/header.c                                       | 14 ++++++++++++++
 lib/libwebsockets.h                                |  4 ++++
 lib/private-libwebsockets.h                        |  2 ++
 lib/server/lejp-conf.c                             |  6 ++++++
 lib/server/server.c                                |  8 ++++++++
 .../minimal-http-server/minimal-http-server.c      |  1 +
 7 files changed, 40 insertions(+)

diff --git a/lib/context.c b/lib/context.c
index 11a0694d..5054d8a5 100644
--- a/lib/context.c
+++ b/lib/context.c
@@ -583,6 +583,11 @@ lws_create_vhost(struct lws_context *context,
 	else
 		vh->name = info->vhost_name;
 
+	vh->error_document_404 = info->error_document_404;
+	if (info->error_document_404 &&
+	    info->error_document_404[0] == '/')
+		vh->error_document_404 = info->error_document_404 + 1;
+
 	if (info->options & LWS_SERVER_OPTION_ONLY_RAW)
 		lwsl_info("%s set to only support RAW\n", vh->name);
 
diff --git a/lib/header.c b/lib/header.c
index 564b0751..5022f26a 100644
--- a/lib/header.c
+++ b/lib/header.c
@@ -233,6 +233,20 @@ lws_return_http_status(struct lws *wsi, unsigned int code,
 	int n = 0, m = 0, len;
 	char slen[20];
 
+	if (wsi->vhost &&
+	    !wsi->handling_404 &&
+	    wsi->vhost->error_document_404 &&
+	    code == HTTP_STATUS_NOT_FOUND)
+		/* we should do a redirect, and do the 404 there */
+		if (lws_http_redirect(wsi, HTTP_STATUS_FOUND,
+				       (uint8_t *)wsi->vhost->error_document_404,
+				       strlen(wsi->vhost->error_document_404),
+				       &p, end) > 0)
+			return 0;
+
+	/* if the redirect failed, just do a simple status */
+	p = start;
+
 	if (!html_body)
 		html_body = "";
 
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index f3fb4598..b62660d6 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -2850,6 +2850,10 @@ struct lws_context_creation_info {
 	 *	      platform default values.
 	 *	      Just leave all at 0 if you don't care.
 	 */
+	const char *error_document_404;
+	/**< VHOST: If non-NULL, when asked to serve a non-existent file,
+	 *          lws attempts to server this url path instead.  Eg,
+	 *          "/404.html" */
 
 	/* Add new things just above here ---^
 	 * This is part of the ABI, don't needlessly break compatibility
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index e4be958c..fcd08dc3 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -965,6 +965,7 @@ struct lws_vhost {
 	const struct lws_protocol_vhost_options *pvo;
 	const struct lws_protocol_vhost_options *headers;
 	struct lws **same_vh_protocol_list;
+	const char *error_document_404;
 #ifdef LWS_OPENSSL_SUPPORT
 	lws_tls_ctx *ssl_ctx;
 	lws_tls_ctx *ssl_client_ctx;
@@ -1951,6 +1952,7 @@ struct lws {
 	unsigned int rxflow_will_be_applied:1;
 	unsigned int event_pipe:1;
 	unsigned int on_same_vh_list:1;
+	unsigned int handling_404;
 
 	unsigned int could_have_pending:1; /* detect back-to-back writes */
 
diff --git a/lib/server/lejp-conf.c b/lib/server/lejp-conf.c
index da21697f..488680ca 100644
--- a/lib/server/lejp-conf.c
+++ b/lib/server/lejp-conf.c
@@ -101,6 +101,7 @@ static const char * const paths_vhosts[] = {
 	"vhosts[].onlyraw",
 	"vhosts[].client-cert-required",
 	"vhosts[].ignore-missing-cert",
+	"vhosts[].error-document-404",
 };
 
 enum lejp_vhost_paths {
@@ -150,6 +151,7 @@ enum lejp_vhost_paths {
 	LEJPVP_FLAG_ONLYRAW,
 	LEJPVP_FLAG_CLIENT_CERT_REQUIRED,
 	LEJPVP_IGNORE_MISSING_CERT,
+	LEJPVP_ERROR_DOCUMENT_404,
 };
 
 static const char * const parser_errs[] = {
@@ -708,6 +710,10 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
 
 		return 0;
 
+	case LEJPVP_ERROR_DOCUMENT_404:
+		a->info->error_document_404 = a->p;
+		break;
+
 	case LEJPVP_SSL_OPTION_SET:
 		a->info->ssl_options_set |= atol(ctx->buf);
 		return 0;
diff --git a/lib/server/server.c b/lib/server/server.c
index 0e3e79af..1cf60ba1 100644
--- a/lib/server/server.c
+++ b/lib/server/server.c
@@ -386,6 +386,11 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin,
 #endif
 	int n;
 
+	wsi->handling_404 = 0;
+	if (wsi->vhost && wsi->vhost->error_document_404 &&
+	    !strcmp(uri, wsi->vhost->error_document_404))
+		wsi->handling_404 = 1;
+
 	lws_snprintf(path, sizeof(path) - 1, "%s/%s", origin, uri);
 
 #if !defined(_WIN32_WCE)
@@ -2736,6 +2741,9 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type,
 	const struct lws_plat_file_ops *fops;
 	const char *vpath;
 
+	if (wsi->handling_404)
+		n = HTTP_STATUS_NOT_FOUND;
+
 	/*
 	 * We either call the platform fops .open with first arg platform fops,
 	 * or we call fops_zip .open with first arg platform fops, and fops_zip
diff --git a/minimal-examples/minimal-http-server/minimal-http-server.c b/minimal-examples/minimal-http-server/minimal-http-server.c
index 0e2e29e7..0784b550 100644
--- a/minimal-examples/minimal-http-server/minimal-http-server.c
+++ b/minimal-examples/minimal-http-server/minimal-http-server.c
@@ -54,6 +54,7 @@ int main(int argc, char **argv)
 	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
 	info.port = 7681;
 	info.mounts = &mount;
+	info.error_document_404 = "/404.html";
 
 	lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_USER
 			/* | LLL_INFO */ /* | LLL_DEBUG */, NULL);
-- 
GitLab