From deb2fe6d93cee640c5c359bdacc08af4d76ac94a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Fri, 27 Jan 2023 16:15:01 +0100 Subject: [PATCH] refactoring, add doc comments --- demo/server_demo.c | 35 +- espfsbuilder/{logging.h => httpd-logging.h} | 0 spritehttpd/Makefile | 10 +- spritehttpd/include/auth.h | 19 - .../{cgiwebsocket.h => cgi-websocket.h} | 0 spritehttpd/include/httpd-auth.h | 44 +++ .../include/{httpdespfs.h => httpd-espfs.h} | 0 .../include/{logging.h => httpd-logging.h} | 0 spritehttpd/include/httpd-platform.h | 12 +- spritehttpd/include/httpd-types.h | 105 ++++++ spritehttpd/include/httpd-utils.h | 77 ++++ spritehttpd/include/httpd.h | 338 ++++++++++++------ spritehttpd/lib/espfs/espfs.c | 2 +- .../src/{cgiwebsocket.c => cgi-websocket.c} | 12 +- spritehttpd/src/httpd-auth.c | 101 ++++++ .../src/{httpdespfs.c => httpd-espfs.c} | 14 +- spritehttpd/src/httpd-loop.c | 183 +++++----- spritehttpd/src/httpd-utils.c | 162 +++++++++ spritehttpd/src/httpd.c | 287 +++++---------- spritehttpd/src/port/httpd-posix.c | 4 +- spritehttpd/src/utils/base64.c | 29 +- spritehttpd/src/utils/base64.h | 6 +- spritehttpd/src/utils/sha1.c | 49 +-- spritehttpd/src/utils/sha1.h | 16 +- 24 files changed, 982 insertions(+), 523 deletions(-) rename espfsbuilder/{logging.h => httpd-logging.h} (100%) delete mode 100644 spritehttpd/include/auth.h rename spritehttpd/include/{cgiwebsocket.h => cgi-websocket.h} (100%) create mode 100644 spritehttpd/include/httpd-auth.h rename spritehttpd/include/{httpdespfs.h => httpd-espfs.h} (100%) rename spritehttpd/include/{logging.h => httpd-logging.h} (100%) create mode 100644 spritehttpd/include/httpd-types.h rename spritehttpd/src/{cgiwebsocket.c => cgi-websocket.c} (98%) create mode 100644 spritehttpd/src/httpd-auth.c rename spritehttpd/src/{httpdespfs.c => httpd-espfs.c} (97%) create mode 100644 spritehttpd/src/httpd-utils.c diff --git a/demo/server_demo.c b/demo/server_demo.c index 0d0ec14..b7bc59d 100644 --- a/demo/server_demo.c +++ b/demo/server_demo.c @@ -1,19 +1,16 @@ #include -#include "httpd.h" -#include "httpd-utils.h" -#include "httpdespfs.h" - -#include -#include -#include -#include #include +#include -#include "logging.h" +#include "httpd.h" +#include "httpd-utils.h" +#include "httpd-espfs.h" extern unsigned char espfs_image[]; extern unsigned int espfs_image_len; +httpd_thread_handle_t *s_serverHandle = NULL; + /** "About" page */ httpd_cgi_state tplIndex(HttpdConnData *connData, char *token, void **arg) { @@ -49,22 +46,30 @@ void sigpipe_handler(int unused) { } +void handle_sigint(int signum) +{ + fprintf(stderr, " SIGINT detected, shutting down HTTPD\n"); + httpdShutdown(s_serverHandle); +} + int main() { - printf("Hello, World!\n"); + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = handle_sigint; + sigaction(SIGINT, &action, NULL); // prevent abort on sigpipe - sigaction(SIGPIPE, &(struct sigaction) {sigpipe_handler}, NULL); + sigaction(SIGPIPE, &(struct sigaction) {{sigpipe_handler}}, NULL); struct httpd_options opts = { .port = 8080, }; - httpd_thread_handle_t *handle = httpdInit(routes, &opts); - httpdSetName("ServerName"); - - httpdJoin(handle); + s_serverHandle = httpdStart(routes, &opts); + httpdSetName("SpriteHTTPD Server Demo"); + httpdJoin(s_serverHandle); return 0; } diff --git a/espfsbuilder/logging.h b/espfsbuilder/httpd-logging.h similarity index 100% rename from espfsbuilder/logging.h rename to espfsbuilder/httpd-logging.h diff --git a/spritehttpd/Makefile b/spritehttpd/Makefile index f753307..d049b8c 100644 --- a/spritehttpd/Makefile +++ b/spritehttpd/Makefile @@ -20,18 +20,18 @@ LIB_SOURCES = ${PORT_SOURCES} \ lib/heatshrink/heatshrink_decoder.c \ src/utils/base64.c \ src/utils/sha1.c \ - src/httpdespfs.c \ src/httpd.c \ + src/httpd-espfs.c \ + src/httpd-auth.c \ + src/httpd-utils.c \ src/httpd-loop.c \ - src/cgiwebsocket.c + src/cgi-websocket.c LIB_OBJS = ${LIB_SOURCES:.c=.o} LIB_INCLUDES = -Iinclude -Ilib/heatshrink -Ilib/espfs -# TODO check what these mean -#LIB_CFLAGS = -fPIC -Wall -Wextra -c -LIB_CFLAGS = -fPIC -Wall -Wextra -c -Og -g +LIB_CFLAGS = -fPIC -Wall -Wextra -c -Os -ggdb -std=gnu99 -DGIT_HASH='"$(shell git rev-parse --short HEAD)"' OBJ_DIR=./obj diff --git a/spritehttpd/include/auth.h b/spritehttpd/include/auth.h deleted file mode 100644 index 76e13ad..0000000 --- a/spritehttpd/include/auth.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "httpd.h" - -#ifndef HTTP_AUTH_REALM -#define HTTP_AUTH_REALM "Protected" -#endif - -#define HTTPD_AUTH_SINGLE 0 -#define HTTPD_AUTH_CALLBACK 1 - -#define AUTH_MAX_USER_LEN 32 -#define AUTH_MAX_PASS_LEN 32 - -//Parameter given to authWhatever functions. This callback returns the usernames/passwords the device -//has. -typedef int (* AuthGetUserPw)(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen); - -httpd_cgi_state authBasic(HttpdConnData *connData); diff --git a/spritehttpd/include/cgiwebsocket.h b/spritehttpd/include/cgi-websocket.h similarity index 100% rename from spritehttpd/include/cgiwebsocket.h rename to spritehttpd/include/cgi-websocket.h diff --git a/spritehttpd/include/httpd-auth.h b/spritehttpd/include/httpd-auth.h new file mode 100644 index 0000000..7135b5e --- /dev/null +++ b/spritehttpd/include/httpd-auth.h @@ -0,0 +1,44 @@ +#pragma once + +#include "httpd-types.h" +#include + +#ifndef HTTP_AUTH_REALM +#define HTTP_AUTH_REALM "Protected" +#endif + +#define AUTH_MAX_USER_LEN 32 +#define AUTH_MAX_PASS_LEN 32 +#define AUTH_MAX_TOKEN_LEN 128 + +/** + * Callback type for basic auth. + * + * 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 (* HttpdBasicAuthCb)(HttpdConnData *connData, const char *user, const char *password); + +/** + * Callback type for bearer auth. + * + * Returns true if the token is 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); + +/** + * Basic auth CGI handler + * + * @param connData + * @return CGI status + */ +httpd_cgi_state cgiAuthBasic(HttpdConnData *connData); + +/** + * Bearer auth CGI handler + * + * @param connData + * @return CGI status + */ +httpd_cgi_state cgiAuthBearer(HttpdConnData *connData); diff --git a/spritehttpd/include/httpdespfs.h b/spritehttpd/include/httpd-espfs.h similarity index 100% rename from spritehttpd/include/httpdespfs.h rename to spritehttpd/include/httpd-espfs.h diff --git a/spritehttpd/include/logging.h b/spritehttpd/include/httpd-logging.h similarity index 100% rename from spritehttpd/include/logging.h rename to spritehttpd/include/httpd-logging.h diff --git a/spritehttpd/include/httpd-platform.h b/spritehttpd/include/httpd-platform.h index 6ecf225..6182429 100644 --- a/spritehttpd/include/httpd-platform.h +++ b/spritehttpd/include/httpd-platform.h @@ -4,17 +4,7 @@ #include #include -#include "httpd-utils.h" - -// opaque conn type struct -struct HttpdConnType; -typedef struct HttpdConnType HttpdConnType; -typedef HttpdConnType* ConnTypePtr; - -struct httpd_thread_handle; -typedef struct httpd_thread_handle httpd_thread_handle_t; - -struct httpd_options; +#include "httpd-types.h" #define httpd_printf(fmt, ...) printf(fmt, ##__VA_ARGS__) diff --git a/spritehttpd/include/httpd-types.h b/spritehttpd/include/httpd-types.h new file mode 100644 index 0000000..a88bd1a --- /dev/null +++ b/spritehttpd/include/httpd-types.h @@ -0,0 +1,105 @@ +/** + * Type definitions used in the http server + */ + +#pragma once + +#include +#include + +// opaque conn type struct +struct HttpdConnType; +typedef struct HttpdConnType HttpdConnType; +typedef HttpdConnType* ConnTypePtr; + +struct httpd_thread_handle; +typedef struct httpd_thread_handle httpd_thread_handle_t; + +struct httpd_options; + +/** + * CGI handler state / return value + */ +typedef enum { + HTTPD_CGI_MORE = 0, + HTTPD_CGI_DONE = 1, + HTTPD_CGI_NOTFOUND = 2, + HTTPD_CGI_AUTHENTICATED = 3, +} httpd_cgi_state; + +/** + * HTTP method (verb) used for the request + */ +typedef enum { + HTTPD_METHOD_GET = 1, + HTTPD_METHOD_POST = 2, + HTTPD_METHOD_OPTIONS = 3, + HTTPD_METHOD_PUT = 4, + HTTPD_METHOD_DELETE = 5, + HTTPD_METHOD_PATCH = 6, + HTTPD_METHOD_HEAD = 7, +} httpd_method; + +/** + * Transfer mode + */ +typedef enum { + HTTPD_TRANSFER_CLOSE = 0, + HTTPD_TRANSFER_CHUNKED = 1, + 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); + +struct httpd_options { + uint16_t port; +}; + +//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 + + 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 + + cgiSendCallback cgi; // CGI function pointer + cgiRecvHandler recvHdl; // Handler for data received after headers, if any + const void *cgiArg; // Argument to the CGI function, as stated as the 3rd argument of + // the builtInUrls entry that referred to the CGI function. + const void *cgiArg2; // 4th argument of the builtInUrls entries, used to pass template file to the tpl handler. + void *cgiData; // Opaque data pointer for the CGI function + + // this should be at the end because of padding + uint16_t remote_port; // Remote TCP port + uint8_t remote_ip[4]; // IP address of client + uint8_t slot; // Slot ID +}; + +//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 +}; + +//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 67bb1f2..b3d5af8 100644 --- a/spritehttpd/include/httpd-utils.h +++ b/spritehttpd/include/httpd-utils.h @@ -1,6 +1,12 @@ +/** + * Utility functions for users and internal use of the HTTP server + */ + #pragma once +#include #include +#include "httpd-types.h" // Custom helpers #define streq(a, b) (strcmp((const char*)(a), (const char*)(b)) == 0) @@ -8,3 +14,74 @@ #define strstarts(a, b) strneq((a), (b), (int)strlen((b))) #define last_char_n(str, n) ((str))[strlen((str)) - (n)] #define last_char(str) last_char_n((str), 1) + +/** + * Turn a nibble (0-15) to a hex char. + * + * Only the bottom 4 bits are considered. + * + * @param val + * @return hex char, uppercase + */ +char httpdHexNibble(uint8_t val); + +/** + * Turn a hex char into integer + * + * @param c - char to convert, [0-9a-fA-F] + * @return integer value 0-15 + */ +uint8_t httpdHexVal(char c); + +/** + * Decode a percent-encoded value. + * Takes the valLen bytes stored in val, and converts it into at most retLen bytes that + * are stored in the ret buffer. Returns the actual amount of bytes used in ret. Also + * zero-terminates the ret buffer. + * + * @param val - the encoded value + * @param valLen - length of the encoded value field + * @param buff - output buffer, the string will be zero-terminated + * @param buffLen - output buffer size + * @return + */ +int httpdUrlDecode(const char *val, size_t valLen, char *buff, size_t buffLen); + +/** + * Find a specific arg in a string of get- or post-data. + * Line is the string of post/get-data, arg is the name of the value to find. The + * zero-terminated result is written in buff, with at most buffLen bytes used. The + * function returns the length of the result, or -1 if the value wasn't found. The + * returned string will be urldecoded already. + * + * @param line - line to parse + * @param arg - name of the argument to retrieve + * @param[out] buff - output buffer, the string will be zero-terminated + * @param buffLen - output buffer size + * @return + */ +int httpdFindArg(const char *line, const char *arg, char *buff, size_t buffLen); + +/** + * Returns a static char* to a mime type for a given url to a file. + * + * @param url - url to parse + * @return mime type string + */ +const char *httpdGetMimetype(const char *url); + +/** + * Turn HTTP method to text + * + * @param m - method enum + * @return text, e.g. GET + */ +const char *httpdMethodName(httpd_method m); + +/** + * Get text version of a HTTP status code + * + * @param code - code + * @return text, e.g OK or Forbidden + */ +const char *httpdStatusName(int code); diff --git a/spritehttpd/include/httpd.h b/spritehttpd/include/httpd.h index 12056a7..e1bad76 100644 --- a/spritehttpd/include/httpd.h +++ b/spritehttpd/include/httpd.h @@ -3,8 +3,10 @@ #include #include #include +#include #include /* needed for ssize_t */ #include "httpd-platform.h" +#include "httpd-types.h" #ifndef GIT_HASH #define GIT_HASH "unknown" @@ -15,29 +17,34 @@ // default servername #ifndef HTTPD_SERVERNAME -#define HTTPD_SERVERNAME "esp8266-httpd " HTTPDVER +#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 +#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 +#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 +#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) +#define HTTPD_MAX_BACKLOG_SIZE (4*1024) #endif //Max len of CORS token. This is allocated in each connection @@ -49,91 +56,6 @@ #define HTTPD_MAX_CONNECTIONS 4 #endif -/** - * CGI handler state / return value - */ -typedef enum { - HTTPD_CGI_MORE = 0, - HTTPD_CGI_DONE = 1, - HTTPD_CGI_NOTFOUND = 2, - HTTPD_CGI_AUTHENTICATED = 3, -} httpd_cgi_state; - -/** - * HTTP method (verb) used for the request - */ -typedef enum { - HTTPD_METHOD_GET = 1, - HTTPD_METHOD_POST = 2, - HTTPD_METHOD_OPTIONS = 3, - HTTPD_METHOD_PUT = 4, - HTTPD_METHOD_DELETE = 5, - HTTPD_METHOD_PATCH = 6, - HTTPD_METHOD_HEAD = 7, -} httpd_method; - -/** - * Transfer mode - */ -typedef enum { - HTTPD_TRANSFER_CLOSE = 0, - HTTPD_TRANSFER_CHUNKED = 1, - HTTPD_TRANSFER_NONE = 2, -} httpd_transfer_opt; - -typedef struct HttpdPriv HttpdPriv; -typedef struct HttpdConnData HttpdConnData; -typedef struct HttpdPostData HttpdPostData; - -// Private static connection pool -extern HttpdConnData *s_connData[HTTPD_MAX_CONNECTIONS]; - -typedef httpd_cgi_state (* cgiSendCallback)(HttpdConnData *connData); -typedef httpd_cgi_state (* cgiRecvHandler)(HttpdConnData *connData, char *data, size_t len); - -struct httpd_options { - uint16_t port; -}; - -//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. - httpd_method requestType; // One of the HTTPD_METHOD_* values - char *url; // The URL requested, without hostname or GET arguments - char *getArgs; // The GET arguments for this request, if any. - const void *cgiArg; // Argument to the CGI function, as stated as the 3rd argument of - // the builtInUrls entry that referred to the CGI function. - const void *cgiArg2; // 4th argument of the builtInUrls entries, used to pass template file to the tpl handler. - void *cgiData; // Opaque data pointer for the CGI function - char *hostName; // Host name field of request - HttpdPriv *priv; // Opaque pointer to data for internal httpd housekeeping - cgiSendCallback cgi; // CGI function pointer - cgiRecvHandler recvHdl; // Handler for data received after headers, if any - HttpdPostData *post; // POST data structure - int remote_port; // Remote TCP port - uint8_t remote_ip[4]; // IP address of client - uint8_t slot; // Slot ID -}; - -//A struct describing the POST data sent inside the http connection. This is used by the CGI functions -struct HttpdPostData { - int len; // POST Content-Length - int buffSize; // The maximum length of the post buffer - int buffLen; // The amount of bytes in the current post buffer - int received; // The total amount of bytes received so far - 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; - // macros for defining HttpdBuiltInUrl's /** Route with a CGI handler and two arguments */ @@ -158,7 +80,10 @@ typedef struct { #define ROUTE_REDIRECT(path, target) ROUTE_CGI_ARG((path), cgiRedirect, (const char*)(target)) /** Following routes are basic-auth protected */ -#define ROUTE_AUTH(path, passwdFunc) ROUTE_CGI_ARG((path), authBasic, (AuthGetUserPw)(passwdFunc)) +#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)) @@ -166,26 +91,109 @@ typedef struct { /** 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 + * + * @return version + */ const char *httpdGetVersion(void); +/** + * Use this as a cgi function to redirect one url to another. + */ httpd_cgi_state cgiRedirect(HttpdConnData *connData); + +/** + * This CGI function redirects to a fixed url of http://[hostname]/ if hostname field of request isn't + * already that hostname. Use this in combination with a DNS server that redirects everything to the + * ESP in order to load a HTML page as soon as a phone, tablet etc connects to the ESP. Watch out: + * this will also redirect connections when the ESP is in STA mode, potentially to a hostname that is not + * in the 'official' DNS and so will fail. + * + * @param conn - connection + */ httpd_cgi_state cgiRedirectToHostname(HttpdConnData *connData); -httpd_cgi_state cgiRedirectApClientToHostname(HttpdConnData *connData); +/** + * Redirect to the given URL. + * + * Sets the status code to 302, adds the Location header and a simple redirect text body. + * + * @param conn - connection + * @param newUrl - URL to redirect to + */ void httpdRedirect(HttpdConnData *conn, const char *newUrl); -int httpdUrlDecode(const char *val, int valLen, char *ret, int retLen); -int httpdFindArg(const char *line, const char *arg, char *buff, int buffLen); -httpd_thread_handle_t *httpdInit(const HttpdBuiltInUrl *fixedUrls, struct httpd_options *options); + +/** + * Start the HTTP server + * + * @param fixedUrls - array of defined URLs + * @param options - server options + * @return server thread handle or NULL on error + */ +httpd_thread_handle_t *httpdStart(const HttpdBuiltInUrl *fixedUrls, struct httpd_options *options); + +/** + * Shutdown the server & wait for the thread to end. + * + * @param handle + */ +void httpdShutdown(httpd_thread_handle_t *handle); + +/** + * Join the server thread. + * This is mainly useful in the posix build to block while the server runs. + * + * @param handle + */ void httpdJoin(httpd_thread_handle_t *handle); -const char *httpdGetMimetype(const char *url); -const char *httpdMethodName(httpd_method m); -void httdSetTransferMode(HttpdConnData *conn, int mode); + +/** + * Set transfer mode for the current connection + * + * @param conn + * @param mode - transfer mode + */ +void httdSetTransferMode(HttpdConnData *conn, httpd_transfer_opt mode); + +/** + * Start a HTTP response. Sends the HTTP line and common headers. + * More headers can be added before starting the message body. + * + * @param conn + * @param code - HTTP status code + */ void httpdStartResponse(HttpdConnData *conn, int code); + +/** + * Add a HTTP header + * + * @param conn + * @param field - name + * @param val - value + */ void httpdHeader(HttpdConnData *conn, const char *field, const char *val); + +/** + * End headers, start sending body + * + * @param conn + */ void httpdEndHeaders(HttpdConnData *conn); -int httpdGetHeader(HttpdConnData *conn, const char *header, char *ret, int retLen); + +/** + * Read value of a request header + * + * @param conn + * @param header - name + * @param[out] buff - buffer for the header value, will be zero terminated + * @param buffLen - capacity of the buffer + * @return 1 = OK + */ +int httpdGetHeader(HttpdConnData *conn, const char *header, char *buff, size_t buffLen); /** * Send binary data @@ -193,7 +201,7 @@ int httpdGetHeader(HttpdConnData *conn, const char *header, char *ret, int retLe * @param conn * @param data - data to send * @param len - num bytes. -1 to use strlen. - * @return 1 = success + * @return 1 = OK */ int httpdSend(HttpdConnData *conn, const uint8_t *data, size_t len); @@ -202,7 +210,7 @@ int httpdSend(HttpdConnData *conn, const uint8_t *data, size_t len); * * @param conn * @param data - string - * @return 1 = success + * @return 1 = OK */ static inline int httpdSendStr(HttpdConnData *conn, const char *data) { @@ -215,7 +223,7 @@ static inline int httpdSendStr(HttpdConnData *conn, const char *data) * @param conn * @param data - string * @param len - num bytes - * @return 1 = success + * @return 1 = OK */ static inline int httpdSendStrN(HttpdConnData *conn, const char *data, size_t len) { @@ -223,21 +231,141 @@ static inline int httpdSendStrN(HttpdConnData *conn, const char *data, size_t le } // TODO convert to a general escaped send function -int httpdSend_js(HttpdConnData *conn, const uint8_t *data, ssize_t len); -int httpdSend_html(HttpdConnData *conn, const uint8_t *data, ssize_t len); +/** + * Send text with JSON escaping + * + * @param conn + * @param data - string + * @param len - string length, -1 to use strlen() + * @return 1 = OK + */ +int httpdSend_js(HttpdConnData *conn, const char *data, ssize_t len); + +/** + * Send text with HTML escaping. Escapes quotes and angle brackets. + * + * @param conn + * @param data - string + * @param len - string length, -1 to use strlen() + * @return 1 = OK + */ +int httpdSend_html(HttpdConnData *conn, const char *data, ssize_t len); +/** + * Function to send any data in conn->priv->sendBuff. Do not use in CGIs unless you know what you + * are doing! Also, if you do set conn->cgi to NULL to indicate the connection is closed, do it BEFORE + * calling this. + * Returns false if data could not be sent nor put in backlog. + * + * @param conn + * @return 1 = OK + */ bool httpdFlushSendBuffer(HttpdConnData *conn); + +/** + * Can be called after a CGI function has returned HTTPD_CGI_MORE to + * resume handling an open connection asynchronously + * + * @param conn + */ void httpdContinue(HttpdConnData *conn); + +/** + * Make a connection 'live' so we can do all the things a cgi can do to it. + * + * @param conn + */ void httpdConnSendStart(HttpdConnData *conn); + +/** + * Finish the live-ness of a connection. Always call this after httpdConnStart + * + * @param conn + */ void httpdConnSendFinish(HttpdConnData *conn); + +/** + * Add sensible cache control headers to avoid needless asset reloading. + * + * @param connData + * @param mime - mime type string + */ void httpdAddCacheHeaders(HttpdConnData *connData, const char *mime); +/** + * Get current HTTP backlog size + * + * @param connData + * @return bytes + */ size_t httpGetBacklogSize(const HttpdConnData *connData); + +/** + * Set HTTP response options + * + * @param conn + * @param cors 0 = don't add CORS header + */ void httdResponseOptions(HttpdConnData *conn, int cors); //Platform dependent code should call these. + +/** + * Callback called when the data on a socket has been successfully sent. + * + * @param conn + * @param remIp - remote IP (4 bytes) + * @param remPort - remote port + */ void httpdSentCb(ConnTypePtr conn, const char *remIp, int remPort); -void httpdRecvCb(ConnTypePtr conn, const char *remIp, int remPort, char *data, unsigned short len); + +/** + * Callback called when there's data available on a socket. + * + * @param conn + * @param remIp - remote IP (4 bytes) + * @param remPort - remote port + * @param data - data received. This is a mutable buffer + * @param len - data len + */ +void httpdRecvCb(ConnTypePtr conn, const char *remIp, int remPort, uint8_t *data, unsigned short len); + +/** + * The platform layer should ALWAYS call this function, regardless if the connection is closed by the server + * or by the client. + * + * @param conn + * @param remIp - remote IP (4 bytes) + * @param remPort - remote port + */ void httpdDisconCb(ConnTypePtr conn, const char *remIp, int remPort); + +/** + * Connect callback - a client connected + * + * @param conn + * @param remIp - remote IP (4 bytes) + * @param remPort - remote port + * @return 1 = OK, 0 = client couldn't be served + */ int httpdConnectCb(ConnTypePtr conn, const char *remIp, int remPort); + +/** + * Set server name (Should not be on stack - the pointer must live as long as the server! Const is preferable.) + * + * @param name - new server name + */ void httpdSetName(const char *name); + +/** + * Low level function to close & release a connection + * + * @param conn + */ +void httpdConnRelease(ConnTypePtr conn); + +/** + * Close and retire all sockets. + * Called during httpd shutdown. + */ +void httpdInternalCloseAllSockets(); diff --git a/spritehttpd/lib/espfs/espfs.c b/spritehttpd/lib/espfs/espfs.c index 6309751..75f7768 100644 --- a/spritehttpd/lib/espfs/espfs.c +++ b/spritehttpd/lib/espfs/espfs.c @@ -22,7 +22,7 @@ It's written for use with httpd, but doesn't need to be used as such. #include "espfsformat.h" #include "espfs.h" -#include "logging.h" +#include "httpd-logging.h" // internal fields struct EspFsFile { diff --git a/spritehttpd/src/cgiwebsocket.c b/spritehttpd/src/cgi-websocket.c similarity index 98% rename from spritehttpd/src/cgiwebsocket.c rename to spritehttpd/src/cgi-websocket.c index c0f3c9e..1b1a47b 100644 --- a/spritehttpd/src/cgiwebsocket.c +++ b/spritehttpd/src/cgi-websocket.c @@ -17,8 +17,8 @@ Websocket support for esphttpd. Inspired by https://github.com/dangrie158/ESP-82 #include "utils/sha1.h" #include "utils/base64.h" -#include "cgiwebsocket.h" -#include "logging.h" +#include "cgi-websocket.h" +#include "httpd-logging.h" #define WS_KEY_IDENTIFIER "Sec-WebSocket-Key: " #define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" @@ -334,7 +334,7 @@ httpd_cgi_state cgiWebsocket(HttpdConnData *connData) { char buff[256]; int i; - sha1nfo s; + httpd_sha1nfo s; if (connData->conn == NULL) { //Connection aborted. Clean up. ws_dbg("WS: Cleanup"); @@ -376,13 +376,13 @@ httpd_cgi_state cgiWebsocket(HttpdConnData *connData) ws->conn = connData; //Reply with the right headers. strcat(buff, WS_GUID); - sha1_init(&s); - sha1_write(&s, buff, strlen(buff)); + httpd_sha1_init(&s); + httpd_sha1_write(&s, buff, strlen(buff)); httdSetTransferMode(connData, HTTPD_TRANSFER_NONE); httpdStartResponse(connData, 101); httpdHeader(connData, "Upgrade", "websocket"); httpdHeader(connData, "Connection", "upgrade"); - base64_encode(20, sha1_result(&s), sizeof(buff), buff); + httpd_base64_encode(20, httpd_sha1_result(&s), sizeof(buff), buff); httpdHeader(connData, "Sec-WebSocket-Accept", buff); httpdEndHeaders(connData); //Set data receive handler diff --git a/spritehttpd/src/httpd-auth.c b/spritehttpd/src/httpd-auth.c new file mode 100644 index 0000000..4b94c04 --- /dev/null +++ b/spritehttpd/src/httpd-auth.c @@ -0,0 +1,101 @@ +/* +HTTP auth implementation. Only does basic authentication for now. +*/ + +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Jeroen Domburg wrote this file. As long as you retain + * this notice you can do whatever you want with this stuff. If we meet some day, + * and you think this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + */ + +#include "httpd-auth.h" +#include "httpd.h" +#include "utils/base64.h" +#include + +// base64 increases length by about 33% +#define BA_HDRBUFLEN ((AUTH_MAX_USER_LEN + AUTH_MAX_PASS_LEN + 2) * 2) + +httpd_cgi_state cgiAuthBasic(HttpdConnData *connData) +{ + int r; + char hdr[BA_HDRBUFLEN]; // +2 because of the terminator + colon ? + + if (connData->conn == NULL) { + //Connection aborted. Clean up. + return HTTPD_CGI_DONE; + } + + r = httpdGetHeader(connData, "Authorization", hdr, sizeof(hdr)); + if (r && strncmp(hdr, "Basic", 5) == 0) { + const char *token = hdr + 5; + // discard leading whitepsace + while (isspace(*token)) { + token++; + } + + r = httpd_base64_decode(strlen(token), token, BA_HDRBUFLEN, (uint8_t *) hdr); // Decoding in-place + if (r < 0) { r = 0; } //just clean out string on decode error + hdr[r] = 0; //zero-terminate user:pass string + + char * colon_ptr = strchr(hdr, ':'); + if (colon_ptr) { + *colon_ptr = 0; // null-terminate username + colon_ptr++; + if (((HttpdBasicAuthCb) (connData->cgiArg))(connData, hdr, colon_ptr)) { + return HTTPD_CGI_AUTHENTICATED; + } + } + } + + //Not authenticated. Go bug user with login screen. + httpdStartResponse(connData, 401); + httpdHeader(connData, "Content-Type", "text/plain"); + httpdHeader(connData, "WWW-Authenticate", "Basic realm=\""HTTP_AUTH_REALM"\""); + httpdEndHeaders(connData); + httpdSendStr(connData, "401 Unauthorized."); + //Okay, all done. + return HTTPD_CGI_DONE; +} + + +httpd_cgi_state cgiAuthBearer(HttpdConnData *connData) +{ + int r; + char hdr[AUTH_MAX_TOKEN_LEN + 1]; + + if (connData->conn == NULL) { + //Connection aborted. Clean up. + return HTTPD_CGI_DONE; + } + + r = httpdGetHeader(connData, "Authorization", hdr, sizeof(hdr)); + if (r && strncmp(hdr, "Bearer", 6) == 0) { + // Don't base64-decode, token may not be encoded! + hdr[AUTH_MAX_TOKEN_LEN] = 0; //zero-terminate + + char *token = hdr + 6; + + // discard leading whitepsace + while (isspace(*token)) { + token++; + } + + if (((HttpdBearerAuthCb) (connData->cgiArg))(connData, token)) { + return HTTPD_CGI_AUTHENTICATED; + } + } + + //Not authenticated. Go bug user with login screen. + httpdStartResponse(connData, 401); + httpdHeader(connData, "Content-Type", "text/plain"); + httpdHeader(connData, "WWW-Authenticate", "Basic realm=\""HTTP_AUTH_REALM"\""); + httpdEndHeaders(connData); + httpdSendStr(connData, "401 Unauthorized."); + //Okay, all done. + return HTTPD_CGI_DONE; +} + diff --git a/spritehttpd/src/httpdespfs.c b/spritehttpd/src/httpd-espfs.c similarity index 97% rename from spritehttpd/src/httpdespfs.c rename to spritehttpd/src/httpd-espfs.c index 984fc0c..791219f 100644 --- a/spritehttpd/src/httpdespfs.c +++ b/spritehttpd/src/httpd-espfs.c @@ -15,10 +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 "httpdespfs.h" +#include "httpd-espfs.h" #include "espfs.h" #include "espfsformat.h" -#include "logging.h" +#include "httpd-logging.h" +#include "httpd-utils.h" #define FILE_CHUNK_LEN 1024 @@ -196,8 +197,7 @@ typedef struct { } TplData; -int -tplSend(HttpdConnData *conn, const char *str, int len) +int tplSend(HttpdConnData *conn, const char *str, int len) { if (conn == NULL) { return 0; } TplData *tpd = conn->cgiData; @@ -205,9 +205,9 @@ tplSend(HttpdConnData *conn, const char *str, int len) if (tpd == NULL || tpd->tokEncode == ENCODE_PLAIN) { return httpdSendStrN(conn, str, len); } else if (tpd->tokEncode == ENCODE_HTML) { - return httpdSend_html(conn, (const uint8_t *) str, len); + return httpdSend_html(conn, str, len); } else if (tpd->tokEncode == ENCODE_JS) { - return httpdSend_js(conn, (const uint8_t *) str, len); + return httpdSend_js(conn, str, len); } return 0; } @@ -361,7 +361,7 @@ httpd_cgi_state cgiEspFsTemplate(HttpdConnData *connData) } else { // Add char to the token buf char c = buff[x]; - bool outOfSpace = tpd->tokenPos >= (sizeof(tpd->token) - 1); + bool outOfSpace = tpd->tokenPos >= ((int) sizeof(tpd->token) - 1); if (outOfSpace || (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z') && diff --git a/spritehttpd/src/httpd-loop.c b/spritehttpd/src/httpd-loop.c index e6739ad..d3d7be2 100644 --- a/spritehttpd/src/httpd-loop.c +++ b/spritehttpd/src/httpd-loop.c @@ -4,7 +4,6 @@ Thanks to my collague at Espressif for writing the foundations of this code. */ #include "httpd.h" -#include "platform.h" #include "httpd-platform.h" #include #include @@ -14,10 +13,7 @@ Thanks to my collague at Espressif for writing the foundations of this code. #include #include #include -#include "logging.h" - -static int httpPort; -static int httpMaxConnCt; +#include "httpd-logging.h" struct HttpdConnType { int fd; @@ -28,6 +24,8 @@ struct HttpdConnType { }; static HttpdConnType s_rconn[HTTPD_MAX_CONNECTIONS]; +static uint8_t s_recv_buf[HTTPD_RECV_BUF_LEN]; +static volatile bool s_shutdown_requested = false; static int fd_is_valid(int fd) { @@ -49,39 +47,34 @@ void httpdConnDisconnect(ConnTypePtr conn) conn->needWriteDoneNotif = 1; //because the real close is done in the writable select code } -#define RECV_BUF_SIZE 2048 - void platHttpServerTask(void *pvParameters) { int32_t listenfd; - int32_t remotefd; int32_t len; int32_t ret; - int x; - int maxfdp = 0; - char *precvbuf; + //char *precvbuf; fd_set readset, writeset; - struct sockaddr name; - //struct timeval timeout; struct sockaddr_in server_addr; struct sockaddr_in remote_addr; + int httpPort; - struct httpd_options *options = pvParameters; + s_shutdown_requested = false; + + const struct httpd_options *options = pvParameters; if (options == NULL) { httpPort = 80; } else { httpPort = options->port; } - for (x = 0; x < HTTPD_MAX_CONNECTIONS; x++) { - s_rconn[x].fd = -1; + for (int i = 0; i < HTTPD_MAX_CONNECTIONS; i++) { + s_rconn[i].fd = -1; } /* Construct local address structure */ memset(&server_addr, 0, sizeof(server_addr)); /* Zero out structure */ server_addr.sin_family = AF_INET; /* Internet address family */ server_addr.sin_addr.s_addr = INADDR_ANY; /* Any incoming interface */ - //server_addr.sin_len = sizeof(server_addr); server_addr.sin_port = htons(httpPort); /* Local port */ /* Create socket for incoming connections */ @@ -93,8 +86,11 @@ void platHttpServerTask(void *pvParameters) } } while (listenfd == -1); - /* https://stackoverflow.com/questions/5592747/bind-error-while-recreating-socket */ - int yes=1; + /* + * Allow taking over an old socket after the server was killed or crashed. + * https://stackoverflow.com/questions/5592747/bind-error-while-recreating-socket + */ + const int yes = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) { perror("setsockopt"); } @@ -115,24 +111,37 @@ void platHttpServerTask(void *pvParameters) error("platHttpServerTask: failed to listen!"); httpdPlatDelayMs(1000); } - } while (ret != 0); - info("esphttpd: active and listening to connections."); + info("httpd: listening on http://0.0.0.0:%d/", httpPort); while (1) { + if (s_shutdown_requested) { + info("httpd: Shutting down"); + break; + } + + //dbg("httpd: loop running"); + // clear fdset, and set the select function wait time int socketsFull = 1; - maxfdp = 0; + int maxfdp = 0; FD_ZERO(&readset); FD_ZERO(&writeset); - //timeout.tv_sec = 2; - //timeout.tv_usec = 0; - - for (x = 0; x < HTTPD_MAX_CONNECTIONS; x++) { - if (s_rconn[x].fd != -1) { - FD_SET(s_rconn[x].fd, &readset); - if (s_rconn[x].needWriteDoneNotif) FD_SET(s_rconn[x].fd, &writeset); - if (s_rconn[x].fd > maxfdp) { maxfdp = s_rconn[x].fd; } + + // shutdown flag polling timeout + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + + for (int i = 0; i < HTTPD_MAX_CONNECTIONS; i++) { + if (s_rconn[i].fd != -1) { + FD_SET(s_rconn[i].fd, &readset); + if (s_rconn[i].needWriteDoneNotif) { + FD_SET(s_rconn[i].fd, &writeset); + } + if (s_rconn[i].fd > maxfdp) { + maxfdp = s_rconn[i].fd; + } } else { socketsFull = 0; } @@ -140,112 +149,114 @@ void platHttpServerTask(void *pvParameters) if (!socketsFull) { FD_SET(listenfd, &readset); - if (listenfd > maxfdp) { maxfdp = listenfd; } + if (listenfd > maxfdp) { + maxfdp = listenfd; + } } //polling all exist client handle,wait until readable/writable - ret = select(maxfdp + 1, &readset, &writeset, NULL, NULL);//&timeout + ret = select(maxfdp + 1, &readset, &writeset, NULL, &timeout); if (ret > 0) { //See if we need to accept a new connection if (FD_ISSET(listenfd, &readset)) { len = sizeof(struct sockaddr_in); - remotefd = accept(listenfd, (struct sockaddr *) &remote_addr, (socklen_t *) &len); + const int remotefd = accept(listenfd, (struct sockaddr *) &remote_addr, (socklen_t *) &len); if (remotefd < 0) { warn("platHttpServerTask: Huh? Accept failed."); continue; } - for (x = 0; x < HTTPD_MAX_CONNECTIONS; x++) { if (s_rconn[x].fd == -1) { break; }} - if (x == HTTPD_MAX_CONNECTIONS) { + + // Find a free slot + int socknum; + for (socknum = 0; socknum < HTTPD_MAX_CONNECTIONS; socknum++) { + if (s_rconn[socknum].fd == -1) { + break; + } + } + if (socknum >= HTTPD_MAX_CONNECTIONS) { warn("platHttpServerTask: Huh? Got accept with all slots full."); continue; } - int keepAlive = 1; //enable keepalive - int keepIdle = 60; //60s - int keepInterval = 5; //5s - int keepCount = 3; //retry times + + const int keepAlive = 1; //enable keepalive + const int keepIdle = 60; //60s + const int keepInterval = 5; //5s + const int keepCount = 3; //retry times setsockopt(remotefd, SOL_SOCKET, SO_KEEPALIVE, (void *) &keepAlive, sizeof(keepAlive)); setsockopt(remotefd, IPPROTO_TCP, TCP_KEEPIDLE, (void *) &keepIdle, sizeof(keepIdle)); setsockopt(remotefd, IPPROTO_TCP, TCP_KEEPINTVL, (void *) &keepInterval, sizeof(keepInterval)); setsockopt(remotefd, IPPROTO_TCP, TCP_KEEPCNT, (void *) &keepCount, sizeof(keepCount)); - s_rconn[x].fd = remotefd; - s_rconn[x].needWriteDoneNotif = 0; - s_rconn[x].needsClose = 0; + s_rconn[socknum].fd = remotefd; + s_rconn[socknum].needWriteDoneNotif = 0; + s_rconn[socknum].needsClose = 0; + struct sockaddr name; len = sizeof(name); getpeername(remotefd, &name, (socklen_t *) &len); struct sockaddr_in *piname = (struct sockaddr_in *) &name; - s_rconn[x].port = piname->sin_port; - memcpy(&s_rconn[x].ip, &piname->sin_addr.s_addr, sizeof(s_rconn[x].ip)); + s_rconn[socknum].port = piname->sin_port; + memcpy(&s_rconn[socknum].ip, &piname->sin_addr.s_addr, sizeof(s_rconn[socknum].ip)); - httpdConnectCb(&s_rconn[x], s_rconn[x].ip, s_rconn[x].port); - //os_timer_disarm(&connData[x].conn->stop_watch); - //os_timer_setfn(&connData[x].conn->stop_watch, (os_timer_func_t *)httpserver_conn_watcher, connData[x].conn); - //os_timer_arm(&connData[x].conn->stop_watch, STOP_TIMER, 0); -// dbg("httpserver acpt index %d sockfd %d!", x, remotefd); + httpdConnectCb(&s_rconn[socknum], s_rconn[socknum].ip, s_rconn[socknum].port); } //See if anything happened on the existing connections. - for (x = 0; x < HTTPD_MAX_CONNECTIONS; x++) { + for (int i = 0; i < HTTPD_MAX_CONNECTIONS; i++) { //Skip empty slots - if (s_rconn[x].fd == -1) { continue; } + if (s_rconn[i].fd == -1) { continue; } //Check for write availability first: the read routines may write needWriteDoneNotif while //the select didn't check for that. - if (s_rconn[x].needWriteDoneNotif && FD_ISSET(s_rconn[x].fd, &writeset)) { - s_rconn[x].needWriteDoneNotif = 0; //Do this first, httpdSentCb may write something making this 1 again. - if (s_rconn[x].needsClose) { + if (s_rconn[i].needWriteDoneNotif && FD_ISSET(s_rconn[i].fd, &writeset)) { + s_rconn[i].needWriteDoneNotif = 0; //Do this first, httpdSentCb may write something making this 1 again. + if (s_rconn[i].needsClose) { //Do callback and close fd. - httpdDisconCb(&s_rconn[x], s_rconn[x].ip, s_rconn[x].port); - close(s_rconn[x].fd); - s_rconn[x].fd = -1; + httpdDisconCb(&s_rconn[i], s_rconn[i].ip, s_rconn[i].port); + close(s_rconn[i].fd); + s_rconn[i].fd = -1; } else { - httpdSentCb(&s_rconn[x], s_rconn[x].ip, s_rconn[x].port); + httpdSentCb(&s_rconn[i], s_rconn[i].ip, s_rconn[i].port); } } - if (FD_ISSET(s_rconn[x].fd, &readset)) { - precvbuf = (char *) malloc(RECV_BUF_SIZE); - if (precvbuf == NULL) { - error("platHttpServerTask: memory exhausted!"); - httpdDisconCb(&s_rconn[x], s_rconn[x].ip, s_rconn[x].port); - close(s_rconn[x].fd); - s_rconn[x].fd = -1; - } - ret = (int) recv(s_rconn[x].fd, precvbuf, RECV_BUF_SIZE, 0); + if (FD_ISSET(s_rconn[i].fd, &readset)) { + ret = (int) recv(s_rconn[i].fd, s_recv_buf, HTTPD_RECV_BUF_LEN, 0); if (ret > 0) { //Data received. Pass to httpd. - httpdRecvCb(&s_rconn[x], s_rconn[x].ip, s_rconn[x].port, precvbuf, ret); + httpdRecvCb(&s_rconn[i], s_rconn[i].ip, s_rconn[i].port, s_recv_buf, ret); } else { //recv error,connection close - httpdDisconCb(&s_rconn[x], s_rconn[x].ip, s_rconn[x].port); - close(s_rconn[x].fd); - s_rconn[x].fd = -1; + httpdDisconCb(&s_rconn[i], s_rconn[i].ip, s_rconn[i].port); + httpdConnRelease(&s_rconn[i]); } - if (precvbuf) { free(precvbuf); } } } } } -//Deinit code, not used here. - /*release data connection*/ - for (x = 0; x < HTTPD_MAX_CONNECTIONS; x++) { - //find all valid handle - if (s_connData[x]->conn == NULL) { continue; } - if (s_connData[x]->conn->fd >= 0) { - //os_timer_disarm((os_timer_t *)&connData[x].conn->stop_watch); // ??? - - close(s_connData[x]->conn->fd); - s_connData[x]->conn->fd = -1; - s_connData[x]->conn = NULL; - if (s_connData[x]->cgi != NULL) { s_connData[x]->cgi(s_connData[x]); } //flush cgi data - } - } + httpdInternalCloseAllSockets(); + /*release listen socket*/ close(listenfd); httpdPlatTaskEnd(); } + +void httpdConnRelease(ConnTypePtr conn) +{ + if (conn && conn->fd >= 0) { + close(conn->fd); + conn->fd = -1; + } + + // Don't free it - it's a pointer into the static struct! +} + +void httpdShutdown(httpd_thread_handle_t *handle) +{ + s_shutdown_requested = true; + httpdJoin(handle); +} diff --git a/spritehttpd/src/httpd-utils.c b/spritehttpd/src/httpd-utils.c new file mode 100644 index 0000000..f534d50 --- /dev/null +++ b/spritehttpd/src/httpd-utils.c @@ -0,0 +1,162 @@ +#include "httpd-utils.h" +#include "httpd.h" +#include "httpd-logging.h" + +char httpdHexNibble(uint8_t val) +{ + val &= 0xf; + if (val < 10) { return (char) ('0' + val); } + return (char) ('A' + (val - 10)); +} + + +uint8_t httpdHexVal(char c) +{ + if (c >= '0' && c <= '9') { return c - '0'; } + if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } + if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } + return 0; +} + + +int httpdUrlDecode(const char *val, size_t valLen, char *buff, size_t buffLen) +{ + size_t s = 0, d = 0; + int esced = 0; + char escVal = 0; + while (s < valLen && d < buffLen) { + if (esced == 1) { + escVal = httpdHexVal(val[s]) << 4; + esced = 2; + } else if (esced == 2) { + escVal |= httpdHexVal(val[s]); + buff[d++] = escVal; + esced = 0; + } else if (val[s] == '%') { + esced = 1; + } else if (val[s] == '+') { + buff[d++] = ' '; + } else { + buff[d++] = val[s]; + } + s++; + } + if (d < buffLen) { buff[d] = 0; } + return d; +} + + +int httpdFindArg(const char *line, const char *arg, char *buff, size_t buffLen) +{ + const char *p, *e; + if (line == NULL) { return -1; } + const size_t arglen = strlen(arg); + p = line; + while (p != NULL && *p != '\n' && *p != '\r' && *p != 0) { + router_dbg("findArg: %s", p); + if (strstarts(p, arg) && p[arglen] == '=') { + p += arglen + 1; //move p to start of value + e = strstr(p, "&"); + if (e == NULL) { e = p + strlen(p); } + router_dbg("findArg: val %s len %d", p, (int) (e - p)); + return httpdUrlDecode(p, (int)(e - p), buff, buffLen); + } + p = strstr(p, "&"); + if (p != NULL) { p += 1; } + } + router_error("Finding arg %s in %s: Not found :/", arg, line); + return -1; //not found +} + + +//Struct to keep extension->mime data in +typedef struct { + const char *ext; + const char *mimetype; +} MimeMap; + + +/** + * The mappings from file extensions to mime types. If you need an extra mime type, + * add it here. + */ +static const MimeMap MIME_TYPES[] = { + {"htm", "text/html"}, + {"html", "text/html"}, + {"css", "text/css"}, + {"js", "text/javascript"}, + {"txt", "text/plain"}, + {"csv", "text/csv"}, + {"ico", "image/x-icon"}, + {"jpg", "image/jpeg"}, + {"jpeg", "image/jpeg"}, + {"png", "image/png"}, + {"gif", "image/gif"}, + {"bmp", "image/bmp"}, + {"svg", "image/svg+xml"}, + {"xml", "text/xml"}, + {"json", "application/json"}, + {NULL, "text/html"}, //default value +}; + + +const char *httpdGetMimetype(const char *url) +{ + int i = 0; + //Go find the extension + const char *ext = url + (strlen(url) - 1); + while (ext != url && *ext != '.') { ext--; } + if (*ext == '.') { ext++; } + + while (MIME_TYPES[i].ext != NULL && strcasecmp(ext, MIME_TYPES[i].ext) != 0) { i++; } + + return MIME_TYPES[i].mimetype; +} + + +const char *httpdMethodName(httpd_method m) +{ + switch (m) { + default: + case HTTPD_METHOD_GET: + return "GET"; + case HTTPD_METHOD_POST: + return "POST"; + case HTTPD_METHOD_OPTIONS: + return "OPTIONS"; + case HTTPD_METHOD_PUT: + return "PUT"; + case HTTPD_METHOD_DELETE: + return "DELETE"; + case HTTPD_METHOD_PATCH: + return "PATCH"; + case HTTPD_METHOD_HEAD: + return "HEAD"; + } +} + + +const char *httpdStatusName(int code) +{ + // TODO more codes + switch (code) { + case 200: + return "OK"; + case 301: + return "Moved Permanently"; + case 302: + return "Found"; + case 400: + return "Bad Request"; + case 401: + return "Unauthorized"; + case 403: + return "Forbidden"; + case 404: + return "Not Found"; + default: + if (code >= 500) { return "Server Error"; } + if (code >= 400) { return "Client Error"; } + return "OK"; + } +} diff --git a/spritehttpd/src/httpd.c b/spritehttpd/src/httpd.c index 8a1726e..66d089e 100644 --- a/spritehttpd/src/httpd.c +++ b/spritehttpd/src/httpd.c @@ -15,11 +15,14 @@ Esp8266 http server - core routines #include #include "httpd.h" #include "httpd-platform.h" -#include "logging.h" +#include "httpd-utils.h" +#include "httpd-logging.h" + +static void httpdRetireConn(HttpdConnData *conn); //This gets set at init time. -static const HttpdBuiltInUrl *builtInUrls; -static const char *serverName = HTTPD_SERVERNAME; +static const HttpdBuiltInUrl *s_builtInUrls; +static const char *s_serverName = HTTPD_SERVERNAME; typedef struct HttpSendBacklogItem HttpSendBacklogItem; @@ -54,105 +57,39 @@ struct HttpdPriv { //Connection pool HttpdConnData *s_connData[HTTPD_MAX_CONNECTIONS]; -//Struct to keep extension->mime data in -typedef struct { - const char *ext; - const char *mimetype; -} MimeMap; - - -//#define RSTR(a) ((const char)(a)) - -//The mappings from file extensions to mime types. If you need an extra mime type, -//add it here. -static const MimeMap mimeTypes[] = { - {"htm", "text/html"}, - {"html", "text/html"}, - {"css", "text/css"}, - {"js", "text/javascript"}, - {"txt", "text/plain"}, - {"csv", "text/csv"}, - {"ico", "image/x-icon"}, - {"jpg", "image/jpeg"}, - {"jpeg", "image/jpeg"}, - {"png", "image/png"}, - {"gif", "image/gif"}, - {"bmp", "image/bmp"}, - {"svg", "image/svg+xml"}, - {"xml", "text/xml"}, - {"json", "application/json"}, - {NULL, "text/html"}, //default value -}; - -//Returns a static char* to a mime type for a given url to a file. -const char *httpdGetMimetype(const char *url) +void httpdInternalCloseAllSockets() { - int i = 0; - //Go find the extension - const char *ext = url + (strlen(url) - 1); - while (ext != url && *ext != '.') { ext--; } - if (*ext == '.') { ext++; } - - while (mimeTypes[i].ext != NULL && strcasecmp(ext, mimeTypes[i].ext) != 0) { i++; } - - return mimeTypes[i].mimetype; -} + httpdPlatLock(); + /*release data connection*/ + for (int i = 0; i < HTTPD_MAX_CONNECTIONS; i++) { + //find all valid handle + if (s_connData[i]->conn == NULL) { + continue; + } -const char *httpdMethodName(httpd_method m) -{ - switch (m) { - default: - case HTTPD_METHOD_GET: - return "GET"; - case HTTPD_METHOD_POST: - return "POST"; - case HTTPD_METHOD_OPTIONS: - return "OPTIONS"; - case HTTPD_METHOD_PUT: - return "PUT"; - case HTTPD_METHOD_DELETE: - return "DELETE"; - case HTTPD_METHOD_PATCH: - return "PATCH"; - case HTTPD_METHOD_HEAD: - return "HEAD"; - } -} + if (s_connData[i]->cgi != NULL) { + //flush cgi data + s_connData[i]->cgi(s_connData[i]); + s_connData[i]->cgi = NULL; + } -const char *code2str(int code) -{ - switch (code) { - case 200: - return "OK"; - case 301: - return "Moved Permanently"; - case 302: - return "Found"; - case 403: - return "Forbidden"; - case 400: - return "Bad Request"; - case 404: - return "Not Found"; - default: - if (code >= 500) { return "Server Error"; } - if (code >= 400) { return "Client Error"; } - return "OK"; + httpdConnRelease(s_connData[i]->conn); + httpdRetireConn(s_connData[i]); + s_connData[i] = NULL; } + httpdPlatUnlock(); } -/** - * Add sensible cache control headers to avoid needless asset reloading - * - * @param connData - * @param mime - mime type string - */ void httpdAddCacheHeaders(HttpdConnData *connData, const char *mime) { - if (streq(mime, "text/html")) { return; } - if (streq(mime, "text/plain")) { return; } - if (streq(mime, "text/csv")) { return; } - if (streq(mime, "application/json")) { return; } + // TODO make this extensible + if (streq(mime, "text/html") + || streq(mime, "text/plain") + || streq(mime, "text/csv") + || streq(mime, "application/json") + ) { + return; + } httpdHeader(connData, "Cache-Control", "max-age=7200, public, must-revalidate"); } @@ -214,75 +151,9 @@ static void httpdRetireConn(HttpdConnData *conn) } } -//Stupid li'l helper function that returns the value of a hex char. -static char httpdHexVal(char c) -{ - if (c >= '0' && c <= '9') { return c - '0'; } - if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } - if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } - return 0; -} - -//Decode a percent-encoded value. -//Takes the valLen bytes stored in val, and converts it into at most retLen bytes that -//are stored in the ret buffer. Returns the actual amount of bytes used in ret. Also -//zero-terminates the ret buffer. -int httpdUrlDecode(const char *val, int valLen, char *ret, int retLen) -{ - int s = 0, d = 0; - int esced = 0; - char escVal = 0; - while (s < valLen && d < retLen) { - if (esced == 1) { - escVal = httpdHexVal(val[s]) << 4; - esced = 2; - } else if (esced == 2) { - escVal |= httpdHexVal(val[s]); - ret[d++] = escVal; - esced = 0; - } else if (val[s] == '%') { - esced = 1; - } else if (val[s] == '+') { - ret[d++] = ' '; - } else { - ret[d++] = val[s]; - } - s++; - } - if (d < retLen) { ret[d] = 0; } - return d; -} - -//Find a specific arg in a string of get- or post-data. -//Line is the string of post/get-data, arg is the name of the value to find. The -//zero-terminated result is written in buff, with at most buffLen bytes used. The -//function returns the length of the result, or -1 if the value wasn't found. The -//returned string will be urldecoded already. -int httpdFindArg(const char *line, const char *arg, char *buff, int buffLen) -{ - const char *p, *e; - if (line == NULL) { return -1; } - const int arglen = (int) strlen(arg); - p = line; - while (p != NULL && *p != '\n' && *p != '\r' && *p != 0) { - router_dbg("findArg: %s", p); - if (strstarts(p, arg) && p[arglen] == '=') { - p += arglen + 1; //move p to start of value - e = strstr(p, "&"); - if (e == NULL) { e = p + strlen(p); } - router_dbg("findArg: val %s len %d", p, (int) (e - p)); - return httpdUrlDecode(p, (int)(e - p), buff, buffLen); - } - p = strstr(p, "&"); - if (p != NULL) { p += 1; } - } - router_error("Finding arg %s in %s: Not found :/", arg, line); - return -1; //not found -} - //Get the value of a certain header in the HTTP client head //Returns true when found, false when not found. -int httpdGetHeader(HttpdConnData *conn, const char *header, char *ret, int retLen) +int httpdGetHeader(HttpdConnData *conn, const char *header, char *buff, size_t buffLen) { char *p = conn->priv->head; p = p + strlen(p) + 1; //skip GET/POST part @@ -296,12 +167,12 @@ int httpdGetHeader(HttpdConnData *conn, const char *header, char *ret, int retLe //Skip past spaces after the colon while (*p == ' ') { p++; } //Copy from p to end - while (*p != 0 && *p != '\r' && *p != '\n' && retLen > 1) { - *ret++ = *p++; - retLen--; + while (*p != 0 && *p != '\r' && *p != '\n' && buffLen > 1) { + *buff++ = *p++; + buffLen--; } //Zero-terminate string - *ret = 0; + *buff = 0; //All done :) return 1; } @@ -310,7 +181,7 @@ int httpdGetHeader(HttpdConnData *conn, const char *header, char *ret, int retLe return 0; } -void httdSetTransferMode(HttpdConnData *conn, int mode) +void httdSetTransferMode(HttpdConnData *conn, httpd_transfer_opt mode) { if (mode == HTTPD_TRANSFER_CLOSE) { conn->priv->flags &= ~HFL_CHUNKED; @@ -333,19 +204,27 @@ void httdResponseOptions(HttpdConnData *conn, int cors) void httpdStartResponse(HttpdConnData *conn, int code) { char buff[256]; - int l; - const char *connStr = "Connection: close\r\n"; - if (conn->priv->flags & HFL_CHUNKED) { connStr = "Transfer-Encoding: chunked\r\n"; } - if (conn->priv->flags & HFL_NOCONNECTIONSTR) { connStr = ""; } + size_t l; + const char *connStr = ""; + + if (!(conn->priv->flags & HFL_NOCONNECTIONSTR)) { + if (conn->priv->flags & HFL_CHUNKED) { + connStr = "Transfer-Encoding: chunked\r\n"; + } else { + connStr = "Connection: close\r\n"; + } + } + l = sprintf(buff, "HTTP/1.%d %d %s\r\nServer: %s\r\n%s", (conn->priv->flags & HFL_HTTP11) ? 1 : 0, code, - code2str(code), - serverName, + httpdStatusName(code), + s_serverName, connStr); + httpdSendStrN(conn, buff, l); - if (0 == (conn->priv->flags & HFL_NOCORS)) { + if (!(conn->priv->flags & HFL_NOCORS)) { // CORS headers httpdSendStr(conn, "Access-Control-Allow-Origin: *\r\n"); httpdSendStr(conn, "Access-Control-Allow-Methods: GET,POST,OPTIONS\r\n"); @@ -463,19 +342,12 @@ int httpdSend(HttpdConnData *conn, const uint8_t *data, size_t len) return 1; } -static char httpdHexNibble(int val) -{ - val &= 0xf; - if (val < 10) { return (char) ('0' + val); } - return (char) ('A' + (val - 10)); -} - #define httpdSend_orDie(conn, data, len) do { if (!httpdSend((conn), (const uint8_t *)(data), (len))) return false; } while (0) #define httpdSendStr_orDie(conn, data) do { if (!httpdSendStr((conn), (data))) return false; } while (0) /* encode for HTML. returns 0 or 1 - 1 = success */ -int httpdSend_html(HttpdConnData *conn, const uint8_t *data, ssize_t len) +int httpdSend_html(HttpdConnData *conn, const char *data, ssize_t len) { int start = 0, end = 0; uint8_t c; @@ -506,7 +378,7 @@ int httpdSend_html(HttpdConnData *conn, const uint8_t *data, ssize_t len) } /* encode for JS. returns 0 or 1 - 1 = success */ -int httpdSend_js(HttpdConnData *conn, const uint8_t *data, ssize_t len) +int httpdSend_js(HttpdConnData *conn, const char *data, ssize_t len) { int start = 0, end = 0; uint8_t c; @@ -710,9 +582,9 @@ static void httpdProcessRequest(HttpdConnData *conn) //See if we can find a CGI that's happy to handle the request. while (1) { //Look up URL in the built-in URL table. - while (builtInUrls[i].url != NULL) { + while (s_builtInUrls[i].url != NULL) { int match = 0; - const char *route = builtInUrls[i].url; + const char *route = s_builtInUrls[i].url; //See if there's a literal match if (streq(route, conn->url)) { match = 1; } //See if there's a wildcard match (*) @@ -729,14 +601,14 @@ static void httpdProcessRequest(HttpdConnData *conn) if (match) { router_dbg("Matched route #%d, url=%s", i, route); conn->cgiData = NULL; - conn->cgi = builtInUrls[i].cgiCb; - conn->cgiArg = builtInUrls[i].cgiArg; - conn->cgiArg2 = builtInUrls[i].cgiArg2; + conn->cgi = s_builtInUrls[i].cgiCb; + conn->cgiArg = s_builtInUrls[i].cgiArg; + conn->cgiArg2 = s_builtInUrls[i].cgiArg2; break; } i++; } - if (builtInUrls[i].url == NULL) { + if (s_builtInUrls[i].url == NULL) { //Drat, we're at the end of the URL table. This usually shouldn't happen. Well, just //generate a built-in 404 to handle this. router_warn("%s not found. 404!", conn->url); @@ -845,14 +717,14 @@ static void httpdParseHeader(char *h, HttpdConnData *conn) } else { conn->post->buffSize = conn->post->len; } - http_dbg("Mallocced buffer for %d + 1 bytes of post data.", conn->post->buffSize); + 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("...failed!"); return; } conn->post->buffLen = 0; - } else if (strstarts(h, "Content-Type: ")) { + } 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; @@ -896,7 +768,7 @@ void httpdConnSendFinish(HttpdConnData *conn) } //Callback called when there's data available on a socket. -void httpdRecvCb(ConnTypePtr rconn, const char *remIp, int remPort, char *data, unsigned short len) +void httpdRecvCb(ConnTypePtr rconn, const char *remIp, int remPort, uint8_t *data, unsigned short len) { int x, r; char *p, *e; @@ -937,7 +809,7 @@ void httpdRecvCb(ConnTypePtr rconn, const char *remIp, int remPort, char *data, //ToDo: return http error code 431 (request header too long) if this happens if (conn->priv->headPos != HTTPD_MAX_HEAD_LEN) { conn->priv->head[conn->priv->headPos++] = data[x]; } conn->priv->head[conn->priv->headPos] = 0; - //Scan for /r/n/r/n. Receiving this indicate the headers end. + //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; @@ -962,7 +834,7 @@ void httpdRecvCb(ConnTypePtr rconn, const char *remIp, int remPort, char *data, conn->post->buff[conn->post->buffLen++] = data[x]; conn->post->received++; conn->hostName = NULL; - if (conn->post->buffLen >= conn->post->buffSize || 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 //Process the data @@ -994,7 +866,9 @@ void httpdRecvCb(ConnTypePtr rconn, const char *remIp, int remPort, char *data, } } } - if (conn->conn) { httpdFlushSendBuffer(conn); } + if (conn->conn) { + httpdFlushSendBuffer(conn); + } httpdPlatFree(sendBuff); httpdPlatUnlock(); } @@ -1022,13 +896,18 @@ int httpdConnectCb(ConnTypePtr conn, const char *remIp, int remPort) int i; httpdPlatLock(); //Find empty conndata in pool - for (i = 0; i < HTTPD_MAX_CONNECTIONS; i++) { if (s_connData[i] == NULL) { break; }} - http_info("Conn req from %d.%d.%d.%d:%d, using pool slot %d", remIp[0] & 0xff, remIp[1] & 0xff, remIp[2] & 0xff, remIp[3] & 0xff, remPort, i); - if (i == HTTPD_MAX_CONNECTIONS) { + for (i = 0; i < HTTPD_MAX_CONNECTIONS; i++) { + if (s_connData[i] == NULL) { + break; + } + } + http_info("Conn req from %d.%d.%d.%d:%d, using pool slot %d", remIp[0], remIp[1], remIp[2], remIp[3], remPort, i); + if (i >= HTTPD_MAX_CONNECTIONS) { http_error("Aiee, conn pool overflow!"); httpdPlatUnlock(); return 0; } + s_connData[i] = httpdPlatMalloc(sizeof(HttpdConnData)); if (s_connData[i] == NULL) { http_warn("Out of memory allocating connData!"); @@ -1063,14 +942,14 @@ int httpdConnectCb(ConnTypePtr conn, const char *remIp, int remPort) } //Httpd initialization routine. Call this to kick off webserver functionality. -httpd_thread_handle_t *httpdInit(const HttpdBuiltInUrl *fixedUrls, struct httpd_options *options) +httpd_thread_handle_t *httpdStart(const HttpdBuiltInUrl *fixedUrls, struct httpd_options *options) { int i; for (i = 0; i < HTTPD_MAX_CONNECTIONS; i++) { s_connData[i] = NULL; } - builtInUrls = fixedUrls; + s_builtInUrls = fixedUrls; httpdPlatInit(); @@ -1081,14 +960,12 @@ httpd_thread_handle_t *httpdInit(const HttpdBuiltInUrl *fixedUrls, struct httpd_ void httpdJoin(httpd_thread_handle_t *handle) { - httpdPlatJoin(handle); + if (handle) { + httpdPlatJoin(handle); + } } -/** - * Set server name (must be constant / strdup) - * @param name - */ void httpdSetName(const char *name) { - serverName = name; + s_serverName = name; } diff --git a/spritehttpd/src/port/httpd-posix.c b/spritehttpd/src/port/httpd-posix.c index 1c15d9e..6f209ab 100644 --- a/spritehttpd/src/port/httpd-posix.c +++ b/spritehttpd/src/port/httpd-posix.c @@ -1,12 +1,9 @@ -#include "httpd.h" #include "httpd-platform.h" #include #include #include #include -#define HTTPD_STACKSIZE 4096 // TODO - static pthread_mutex_t Mutex; static pthread_mutexattr_t MutexAttr; @@ -34,6 +31,7 @@ void httpdPlatTaskEnd() void httpdPlatDisableTimeout(ConnTypePtr conn) { //Unimplemented + (void) conn; } void httpdPlatInit() { diff --git a/spritehttpd/src/utils/base64.c b/spritehttpd/src/utils/base64.c index 2b8aba0..1ee50bb 100644 --- a/spritehttpd/src/utils/base64.c +++ b/spritehttpd/src/utils/base64.c @@ -23,24 +23,8 @@ static const int base64dec_tab[256] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, }; -#if 0 -static int base64decode(const char in[4], char out[3]) { - uint8_t v[4]; - - v[0]=base64dec_tab[(unsigned)in[0]]; - v[1]=base64dec_tab[(unsigned)in[1]]; - v[2]=base64dec_tab[(unsigned)in[2]]; - v[3]=base64dec_tab[(unsigned)in[3]]; - - out[0]=(v[0]<<2)|(v[1]>>4); - out[1]=(v[1]<<4)|(v[2]>>2); - out[2]=(v[2]<<6)|(v[3]); - return (v[0]|v[1]|v[2]|v[3])!=255 ? in[3]=='=' ? in[2]=='=' ? 1 : 2 : 3 : 0; -} -#endif - /* decode a base64 string in one shot */ -int __attribute__((weak)) base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out) +int __attribute__((weak)) httpd_base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out) { unsigned int ii, io; uint32_t v; @@ -70,16 +54,7 @@ int __attribute__((weak)) base64_decode(size_t in_len, const char *in, size_t ou static const uint8_t base64enc_tab[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -#if 0 -void base64encode(const unsigned char in[3], unsigned char out[4], int count) { - out[0]=base64enc_tab[(in[0]>>2)]; - out[1]=base64enc_tab[((in[0]&3)<<4)|(in[1]>>4)]; - out[2]=count<2 ? '=' : base64enc_tab[((in[1]&15)<<2)|(in[2]>>6)]; - out[3]=count<3 ? '=' : base64enc_tab[(in[2]&63)]; -} -#endif - -int __attribute__((weak)) base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out) +int __attribute__((weak)) httpd_base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out) { unsigned ii, io; uint32_t v; diff --git a/spritehttpd/src/utils/base64.h b/spritehttpd/src/utils/base64.h index bd862a4..0eaaad3 100644 --- a/spritehttpd/src/utils/base64.h +++ b/spritehttpd/src/utils/base64.h @@ -2,5 +2,7 @@ #include -int base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out); -int base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out); +int httpd_base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out); +int httpd_base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out); + +// The implementations in the c file are WEAK to allow overriding in case the same function already exists elsewhere in the system. diff --git a/spritehttpd/src/utils/sha1.c b/spritehttpd/src/utils/sha1.c index bf75428..9f98300 100644 --- a/spritehttpd/src/utils/sha1.c +++ b/spritehttpd/src/utils/sha1.c @@ -10,7 +10,10 @@ //according to http://ip.cadence.com/uploads/pdf/xtensalx_overview_handbook.pdf // the cpu is normally defined as little ending, but can be big endian too. // for the esp this seems to work -//#define SHA_BIG_ENDIAN + +#if defined(__BYTE_ORDER__)&&(__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#define SHA_BIG_ENDIAN +#endif /* code */ #define SHA1_K0 0x5a827999 @@ -18,7 +21,7 @@ #define SHA1_K40 0x8f1bbcdc #define SHA1_K60 0xca62c1d6 -void sha1_init(sha1nfo *s) +void httpd_sha1_init(httpd_sha1nfo *s) { s->state[0] = 0x67452301; s->state[1] = 0xefcdab89; @@ -29,12 +32,12 @@ void sha1_init(sha1nfo *s) s->bufferOffset = 0; } -uint32_t sha1_rol32(uint32_t number, uint8_t bits) +static uint32_t sha1_rol32(uint32_t number, uint8_t bits) { return ((number << bits) | (number >> (32 - bits))); } -void sha1_hashBlock(sha1nfo *s) +static void sha1_hashBlock(httpd_sha1nfo *s) { uint8_t i; uint32_t a, b, c, d, e, t; @@ -72,7 +75,7 @@ void sha1_hashBlock(sha1nfo *s) s->state[4] += e; } -void sha1_addUncounted(sha1nfo *s, uint8_t data) +static void sha1_addUncounted(httpd_sha1nfo *s, uint8_t data) { uint8_t *const b = (uint8_t *) s->buffer; #ifdef SHA_BIG_ENDIAN @@ -87,20 +90,20 @@ void sha1_addUncounted(sha1nfo *s, uint8_t data) } } -void sha1_writebyte(sha1nfo *s, uint8_t data) +void httpd_sha1_writebyte(httpd_sha1nfo *s, uint8_t data) { ++s->byteCount; sha1_addUncounted(s, data); } -void sha1_write(sha1nfo *s, const char *data, size_t len) +void httpd_sha1_write(httpd_sha1nfo *s, const char *data, size_t len) { - for (; len--;) { sha1_writebyte(s, (uint8_t) *data++); } + for (; len--;) { httpd_sha1_writebyte(s, (uint8_t) *data++); } } -void sha1_pad(sha1nfo *s) +static void sha1_pad(httpd_sha1nfo *s) { - // Implement SHA-1 padding (fips180-2 §5.1.1) + // Implement SHA-1 padding (fips180-2 §5.1.1) // Pad with 0x80 followed by 0x00 until the end of the block sha1_addUncounted(s, 0x80); @@ -117,7 +120,7 @@ void sha1_pad(sha1nfo *s) sha1_addUncounted(s, s->byteCount << 3); } -uint8_t *sha1_result(sha1nfo *s) +uint8_t *httpd_sha1_result(httpd_sha1nfo *s) { // Pad to complete the last block sha1_pad(s); @@ -141,34 +144,34 @@ uint8_t *sha1_result(sha1nfo *s) #define HMAC_IPAD 0x36 #define HMAC_OPAD 0x5c -void sha1_initHmac(sha1nfo *s, const uint8_t *key, int keyLength) +void httpd_sha1_initHmac(httpd_sha1nfo *s, const uint8_t *key, size_t keyLength) { uint8_t i; memset(s->keyBuffer, 0, BLOCK_LENGTH); if (keyLength > BLOCK_LENGTH) { // Hash long keys - sha1_init(s); - for (; keyLength--;) { sha1_writebyte(s, *key++); } - memcpy(s->keyBuffer, sha1_result(s), HASH_LENGTH); + httpd_sha1_init(s); + for (; keyLength--;) { httpd_sha1_writebyte(s, *key++); } + memcpy(s->keyBuffer, httpd_sha1_result(s), HASH_LENGTH); } else { // Block length keys are used as is memcpy(s->keyBuffer, key, keyLength); } // Start inner hash - sha1_init(s); + httpd_sha1_init(s); for (i = 0; i < BLOCK_LENGTH; i++) { - sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_IPAD); + httpd_sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_IPAD); } } -uint8_t *sha1_resultHmac(sha1nfo *s) +uint8_t *httpd_sha1_resultHmac(httpd_sha1nfo *s) { uint8_t i; // Complete inner hash - memcpy(s->innerHash, sha1_result(s), HASH_LENGTH); + memcpy(s->innerHash, httpd_sha1_result(s), HASH_LENGTH); // Calculate outer hash - sha1_init(s); - for (i = 0; i < BLOCK_LENGTH; i++) { sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_OPAD); } - for (i = 0; i < HASH_LENGTH; i++) { sha1_writebyte(s, s->innerHash[i]); } - return sha1_result(s); + httpd_sha1_init(s); + for (i = 0; i < BLOCK_LENGTH; i++) { httpd_sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_OPAD); } + for (i = 0; i < HASH_LENGTH; i++) { httpd_sha1_writebyte(s, s->innerHash[i]); } + return httpd_sha1_result(s); } diff --git a/spritehttpd/src/utils/sha1.h b/spritehttpd/src/utils/sha1.h index 8a99d49..f6a0ba2 100644 --- a/spritehttpd/src/utils/sha1.h +++ b/spritehttpd/src/utils/sha1.h @@ -3,32 +3,32 @@ #define HASH_LENGTH 20 #define BLOCK_LENGTH 64 -typedef struct sha1nfo { +typedef struct httpd_sha1nfo { uint32_t buffer[BLOCK_LENGTH/4]; uint32_t state[HASH_LENGTH/4]; uint32_t byteCount; uint8_t bufferOffset; uint8_t keyBuffer[BLOCK_LENGTH]; uint8_t innerHash[HASH_LENGTH]; -} sha1nfo; +} httpd_sha1nfo; /* public API - prototypes - TODO: doxygen*/ /** */ -void sha1_init(sha1nfo *s); +void httpd_sha1_init(httpd_sha1nfo *s); /** */ -void sha1_writebyte(sha1nfo *s, uint8_t data); +void httpd_sha1_writebyte(httpd_sha1nfo *s, uint8_t data); /** */ -void sha1_write(sha1nfo *s, const char *data, size_t len); +void httpd_sha1_write(httpd_sha1nfo *s, const char *data, size_t len); /** */ -uint8_t* sha1_result(sha1nfo *s); +uint8_t* httpd_sha1_result(httpd_sha1nfo *s); /** */ -void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength); +void httpd_sha1_initHmac(httpd_sha1nfo *s, const uint8_t* key, size_t keyLength); /** */ -uint8_t* sha1_resultHmac(sha1nfo *s); +uint8_t* httpd_sha1_resultHmac(httpd_sha1nfo *s);