refactoring, add doc comments

master
Ondřej Hruška 2 years ago
parent 5acf2fe4e4
commit deb2fe6d93
  1. 35
      demo/server_demo.c
  2. 0
      espfsbuilder/httpd-logging.h
  3. 10
      spritehttpd/Makefile
  4. 19
      spritehttpd/include/auth.h
  5. 0
      spritehttpd/include/cgi-websocket.h
  6. 44
      spritehttpd/include/httpd-auth.h
  7. 0
      spritehttpd/include/httpd-espfs.h
  8. 0
      spritehttpd/include/httpd-logging.h
  9. 12
      spritehttpd/include/httpd-platform.h
  10. 105
      spritehttpd/include/httpd-types.h
  11. 77
      spritehttpd/include/httpd-utils.h
  12. 330
      spritehttpd/include/httpd.h
  13. 2
      spritehttpd/lib/espfs/espfs.c
  14. 12
      spritehttpd/src/cgi-websocket.c
  15. 101
      spritehttpd/src/httpd-auth.c
  16. 14
      spritehttpd/src/httpd-espfs.c
  17. 181
      spritehttpd/src/httpd-loop.c
  18. 162
      spritehttpd/src/httpd-utils.c
  19. 283
      spritehttpd/src/httpd.c
  20. 4
      spritehttpd/src/port/httpd-posix.c
  21. 29
      spritehttpd/src/utils/base64.c
  22. 6
      spritehttpd/src/utils/base64.h
  23. 49
      spritehttpd/src/utils/sha1.c
  24. 16
      spritehttpd/src/utils/sha1.h

@ -1,19 +1,16 @@
#include <stdio.h> #include <stdio.h>
#include "httpd.h"
#include "httpd-utils.h"
#include "httpdespfs.h"
#include <httpd.h>
#include <cgiwebsocket.h>
#include <httpdespfs.h>
#include <auth.h>
#include <signal.h> #include <signal.h>
#include <unistd.h>
#include "logging.h" #include "httpd.h"
#include "httpd-utils.h"
#include "httpd-espfs.h"
extern unsigned char espfs_image[]; extern unsigned char espfs_image[];
extern unsigned int espfs_image_len; extern unsigned int espfs_image_len;
httpd_thread_handle_t *s_serverHandle = NULL;
/** "About" page */ /** "About" page */
httpd_cgi_state tplIndex(HttpdConnData *connData, char *token, void **arg) 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() 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 // prevent abort on sigpipe
sigaction(SIGPIPE, &(struct sigaction) {sigpipe_handler}, NULL); sigaction(SIGPIPE, &(struct sigaction) {{sigpipe_handler}}, NULL);
struct httpd_options opts = { struct httpd_options opts = {
.port = 8080, .port = 8080,
}; };
httpd_thread_handle_t *handle = httpdInit(routes, &opts); s_serverHandle = httpdStart(routes, &opts);
httpdSetName("ServerName"); httpdSetName("SpriteHTTPD Server Demo");
httpdJoin(handle);
httpdJoin(s_serverHandle);
return 0; return 0;
} }

@ -20,18 +20,18 @@ LIB_SOURCES = ${PORT_SOURCES} \
lib/heatshrink/heatshrink_decoder.c \ lib/heatshrink/heatshrink_decoder.c \
src/utils/base64.c \ src/utils/base64.c \
src/utils/sha1.c \ src/utils/sha1.c \
src/httpdespfs.c \
src/httpd.c \ src/httpd.c \
src/httpd-espfs.c \
src/httpd-auth.c \
src/httpd-utils.c \
src/httpd-loop.c \ src/httpd-loop.c \
src/cgiwebsocket.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
# TODO check what these mean LIB_CFLAGS = -fPIC -Wall -Wextra -c -Os -ggdb -std=gnu99 -DGIT_HASH='"$(shell git rev-parse --short HEAD)"'
#LIB_CFLAGS = -fPIC -Wall -Wextra -c
LIB_CFLAGS = -fPIC -Wall -Wextra -c -Og -g
OBJ_DIR=./obj OBJ_DIR=./obj

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

@ -0,0 +1,44 @@
#pragma once
#include "httpd-types.h"
#include <stdbool.h>
#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);

@ -4,17 +4,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include "httpd-utils.h" #include "httpd-types.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;
#define httpd_printf(fmt, ...) printf(fmt, ##__VA_ARGS__) #define httpd_printf(fmt, ...) printf(fmt, ##__VA_ARGS__)

