From b24aaeb82261c480ab8e03baebda7aebedbc7d87 Mon Sep 17 00:00:00 2001
From: Andy Green <andy@warmcat.com>
Date: Mon, 9 May 2016 09:37:01 +0800
Subject: [PATCH] add protocol plugin for post demo

Signed-off-by: Andy Green <andy@warmcat.com>
---
 CMakeLists.txt                 |   2 +
 README.coding.md               |  53 ++++++++++++
 README.lwsws.md                |  20 ++++-
 lib/context.c                  |   1 +
 lib/libwebsockets.c            |   1 +
 lib/libwebsockets.h            |  13 +--
 lib/parsers.c                  |   2 +-
 lib/server.c                   |  72 +++++++++++++++--
 lwsws/conf.c                   |   1 +
 plugins/protocol_post_demo.c   | 144 +++++++++++++++++++++++++++++++++
 test-server/test-server-v2.0.c |  36 +++++++--
 11 files changed, 323 insertions(+), 22 deletions(-)
 create mode 100644 plugins/protocol_post_demo.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 803c49c9..cf1465bf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1216,6 +1216,8 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 			      "plugins/protocol_lws_mirror.c")
 		create_plugin(protocol_lws_status
 			      "plugins/protocol_lws_status.c")
+		create_plugin(protocol_post_demo
+			      "plugins/protocol_post_demo.c")
 if (LWS_WITH_SERVER_STATUS)
 		create_plugin(protocol_lws_server_status
 			      "plugins/protocol_lws_server_status.c")
diff --git a/README.coding.md b/README.coding.md
index 733508d7..1e3f171f 100644
--- a/README.coding.md
+++ b/README.coding.md
@@ -542,11 +542,64 @@ enum {
 	LWSMPRO_CGI,
 	LWSMPRO_REDIR_HTTP,
 	LWSMPRO_REDIR_HTTPS,
+	LWSMPRO_CALLBACK,
 };
 ```
 
 LWSMPRO_FILE is used for mapping url namespace to a filesystem directory and
 serve it automatically.
 
+LWSMPRO_CGI associates the url namespace with the given CGI executable, which
+runs when the URL is accessed and the output provided to the client.
 
+LWSMPRO_REDIR_HTTP and LWSMPRO_REDIR_HTTPS auto-redirect clients to the given
+origin URL.
 
+LWSMPRO_CALLBACK causes the http connection to attach to the callback
+associated with the named protocol (which may be a plugin).
+
+
+Operation of LWSMPRO_CALLBACK mounts
+------------------------------------
+
+The feature provided by CALLBACK type mounts is binding a part of the URL
+namespace to a named protocol callback handler.
+
+This allows protocol plugins to handle areas of the URL namespace.  For example
+in test-server-v2.0.c, the URL area "/formtest" is associated with the plugin
+providing "protocol-post-demo" like this
+
+```
+static const struct lws_http_mount mount_post = {
+	NULL,		/* linked-list pointer to next*/
+	"/formtest",		/* mountpoint in URL namespace on this vhost */
+	"protocol-post-demo",	/* handler */
+	NULL,	/* default filename if none given */
+	NULL,
+	0,
+	0,
+	0,
+	0,
+	0,
+	LWSMPRO_CALLBACK,	/* origin points to a callback */
+	9,			/* strlen("/formtest"), ie length of the mountpoint */
+};
+```
+
+Client access to /formtest[anything] will be passed to the callback registered
+with the named protocol, which in this case is provided by a protocol plugin.
+
+Access by all methods, eg, GET and POST are handled by the callback.
+
+protocol-post-demo deals with accepting and responding to the html form that
+is in the test server HTML.
+
+When a connection accesses a URL related to a CALLBACK type mount, the
+connection protocol is changed until the next access on the connection to a
+URL outside the same CALLBACK mount area.  User space on the connection is
+arranged to be the size of the new protocol user space allocation as given in
+the protocol struct.
+
+This allocation is only deleted / replaced when the connection accesses a
+URL region with a different protocol (or the default protocols[0] if no
+CALLBACK area matches it).
diff --git a/README.lwsws.md b/README.lwsws.md
index 9d6c82a3..219b6be8 100644
--- a/README.lwsws.md
+++ b/README.lwsws.md
@@ -256,7 +256,25 @@ Other mount options
 	"cgi-timeout": "30"
 ```
 
