From b02f28f6f533127c324401e4b5d1f6a18f7be41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Fri, 27 Jan 2023 19:57:14 +0100 Subject: [PATCH] more doc comments and refactoring --- spritehttpd/Makefile | 10 +- spritehttpd/include/cgi-espfs.h | 66 +++++ spritehttpd/include/cgi-websocket.h | 115 +++++++- spritehttpd/include/httpd-auth.h | 28 +- spritehttpd/include/httpd-espfs.h | 13 - spritehttpd/include/httpd-logging.h | 2 +- spritehttpd/include/httpd-platform.h | 111 +++++++- spritehttpd/include/httpd-routes.h | 54 ++++ spritehttpd/include/httpd-types.h | 9 - spritehttpd/include/httpd-utils.h | 6 + spritehttpd/include/httpd.h | 39 +-- spritehttpd/lib/espfs/espfs.c | 2 +- spritehttpd/lib/espfs/espfs.h | 39 ++- spritehttpd/lib/espfs/espfsformat.h | 2 +- .../src/{httpd-espfs.c => cgi-espfs.c} | 248 ++++++++++-------- spritehttpd/src/cgi-websocket.c | 127 ++++++--- spritehttpd/src/httpd-auth.c | 2 +- spritehttpd/src/httpd-loop.c | 6 +- spritehttpd/src/httpd-utils.c | 1 - spritehttpd/src/httpd.c | 16 +- spritehttpd/src/port/httpd-freertos.c | 4 - spritehttpd/src/port/httpd-posix.c | 6 +- spritehttpd/todo/esphttpclient/httpclient.c | 36 ++- .../todo/{ => esphttpclient}/httpclient.h | 0 spritehttpd/todo/uptime.c | 59 ----- spritehttpd/todo/uptime.h | 22 -- spritehttpd/todo/webpages-espfs.h | 3 - spritehttpd/todo/webpages.espfs.ld | 12 - 28 files changed, 646 insertions(+), 392 deletions(-) create mode 100644 spritehttpd/include/cgi-espfs.h delete mode 100644 spritehttpd/include/httpd-espfs.h create mode 100644 spritehttpd/include/httpd-routes.h rename spritehttpd/src/{httpd-espfs.c => cgi-espfs.c} (62%) rename spritehttpd/todo/{ => esphttpclient}/httpclient.h (100%) delete mode 100755 spritehttpd/todo/uptime.c delete mode 100755 spritehttpd/todo/uptime.h delete mode 100644 spritehttpd/todo/webpages-espfs.h delete mode 100644 spritehttpd/todo/webpages.espfs.ld diff --git a/spritehttpd/Makefile b/spritehttpd/Makefile index d049b8c..04fe2eb 100644 --- a/spritehttpd/Makefile +++ b/spritehttpd/Makefile @@ -21,28 +21,24 @@ LIB_SOURCES = ${PORT_SOURCES} \ src/utils/base64.c \ src/utils/sha1.c \ src/httpd.c \ - src/httpd-espfs.c \ + src/cgi-espfs.c \ src/httpd-auth.c \ src/httpd-utils.c \ src/httpd-loop.c \ src/cgi-websocket.c -LIB_OBJS = ${LIB_SOURCES:.c=.o} +LIB_OBJS = $(LIB_SOURCES:.c=.o) LIB_INCLUDES = -Iinclude -Ilib/heatshrink -Ilib/espfs LIB_CFLAGS = -fPIC -Wall -Wextra -c -Os -ggdb -std=gnu99 -DGIT_HASH='"$(shell git rev-parse --short HEAD)"' -OBJ_DIR=./obj - -OUT_DIR=. - all: $(LIB_FILE) %.o: %.c $(CC) -c $(LIB_INCLUDES) $(CFLAGS) $(LIB_CFLAGS) -o $@ $< -$(LIB_FILE): ${LIB_OBJS} +$(LIB_FILE): $(LIB_OBJS) $(AR) rcs $@ $^ clean: diff --git a/spritehttpd/include/cgi-espfs.h b/spritehttpd/include/cgi-espfs.h new file mode 100644 index 0000000..ccab51a --- /dev/null +++ b/spritehttpd/include/cgi-espfs.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include "httpd-types.h" + +#define HTTPD_ESPFS_TOKEN_LEN 64 + +typedef struct TplData { + /// Connection. Is set to NULL during final clean-up + HttpdConnData *conn; + /// Currently parsed token. Cleared during clean-up + const char *token; + /// Pointer to arbitrary user data that should be freed/cleaned up + /// during clean-up + void *userData; +} TplData; + +/** + * The template substitution callback. + * + * Returns CGI_MORE if more should be sent within the token, CGI_DONE otherwise. + * + * The td.userData pointer can be used to attach arbitrary data to the request. + * If connData is NULL (and token empty), we are doing cleanup - free td.userData, if needed. + */ +typedef httpd_cgi_state (* TplCallback)(TplData *td); + +/** + * Serve a static file from EspFs. The file name is in the first CGI arg. If NULL, the URI is used as a file name. + * + * @param connData + * @return CGI state + */ +httpd_cgi_state cgiEspFsStaticFile(HttpdConnData *connData); + +/** + * Serve a template from EspFs. + * + * - The first arg is the TplCallback replacer function. + * - The second arg is the file name, if given, otherwise the URI is used as a file name + * + * @param conn + * @return CGI state + */ +httpd_cgi_state cgiEspFsTemplate(HttpdConnData *conn); + +/** + * Send template substitution string with known len + * + * @param td - template data + * @param str - string to send + * @param len - string len + * @return 1 = OK + */ +int tplSendN(TplData *td, const char *str, int len); + +/** + * Send template substitution string (use strlen) + * + * @param tpd - template data + * @param str - string to send + * @return 1 = OK + */ +static inline int tplSend(TplData *tpd, const char *str) { + return tplSendN(tpd, str, strlen(str)); +} diff --git a/spritehttpd/include/cgi-websocket.h b/spritehttpd/include/cgi-websocket.h index e26e765..dbce230 100644 --- a/spritehttpd/include/cgi-websocket.h +++ b/spritehttpd/include/cgi-websocket.h @@ -2,32 +2,121 @@ #include "httpd.h" +/** + * Websocket handle + */ +typedef struct Websock Websock; + #define WEBSOCK_FLAG_NONE 0 -#define WEBSOCK_FLAG_MORE (1<<0) //Set if the data is not the final data in the message; more follows -#define WEBSOCK_FLAG_BIN (1<<1) //Set if the data is binary instead of text -#define WEBSOCK_FLAG_CONT (1<<2) // set if this is a continuation frame (after WEBSOCK_FLAG_MORE) +#define WEBSOCK_FLAG_MORE (1<<0) // Set if the data is not the final data in the message; more follows +#define WEBSOCK_FLAG_BIN (1<<1) // Set if the data is binary instead of text +#define WEBSOCK_FLAG_CONT (1<<2) // set if this is a continuation frame (after WEBSOCK_FLAG_MORE) -typedef struct Websock Websock; -typedef struct WebsockPriv WebsockPriv; +/* https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1 */ +typedef enum websock_close_reason { + /// Normal close + WS_CLOSE_OK = 1000, + /// Server shutting down + WS_CLOSE_GONE = 1001, + /// Protocol error + WS_CLOSE_PROTOCOL_ERR = 1002, + /// Data type error (e.g. binary frame on a text socket) + WS_CLOSE_DATA_TYPE_ERR = 1003, + /// Data format error (e.g. malformed UTF-8) + WS_CLOSE_DATA_FORMAT_ERR = 1007, + /// Policy violation of some kind + WS_CLOSE_POLICY_ERR = 1008, + /// Message too big to handle + WS_CLOSE_MESSAGE_TOO_BIG = 1009, + /// Internal server error + WS_CLOSE_SERVER_ERROR = 1011, +} websock_close_reason; +/** Type for the WS connect callback */ typedef void(*WsConnectedCb)(Websock *ws); + +/** Type for the WS receive callback */ typedef void(*WsRecvCb)(Websock *ws, uint8_t *data, size_t len, int flags); + +/** Type for the WS sending done callback */ typedef void(*WsSentCb)(Websock *ws); + +/** Type for the WS close callback */ typedef void(*WsCloseCb)(Websock *ws); +/** Internal websocket struct (opaque) */ +typedef struct WebsockPriv WebsockPriv; + +// TODO convert to container_of and avoid separate malloc for priv + struct Websock { - void *userData; - HttpdConnData *conn; - uint8_t status; - WsRecvCb recvCb; - WsSentCb sentCb; - WsCloseCb closeCb; - WebsockPriv *priv; + /// Pointer to arbitrary user data, put there e.g. during 'recvCb' + void *userData; + /// Connection pointer + HttpdConnData *conn; + /// Receive callback - new data; optional, set by user in WsConnectedCb + WsRecvCb recvCb; + /// Sent callback - last sending finished; optional, set by user in WsConnectedCb + WsSentCb sentCb; + /// Close callback - socket was closed, either by client or server ; optional, set by user in WsConnectedCb + WsCloseCb closeCb; + /// Websocket private data + WebsockPriv *priv; }; +/** + * CGI function for a websocket endpoint + * + * @param connData + * @return CGI state + */ httpd_cgi_state cgiWebsocket(HttpdConnData *connData); + +/** + * Send data to a websocket + * + * @param ws - Websocket handle + * @param data - data to send + * @param len - data len + * @param flags - flags, OR'ed WEBSOCK_FLAG_X constants + * @return 1 = OK + */ int cgiWebsocketSend(Websock *ws, const uint8_t *data, size_t len, int flags); -void cgiWebsocketClose(Websock *ws, int reason); + +/** + * Close a websocket + * + * @param ws - Websocket handle + * @param reason - close reason (2 bytes). Normally 1000 + */ +void cgiWebsocketClose(Websock *ws, websock_close_reason reason); + +/** + * Websocket endpoint receive handler (used in the CGI route) + * + * @param connData - connection + * @param data - bytes received + * @param len - data len + * @return CGI state + */ httpd_cgi_state cgiWebSocketRecv(HttpdConnData *connData, uint8_t *data, size_t len); + +/** + * Broadcast a message to all open websockets at a given endpoint + * + * @param resource - websocket path + * @param data - bytes received + * @param len - data len + * @param flags - OR'ed WEBSOCK_FLAG_X constants + * @return Returns the amount of connections sent to, -1 if sending failed + */ int cgiWebsockBroadcast(const char *resource, const uint8_t *data, size_t len, int flags); + +/** + * Measure websockets backlog (debug telemetry) + * + * @param resource - websocket path + * @param[out] total - the total backlog for the path is stored here + * @param[out] max - the max backlog per websock is stored here + */ void cgiWebsockMeasureBacklog(const char *resource, size_t *total, size_t *max); diff --git a/spritehttpd/include/httpd-auth.h b/spritehttpd/include/httpd-auth.h index 7135b5e..20f938b 100644 --- a/spritehttpd/include/httpd-auth.h +++ b/spritehttpd/include/httpd-auth.h @@ -1,7 +1,7 @@ #pragma once -#include "httpd-types.h" #include +#include "httpd-types.h" #ifndef HTTP_AUTH_REALM #define HTTP_AUTH_REALM "Protected" @@ -12,33 +12,33 @@ #define AUTH_MAX_TOKEN_LEN 128 /** - * Callback type for basic auth. + * Basic auth CGI handler. CGI arg1 is HttpdBasicAuthCb * - * Returns true if the username and password are valid. - * The connData pointer can be used to store session data to e.g. make the authorization persistent. + * @param connData + * @return CGI status */ -typedef bool (* HttpdBasicAuthCb)(HttpdConnData *connData, const char *user, const char *password); +httpd_cgi_state cgiAuthBasic(HttpdConnData *connData); /** - * Callback type for bearer auth. + * Callback type for basic auth. * - * Returns true if the token is valid. + * Returns true if the username and password are valid. * The connData pointer can be used to store session data to e.g. make the authorization persistent. */ -typedef bool (* HttpdBearerAuthCb)(HttpdConnData *connData, const char *token); +typedef bool (* HttpdBasicAuthCb)(HttpdConnData *connData, const char *user, const char *password); /** - * Basic auth CGI handler + * Bearer auth CGI handler. CGI arg1 is HttpdBearerAuthCb * * @param connData * @return CGI status */ -httpd_cgi_state cgiAuthBasic(HttpdConnData *connData); +httpd_cgi_state cgiAuthBearer(HttpdConnData *connData); /** - * Bearer auth CGI handler + * Callback type for bearer auth. * - * @param connData - * @return CGI status + * Returns true if the token is valid. + * The connData pointer can be used to store session data to e.g. make the authorization persistent. */ -httpd_cgi_state cgiAuthBearer(HttpdConnData *connData); +typedef bool (* HttpdBearerAuthCb)(HttpdConnData *connData, const char *token); diff --git a/spritehttpd/include/httpd-espfs.h b/spritehttpd/include/httpd-espfs.h deleted file mode 100644 index da403e2..0000000 --- a/spritehttpd/include/httpd-espfs.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "httpd.h" - -/** - * The template substitution callback. - * Returns CGI_MORE if more should be sent within the token, CGI_DONE otherwise. - */ -typedef httpd_cgi_state (* TplCallback)(HttpdConnData *connData, char *token, void **arg); - -httpd_cgi_state cgiEspFsHook(HttpdConnData *connData); -httpd_cgi_state cgiEspFsTemplate(HttpdConnData *connData); -int tplSend(HttpdConnData *conn, const char *str, int len); diff --git a/spritehttpd/include/httpd-logging.h b/spritehttpd/include/httpd-logging.h index a5b1a18..dc76b59 100755 --- a/spritehttpd/include/httpd-logging.h +++ b/spritehttpd/include/httpd-logging.h @@ -1,7 +1,7 @@ #pragma once -#include "httpd-platform.h" #include +#include "httpd-platform.h" #ifndef VERBOSE_LOGGING #define VERBOSE_LOGGING 1 diff --git a/spritehttpd/include/httpd-platform.h b/spritehttpd/include/httpd-platform.h index 6182429..c3f2c3f 100644 --- a/spritehttpd/include/httpd-platform.h +++ b/spritehttpd/include/httpd-platform.h @@ -6,24 +6,113 @@ #include "httpd-types.h" +// TODO disambiguate by platform? #define httpd_printf(fmt, ...) printf(fmt, ##__VA_ARGS__) -// Prototypes for porting +// (mostly) prototypes for porting -int httpdConnSendData(ConnTypePtr conn, char *buff, int len); -void httpdConnDisconnect(ConnTypePtr conn); -void httpdPlatDisableTimeout(ConnTypePtr conn); -void httpdPlatInit(); -httpd_thread_handle_t* httpdPlatStart(struct httpd_options *opts); -void httpdPlatJoin(httpd_thread_handle_t * handle); +/** + * Lock the server mutex + */ void httpdPlatLock(); + +/** + * Unlock the server mutex + */ void httpdPlatUnlock(); -void* httpdPlatMalloc(size_t len); + +/** + * Allocate memory + * + * @param len - alloc size + * @return pointer to allocated data or NULL + */ +void *httpdPlatMalloc(size_t len); + +/** + * Free allocated data + * + * @param ptr - data pointer + */ void httpdPlatFree(void *ptr); -char* httpdPlatStrdup(const char *s); + +/** + * Delay the current thread + * + * @param ms - time in milliseconds + */ void httpdPlatDelayMs(uint32_t ms); + +/** + * Platform-specific way of terminating the current task + */ void httpdPlatTaskEnd(); -int httpdPlatEspfsRead(void *dest, uint32_t offset, size_t len); +/** + * Low-level send data to a socket + * + * @param conn - connection struct + * @param buff - bytes to send + * @param len - buffer size + * @return 0 on error. TODO allow partial sending via backlog + */ +int httpdConnSendData(ConnTypePtr conn, const uint8_t *buff, size_t len); + +/** + * Disconnect the low-level socket + * + * @param conn - connection struct + */ +void httpdConnDisconnect(ConnTypePtr conn); + +/** + * Disable receive timeout on a socket, if it's implemented on this platform. + * + * @param conn - connection struct + */ +void httpdPlatDisableTimeout(ConnTypePtr conn); + +/** + * Init platform specific stuff for the http server + */ +void httpdPlatInit(); + +/** + * Start the HTTPD server loop + * + * @param opts - server options + * @return join handle + */ +httpd_thread_handle_t *httpdPlatStart(struct httpd_options *opts); + +/** + * Server task function, called by httpdPlatStart inside a thread + * + * @param pvParameters + */ void platHttpServerTask(void *pvParameters); -void* platHttpServerTaskPosix(void *pvParameters); + +/** + * Posix format of the server task function + * + * @param pvParameters + * @return + */ +void *platHttpServerTaskPosix(void *pvParameters); + +/** + * Wait for the server to end + * + * @param handle - server join handle + */ +void httpdPlatJoin(httpd_thread_handle_t *handle); + +/** + * Read from the ESPFS image file. + * + * @param dest destination buffer + * @param offset offset in the filesystem ("address") + * @param len read length + * @return 0 on success + */ +int httpdPlatEspfsRead(void *dest, uint32_t offset, size_t len); diff --git a/spritehttpd/include/httpd-routes.h b/spritehttpd/include/httpd-routes.h new file mode 100644 index 0000000..8c7aa3f --- /dev/null +++ b/spritehttpd/include/httpd-routes.h @@ -0,0 +1,54 @@ +/** + * Macros used to define static routes + */ + +#pragma once + +#include "cgi-websocket.h" + +//A struct describing an url. This is the main struct that's used to send different URL requests to +//different routines. +typedef struct { + const char *url; + cgiSendCallback cgiCb; + const void *cgiArg; + const void *cgiArg2; +} HttpdBuiltInUrl; + +// macros for defining HttpdBuiltInUrl's + +/** Route with a CGI handler and two arguments */ +#define ROUTE_CGI_ARG2(path, handler, arg1, arg2) {(path), (handler), (void *)(arg1), (void *)(arg2)} + +/** Route with a CGI handler and one arguments */ +#define ROUTE_CGI_ARG(path, handler, arg1) ROUTE_CGI_ARG2((path), (handler), (arg1), NULL) + +/** Route with an argument-less CGI handler */ +#define ROUTE_CGI(path, handler) ROUTE_CGI_ARG2((path), (handler), NULL, NULL) + +/** Static file route (file loaded from espfs) */ +#define ROUTE_FILE(path, filepath) ROUTE_CGI_ARG((path), cgiEspFsStaticFile, (const char*)(filepath)) + +/** Static file as a template with a replacer function */ +#define ROUTE_TPL(path, replacer) ROUTE_CGI_ARG((path), cgiEspFsTemplate, (TplCallback)(replacer)) + +/** Static file as a template with a replacer function, taking additional argument connData->cgiArg2 */ +#define ROUTE_TPL_FILE(path, replacer, filepath) ROUTE_CGI_ARG2((path), cgiEspFsTemplate, (TplCallback)(replacer), (filepath)) + +/** Redirect to some URL */ +#define ROUTE_REDIRECT(path, target) ROUTE_CGI_ARG((path), cgiRedirect, (const char*)(target)) + +/** Following routes are basic-auth protected */ +#define ROUTE_BASIC_AUTH(path, passwdFunc) ROUTE_CGI_ARG((path), cgiAuthBasic, (HttpdBasicAuthCb)(passwdFunc)) + +/** Following routes are basic-auth protected */ +#define ROUTE_BEARER_AUTH(path, passwdFunc) ROUTE_CGI_ARG((path), cgiAuthBearer, (HttpdBearerAuthCb)(passwdFunc)) + +/** Websocket endpoint */ +#define ROUTE_WS(path, callback) ROUTE_CGI_ARG((path), cgiWebsocket, (WsConnectedCb)(callback)) + +/** Catch-all filesystem route */ +#define ROUTE_FILESYSTEM() ROUTE_CGI("*", cgiEspFsHook) + +/** Marker for the end of the route list */ +#define ROUTE_END() {NULL, NULL, NULL, NULL} diff --git a/spritehttpd/include/httpd-types.h b/spritehttpd/include/httpd-types.h index a88bd1a..b82ec61 100644 --- a/spritehttpd/include/httpd-types.h +++ b/spritehttpd/include/httpd-types.h @@ -94,12 +94,3 @@ struct HttpdPostData { char *buff; // Actual POST data buffer char *multipartBoundary; //Text of the multipart boundary, if any }; - -//A struct describing an url. This is the main struct that's used to send different URL requests to -//different routines. -typedef struct { - const char *url; - cgiSendCallback cgiCb; - const void *cgiArg; - const void *cgiArg2; -} HttpdBuiltInUrl; diff --git a/spritehttpd/include/httpd-utils.h b/spritehttpd/include/httpd-utils.h index b3d5af8..7c0bc19 100644 --- a/spritehttpd/include/httpd-utils.h +++ b/spritehttpd/include/httpd-utils.h @@ -15,6 +15,12 @@ #define last_char_n(str, n) ((str))[strlen((str)) - (n)] #define last_char(str) last_char_n((str), 1) +#ifndef container_of +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) +#endif + /** * Turn a nibble (0-15) to a hex char. * diff --git a/spritehttpd/include/httpd.h b/spritehttpd/include/httpd.h index e1bad76..7fa89ce 100644 --- a/spritehttpd/include/httpd.h +++ b/spritehttpd/include/httpd.h @@ -7,6 +7,7 @@ #include /* needed for ssize_t */ #include "httpd-platform.h" #include "httpd-types.h" +#include "httpd-routes.h" #ifndef GIT_HASH #define GIT_HASH "unknown" @@ -56,44 +57,6 @@ #define HTTPD_MAX_CONNECTIONS 4 #endif -// macros for defining HttpdBuiltInUrl's - -/** Route with a CGI handler and two arguments */ -#define ROUTE_CGI_ARG2(path, handler, arg1, arg2) {(path), (handler), (void *)(arg1), (void *)(arg2)} - -/** Route with a CGI handler and one arguments */ -#define ROUTE_CGI_ARG(path, handler, arg1) ROUTE_CGI_ARG2((path), (handler), (arg1), NULL) - -/** Route with an argument-less CGI handler */ -#define ROUTE_CGI(path, handler) ROUTE_CGI_ARG2((path), (handler), NULL, NULL) - -/** Static file route (file loaded from espfs) */ -#define ROUTE_FILE(path, filepath) ROUTE_CGI_ARG((path), cgiEspFsHook, (const char*)(filepath)) - -/** Static file as a template with a replacer function */ -#define ROUTE_TPL(path, replacer) ROUTE_CGI_ARG((path), cgiEspFsTemplate, (TplCallback)(replacer)) - -/** Static file as a template with a replacer function, taking additional argument connData->cgiArg2 */ -#define ROUTE_TPL_FILE(path, replacer, filepath) ROUTE_CGI_ARG2((path), cgiEspFsTemplate, (TplCallback)(replacer), (filepath)) - -/** Redirect to some URL */ -#define ROUTE_REDIRECT(path, target) ROUTE_CGI_ARG((path), cgiRedirect, (const char*)(target)) - -/** Following routes are basic-auth protected */ -#define ROUTE_BASIC_AUTH(path, passwdFunc) ROUTE_CGI_ARG((path), cgiAuthBasic, (HttpdBasicAuthCb)(passwdFunc)) - -/** Following routes are basic-auth protected */ -#define ROUTE_BEARER_AUTH(path, passwdFunc) ROUTE_CGI_ARG((path), cgiAuthBearer, (HttpdBearerAuthCb)(passwdFunc)) - -/** Websocket endpoint */ -#define ROUTE_WS(path, callback) ROUTE_CGI_ARG((path), cgiWebsocket, (WsConnectedCb)(callback)) - -/** Catch-all filesystem route */ -#define ROUTE_FILESYSTEM() ROUTE_CGI("*", cgiEspFsHook) - -/** Marker for the end of the route list */ -#define ROUTE_END() {NULL, NULL, NULL, NULL} - /** * Get the server version string * diff --git a/spritehttpd/lib/espfs/espfs.c b/spritehttpd/lib/espfs/espfs.c index 75f7768..81e74f4 100644 --- a/spritehttpd/lib/espfs/espfs.c +++ b/spritehttpd/lib/espfs/espfs.c @@ -248,7 +248,7 @@ EspFsFile *espFsOpen(const char *fileName) } //Read len bytes from the given file into buff. Returns the actual amount of bytes read. -int espFsRead(EspFsFile *fh, uint8_t *buff, size_t buf_cap) +size_t espFsRead(EspFsFile *fh, uint8_t *buff, size_t buf_cap) { int rv = 0; uint32_t binary_len = 0; diff --git a/spritehttpd/lib/espfs/espfs.h b/spritehttpd/lib/espfs/espfs.h index 9ff29dc..8123512 100644 --- a/spritehttpd/lib/espfs/espfs.h +++ b/spritehttpd/lib/espfs/espfs.h @@ -26,6 +26,7 @@ typedef struct EspFsWalk EspFsWalk; * @return 0 = success */ int espFsFileReadHeader(const EspFsFile *file, EspFsHeader *header); + /** Init filesystem walk */ void espFsWalkInit(EspFsWalk *walk); /** @@ -36,16 +37,52 @@ void espFsWalkInit(EspFsWalk *walk); * filepos - the file header's pos is copied here, if not NULL */ bool espFsWalkNext(EspFsWalk *walk, EspFsHeader *header, char *namebuf, size_t namebuf_cap, uint32_t *filepos); + /** Open a file with header starting at the given position */ EspFsFile *espFsOpenAt(uint32_t hpos); + /** * Open a file using an already read header and it's offset. * This is the same function as espFsOpenAt, but avoids reading the header again if already read. */ EspFsFile *espFsOpenFromHeader(EspFsHeader *h, uint32_t hpos); +/** + * Init the filesystem global state + * + * @return + */ EspFsInitResult espFsInit(); + +/** + * Open a file + * + * @param fileName + * @return File handle or null + */ EspFsFile *espFsOpen(const char *fileName); + +/** + * Read file flags + * + * @param fh - file handle + * @return flags + */ int espFsFlags(EspFsFile *fh); -int espFsRead(EspFsFile *fh, uint8_t *buff, size_t len); + +/** + * Read from an open file + * + * @param fh - file handle + * @param[out] buff - output buffer + * @param len - read len + * @return real length of read bytes + */ +size_t espFsRead(EspFsFile *fh, uint8_t *buff, size_t len); + +/** + * Close a file + * + * @param fh - file handle + */ void espFsClose(EspFsFile *fh); diff --git a/spritehttpd/lib/espfs/espfsformat.h b/spritehttpd/lib/espfs/espfsformat.h index 739867c..204c13c 100644 --- a/spritehttpd/lib/espfs/espfsformat.h +++ b/spritehttpd/lib/espfs/espfsformat.h @@ -12,7 +12,7 @@ with the FLAG_LASTFILE flag set. #define FLAG_GZIP (1<<1) #define COMPRESS_NONE 0 #define COMPRESS_HEATSHRINK 1 -#define ESPFS_MAGIC 0x73665345 /* ASCII sequence of bytes 'E' 'S' 'f' 's' interpreted as as little endian uint32_t */ +#define ESPFS_MAGIC 0x73665345 /* ASCII sequence of bytes 'E' 'S' 'f' 's' interpreted as a little endian uint32_t */ /* 16 bytes long for alignment */ typedef struct { diff --git a/spritehttpd/src/httpd-espfs.c b/spritehttpd/src/cgi-espfs.c similarity index 62% rename from spritehttpd/src/httpd-espfs.c rename to spritehttpd/src/cgi-espfs.c index 791219f..cdf9693 100644 --- a/spritehttpd/src/httpd-espfs.c +++ b/spritehttpd/src/cgi-espfs.c @@ -15,11 +15,11 @@ Connector to let httpd use the espfs filesystem to serve the files in it. #include #include "httpd.h" #include "httpd-platform.h" -#include "httpd-espfs.h" -#include "espfs.h" -#include "espfsformat.h" +#include "cgi-espfs.h" #include "httpd-logging.h" #include "httpd-utils.h" +#include "espfs.h" +#include "espfsformat.h" #define FILE_CHUNK_LEN 1024 @@ -82,8 +82,7 @@ EspFsFile *tryOpenIndex(const char *path) return NULL; // failed to guess the right name } -httpd_cgi_state -serveStaticFile(HttpdConnData *connData, const char *filepath) +static httpd_cgi_state serveStaticFile(HttpdConnData *connData, const char *filepath) { EspFsFile *file = connData->cgiData; int len; @@ -165,7 +164,7 @@ serveStaticFile(HttpdConnData *connData, const char *filepath) //This is a catch-all cgi function. It takes the url passed to it, looks up the corresponding //path in the filesystem and if it exists, passes the file through. This simulates what a normal //webserver would do with static files. -httpd_cgi_state cgiEspFsHook(HttpdConnData *connData) +httpd_cgi_state cgiEspFsStaticFile(HttpdConnData *connData) { const char *filepath = (connData->cgiArg == NULL) ? connData->url : (char *) connData->cgiArg; return serveStaticFile(connData, filepath); @@ -182,11 +181,14 @@ typedef enum { typedef struct { EspFsFile *file; - void *tplArg; - char token[64]; + + // this is the struct passed to user + TplData td; + int tokenPos; char buff[FILE_CHUNK_LEN + 1]; + char token[HTTPD_ESPFS_TOKEN_LEN]; bool chunk_resume; int buff_len; @@ -194,99 +196,111 @@ typedef struct { int buff_sp; char *buff_e; TplEncode tokEncode; -} TplData; +} TplDataInternal; -int tplSend(HttpdConnData *conn, const char *str, int len) +int tplSendN(TplData *td, const char *str, int len) { - if (conn == NULL) { return 0; } - TplData *tpd = conn->cgiData; - - if (tpd == NULL || tpd->tokEncode == ENCODE_PLAIN) { - return httpdSendStrN(conn, str, len); - } else if (tpd->tokEncode == ENCODE_HTML) { - return httpdSend_html(conn, str, len); - } else if (tpd->tokEncode == ENCODE_JS) { - return httpdSend_js(conn, str, len); + if (td == NULL) { return 0; } + TplDataInternal *tdi = container_of(td, TplDataInternal, td); + + if (tdi->tokEncode == ENCODE_PLAIN) { + return httpdSendStrN(td->conn, str, len); + } else if (tdi->tokEncode == ENCODE_HTML) { + return httpdSend_html(td->conn, str, len); + } else if (tdi->tokEncode == ENCODE_JS) { + return httpdSend_js(td->conn, str, len); } return 0; } -httpd_cgi_state cgiEspFsTemplate(HttpdConnData *connData) +httpd_cgi_state cgiEspFsTemplate(HttpdConnData *conn) { - TplData *tpd = connData->cgiData; + TplDataInternal *tdi = conn->cgiData; + int len; int x, sp = 0; char *e = NULL; - int tokOfs; - if (connData->conn == NULL) { + if (conn->conn == NULL) { //Connection aborted. Clean up. - ((TplCallback) (connData->cgiArg))(connData, NULL, &tpd->tplArg); - espFsClose(tpd->file); - httpdPlatFree(tpd); + if (tdi) { + TplCallback callback = (TplCallback) conn->cgiArg; + if (callback) { + tdi->td.conn = NULL; // indicate that this is the final call + tdi->td.token = NULL; + callback(&tdi->td); + } + + espFsClose(tdi->file); + httpdPlatFree(tdi); + } return HTTPD_CGI_DONE; } - if (tpd == NULL) { + if (tdi == NULL) { //First call to this cgi. Open the file so we can read it. - tpd = (TplData *) httpdPlatMalloc(sizeof(TplData)); - if (tpd == NULL) { + tdi = (TplDataInternal *) httpdPlatMalloc(sizeof(TplDataInternal)); + if (tdi == NULL) { espfs_error("Failed to malloc tpl struct"); return HTTPD_CGI_NOTFOUND; } - tpd->chunk_resume = false; + tdi->chunk_resume = false; - const char *filepath = connData->url; + const char *filepath = conn->url; // check for custom template URL - if (connData->cgiArg2 != NULL) { - filepath = connData->cgiArg2; + if (conn->cgiArg2 != NULL) { + filepath = conn->cgiArg2; espfs_dbg("Using filepath %s", filepath); } - tpd->file = espFsOpen(filepath); + tdi->file = espFsOpen(filepath); - if (tpd->file == NULL) { + if (tdi->file == NULL) { // maybe a folder, look for index file - tpd->file = tryOpenIndex(filepath); - if (tpd->file == NULL) { - httpdPlatFree(tpd); + tdi->file = tryOpenIndex(filepath); + if (tdi->file == NULL) { + espfs_error("cgiEspFsTemplate: Couldn't find template for path: %s", conn->url); + httpdPlatFree(tdi); return HTTPD_CGI_NOTFOUND; } } - tpd->tplArg = NULL; - tpd->tokenPos = -1; - if (espFsFlags(tpd->file) & FLAG_GZIP) { - espfs_error("cgiEspFsTemplate: Trying to use gzip-compressed file %s as template!", connData->url); - espFsClose(tpd->file); - httpdPlatFree(tpd); + if (espFsFlags(tdi->file) & FLAG_GZIP) { + espfs_error("cgiEspFsTemplate: Trying to use gzip-compressed file %s as template!", conn->url); + espFsClose(tdi->file); + httpdPlatFree(tdi); return HTTPD_CGI_NOTFOUND; } - connData->cgiData = tpd; - httpdStartResponse(connData, 200); - const char *mime = httpdGetMimetype(connData->url); - httpdHeader(connData, "Content-Type", mime); - httpdAddCacheHeaders(connData, mime); - httpdEndHeaders(connData); + + tdi->td.conn = conn; + tdi->td.userData = NULL; + + tdi->tokenPos = -1; + conn->cgiData = tdi; + httpdStartResponse(conn, 200); + const char *mime = httpdGetMimetype(conn->url); + httpdHeader(conn, "Content-Type", mime); + httpdAddCacheHeaders(conn, mime); + httpdEndHeaders(conn); return HTTPD_CGI_MORE; } - char *buff = tpd->buff; + char *buff = tdi->buff; // resume the parser state from the last token, // if subst. func wants more data to be sent. - if (tpd->chunk_resume) { + if (tdi->chunk_resume) { //espfs_dbg("Resuming tpl parser for multi-part subst"); - len = tpd->buff_len; - e = tpd->buff_e; - sp = tpd->buff_sp; - x = tpd->buff_x; + len = tdi->buff_len; + e = tdi->buff_e; + sp = tdi->buff_sp; + x = tdi->buff_x; } else { - len = espFsRead(tpd->file, (uint8_t *) buff, FILE_CHUNK_LEN); - tpd->buff_len = len; + len = espFsRead(tdi->file, (uint8_t *) buff, FILE_CHUNK_LEN); + tdi->buff_len = len; e = buff; sp = 0; @@ -295,111 +309,113 @@ httpd_cgi_state cgiEspFsTemplate(HttpdConnData *connData) if (len > 0) { for (; x < len; x++) { - if (tpd->tokenPos == -1) { + char c = buff[x]; + + if (tdi->tokenPos == -1) { //Inside ordinary text. - if (buff[x] == '%') { + if (c == '%') { //Send raw data up to now - if (sp != 0) { httpdSendStrN(connData, e, sp); } + if (sp != 0) { httpdSendStrN(conn, e, sp); } sp = 0; //Go collect token chars. - tpd->tokenPos = 0; + tdi->tokenPos = 0; } else { sp++; } } else { - if (buff[x] == '%') { - if (tpd->tokenPos == 0) { + if (c == '%') { + if (tdi->tokenPos == 0) { //This is the second % of a %% escape string. //Send a single % and resume with the normal program flow. - httpdSendStrN(connData, "%", 1); + httpdSendStrN(conn, "%", 1); } else { - if (!tpd->chunk_resume) { + if (!tdi->chunk_resume) { //This is an actual token. - tpd->token[tpd->tokenPos++] = 0; //zero-terminate token - - tokOfs = 0; - tpd->tokEncode = ENCODE_PLAIN; - if (strneq(tpd->token, "html:", 5)) { - tokOfs = 5; - tpd->tokEncode = ENCODE_HTML; - } else if (strneq(tpd->token, "h:", 2)) { - tokOfs = 2; - tpd->tokEncode = ENCODE_HTML; - } else if (strneq(tpd->token, "js:", 3)) { - tokOfs = 3; - tpd->tokEncode = ENCODE_JS; - } else if (strneq(tpd->token, "j:", 2)) { - tokOfs = 2; - tpd->tokEncode = ENCODE_JS; + tdi->token[tdi->tokenPos++] = 0; //zero-terminate token + + int prefixLen = 0; + tdi->tokEncode = ENCODE_PLAIN; + if (strneq(tdi->token, "html:", 5)) { + prefixLen = 5; + tdi->tokEncode = ENCODE_HTML; + } else if (strneq(tdi->token, "h:", 2)) { + prefixLen = 2; + tdi->tokEncode = ENCODE_HTML; + } else if (strneq(tdi->token, "js:", 3)) { + prefixLen = 3; + tdi->tokEncode = ENCODE_JS; + } else if (strneq(tdi->token, "j:", 2)) { + prefixLen = 2; + tdi->tokEncode = ENCODE_JS; } - // do the shifting - if (tokOfs > 0) { - for (int i = tokOfs; i <= tpd->tokenPos; i++) { - tpd->token[i - tokOfs] = tpd->token[i]; - } - } + tdi->td.token = &tdi->token[prefixLen]; } - tpd->chunk_resume = false; + tdi->chunk_resume = false; - httpd_cgi_state status = ((TplCallback) (connData->cgiArg))(connData, tpd->token, &tpd->tplArg); + TplCallback callback = (TplCallback) conn->cgiArg; + httpd_cgi_state status = callback(&tdi->td); if (status == HTTPD_CGI_MORE) { // espfs_dbg("Multi-part tpl subst, saving parser state"); // wants to send more in this token's place..... - tpd->chunk_resume = true; - tpd->buff_len = len; - tpd->buff_e = e; - tpd->buff_sp = sp; - tpd->buff_x = x; + tdi->chunk_resume = true; + tdi->buff_len = len; + tdi->buff_e = e; + tdi->buff_sp = sp; + tdi->buff_x = x; break; } } //Go collect normal chars again. e = &buff[x + 1]; - tpd->tokenPos = -1; + tdi->tokenPos = -1; } else { // Add char to the token buf - char c = buff[x]; - bool outOfSpace = tpd->tokenPos >= ((int) sizeof(tpd->token) - 1); - if (outOfSpace || - (!(c >= 'a' && c <= 'z') && - !(c >= 'A' && c <= 'Z') && - !(c >= '0' && c <= '9') && - c != '.' && c != '_' && c != '-' && c != ':' - )) { - // looks like we collected some garbage - httpdSendStrN(connData, "%", 1); - if (tpd->tokenPos > 0) { - httpdSendStrN(connData, tpd->token, tpd->tokenPos); + bool outOfSpace = tdi->tokenPos >= ((int) sizeof(tdi->token) - 1); + if (outOfSpace) { + // looks like we collected some garbage, put it back + httpdSendStrN(conn, "%", 1); + if (tdi->tokenPos > 0) { + httpdSendStrN(conn, tdi->token, tdi->tokenPos); } // the bad char - httpdSendStrN(connData, &c, 1); + httpdSendStrN(conn, &c, 1); //Go collect normal chars again. e = &buff[x + 1]; - tpd->tokenPos = -1; + tdi->tokenPos = -1; } else { // collect it - tpd->token[tpd->tokenPos++] = c; + tdi->token[tdi->tokenPos++] = c; } } } } } - if (tpd->chunk_resume) { + if (tdi->chunk_resume) { return HTTPD_CGI_MORE; } //Send remaining bit. - if (sp != 0) { httpdSendStrN(connData, e, sp); } + if (sp != 0) { + httpdSendStrN(conn, e, sp); + } + if (len != FILE_CHUNK_LEN) { //We're done. - ((TplCallback) (connData->cgiArg))(connData, NULL, &tpd->tplArg); + + TplCallback callback = (TplCallback) conn->cgiArg; + if (callback) { + tdi->td.conn = NULL; + tdi->td.token = NULL; + callback(&tdi->td); + } + espfs_info("Template sent."); - espFsClose(tpd->file); - httpdPlatFree(tpd); + espFsClose(tdi->file); + httpdPlatFree(tdi); return HTTPD_CGI_DONE; } else { //Ok, till next time. diff --git a/spritehttpd/src/cgi-websocket.c b/spritehttpd/src/cgi-websocket.c index 1b1a47b..4e38e0a 100644 --- a/spritehttpd/src/cgi-websocket.c +++ b/spritehttpd/src/cgi-websocket.c @@ -47,16 +47,16 @@ Websocket support for esphttpd. Inspired by https://github.com/dangrie158/ESP-82 #define FLAG_FIN (1 << 7) #define OPCODE_CONTINUE 0x0 -#define OPCODE_TEXT 0x1 +#define OPCODE_TEXT 0x1 #define OPCODE_BINARY 0x2 -#define OPCODE_CLOSE 0x8 -#define OPCODE_PING 0x9 -#define OPCODE_PONG 0xA +#define OPCODE_CLOSE 0x8 +#define OPCODE_PING 0x9 +#define OPCODE_PONG 0xA -#define FLAGS_MASK ((uint8_t)0xF0) -#define OPCODE_MASK ((uint8_t)0x0F) -#define IS_MASKED ((uint8_t)(1<<7)) -#define PAYLOAD_MASK ((uint8_t)0x7F) +#define FLAGS_MASK ((uint8_t) 0xF0) +#define OPCODE_MASK ((uint8_t) 0x0F) +#define IS_MASKED ((uint8_t) 0x80) +#define PAYLOAD_MASK ((uint8_t) 0x7F) typedef struct WebsockFrame WebsockFrame; @@ -70,18 +70,26 @@ typedef struct WebsockFrame WebsockFrame; #define ST_MASK4 13 #define ST_PAYLOAD 14 +/// Parsed last frame struct WebsockFrame { uint8_t flags; + /// Short len; 127 or 126 indicates the longer len field is used. + /// Bit 7 is the IS_MASKED flag uint8_t len8; uint64_t len; uint8_t mask[4]; }; struct WebsockPriv { + /// Parsed last frame - incomplete before the parsing finishes, which can span multiple tcp frames struct WebsockFrame fr; + /// Mask counter - used to cycle through mask bytes uint8_t maskCtr; + /// True if the current frame isn't fully received yet and more data will come uint8_t frameCont; + /// True if we initiated the close handshake - this prevents a close frame loop uint8_t closedHere; + /// One of the ST_* constants int wsStatus; Websock *next; //in linked list }; @@ -129,7 +137,9 @@ int cgiWebsocketSend(Websock *ws, const uint8_t *data, size_t len, int flags) // add FIN to last frame if (!(flags & WEBSOCK_FLAG_MORE)) { fl |= FLAG_FIN; } sendFrameHead(ws, fl, len); - if (len != 0) { r = httpdSend(ws->conn, data, len); } + if (len != 0) { + r = httpdSend(ws->conn, data, len); + } r &= httpdFlushSendBuffer(ws->conn); return r; } @@ -178,13 +188,14 @@ void cgiWebsockMeasureBacklog(const char *resource, size_t *total, size_t *max) bTotal += bs; if (bs > bMax) { bMax = bs; } } + lw = lw->priv->next; } *total = bTotal; *max = bMax; } -void cgiWebsocketClose(Websock *ws, int reason) +void cgiWebsocketClose(Websock *ws, websock_close_reason reason) { uint8_t rs[2] = {reason >> 8, reason & 0xff}; sendFrameHead(ws, FLAG_FIN | OPCODE_CLOSE, 2); @@ -197,17 +208,28 @@ void cgiWebsocketClose(Websock *ws, int reason) static void websockFree(Websock *ws) { ws_dbg("Ws: Free"); - if (ws->closeCb) { ws->closeCb(ws); } + if (ws->closeCb) { + ws->closeCb(ws); + } + //Clean up linked list if (llStart == ws) { llStart = ws->priv->next; } else if (llStart) { Websock *lws = llStart; //Find ws that links to this one. - while (lws != NULL && lws->priv->next != ws) { lws = lws->priv->next; } - if (lws != NULL) { lws->priv->next = ws->priv->next; } + while (lws != NULL && lws->priv->next != ws) { + lws = lws->priv->next; + } + + if (lws != NULL) { + lws->priv->next = ws->priv->next; + } + } + + if (ws->priv) { + httpdPlatFree(ws->priv); } - if (ws->priv) { httpdPlatFree(ws->priv); } } httpd_cgi_state cgiWebSocketRecv(HttpdConnData *connData, uint8_t *data, size_t len) @@ -253,6 +275,7 @@ httpd_cgi_state cgiWebSocketRecv(HttpdConnData *connData, uint8_t *data, size_t //the payload code works as usual. i++; } + //Also finish parsing frame if we haven't received any payload bytes yet, but the length of the frame //is zero. if (ws->priv->wsStatus == ST_PAYLOAD) { @@ -260,9 +283,15 @@ httpd_cgi_state cgiWebSocketRecv(HttpdConnData *connData, uint8_t *data, size_t //received here at the same time; no more byte iterations till the end of this frame. //First, unmask the data sl = len - i; -// ws_dbg("Ws: Frame payload. wasHeaderByte %d fr.len %d sl %d cmd 0x%x", wasHeaderByte, (int)ws->priv->fr.len, (int)sl, ws->priv->fr.flags); - if ((uint64_t) sl > ws->priv->fr.len) { sl = (int) ws->priv->fr.len; } - for (j = 0; j < sl; j++) { data[i + j] ^= (ws->priv->fr.mask[(ws->priv->maskCtr++) & 3]); } + // ws_dbg("Ws: Frame payload. wasHeaderByte %d fr.len %d sl %d cmd 0x%x", wasHeaderByte, (int)ws->priv->fr.len, (int)sl, ws->priv->fr.flags); + + if ((uint64_t) sl > ws->priv->fr.len) { + sl = (int) ws->priv->fr.len; + } + + for (j = 0; j < sl; j++) { + data[i + j] ^= (ws->priv->fr.mask[(ws->priv->maskCtr++) & 3]); + } // if (DEBUG_WS) { // ws_dbg("Unmasked: "); @@ -273,33 +302,53 @@ httpd_cgi_state cgiWebSocketRecv(HttpdConnData *connData, uint8_t *data, size_t //Inspect the header to see what we need to do. if ((ws->priv->fr.flags & OPCODE_MASK) == OPCODE_PING) { if (ws->priv->fr.len > 125) { - if (!ws->priv->frameCont) { cgiWebsocketClose(ws, 1002); } + if (!ws->priv->frameCont) { + cgiWebsocketClose(ws, WS_CLOSE_PROTOCOL_ERR); + } r = HTTPD_CGI_DONE; break; } else { - if (!ws->priv->frameCont) { sendFrameHead(ws, OPCODE_PONG | FLAG_FIN, ws->priv->fr.len); } - if (sl > 0) { httpdSend(ws->conn, data + i, sl); } + if (!ws->priv->frameCont) { + sendFrameHead(ws, OPCODE_PONG | FLAG_FIN, ws->priv->fr.len); + } + + if (sl > 0) { + httpdSend(ws->conn, data + i, sl); + } } } else if ((ws->priv->fr.flags & OPCODE_MASK) == OPCODE_TEXT || (ws->priv->fr.flags & OPCODE_MASK) == OPCODE_BINARY || (ws->priv->fr.flags & OPCODE_MASK) == OPCODE_CONTINUE) { - if ((uint64_t) sl > ws->priv->fr.len) { sl = ws->priv->fr.len; } + + if ((uint64_t) sl > ws->priv->fr.len) { + sl = ws->priv->fr.len; + } + if (!(ws->priv->fr.len8 & IS_MASKED)) { //We're a server; client should send us masked packets. - cgiWebsocketClose(ws, 1002); + cgiWebsocketClose(ws, WS_CLOSE_PROTOCOL_ERR); r = HTTPD_CGI_DONE; break; } else { int flags = 0; - if ((ws->priv->fr.flags & OPCODE_MASK) == OPCODE_BINARY) { flags |= WEBSOCK_FLAG_BIN; } - if ((ws->priv->fr.flags & FLAG_FIN) == 0) { flags |= WEBSOCK_FLAG_MORE; } - if (ws->recvCb) { ws->recvCb(ws, data + i, sl, flags); } + + if ((ws->priv->fr.flags & OPCODE_MASK) == OPCODE_BINARY) { + flags |= WEBSOCK_FLAG_BIN; + } + + if ((ws->priv->fr.flags & FLAG_FIN) == 0) { + flags |= WEBSOCK_FLAG_MORE; + } + + if (ws->recvCb) { + ws->recvCb(ws, data + i, sl, flags); + } } } else if ((ws->priv->fr.flags & OPCODE_MASK) == OPCODE_CLOSE) { // ws_dbg("WS: Got close frame"); if (!ws->priv->closedHere) { // ws_dbg("WS: Sending response close frame, %x %x (i=%d, len=%d)", data[i], data[i+1], i, len); - int cause = 1000; + int cause = WS_CLOSE_OK; if (i <= len - 2) { cause = ((data[i] << 8) & 0xff00) + (data[i + 1] & 0xff); } @@ -308,10 +357,14 @@ httpd_cgi_state cgiWebSocketRecv(HttpdConnData *connData, uint8_t *data, size_t r = HTTPD_CGI_DONE; break; } else { - if (!ws->priv->frameCont) ws_error("WS: Unknown opcode 0x%X", ws->priv->fr.flags & OPCODE_MASK); + if (!ws->priv->frameCont) { + ws_error("WS: Unknown opcode 0x%X", ws->priv->fr.flags & OPCODE_MASK); + } } + i += sl - 1; ws->priv->fr.len -= sl; + if (ws->priv->fr.len == 0) { ws->priv->wsStatus = ST_FLAGS; //go receive next frame } else { @@ -355,6 +408,12 @@ httpd_cgi_state cgiWebsocket(HttpdConnData *connData) if (i && strcasecmp(buff, "websocket") == 0) { i = httpdGetHeader(connData, "Sec-WebSocket-Key", buff, sizeof(buff) - 1); if (i) { + WsConnectedCb connCb = connData->cgiArg; + if (!connCb) { + ws_error("WS route missing connCb!"); + return HTTPD_CGI_DONE; + } + // httpd_printf("WS: Key: %s\n", buff); //Seems like a WebSocket connection. // Alloc structs @@ -364,7 +423,9 @@ httpd_cgi_state cgiWebsocket(HttpdConnData *connData) return HTTPD_CGI_DONE; } memset(connData->cgiData, 0, sizeof(Websock)); + Websock *ws = (Websock *) connData->cgiData; + ws->priv = httpdPlatMalloc(sizeof(WebsockPriv)); if (ws->priv == NULL) { ws_error("Can't allocate mem for websocket priv"); @@ -372,8 +433,10 @@ httpd_cgi_state cgiWebsocket(HttpdConnData *connData) connData->cgiData = NULL; return HTTPD_CGI_DONE; } + memset(ws->priv, 0, sizeof(WebsockPriv)); ws->conn = connData; + //Reply with the right headers. strcat(buff, WS_GUID); httpd_sha1_init(&s); @@ -388,14 +451,16 @@ httpd_cgi_state cgiWebsocket(HttpdConnData *connData) //Set data receive handler connData->recvHdl = cgiWebSocketRecv; //Inform CGI function we have a connection - WsConnectedCb connCb = connData->cgiArg; connCb(ws); + //Insert ws into linked list if (llStart == NULL) { llStart = ws; } else { Websock *lw = llStart; - while (lw->priv->next) { lw = lw->priv->next; } + while (lw->priv->next) { + lw = lw->priv->next; + } lw->priv->next = ws; } return HTTPD_CGI_MORE; @@ -409,7 +474,9 @@ httpd_cgi_state cgiWebsocket(HttpdConnData *connData) //Sending is done. Call the sent callback if we have one. Websock *ws = (Websock *) connData->cgiData; - if (ws && ws->sentCb) { ws->sentCb(ws); } + if (ws && ws->sentCb) { + ws->sentCb(ws); + } return HTTPD_CGI_MORE; } diff --git a/spritehttpd/src/httpd-auth.c b/spritehttpd/src/httpd-auth.c index 4b94c04..7595483 100644 --- a/spritehttpd/src/httpd-auth.c +++ b/spritehttpd/src/httpd-auth.c @@ -11,8 +11,8 @@ HTTP auth implementation. Only does basic authentication for now. * ---------------------------------------------------------------------------- */ -#include "httpd-auth.h" #include "httpd.h" +#include "httpd-auth.h" #include "utils/base64.h" #include diff --git a/spritehttpd/src/httpd-loop.c b/spritehttpd/src/httpd-loop.c index d3d7be2..2ca2c2a 100644 --- a/spritehttpd/src/httpd-loop.c +++ b/spritehttpd/src/httpd-loop.c @@ -32,13 +32,13 @@ static int fd_is_valid(int fd) return fcntl(fd, F_GETFD) != -1 || errno != EBADF; } -int httpdConnSendData(ConnTypePtr conn, char *buff, int len) +int httpdConnSendData(ConnTypePtr conn, const uint8_t *buff, size_t len) { conn->needWriteDoneNotif = 1; if (!fd_is_valid(conn->fd)) { - return -1; + return 0; } - return (write(conn->fd, buff, len) >= 0); + return write(conn->fd, buff, len) == (int) len; } void httpdConnDisconnect(ConnTypePtr conn) diff --git a/spritehttpd/src/httpd-utils.c b/spritehttpd/src/httpd-utils.c index f534d50..8921a6a 100644 --- a/spritehttpd/src/httpd-utils.c +++ b/spritehttpd/src/httpd-utils.c @@ -1,5 +1,4 @@ #include "httpd-utils.h" -#include "httpd.h" #include "httpd-logging.h" char httpdHexNibble(uint8_t val) diff --git a/spritehttpd/src/httpd.c b/spritehttpd/src/httpd.c index 66d089e..43a1bb3 100644 --- a/spritehttpd/src/httpd.c +++ b/spritehttpd/src/httpd.c @@ -45,7 +45,7 @@ struct HttpdPriv { char head[HTTPD_MAX_HEAD_LEN]; char corsToken[HTTPD_MAX_CORS_TOKEN_LEN]; int headPos; - char *sendBuff; + uint8_t *sendBuff; int sendBuffLen; char *chunkHdr; HttpSendBacklogItem *sendBacklog; @@ -332,7 +332,7 @@ int httpdSend(HttpdConnData *conn, const uint8_t *data, size_t len) if (conn->priv->flags & HFL_CHUNKED && conn->priv->flags & HFL_SENDINGBODY && conn->priv->chunkHdr == NULL) { if (conn->priv->sendBuffLen + len + 6 > HTTPD_MAX_SENDBUFF_LEN) { return 0; } //Establish start of chunk - conn->priv->chunkHdr = &conn->priv->sendBuff[conn->priv->sendBuffLen]; + conn->priv->chunkHdr = (char *) &conn->priv->sendBuff[conn->priv->sendBuffLen]; strcpy(conn->priv->chunkHdr, "0000\r\n"); conn->priv->sendBuffLen += 6; } @@ -424,7 +424,7 @@ bool httpdFlushSendBuffer(HttpdConnData *conn) //Finish chunk with cr/lf httpdSendStr(conn, "\r\n"); //Calculate length of chunk - len = ((&conn->priv->sendBuff[conn->priv->sendBuffLen]) - conn->priv->chunkHdr) - 8; + len = ((char *)(&conn->priv->sendBuff[conn->priv->sendBuffLen]) - conn->priv->chunkHdr) - 8; //Fix up chunk header to correct value conn->priv->chunkHdr[0] = httpdHexNibble(len >> 12); conn->priv->chunkHdr[1] = httpdHexNibble(len >> 8); @@ -435,7 +435,7 @@ bool httpdFlushSendBuffer(HttpdConnData *conn) } if (conn->priv->flags & HFL_CHUNKED && conn->priv->flags & HFL_SENDINGBODY && conn->cgi == NULL) { //Connection finished sending whatever needs to be sent. Add NULL chunk to indicate this. - strcpy(&conn->priv->sendBuff[conn->priv->sendBuffLen], "0\r\n\r\n"); + strcpy((char *) &conn->priv->sendBuff[conn->priv->sendBuffLen], "0\r\n\r\n"); conn->priv->sendBuffLen += 5; } if (conn->priv->sendBuffLen != 0) { @@ -505,14 +505,14 @@ void httpdContinue(HttpdConnData *conn) int r; httpdPlatLock(); - char *sendBuff; + uint8_t *sendBuff; if (conn == NULL) { return; } if (conn->priv->sendBacklog != NULL) { //We have some backlog to send first. HttpSendBacklogItem *next = conn->priv->sendBacklog->next; - httpdConnSendData(conn->conn, conn->priv->sendBacklog->data, conn->priv->sendBacklog->len); + httpdConnSendData(conn->conn, (uint8_t *) conn->priv->sendBacklog->data, conn->priv->sendBacklog->len); conn->priv->sendBacklogSize -= conn->priv->sendBacklog->len; httpdPlatFree(conn->priv->sendBacklog); conn->priv->sendBacklog = next; @@ -750,7 +750,7 @@ static void httpdParseHeader(char *h, HttpdConnData *conn) void httpdConnSendStart(HttpdConnData *conn) { httpdPlatLock(); - char *sendBuff = httpdPlatMalloc(HTTPD_MAX_SENDBUFF_LEN); + uint8_t *sendBuff = httpdPlatMalloc(HTTPD_MAX_SENDBUFF_LEN); if (sendBuff == NULL) { http_error("Malloc sendBuff failed!"); return; @@ -780,7 +780,7 @@ void httpdRecvCb(ConnTypePtr rconn, const char *remIp, int remPort, uint8_t *dat return; } - char *sendBuff = httpdPlatMalloc(HTTPD_MAX_SENDBUFF_LEN); + uint8_t *sendBuff = httpdPlatMalloc(HTTPD_MAX_SENDBUFF_LEN); if (sendBuff == NULL) { http_error("Malloc sendBuff failed!"); httpdPlatUnlock(); diff --git a/spritehttpd/src/port/httpd-freertos.c b/spritehttpd/src/port/httpd-freertos.c index 55bae32..f08373b 100644 --- a/spritehttpd/src/port/httpd-freertos.c +++ b/spritehttpd/src/port/httpd-freertos.c @@ -50,7 +50,3 @@ void httpdPlatFree(void *ptr) { free(ptr); } - -char * httpdPlatStrdup(const char *s) { - return strdup(s); // FIXME -} diff --git a/spritehttpd/src/port/httpd-posix.c b/spritehttpd/src/port/httpd-posix.c index 6f209ab..9864e2c 100644 --- a/spritehttpd/src/port/httpd-posix.c +++ b/spritehttpd/src/port/httpd-posix.c @@ -25,7 +25,7 @@ void httpdPlatDelayMs(uint32_t ms) void httpdPlatTaskEnd() { - // TODO + // Nothing special needed with pthreads } void httpdPlatDisableTimeout(ConnTypePtr conn) @@ -79,7 +79,3 @@ void httpdPlatFree(void *ptr) { free(ptr); } - -char * httpdPlatStrdup(const char *s) { - return strdup(s); -} diff --git a/spritehttpd/todo/esphttpclient/httpclient.c b/spritehttpd/todo/esphttpclient/httpclient.c index 8823dc0..c216a1a 100644 --- a/spritehttpd/todo/esphttpclient/httpclient.c +++ b/spritehttpd/todo/esphttpclient/httpclient.c @@ -9,12 +9,10 @@ // FIXME: sprintf->snprintf everywhere. -#include -#include -#include - +#include +#include +#include #include "httpclient.h" -#include "esp_utils.h" // Internal state. typedef struct { @@ -34,7 +32,7 @@ typedef struct { void *userData; } request_args; -static int ICACHE_FLASH_ATTR +static int chunked_decode(char *chunked, int size) { char *src = chunked; @@ -64,7 +62,7 @@ chunked_decode(char *chunked, int size) return dst; } -static void ICACHE_FLASH_ATTR +static void receive_callback(void *arg, char *buf, unsigned short len) { struct espconn *conn = (struct espconn *) arg; @@ -126,7 +124,7 @@ receive_callback(void *arg, char *buf, unsigned short len) } -static void ICACHE_FLASH_ATTR +static void sent_callback(void *arg) { struct espconn *conn = (struct espconn *) arg; @@ -151,7 +149,7 @@ sent_callback(void *arg) } } -static void ICACHE_FLASH_ATTR +static void connect_callback(void *arg) { httpc_info("Connected!"); @@ -228,7 +226,7 @@ free_req(request_args *req) free(req); } -static void ICACHE_FLASH_ATTR +static void disconnect_callback(void *arg) { httpc_dbg("Disconnected"); @@ -296,7 +294,7 @@ disconnect_callback(void *arg) free(conn); } -static void ICACHE_FLASH_ATTR +static void error_callback(void *arg, sint8 errType) { (void) errType; @@ -305,7 +303,7 @@ error_callback(void *arg, sint8 errType) disconnect_callback(arg); } -static void ICACHE_FLASH_ATTR +static void http_timeout_callback(void *arg) { httpc_error("Connection timeout\n"); @@ -341,7 +339,7 @@ http_timeout_callback(void *arg) free(conn); } -static void ICACHE_FLASH_ATTR +static void dns_callback(const char *hostname, ip_addr_t *addr, void *arg) { (void)hostname; @@ -388,7 +386,7 @@ dns_callback(const char *hostname, ip_addr_t *addr, void *arg) } } -bool ICACHE_FLASH_ATTR +bool http_request(const httpclient_args *args, httpclient_cb user_callback) { // --- prepare port, secure... --- @@ -502,7 +500,7 @@ void httpclient_args_init(httpclient_args *args) } -bool ICACHE_FLASH_ATTR +bool http_post(const char *url, const char *body, void *userData, httpclient_cb user_callback) { httpclient_args args; @@ -517,7 +515,7 @@ http_post(const char *url, const char *body, void *userData, httpclient_cb user_ } -bool ICACHE_FLASH_ATTR +bool http_get(const char *url, void *userData, httpclient_cb user_callback) { httpclient_args args; @@ -531,7 +529,7 @@ http_get(const char *url, void *userData, httpclient_cb user_callback) } -bool ICACHE_FLASH_ATTR +bool http_put(const char *url, const char *body, void *userData, httpclient_cb user_callback) { httpclient_args args; @@ -546,7 +544,7 @@ http_put(const char *url, const char *body, void *userData, httpclient_cb user_c } -void ICACHE_FLASH_ATTR +void http_callback_example(int http_status, char *response_headers, char *response_body, @@ -562,7 +560,7 @@ http_callback_example(int http_status, } -void ICACHE_FLASH_ATTR +void http_callback_showstatus(int code, char *response_headers, char *response_body, diff --git a/spritehttpd/todo/httpclient.h b/spritehttpd/todo/esphttpclient/httpclient.h similarity index 100% rename from spritehttpd/todo/httpclient.h rename to spritehttpd/todo/esphttpclient/httpclient.h diff --git a/spritehttpd/todo/uptime.c b/spritehttpd/todo/uptime.c deleted file mode 100755 index d145863..0000000 --- a/spritehttpd/todo/uptime.c +++ /dev/null @@ -1,59 +0,0 @@ -#include - -volatile uint32_t uptime = 0; - -static ETSTimer prUptimeTimer; - -static void uptimeTimerCb(void *arg) -{ - uptime++; -} - -void uptime_timer_init(void) -{ - os_timer_disarm(&prUptimeTimer); - os_timer_setfn(&prUptimeTimer, uptimeTimerCb, NULL); - os_timer_arm(&prUptimeTimer, 1000, 1); -} - -void uptime_str(char *buf) -{ - u32 a = uptime; - u32 days = a / 86400; - a -= days * 86400; - - u32 hours = a / 3600; - a -= hours * 3600; - - u32 mins = a / 60; - a -= mins * 60; - - u32 secs = a; - - if (days > 0) { - sprintf(buf, "%ud %02u:%02u:%02u", days, hours, mins, secs); - } else { - sprintf(buf, "%02u:%02u:%02u", hours, mins, secs); - } -} - -void uptime_print(void) -{ - u32 a = uptime; - u32 days = a / 86400; - a -= days * 86400; - - u32 hours = a / 3600; - a -= hours * 3600; - - u32 mins = a / 60; - a -= mins * 60; - - u32 secs = a; - - if (days > 0) { - printf("%ud %02u:%02u:%02u", days, hours, mins, secs); - } else { - printf("%02u:%02u:%02u", hours, mins, secs); - } -} diff --git a/spritehttpd/todo/uptime.h b/spritehttpd/todo/uptime.h deleted file mode 100755 index e53bb12..0000000 --- a/spritehttpd/todo/uptime.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef UPTIME_H -#define UPTIME_H - -#include - -extern volatile uint32_t uptime; - -/** - * Initialize the virtual timer for uptime counter. - */ -void uptime_timer_init(void); - -/** - * Print uptime to a buffer in user-friendly format. - * Should be at least 20 bytes long. - */ -void uptime_str(char *buf); - -/** Print uptime to stdout in user-friendly format */ -void uptime_print(void); - -#endif // UPTIME_H diff --git a/spritehttpd/todo/webpages-espfs.h b/spritehttpd/todo/webpages-espfs.h deleted file mode 100644 index e6889d7..0000000 --- a/spritehttpd/todo/webpages-espfs.h +++ /dev/null @@ -1,3 +0,0 @@ -extern char webpages_espfs_start[]; -extern char webpages_espfs_end[]; -extern int webpages_espfs_size; diff --git a/spritehttpd/todo/webpages.espfs.ld b/spritehttpd/todo/webpages.espfs.ld deleted file mode 100644 index 0968d4b..0000000 --- a/spritehttpd/todo/webpages.espfs.ld +++ /dev/null @@ -1,12 +0,0 @@ -OUTPUT_FORMAT("elf32-xtensa-le") - - -SECTIONS -{ - .irom0.literal : ALIGN(4) SUBALIGN(4) { - webpages_espfs_start = .; - *(*) - webpages_espfs_end = .; - webpages_espfs_size = webpages_espfs_end - webpages_espfs_start; - } -} \ No newline at end of file