@ -0,0 +1,105 @@
/**
* Type definitions used in the http server
*/
#pragma once
#include <stdint.h>
#include <stddef.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;
/**
* 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;

@ -1,6 +1,12 @@
/**
* Utility functions for users and internal use of the HTTP server
*/
#pragma once #pragma once
#include <stdint.h>
#include <string.h> #include <string.h>
#include "httpd-types.h"
// Custom helpers // Custom helpers
#define streq(a, b) (strcmp((const char*)(a), (const char*)(b)) == 0) #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 strstarts(a, b) strneq((a), (b), (int)strlen((b)))
#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)
/**
* 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);

@ -3,8 +3,10 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <string.h>
#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"
#ifndef GIT_HASH #ifndef GIT_HASH
#define GIT_HASH "unknown" #define GIT_HASH "unknown"
@ -15,7 +17,7 @@
// default servername // default servername
#ifndef HTTPD_SERVERNAME #ifndef HTTPD_SERVERNAME
#define HTTPD_SERVERNAME "esp8266-httpd " HTTPDVER #define HTTPD_SERVERNAME "SpriteHTTPD " HTTPDVER
#endif #endif
//Max length of request head. This is statically allocated for each connection. //Max length of request head. This is statically allocated for each connection.
@ -33,6 +35,11 @@
#define HTTPD_MAX_SENDBUFF_LEN 2048 #define HTTPD_MAX_SENDBUFF_LEN 2048
#endif #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 //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 //layer is prone to do), we put it in a backlog that is dynamically malloc'ed. This defines the max
//size of the backlog. //size of the backlog.
@ -49,91 +56,6 @@
#define HTTPD_MAX_CONNECTIONS 4 #define HTTPD_MAX_CONNECTIONS 4
#endif #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 // macros for defining HttpdBuiltInUrl's
/** Route with a CGI handler and two arguments */ /** 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)) #define ROUTE_REDIRECT(path, target) ROUTE_CGI_ARG((path), cgiRedirect, (const char*)(target))
/** Following routes are basic-auth protected */ /** 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 */ /** Websocket endpoint */
#define ROUTE_WS(path, callback) ROUTE_CGI_ARG((path), cgiWebsocket, (WsConnectedCb)(callback)) #define ROUTE_WS(path, callback) ROUTE_CGI_ARG((path), cgiWebsocket, (WsConnectedCb)(callback))
@ -166,26 +91,109 @@ typedef struct {
/** Catch-all filesystem route */ /** Catch-all filesystem route */
#define ROUTE_FILESYSTEM() ROUTE_CGI("*", cgiEspFsHook) #define ROUTE_FILESYSTEM() ROUTE_CGI("*", cgiEspFsHook)
/** Marker for the end of the route list */
#define ROUTE_END() {NULL, NULL, NULL, NULL} #define ROUTE_END() {NULL, NULL, NULL, NULL}
/**
* Get the server version string
*
* @return version
*/
const char *httpdGetVersion(void); const char *httpdGetVersion(void);
/**
* Use this as a cgi function to redirect one url to another.
*/
httpd_cgi_state cgiRedirect(HttpdConnData *connData); 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 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); 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); 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); 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); void httpdHeader(HttpdConnData *conn, const char *field, const char *val);
/**
* End headers, start sending body
*
* @param conn
*/
void httpdEndHeaders(HttpdConnData *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 * Send binary data
@ -193,7 +201,7 @@ int httpdGetHeader(HttpdConnData *conn, const char *header, char *ret, int retLe
* @param conn * @param conn
* @param data - data to send * @param data - data to send
* @param len - num bytes. -1 to use strlen. * @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); 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 conn
* @param data - string * @param data - string
* @return 1 = success * @return 1 = OK
*/ */
static inline int httpdSendStr(HttpdConnData *conn, const char *data) 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 conn
* @param data - string * @param data - string
* @param len - num bytes * @param len - num bytes
* @return 1 = success * @return 1 = OK
*/ */
static inline int httpdSendStrN(HttpdConnData *conn, const char *data, size_t len) 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 // 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); 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); 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); void httpdConnSendStart(HttpdConnData *conn);
/**
* Finish the live-ness of a connection. Always call this after httpdConnStart
*
* @param conn
*/
void httpdConnSendFinish(HttpdConnData *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); void httpdAddCacheHeaders(HttpdConnData *connData, const char *mime);
/**
* Get current HTTP backlog size
*
* @param connData
* @return bytes
*/
size_t httpGetBacklogSize(const HttpdConnData *connData); 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); void httdResponseOptions(HttpdConnData *conn, int cors);
//Platform dependent code should call these. //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 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); 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); 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); 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();