-3) Cache policy of the files in the mount can also be set.  If no
+3) `callback://` protocol may be used when defining a mount to associate a
+named protocol callback with the URL namespace area.  For example
+
+```
+       {
+        "mountpoint": "/formtest",
+        "origin": "callback://protocol-post-demo"
+       }
+```
+
+All handling of client access to /formtest[anything] will be passed to the
+callback registered to the protocol "protocol-post-demo".
+
+This is useful for handling POST http body content or general non-cgi http
+payload generation inside a plugin.
+
+See the related notes in README.coding.md
+
+4) Cache policy of the files in the mount can also be set.  If no
 options are given, the content is marked uncacheable.
 
        {
diff --git a/lib/context.c b/lib/context.c
index 83be24b6..73e986c9 100644
--- a/lib/context.c
+++ b/lib/context.c
@@ -47,6 +47,7 @@ static const char * const mount_protocols[] = {
 	"cgi://",
 	">http://",
 	">https://",
+	"callback://"
 };
 
 LWS_VISIBLE void *
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index b2046ba7..0d82eee1 100644
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -2368,6 +2368,7 @@ lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len)
 		"cgi://",
 		">http://",
 		">https://",
+		"callback://"
 	};
 	char *orig = buf, *end = buf + len - 1, first = 1;
 	int n = 0;
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index d0cdfb6b..a130e556 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -1600,12 +1600,13 @@ struct lws_client_connect_info {
 };
 
 enum {
-	LWSMPRO_HTTP,
-	LWSMPRO_HTTPS,
-	LWSMPRO_FILE,
-	LWSMPRO_CGI,
-	LWSMPRO_REDIR_HTTP,
-	LWSMPRO_REDIR_HTTPS,
+	LWSMPRO_HTTP		= 0,
+	LWSMPRO_HTTPS		= 1,
+	LWSMPRO_FILE		= 2,
+	LWSMPRO_CGI		= 3,
+	LWSMPRO_REDIR_HTTP	= 4,
+	LWSMPRO_REDIR_HTTPS	= 5,
+	LWSMPRO_CALLBACK	= 6,
 };
 
 LWS_EXTERN int
diff --git a/lib/parsers.c b/lib/parsers.c
index 8af5079d..a23f4c92 100644
--- a/lib/parsers.c
+++ b/lib/parsers.c
@@ -82,7 +82,7 @@ lws_header_table_reset(struct lws *wsi, int autoservice)
 	ah->rxlen = 0;
 
 	/* since we will restart the ah, our new headers are not completed */
-	wsi->hdr_parsing_completed = 0;
+	// wsi->hdr_parsing_completed = 0;
 
 	/*
 	 * if we inherited pending rx (from socket adoption deferred
diff --git a/lib/server.c b/lib/server.c
index 1343a243..4f0f3637 100644
--- a/lib/server.c
+++ b/lib/server.c
@@ -579,9 +579,10 @@ lws_http_action(struct lws *wsi)
 		     uri_ptr[hm->mountpoint_len] == '/' ||
 		     hm->mountpoint_len == 1)
 		    ) {
-			if ((hm->origin_protocol == LWSMPRO_CGI ||
+			if (hm->origin_protocol == LWSMPRO_CALLBACK ||
+			    ((hm->origin_protocol == LWSMPRO_CGI ||
 			     lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) &&
-			    hm->mountpoint_len > best) {
+			    hm->mountpoint_len > best)) {
 				best = hm->mountpoint_len;
 				hit = hm;
 			}
@@ -609,9 +610,13 @@ lws_http_action(struct lws *wsi)
 		 * / at the end, we must redirect to add it so the browser
 		 * understands he is one "directory level" down.
 		 */
