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 + * + * 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 + +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, + "

Form results

'%s'
" + "", 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;