@ -22,7 +22,7 @@ It's written for use with httpd, but doesn't need to be used as such.
#include "espfsformat.h" #include "espfsformat.h"
#include "espfs.h" #include "espfs.h"
#include "logging.h" #include "httpd-logging.h"
// internal fields // internal fields
struct EspFsFile { struct EspFsFile {

@ -17,8 +17,8 @@ Websocket support for esphttpd. Inspired by https://github.com/dangrie158/ESP-82
#include "utils/sha1.h" #include "utils/sha1.h"
#include "utils/base64.h" #include "utils/base64.h"
#include "cgiwebsocket.h" #include "cgi-websocket.h"
#include "logging.h" #include "httpd-logging.h"
#define WS_KEY_IDENTIFIER "Sec-WebSocket-Key: " #define WS_KEY_IDENTIFIER "Sec-WebSocket-Key: "
#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" #define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
@ -334,7 +334,7 @@ httpd_cgi_state cgiWebsocket(HttpdConnData *connData)
{ {
char buff[256]; char buff[256];
int i; int i;
sha1nfo s; httpd_sha1nfo s;
if (connData->conn == NULL) { if (connData->conn == NULL) {
//Connection aborted. Clean up. //Connection aborted. Clean up.
ws_dbg("WS: Cleanup"); ws_dbg("WS: Cleanup");
@ -376,13 +376,13 @@ httpd_cgi_state cgiWebsocket(HttpdConnData *connData)
ws->conn = connData; ws->conn = connData;
//Reply with the right headers. //Reply with the right headers.
strcat(buff, WS_GUID); strcat(buff, WS_GUID);
sha1_init(&s); httpd_sha1_init(&s);
sha1_write(&s, buff, strlen(buff)); httpd_sha1_write(&s, buff, strlen(buff));
httdSetTransferMode(connData, HTTPD_TRANSFER_NONE); httdSetTransferMode(connData, HTTPD_TRANSFER_NONE);
httpdStartResponse(connData, 101); httpdStartResponse(connData, 101);
httpdHeader(connData, "Upgrade", "websocket"); httpdHeader(connData, "Upgrade", "websocket");
httpdHeader(connData, "Connection", "upgrade"); 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); httpdHeader(connData, "Sec-WebSocket-Accept", buff);
httpdEndHeaders(connData); httpdEndHeaders(connData);
//Set data receive handler //Set data receive handler

@ -0,0 +1,101 @@
/*
HTTP auth implementation. Only does basic authentication for now.
*/
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Jeroen Domburg <jeroen@spritesmods.com> 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 <ctype.h>
// 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;
}

@ -15,10 +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 "httpdespfs.h" #include "httpd-espfs.h"
#include "espfs.h" #include "espfs.h"
#include "espfsformat.h" #include "espfsformat.h"
#include "logging.h" #include "httpd-logging.h"
#include "httpd-utils.h"
#define FILE_CHUNK_LEN 1024 #define FILE_CHUNK_LEN 1024
@ -196,8 +197,7 @@ typedef struct {
} TplData; } TplData;
int int tplSend(HttpdConnData *conn, const char *str, int len)
tplSend(HttpdConnData *conn, const char *str, int len)
{ {
if (conn == NULL) { return 0; } if (conn == NULL) { return 0; }
TplData *tpd = conn->cgiData; TplData *tpd = conn->cgiData;
@ -205,9 +205,9 @@ tplSend(HttpdConnData *conn, const char *str, int len)
if (tpd == NULL || tpd->tokEncode == ENCODE_PLAIN) { if (tpd == NULL || tpd->tokEncode == ENCODE_PLAIN) {
return httpdSendStrN(conn, str, len); return httpdSendStrN(conn, str, len);
} else if (tpd->tokEncode == ENCODE_HTML) { } 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) { } else if (tpd->tokEncode == ENCODE_JS) {
return httpdSend_js(conn, (const uint8_t *) str, len); return httpdSend_js(conn, str, len);
} }
return 0; return 0;
} }
@ -361,7 +361,7 @@ httpd_cgi_state cgiEspFsTemplate(HttpdConnData *connData)
} else { } else {
// Add char to the token buf // Add char to the token buf
char c = buff[x]; char c = buff[x];
bool outOfSpace = tpd->tokenPos >= (sizeof(tpd->token) - 1); bool outOfSpace = tpd->tokenPos >= ((int) sizeof(tpd->token) - 1);
if (outOfSpace || if (outOfSpace ||
(!(c >= 'a' && c <= 'z') && (!(c >= 'a' && c <= 'z') &&
!(c >= 'A' && c <= 'Z') && !(c >= 'A' && c <= 'Z') &&

@ -4,7 +4,6 @@ Thanks to my collague at Espressif for writing the foundations of this code.
*/ */
#include "httpd.h" #include "httpd.h"
#include "platform.h"
#include "httpd-platform.h" #include "httpd-platform.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -14,10 +13,7 @@ Thanks to my collague at Espressif for writing the foundations of this code.
#include <netinet/tcp.h> #include <netinet/tcp.h>
#include <fcntl.h> #include <fcntl.h>
#include <errno.h> #include <errno.h>
#include "logging.h" #include "httpd-logging.h"
static int httpPort;
static int httpMaxConnCt;
struct HttpdConnType { struct HttpdConnType {
int fd; int fd;
@ -28,6 +24,8 @@ struct HttpdConnType {
}; };
static HttpdConnType s_rconn[HTTPD_MAX_CONNECTIONS]; 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) 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 conn->needWriteDoneNotif = 1; //because the real close is done in the writable select code
} }
#define RECV_BUF_SIZE 2048
void platHttpServerTask(void *pvParameters) void platHttpServerTask(void *pvParameters)
{ {
int32_t listenfd; int32_t listenfd;
int32_t remotefd;
int32_t len; int32_t len;
int32_t ret; int32_t ret;
int x; //char *precvbuf;
int maxfdp = 0;
char *precvbuf;
fd_set readset, writeset; fd_set readset, writeset;
struct sockaddr name;
//struct timeval timeout;
struct sockaddr_in server_addr; struct sockaddr_in server_addr;
struct sockaddr_in remote_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) { if (options == NULL) {
httpPort = 80; httpPort = 80;
} else { } else {
httpPort = options->port; httpPort = options->port;
} }
for (x = 0; x < HTTPD_MAX_CONNECTIONS; x++) { for (int i = 0; i < HTTPD_MAX_CONNECTIONS; i++) {
s_rconn[x].fd = -1; s_rconn[i].fd = -1;
} }
/* Construct local address structure */ /* Construct local address structure */
memset(&server_addr, 0, sizeof(server_addr)); /* Zero out structure */ memset(&server_addr, 0, sizeof(server_addr)); /* Zero out structure */
server_addr.sin_family = AF_INET; /* Internet address family */ server_addr.sin_family = AF_INET; /* Internet address family */
server_addr.sin_addr.s_addr = INADDR_ANY; /* Any incoming interface */ 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 */ server_addr.sin_port = htons(httpPort); /* Local port */
/* Create socket for incoming connections */ /* Create socket for incoming connections */
@ -93,8 +86,11 @@ void platHttpServerTask(void *pvParameters)
} }
} while (listenfd == -1); } 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) { if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) {
perror("setsockopt"); perror("setsockopt");
} }
@ -115,24 +111,37 @@ void platHttpServerTask(void *pvParameters)
error("platHttpServerTask: failed to listen!"); error("platHttpServerTask: failed to listen!");
httpdPlatDelayMs(1000); httpdPlatDelayMs(1000);
} }
} while (ret != 0); } while (ret != 0);
info("esphttpd: active and listening to connections."); info("httpd: listening on http://0.0.0.0:%d/", httpPort);
while (1) { while (1) {
if (s_shutdown_requested) {
info("httpd: Shutting down");
break;
}
//dbg("httpd: loop running");
// clear fdset, and set the select function wait time // clear fdset, and set the select function wait time
int socketsFull = 1; int socketsFull = 1;
maxfdp = 0; int maxfdp = 0;
FD_ZERO(&readset); FD_ZERO(&readset);
FD_ZERO(&writeset); FD_ZERO(&writeset);
//timeout.tv_sec = 2;
//timeout.tv_usec = 0; // shutdown flag polling timeout
struct timeval timeout;
for (x = 0; x < HTTPD_MAX_CONNECTIONS; x++) { timeout.tv_sec = 1;
if (s_rconn[x].fd != -1) { timeout.tv_usec = 0;
FD_SET(s_rconn[x].fd, &readset);
if (s_rconn[x].needWriteDoneNotif) FD_SET(s_rconn[x].fd, &writeset); for (int i = 0; i < HTTPD_MAX_CONNECTIONS; i++) {
if (s_rconn[x].fd > maxfdp) { maxfdp = s_rconn[x].fd; } 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 { } else {
socketsFull = 0; socketsFull = 0;
} }
@ -140,112 +149,114 @@ void platHttpServerTask(void *pvParameters)
if (!socketsFull) { if (!socketsFull) {
FD_SET(listenfd, &readset); FD_SET(listenfd, &readset);
if (listenfd > maxfdp) { maxfdp = listenfd; } if (listenfd > maxfdp) {
maxfdp = listenfd;
}
} }
//polling all exist client handle,wait until readable/writable //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) { if (ret > 0) {
//See if we need to accept a new connection //See if we need to accept a new connection
if (FD_ISSET(listenfd, &readset)) { if (FD_ISSET(listenfd, &readset)) {
len = sizeof(struct sockaddr_in); 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) { if (remotefd < 0) {
warn("platHttpServerTask: Huh? Accept failed."); warn("platHttpServerTask: Huh? Accept failed.");
continue; 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."); warn("platHttpServerTask: Huh? Got accept with all slots full.");
continue; continue;
} }
int keepAlive = 1; //enable keepalive
int keepIdle = 60; //60s const int keepAlive = 1; //enable keepalive
int keepInterval = 5; //5s const int keepIdle = 60; //60s
int keepCount = 3; //retry times const int keepInterval = 5; //5s
const int keepCount = 3; //retry times
setsockopt(remotefd, SOL_SOCKET, SO_KEEPALIVE, (void *) &keepAlive, sizeof(keepAlive)); 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_KEEPIDLE, (void *) &keepIdle, sizeof(keepIdle));
setsockopt(remotefd, IPPROTO_TCP, TCP_KEEPINTVL, (void *) &keepInterval, sizeof(keepInterval)); setsockopt(remotefd, IPPROTO_TCP, TCP_KEEPINTVL, (void *) &keepInterval, sizeof(keepInterval));
setsockopt(remotefd, IPPROTO_TCP, TCP_KEEPCNT, (void *) &keepCount, sizeof(keepCount)); setsockopt(remotefd, IPPROTO_TCP, TCP_KEEPCNT, (void *) &keepCount, sizeof(keepCount));
s_rconn[x].fd = remotefd; s_rconn[socknum].fd = remotefd;
s_rconn[x].needWriteDoneNotif = 0; s_rconn[socknum].needWriteDoneNotif = 0;
s_rconn[x].needsClose = 0; s_rconn[socknum].needsClose = 0;
struct sockaddr name;
len = sizeof(name); len = sizeof(name);
getpeername(remotefd, &name, (socklen_t *) &len); getpeername(remotefd, &name, (socklen_t *) &len);
struct sockaddr_in *piname = (struct sockaddr_in *) &name; struct sockaddr_in *piname = (struct sockaddr_in *) &name;
s_rconn[x].port = piname->sin_port; s_rconn[socknum].port = piname->sin_port;
memcpy(&s_rconn[x].ip, &piname->sin_addr.s_addr, sizeof(s_rconn[x].ip)); 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); httpdConnectCb(&s_rconn[socknum], s_rconn[socknum].ip, s_rconn[socknum].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);
} }
//See if anything happened on the existing connections. //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 //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 //Check for write availability first: the read routines may write needWriteDoneNotif while
//the select didn't check for that. //the select didn't check for that.
if (s_rconn[x].needWriteDoneNotif && FD_ISSET(s_rconn[x].fd, &writeset)) { if (s_rconn[i].needWriteDoneNotif && FD_ISSET(s_rconn[i].fd, &writeset)) {
s_rconn[x].needWriteDoneNotif = 0; //Do this first, httpdSentCb may write something making this 1 again. s_rconn[i].needWriteDoneNotif = 0; //Do this first, httpdSentCb may write something making this 1 again.
if (s_rconn[x].needsClose) { if (s_rconn[i].needsClose) {
//Do callback and close fd. //Do callback and close fd.
httpdDisconCb(&s_rconn[x], s_rconn[x].ip, s_rconn[x].port); httpdDisconCb(&s_rconn[i], s_rconn[i].ip, s_rconn[i].port);
close(s_rconn[x].fd); close(s_rconn[i].fd);
s_rconn[x].fd = -1; s_rconn[i].fd = -1;
} else { } 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)) { if (FD_ISSET(s_rconn[i].fd, &readset)) {
precvbuf = (char *) malloc(RECV_BUF_SIZE); ret = (int) recv(s_rconn[i].fd, s_recv_buf, HTTPD_RECV_BUF_LEN, 0);
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 (ret > 0) { if (ret > 0) {
//Data received. Pass to httpd. //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 { } else {
//recv error,connection close //recv error,connection close
httpdDisconCb(&s_rconn[x], s_rconn[x].ip, s_rconn[x].port); httpdDisconCb(&s_rconn[i], s_rconn[i].ip, s_rconn[i].port);
close(s_rconn[x].fd); httpdConnRelease(&s_rconn[i]);
s_rconn[x].fd = -1;
} }
if (precvbuf) { free(precvbuf); }
} }
} }
} }
} }
//Deinit code, not used here. httpdInternalCloseAllSockets();
/*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
}
}
/*release listen socket*/ /*release listen socket*/
close(listenfd); close(listenfd);
httpdPlatTaskEnd(); 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);
}

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