-		if ((hit->mountpoint_len > 1 || (hit->origin_protocol & 4)) &&
-		    (*s != '/' || (hit->origin_protocol & 4)) &&
-		    (hit->origin_protocol != LWSMPRO_CGI)) {
+		if ((hit->mountpoint_len > 1 ||
+		     (hit->origin_protocol == LWSMPRO_REDIR_HTTP ||
+		      hit->origin_protocol == LWSMPRO_REDIR_HTTPS)) &&
+		    (*s != '/' ||
+		     (hit->origin_protocol == LWSMPRO_REDIR_HTTP ||
+		      hit->origin_protocol == LWSMPRO_REDIR_HTTPS)) &&
+		    (hit->origin_protocol != LWSMPRO_CGI && hit->origin_protocol != LWSMPRO_CALLBACK)) {
 			unsigned char *start = pt->serv_buf + LWS_PRE,
 					      *p = start, *end = p + 512;
 			static const char *oprot[] = {
@@ -642,6 +647,50 @@ lws_http_action(struct lws *wsi)
 			return lws_http_transaction_completed(wsi);
 		}
 
+		/*
+		 * A particular protocol callback is mounted here?
+		 *
+		 * For the duration of this http transaction, bind us to the
+		 * associated protocol
+		 */
+		if (hit->origin_protocol == LWSMPRO_CALLBACK) {
+
+			for (n = 0; n < wsi->vhost->count_protocols; n++)
+				if (!strcmp(wsi->vhost->protocols[n].name,
+					   hit->origin)) {
+
+					if (wsi->protocol != &wsi->vhost->protocols[n])
+						if (!wsi->user_space_externally_allocated)
+							lws_free_set_NULL(wsi->user_space);
+					wsi->protocol = &wsi->vhost->protocols[n];
+					if (lws_ensure_user_space(wsi)) {
+						lwsl_err("Unable to allocate user space\n");
+
+						return 1;
+					}
+					break;
+				}
+
+			if (n == wsi->vhost->count_protocols) {
+				n = -1;
+				lwsl_err("Unable to find plugin '%s'\n",
+					 hit->origin);
+			}
+
+			n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP,
+						    wsi->user_space, uri_ptr, uri_len);
+
+			goto after;
+		}
+
+		/* deferred cleanup and reset to protocols[0] */
+
+		if (wsi->protocol != &wsi->vhost->protocols[0])
+			if (!wsi->user_space_externally_allocated)
+				lws_free_set_NULL(wsi->user_space);
+
+		wsi->protocol = &wsi->vhost->protocols[0];
+
 #ifdef LWS_WITH_CGI
 		/* did we hit something with a cgi:// origin? */
 		if (hit->origin_protocol == LWSMPRO_CGI) {
@@ -699,10 +748,18 @@ lws_http_action(struct lws *wsi)
 			n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP,
 					    wsi->user_space, uri_ptr, uri_len);
 		}
-	} else
+	} else {
+		/* deferred cleanup and reset to protocols[0] */
+
+		if (wsi->protocol != &wsi->vhost->protocols[0])
+			if (!wsi->user_space_externally_allocated)
+				lws_free_set_NULL(wsi->user_space);
+		wsi->protocol = &wsi->vhost->protocols[0];
+
 		n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP,
 				    wsi->user_space, uri_ptr, uri_len);
