more doc comments and refactoring

master
Ondřej Hruška 2 years ago
parent deb2fe6d93
commit b02f28f6f5
  1. 10
      spritehttpd/Makefile
  2. 66
      spritehttpd/include/cgi-espfs.h
  3. 97
      spritehttpd/include/cgi-websocket.h
  4. 28
      spritehttpd/include/httpd-auth.h
  5. 13
      spritehttpd/include/httpd-espfs.h
  6. 2
      spritehttpd/include/httpd-logging.h
  7. 107
      spritehttpd/include/httpd-platform.h
  8. 54
      spritehttpd/include/httpd-routes.h
  9. 9
      spritehttpd/include/httpd-types.h
  10. 6
      spritehttpd/include/httpd-utils.h
  11. 39
      spritehttpd/include/httpd.h
  12. 2
      spritehttpd/lib/espfs/espfs.c
  13. 39
      spritehttpd/lib/espfs/espfs.h
  14. 2
      spritehttpd/lib/espfs/espfsformat.h
  15. 244
      spritehttpd/src/cgi-espfs.c
  16. 111
      spritehttpd/src/cgi-websocket.c
  17. 2
      spritehttpd/src/httpd-auth.c
  18. 6
      spritehttpd/src/httpd-loop.c
  19. 1
      spritehttpd/src/httpd-utils.c
  20. 16
      spritehttpd/src/httpd.c
  21. 4
      spritehttpd/src/port/httpd-freertos.c
  22. 6
      spritehttpd/src/port/httpd-posix.c
  23. 36
      spritehttpd/todo/esphttpclient/httpclient.c
  24. 0
      spritehttpd/todo/esphttpclient/httpclient.h
  25. 59
      spritehttpd/todo/uptime.c
  26. 22
      spritehttpd/todo/uptime.h
  27. 3
      spritehttpd/todo/webpages-espfs.h
  28. 12
      spritehttpd/todo/webpages.espfs.ld

@ -21,28 +21,24 @@ LIB_SOURCES = ${PORT_SOURCES} \
src/utils/base64.c \ src/utils/base64.c \
src/utils/sha1.c \ src/utils/sha1.c \
src/httpd.c \ src/httpd.c \
src/httpd-espfs.c \ src/cgi-espfs.c \
src/httpd-auth.c \ src/httpd-auth.c \
src/httpd-utils.c \ src/httpd-utils.c \
src/httpd-loop.c \ src/httpd-loop.c \
src/cgi-websocket.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_INCLUDES = -Iinclude -Ilib/heatshrink -Ilib/espfs
LIB_CFLAGS = -fPIC -Wall -Wextra -c -Os -ggdb -std=gnu99 -DGIT_HASH='"$(shell git rev-parse --short HEAD)"' 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) all: $(LIB_FILE)
%.o: %.c %.o: %.c
$(CC) -c $(LIB_INCLUDES) $(CFLAGS) $(LIB_CFLAGS) -o $@ $< $(CC) -c $(LIB_INCLUDES) $(CFLAGS) $(LIB_CFLAGS) -o $@ $<
$(LIB_FILE): ${LIB_OBJS} $(LIB_FILE): $(LIB_OBJS)
$(AR) rcs $@ $^ $(AR) rcs $@ $^
clean: clean:

@ -0,0 +1,66 @@
#pragma once
#include <string.h>
#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));
}

@ -2,32 +2,121 @@
#include "httpd.h" #include "httpd.h"
/**
* Websocket handle
*/
typedef struct Websock Websock;
#define WEBSOCK_FLAG_NONE 0 #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_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_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_CONT (1<<2) // set if this is a continuation frame (after WEBSOCK_FLAG_MORE)
typedef struct Websock Websock; /* https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1 */
typedef struct WebsockPriv WebsockPriv; 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); typedef void(*WsConnectedCb)(Websock *ws);
/** Type for the WS receive callback */
typedef void(*WsRecvCb)(Websock *ws, uint8_t *data, size_t len, int flags); 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); typedef void(*WsSentCb)(Websock *ws);
/** Type for the WS close callback */
typedef void(*WsCloseCb)(Websock *ws); 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 { struct Websock {
/// Pointer to arbitrary user data, put there e.g. during 'recvCb'
void *userData; void *userData;
/// Connection pointer
HttpdConnData *conn; HttpdConnData *conn;
uint8_t status; /// Receive callback - new data; optional, set by user in WsConnectedCb
WsRecvCb recvCb; WsRecvCb recvCb;
/// Sent callback - last sending finished; optional, set by user in WsConnectedCb
WsSentCb sentCb; WsSentCb sentCb;
/// Close callback - socket was closed, either by client or server ; optional, set by user in WsConnectedCb
WsCloseCb closeCb; WsCloseCb closeCb;
/// Websocket private data
WebsockPriv *priv; WebsockPriv *priv;
}; };
/**
* CGI function for a websocket endpoint
*
* @param connData
* @return CGI state
*/
httpd_cgi_state cgiWebsocket(HttpdConnData *connData); 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); 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); 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); 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); void cgiWebsockMeasureBacklog(const char *resource, size_t *total, size_t *max);