@ -15,11 +15,14 @@ Esp8266 http server - core routines
#include <stdlib.h> #include <stdlib.h>
#include "httpd.h" #include "httpd.h"
#include "httpd-platform.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. //This gets set at init time.
static const HttpdBuiltInUrl *builtInUrls; static const HttpdBuiltInUrl *s_builtInUrls;
static const char *serverName = HTTPD_SERVERNAME; static const char *s_serverName = HTTPD_SERVERNAME;
typedef struct HttpSendBacklogItem HttpSendBacklogItem; typedef struct HttpSendBacklogItem HttpSendBacklogItem;
@ -54,105 +57,39 @@ struct HttpdPriv {
//Connection pool //Connection pool
HttpdConnData *s_connData[HTTPD_MAX_CONNECTIONS]; HttpdConnData *s_connData[HTTPD_MAX_CONNECTIONS];
//Struct to keep extension->mime data in void httpdInternalCloseAllSockets()
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)
{ {
int i = 0; httpdPlatLock();
//Go find the extension /*release data connection*/
const char *ext = url + (strlen(url) - 1); for (int i = 0; i < HTTPD_MAX_CONNECTIONS; i++) {
while (ext != url && *ext != '.') { ext--; } //find all valid handle
if (*ext == '.') { ext++; } if (s_connData[i]->conn == NULL) {
continue;
while (mimeTypes[i].ext != NULL && strcasecmp(ext, mimeTypes[i].ext) != 0) { i++; } }
return mimeTypes[i].mimetype;
}
const char *httpdMethodName(httpd_method m) if (s_connData[i]->cgi != NULL) {
{ //flush cgi data
switch (m) { s_connData[i]->cgi(s_connData[i]);
default: s_connData[i]->cgi = NULL;
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 *code2str(int code) httpdConnRelease(s_connData[i]->conn);
{ httpdRetireConn(s_connData[i]);
switch (code) { s_connData[i] = NULL;
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";
} }
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) void httpdAddCacheHeaders(HttpdConnData *connData, const char *mime)
{ {
if (streq(mime, "text/html")) { return; } // TODO make this extensible
if (streq(mime, "text/plain")) { return; } if (streq(mime, "text/html")
if (streq(mime, "text/csv")) { return; } || streq(mime, "text/plain")
if (streq(mime, "application/json")) { return; } || streq(mime, "text/csv")
|| streq(mime, "application/json")
) {
return;
}
httpdHeader(connData, "Cache-Control", "max-age=7200, public, must-revalidate"); 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 //Get the value of a certain header in the HTTP client head
//Returns true when found, false when not found. //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; char *p = conn->priv->head;
p = p + strlen(p) + 1; //skip GET/POST part 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 //Skip past spaces after the colon
while (*p == ' ') { p++; } while (*p == ' ') { p++; }
//Copy from p to end //Copy from p to end
while (*p != 0 && *p != '\r' && *p != '\n' && retLen > 1) { while (*p != 0 && *p != '\r' && *p != '\n' && buffLen > 1) {
*ret++ = *p++; *buff++ = *p++;
retLen--; buffLen--;
} }
//Zero-terminate string //Zero-terminate string
*ret = 0; *buff = 0;
//All done :) //All done :)
return 1; return 1;
} }
@ -310,7 +181,7 @@ int httpdGetHeader(HttpdConnData *conn, const char *header, char *ret, int retLe
return 0; return 0;
} }
void httdSetTransferMode(HttpdConnData *conn, int mode) void httdSetTransferMode(HttpdConnData *conn, httpd_transfer_opt mode)
{ {
if (mode == HTTPD_TRANSFER_CLOSE) { if (mode == HTTPD_TRANSFER_CLOSE) {
conn->priv->flags &= ~HFL_CHUNKED; conn->priv->flags &= ~HFL_CHUNKED;
@ -333,19 +204,27 @@ void httdResponseOptions(HttpdConnData *conn, int cors)
void httpdStartResponse(HttpdConnData *conn, int code) void httpdStartResponse(HttpdConnData *conn, int code)
{ {
char buff[256]; char buff[256];
int l; size_t l;
const char *connStr = "Connection: close\r\n"; const char *connStr = "";
if (conn->priv->flags & HFL_CHUNKED) { connStr = "Transfer-Encoding: chunked\r\n"; }
if (conn->priv->flags & HFL_NOCONNECTIONSTR) { 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", l = sprintf(buff, "HTTP/1.%d %d %s\r\nServer: %s\r\n%s",
(conn->priv->flags & HFL_HTTP11) ? 1 : 0, (conn->priv->flags & HFL_HTTP11) ? 1 : 0,
code, code,
code2str(code), httpdStatusName(code),
serverName, s_serverName,
connStr); connStr);
httpdSendStrN(conn, buff, l); httpdSendStrN(conn, buff, l);
if (0 == (conn->priv->flags & HFL_NOCORS)) { if (!(conn->priv->flags & HFL_NOCORS)) {
// CORS headers // CORS headers
httpdSendStr(conn, "Access-Control-Allow-Origin: *\r\n"); httpdSendStr(conn, "Access-Control-Allow-Origin: *\r\n");
httpdSendStr(conn, "Access-Control-Allow-Methods: GET,POST,OPTIONS\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; 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 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) #define httpdSendStr_orDie(conn, data) do { if (!httpdSendStr((conn), (data))) return false; } while (0)
/* encode for HTML. returns 0 or 1 - 1 = success */ /* 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; int start = 0, end = 0;
uint8_t c; 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 */ /* 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; int start = 0, end = 0;
uint8_t c; 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. //See if we can find a CGI that's happy to handle the request.
while (1) { while (1) {
//Look up URL in the built-in URL table. //Look up URL in the built-in URL table.
while (builtInUrls[i].url != NULL) { while (s_builtInUrls[i].url != NULL) {
int match = 0; int match = 0;
const char *route = builtInUrls[i].url; const char *route = s_builtInUrls[i].url;
//See if there's a literal match //See if there's a literal match
if (streq(route, conn->url)) { match = 1; } if (streq(route, conn->url)) { match = 1; }
//See if there's a wildcard match (*) //See if there's a wildcard match (*)
@ -729,14 +601,14 @@ static void httpdProcessRequest(HttpdConnData *conn)
if (match) { if (match) {
router_dbg("Matched route #%d, url=%s", i, route); router_dbg("Matched route #%d, url=%s", i, route);
conn->cgiData = NULL; conn->cgiData = NULL;
conn->cgi = builtInUrls[i].cgiCb; conn->cgi = s_builtInUrls[i].cgiCb;
conn->cgiArg = builtInUrls[i].cgiArg; conn->cgiArg = s_builtInUrls[i].cgiArg;
conn->cgiArg2 = builtInUrls[i].cgiArg2; conn->cgiArg2 = s_builtInUrls[i].cgiArg2;
break; break;
} }
i++; 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 //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. //generate a built-in 404 to handle this.
router_warn("%s not found. 404!", conn->url); router_warn("%s not found. 404!", conn->url);
@ -845,14 +717,14 @@ static void httpdParseHeader(char *h, HttpdConnData *conn)
} else { } else {
conn->post->buffSize = conn->post->len; 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); conn->post->buff = (char *) httpdPlatMalloc(conn->post->buffSize + 1);
if (conn->post->buff == NULL) { if (conn->post->buff == NULL) {
http_error("...failed!"); http_error("...failed!");
return; return;
} }
conn->post->buffLen = 0; conn->post->buffLen = 0;
} else if (strstarts(h, "Content-Type: ")) { } else if (strstarts(h, "Content-Type:")) {
if (strstr(h, "multipart/form-data")) { if (strstr(h, "multipart/form-data")) {
// It's multipart form data so let's pull out the boundary for future use // It's multipart form data so let's pull out the boundary for future use
char *b; char *b;
@ -896,7 +768,7 @@ void httpdConnSendFinish(HttpdConnData *conn)
} }
//Callback called when there's data available on a socket. //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; int x, r;
char *p, *e; 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 //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]; } if (conn->priv->headPos != HTTPD_MAX_HEAD_LEN) { conn->priv->head[conn->priv->headPos++] = data[x]; }
conn->priv->head[conn->priv->headPos] = 0; 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) { if (data[x] == '\n' && strstr(conn->priv->head, "\r\n\r\n") != NULL) {
//Indicate we're done with the headers. //Indicate we're done with the headers.
conn->post->len = 0; 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->buff[conn->post->buffLen++] = data[x];
conn->post->received++; conn->post->received++;
conn->hostName = NULL; 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 //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 //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); httpdPlatFree(sendBuff);
httpdPlatUnlock(); httpdPlatUnlock();
} }
@ -1022,13 +896,18 @@ int httpdConnectCb(ConnTypePtr conn, const char *remIp, int remPort)
int i; int i;
httpdPlatLock(); httpdPlatLock();
//Find empty conndata in pool //Find empty conndata in pool
for (i = 0; i < HTTPD_MAX_CONNECTIONS; i++) { if (s_connData[i] == NULL) { break; }} for (i = 0; i < HTTPD_MAX_CONNECTIONS; i++) {
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 (s_connData[i] == NULL) {
if (i == HTTPD_MAX_CONNECTIONS) { 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!"); http_error("Aiee, conn pool overflow!");
httpdPlatUnlock(); httpdPlatUnlock();
return 0; return 0;
} }
s_connData[i] = httpdPlatMalloc(sizeof(HttpdConnData)); s_connData[i] = httpdPlatMalloc(sizeof(HttpdConnData));
if (s_connData[i] == NULL) { if (s_connData[i] == NULL) {
http_warn("Out of memory allocating connData!"); 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 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; int i;
for (i = 0; i < HTTPD_MAX_CONNECTIONS; i++) { for (i = 0; i < HTTPD_MAX_CONNECTIONS; i++) {
s_connData[i] = NULL; s_connData[i] = NULL;
} }
builtInUrls = fixedUrls; s_builtInUrls = fixedUrls;
httpdPlatInit(); httpdPlatInit();
@ -1081,14 +960,12 @@ httpd_thread_handle_t *httpdInit(const HttpdBuiltInUrl *fixedUrls, struct httpd_
void httpdJoin(httpd_thread_handle_t *handle) void httpdJoin(httpd_thread_handle_t *handle)
{ {
if (handle) {
httpdPlatJoin(handle); httpdPlatJoin(handle);
}
} }
/**
* Set server name (must be constant / strdup)
* @param name
*/
void httpdSetName(const char *name) void httpdSetName(const char *name)
{ {
serverName = name; s_serverName = name;
} }

@ -1,12 +1,9 @@
#include "httpd.h"
#include "httpd-platform.h" #include "httpd-platform.h"
#include <pthread.h> #include <pthread.h>
#include <unistd.h> #include <unistd.h>
#include <malloc.h> #include <malloc.h>
#include <string.h> #include <string.h>
#define HTTPD_STACKSIZE 4096 // TODO
static pthread_mutex_t Mutex; static pthread_mutex_t Mutex;
static pthread_mutexattr_t MutexAttr; static pthread_mutexattr_t MutexAttr;
@ -34,6 +31,7 @@ void httpdPlatTaskEnd()
void httpdPlatDisableTimeout(ConnTypePtr conn) void httpdPlatDisableTimeout(ConnTypePtr conn)
{ {
//Unimplemented //Unimplemented
(void) conn;
} }
void httpdPlatInit() { void httpdPlatInit() {

@ -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, 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 */ /* 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; unsigned int ii, io;
uint32_t v; 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+/"; static const uint8_t base64enc_tab[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#if 0 int __attribute__((weak)) httpd_base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out)
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)
{ {
unsigned ii, io; unsigned ii, io;
uint32_t v; uint32_t v;

@ -2,5 +2,7 @@
#include <stddef.h> #include <stddef.h>
int base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out); int httpd_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_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.

@ -10,7 +10,10 @@
//according to http://ip.cadence.com/uploads/pdf/xtensalx_overview_handbook.pdf //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. // the cpu is normally defined as little ending, but can be big endian too.
// for the esp this seems to work // 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 */ /* code */
#define SHA1_K0 0x5a827999 #define SHA1_K0 0x5a827999
@ -18,7 +21,7 @@
#define SHA1_K40 0x8f1bbcdc #define SHA1_K40 0x8f1bbcdc
#define SHA1_K60 0xca62c1d6 #define SHA1_K60 0xca62c1d6
void sha1_init(sha1nfo *s) void httpd_sha1_init(httpd_sha1nfo *s)
{ {
s->state[0] = 0x67452301; s->state[0] = 0x67452301;
s->state[1] = 0xefcdab89; s->state[1] = 0xefcdab89;
@ -29,12 +32,12 @@ void sha1_init(sha1nfo *s)
s->bufferOffset = 0; 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))); return ((number << bits) | (number >> (32 - bits)));
} }
void sha1_hashBlock(sha1nfo *s) static void sha1_hashBlock(httpd_sha1nfo *s)
{ {
uint8_t i; uint8_t i;
uint32_t a, b, c, d, e, t; uint32_t a, b, c, d, e, t;
@ -72,7 +75,7 @@ void sha1_hashBlock(sha1nfo *s)
s->state[4] += e; 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; uint8_t *const b = (uint8_t *) s->buffer;
#ifdef SHA_BIG_ENDIAN #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; ++s->byteCount;
sha1_addUncounted(s, data); 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 // Pad with 0x80 followed by 0x00 until the end of the block
sha1_addUncounted(s, 0x80); sha1_addUncounted(s, 0x80);
@ -117,7 +120,7 @@ void sha1_pad(sha1nfo *s)
sha1_addUncounted(s, s->byteCount << 3); 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 // Pad to complete the last block
sha1_pad(s); sha1_pad(s);
@ -141,34 +144,34 @@ uint8_t *sha1_result(sha1nfo *s)
#define HMAC_IPAD 0x36 #define HMAC_IPAD 0x36
#define HMAC_OPAD 0x5c #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; uint8_t i;
memset(s->keyBuffer, 0, BLOCK_LENGTH); memset(s->keyBuffer, 0, BLOCK_LENGTH);
if (keyLength > BLOCK_LENGTH) { if (keyLength > BLOCK_LENGTH) {
// Hash long keys // Hash long keys
sha1_init(s); httpd_sha1_init(s);
for (; keyLength--;) { sha1_writebyte(s, *key++); } for (; keyLength--;) { httpd_sha1_writebyte(s, *key++); }
memcpy(s->keyBuffer, sha1_result(s), HASH_LENGTH); memcpy(s->keyBuffer, httpd_sha1_result(s), HASH_LENGTH);
} else { } else {
// Block length keys are used as is // Block length keys are used as is
memcpy(s->keyBuffer, key, keyLength); memcpy(s->keyBuffer, key, keyLength);
} }
// Start inner hash // Start inner hash
sha1_init(s); httpd_sha1_init(s);
for (i = 0; i < BLOCK_LENGTH; i++) { 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; uint8_t i;
// Complete inner hash // Complete inner hash
memcpy(s->innerHash, sha1_result(s), HASH_LENGTH); memcpy(s->innerHash, httpd_sha1_result(s), HASH_LENGTH);
// Calculate outer hash // Calculate outer hash
sha1_init(s); httpd_sha1_init(s);
for (i = 0; i < BLOCK_LENGTH; i++) { sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_OPAD); } for (i = 0; i < BLOCK_LENGTH; i++) { httpd_sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_OPAD); }
for (i = 0; i < HASH_LENGTH; i++) { sha1_writebyte(s, s->innerHash[i]); } for (i = 0; i < HASH_LENGTH; i++) { httpd_sha1_writebyte(s, s->innerHash[i]); }
return sha1_result(s); return httpd_sha1_result(s);
} }

@ -3,32 +3,32 @@
#define HASH_LENGTH 20 #define HASH_LENGTH 20
#define BLOCK_LENGTH 64 #define BLOCK_LENGTH 64
typedef struct sha1nfo { typedef struct httpd_sha1nfo {
uint32_t buffer[BLOCK_LENGTH/4]; uint32_t buffer[BLOCK_LENGTH/4];
uint32_t state[HASH_LENGTH/4]; uint32_t state[HASH_LENGTH/4];
uint32_t byteCount; uint32_t byteCount;
uint8_t bufferOffset; uint8_t bufferOffset;
uint8_t keyBuffer[BLOCK_LENGTH]; uint8_t keyBuffer[BLOCK_LENGTH];
uint8_t innerHash[HASH_LENGTH]; uint8_t innerHash[HASH_LENGTH];
} sha1nfo; } httpd_sha1nfo;
/* public API - prototypes - TODO: doxygen*/ /* 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);

Loading…
Cancel
Save