-
+	}
+after:
 	if (n) {
 		lwsl_info("LWS_CALLBACK_HTTP closing\n");
 
@@ -1185,6 +1242,7 @@ lws_http_transaction_completed(struct lws *wsi)
 	/* otherwise set ourselves up ready to go again */
 	wsi->state = LWSS_HTTP;
 	wsi->mode = LWSCM_HTTP_SERVING;
+	/* reset of non [0] protocols (and freeing of user_space) is deferred */
 	wsi->u.http.content_length = 0;
 	wsi->hdr_parsing_completed = 0;
 #ifdef LWS_WITH_ACCESS_LOG
diff --git a/lwsws/conf.c b/lwsws/conf.c
index f222ed64..f09d9ada 100644
--- a/lwsws/conf.c
+++ b/lwsws/conf.c
@@ -277,6 +277,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
 			"cgi://",
 			">http://",
 			">https://",
+			"callback://"
 		};
 
 		if (!a->m.mountpoint || !a->m.origin) {
diff --git a/plugins/protocol_post_demo.c b/plugins/protocol_post_demo.c
new file mode 100644
index 00000000..af356f2a
--- /dev/null
+++ b/plugins/protocol_post_demo.c
@@ -0,0 +1,144 @@
+/*
+ * ws protocol handler plugin for "dumb increment"
+ *
+ * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The person who associated a work with this deed has dedicated
+ * the work to the public domain by waiving all of his or her rights
+ * to the work worldwide under copyright law, including all related
+ * and neighboring rights, to the extent allowed by law. You can copy,
+ * modify, distribute and perform the work, even for commercial purposes,
+ * all without asking permission.
+ *
+ * These test plugins are intended to be adapted for use in your code, which
+ * may be proprietary.  So unlike the library itself, they are licensed
+ * Public Domain.
+ */
+#include "../lib/libwebsockets.h"
+#include <string.h>
+
+struct per_session_data__post_demo {
+	char post_string[256];
+	char result[500 + LWS_PRE];
+	int result_len;
+};
+
+static int
+callback_post_demo(struct lws *wsi, enum lws_callback_reasons reason,
+		   void *user, void *in, size_t len)
+{
+	struct per_session_data__post_demo *pss =
+			(struct per_session_data__post_demo *)user;
+	unsigned char buffer[LWS_PRE + 512];
+	unsigned char *p, *start, *end;
+	int n;
+
+	switch (reason) {
+	case LWS_CALLBACK_HTTP:
+		lwsl_debug("LWS_CALLBACK_HTTP\n");
+		if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI))
+			return 0;
+		break;
+
+	case LWS_CALLBACK_HTTP_BODY:
+		lwsl_debug("LWS_CALLBACK_HTTP_BODY: len %d\n", (int)len);
+		strncpy(pss->post_string, in, sizeof (pss->post_string) -1);
+		pss->post_string[sizeof(pss->post_string) - 1] = '\0';
+
+		if (len < sizeof(pss->post_string) - 1)
+			pss->post_string[len] = '\0';
+		break;
+
+	case LWS_CALLBACK_HTTP_WRITEABLE:
+		lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n", pss->result_len);
+		n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE,
+			      pss->result_len, LWS_WRITE_HTTP);
+		if (n < 0)
+			return 1;
+		goto try_to_reuse;
+
+	case LWS_CALLBACK_HTTP_BODY_COMPLETION:
+		lwsl_debug("LWS_CALLBACK_HTTP_BODY_COMPLETION\n");
+		/*
+		 * the whole of the sent body arrived,
+		 * respond to the client with a redirect to show the
+		 * results
+		 */
+		pss->result_len = sprintf((char *)pss->result + LWS_PRE,
+			    "<html><body><h1>Form results</h1>'%s'<br>"
+			    "</body></html>", pss->post_string);
+
+		p = buffer + LWS_PRE;
+		start = p;
+		end = p + sizeof(buffer) - LWS_PRE;
+
+		if (lws_add_http_header_status(wsi, 200, &p, end))
+			return 1;
+
+		if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
+				(unsigned char *)"text/html", 9, &p, end))
+			return 1;
+		if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end))
+			return 1;
+		if (lws_finalize_http_header(wsi, &p, end))
+			return 1;
+
+		n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
+		if (n < 0)
+			return 1;
+
+		/*
+		 *  send the payload next time, in case would block after
+		 * headers
+		 */
+		lws_callback_on_writable(wsi);
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+
+try_to_reuse:
+	if (lws_http_transaction_completed(wsi))
+		return -1;
+
+	return 0;
+}
+
+static const struct lws_protocols protocols[] = {
+	{
+		"protocol-post-demo",
+		callback_post_demo,
+		sizeof(struct per_session_data__post_demo),
+		1024,
+	},
+};
+
+LWS_VISIBLE int
+init_protocol_post_demo(struct lws_context *context,
+			struct lws_plugin_capability *c)
+{
+	if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+		lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+			 c->api_magic);
+		return 1;
+	}
+
+	c->protocols = protocols;
+	c->count_protocols = ARRAY_SIZE(protocols);
+	c->extensions = NULL;
+	c->count_extensions = 0;
+
+	return 0;
+}
+
+LWS_VISIBLE int
+destroy_protocol_post_demo(struct lws_context *context)
+{
+	return 0;
+}
diff --git a/test-server/test-server-v2.0.c b/test-server/test-server-v2.0.c
index debe72ba..ae170783 100644
--- a/test-server/test-server-v2.0.c
+++ b/test-server/test-server-v2.0.c
@@ -69,6 +69,25 @@ static const struct lws_extension exts[] = {
 	{ NULL, NULL, NULL /* terminator */ }
 };
 
