diff --git a/spritehttpd/include/httpd-config.h b/spritehttpd/include/httpd-config.h new file mode 100644 index 0000000..76a9ade --- /dev/null +++ b/spritehttpd/include/httpd-config.h @@ -0,0 +1,53 @@ +/** + * HTTPD config defines + */ + +#pragma once + +#ifndef GIT_HASH +#define GIT_HASH "unknown" +#endif + +// we must not use this macro outside the library, as the git hash is not defined there +#define HTTPDVER "0.4+MightyPork/libesphttpd#" GIT_HASH + +// default servername +#ifndef HTTPD_SERVERNAME +#define HTTPD_SERVERNAME "SpriteHTTPD " HTTPDVER +#endif + +//Max length of request head. This is statically allocated for each connection. +#ifndef HTTPD_MAX_HEAD_LEN +#define HTTPD_MAX_HEAD_LEN 1024 +#endif + +//Max post buffer len. This is dynamically malloc'ed if needed. +#ifndef HTTPD_MAX_POST_LEN +#define HTTPD_MAX_POST_LEN 2048 +#endif + +//Max send buffer len. This is allocated on the stack. +#ifndef HTTPD_MAX_SENDBUFF_LEN +#define HTTPD_MAX_SENDBUFF_LEN 2048 +#endif + +//Receive buffer +#ifndef HTTPD_RECV_BUF_LEN +#define HTTPD_RECV_BUF_LEN 2048 +#endif + +//If some data can't be sent because the underlaying socket doesn't accept the data (like the nonos +//layer is prone to do), we put it in a backlog that is dynamically malloc'ed. This defines the max +//size of the backlog. +#ifndef HTTPD_MAX_BACKLOG_SIZE +#define HTTPD_MAX_BACKLOG_SIZE (4*1024) +#endif + +//Max len of CORS token. This is allocated in each connection +#ifndef HTTPD_MAX_CORS_TOKEN_LEN +#define HTTPD_MAX_CORS_TOKEN_LEN 256 +#endif + +#ifndef HTTPD_MAX_CONNECTIONS +#define HTTPD_MAX_CONNECTIONS 4 +#endif diff --git a/spritehttpd/include/httpd-types.h b/spritehttpd/include/httpd-types.h index 6eba210..08d8b85 100644 --- a/spritehttpd/include/httpd-types.h +++ b/spritehttpd/include/httpd-types.h @@ -6,6 +6,7 @@ #include #include +#include "httpd-config.h" // opaque conn type struct struct HttpdConnType; @@ -57,31 +58,77 @@ typedef enum { HTTPD_TRANSFER_NONE = 2, } httpd_transfer_opt; -typedef struct HttpdPriv HttpdPriv; -typedef struct HttpdConnData HttpdConnData; -typedef struct HttpdPostData HttpdPostData; -typedef httpd_cgi_state (* cgiSendCallback)(HttpdConnData *connData); -typedef httpd_cgi_state (* cgiRecvHandler)(HttpdConnData *connData, uint8_t *data, size_t len); +/* init options */ + +struct httpd_options { + uint16_t port; +}; + + +/* Private types - exposed to allow static alloc */ + +struct HttpdQueuedHeader; +typedef struct HttpdQueuedHeader { + /// Pointer to the next queued header + struct HttpdQueuedHeader *next; + /// Text of the header, including CRLF + char headerLine[]; +} HttpdQueuedHeader; + +typedef struct HttpSendBacklogItem HttpSendBacklogItem; +struct HttpSendBacklogItem { + size_t len; + HttpSendBacklogItem *next; + uint8_t data[]; +}; + +//Private data for http connection +typedef struct HttpdPriv HttpdPriv; +struct HttpdPriv { + char head[HTTPD_MAX_HEAD_LEN]; + char corsToken[HTTPD_MAX_CORS_TOKEN_LEN]; + size_t headPos; + uint8_t *sendBuff; + size_t sendBuffLen; + char *chunkHdr; + HttpSendBacklogItem *sendBacklog; + HttpdQueuedHeader *headersToSend; // Linked list of headers to send with the response. Will be freed with the request. + size_t sendBacklogSize; + uint8_t flags; +}; /// Callback type that releases the user data attached to the connection. /// The format is compatible with regular "free()" or -typedef void (* httpdConnUserDataCleanupCb)(void *userData); +typedef void (* httpdUserDataCleanupCb)(void *userData); -struct httpd_options { - uint16_t port; + +//A struct describing the POST data sent inside the http connection. This is used by the CGI functions +typedef struct HttpdPostData HttpdPostData; +struct HttpdPostData { + // FIXME len can be negative due to a stupid hack at `src/httpd.c:923` + int len; // POST Content-Length + size_t buffSize; // The maximum length of the post buffer + size_t buffLen; // The amount of bytes in the current post buffer + size_t received; // The total amount of bytes received so far + char *buff; // Actual POST data buffer + char *multipartBoundary; //Text of the multipart boundary, if any }; +typedef struct HttpdConnData HttpdConnData; +typedef httpd_cgi_state (* cgiSendCallback)(HttpdConnData *connData); +typedef httpd_cgi_state (* cgiRecvHandler)(HttpdConnData *connData, uint8_t *data, size_t len); + //A struct describing a http connection. This gets passed to cgi functions. struct HttpdConnData { ConnTypePtr conn; // The TCP connection. Exact type depends on the platform. - HttpdPriv *priv; // Opaque pointer to data for internal httpd housekeeping + HttpdPriv *priv; // Internal httpd state for the connection httpd_method requestType; // One of the HTTPD_METHOD_* values char *hostName; // Host name field of request char *url; // The URL requested, without hostname or GET arguments char *getArgs; // The GET arguments for this request, if any. - HttpdPostData *post; // POST data structure + HttpdPostData post; // POST data structure cgiSendCallback cgi; // CGI function pointer cgiRecvHandler recvHdl; // Handler for data received after headers, if any @@ -94,21 +141,10 @@ struct HttpdConnData { /// If userData is not NULL when a connection is finalized, this function (if set) is called to handle the cleanup. /// This mechanism is useful when the user data is allocated by an auth CGI and the request can end in any number /// of other routes, perhaps even with a static file - then the cleanup code doesn't need to be repeated everywhere. - httpdConnUserDataCleanupCb userDataCleanupCb; + httpdUserDataCleanupCb userDataCleanupCb; // this should be at the end because of padding uint16_t remote_port; // Remote TCP port httpd_ipaddr_t remote_ip; // IP address of client uint8_t slot; // Slot ID - index in s_connData }; - -//A struct describing the POST data sent inside the http connection. This is used by the CGI functions -struct HttpdPostData { - // FIXME len can be negative due to a stupid hack at `src/httpd.c:923` - int len; // POST Content-Length - size_t buffSize; // The maximum length of the post buffer - size_t buffLen; // The amount of bytes in the current post buffer - size_t received; // The total amount of bytes received so far - char *buff; // Actual POST data buffer - char *multipartBoundary; //Text of the multipart boundary, if any -}; diff --git a/spritehttpd/include/httpd.h b/spritehttpd/include/httpd.h index ee157da..e7b3193 100644 --- a/spritehttpd/include/httpd.h +++ b/spritehttpd/include/httpd.h @@ -8,54 +8,8 @@ #include "httpd-platform.h" #include "httpd-types.h" #include "httpd-routes.h" +#include "httpd-config.h" -#ifndef GIT_HASH -#define GIT_HASH "unknown" -#endif - -// we must not use this macro outside the library, as the git hash is not defined there -#define HTTPDVER "0.4+MightyPork/libesphttpd#" GIT_HASH - -// default servername -#ifndef HTTPD_SERVERNAME -#define HTTPD_SERVERNAME "SpriteHTTPD " HTTPDVER -#endif - -//Max length of request head. This is statically allocated for each connection. -#ifndef HTTPD_MAX_HEAD_LEN -#define HTTPD_MAX_HEAD_LEN 1024 -#endif - -//Max post buffer len. This is dynamically malloc'ed if needed. -#ifndef HTTPD_MAX_POST_LEN -#define HTTPD_MAX_POST_LEN 2048 -#endif - -//Max send buffer len. This is allocated on the stack. -#ifndef HTTPD_MAX_SENDBUFF_LEN -#define HTTPD_MAX_SENDBUFF_LEN 2048 -#endif - -//Receive buffer -#ifndef HTTPD_RECV_BUF_LEN -#define HTTPD_RECV_BUF_LEN 2048 -#endif - -//If some data can't be sent because the underlaying socket doesn't accept the data (like the nonos -//layer is prone to do), we put it in a backlog that is dynamically malloc'ed. This defines the max -//size of the backlog. -#ifndef HTTPD_MAX_BACKLOG_SIZE -#define HTTPD_MAX_BACKLOG_SIZE (4*1024) -#endif - -//Max len of CORS token. This is allocated in each connection -#ifndef HTTPD_MAX_CORS_TOKEN_LEN -#define HTTPD_MAX_CORS_TOKEN_LEN 256 -#endif - -#ifndef HTTPD_MAX_CONNECTIONS -#define HTTPD_MAX_CONNECTIONS 4 -#endif /** * Get the server version string diff --git a/spritehttpd/src/cgi-espfs.c b/spritehttpd/src/cgi-espfs.c index 6686ebf..88ec1c5 100644 --- a/spritehttpd/src/cgi-espfs.c +++ b/spritehttpd/src/cgi-espfs.c @@ -86,6 +86,7 @@ static httpd_cgi_state serveStaticFile(HttpdConnData *hconn, const char *filepat if (hconn->conn == NULL) { //Connection aborted. Clean up. espFsClose(file); + hconn->cgiData = NULL; return HTTPD_CGI_DONE; } @@ -122,6 +123,7 @@ static httpd_cgi_state serveStaticFile(HttpdConnData *hconn, const char *filepat //No Accept-Encoding: gzip header present httpdSendStr(hconn, gzipNonSupportedMessage); espFsClose(file); + hconn->cgiData = NULL; return HTTPD_CGI_DONE; } } @@ -146,6 +148,7 @@ static httpd_cgi_state serveStaticFile(HttpdConnData *hconn, const char *filepat if (len != ESPFS_FILE_CHUNK_LEN) { //We're done. espFsClose(file); + hconn->cgiData = NULL; return HTTPD_CGI_DONE; } else { //Ok, till next time. diff --git a/spritehttpd/src/httpd.c b/spritehttpd/src/httpd.c index efef0d2..b7d8e17 100644 --- a/spritehttpd/src/httpd.c +++ b/spritehttpd/src/httpd.c @@ -28,22 +28,6 @@ _Static_assert(HTTPD_MAX_CONNECTIONS < 256, "HTTPD_MAX_CONNECTIONS must be at mo static const HttpdBuiltInUrl *s_builtInUrls; static const char *s_serverName = HTTPD_SERVERNAME; -struct HttpdQueuedHeader; -typedef struct HttpdQueuedHeader { - /// Pointer to the next queued header - struct HttpdQueuedHeader *next; - /// Text of the header, including CRLF - char headerLine[]; -} HttpdQueuedHeader; - -typedef struct HttpSendBacklogItem HttpSendBacklogItem; - -struct HttpSendBacklogItem { - size_t len; - HttpSendBacklogItem *next; - uint8_t data[]; -}; - //Flags (1 byte) #define HFL_HTTP11 (1<<0) #define HFL_CHUNKED (1<<1) @@ -52,19 +36,6 @@ struct HttpSendBacklogItem { #define HFL_NOCONNECTIONSTR (1<<4) #define HFL_NOCORS (1<<5) -//Private data for http connection -struct HttpdPriv { - char head[HTTPD_MAX_HEAD_LEN]; - char corsToken[HTTPD_MAX_CORS_TOKEN_LEN]; - size_t headPos; - uint8_t *sendBuff; - size_t sendBuffLen; - char *chunkHdr; - HttpSendBacklogItem *sendBacklog; - HttpdQueuedHeader *headersToSend; // Linked list of headers to send with the response. Will be freed with the request. - size_t sendBacklogSize; - uint8_t flags; -}; //Connection pool @@ -160,9 +131,14 @@ static void httpdRetireConn(HttpdConnData *hconn) httpdPlatFree(j); } while (i != NULL); } - if (hconn->post->buff != NULL) { httpdPlatFree(hconn->post->buff); } - if (hconn->post != NULL) { httpdPlatFree(hconn->post); } - if (hconn->priv != NULL) { httpdPlatFree(hconn->priv); } + if (hconn->post.buff != NULL) { + httpdPlatFree(hconn->post.buff); + hconn->post.buff = NULL; + } + if (hconn->priv != NULL) { + httpdPlatFree(hconn->priv); + hconn->priv = NULL; + } // Unlink from the connection list s_connData[hconn->slot] = NULL; @@ -486,12 +462,14 @@ static void httpdCgiIsDone(HttpdConnData *conn) httpdFlushSendBuffer(conn); //Note: Do not clean up sendBacklog, it may still contain data at this point. conn->priv->headPos = 0; - conn->post->len = -1; + conn->post.len = -1; conn->priv->flags = 0; - if (conn->post->buff) { httpdPlatFree(conn->post->buff); } - conn->post->buff = NULL; - conn->post->buffLen = 0; - conn->post->received = 0; + if (conn->post.buff) { + httpdPlatFree(conn->post.buff); + conn->post.buff = NULL; + } + conn->post.buffLen = 0; + conn->post.received = 0; conn->hostName = NULL; } else { //Cannot re-use this connection. Mark to get it killed after all data is sent. @@ -715,31 +693,31 @@ static void httpdParseHeader(char *h, HttpdConnData *conn) //Skip trailing spaces while (h[i] == ' ') { i++; } //Get POST data length - conn->post->len = (int) strtol(h + i, NULL, 10); + conn->post.len = (int) strtol(h + i, NULL, 10); // Allocate the buffer - if (conn->post->len > HTTPD_MAX_POST_LEN) { + if (conn->post.len > HTTPD_MAX_POST_LEN) { // we'll stream this in in chunks - conn->post->buffSize = HTTPD_MAX_POST_LEN; + conn->post.buffSize = HTTPD_MAX_POST_LEN; } else { - conn->post->buffSize = (size_t) conn->post->len; + conn->post.buffSize = (size_t) conn->post.len; } - http_dbg("Mallocced buffer for %d + 1 bytes of post data.", (int) conn->post->buffSize); - conn->post->buff = (char *) httpdPlatMalloc(conn->post->buffSize + 1); - if (conn->post->buff == NULL) { + http_dbg("Mallocced buffer for %d + 1 bytes of post data.", (int) conn->post.buffSize); + conn->post.buff = (char *) httpdPlatMalloc(conn->post.buffSize + 1); + if (conn->post.buff == NULL) { http_error("post buf alloc failed"); return; } - conn->post->buffLen = 0; + conn->post.buffLen = 0; } else if (strstarts(h, "Content-Type:")) { if (strstr(h, "multipart/form-data")) { // It's multipart form data so let's pull out the boundary for future use char *b; if ((b = strstr(h, "boundary=")) != NULL) { - conn->post->multipartBoundary = b + 7; // move the pointer 2 chars before boundary then fill them with dashes - conn->post->multipartBoundary[0] = '-'; - conn->post->multipartBoundary[1] = '-'; - http_dbg("boundary = %s", conn->post->multipartBoundary); + conn->post.multipartBoundary = b + 7; // move the pointer 2 chars before boundary then fill them with dashes + conn->post.multipartBoundary[0] = '-'; + conn->post.multipartBoundary[1] = '-'; + http_dbg("boundary = %s", conn->post.multipartBoundary); } } } else if (strstarts(h, "Access-Control-Request-Headers: ")) { @@ -806,7 +784,7 @@ void httpdRecvCb(ConnTypePtr rconn, httpd_ipaddr_t remIp, uint16_t remPort, uint //ToDo: See if we can use something more elegant for this. for (x = 0; x < len; x++) { - if (conn->post->len < 0) { + if (conn->post.len < 0) { //This byte is a header byte. if (data[x] == '\n') { //Compatibility with clients that send \n only: fake a \r in front of this. @@ -820,7 +798,7 @@ void httpdRecvCb(ConnTypePtr rconn, httpd_ipaddr_t remIp, uint16_t remPort, uint //Scan for /r/n/r/n. Receiving this indicates the headers end. if (data[x] == '\n' && strstr(conn->priv->head, "\r\n\r\n") != NULL) { //Indicate we're done with the headers. - conn->post->len = 0; + conn->post.len = 0; //Reset url data conn->url = NULL; //Iterate over all received headers and parse them. @@ -833,18 +811,18 @@ void httpdRecvCb(ConnTypePtr rconn, httpd_ipaddr_t remIp, uint16_t remPort, uint p = e + 2; //Skip /r/n (now /0/n) } //If we don't need to receive post data, we can send the response now. - if (conn->post->len == 0) { + if (conn->post.len == 0) { httpdProcessRequest(conn); } } - } else if (conn->post->len != 0) { + } else if (conn->post.len != 0) { //This byte is a POST byte. - conn->post->buff[conn->post->buffLen++] = (char) data[x]; - conn->post->received++; + conn->post.buff[conn->post.buffLen++] = (char) data[x]; + conn->post.received++; conn->hostName = NULL; - if (conn->post->buffLen >= conn->post->buffSize || (int) conn->post->received == conn->post->len) { + if (conn->post.buffLen >= conn->post.buffSize || (int) conn->post.received == conn->post.len) { //Received a chunk of post data - conn->post->buff[conn->post->buffLen] = 0; //zero-terminate, in case the cgi handler knows it can use strings + conn->post.buff[conn->post.buffLen] = 0; //zero-terminate, in case the cgi handler knows it can use strings //Process the data if (conn->cgi) { r = conn->cgi(conn); @@ -856,8 +834,8 @@ void httpdRecvCb(ConnTypePtr rconn, httpd_ipaddr_t remIp, uint16_t remPort, uint //call it the first time. httpdProcessRequest(conn); } - conn->post->buffLen = 0; - conn->post->len = 0; // this causes transfer to the recvHdl branch + conn->post.buffLen = 0; + conn->post.len = 0; // this causes transfer to the recvHdl branch } } else { //Let cgi handle data if it registered a recvHdl callback. If not, ignore. @@ -947,6 +925,7 @@ int httpdConnectCb(ConnTypePtr conn, httpd_ipaddr_t remIp, uint16_t remPort) s_connData[ci]->slot = ci; s_connData[ci]->remote_ip = remIp; s_connData[ci]->remote_port = remPort; + s_connData[ci]->post.len = -1; s_connData[ci]->priv = httpdPlatMalloc(sizeof(HttpdPriv)); if (s_connData[ci]->priv == NULL) { @@ -956,15 +935,6 @@ int httpdConnectCb(ConnTypePtr conn, httpd_ipaddr_t remIp, uint16_t remPort) } memset(s_connData[ci]->priv, 0, sizeof(HttpdPriv)); - s_connData[ci]->post = httpdPlatMalloc(sizeof(HttpdPostData)); - if (s_connData[ci]->post == NULL) { - http_error("Out of memory allocating connData post struct!"); - httpdPlatUnlock(); - return 0; - } - memset(s_connData[ci]->post, 0, sizeof(HttpdPostData)); - s_connData[ci]->post->len = -1; - httpdPlatUnlock(); return 1; }