cleaning, add header queuing

master
Ondřej Hruška 1 year ago
parent 5ae683e3f2
commit 935271510d
  1. 16
      demo/server_demo.c
  2. 2
      spritehttpd/include/httpd-types.h
  3. 30
      spritehttpd/include/httpd-utils.h
  4. 10
      spritehttpd/include/httpd.h
  5. 83
      spritehttpd/src/cgi-espfs.c
  6. 31
      spritehttpd/src/httpd-utils.c
  7. 110
      spritehttpd/src/httpd.c

@ -80,13 +80,13 @@ httpd_cgi_state templateReplacer(HttpdConnData *conn, const char *token)
// httpdGetHeader(conn, "Cookie", )
//}
//
//httpd_cgi_state cgiStartSession(HttpdConnData *conn)
//{
//
//
// return HTTPD_CGI_NOTFOUND;
//}
httpd_cgi_state cgiStartSession(HttpdConnData *conn)
{
httpdQueueHeader(conn, "X-Foo", "FOO");
httpdQueueHeader(conn, "X-Bar", "Bar");
return HTTPD_CGI_NOTFOUND;
}
@ -96,7 +96,7 @@ httpd_cgi_state templateReplacer(HttpdConnData *conn, const char *token)
*/
const HttpdBuiltInUrl routes[] = {
// TODO password lock ...
// ROUTE_CGI("*", cgiStartSession),
ROUTE_CGI("*", cgiStartSession),
// --- Web pages ---
// ROUTE_TPL_FILE("/", tplIndex, "/index.tpl"),

@ -30,7 +30,7 @@ typedef enum {
HTTPD_CGI_NOTFOUND = 2,
/// This is, in effect, identical to NOTFOUND, it's returned by auth functions when a fall-through is allowed.
/// The next route in the route list will be attempted.
HTTPD_CGI_AUTHENTICATED = 3,
HTTPD_CGI_AUTHENTICATED = 3, // TODO rename to PASS?
} httpd_cgi_state;
/**

@ -9,18 +9,40 @@
#include "httpd-types.h"
// Custom helpers
/// Test string equality
#define streq(a, b) (strcmp((const char*)(a), (const char*)(b)) == 0)
/// Test string equality, case-insensitive
#define strcaseeq(a, b) (strcasecmp((const char*)(a), (const char*)(b)) == 0)
/// Test string equality up to N chars
#define strneq(a, b, n) (strncmp((const char*)(a), (const char*)(b), (n)) == 0)
/// Test if string A starts with string B
#define strstarts(a, b) strneq((a), (b), strlen((b)))
/// Get nth char from the end of string (1 = last)
#define last_char_n(str, n) ((str))[strlen((str)) - (n)]
/// Get last char of string
#define last_char(str) last_char_n((str), 1)
/// The container_of macro from the linux kernel
#ifndef container_of
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
#endif
/// Get length of a static or stack array
#define array_len(a) (sizeof((a)) / sizeof((a)[0]))
/**
* Strstr up to N chars into the searched string
*
* @param str - haystack
* @param substr - needle
* @param n - haystack len to search
* @return pointer to the matching substring or NULL
*/
const char *strnstr(const char *str, const char *substr, size_t n);
/**
* Turn a nibble (0-15) to a hex char.
*
@ -83,12 +105,10 @@ const char *httpdGetMimetype(const char *url);
* @param fallback - fallback mime if none was resolved
* @return mime string
*/
static inline const char *httpdGetMimetypeOr(const char *url, const char *fallback) {
static inline const char *httpdGetMimetypeOr(const char *url, const char *fallback)
{
const char *mime = httpdGetMimetype(url);
if (!mime) {
mime = fallback;
}
return mime;
return mime ? mime : fallback;
}
/**

@ -149,6 +149,16 @@ void httpdEndHeaders(HttpdConnData *conn);
*/
int httpdGetHeader(HttpdConnData *conn, const char *header, char *buff, size_t buffLen);
/**
* Queue a header to be sent with the response.
*
* @param conn
* @param header - name
* @param value - value
* @return 1 = OK
*/
void httpdQueueHeader(HttpdConnData *conn, const char *header, const char *value);
/**
* Send binary data
*

@ -21,7 +21,12 @@ Connector to let httpd use the espfs filesystem to serve the files in it.
#include "espfs.h"
#include "espfsformat.h"
#define FILE_CHUNK_LEN 512
/// EspFs CGI filename len buffer size
#define ESPFS_FILENAME_LEN 100
/// EspFs CGI file chunk buffer size
#define ESPFS_FILE_CHUNK_LEN 512
/// EspFs CGI header buffer (used to detect gzip)
#define ESPFS_HEADER_LEN 64
// The static files marked with FLAG_GZIP are compressed and will be served with GZIP compression.
// If the client does not advertise that he accepts GZIP send following warning message (telnet users for e.g.)
@ -32,29 +37,6 @@ static const char *gzipNonSupportedMessage = "HTTP/1.0 501 Not implemented\r\n"
"\r\n"
"Your browser does not accept gzip-compressed data.\r\n";
/**
* Try to open a file
* @param path - path to the file, may end with slash
* @param indexname - filename at the path
* @return file pointer or NULL
*/
static EspFsFile *tryOpenIndex_do(const char *path, const char *indexname)
{
char fname[100];
size_t url_len = strlen(path);
strncpy(fname, path, 99);
// Append slash if missing
if (path[url_len - 1] != '/') {
fname[url_len++] = '/';
}
strcpy(fname + url_len, indexname);
// Try to open, returns NULL if failed
return espFsOpen(fname);
}
/**
* Try to find index file on a path
* @param path - directory
@ -67,17 +49,29 @@ EspFsFile *tryOpenIndex(const char *path)
// no point in trying to look for index.
if (strchr(path, '.') != NULL) { return NULL; }
file = tryOpenIndex_do(path, "index.html");
if (file != NULL) { return file; }
char fname[ESPFS_FILENAME_LEN];
size_t url_len = strlen(path);
if (url_len >= ESPFS_FILENAME_LEN) {
// too long to append anything
return NULL;
}
strncpy(fname, path, ESPFS_FILENAME_LEN - 1);
file = tryOpenIndex_do(path, "index.htm");
if (file != NULL) { return file; }
// Append slash if missing
if (path[url_len - 1] != '/') {
fname[url_len++] = '/';
}
file = tryOpenIndex_do(path, "index.tpl.html");
if (file != NULL) { return file; }
const char *index_names[4] = {
"index.html", "index.htm", "index.tpl.html", "index.tpl"
};
file = tryOpenIndex_do(path, "index.tpl");
if (file != NULL) { return file; }
for (size_t i = 0; i < array_len(index_names); i++) {
strcpy(fname + url_len, index_names[i]);
// Try to open, returns NULL if failed
file = espFsOpen(fname);
if (file != NULL) { return file; }
}
return NULL; // failed to guess the right name
}
@ -86,8 +80,8 @@ static httpd_cgi_state serveStaticFile(HttpdConnData *hconn, const char *filepat
{
EspFsFile *file = hconn->cgiData;
size_t len;
uint8_t buff[FILE_CHUNK_LEN + 1];
char acceptEncodingBuffer[64 + 1];
uint8_t buff[ESPFS_FILE_CHUNK_LEN + 1];
char acceptEncodingBuffer[ESPFS_HEADER_LEN + 1];
if (hconn->conn == NULL) {
//Connection aborted. Clean up.
@ -123,7 +117,7 @@ static httpd_cgi_state serveStaticFile(HttpdConnData *hconn, const char *filepat
if (isGzip) {
// Check the browser's "Accept-Encoding" header. If the client does not
// advertise that he accepts GZIP send a warning message (telnet users for e.g.)
httpdGetHeader(hconn, "Accept-Encoding", acceptEncodingBuffer, 64);
httpdGetHeader(hconn, "Accept-Encoding", acceptEncodingBuffer, ESPFS_HEADER_LEN);
if (strstr(acceptEncodingBuffer, "gzip") == NULL) {
//No Accept-Encoding: gzip header present
httpdSendStr(hconn, gzipNonSupportedMessage);
@ -144,12 +138,12 @@ static httpd_cgi_state serveStaticFile(HttpdConnData *hconn, const char *filepat
return HTTPD_CGI_MORE;
}
len = espFsRead(file, buff, FILE_CHUNK_LEN);
len = espFsRead(file, buff, ESPFS_FILE_CHUNK_LEN);
if (len > 0) {
espfs_dbg("[EspFS] Read file chunk: %d bytes", len);
httpdSend(hconn, buff, len);
}
if (len != FILE_CHUNK_LEN) {
if (len != ESPFS_FILE_CHUNK_LEN) {
//We're done.
espFsClose(file);
return HTTPD_CGI_DONE;
@ -182,7 +176,7 @@ typedef struct {
EspFsFile *file;
ssize_t tokenPos;
char buff[FILE_CHUNK_LEN + 1];
char buff[ESPFS_FILE_CHUNK_LEN + 1];
char token[HTTPD_ESPFS_TOKEN_LEN];
char *pToken;
@ -301,7 +295,7 @@ httpd_cgi_state cgiEspFsTemplate(HttpdConnData *conn)
sp = tdi->buff_sp;
x = tdi->buff_x;
} else {
len = espFsRead(tdi->file, (uint8_t *) buff, FILE_CHUNK_LEN);
len = espFsRead(tdi->file, (uint8_t *) buff, ESPFS_FILE_CHUNK_LEN);
tdi->buff_len = len;
e = buff;
@ -337,19 +331,20 @@ httpd_cgi_state cgiEspFsTemplate(HttpdConnData *conn)
int prefixLen = 0;
tdi->tokEncode = ENCODE_PLAIN;
if (strneq(tdi->token, "html:", 5)) {
if (strstarts(tdi->token, "html:")) {
prefixLen = 5;
tdi->tokEncode = ENCODE_HTML;
} else if (strneq(tdi->token, "h:", 2)) {
} else if (strstarts(tdi->token, "h:")) {
prefixLen = 2;
tdi->tokEncode = ENCODE_HTML;
} else if (strneq(tdi->token, "js:", 3)) {
} else if (strstarts(tdi->token, "js:")) {
prefixLen = 3;
tdi->tokEncode = ENCODE_JS;
} else if (strneq(tdi->token, "j:", 2)) {
} else if (strstarts(tdi->token, "j:")) {
prefixLen = 2;
tdi->tokEncode = ENCODE_JS;
}
// TODO implement "include" tokens?
tdi->pToken = &tdi->token[prefixLen];
}
@ -410,7 +405,7 @@ httpd_cgi_state cgiEspFsTemplate(HttpdConnData *conn)
httpdSendStrN(conn, e, (size_t) sp);
}
if (len != FILE_CHUNK_LEN) {
if (len != ESPFS_FILE_CHUNK_LEN) {
//We're done.
TplCallback callback = (TplCallback) conn->cgiArg;

@ -1,6 +1,23 @@
#include "httpd-utils.h"
#include "httpd-logging.h"
const char *strnstr(const char *str, const char *substr, size_t n)
{
if (!str || !substr) {
return NULL;
}
size_t substr_len = strlen(substr);
if (0 == substr_len || substr_len > n) { return NULL; }
const char *pEnd = str + n + 1 - substr_len;
for (const char *p = str; p < pEnd; p++) {
if (0 == strncmp(p, substr, substr_len)) {
return p;
}
}
return NULL;
}
char httpdHexNibble(uint8_t val)
{
val &= 0xf;
@ -25,7 +42,7 @@ size_t httpdUrlDecode(const char *val, size_t valLen, char *buff, size_t buffLen
uint8_t escVal = 0;
while (s < valLen && d < buffLen) {
if (esced == 1) {
escVal = httpdHexVal(val[s]) << 4;
escVal = (uint8_t) (httpdHexVal(val[s]) << 4);
esced = 2;
} else if (esced == 2) {
escVal |= httpdHexVal(val[s]);
@ -58,7 +75,7 @@ int httpdFindArg(const char *line, const char *arg, char *buff, size_t buffLen)
e = strstr(p, "&");
if (e == NULL) { e = p + strlen(p); }
router_dbg("findArg: val %s len %d", p, (int) (e - p));
return (int) httpdUrlDecode(p, (size_t)(e - p), buff, buffLen);
return (int) httpdUrlDecode(p, (size_t) (e - p), buff, buffLen);
}
p = strstr(p, "&");
if (p != NULL) { p += 1; }
@ -95,21 +112,23 @@ static const MimeMap MIME_TYPES[] = {
{"svg", "image/svg+xml"},
{"xml", "text/xml"},
{"json", "application/json"},
{NULL, NULL}, //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++; }
for (size_t i = 0; i < array_len(MIME_TYPES); i++) {
if (strcaseeq(ext, MIME_TYPES[i].ext)) {
return MIME_TYPES[i].mimetype;
}
}
return MIME_TYPES[i].mimetype;
return NULL;
}

@ -19,6 +19,7 @@ Esp8266 http server - core routines
#include "httpd-logging.h"
static void cleanupCgiAndUserData(HttpdConnData *hconn);
static void httpdRetireConn(HttpdConnData *hconn);
_Static_assert(HTTPD_MAX_CONNECTIONS < 256, "HTTPD_MAX_CONNECTIONS must be at most 255");
@ -27,6 +28,14 @@ _Static_assert(HTTPD_MAX_CONNECTIONS < 256, "HTTPD_MAX_CONNECTIONS must be at mo
static const HttpdBuiltInUrl *s_builtInUrls;
static const char *s_serverName = HTTPD_SERVERNAME;
struct HttpdQueuedHeader;
typedef struct HttpdQueuedHeader {
/// Pointer to the next queued header
struct HttpdQueuedHeader *next;
/// Text of the header, including CRLF
char headerLine[];
} HttpdQueuedHeader;
typedef struct HttpSendBacklogItem HttpSendBacklogItem;
struct HttpSendBacklogItem {
@ -52,6 +61,7 @@ struct HttpdPriv {
size_t sendBuffLen;
char *chunkHdr;
HttpSendBacklogItem *sendBacklog;
HttpdQueuedHeader *headersToSend; // Linked list of headers to send with the response. Will be freed with the request.
size_t sendBacklogSize;
uint8_t flags;
};
@ -60,7 +70,7 @@ struct HttpdPriv {
//Connection pool
HttpdConnData *s_connData[HTTPD_MAX_CONNECTIONS];
void httpdInternalCloseAllSockets()
void httpdInternalCloseAllSockets(void)
{
httpdPlatLock();
/*release data connection*/
@ -89,8 +99,7 @@ void httpdAddCacheHeaders(HttpdConnData *connData, const char *mime)
if (streq(mime, "text/html")
|| streq(mime, "text/plain")
|| streq(mime, "text/csv")
|| streq(mime, "application/json")
) {
|| streq(mime, "application/json")) {
return;
}
@ -191,6 +200,37 @@ int httpdGetHeader(HttpdConnData *conn, const char *header, char *buff, size_t b
return 0;
}
void httpdQueueHeader(HttpdConnData *conn, const char *header, const char *value)
{
if (!conn || !header || !value) {
return;
}
if (conn->priv->flags & HFL_SENDINGBODY) {
http_error("Headers already sent.");
return;
}
HttpdQueuedHeader *queEntry = httpdPlatMalloc(sizeof(void *) + strlen(header) + strlen(value) + 5);
if (!queEntry) {
http_error("httpdQueueHeader - no mem");
return;
}
queEntry->next = NULL;
queEntry->headerLine[0] = 0;
strcat(queEntry->headerLine, header);
strcat(queEntry->headerLine, ": ");
strcat(queEntry->headerLine, value);
strcat(queEntry->headerLine, "\r\n");
// Attach it to the linked list
HttpdQueuedHeader **ph = &conn->priv->headersToSend;
while (*ph) {
ph = &(*ph)->next;
}
*ph = queEntry;
}
void httdSetTransferMode(HttpdConnData *conn, httpd_transfer_opt mode)
{
if (mode == HTTPD_TRANSFER_CLOSE) {
@ -224,12 +264,12 @@ void httpdStartResponse(HttpdConnData *conn, int code)
}
}
size_t l = (size_t)sprintf(buff, "HTTP/1.%d %d %s\r\nServer: %s\r\n%s",
((conn->priv->flags & HFL_HTTP11) ? 1 : 0),
code,
httpdStatusName(code),
s_serverName,
connStr);
size_t l = (size_t) sprintf(buff, "HTTP/1.%d %d %s\r\nServer: %s\r\n%s",
((conn->priv->flags & HFL_HTTP11) ? 1 : 0),
code,
httpdStatusName(code),
s_serverName,
connStr);
httpdSendStrN(conn, buff, l);
@ -252,6 +292,15 @@ void httpdHeader(HttpdConnData *conn, const char *field, const char *val)
//Finish the headers.
void httpdEndHeaders(HttpdConnData *conn)
{
// Add queued headers
HttpdQueuedHeader *qh = conn->priv->headersToSend;
while (qh) {
httpdSendStr(conn, qh->headerLine);
HttpdQueuedHeader *next = qh->next;
httpdPlatFree(qh);
qh = next;
}
httpdSendStr(conn, "\r\n");
conn->priv->flags |= HFL_SENDINGBODY;
}
@ -309,17 +358,21 @@ int httpdSend_html(HttpdConnData *conn, const char *data, ssize_t len)
}
if (c == '"' || c == '\'' || c == '<' || c == '>') {
if (start < end) httpdSend_orDie(conn, data + start, end - start);
if (start < end) {
httpdSend_orDie(conn, data + start, end - start);
}
start = end + 1;
}
if (c == '"') httpdSendStr_orDie(conn, "&#34;");
else if (c == '\'') httpdSendStr_orDie(conn, "&#39;");
else if (c == '<') httpdSendStr_orDie(conn, "&lt;");
else if (c == '>') httpdSendStr_orDie(conn, "&gt;");
if (c == '"') { httpdSendStr_orDie(conn, "&#34;"); }
else if (c == '\'') { httpdSendStr_orDie(conn, "&#39;"); }
else if (c == '<') { httpdSendStr_orDie(conn, "&lt;"); }
else if (c == '>') { httpdSendStr_orDie(conn, "&gt;"); }
}
if (start < end) httpdSend_orDie(conn, data + start, end - start);
if (start < end) {
httpdSend_orDie(conn, data + start, end - start);
}
return 1;
}
@ -340,20 +393,24 @@ int httpdSend_js(HttpdConnData *conn, const char *data, ssize_t len)
}
if (c == '"' || c == '\\' || c == '\'' || c == '<' || c == '>' || c == '\n' || c == '\r') {
if (start < end) httpdSend_orDie(conn, data + start, end - start);
if (start < end) {
httpdSend_orDie(conn, data + start, end - start);
}
start = end + 1;
}
if (c == '"') httpdSendStr_orDie(conn, "\\\"");
else if (c == '\'') httpdSendStr_orDie(conn, "\\'");
else if (c == '\\') httpdSendStr_orDie(conn, "\\\\");
else if (c == '<') httpdSendStr_orDie(conn, "\\u003C");
else if (c == '>') httpdSendStr_orDie(conn, "\\u003E");
else if (c == '\n') httpdSendStr_orDie(conn, "\\n");
else if (c == '\r') httpdSendStr_orDie(conn, "\\r");
if (c == '"') { httpdSendStr_orDie(conn, "\\\""); }
else if (c == '\'') { httpdSendStr_orDie(conn, "\\'"); }
else if (c == '\\') { httpdSendStr_orDie(conn, "\\\\"); }
else if (c == '<') { httpdSendStr_orDie(conn, "\\u003C"); }
else if (c == '>') { httpdSendStr_orDie(conn, "\\u003E"); }
else if (c == '\n') { httpdSendStr_orDie(conn, "\\n"); }
else if (c == '\r') { httpdSendStr_orDie(conn, "\\r"); }
}
if (start < end) httpdSend_orDie(conn, data + start, end - start);
if (start < end) {
httpdSend_orDie(conn, data + start, end - start);
}
return 1;
}
@ -371,7 +428,7 @@ bool httpdFlushSendBuffer(HttpdConnData *conn)
//Finish chunk with cr/lf
httpdSendStr(conn, "\r\n");
//Calculate length of chunk
len = (size_t) ((char *)(&conn->priv->sendBuff[conn->priv->sendBuffLen]) - conn->priv->chunkHdr) - 8;
len = (size_t) ((char *) (&conn->priv->sendBuff[conn->priv->sendBuffLen]) - conn->priv->chunkHdr) - 8;
//Fix up chunk header to correct value
conn->priv->chunkHdr[0] = httpdHexNibble((uint8_t) (len >> 12));
conn->priv->chunkHdr[1] = httpdHexNibble((uint8_t) (len >> 8));
@ -507,7 +564,6 @@ void httpdContinue(HttpdConnData *conn)
//find the next cgi function, wait till the cgi data is sent or close up the connection.
static void httpdProcessRequest(HttpdConnData *conn)
{
int r;
int i = 0;
if (conn->url == NULL) {
router_warn("WtF? url = NULL");
@ -564,7 +620,7 @@ static void httpdProcessRequest(HttpdConnData *conn)
//Okay, we have a CGI function that matches the URL. See if it wants to handle the
//particular URL we're supposed to handle.
r = conn->cgi(conn);
httpd_cgi_state r = conn->cgi(conn);
if (r == HTTPD_CGI_MORE) {
//Yep, it's happy to do so and has more data to send.
if (conn->recvHdl) {

Loading…
Cancel
Save