From: Andy Green Date: Mon, 28 Mar 2016 02:12:37 +0000 (+0800) Subject: lwsws Libwebsockets Web Server X-Git-Tag: upstream/2.0.3~155 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=cd0c696a0d879f66c8142ace0619b944efd08915;p=platform%2Fupstream%2Flibwebsockets.git lwsws Libwebsockets Web Server This makes a start on the LibWebSockets WebServer. The app cmake build support and JSON config parsing are implemented and the app can start, create the vhosts, listen and serve file:// mounts on them. Signed-off-by: Andy Green --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ea0944..acbf1ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,7 +89,7 @@ option(LWS_MBED3 "Platform is MBED3" OFF) option(LWS_SSL_SERVER_WITH_ECDH_CERT "Include SSL server use ECDH certificate" OFF) option(LWS_WITH_CGI "Include CGI (spawn process with network-connected stdin/out/err) APIs" OFF) option(LWS_WITH_HTTP_PROXY "Support for rewriting HTTP proxying" OFF) - +option(LWS_WITH_LWSWS "Libwebsockets Webserver" ON) if (DEFINED YOTTA_WEBSOCKETS_VERSION_STRING) @@ -110,6 +110,11 @@ if (WIN32) set(LWS_MAX_SMP 1) endif() + +if (LWS_WITHOUT_SERVER) +set(LWS_WITH_LWSWS OFF) +endif() + if (LWS_WITH_HTTP_PROXY AND (LWS_WITHOUT_CLIENT OR LWS_WITHOUT_SERVER)) message(FATAL_ERROR "You have to enable both client and server for http proxy") endif() @@ -1065,6 +1070,54 @@ if (NOT LWS_WITHOUT_TESTAPPS) endif() endif(NOT LWS_WITHOUT_TESTAPPS) +if (LWS_WITH_LWSWS) + list(APPEND LWSWS_SRCS + "lwsws/main.c" + "lwsws/lejp.c" + "lwsws/conf.c" + "lwsws/http.c" + ) + + if (WIN32) + list(APPEND LWSWS_SRCS + ${WIN32_HELPERS_PATH}/getopt.c + ${WIN32_HELPERS_PATH}/getopt_long.c + ${WIN32_HELPERS_PATH}/gettimeofday.c + ) + + list(APPEND LWSWS_HDR + ${WIN32_HELPERS_PATH}/getopt.h + ${WIN32_HELPERS_PATH}/gettimeofday.h + ) + endif(WIN32) + + source_group("Headers Private" FILES ${LWSWS_HDR}) + source_group("Sources" FILES ${LWSWS_SRCS}) + add_executable("lwsws" ${LWSWS_SRCS} ${LWSWS_HDR}) + + if (LWS_LINK_TESTAPPS_DYNAMIC) + if (NOT LWS_WITH_SHARED) + message(FATAL_ERROR "Build of the shared library is disabled. LWS_LINK_TESTAPPS_DYNAMIC must be combined with LWS_WITH_SHARED.") + endif() + target_link_libraries("lwsws" websockets_shared) + add_dependencies("lwsws" websockets_shared) + else() + if (NOT LWS_WITH_STATIC) + message(FATAL_ERROR "Build of the static library is disabled. Disabled LWS_LINK_TESTAPPS_DYNAMIC must be combined with LWS_WITH_STATIC.") + endif() + target_link_libraries("lwsws" websockets) + add_dependencies("lwsws" websockets) + endif() + + # Set test app specific defines. + set_property(TARGET "lwsws" + PROPERTY COMPILE_DEFINITIONS + INSTALL_DATADIR="${CMAKE_INSTALL_PREFIX}/share" + ) + + +endif (LWS_WITH_LWSWS) + if (UNIX) # Generate documentation. # TODO: Fix this on Windows. @@ -1177,6 +1230,12 @@ if (NOT LWS_WITHOUT_TESTAPPS AND NOT LWS_WITHOUT_CLIENT) set(CPACK_COMPONENT_EXAMPLES_DISPLAY_NAME "Example files") endif() +# lwsws +if (LWS_WITH_LWSWS) + install(TARGETS lwsws + RUNTIME DESTINATION "${LWS_INSTALL_BIN_DIR}" COMPONENT lwsws ) +endif() + # Programs shared files used by the test-server. if (NOT LWS_WITHOUT_TESTAPPS AND NOT LWS_WITHOUT_SERVER) install(FILES ${TEST_SERVER_DATA} diff --git a/LICENSE b/LICENSE index da87198..ef462df 100644 --- a/LICENSE +++ b/LICENSE @@ -40,6 +40,16 @@ Public Domain (CC-zero) to simplify reuse - test-server/*.c - test-server/*.h + +4) lwsws (Libwebsocket web server) is a bundled application that is not +part of the libwebsockets library, it's a separate application that uses +the library. The related sources are in a separate directory. If you don't +distribute lwsws, you do not need to observe its license. + + - lwsws/lejp.c - LGPL2.1 + - lwsws/lejp.h - LGPL2.1 + - lwsws/[all else] - GPL2.1 + GNU LESSER GENERAL PUBLIC LICENSE diff --git a/README.lwsws.md b/README.lwsws.md new file mode 100644 index 0000000..460e0e5 --- /dev/null +++ b/README.lwsws.md @@ -0,0 +1,110 @@ +Libwebsockets Web Server +------------------------ + +lwsws is an implementation of a very lightweight, ws-capable generic web +server, which uses libwebsockets to implement everything underneath. + +Configuration +------------- + +lwsws uses JSON config files, there is a single file intended for global +settings + +/etc/lwsws/conf + +``` +# these are the server global settings +# stuff related to vhosts should go in one +# file per vhost in ../conf.d/ + +{ + "global": { + "uid": "99", + "gid": "99", + "interface": "eth0", + "count-threads": "1", + "init-ssl": "yes" + } +} +``` + +and a config directory intended to take one file per vhost + +/etc/lwsws/conf.d/warmcat.com + +``` +{ + "vhosts": [{ + "name": "warmcat.com", + "port": "443", + "host-ssl-key": "/etc/pki/tls/private/warmcat.com.key", + "host-ssl-cert": "/etc/pki/tls/certs/warmcat.com.crt", + "host-ssl-ca": "/etc/pki/tls/certs/warmcat.com.cer", + "mounts": [{ + "mountpoint": "/", + "origin": "file:///var/www/warmcat.com", + "default": "index.html" + }] + }] +} +``` + +Vhosts +------ + +One server can run many vhosts, where SSL is in use SNI is used to match +the connection to a vhost and its vhost-specific SSL keys during SSL +negotiation. + +Listing multiple vhosts looks something like this + +``` +{ + "vhosts": [{ + "name": "warmcat.com", + "port": "443", + "host-ssl-key": "/etc/pki/tls/private/warmcat.com.key", + "host-ssl-cert": "/etc/pki/tls/certs/warmcat.com.crt", + "host-ssl-ca": "/etc/pki/tls/certs/warmcat.com.cer", + "mounts": [{ + "mountpoint": "/", + "origin": "file:///var/www/warmcat.com", + "default": "index.html" + }] + }, { + "name": "warmcat2.com", + "port": "443", + "host-ssl-key": "/etc/pki/tls/private/warmcat.com.key", + "host-ssl-cert": "/etc/pki/tls/certs/warmcat.com.crt", + "host-ssl-ca": "/etc/pki/tls/certs/warmcat.com.cer", + "mounts": [{ + "mountpoint": "/", + "origin": "file:///var/www/warmcat2.com", + "default": "index.html" + }] + } +] +} +``` + +Vhost name and port +------------------- + +The vhost name field is used to match on incoming SNI or Host: header, so it +must always be the host name used to reach the vhost externally. + +Vhosts may have the same name and different ports, these will each create a +listening socket on the appropriate port, and they may have the same port and +different name: these will be treated as true vhosts on one listening socket +and the active vhost decided at SSL negotiation time (via SNI) or if no SSL, +then after the Host: header from the client has been parsed. + + +Mounts +------ + +Where mounts are given in the vhost definition, then directory contents may +be auto-served if it matches the mountpoint. + +Currently only file:// mount protocol and a fixed set of mimetypes are +supported. \ No newline at end of file diff --git a/changelog b/changelog index 9c598a1..3142d55 100644 --- a/changelog +++ b/changelog @@ -185,6 +185,49 @@ LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS. If you give this, non-ssl connections to the server listen port are accepted and receive a 301 redirect to / on the same host and port using https:// +New application lwsws +--------------------- + +A libwebsockets-based general webserver is built by default now, lwsws. + +It's configured by JSON, by default in + + /etc/lwsws/conf + +which contains global lws context settings like this + +{ + "global": { + "uid": "99", + "gid": "99", + "interface": "eth0", + "count-threads": "1" + } +} + + /etc/lwsws/conf.d/* + +which contains zero or more files describing vhosts, like this + +{ + "vhosts": [ + { "name": "warmcat.com", + "port": "443", + "host-ssl-key": "/etc/pki/tls/private/warmcat.com.key", + "host-ssl-cert": "/etc/pki/tls/certs/warmcat.com.crt", + "host-ssl-ca": "/etc/pki/tls/certs/warmcat.com.cer", + "mounts": [ + { "/": [ + { "home": "file:///var/www/warmcat.com" }, + { "default": "index.html" } + ] + } + ] + } + ] +} + + v1.7.0 ====== diff --git a/lwsws/conf.c b/lwsws/conf.c new file mode 100644 index 0000000..4c94c5d --- /dev/null +++ b/lwsws/conf.c @@ -0,0 +1,382 @@ +/* + * libwebsockets web server application + * + * Copyright (C) 2010-2016 Andy Green + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "lwsws.h" + +static const char * const paths_global[] = { + "global.uid", + "global.gid", + "global.interface", + "global.count-threads", + "global.init-ssl", +}; + +enum lejp_global_paths { + LEJPGP_UID, + LEJPGP_GID, + LEJPGP_INTERFACE, + LEJPGP_COUNT_THREADS, + LWJPGP_INIT_SSL, +}; + +static const char * const paths_vhosts[] = { + "vhosts[]", + "vhosts[].mounts[]", + "vhosts[].name", + "vhosts[].port", + "vhosts[].host-ssl-key", + "vhosts[].host-ssl-cert", + "vhosts[].host-ssl-ca", + "vhosts[].mounts[].mountpoint", + "vhosts[].mounts[].origin", + "vhosts[].mounts[].default" +}; + +enum lejp_vhost_paths { + LEJPVP, + LEJPVP_MOUNTS, + LEJPVP_NAME, + LEJPVP_PORT, + LEJPVP_HOST_SSL_KEY, + LEJPVP_HOST_SSL_CERT, + LEJPVP_HOST_SSL_CA, + LEJPVP_MOUNTPOINT, + LEJPVP_ORIGIN, + LEJPVP_DEFAULT, +}; + +struct jpargs { + struct lws_context_creation_info *info; + struct lws_context *context; + const struct lws_protocols *protocols; + const struct lws_extension *extensions; + char *p, *end, valid; + struct lws_http_mount *head, *last; + char *mountpoint, *origin, *def; +}; + +static int arg_to_bool(const char *s) +{ + static const char * const on[] = { "on", "yes", "true" }; + int n = atoi(s); + + if (n) + return 1; + + for (n = 0; n < ARRAY_SIZE(on); n++) + if (!strcasecmp(s, on[n])) + return 1; + + return 0; +} + +static char +lejp_globals_cb(struct lejp_ctx *ctx, char reason) +{ + struct jpargs *a = (struct jpargs *)ctx->user; + + /* we only match on the prepared path strings */ + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + switch (ctx->path_match - 1) { + case LEJPGP_UID: + a->info->uid = atoi(ctx->buf); + return 0; + case LEJPGP_GID: + a->info->gid = atoi(ctx->buf); + return 0; + case LEJPGP_INTERFACE: + a->info->iface = a->p; + break; + case LEJPGP_COUNT_THREADS: + a->info->count_threads = atoi(ctx->buf); + return 0; + case LWJPGP_INIT_SSL: + if (arg_to_bool(ctx->buf)) + a->info->options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + return 0; + + default: + return 0; + } + + a->p += snprintf(a->p, a->end - a->p, "%s", ctx->buf); + + return 0; +} + +static char +lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) +{ + struct jpargs *a = (struct jpargs *)ctx->user; + struct lws_http_mount *m; + int n; + + if (reason == LEJPCB_OBJECT_START && ctx->path_match == LEJPVP + 1) { + a->valid = 1; + a->head = NULL; + a->last = NULL; + a->info->port = 0; + a->info->iface = NULL; + a->info->protocols = a->protocols; + a->info->extensions = a->extensions; + a->info->ssl_cert_filepath = NULL; + a->info->ssl_private_key_filepath = NULL; + a->info->ssl_ca_filepath = NULL; + a->info->timeout_secs = 5; + a->info->ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:" + "ECDHE-RSA-AES256-GCM-SHA384:" + "DHE-RSA-AES256-GCM-SHA384:" + "ECDHE-RSA-AES256-SHA384:" + "HIGH:!aNULL:!eNULL:!EXPORT:" + "!DES:!MD5:!PSK:!RC4:!HMAC_SHA1:" + "!SHA1:!DHE-RSA-AES128-GCM-SHA256:" + "!DHE-RSA-AES128-SHA256:" + "!AES128-GCM-SHA256:" + "!AES128-SHA256:" + "!DHE-RSA-AES256-SHA256:" + "!AES256-GCM-SHA384:" + "!AES256-SHA256"; + } + + if (reason == LEJPCB_OBJECT_START && + ctx->path_match == LEJPVP_MOUNTS + 1) { + a->mountpoint = NULL; + a->origin = NULL; + a->def = NULL; + } + + if (reason == LEJPCB_OBJECT_END && + (ctx->path_match == LEJPVP + 1 || !ctx->path[0]) && + a->valid) { + + //lwsl_notice("%s\n", ctx->path); + if (!a->info->port) { + lwsl_err("Port required (eg, 443)"); + return 1; + } + a->valid = 0; + + if (!lws_create_vhost(a->context, a->info, a->head)) { + lwsl_err("Failed to create vhost %s\n", + a->info->vhost_name); + return 1; + } + + return 0; + } + + if (reason == LEJPCB_OBJECT_END && + ctx->path_match == LEJPVP_MOUNTS + 1) { + if (!a->mountpoint || !a->origin) { + lwsl_err("mountpoint and origin required\n"); + return 1; + } + + n = lws_write_http_mount(a->last, &m, a->p, a->mountpoint, + a->origin, a->def); + if (!n) + return 1; + a->p += n; + if (!a->head) + a->head = m; + + a->last = m; + } + + /* we only match on the prepared path strings */ + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + switch (ctx->path_match - 1) { + case LEJPVP_NAME: + a->info->vhost_name = a->p; + break; + case LEJPVP_PORT: + a->info->port = atoi(ctx->buf); + return 0; + case LEJPVP_HOST_SSL_KEY: + a->info->ssl_private_key_filepath = a->p; + break; + case LEJPVP_HOST_SSL_CERT: + a->info->ssl_cert_filepath = a->p; + break; + case LEJPVP_HOST_SSL_CA: + a->info->ssl_ca_filepath = a->p; + break; + case LEJPVP_MOUNTPOINT: + a->mountpoint = a->p; + break; + case LEJPVP_ORIGIN: + a->origin = a->p; + break; + case LEJPVP_DEFAULT: + a->def = a->p; + break; + default: + return 0; + } + + a->p += snprintf(a->p, a->end - a->p, "%s", ctx->buf); + *(a->p)++ = '\0'; + + return 0; +} + +/* + * returns 0 = OK, 1 = can't open, 2 = parsing error + */ + +static int +lwsws_get_config(void *user, const char *f, const char * const *paths, + int count_paths, lejp_callback cb) +{ + unsigned char buf[128]; + struct lejp_ctx ctx; + int n, m, fd; + + fd = open(f, O_RDONLY); + if (fd < 0) { + lwsl_err("Cannot open %s\n", f); + return 1; + } + lwsl_info("%s: %s\n", __func__, f); + lejp_construct(&ctx, cb, user, paths, count_paths); + + do { + n = read(fd, buf, sizeof(buf)); + if (!n) + break; + + m = (int)(char)lejp_parse(&ctx, buf, n); + } while (m == LEJP_CONTINUE); + + close(fd); + n = ctx.line; + lejp_destruct(&ctx); + + if (m < 0) { + lwsl_err("%s(%u): parsing error %d\n", f, n, m); + return 2; + } + + return 0; +} + +#ifndef _WIN32 +static int filter(const struct dirent *ent) +{ + if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) + return 0; + + return 1; +} +#endif + +static int +lwsws_get_config_d(void *user, const char *d, const char * const *paths, + int count_paths, lejp_callback cb) +{ +#ifndef _WIN32 + struct dirent **namelist; + char path[256]; + int n, i, ret = 0; + + n = scandir(d, &namelist, filter, alphasort); + if (n < 0) { + lwsl_err("Scandir on %d failed\n", d); + } + + for (i = 0; i < n; i++) { + snprintf(path, sizeof(path) - 1, "%s/%s", d, + namelist[i]->d_name); + ret = lwsws_get_config(user, path, paths, count_paths, cb); + if (ret) { + while (i++ < n) + free(namelist[i]); + goto bail; + } + free(namelist[i]); + } + +bail: + free(namelist); + + return ret; +#else + return 0; +#endif +} + +int +lwsws_get_config_globals(struct lws_context_creation_info *info, const char *d, + char **cs, int *len) +{ + struct jpargs a; + + a.info = info; + a.p = *cs; + a.end = a.p + *len; + a.valid = 0; + + if (lwsws_get_config(&a, "/etc/lwsws/conf", paths_global, + ARRAY_SIZE(paths_global), lejp_globals_cb) > 1) + return 1; + if (lwsws_get_config_d(&a, d, paths_global, + ARRAY_SIZE(paths_global), lejp_globals_cb) > 1) + return 1; + + *cs = a.p; + *len = a.end - a.p; + + return 0; +} + +int +lwsws_get_config_vhosts(struct lws_context *context, + struct lws_context_creation_info *info, const char *d, + char **cs, int *len) +{ + struct jpargs a; + + a.info = info; + a.p = *cs; + a.end = a.p + *len; + a.valid = 0; + a.context = context; + a.protocols = info->protocols; + a.extensions = info->extensions; + + if (lwsws_get_config(&a, "/etc/lwsws/conf", paths_vhosts, + ARRAY_SIZE(paths_vhosts), lejp_vhosts_cb) > 1) + return 1; + if (lwsws_get_config_d(&a, d, paths_vhosts, + ARRAY_SIZE(paths_vhosts), lejp_vhosts_cb) > 1) + return 1; + + *cs = a.p; + *len = a.end - a.p; + + lws_finalize_startup(context); + + return 0; +} diff --git a/lwsws/http.c b/lwsws/http.c new file mode 100644 index 0000000..1b27a7d --- /dev/null +++ b/lwsws/http.c @@ -0,0 +1,663 @@ +/* + * libwebsockets web server application + * + * Copyright (C) 2010-2016 Andy Green + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +#include "lwsws.h" + +/* http server gets files from this path */ +#define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server" +char *resource_path = LOCAL_RESOURCE_PATH; + + + +/* + * We take a strict whitelist approach to stop ../ attacks + */ +struct serveable { + const char *urlpath; + const char *mimetype; +}; + +const char * get_mimetype(const char *file) +{ + int n = strlen(file); + + if (n < 5) + return NULL; + + if (!strcmp(&file[n - 4], ".ico")) + return "image/x-icon"; + + if (!strcmp(&file[n - 4], ".png")) + return "image/png"; + + if (!strcmp(&file[n - 5], ".html")) + return "text/html"; + + if (!strcmp(&file[n - 4], ".css")) + return "text/css"; + + return NULL; +} + +/* this protocol server (always the first one) handles HTTP, + * + * Some misc callbacks that aren't associated with a protocol also turn up only + * here on the first protocol server. + */ + +int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, + void *in, size_t len) +{ + struct per_session_data__http *pss = + (struct per_session_data__http *)user; + unsigned char buffer[4096 + LWS_PRE]; + unsigned long amount, file_len, sent; + char leaf_path[1024]; + const char *mimetype; + char *other_headers; + unsigned char *end; + struct timeval tv; + unsigned char *p; +#ifndef LWS_NO_CLIENT + struct per_session_data__http *pss1; + struct lws *wsi1; +#endif + char buf[256]; + char b64[64]; + int n, m; +#ifdef EXTERNAL_POLL + struct lws_pollargs *pa = (struct lws_pollargs *)in; +#endif + +// lwsl_err("%s: reason %d\n", __func__, reason); + + switch (reason) { + case LWS_CALLBACK_HTTP: + + { + char name[100], rip[50]; + lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, + sizeof(name), rip, sizeof(rip)); + sprintf(buf, "%s (%s)", name, rip); + lwsl_notice("HTTP connect from %s\n", buf); + } + + if (len < 1) { + lws_return_http_status(wsi, + HTTP_STATUS_BAD_REQUEST, NULL); + goto try_to_reuse; + } + +#ifndef LWS_NO_CLIENT + if (!strncmp(in, "/proxytest", 10)) { + struct lws_client_connect_info i; + char *rootpath = "/"; + const char *p = (const char *)in; + + if (lws_get_child(wsi)) + break; + + pss->client_finished = 0; + memset(&i,0, sizeof(i)); + i.context = lws_get_context(wsi); + i.address = "git.libwebsockets.org"; + i.port = 80; + i.ssl_connection = 0; + if (p[10]) + i.path = (char *)in + 10; + else + i.path = rootpath; + i.host = "git.libwebsockets.org"; + i.origin = NULL; + i.method = "GET"; + i.parent_wsi = wsi; + i.uri_replace_from = "git.libwebsockets.org/"; + i.uri_replace_to = "/proxytest/"; + if (!lws_client_connect_via_info(&i)) { + lwsl_err("proxy connect fail\n"); + break; + } + + + + break; + } +#endif + +#if 1 + /* this example server has no concept of directories */ + if (strchr((const char *)in + 1, '/')) { + lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL); + goto try_to_reuse; + } +#endif + +#ifdef LWS_WITH_CGI + if (!strncmp(in, "/cgitest", 8)) { + static char *cmd[] = { + "/bin/sh", + "-c", + INSTALL_DATADIR"/libwebsockets-test-server/lws-cgi-test.sh", +// "/var/www/cgi-bin/cgit", + NULL + }; + + lwsl_notice("%s: cgitest\n", __func__); + n = lws_cgi(wsi, cmd, 8, 5); + if (n) { + lwsl_err("%s: cgi failed\n"); + return -1; + } + p = buffer + LWS_PRE; + 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_CONNECTION, + (unsigned char *)"close", 5, &p, end)) + return 1; + n = lws_write(wsi, buffer + LWS_PRE, + p - (buffer + LWS_PRE), + LWS_WRITE_HTTP_HEADERS); + + /* the cgi starts by outputting headers, we can't + * finalize the headers until we see the end of that + */ + + break; + } +#endif + + /* if a legal POST URL, let it continue and accept data */ + if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) + return 0; + + /* check for the "send a big file by hand" example case */ + + if (!strcmp((const char *)in, "/leaf.jpg")) { + if (strlen(resource_path) > sizeof(leaf_path) - 10) + return -1; + sprintf(leaf_path, "%s/leaf.jpg", resource_path); + + /* well, let's demonstrate how to send the hard way */ + + p = buffer + LWS_PRE; + end = p + sizeof(buffer) - LWS_PRE; + + pss->fd = lws_plat_file_open(wsi, leaf_path, &file_len, + LWS_O_RDONLY); + + if (pss->fd == LWS_INVALID_FILE) { + lwsl_err("faild to open file %s\n", leaf_path); + return -1; + } + + /* + * we will send a big jpeg file, but it could be + * anything. Set the Content-Type: appropriately + * so the browser knows what to do with it. + * + * Notice we use the APIs to build the header, which + * will do the right thing for HTTP 1/1.1 and HTTP2 + * depending on what connection it happens to be working + * on + */ + if (lws_add_http_header_status(wsi, 200, &p, end)) + return 1; + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER, + (unsigned char *)"libwebsockets", + 13, &p, end)) + return 1; + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_CONTENT_TYPE, + (unsigned char *)"image/jpeg", + 10, &p, end)) + return 1; + if (lws_add_http_header_content_length(wsi, + file_len, &p, + end)) + return 1; + if (lws_finalize_http_header(wsi, &p, end)) + return 1; + + /* + * send the http headers... + * this won't block since it's the first payload sent + * on the connection since it was established + * (too small for partial) + * + * Notice they are sent using LWS_WRITE_HTTP_HEADERS + * which also means you can't send body too in one step, + * this is mandated by changes in HTTP2 + */ + + *p = '\0'; + lwsl_info("%s\n", buffer + LWS_PRE); + + n = lws_write(wsi, buffer + LWS_PRE, + p - (buffer + LWS_PRE), + LWS_WRITE_HTTP_HEADERS); + if (n < 0) { + lws_plat_file_close(wsi, pss->fd); + return -1; + } + /* + * book us a LWS_CALLBACK_HTTP_WRITEABLE callback + */ + lws_callback_on_writable(wsi); + break; + } + + /* if not, send a file the easy way */ + if (!strncmp(in, "/cgit-data/", 11)) { + in = (char *)in + 11; + strcpy(buf, "/usr/share/cgit"); + } else + strcpy(buf, resource_path); + + if (strcmp(in, "/")) { + if (*((const char *)in) != '/') + strcat(buf, "/"); + strncat(buf, in, sizeof(buf) - strlen(buf) - 1); + } else /* default file to serve */ + strcat(buf, "/test.html"); + buf[sizeof(buf) - 1] = '\0'; + + /* refuse to serve files we don't understand */ + mimetype = get_mimetype(buf); + if (!mimetype) { + lwsl_err("Unknown mimetype for %s\n", buf); + lws_return_http_status(wsi, + HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL); + return -1; + } + + /* demonstrates how to set a cookie on / */ + + other_headers = leaf_path; + p = (unsigned char *)leaf_path; + if (!strcmp((const char *)in, "/") && + !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) { + /* this isn't very unguessable but it'll do for us */ + gettimeofday(&tv, NULL); + n = sprintf(b64, "test=LWS_%u_%u_COOKIE;Max-Age=360000", + (unsigned int)tv.tv_sec, + (unsigned int)tv.tv_usec); + + if (lws_add_http_header_by_name(wsi, + (unsigned char *)"set-cookie:", + (unsigned char *)b64, n, &p, + (unsigned char *)leaf_path + sizeof(leaf_path))) + return 1; + } + if (lws_is_ssl(wsi) && lws_add_http_header_by_name(wsi, + (unsigned char *) + "Strict-Transport-Security:", + (unsigned char *) + "max-age=15768000 ; " + "includeSubDomains", 36, &p, + (unsigned char *)leaf_path + + sizeof(leaf_path))) + return 1; + n = (char *)p - leaf_path; + + n = lws_serve_http_file(wsi, buf, mimetype, other_headers, n); + if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi))) + return -1; /* error or can't reuse connection: close the socket */ + + /* + * notice that the sending of the file completes asynchronously, + * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when + * it's done + */ + break; + + case LWS_CALLBACK_HTTP_BODY: + strncpy(buf, in, 20); + buf[20] = '\0'; + if (len < 20) + buf[len] = '\0'; + + lwsl_notice("LWS_CALLBACK_HTTP_BODY: %s... len %d\n", + (const char *)buf, (int)len); + + break; + + case LWS_CALLBACK_HTTP_BODY_COMPLETION: + lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); + /* the whole of the sent body arrived, close or reuse the connection */ + lws_return_http_status(wsi, HTTP_STATUS_OK, NULL); + goto try_to_reuse; + + case LWS_CALLBACK_HTTP_FILE_COMPLETION: + goto try_to_reuse; + + case LWS_CALLBACK_HTTP_WRITEABLE: + lwsl_info("LWS_CALLBACK_HTTP_WRITEABLE\n"); + + if (pss->client_finished) + return -1; + + if (pss->fd == LWS_INVALID_FILE) + goto try_to_reuse; +#ifdef LWS_WITH_CGI + if (pss->reason_bf & 1) { + if (lws_cgi_write_split_stdout_headers(wsi) < 0) + goto bail; + + pss->reason_bf &= ~1; + break; + } +#endif +#ifndef LWS_NO_CLIENT + if (pss->reason_bf & 2) { + char *px = buf + LWS_PRE; + int lenx = sizeof(buf) - LWS_PRE; + /* + * our sink is writeable and our source has something + * to read. So read a lump of source material of + * suitable size to send or what's available, whichever + * is the smaller. + */ + pss->reason_bf &= ~2; + wsi1 = lws_get_child(wsi); + if (!wsi1) + break; + if (lws_http_client_read(wsi1, &px, &lenx) < 0) + goto bail; + + if (pss->client_finished) + return -1; + break; + } +#endif + /* + * we can send more of whatever it is we were sending + */ + sent = 0; + do { + /* we'd like the send this much */ + n = sizeof(buffer) - LWS_PRE; + + /* but if the peer told us he wants less, we can adapt */ + m = lws_get_peer_write_allowance(wsi); + + /* -1 means not using a protocol that has this info */ + if (m == 0) + /* right now, peer can't handle anything */ + goto later; + + if (m != -1 && m < n) + /* he couldn't handle that much */ + n = m; + + n = lws_plat_file_read(wsi, pss->fd, + &amount, buffer + LWS_PRE, n); + /* problem reading, close conn */ + if (n < 0) { + lwsl_err("problem reading file\n"); + goto bail; + } + n = (int)amount; + /* sent it all, close conn */ + if (n == 0) + goto penultimate; + /* + * To support HTTP2, must take care about preamble space + * + * identification of when we send the last payload frame + * is handled by the library itself if you sent a + * content-length header + */ + m = lws_write(wsi, buffer + LWS_PRE, n, LWS_WRITE_HTTP); + if (m < 0) { + lwsl_err("write failed\n"); + /* write failed, close conn */ + goto bail; + } + if (m) /* while still active, extend timeout */ + lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 5); + sent += m; + + } while (!lws_send_pipe_choked(wsi) && (sent < 1024 * 1024)); +later: + lws_callback_on_writable(wsi); + break; +penultimate: + lws_plat_file_close(wsi, pss->fd); + pss->fd = LWS_INVALID_FILE; + goto try_to_reuse; + +bail: + lws_plat_file_close(wsi, pss->fd); + + return -1; + + /* + * callback for confirming to continue with client IP appear in + * protocol 0 callback since no websocket protocol has been agreed + * yet. You can just ignore this if you won't filter on client IP + * since the default unhandled callback return is 0 meaning let the + * connection continue. + */ + case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: + /* if we returned non-zero from here, we kill the connection */ + break; + +#ifndef LWS_NO_CLIENT + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { + char ctype[64], ctlen = 0; + lwsl_err("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP\n"); + p = buffer + LWS_PRE; + end = p + sizeof(buffer) - LWS_PRE; + if (lws_add_http_header_status(lws_get_parent(wsi), 200, &p, end)) + return 1; + if (lws_add_http_header_by_token(lws_get_parent(wsi), + WSI_TOKEN_HTTP_SERVER, + (unsigned char *)"libwebsockets", + 13, &p, end)) + return 1; + + ctlen = lws_hdr_copy(wsi, ctype, sizeof(ctype), WSI_TOKEN_HTTP_CONTENT_TYPE); + if (ctlen > 0) { + if (lws_add_http_header_by_token(lws_get_parent(wsi), + WSI_TOKEN_HTTP_CONTENT_TYPE, + (unsigned char *)ctype, ctlen, &p, end)) + return 1; + } +#if 0 + if (lws_add_http_header_content_length(lws_get_parent(wsi), + file_len, &p, end)) + return 1; +#endif + if (lws_finalize_http_header(lws_get_parent(wsi), &p, end)) + return 1; + + *p = '\0'; + lwsl_info("%s\n", buffer + LWS_PRE); + + n = lws_write(lws_get_parent(wsi), buffer + LWS_PRE, + p - (buffer + LWS_PRE), + LWS_WRITE_HTTP_HEADERS); + if (n < 0) + return -1; + + break; } + case LWS_CALLBACK_CLOSED_CLIENT_HTTP: + //lwsl_err("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n"); + return -1; + break; + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: + //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p\n", wsi); + assert(lws_get_parent(wsi)); + if (!lws_get_parent(wsi)) + break; + // lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p: sock: %d, parent_wsi: %p, parent_sock:%d, len %d\n", + // wsi, lws_get_socket_fd(wsi), + // lws_get_parent(wsi), + // lws_get_socket_fd(lws_get_parent(wsi)), len); + pss1 = lws_wsi_user(lws_get_parent(wsi)); + pss1->reason_bf |= 2; + lws_callback_on_writable(lws_get_parent(wsi)); + break; + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: + //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ len %d\n", len); + assert(lws_get_parent(wsi)); + m = lws_write(lws_get_parent(wsi), (unsigned char *)in, + len, LWS_WRITE_HTTP); + if (m < 0) + return -1; + break; + case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: + //lwsl_err("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n"); + assert(lws_get_parent(wsi)); + if (!lws_get_parent(wsi)) + break; + pss1 = lws_wsi_user(lws_get_parent(wsi)); + pss1->client_finished = 1; + break; +#endif + +#ifdef LWS_WITH_CGI + /* CGI IO events (POLLIN/OUT) appear here our demo user code policy is + * + * - POST data goes on subprocess stdin + * - subprocess stdout goes on http via writeable callback + * - subprocess stderr goes to the logs + */ + case LWS_CALLBACK_CGI: + pss->args = *((struct lws_cgi_args *)in); + //lwsl_notice("LWS_CALLBACK_CGI: ch %d\n", pss->args.ch); + switch (pss->args.ch) { /* which of stdin/out/err ? */ + case LWS_STDIN: + /* TBD stdin rx flow control */ + break; + case LWS_STDOUT: + pss->reason_bf |= 1; + /* when writing to MASTER would not block */ + lws_callback_on_writable(wsi); + break; + case LWS_STDERR: + n = read(lws_get_socket_fd(pss->args.stdwsi[LWS_STDERR]), + buf, 127); + //lwsl_notice("stderr reads %d\n", n); + if (n > 0) { + if (buf[n - 1] != '\n') + buf[n++] = '\n'; + buf[n] = '\0'; + lwsl_notice("CGI-stderr: %s\n", buf); + } + break; + } + break; + + case LWS_CALLBACK_CGI_TERMINATED: + //lwsl_notice("LWS_CALLBACK_CGI_TERMINATED\n"); + /* because we sent on openended http, close the connection */ + return -1; + + case LWS_CALLBACK_CGI_STDIN_DATA: /* POST body for stdin */ + //lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA\n"); + pss->args = *((struct lws_cgi_args *)in); + n = write(lws_get_socket_fd(pss->args.stdwsi[LWS_STDIN]), + pss->args.data, pss->args.len); + //lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: write says %d", n); + if (n < pss->args.len) + lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: sent %d only %d went", + n, pss->args.len); + return n; +#endif + + /* + * callbacks for managing the external poll() array appear in + * protocol 0 callback + */ + + case LWS_CALLBACK_LOCK_POLL: + /* + * lock mutex to protect pollfd state + * called before any other POLL related callback + * if protecting wsi lifecycle change, len == 1 + */ + test_server_lock(len); + break; + + case LWS_CALLBACK_UNLOCK_POLL: + /* + * unlock mutex to protect pollfd state when + * called after any other POLL related callback + * if protecting wsi lifecycle change, len == 1 + */ + test_server_unlock(len); + break; + +#ifdef EXTERNAL_POLL + case LWS_CALLBACK_ADD_POLL_FD: + + if (count_pollfds >= max_poll_elements) { + lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n"); + return 1; + } + + fd_lookup[pa->fd] = count_pollfds; + pollfds[count_pollfds].fd = pa->fd; + pollfds[count_pollfds].events = pa->events; + pollfds[count_pollfds++].revents = 0; + break; + + case LWS_CALLBACK_DEL_POLL_FD: + if (!--count_pollfds) + break; + m = fd_lookup[pa->fd]; + /* have the last guy take up the vacant slot */ + pollfds[m] = pollfds[count_pollfds]; + fd_lookup[pollfds[count_pollfds].fd] = m; + break; + + case LWS_CALLBACK_CHANGE_MODE_POLL_FD: + pollfds[fd_lookup[pa->fd]].events = pa->events; + break; +#endif + + case LWS_CALLBACK_GET_THREAD_ID: + /* + * if you will call "lws_callback_on_writable" + * from a different thread, return the caller thread ID + * here so lws can use this information to work out if it + * should signal the poll() loop to exit and restart early + */ + + /* return pthread_getthreadid_np(); */ + + break; + + default: + break; + } + + return 0; + + /* if we're on HTTP1.1 or 2.0, will keep the idle connection alive */ +try_to_reuse: + if (lws_http_transaction_completed(wsi)) + return -1; + + return 0; +} diff --git a/lwsws/lejp.c b/lwsws/lejp.c new file mode 100644 index 0000000..9eff615 --- /dev/null +++ b/lwsws/lejp.c @@ -0,0 +1,621 @@ +/* + * Lightweight Embedded JSON Parser + * + * Copyright (C) 2013 Andy Green + * This code is licensed under LGPL 2.1 + * http://www.gnu.org/licenses/lgpl-2.1.html + */ + +#include +#include "lejp.h" + +#include + +/** + * lejp_construct - prepare a struct lejp_ctx for use + * + * @ctx: pointer to your struct lejp_ctx + * @callback: your user callback which will received parsed tokens + * @user: optional user data pointer untouched by lejp + * @paths: your array of name elements you are interested in + * @count_paths: ARRAY_SIZE() of @paths + * + * Prepares your context struct for use with lejp + */ + +void +lejp_construct(struct lejp_ctx *ctx, + char (*callback)(struct lejp_ctx *ctx, char reason), void *user, + const char * const *paths, unsigned char count_paths) +{ + ctx->st[0].s = 0; + ctx->st[0].p = 0; + ctx->st[0].i = 0; + ctx->st[0].b = 0; + ctx->sp = 0; + ctx->ipos = 0; + ctx->ppos = 0; + ctx->path_match = 0; + ctx->path[0] = '\0'; + ctx->callback = callback; + ctx->user = user; + ctx->paths = paths; + ctx->count_paths = count_paths; + ctx->line = 1; + ctx->callback(ctx, LEJPCB_CONSTRUCTED); +} + +/** + * lejp_destruct - retire a previously constructed struct lejp_ctx + * + * @ctx: pointer to your struct lejp_ctx + * + * lejp does not perform any allocations, but since your user code might, this + * provides a one-time LEJPCB_DESTRUCTED callback at destruction time where + * you can clean up in your callback. + */ + +void +lejp_destruct(struct lejp_ctx *ctx) +{ + /* no allocations... just let callback know what it happening */ + ctx->callback(ctx, LEJPCB_DESTRUCTED); +} + +/** + * lejp_change_callback - switch to a different callback from now on + * + * @ctx: pointer to your struct lejp_ctx + * @callback: your user callback which will received parsed tokens + * + * This tells the old callback it was destroyed, in case you want to take any + * action because that callback "lost focus", then changes to the new + * callback and tells it first that it was constructed, and then started. + * + * Changing callback is a cheap and powerful trick to split out handlers + * according to information earlier in the parse. For example you may have + * a JSON pair "schema" whose value defines what can be expected for the rest + * of the JSON. Rather than having one huge callback for all cases, you can + * have an initial one looking for "schema" which then calls + * lejp_change_callback() to a handler specific for the schema. + * + * Notice that afterwards, you need to construct the context again anyway to + * parse another JSON object, and the callback is reset then to the main, + * schema-interpreting one. The construction action is very lightweight. + */ + +void +lejp_change_callback(struct lejp_ctx *ctx, + char (*callback)(struct lejp_ctx *ctx, char reason)) +{ + ctx->callback(ctx, LEJPCB_DESTRUCTED); + ctx->callback = callback; + ctx->callback(ctx, LEJPCB_CONSTRUCTED); + ctx->callback(ctx, LEJPCB_START); +} + +static void +lejp_check_path_match(struct lejp_ctx *ctx) +{ + int n; + + /* we only need to check if a match is not active */ + for (n = 0; !ctx->path_match && n < ctx->count_paths; n++) { + if (strcmp(ctx->path, ctx->paths[n])) + continue; + ctx->path_match = n + 1; + ctx->path_match_len = ctx->ppos; + return; + } +} + +/** + * lejp_parse - interpret some more incoming data incrementally + * + * @ctx: previously constructed parsing context + * @json: char buffer with the new data to interpret + * @len: amount of data in the buffer + * + * Because lejp is a stream parser, it incrementally parses as new data + * becomes available, maintaining all state in the context struct. So an + * incomplete JSON is a normal situation, getting you a LEJP_CONTINUE + * return, signalling there's no error but to call again with more data when + * it comes to complete the parsing. Successful parsing completes with a + * 0 or positive integer indicating how much of the last input buffer was + * unused. + */ + +int +lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) +{ + unsigned char c, n, s, ret = LEJP_REJECT_UNKNOWN; + static const char esc_char[] = "\"\\/bfnrt"; + static const char esc_tran[] = "\"\\/\b\f\n\r\t"; + static const char tokens[] = "rue alse ull "; + + if (!ctx->sp && !ctx->ppos) + ctx->callback(ctx, LEJPCB_START); + + while (len--) { + c = *json++; + + s = ctx->st[ctx->sp].s; + + /* skip whitespace unless we should care */ + if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '#') { + if (c == '\n') { + ctx->line++; + ctx->st[ctx->sp].s &= ~LEJP_FLAG_WS_COMMENTLINE; + } + if (!(s & LEJP_FLAG_WS_KEEP)) { + if (c == '#') + ctx->st[ctx->sp].s |= + LEJP_FLAG_WS_COMMENTLINE; + continue; + } + } + + if (ctx->st[ctx->sp].s & LEJP_FLAG_WS_COMMENTLINE) + continue; + + switch (s) { + case LEJP_IDLE: + if (c != '{') { + ret = LEJP_REJECT_IDLE_NO_BRACE; + goto reject; + } + ctx->callback(ctx, LEJPCB_OBJECT_START); + ctx->st[ctx->sp].s = LEJP_MEMBERS; + break; + case LEJP_MEMBERS: + if (c == '}') { + ctx->st[ctx->sp].s = LEJP_IDLE; + ret = LEJP_REJECT_MEMBERS_NO_CLOSE; + goto reject; + } + ctx->st[ctx->sp].s = LEJP_M_P; + goto redo_character; + case LEJP_M_P: + if (c != '\"') { + ret = LEJP_REJECT_MP_NO_OPEN_QUOTE; + goto reject; + } + /* push */ + ctx->st[ctx->sp].s = LEJP_MP_DELIM; + c = LEJP_MP_STRING; + goto add_stack_level; + + case LEJP_MP_STRING: + if (c == '\"') { + if (!ctx->sp) { + ret = LEJP_REJECT_MP_STRING_UNDERRUN; + goto reject; + } + if (ctx->st[ctx->sp - 1].s != LEJP_MP_DELIM) { + ctx->buf[ctx->npos] = '\0'; + if (ctx->callback(ctx, + LEJPCB_VAL_STR_END) < 0) { + ret = LEJP_REJECT_CALLBACK; + goto reject; + } + } + /* pop */ + ctx->sp--; + break; + } + if (c == '\\') { + ctx->st[ctx->sp].s = LEJP_MP_STRING_ESC; + break; + } + if (c < ' ') {/* "control characters" not allowed */ + ret = LEJP_REJECT_MP_ILLEGAL_CTRL; + goto reject; + } + goto emit_string_char; + + case LEJP_MP_STRING_ESC: + if (c == 'u') { + ctx->st[ctx->sp].s = LEJP_MP_STRING_ESC_U1; + ctx->uni = 0; + break; + } + for (n = 0; n < sizeof(esc_char); n++) { + if (c != esc_char[n]) + continue; + /* found it */ + c = esc_tran[n]; + ctx->st[ctx->sp].s = LEJP_MP_STRING; + goto emit_string_char; + } + ret = LEJP_REJECT_MP_STRING_ESC_ILLEGAL_ESC; + /* illegal escape char */ + goto reject; + + case LEJP_MP_STRING_ESC_U1: + case LEJP_MP_STRING_ESC_U2: + case LEJP_MP_STRING_ESC_U3: + case LEJP_MP_STRING_ESC_U4: + ctx->uni <<= 4; + if (c >= '0' && c <= '9') + ctx->uni |= c - '0'; + else + if (c >= 'a' && c <= 'f') + ctx->uni = c - 'a' + 10; + else + if (c >= 'A' && c <= 'F') + ctx->uni = c - 'A' + 10; + else { + ret = LEJP_REJECT_ILLEGAL_HEX; + goto reject; + } + ctx->st[ctx->sp].s++; + switch (s) { + case LEJP_MP_STRING_ESC_U2: + if (ctx->uni < 0x08) + break; + /* + * 0x08-0xff (0x0800 - 0xffff) + * emit 3-byte UTF-8 + */ + c = 0xe0 | ((ctx->uni >> 4) & 0xf); + goto emit_string_char; + + case LEJP_MP_STRING_ESC_U3: + if (ctx->uni >= 0x080) { + /* + * 0x080 - 0xfff (0x0800 - 0xffff) + * middle 3-byte seq + * send ....XXXXXX.. + */ + c = 0x80 | ((ctx->uni >> 2) & 0x3f); + goto emit_string_char; + } + if (ctx->uni < 0x008) + break; + /* + * 0x008 - 0x7f (0x0080 - 0x07ff) + * start 2-byte seq + */ + c = 0xc0 | (ctx->uni >> 2); + goto emit_string_char; + + case LEJP_MP_STRING_ESC_U4: + if (ctx->uni >= 0x0080) + /* end of 2 or 3-byte seq */ + c = 0x80 | (ctx->uni & 0x3f); + else + /* literal */ + c = (unsigned char)ctx->uni; + + ctx->st[ctx->sp].s = LEJP_MP_STRING; + goto emit_string_char; + default: + break; + } + break; + + case LEJP_MP_DELIM: + if (c != ':') { + ret = LEJP_REJECT_MP_DELIM_MISSING_COLON; + goto reject; + } + ctx->st[ctx->sp].s = LEJP_MP_VALUE; + ctx->path[ctx->ppos] = '\0'; + + lejp_check_path_match(ctx); + ctx->callback(ctx, LEJPCB_PAIR_NAME); + break; + + case LEJP_MP_VALUE: + if (c >= '0' && c <= '9') { + ctx->npos = 0; + ctx->dcount = 0; + ctx->f = 0; + ctx->st[ctx->sp].s = LEJP_MP_VALUE_NUM_INT; + goto redo_character; + } + switch (c) { + case'\"': + /* push */ + ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END; + c = LEJP_MP_STRING; + ctx->npos = 0; + ctx->buf[0] = '\0'; + ctx->callback(ctx, LEJPCB_VAL_STR_START); + goto add_stack_level; + + case '{': + /* push */ + ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END; + c = LEJP_MEMBERS; + lejp_check_path_match(ctx); + ctx->callback(ctx, LEJPCB_OBJECT_START); + ctx->path_match = 0; + goto add_stack_level; + + case '[': + /* push */ + ctx->st[ctx->sp].s = LEJP_MP_ARRAY_END; + c = LEJP_MP_VALUE; + ctx->path[ctx->ppos++] = '['; + ctx->path[ctx->ppos++] = ']'; + ctx->path[ctx->ppos] = '\0'; + ctx->callback(ctx, LEJPCB_ARRAY_START); + ctx->i[ctx->ipos++] = 0; + if (ctx->ipos > ARRAY_SIZE(ctx->i)) { + ret = LEJP_REJECT_MP_DELIM_ISTACK; + goto reject; + } + goto add_stack_level; + + case 't': /* true */ + ctx->uni = 0; + ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK; + break; + + case 'f': + ctx->uni = 4; + ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK; + break; + + case 'n': + ctx->uni = 4 + 5; + ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK; + break; + default: + ret = LEJP_REJECT_MP_DELIM_BAD_VALUE_START; + goto reject; + } + break; + + case LEJP_MP_VALUE_NUM_INT: + if (!ctx->npos && c == '-') { + ctx->f |= LEJP_SEEN_MINUS; + goto append_npos; + } + + if (ctx->dcount < 10 && c >= '0' && c <= '9') { + if (ctx->f & LEJP_SEEN_POINT) + ctx->f |= LEJP_SEEN_POST_POINT; + ctx->dcount++; + goto append_npos; + } + if (c == '.') { + if (ctx->dcount || (ctx->f & LEJP_SEEN_POINT)) { + ret = LEJP_REJECT_MP_VAL_NUM_FORMAT; + goto reject; + } + ctx->f |= LEJP_SEEN_POINT; + goto append_npos; + } + /* + * before exponent, if we had . we must have had at + * least one more digit + */ + if ((ctx->f & + (LEJP_SEEN_POINT | LEJP_SEEN_POST_POINT)) == + LEJP_SEEN_POINT) { + ret = LEJP_REJECT_MP_VAL_NUM_INT_NO_FRAC; + goto reject; + } + if (c == 'e' || c == 'E') { + if (ctx->f & LEJP_SEEN_EXP) { + ret = LEJP_REJECT_MP_VAL_NUM_FORMAT; + goto reject; + } + ctx->f |= LEJP_SEEN_EXP; + ctx->st[ctx->sp].s = LEJP_MP_VALUE_NUM_EXP; + goto append_npos; + } + /* if none of the above, did we even have a number? */ + if (!ctx->dcount) { + ret = LEJP_REJECT_MP_VAL_NUM_FORMAT; + goto reject; + } + + ctx->buf[ctx->npos] = '\0'; + if (ctx->f & LEJP_SEEN_POINT) + ctx->callback(ctx, LEJPCB_VAL_NUM_FLOAT); + else + ctx->callback(ctx, LEJPCB_VAL_NUM_INT); + + /* then this is the post-number character, loop */ + ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END; + goto redo_character; + + case LEJP_MP_VALUE_NUM_EXP: + ctx->st[ctx->sp].s = LEJP_MP_VALUE_NUM_INT; + if (c >= '0' && c <= '9') + goto redo_character; + if (c == '+' || c == '-') + goto append_npos; + ret = LEJP_REJECT_MP_VAL_NUM_EXP_BAD_EXP; + goto reject; + + case LEJP_MP_VALUE_TOK: /* true, false, null */ + if (c != tokens[ctx->uni]) { + ret = LEJP_REJECT_MP_VAL_TOK_UNKNOWN; + goto reject; + } + ctx->uni++; + if (tokens[ctx->uni] != ' ') + break; + switch (ctx->uni) { + case 3: + ctx->buf[0] = '1'; + ctx->buf[1] = '\0'; + ctx->callback(ctx, LEJPCB_VAL_TRUE); + break; + case 8: + ctx->buf[0] = '0'; + ctx->buf[1] = '\0'; + ctx->callback(ctx, LEJPCB_VAL_FALSE); + break; + case 12: + ctx->buf[0] = '\0'; + ctx->callback(ctx, LEJPCB_VAL_NULL); + break; + } + ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END; + break; + + case LEJP_MP_COMMA_OR_END: + ctx->path[ctx->ppos] = '\0'; + if (c == ',') { + /* increment this stack level's index */ + ctx->st[ctx->sp].s = LEJP_M_P; + if (!ctx->sp) { + ctx->ppos = 0; + /* + * since we came back to root level, + * no path can still match + */ + ctx->path_match = 0; + break; + } + ctx->ppos = ctx->st[ctx->sp - 1].p; + ctx->path[ctx->ppos] = '\0'; + if (ctx->path_match && + ctx->ppos <= ctx->path_match_len) + /* + * we shrank the path to be + * smaller than the matching point + */ + ctx->path_match = 0; + + if (ctx->st[ctx->sp - 1].s != LEJP_MP_ARRAY_END) + break; + /* top level is definitely an array... */ + if (ctx->ipos) + ctx->i[ctx->ipos - 1]++; + ctx->st[ctx->sp].s = LEJP_MP_VALUE; + break; + } + if (c == ']') { + if (!ctx->sp) { + ret = LEJP_REJECT_MP_C_OR_E_UNDERF; + goto reject; + } + /* pop */ + ctx->sp--; + if (ctx->st[ctx->sp].s != LEJP_MP_ARRAY_END) { + ret = LEJP_REJECT_MP_C_OR_E_NOTARRAY; + goto reject; + } + /* drop the path [n] bit */ + ctx->ppos = ctx->st[ctx->sp - 1].p; + ctx->ipos = ctx->st[ctx->sp - 1].i; + ctx->path[ctx->ppos] = '\0'; + if (ctx->path_match && + ctx->ppos <= ctx->path_match_len) + /* + * we shrank the path to be + * smaller than the matching point + */ + ctx->path_match = 0; + + /* do LEJP_MP_ARRAY_END processing */ + goto redo_character; + } + if (c == '}') { + if (ctx->sp == 0) { + lejp_check_path_match(ctx); + ctx->callback(ctx, LEJPCB_OBJECT_END); + ctx->callback(ctx, LEJPCB_COMPLETE); + /* done, return unused amount */ + return len; + } + /* pop */ + ctx->sp--; + ctx->ppos = ctx->st[ctx->sp - 1].p; + ctx->ipos = ctx->st[ctx->sp - 1].i; + ctx->path[ctx->ppos] = '\0'; + if (ctx->path_match && + ctx->ppos <= ctx->path_match_len) + /* + * we shrank the path to be + * smaller than the matching point + */ + ctx->path_match = 0; + lejp_check_path_match(ctx); + ctx->callback(ctx, LEJPCB_OBJECT_END); + break; + } + + ret = LEJP_REJECT_MP_C_OR_E_NEITHER; + goto reject; + + case LEJP_MP_ARRAY_END: + ctx->path[ctx->ppos] = '\0'; + if (c == ',') { + /* increment this stack level's index */ + if (ctx->ipos) + ctx->i[ctx->ipos - 1]++; + ctx->st[ctx->sp].s = LEJP_MP_VALUE; + if (ctx->sp) + ctx->ppos = ctx->st[ctx->sp - 1].p; + ctx->path[ctx->ppos] = '\0'; + break; + } + if (c != ']') { + ret = LEJP_REJECT_MP_ARRAY_END_MISSING; + goto reject; + } + + ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END; + ctx->callback(ctx, LEJPCB_ARRAY_END); + break; + } + + continue; + +emit_string_char: + if (!ctx->sp || ctx->st[ctx->sp - 1].s != LEJP_MP_DELIM) { + /* assemble the string value into chunks */ + ctx->buf[ctx->npos++] = c; + if (ctx->npos == sizeof(ctx->buf) - 1) { + ctx->callback(ctx, LEJPCB_VAL_STR_CHUNK); + ctx->npos = 0; + } + continue; + } + /* name part of name:value pair */ + ctx->path[ctx->ppos++] = c; + continue; + +add_stack_level: + /* push on to the object stack */ + if (ctx->ppos && ctx->st[ctx->sp].s != LEJP_MP_COMMA_OR_END && + ctx->st[ctx->sp].s != LEJP_MP_ARRAY_END) + ctx->path[ctx->ppos++] = '.'; + + ctx->st[ctx->sp].p = ctx->ppos; + ctx->st[ctx->sp].i = ctx->ipos; + if (++ctx->sp == ARRAY_SIZE(ctx->st)) { + ret = LEJP_REJECT_STACK_OVERFLOW; + goto reject; + } + ctx->path[ctx->ppos] = '\0'; + ctx->st[ctx->sp].s = c; + ctx->st[ctx->sp].b = 0; + continue; + +append_npos: + if (ctx->npos >= sizeof(ctx->buf)) { + ret = LEJP_REJECT_NUM_TOO_LONG; + goto reject; + } + ctx->buf[ctx->npos++] = c; + continue; + +redo_character: + json--; + len++; + } + + return LEJP_CONTINUE; + +reject: + ctx->callback(ctx, LEJPCB_FAILED); + return ret; +} diff --git a/lwsws/lejp.h b/lwsws/lejp.h new file mode 100644 index 0000000..455c203 --- /dev/null +++ b/lwsws/lejp.h @@ -0,0 +1,227 @@ + +struct lejp_ctx; + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(_x) (sizeof(_x) / sizeof(_x[0])) +#endif +#define LEJP_FLAG_WS_KEEP 64 +#define LEJP_FLAG_WS_COMMENTLINE 32 + +enum lejp_states { + LEJP_IDLE = 0, + LEJP_MEMBERS = 1, + LEJP_M_P = 2, + LEJP_MP_STRING = LEJP_FLAG_WS_KEEP | 3, + LEJP_MP_STRING_ESC = LEJP_FLAG_WS_KEEP | 4, + LEJP_MP_STRING_ESC_U1 = LEJP_FLAG_WS_KEEP | 5, + LEJP_MP_STRING_ESC_U2 = LEJP_FLAG_WS_KEEP | 6, + LEJP_MP_STRING_ESC_U3 = LEJP_FLAG_WS_KEEP | 7, + LEJP_MP_STRING_ESC_U4 = LEJP_FLAG_WS_KEEP | 8, + LEJP_MP_DELIM = 9, + LEJP_MP_VALUE = 10, + LEJP_MP_VALUE_NUM_INT = LEJP_FLAG_WS_KEEP | 11, + LEJP_MP_VALUE_NUM_EXP = LEJP_FLAG_WS_KEEP | 12, + LEJP_MP_VALUE_TOK = LEJP_FLAG_WS_KEEP | 13, + LEJP_MP_COMMA_OR_END = 14, + LEJP_MP_ARRAY_END = 15, +}; + +enum lejp_reasons { + LEJP_CONTINUE = -1, + LEJP_REJECT_IDLE_NO_BRACE = -2, + LEJP_REJECT_MEMBERS_NO_CLOSE = -3, + LEJP_REJECT_MP_NO_OPEN_QUOTE = -4, + LEJP_REJECT_MP_STRING_UNDERRUN = -5, + LEJP_REJECT_MP_ILLEGAL_CTRL = -6, + LEJP_REJECT_MP_STRING_ESC_ILLEGAL_ESC = -7, + LEJP_REJECT_ILLEGAL_HEX = -8, + LEJP_REJECT_MP_DELIM_MISSING_COLON = -9, + LEJP_REJECT_MP_DELIM_BAD_VALUE_START = -10, + LEJP_REJECT_MP_VAL_NUM_INT_NO_FRAC = -11, + LEJP_REJECT_MP_VAL_NUM_FORMAT = -12, + LEJP_REJECT_MP_VAL_NUM_EXP_BAD_EXP = -13, + LEJP_REJECT_MP_VAL_TOK_UNKNOWN = -14, + LEJP_REJECT_MP_C_OR_E_UNDERF = -15, + LEJP_REJECT_MP_C_OR_E_NOTARRAY = -16, + LEJP_REJECT_MP_ARRAY_END_MISSING = -17, + LEJP_REJECT_STACK_OVERFLOW = -18, + LEJP_REJECT_MP_DELIM_ISTACK = -19, + LEJP_REJECT_NUM_TOO_LONG = -20, + LEJP_REJECT_MP_C_OR_E_NEITHER = -21, + LEJP_REJECT_UNKNOWN = -22, + LEJP_REJECT_CALLBACK = -23 +}; + +#define LEJP_FLAG_CB_IS_VALUE 64 + +enum lejp_callbacks { + LEJPCB_CONSTRUCTED = 0, + LEJPCB_DESTRUCTED = 1, + + LEJPCB_START = 2, + LEJPCB_COMPLETE = 3, + LEJPCB_FAILED = 4, + + LEJPCB_PAIR_NAME = 5, + + LEJPCB_VAL_TRUE = LEJP_FLAG_CB_IS_VALUE | 6, + LEJPCB_VAL_FALSE = LEJP_FLAG_CB_IS_VALUE | 7, + LEJPCB_VAL_NULL = LEJP_FLAG_CB_IS_VALUE | 8, + LEJPCB_VAL_NUM_INT = LEJP_FLAG_CB_IS_VALUE | 9, + LEJPCB_VAL_NUM_FLOAT = LEJP_FLAG_CB_IS_VALUE | 10, + LEJPCB_VAL_STR_START = 11, /* notice handle separately */ + LEJPCB_VAL_STR_CHUNK = LEJP_FLAG_CB_IS_VALUE | 12, + LEJPCB_VAL_STR_END = LEJP_FLAG_CB_IS_VALUE | 13, + + LEJPCB_ARRAY_START = 14, + LEJPCB_ARRAY_END = 15, + + LEJPCB_OBJECT_START = 16, + LEJPCB_OBJECT_END = 17 +}; + +/** + * _lejp_callback() - User parser actions + * @ctx: LEJP context + * @reason: Callback reason + * + * Your user callback is associated with the context at construction time, + * and receives calls as the parsing progresses. + * + * All of the callbacks may be ignored and just return 0. + * + * The reasons it might get called, found in @reason, are: + * + * LEJPCB_CONSTRUCTED: The context was just constructed... you might want to + * perform one-time allocation for the life of the context. + * + * LEJPCB_DESTRUCTED: The context is being destructed... if you made any + * allocations at construction-time, you can free them now + * + * LEJPCB_START: Parsing is beginning at the first byte of input + * + * LEJPCB_COMPLETE: Parsing has completed successfully. You'll get a 0 or + * positive return code from lejp_parse indicating the + * amount of unused bytes left in the input buffer + * + * LEJPCB_FAILED: Parsing failed. You'll get a negative error code + * returned from lejp_parse + * + * LEJPCB_PAIR_NAME: When a "name":"value" pair has had the name parsed, + * this callback occurs. You can find the new name at + * the end of ctx->path[] + * + * LEJPCB_VAL_TRUE: The "true" value appeared + * + * LEJPCB_VAL_FALSE: The "false" value appeared + * + * LEJPCB_VAL_NULL: The "null" value appeared + * + * LEJPCB_VAL_NUM_INT: A string representing an integer is in ctx->buf + * + * LEJPCB_VAL_NUM_FLOAT: A string representing a float is in ctx->buf + * + * LEJPCB_VAL_STR_START: We are starting to parse a string, no data yet + * + * LEJPCB_VAL_STR_CHUNK: We parsed LEJP_STRING_CHUNK -1 bytes of string data in + * ctx->buf, which is as much as we can buffer, so we are + * spilling it. If all your strings are less than + * LEJP_STRING_CHUNK - 1 bytes, you will never see this + * callback. + * + * LEJPCB_VAL_STR_END: String parsing has completed, the last chunk of the + * string is in ctx->buf. + * + * LEJPCB_ARRAY_START: An array started + * + * LEJPCB_ARRAY_END: An array ended + * + * LEJPCB_OBJECT_START: An object started + * + * LEJPCB_OBJECT_END: An object ended + */ +extern char _lejp_callback(struct lejp_ctx *ctx, char reason); + +typedef char (*lejp_callback)(struct lejp_ctx *ctx, char reason); + +#ifndef LEJP_MAX_DEPTH +#define LEJP_MAX_DEPTH 12 +#endif +#ifndef LEJP_MAX_INDEX_DEPTH +#define LEJP_MAX_INDEX_DEPTH 5 +#endif +#ifndef LEJP_MAX_PATH +#define LEJP_MAX_PATH 128 +#endif +#ifndef LEJP_STRING_CHUNK +/* must be >= 30 to assemble floats */ +#define LEJP_STRING_CHUNK 64 +#endif + +enum num_flags { + LEJP_SEEN_MINUS = (1 << 0), + LEJP_SEEN_POINT = (1 << 1), + LEJP_SEEN_POST_POINT = (1 << 2), + LEJP_SEEN_EXP = (1 << 3) +}; + +struct _lejp_stack { + char s; /* lejp_state stack*/ + char p; /* path length */ + char i; /* index array length */ + char b; /* user bitfield */ +}; + +struct lejp_ctx { + + /* sorted by type for most compact alignment + * + * pointers + */ + + char (*callback)(struct lejp_ctx *ctx, char reason); + void *user; + const char * const *paths; + + /* arrays */ + + struct _lejp_stack st[LEJP_MAX_DEPTH]; + unsigned short i[LEJP_MAX_INDEX_DEPTH]; /* index array */ + char path[LEJP_MAX_PATH]; + char buf[LEJP_STRING_CHUNK]; + + /* int */ + + unsigned int line; + + /* short */ + + unsigned short uni; + + /* char */ + + unsigned char npos; + unsigned char dcount; + unsigned char f; + unsigned char sp; /* stack head */ + unsigned char ipos; /* index stack depth */ + unsigned char ppos; + unsigned char count_paths; + unsigned char path_match; + unsigned char path_match_len; +}; + +extern void +lejp_construct(struct lejp_ctx *ctx, + char (*callback)(struct lejp_ctx *ctx, char reason), void *user, + const char * const *paths, unsigned char paths_count); + +extern void +lejp_destruct(struct lejp_ctx *ctx); + +extern int +lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len); + +extern void +lejp_change_callback(struct lejp_ctx *ctx, + char (*callback)(struct lejp_ctx *ctx, char reason)); diff --git a/lwsws/lwsws.h b/lwsws/lwsws.h new file mode 100644 index 0000000..a1936c9 --- /dev/null +++ b/lwsws/lwsws.h @@ -0,0 +1,62 @@ +#if defined(_WIN32) && defined(EXTERNAL_POLL) +#define WINVER 0x0600 +#define _WIN32_WINNT 0x0600 +#define poll(fdArray, fds, timeout) WSAPoll((LPWSAPOLLFD)(fdArray), (ULONG)(fds), (INT)(timeout)) +#endif + +#include "lws_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +#include "../lib/libwebsockets.h" +#include "lejp.h" + +#ifdef _WIN32 +#include +#include "gettimeofday.h" +#else +#include +#include +#include +#endif + +extern void test_server_lock(int care); +extern void test_server_unlock(int care); + +#ifndef __func__ +#define __func__ __FUNCTION__ +#endif + +struct per_session_data__http { + lws_filefd_type fd; +#ifdef LWS_WITH_CGI + struct lws_cgi_args args; +#endif +#if defined(LWS_WITH_CGI) || !defined(LWS_NO_CLIENT) + int reason_bf; +#endif + unsigned int client_finished:1; +}; + +extern int +lwsws_get_config_globals(struct lws_context_creation_info *info, const char *d, + char **config_strings, int *len); + +extern int +lwsws_get_config_vhosts(struct lws_context *context, + struct lws_context_creation_info *info, const char *d, + char **config_strings, int *len); + +extern int +callback_http(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len); diff --git a/lwsws/main.c b/lwsws/main.c new file mode 100644 index 0000000..741fe7a --- /dev/null +++ b/lwsws/main.c @@ -0,0 +1,238 @@ +/* + * libwebsockets web server application + * + * Copyright (C) 2010-2016 Andy Green + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "lwsws.h" + +int debug_level = 7; + +volatile int force_exit = 0; +struct lws_context *context; + +static char *config_dir = "/etc/lwsws/conf.d"; + +/* + * strings and objects from the config file parsing are created here + */ +#define LWSWS_CONFIG_STRING_SIZE (32 * 1024) +char config_strings[LWSWS_CONFIG_STRING_SIZE]; + +/* singlethreaded version --> no locks */ + +void test_server_lock(int care) +{ +} +void test_server_unlock(int care) +{ +} + + +enum demo_protocols { + /* always first */ + PROTOCOL_HTTP = 0, + + /* always last */ + DEMO_PROTOCOL_COUNT +}; + +/* list of supported protocols and callbacks */ + +static struct lws_protocols protocols[] = { + /* first protocol must always be HTTP handler */ + { + "http-only", /* name */ + callback_http, /* callback */ + sizeof (struct per_session_data__http), /* per_session_data_size */ + 0, /* max frame size / rx buffer */ + }, +}; + +void sighandler(int sig) +{ + force_exit = 1; + lws_cancel_service(context); +} + +static const struct lws_extension exts[] = { + { + "permessage-deflate", + lws_extension_callback_pm_deflate, + "permessage-deflate" + }, + { NULL, NULL, NULL /* terminator */ } +}; + +static struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "debug", required_argument, NULL, 'd' }, + { "configdir", required_argument, NULL, 'c' }, +#ifndef LWS_NO_DAEMONIZE + { "daemonize", no_argument, NULL, 'D' }, +#endif + { NULL, 0, 0, 0 } +}; + +#ifdef LWS_USE_LIBUV +void signal_cb(uv_signal_t *watcher, int signum) +{ + lwsl_err("Signal %d caught, exiting...\n", watcher->signum); + switch (watcher->signum) { + case SIGTERM: + case SIGINT: + break; + default: + signal(SIGABRT, SIG_DFL); + abort(); + break; + } + lws_libuv_stop(context); +} +#endif + +int main(int argc, char **argv) +{ + struct lws_context_creation_info info; + char *cs = config_strings; + int opts = 0, cs_len = sizeof(config_strings) - 1; + int n = 0; +#ifndef _WIN32 + int syslog_options = LOG_PID | LOG_PERROR; +#endif +#ifndef LWS_NO_DAEMONIZE + int daemonize = 0; +#endif + + memset(&info, 0, sizeof info); + + while (n >= 0) { + n = getopt_long(argc, argv, "hd:c:D", options, NULL); + if (n < 0) + continue; + switch (n) { +#ifndef LWS_NO_DAEMONIZE + case 'D': + daemonize = 1; + #ifndef _WIN32 + syslog_options &= ~LOG_PERROR; + #endif + break; +#endif + case 'd': + debug_level = atoi(optarg); + break; + case 'c': + strncpy(config_dir, optarg, sizeof(config_dir) - 1); + config_dir[sizeof(config_dir) - 1] = '\0'; + break; + case 'h': + fprintf(stderr, "Usage: lwsws [-c ] " + "[-d ] [-D] [--help]\n"); + exit(1); + } + } + +#if !defined(LWS_NO_DAEMONIZE) && !defined(WIN32) + /* + * normally lock path would be /var/lock/lwsts or similar, to + * simplify getting started without having to take care about + * permissions or running as root, set to /tmp/.lwsts-lock + */ + if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) { + fprintf(stderr, "Failed to daemonize\n"); + return 10; + } +#endif + + signal(SIGINT, sighandler); + +#ifndef _WIN32 + /* we will only try to log things according to our debug_level */ + setlogmask(LOG_UPTO (LOG_DEBUG)); + openlog("lwsws", syslog_options, LOG_DAEMON); +#endif + + lws_set_log_level(debug_level, lwsl_emit_syslog); + + lwsl_notice("lwsws libwebsockets web server - license GPL2.1\n"); + lwsl_notice("(C) Copyright 2010-2016 Andy Green \n"); + + memset(&info, 0, sizeof(info)); + + info.max_http_header_pool = 16; + info.options = opts | LWS_SERVER_OPTION_VALIDATE_UTF8 | + LWS_SERVER_OPTION_EXPLICIT_VHOSTS; +#ifdef LWS_USE_LIBUV + info.options |= LWS_SERVER_OPTION_LIBUV; +#endif + + lwsl_notice("Using config dir: \"%s\"\n", config_dir); + + /* + * first go through the config for creating the outer context + */ + + if (lwsws_get_config_globals(&info, config_dir, &cs, &cs_len)) + goto bail; + + context = lws_create_context(&info); + if (context == NULL) { + lwsl_err("libwebsocket init failed\n"); + return -1; + } + + /* + * then create the vhosts... + * + * protocols and extensions are the global list of possible + * protocols and extensions offered serverwide. The vhosts + * in the config files enable the ones they want to offer + * per vhost. + * + * The first protocol is always included for http support. + */ + + info.protocols = protocols; + info.extensions = exts; + + if (lwsws_get_config_vhosts(context, &info, config_dir, &cs, &cs_len)) + goto bail; + +#ifdef LWS_USE_LIBUV + lws_uv_sigint_cfg(context, 1, signal_cb); + lws_uv_initloop(context, NULL, 0); + lws_libuv_run(context, 0); +#else + + n = 0; + while (n >= 0 && !force_exit) { + n = lws_service(context, 50); + } +#endif + +bail: + lws_context_destroy(context); + lwsl_notice("lwsws exited cleanly\n"); + +#ifndef _WIN32 + closelog(); +#endif + + return 0; +}