@ -1,7 +1,7 @@
#pragma once #pragma once
#include "httpd-types.h"
#include <stdbool.h> #include <stdbool.h>
#include "httpd-types.h"
#ifndef HTTP_AUTH_REALM #ifndef HTTP_AUTH_REALM
#define HTTP_AUTH_REALM "Protected" #define HTTP_AUTH_REALM "Protected"
@ -12,33 +12,33 @@
#define AUTH_MAX_TOKEN_LEN 128 #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. * @param connData
* The connData pointer can be used to store session data to e.g. make the authorization persistent. * @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. * 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 * @param connData
* @return CGI status * @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 * Returns true if the token is valid.
* @return CGI status * 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);

@ -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);

@ -1,7 +1,7 @@
#pragma once #pragma once
#include "httpd-platform.h"
#include <stdio.h> #include <stdio.h>
#include "httpd-platform.h"
#ifndef VERBOSE_LOGGING #ifndef VERBOSE_LOGGING
#define VERBOSE_LOGGING 1 #define VERBOSE_LOGGING 1

@ -6,24 +6,113 @@
#include "httpd-types.h" #include "httpd-types.h"
// TODO disambiguate by platform?
#define httpd_printf(fmt, ...) printf(fmt, ##__VA_ARGS__) #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); * Lock the server mutex
void httpdPlatDisableTimeout(ConnTypePtr conn); */
void httpdPlatInit();
httpd_thread_handle_t* httpdPlatStart(struct httpd_options *opts);
void httpdPlatJoin(httpd_thread_handle_t * handle);
void httpdPlatLock(); void httpdPlatLock();
/**
* Unlock the server mutex
*/
void httpdPlatUnlock(); void httpdPlatUnlock();
/**
* Allocate memory
*
* @param len - alloc size
* @return pointer to allocated data or NULL
*/
void *httpdPlatMalloc(size_t len); void *httpdPlatMalloc(size_t len);
/**
* Free allocated data
*
* @param ptr - data pointer
*/
void httpdPlatFree(void *ptr); void httpdPlatFree(void *ptr);
char* httpdPlatStrdup(const char *s);
/**
* Delay the current thread
*
* @param ms - time in milliseconds
*/
void httpdPlatDelayMs(uint32_t ms); void httpdPlatDelayMs(uint32_t ms);
/**
* Platform-specific way of terminating the current task
*/
void httpdPlatTaskEnd(); 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 platHttpServerTask(void *pvParameters);
/**
* Posix format of the server task function
*
* @param pvParameters
* @return
*/
void *platHttpServerTaskPosix(void *pvParameters); 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);

@ -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}

@ -94,12 +94,3 @@ struct HttpdPostData {
char *buff; // Actual POST data buffer char *buff; // Actual POST data buffer
char *multipartBoundary; //Text of the multipart boundary, if any 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;

@ -15,6 +15,12 @@
#define last_char_n(str, n) ((str))[strlen((str)) - (n)] #define last_char_n(str, n) ((str))[strlen((str)) - (n)]
#define last_char(str) last_char_n((str), 1) #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. * Turn a nibble (0-15) to a hex char.
* *

@ -7,6 +7,7 @@
#include <sys/types.h> /* needed for ssize_t */ #include <sys/types.h> /* needed for ssize_t */
#include "httpd-platform.h" #include "httpd-platform.h"
#include "httpd-types.h" #include "httpd-types.h"
#include "httpd-routes.h"
#ifndef GIT_HASH #ifndef GIT_HASH
#define GIT_HASH "unknown" #define GIT_HASH "unknown"
@ -56,44 +57,6 @@
#define HTTPD_MAX_CONNECTIONS 4 #define HTTPD_MAX_CONNECTIONS 4
#endif #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 * Get the server version string
* *

@ -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. //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; int rv = 0;
uint32_t binary_len = 0; uint32_t binary_len = 0;

@ -26,6 +26,7 @@ typedef struct EspFsWalk EspFsWalk;
* @return 0 = success * @return 0 = success
*/ */
int espFsFileReadHeader(const EspFsFile *file, EspFsHeader *header); int espFsFileReadHeader(const EspFsFile *file, EspFsHeader *header);
/** Init filesystem walk */ /** Init filesystem walk */
void espFsWalkInit(EspFsWalk *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 * 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); 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 */ /** Open a file with header starting at the given position */
EspFsFile *espFsOpenAt(uint32_t hpos); EspFsFile *espFsOpenAt(uint32_t hpos);
/** /**
* Open a file using an already read header and it's offset. * 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. * This is the same function as espFsOpenAt, but avoids reading the header again if already read.
*/ */
EspFsFile *espFsOpenFromHeader(EspFsHeader *h, uint32_t hpos); EspFsFile *espFsOpenFromHeader(EspFsHeader *h, uint32_t hpos);
/**
* Init the filesystem global state
*
* @return
*/
EspFsInitResult espFsInit(); EspFsInitResult espFsInit();
/**
* Open a file
*
* @param fileName
* @return File handle or null
*/
EspFsFile *espFsOpen(const char *fileName); EspFsFile *espFsOpen(const char *fileName);
/**
* Read file flags
*
* @param fh - file handle
* @return flags
*/
int espFsFlags(EspFsFile *fh); 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); void espFsClose(EspFsFile *fh);