+/*
+ * mount a handler for a section of the URL space
+ */
+
+static const struct lws_http_mount mount_post = {
+	NULL,		/* linked-list pointer to next*/
+	"/formtest",		/* mountpoint in URL namespace on this vhost */
+	"protocol-post-demo",	/* handler */
+	NULL,	/* default filename if none given */
+	NULL,
+	0,
+	0,
+	0,
+	0,
+	0,
+	LWSMPRO_CALLBACK,	/* origin points to a callback */
+	9,			/* strlen("/formtest"), ie length of the mountpoint */
+};
+
 /*
  * mount a filesystem directory into the URL space at /
  * point it to our /usr/share directory with our assets in
@@ -76,7 +95,7 @@ static const struct lws_extension exts[] = {
  */
 
 static const struct lws_http_mount mount = {
-	NULL,		/* linked-list pointer to next, but we only have one */
+	(struct lws_http_mount *)&mount_post,		/* linked-list pointer to next*/
 	"/",		/* mountpoint in URL namespace on this vhost */
 	LOCAL_RESOURCE_PATH, /* where to go on the filesystem for that */
 	"test.html",	/* default filename if none given */
@@ -108,9 +127,16 @@ static const struct lws_protocol_vhost_options pvo_opt = {
  * linked-list.  We can also give the plugin per-vhost options here.
  */
 
-static const struct lws_protocol_vhost_options pvo_2 = {
+static const struct lws_protocol_vhost_options pvo_3 = {
 	NULL,
 	NULL,
+	"protocol-post-demo",
+	"" /* ignored, just matches the protocol name above */
+};
+
+static const struct lws_protocol_vhost_options pvo_2 = {
+	&pvo_3,
+	NULL,
 	"lws-status",
 	"" /* ignored, just matches the protocol name above */
 };
@@ -160,7 +186,6 @@ static const struct option options[] = {
 	{ "ssl-crl",  required_argument,		NULL, 'R' },
 #endif
 #endif
-	{ "libev",  no_argument,		NULL, 'e' },
 #ifndef LWS_NO_DAEMONIZE
 	{ "daemonize", 	no_argument,		NULL, 'D' },
 #endif
@@ -200,13 +225,10 @@ int main(int argc, char **argv)
 	info.port = 7681;
 
 	while (n >= 0) {
-		n = getopt_long(argc, argv, "ei:hsap:d:Dr:C:K:A:R:vu:g:", options, NULL);
+		n = getopt_long(argc, argv, "i:hsap:d:Dr:C:K:A:R:vu:g:", options, NULL);
 		if (n < 0)
 			continue;
 		switch (n) {
-		case 'e':
-			opts |= LWS_SERVER_OPTION_LIBEV;
-			break;
 #ifndef LWS_NO_DAEMONIZE
 		case 'D':
 			daemonize = 1;
-- 
GitLab