@ -12,7 +12,7 @@ with the FLAG_LASTFILE flag set.
#define FLAG_GZIP (1<<1) #define FLAG_GZIP (1<<1)
#define COMPRESS_NONE 0 #define COMPRESS_NONE 0
#define COMPRESS_HEATSHRINK 1 #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 */ /* 16 bytes long for alignment */
typedef struct { typedef struct {

@ -15,11 +15,11 @@ Connector to let httpd use the espfs filesystem to serve the files in it.
#include <string.h> #include <string.h>
#include "httpd.h" #include "httpd.h"
#include "httpd-platform.h" #include "httpd-platform.h"
#include "httpd-espfs.h" #include "cgi-espfs.h"
#include "espfs.h"
#include "espfsformat.h"
#include "httpd-logging.h" #include "httpd-logging.h"
#include "httpd-utils.h" #include "httpd-utils.h"
#include "espfs.h"
#include "espfsformat.h"
#define FILE_CHUNK_LEN 1024 #define FILE_CHUNK_LEN 1024
@ -82,8 +82,7 @@ EspFsFile *tryOpenIndex(const char *path)
return NULL; // failed to guess the right name return NULL; // failed to guess the right name
} }
httpd_cgi_state static httpd_cgi_state serveStaticFile(HttpdConnData *connData, const char *filepath)
serveStaticFile(HttpdConnData *connData, const char *filepath)
{ {
EspFsFile *file = connData->cgiData; EspFsFile *file = connData->cgiData;
int len; 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 //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 //path in the filesystem and if it exists, passes the file through. This simulates what a normal
//webserver would do with static files. //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; const char *filepath = (connData->cgiArg == NULL) ? connData->url : (char *) connData->cgiArg;
return serveStaticFile(connData, filepath); return serveStaticFile(connData, filepath);
@ -182,11 +181,14 @@ typedef enum {
typedef struct { typedef struct {
EspFsFile *file; EspFsFile *file;
void *tplArg;
char token[64]; // this is the struct passed to user
TplData td;
int tokenPos; int tokenPos;
char buff[FILE_CHUNK_LEN + 1]; char buff[FILE_CHUNK_LEN + 1];
char token[HTTPD_ESPFS_TOKEN_LEN];
bool chunk_resume; bool chunk_resume;
int buff_len; int buff_len;
@ -194,99 +196,111 @@ typedef struct {
int buff_sp; int buff_sp;
char *buff_e; char *buff_e;
TplEncode tokEncode; 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; } if (td == NULL) { return 0; }
TplData *tpd = conn->cgiData; TplDataInternal *tdi = container_of(td, TplDataInternal, td);
if (tpd == NULL || tpd->tokEncode == ENCODE_PLAIN) { if (tdi->tokEncode == ENCODE_PLAIN) {
return httpdSendStrN(conn, str, len); return httpdSendStrN(td->conn, str, len);
} else if (tpd->tokEncode == ENCODE_HTML) { } else if (tdi->tokEncode == ENCODE_HTML) {
return httpdSend_html(conn, str, len); return httpdSend_html(td->conn, str, len);
} else if (tpd->tokEncode == ENCODE_JS) { } else if (tdi->tokEncode == ENCODE_JS) {
return httpdSend_js(conn, str, len); return httpdSend_js(td->conn, str, len);
} }
return 0; 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 len;
int x, sp = 0; int x, sp = 0;
char *e = NULL; char *e = NULL;
int tokOfs;
if (connData->conn == NULL) { if (conn->conn == NULL) {
//Connection aborted. Clean up. //Connection aborted. Clean up.
((TplCallback) (connData->cgiArg))(connData, NULL, &tpd->tplArg); if (tdi) {
espFsClose(tpd->file); TplCallback callback = (TplCallback) conn->cgiArg;
httpdPlatFree(tpd); 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; return HTTPD_CGI_DONE;
} }
if (tpd == NULL) { if (tdi == NULL) {
//First call to this cgi. Open the file so we can read it. //First call to this cgi. Open the file so we can read it.
tpd = (TplData *) httpdPlatMalloc(sizeof(TplData)); tdi = (TplDataInternal *) httpdPlatMalloc(sizeof(TplDataInternal));
if (tpd == NULL) { if (tdi == NULL) {
espfs_error("Failed to malloc tpl struct"); espfs_error("Failed to malloc tpl struct");
return HTTPD_CGI_NOTFOUND; 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 // check for custom template URL
if (connData->cgiArg2 != NULL) { if (conn->cgiArg2 != NULL) {
filepath = connData->cgiArg2; filepath = conn->cgiArg2;
espfs_dbg("Using filepath %s", filepath); 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 // maybe a folder, look for index file
tpd->file = tryOpenIndex(filepath); tdi->file = tryOpenIndex(filepath);
if (tpd->file == NULL) { if (tdi->file == NULL) {
httpdPlatFree(tpd); espfs_error("cgiEspFsTemplate: Couldn't find template for path: %s", conn->url);
httpdPlatFree(tdi);
return HTTPD_CGI_NOTFOUND; return HTTPD_CGI_NOTFOUND;
} }
} }
tpd->tplArg = NULL; if (espFsFlags(tdi->file) & FLAG_GZIP) {
tpd->tokenPos = -1; espfs_error("cgiEspFsTemplate: Trying to use gzip-compressed file %s as template!", conn->url);
if (espFsFlags(tpd->file) & FLAG_GZIP) { espFsClose(tdi->file);
espfs_error("cgiEspFsTemplate: Trying to use gzip-compressed file %s as template!", connData->url); httpdPlatFree(tdi);
espFsClose(tpd->file);
httpdPlatFree(tpd);
return HTTPD_CGI_NOTFOUND; return HTTPD_CGI_NOTFOUND;
} }
connData->cgiData = tpd;
httpdStartResponse(connData, 200); tdi->td.conn = conn;
const char *mime = httpdGetMimetype(connData->url); tdi->td.userData = NULL;
httpdHeader(connData, "Content-Type", mime);
httpdAddCacheHeaders(connData, mime); tdi->tokenPos = -1;
httpdEndHeaders(connData); 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; return HTTPD_CGI_MORE;
} }
char *buff = tpd->buff; char *buff = tdi->buff;
// resume the parser state from the last token, // resume the parser state from the last token,
// if subst. func wants more data to be sent. // 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"); //espfs_dbg("Resuming tpl parser for multi-part subst");
len = tpd->buff_len; len = tdi->buff_len;
e = tpd->buff_e; e = tdi->buff_e;
sp = tpd->buff_sp; sp = tdi->buff_sp;
x = tpd->buff_x; x = tdi->buff_x;
} else { } else {
len = espFsRead(tpd->file, (uint8_t *) buff, FILE_CHUNK_LEN); len = espFsRead(tdi->file, (uint8_t *) buff, FILE_CHUNK_LEN);
tpd->buff_len = len; tdi->buff_len = len;
e = buff; e = buff;
sp = 0; sp = 0;
@ -295,111 +309,113 @@ httpd_cgi_state cgiEspFsTemplate(HttpdConnData *connData)
if (len > 0) { if (len > 0) {
for (; x < len; x++) { for (; x < len; x++) {
if (tpd->tokenPos == -1) { char c = buff[x];
if (tdi->tokenPos == -1) {
//Inside ordinary text. //Inside ordinary text.
if (buff[x] == '%') { if (c == '%') {
//Send raw data up to now //Send raw data up to now
if (sp != 0) { httpdSendStrN(connData, e, sp); } if (sp != 0) { httpdSendStrN(conn, e, sp); }
sp = 0; sp = 0;
//Go collect token chars. //Go collect token chars.
tpd->tokenPos = 0; tdi->tokenPos = 0;
} else { } else {
sp++; sp++;
} }
} else { } else {
if (buff[x] == '%') { if (c == '%') {
if (tpd->tokenPos == 0) { if (tdi->tokenPos == 0) {
//This is the second % of a %% escape string. //This is the second % of a %% escape string.
//Send a single % and resume with the normal program flow. //Send a single % and resume with the normal program flow.
httpdSendStrN(connData, "%", 1); httpdSendStrN(conn, "%", 1);
} else { } else {
if (!tpd->chunk_resume) { if (!tdi->chunk_resume) {
//This is an actual token. //This is an actual token.
tpd->token[tpd->tokenPos++] = 0; //zero-terminate token tdi->token[tdi->tokenPos++] = 0; //zero-terminate token
tokOfs = 0; int prefixLen = 0;
tpd->tokEncode = ENCODE_PLAIN; tdi->tokEncode = ENCODE_PLAIN;
if (strneq(tpd->token, "html:", 5)) { if (strneq(tdi->token, "html:", 5)) {
tokOfs = 5; prefixLen = 5;
tpd->tokEncode = ENCODE_HTML; tdi->tokEncode = ENCODE_HTML;
} else if (strneq(tpd->token, "h:", 2)) { } else if (strneq(tdi->token, "h:", 2)) {
tokOfs = 2; prefixLen = 2;
tpd->tokEncode = ENCODE_HTML; tdi->tokEncode = ENCODE_HTML;
} else if (strneq(tpd->token, "js:", 3)) { } else if (strneq(tdi->token, "js:", 3)) {
tokOfs = 3; prefixLen = 3;
tpd->tokEncode = ENCODE_JS; tdi->tokEncode = ENCODE_JS;
} else if (strneq(tpd->token, "j:", 2)) { } else if (strneq(tdi->token, "j:", 2)) {
tokOfs = 2; prefixLen = 2;
tpd->tokEncode = ENCODE_JS; tdi->tokEncode = ENCODE_JS;
} }
// do the shifting tdi->td.token = &tdi->token[prefixLen];
if (tokOfs > 0) {
for (int i = tokOfs; i <= tpd->tokenPos; i++) {
tpd->token[i - tokOfs] = tpd->token[i];
}
}
} }
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) { if (status == HTTPD_CGI_MORE) {
// espfs_dbg("Multi-part tpl subst, saving parser state"); // espfs_dbg("Multi-part tpl subst, saving parser state");
// wants to send more in this token's place..... // wants to send more in this token's place.....
tpd->chunk_resume = true; tdi->chunk_resume = true;
tpd->buff_len = len; tdi->buff_len = len;
tpd->buff_e = e; tdi->buff_e = e;
tpd->buff_sp = sp; tdi->buff_sp = sp;
tpd->buff_x = x; tdi->buff_x = x;
break; break;
} }
} }
//Go collect normal chars again. //Go collect normal chars again.
e = &buff[x + 1]; e = &buff[x + 1];
tpd->tokenPos = -1; tdi->tokenPos = -1;
} else { } else {
// Add char to the token buf // Add char to the token buf
char c = buff[x]; bool outOfSpace = tdi->tokenPos >= ((int) sizeof(tdi->token) - 1);
bool outOfSpace = tpd->tokenPos >= ((int) sizeof(tpd->token) - 1); if (outOfSpace) {
if (outOfSpace || // looks like we collected some garbage, put it back
(!(c >= 'a' && c <= 'z') && httpdSendStrN(conn, "%", 1);
!(c >= 'A' && c <= 'Z') && if (tdi->tokenPos > 0) {
!(c >= '0' && c <= '9') && httpdSendStrN(conn, tdi->token, tdi->tokenPos);
c != '.' && c != '_' && c != '-' && c != ':'
)) {
// looks like we collected some garbage
httpdSendStrN(connData, "%", 1);
if (tpd->tokenPos > 0) {
httpdSendStrN(connData, tpd->token, tpd->tokenPos);
} }
// the bad char // the bad char
httpdSendStrN(connData, &c, 1); httpdSendStrN(conn, &c, 1);
//Go collect normal chars again. //Go collect normal chars again.
e = &buff[x + 1]; e = &buff[x + 1];
tpd->tokenPos = -1; tdi->tokenPos = -1;
} else { } else {
// collect it // collect it
tpd->token[tpd->tokenPos++] = c; tdi->token[tdi->tokenPos++] = c;
} }
} }
} }
} }
} }
if (tpd->chunk_resume) { if (tdi->chunk_resume) {
return HTTPD_CGI_MORE; return HTTPD_CGI_MORE;
} }
//Send remaining bit. //Send remaining bit.
if (sp != 0) { httpdSendStrN(connData, e, sp); } if (sp != 0) {
httpdSendStrN(conn, e, sp);
}
if (len != FILE_CHUNK_LEN) { if (len != FILE_CHUNK_LEN) {
//We're done. //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."); espfs_info("Template sent.");
espFsClose(tpd->file); espFsClose(tdi->file);
httpdPlatFree(tpd); httpdPlatFree(tdi);
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} else { } else {
//Ok, till next time. //Ok, till next time.

@ -55,7 +55,7 @@ Websocket support for esphttpd. Inspired by https://github.com/dangrie158/ESP-82
#define FLAGS_MASK ((uint8_t) 0xF0) #define FLAGS_MASK ((uint8_t) 0xF0)
#define OPCODE_MASK ((uint8_t) 0x0F) #define OPCODE_MASK ((uint8_t) 0x0F)
#define IS_MASKED ((uint8_t)(1<<7)) #define IS_MASKED ((uint8_t) 0x80)
#define PAYLOAD_MASK ((uint8_t) 0x7F) #define PAYLOAD_MASK ((uint8_t) 0x7F)
typedef struct WebsockFrame WebsockFrame; typedef struct WebsockFrame WebsockFrame;
@ -70,18 +70,26 @@ typedef struct WebsockFrame WebsockFrame;
#define ST_MASK4 13 #define ST_MASK4 13
#define ST_PAYLOAD 14 #define ST_PAYLOAD 14
/// Parsed last frame
struct WebsockFrame { struct WebsockFrame {
uint8_t flags; 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; uint8_t len8;
uint64_t len; uint64_t len;
uint8_t mask[4]; uint8_t mask[4];
}; };
struct WebsockPriv { struct WebsockPriv {
/// Parsed last frame - incomplete before the parsing finishes, which can span multiple tcp frames
struct WebsockFrame fr; struct WebsockFrame fr;
/// Mask counter - used to cycle through mask bytes
uint8_t maskCtr; uint8_t maskCtr;
/// True if the current frame isn't fully received yet and more data will come
uint8_t frameCont; uint8_t frameCont;
/// True if we initiated the close handshake - this prevents a close frame loop
uint8_t closedHere; uint8_t closedHere;
/// One of the ST_* constants
int wsStatus; int wsStatus;
Websock *next; //in linked list 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 // add FIN to last frame
if (!(flags & WEBSOCK_FLAG_MORE)) { fl |= FLAG_FIN; } if (!(flags & WEBSOCK_FLAG_MORE)) { fl |= FLAG_FIN; }
sendFrameHead(ws, fl, len); 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); r &= httpdFlushSendBuffer(ws->conn);
return r; return r;
} }
@ -178,13 +188,14 @@ void cgiWebsockMeasureBacklog(const char *resource, size_t *total, size_t *max)
bTotal += bs; bTotal += bs;
if (bs > bMax) { bMax = bs; } if (bs > bMax) { bMax = bs; }
} }
lw = lw->priv->next; lw = lw->priv->next;
} }
*total = bTotal; *total = bTotal;
*max = bMax; *max = bMax;
} }
void cgiWebsocketClose(Websock *ws, int reason) void cgiWebsocketClose(Websock *ws, websock_close_reason reason)
{ {
uint8_t rs[2] = {reason >> 8, reason & 0xff}; uint8_t rs[2] = {reason >> 8, reason & 0xff};
sendFrameHead(ws, FLAG_FIN | OPCODE_CLOSE, 2); sendFrameHead(ws, FLAG_FIN | OPCODE_CLOSE, 2);
@ -197,17 +208,28 @@ void cgiWebsocketClose(Websock *ws, int reason)
static void websockFree(Websock *ws) static void websockFree(Websock *ws)
{ {
ws_dbg("Ws: Free"); ws_dbg("Ws: Free");
if (ws->closeCb) { ws->closeCb(ws); } if (ws->closeCb) {
ws->closeCb(ws);
}
//Clean up linked list //Clean up linked list
if (llStart == ws) { if (llStart == ws) {
llStart = ws->priv->next; llStart = ws->priv->next;
} else if (llStart) { } else if (llStart) {
Websock *lws = llStart; Websock *lws = llStart;
//Find ws that links to this one. //Find ws that links to this one.
while (lws != NULL && lws->priv->next != ws) { lws = lws->priv->next; } while (lws != NULL && lws->priv->next != ws) {
if (lws != NULL) { lws->priv->next = ws->priv->next; } 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) 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. //the payload code works as usual.
i++; i++;
} }
//Also finish parsing frame if we haven't received any payload bytes yet, but the length of the frame //Also finish parsing frame if we haven't received any payload bytes yet, but the length of the frame
//is zero. //is zero.
if (ws->priv->wsStatus == ST_PAYLOAD) { if (ws->priv->wsStatus == ST_PAYLOAD) {
@ -261,8 +284,14 @@ httpd_cgi_state cgiWebSocketRecv(HttpdConnData *connData, uint8_t *data, size_t
//First, unmask the data //First, unmask the data
sl = len - i; 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); // 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 ((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) { // if (DEBUG_WS) {
// ws_dbg("Unmasked: "); // 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. //Inspect the header to see what we need to do.
if ((ws->priv->fr.flags & OPCODE_MASK) == OPCODE_PING) { if ((ws->priv->fr.flags & OPCODE_MASK) == OPCODE_PING) {
if (ws->priv->fr.len > 125) { 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; r = HTTPD_CGI_DONE;
break; break;
} else { } else {
if (!ws->priv->frameCont) { sendFrameHead(ws, OPCODE_PONG | FLAG_FIN, ws->priv->fr.len); } if (!ws->priv->frameCont) {
if (sl > 0) { httpdSend(ws->conn, data + i, sl); } 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 || } 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_BINARY ||
(ws->priv->fr.flags & OPCODE_MASK) == OPCODE_CONTINUE) { (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)) { if (!(ws->priv->fr.len8 & IS_MASKED)) {
//We're a server; client should send us masked packets. //We're a server; client should send us masked packets.
cgiWebsocketClose(ws, 1002); cgiWebsocketClose(ws, WS_CLOSE_PROTOCOL_ERR);
r = HTTPD_CGI_DONE; r = HTTPD_CGI_DONE;
break; break;
} else { } else {
int flags = 0; 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->priv->fr.flags & OPCODE_MASK) == OPCODE_BINARY) {
if (ws->recvCb) { ws->recvCb(ws, data + i, sl, flags); } 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) { } else if ((ws->priv->fr.flags & OPCODE_MASK) == OPCODE_CLOSE) {
// ws_dbg("WS: Got close frame"); // ws_dbg("WS: Got close frame");
if (!ws->priv->closedHere) { if (!ws->priv->closedHere) {
// ws_dbg("WS: Sending response close frame, %x %x (i=%d, len=%d)", data[i], data[i+1], i, len); // 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) { if (i <= len - 2) {
cause = ((data[i] << 8) & 0xff00) + (data[i + 1] & 0xff); 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; r = HTTPD_CGI_DONE;
break; break;
} else { } 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; i += sl - 1;
ws->priv->fr.len -= sl; ws->priv->fr.len -= sl;
if (ws->priv->fr.len == 0) { if (ws->priv->fr.len == 0) {
ws->priv->wsStatus = ST_FLAGS; //go receive next frame ws->priv->wsStatus = ST_FLAGS; //go receive next frame
} else { } else {
@ -355,6 +408,12 @@ httpd_cgi_state cgiWebsocket(HttpdConnData *connData)
if (i && strcasecmp(buff, "websocket") == 0) { if (i && strcasecmp(buff, "websocket") == 0) {
i = httpdGetHeader(connData, "Sec-WebSocket-Key", buff, sizeof(buff) - 1); i = httpdGetHeader(connData, "Sec-WebSocket-Key", buff, sizeof(buff) - 1);
if (i) { 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); // httpd_printf("WS: Key: %s\n", buff);
//Seems like a WebSocket connection. //Seems like a WebSocket connection.
// Alloc structs // Alloc structs
@ -364,7 +423,9 @@ httpd_cgi_state cgiWebsocket(HttpdConnData *connData)
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
memset(connData->cgiData, 0, sizeof(Websock)); memset(connData->cgiData, 0, sizeof(Websock));
Websock *ws = (Websock *) connData->cgiData; Websock *ws = (Websock *) connData->cgiData;
ws->priv = httpdPlatMalloc(sizeof(WebsockPriv)); ws->priv = httpdPlatMalloc(sizeof(WebsockPriv));
if (ws->priv == NULL) { if (ws->priv == NULL) {
ws_error("Can't allocate mem for websocket priv"); ws_error("Can't allocate mem for websocket priv");
@ -372,8 +433,10 @@ httpd_cgi_state cgiWebsocket(HttpdConnData *connData)
connData->cgiData = NULL; connData->cgiData = NULL;
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
memset(ws->priv, 0, sizeof(WebsockPriv)); memset(ws->priv, 0, sizeof(WebsockPriv));
ws->conn = connData; ws->conn = connData;
//Reply with the right headers. //Reply with the right headers.
strcat(buff, WS_GUID); strcat(buff, WS_GUID);
httpd_sha1_init(&s); httpd_sha1_init(&s);
@ -388,14 +451,16 @@ httpd_cgi_state cgiWebsocket(HttpdConnData *connData)
//Set data receive handler //Set data receive handler
connData->recvHdl = cgiWebSocketRecv; connData->recvHdl = cgiWebSocketRecv;
//Inform CGI function we have a connection //Inform CGI function we have a connection
WsConnectedCb connCb = connData->cgiArg;
connCb(ws); connCb(ws);
//Insert ws into linked list //Insert ws into linked list
if (llStart == NULL) { if (llStart == NULL) {
llStart = ws; llStart = ws;
} else { } else {
Websock *lw = llStart; Websock *lw = llStart;
while (lw->priv->next) { lw = lw->priv->next; } while (lw->priv->next) {
lw = lw->priv->next;
}
lw->priv->next = ws; lw->priv->next = ws;
} }
return HTTPD_CGI_MORE; 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. //Sending is done. Call the sent callback if we have one.
Websock *ws = (Websock *) connData->cgiData; Websock *ws = (Websock *) connData->cgiData;
if (ws && ws->sentCb) { ws->sentCb(ws); } if (ws && ws->sentCb) {
ws->sentCb(ws);
}
return HTTPD_CGI_MORE; return HTTPD_CGI_MORE;
} }

@ -11,8 +11,8 @@ HTTP auth implementation. Only does basic authentication for now.
* ---------------------------------------------------------------------------- * ----------------------------------------------------------------------------
*/ */
#include "httpd-auth.h"
#include "httpd.h" #include "httpd.h"
#include "httpd-auth.h"
#include "utils/base64.h" #include "utils/base64.h"
#include <ctype.h> #include <ctype.h>

@ -32,13 +32,13 @@ static int fd_is_valid(int fd)
return fcntl(fd, F_GETFD) != -1 || errno != EBADF; 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; conn->needWriteDoneNotif = 1;
if (!fd_is_valid(conn->fd)) { 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) void httpdConnDisconnect(ConnTypePtr conn)

@ -1,5 +1,4 @@
#include "httpd-utils.h" #include "httpd-utils.h"
#include "httpd.h"
#include "httpd-logging.h" #include "httpd-logging.h"
char httpdHexNibble(uint8_t val) char httpdHexNibble(uint8_t val)

@ -45,7 +45,7 @@ struct HttpdPriv {
char head[HTTPD_MAX_HEAD_LEN]; char head[HTTPD_MAX_HEAD_LEN];
char corsToken[HTTPD_MAX_CORS_TOKEN_LEN]; char corsToken[HTTPD_MAX_CORS_TOKEN_LEN];
int headPos; int headPos;
char *sendBuff; uint8_t *sendBuff;
int sendBuffLen; int sendBuffLen;
char *chunkHdr; char *chunkHdr;
HttpSendBacklogItem *sendBacklog; 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->flags & HFL_CHUNKED && conn->priv->flags & HFL_SENDINGBODY && conn->priv->chunkHdr == NULL) {
if (conn->priv->sendBuffLen + len + 6 > HTTPD_MAX_SENDBUFF_LEN) { return 0; } if (conn->priv->sendBuffLen + len + 6 > HTTPD_MAX_SENDBUFF_LEN) { return 0; }
//Establish start of chunk //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"); strcpy(conn->priv->chunkHdr, "0000\r\n");
conn->priv->sendBuffLen += 6; conn->priv->sendBuffLen += 6;
} }
@ -424,7 +424,7 @@ bool httpdFlushSendBuffer(HttpdConnData *conn)
//Finish chunk with cr/lf //Finish chunk with cr/lf
httpdSendStr(conn, "\r\n"); httpdSendStr(conn, "\r\n");
//Calculate length of chunk //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 //Fix up chunk header to correct value
conn->priv->chunkHdr[0] = httpdHexNibble(len >> 12); conn->priv->chunkHdr[0] = httpdHexNibble(len >> 12);
conn->priv->chunkHdr[1] = httpdHexNibble(len >> 8); 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) { 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. //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; conn->priv->sendBuffLen += 5;
} }
if (conn->priv->sendBuffLen != 0) { if (conn->priv->sendBuffLen != 0) {
@ -505,14 +505,14 @@ void httpdContinue(HttpdConnData *conn)
int r; int r;
httpdPlatLock(); httpdPlatLock();
char *sendBuff; uint8_t *sendBuff;
if (conn == NULL) { return; } if (conn == NULL) { return; }
if (conn->priv->sendBacklog != NULL) { if (conn->priv->sendBacklog != NULL) {
//We have some backlog to send first. //We have some backlog to send first.
HttpSendBacklogItem *next = conn->priv->sendBacklog->next; 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; conn->priv->sendBacklogSize -= conn->priv->sendBacklog->len;
httpdPlatFree(conn->priv->sendBacklog); httpdPlatFree(conn->priv->sendBacklog);
conn->priv->sendBacklog = next; conn->priv->sendBacklog = next;
@ -750,7 +750,7 @@ static void httpdParseHeader(char *h, HttpdConnData *conn)
void httpdConnSendStart(HttpdConnData *conn) void httpdConnSendStart(HttpdConnData *conn)
{ {
httpdPlatLock(); httpdPlatLock();
char *sendBuff = httpdPlatMalloc(HTTPD_MAX_SENDBUFF_LEN); uint8_t *sendBuff = httpdPlatMalloc(HTTPD_MAX_SENDBUFF_LEN);
if (sendBuff == NULL) { if (sendBuff == NULL) {
http_error("Malloc sendBuff failed!"); http_error("Malloc sendBuff failed!");
return; return;
@ -780,7 +780,7 @@ void httpdRecvCb(ConnTypePtr rconn, const char *remIp, int remPort, uint8_t *dat
return; return;
} }
char *sendBuff = httpdPlatMalloc(HTTPD_MAX_SENDBUFF_LEN); uint8_t *sendBuff = httpdPlatMalloc(HTTPD_MAX_SENDBUFF_LEN);
if (sendBuff == NULL) { if (sendBuff == NULL) {
http_error("Malloc sendBuff failed!"); http_error("Malloc sendBuff failed!");
httpdPlatUnlock(); httpdPlatUnlock();

@ -50,7 +50,3 @@ void httpdPlatFree(void *ptr)
{ {
free(ptr); free(ptr);
} }
char * httpdPlatStrdup(const char *s) {
return strdup(s); // FIXME
}

@ -25,7 +25,7 @@ void httpdPlatDelayMs(uint32_t ms)
void httpdPlatTaskEnd() void httpdPlatTaskEnd()
{ {
// TODO // Nothing special needed with pthreads
} }
void httpdPlatDisableTimeout(ConnTypePtr conn) void httpdPlatDisableTimeout(ConnTypePtr conn)
@ -79,7 +79,3 @@ void httpdPlatFree(void *ptr)
{ {
free(ptr); free(ptr);
} }
char * httpdPlatStrdup(const char *s) {
return strdup(s);
}

@ -9,12 +9,10 @@
// FIXME: sprintf->snprintf everywhere. // FIXME: sprintf->snprintf everywhere.
#include <esp8266.h> #include <stddef.h>
#include <httpd.h> #include <stdint.h>
#include <httpclient.h> #include <stdbool.h>
#include "httpclient.h" #include "httpclient.h"
#include "esp_utils.h"
// Internal state. // Internal state.
typedef struct { typedef struct {
@ -34,7 +32,7 @@ typedef struct {
void *userData; void *userData;
} request_args; } request_args;
static int ICACHE_FLASH_ATTR static int
chunked_decode(char *chunked, int size) chunked_decode(char *chunked, int size)
{ {
char *src = chunked; char *src = chunked;
@ -64,7 +62,7 @@ chunked_decode(char *chunked, int size)
return dst; return dst;
} }
static void ICACHE_FLASH_ATTR static void
receive_callback(void *arg, char *buf, unsigned short len) receive_callback(void *arg, char *buf, unsigned short len)
{ {
struct espconn *conn = (struct espconn *) arg; 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) sent_callback(void *arg)
{ {
struct espconn *conn = (struct espconn *) 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) connect_callback(void *arg)
{ {
httpc_info("Connected!"); httpc_info("Connected!");
@ -228,7 +226,7 @@ free_req(request_args *req)
free(req); free(req);
} }
static void ICACHE_FLASH_ATTR static void
disconnect_callback(void *arg) disconnect_callback(void *arg)
{ {
httpc_dbg("Disconnected"); httpc_dbg("Disconnected");
@ -296,7 +294,7 @@ disconnect_callback(void *arg)
free(conn); free(conn);
} }
static void ICACHE_FLASH_ATTR static void
error_callback(void *arg, sint8 errType) error_callback(void *arg, sint8 errType)
{ {
(void) errType; (void) errType;
@ -305,7 +303,7 @@ error_callback(void *arg, sint8 errType)
disconnect_callback(arg); disconnect_callback(arg);
} }
static void ICACHE_FLASH_ATTR static void
http_timeout_callback(void *arg) http_timeout_callback(void *arg)
{ {
httpc_error("Connection timeout\n"); httpc_error("Connection timeout\n");
@ -341,7 +339,7 @@ http_timeout_callback(void *arg)
free(conn); free(conn);
} }
static void ICACHE_FLASH_ATTR static void
dns_callback(const char *hostname, ip_addr_t *addr, void *arg) dns_callback(const char *hostname, ip_addr_t *addr, void *arg)
{ {
(void)hostname; (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) http_request(const httpclient_args *args, httpclient_cb user_callback)
{ {
// --- prepare port, secure... --- // --- 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) http_post(const char *url, const char *body, void *userData, httpclient_cb user_callback)
{ {
httpclient_args args; 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) http_get(const char *url, void *userData, httpclient_cb user_callback)
{ {
httpclient_args args; 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) http_put(const char *url, const char *body, void *userData, httpclient_cb user_callback)
{ {
httpclient_args args; 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, http_callback_example(int http_status,
char *response_headers, char *response_headers,
char *response_body, char *response_body,
@ -562,7 +560,7 @@ http_callback_example(int http_status,
} }
void ICACHE_FLASH_ATTR void
http_callback_showstatus(int code, http_callback_showstatus(int code,
char *response_headers, char *response_headers,
char *response_body, char *response_body,

@ -1,59 +0,0 @@
#include <esp8266.h>
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);
}
}

@ -1,22 +0,0 @@
#ifndef UPTIME_H
#define UPTIME_H
#include <esp8266.h>
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

@ -1,3 +0,0 @@
extern char webpages_espfs_start[];
extern char webpages_espfs_end[];
extern int webpages_espfs_size;

@ -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;
}
}
Loading…
Cancel
Save