|
|
|
/*
|
|
|
|
Esp8266 http server - core routines
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
* "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 <string.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "httpd.h"
|
|
|
|
#include "httpd-platform.h"
|
|
|
|
#include "httpd-utils.h"
|
|
|
|
#include "httpd-logging.h"
|
|
|
|
#include "httpd-heap.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");
|
|
|
|
|
|
|
|
//This gets set at init time.
|
|
|
|
static const HttpdBuiltInUrl *s_builtInUrls;
|
|
|
|
static const char *s_serverName = HTTPD_SERVERNAME;
|
|
|
|
|
|
|
|
//Connection pool
|
|
|
|
HttpdConnData s_connData[HTTPD_MAX_CONNECTIONS];
|
|
|
|
|
|
|
|
void httpdInternalCloseAllSockets(void)
|
|
|
|
{
|
|
|
|
httpdPlatLock();
|
|
|
|
/*release data connection*/
|
|
|
|
for (int i = 0; i < HTTPD_MAX_CONNECTIONS; i++) {
|
|
|
|
HttpdConnData *hconn = &s_connData[i];
|
|
|
|
//find all valid handle
|
|
|
|
if (!hconn->occupied) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
httpdConnRelease(hconn->conn);
|
|
|
|
httpdRetireConn(hconn);
|
|
|
|
}
|
|
|
|
httpdPlatUnlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void httpdAddCacheHeaders(HttpdConnData *connData, const char *mime)
|
|
|
|
{
|
|
|
|
// TODO make this extensible
|
|
|
|
if (streq(mime, "text/html")
|
|
|
|
|| streq(mime, "text/plain")
|
|
|
|
|| streq(mime, "text/csv")
|
|
|
|
|| streq(mime, "application/json")) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
httpdHeader(connData, "Cache-Control", "max-age=7200, public, must-revalidate");
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *httpdGetVersion(void)
|
|
|
|
{
|
|
|
|
return HTTPDVER;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t httpGetBacklogSize(const HttpdConnData *conn)
|
|
|
|
{
|
|
|
|
HttpdSendBacklogItem *bl = conn->priv.sendBacklog;
|
|
|
|
if (!bl) { return 0; }
|
|
|
|
size_t bytes = 0;
|
|
|
|
while (bl != NULL) {
|
|
|
|
bytes += bl->len;
|
|
|
|
bl = bl->next;
|
|
|
|
}
|
|
|
|
return bytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Looks up the connData info for a specific connection
|
|
|
|
static HttpdConnData *httpdFindConnData(ConnTypePtr conn, httpd_ipaddr_t remIp, int remPort)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < HTTPD_MAX_CONNECTIONS; i++) {
|
|
|
|
if (s_connData[i].occupied && s_connData[i].remote_port == remPort
|
|
|
|
&& s_connData[i].remote_ip == remIp) {
|
|
|
|
s_connData[i].conn = conn;
|
|
|
|
return &s_connData[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//Shouldn't happen.
|
|
|
|
http_error("Unknown connection");
|
|
|
|
httpdConnDisconnect(conn);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Retires a connection for re-use
|
|
|
|
static void httpdRetireConn(HttpdConnData *hconn)
|
|
|
|
{
|
|
|
|
if (!hconn || !hconn->occupied) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
http_info("Pool slot %d: socket closed.", hconn->slot);
|
|
|
|
|
|
|
|
// this sets the `conn` pointer to NULL to indicate we are cleaning up to CGI
|
|
|
|
cleanupCgiAndUserData(hconn);
|
|
|
|
|
|
|
|
// Free any memory allocated for backlog - walk the linked list
|
|
|
|
if (hconn->priv.sendBacklog) {
|
|
|
|
HttpdSendBacklogItem *backlog, *next;
|
|
|
|
backlog = hconn->priv.sendBacklog;
|
|
|
|
do {
|
|
|
|
next = backlog->next;
|
|
|
|
httpdFree(backlog);
|
|
|
|
backlog = next;
|
|
|
|
} while (backlog != NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Free post data buffer
|
|
|
|
if (hconn->post.buff) {
|
|
|
|
httpdFree(hconn->post.buff);
|
|
|
|
hconn->post.buff = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Free left-over queued headers - can happen if the client disconnects before headers were sent
|
|
|
|
HttpdQueuedHeader *hdr = hconn->priv.headersToSend;
|
|
|
|
hconn->priv.headersToSend = NULL;
|
|
|
|
while (hdr) {
|
|
|
|
HttpdQueuedHeader *next = hdr->next;
|
|
|
|
httpdFree(hdr);
|
|
|
|
hdr = next;
|
|
|
|
}
|
|
|
|
|
|
|
|
s_connData[hconn->slot].occupied = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Get the value of a certain header in the HTTP client head
|
|
|
|
//Returns true when found, false when not found.
|
|
|
|
int httpdGetHeader(HttpdConnData *conn, const char *header, char *buff, size_t buffLen)
|
|
|
|
{
|
|
|
|
const char *p = httpdGetHeaderPtr(conn, header);
|
|
|
|
if (!p) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (*p != 0 && buffLen > 1) { // && *p != '\r' && *p != '\n'
|
|
|
|
*buff++ = *p++;
|
|
|
|
buffLen--;
|
|
|
|
}
|
|
|
|
//Zero-terminate string
|
|
|
|
*buff = 0;
|
|
|
|
//All done :)
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char * httpdGetHeaderPtr(HttpdConnData *conn, const char *header)
|
|
|
|
{
|
|
|
|
char *p = conn->priv.head;
|
|
|
|
p = p + strlen(p) + 1; //skip GET/POST part
|
|
|
|
p = p + strlen(p) + 1; //skip HTTP part
|
|
|
|
while (p < (conn->priv.head + conn->priv.headPos)) {
|
|
|
|
while (*p <= 32 && *p != 0) { p++; } //skip crap at start
|
|
|
|
//See if this is the header
|
|
|
|
if (strstarts(p, header) && p[strlen(header)] == ':') {
|
|
|
|
//Skip 'key:' bit of header line
|
|
|
|
p = p + strlen(header) + 1;
|
|
|
|
//Skip past spaces after the colon
|
|
|
|
while (*p == ' ') { p++; }
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
p += strlen(p) + 1; //Skip past end of string and \0 terminator
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 = httpdMalloc(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");
|
|
|
|
|
|
|
|
if (!httpdQueueHeaderRaw(conn, queEntry)) {
|
|
|
|
httpdFree(queEntry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool httpdQueueHeaderRaw(HttpdConnData *conn, HttpdQueuedHeader *queEntry)
|
|
|
|
{
|
|
|
|
if (!conn || !queEntry) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (conn->priv.flags & HFL_SENDINGBODY) {
|
|
|
|
http_error("Headers already sent.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
queEntry->next = NULL;
|
|
|
|
|
|
|
|
if (!conn->priv.headersToSend) {
|
|
|
|
conn->priv.headersToSend = queEntry;
|
|
|
|
} else {
|
|
|
|
HttpdQueuedHeader *ph = conn->priv.headersToSend;
|
|
|
|
// Go to the end of the linked list
|
|
|
|
while (ph->next) {
|
|
|
|
ph = ph->next;
|
|
|
|
}
|
|
|
|
ph->next = queEntry;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool httpdSetCookie(HttpdConnData *conn, const SetCookie *parm)
|
|
|
|
{
|
|
|
|
if (!conn || !parm) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!parm->name || !parm->value) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (conn->priv.flags & HFL_SENDINGBODY) {
|
|
|
|
http_error("Headers already sent.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define COOKIE_PART_SET_COOKIE "Set-Cookie: "
|
|
|
|
#define COOKIE_PART_EXPIRES "; Expires="
|
|
|
|
#define COOKIE_PART_MAX_AGE "; Max-Age="
|
|
|
|
#define COOKIE_PART_DOMAIN "; Domain="
|
|
|
|
#define COOKIE_PART_PATH "; Path="
|
|
|
|
#define COOKIE_PART_SAME_SITE "; SameSite="
|
|
|
|
#define COOKIE_PART_SECURE "; Secure"
|
|
|
|
#define COOKIE_PART_HTTP_ONLY "; HttpOnly"
|
|
|
|
|
|
|
|
// "Set-Cookie: "
|
|
|
|
size_t buflen = strlen(COOKIE_PART_SET_COOKIE)
|
|
|
|
+ strlen(parm->name) + 1 + strlen(parm->value)
|
|
|
|
+ 5; // cr lf + nul + some spare space
|
|
|
|
if (parm->expires) {
|
|
|
|
buflen += strlen(COOKIE_PART_EXPIRES) + strlen(parm->expires);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parm->maxAge != 0) {
|
|
|
|
// 10 chars are needed for 32bit integer
|
|
|
|
buflen += strlen(COOKIE_PART_MAX_AGE) + 10;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parm->domain) {
|
|
|
|
buflen += strlen(COOKIE_PART_DOMAIN) + strlen(parm->domain);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parm->path) {
|
|
|
|
buflen += strlen(COOKIE_PART_PATH) + strlen(parm->path);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parm->sameSite) {
|
|
|
|
// Strict, Lax, None
|
|
|
|
buflen += strlen(COOKIE_PART_SAME_SITE) + strlen(parm->sameSite);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parm->secure) {
|
|
|
|
buflen += strlen(COOKIE_PART_SECURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parm->httponly) {
|
|
|
|
buflen += strlen(COOKIE_PART_HTTP_ONLY);
|
|
|
|
}
|
|
|
|
|
|
|
|
HttpdQueuedHeader *queEntry = httpdMalloc(sizeof(void*) + buflen);
|
|
|
|
if (!queEntry) {
|
|
|
|
http_error("httpdSetCookie - no mem");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
queEntry->next = NULL;
|
|
|
|
queEntry->headerLine[0] = 0;
|
|
|
|
strcat(queEntry->headerLine, COOKIE_PART_SET_COOKIE);
|
|
|
|
strcat(queEntry->headerLine, parm->name);
|
|
|
|
strcat(queEntry->headerLine, "=");
|
|
|
|
strcat(queEntry->headerLine, parm->value);
|
|
|
|
if (parm->expires) {
|
|
|
|
strcat(queEntry->headerLine, COOKIE_PART_EXPIRES);
|
|
|
|
strcat(queEntry->headerLine, parm->expires);
|
|
|
|
}
|
|
|
|
if (parm->maxAge != 0) {
|
|
|
|
strcat(queEntry->headerLine, COOKIE_PART_MAX_AGE);
|
|
|
|
sprintf(queEntry->headerLine + strlen(queEntry->headerLine), "%d", parm->maxAge);
|
|
|
|
}
|
|
|
|
if (parm->domain) {
|
|
|
|
strcat(queEntry->headerLine, COOKIE_PART_DOMAIN);
|
|
|
|
strcat(queEntry->headerLine, parm->domain);
|
|
|
|
}
|
|
|
|
if (parm->path) {
|
|
|
|
strcat(queEntry->headerLine, COOKIE_PART_PATH);
|
|
|
|
strcat(queEntry->headerLine, parm->path);
|
|
|
|
}
|
|
|
|
if (parm->sameSite) {
|
|
|
|
strcat(queEntry->headerLine, COOKIE_PART_SAME_SITE);
|
|
|
|
strcat(queEntry->headerLine, parm->sameSite);
|
|
|
|
}
|
|
|
|
if (parm->secure) {
|
|
|
|
strcat(queEntry->headerLine, COOKIE_PART_SECURE);
|
|
|
|
}
|
|
|
|
if (parm->httponly) {
|
|
|
|
strcat(queEntry->headerLine, COOKIE_PART_HTTP_ONLY);
|
|
|
|
}
|
|
|
|
strcat(queEntry->headerLine, "\r\n");
|
|
|
|
|
|
|
|
if (!httpdQueueHeaderRaw(conn, queEntry)) {
|
|
|
|
// should not be possible
|
|
|
|
httpdFree(queEntry);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool httpdGetCookie(HttpdConnData *conn, const char *name, char *buff, size_t buffLen)
|
|
|
|
{
|
|
|
|
const char *p = httpdGetHeaderPtr(conn, "Cookie");
|
|
|
|
if (!p) return 0;
|
|
|
|
|
|
|
|
size_t namelen = strlen(name);
|
|
|
|
while (*p != 0 && *p != '\r' && *p != '\n') {
|
|
|
|
if (*p == ' ') { // skip whitespace
|
|
|
|
p++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *next_semi = strchr(p, ';');
|
|
|
|
if (!next_semi) {
|
|
|
|
next_semi = p + strlen(p); // end of string
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strstarts(p, name) && p[namelen] == '=') {
|
|
|
|
const char * valuestart = p + namelen + 1;
|
|
|
|
|
|
|
|
size_t valuelen = (size_t) (next_semi - valuestart);
|
|
|
|
if (valuelen >= buffLen - 1) {
|
|
|
|
valuelen = buffLen - 1;
|
|
|
|
}
|
|
|
|
strncpy(buff, valuestart, valuelen);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
p = next_semi + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void httdSetTransferMode(HttpdConnData *conn, httpd_transfer_opt mode)
|
|
|
|
{
|
|
|
|
if (mode == HTTPD_TRANSFER_CLOSE) {
|
|
|
|
conn->priv.flags &= (uint8_t) ~HFL_CHUNKED;
|
|
|
|
conn->priv.flags &= (uint8_t) ~HFL_NOCONNECTIONSTR;
|
|
|
|
} else if (mode == HTTPD_TRANSFER_CHUNKED) {
|
|
|
|
conn->priv.flags |= HFL_CHUNKED;
|
|
|
|
conn->priv.flags &= (uint8_t) ~HFL_NOCONNECTIONSTR;
|
|
|
|
} else if (mode == HTTPD_TRANSFER_NONE) {
|
|
|
|
conn->priv.flags &= (uint8_t) ~HFL_CHUNKED;
|
|
|
|
conn->priv.flags |= HFL_NOCONNECTIONSTR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void httdResponseOptions(HttpdConnData *conn, int cors)
|
|
|
|
{
|
|
|
|
if (cors == 0) { conn->priv.flags |= HFL_NOCORS; }
|
|
|
|
}
|
|
|
|
|
|
|
|
//Start the response headers.
|
|
|
|
void httpdStartResponse(HttpdConnData *conn, int code)
|
|
|
|
{
|
|
|
|
char buff[256];
|
|
|
|
const char *connStr = "";
|
|
|
|
|
|
|
|
if (!(conn->priv.flags & HFL_NOCONNECTIONSTR)) {
|
|
|
|
if (conn->priv.flags & HFL_CHUNKED) {
|
|
|
|
connStr = "Transfer-Encoding: chunked\r\n";
|
|
|
|
} else {
|
|
|
|
connStr = "Connection: close\r\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
if (!(conn->priv.flags & HFL_NOCORS)) {
|
|
|
|
// CORS headers
|
|
|
|
httpdSendStr(conn, "Access-Control-Allow-Origin: *\r\n");
|
|
|
|
httpdSendStr(conn, "Access-Control-Allow-Methods: GET,POST,OPTIONS\r\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Send a http header.
|
|
|
|
void httpdHeader(HttpdConnData *conn, const char *field, const char *val)
|
|
|
|
{
|
|
|
|
httpdSendStr(conn, field);
|
|
|
|
httpdSendStr(conn, ": ");
|
|
|
|
httpdSendStr(conn, val);
|
|
|
|
httpdSendStr(conn, "\r\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
//Finish the headers.
|
|
|
|
void httpdEndHeaders(HttpdConnData *conn)
|
|
|
|
{
|
|
|
|
// Add queued headers & dealloc the struct
|
|
|
|
HttpdQueuedHeader *qh = conn->priv.headersToSend;
|
|
|
|
conn->priv.headersToSend = NULL;
|
|
|
|
while (qh) {
|
|
|
|
httpdSendStr(conn, qh->headerLine);
|
|
|
|
HttpdQueuedHeader *next = qh->next;
|
|
|
|
httpdFree(qh);
|
|
|
|
qh = next;
|
|
|
|
}
|
|
|
|
|
|
|
|
httpdSendStr(conn, "\r\n");
|
|
|
|
conn->priv.flags |= HFL_SENDINGBODY;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Redirect to the given URL.
|
|
|
|
void httpdRedirect(HttpdConnData *conn, const char *newUrl)
|
|
|
|
{
|
|
|
|
http_dbg("Redirecting to %s", newUrl);
|
|
|
|
httpdStartResponse(conn, 302);
|
|
|
|
httpdHeader(conn, "Location", newUrl);
|
|
|
|
httpdEndHeaders(conn);
|
|
|
|
httpdSendStr(conn, "Moved to ");
|
|
|
|
httpdSendStr(conn, newUrl);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//Add data to the send buffer. len is the length of the data. If len is -1
|
|
|
|
//the data is seen as a C-string.
|
|
|
|
//Returns 1 for success, 0 for out-of-memory.
|
|
|
|
int httpdSend(HttpdConnData *conn, const uint8_t *data, size_t len)
|
|
|
|
{
|
|
|
|
if (conn->conn == NULL) { return 0; }
|
|
|
|
if (len == 0) { return 0; }
|
|
|
|
if (conn->priv.flags & HFL_CHUNKED && conn->priv.flags & HFL_SENDINGBODY && conn->priv.chunkHdr == NULL) {
|
|
|
|
if (conn->priv.sendBuffLen + len + 6 > HTTPD_MAX_SENDBUFF_LEN) { return 0; }
|
|
|
|
//Establish start of chunk
|
|
|
|
conn->priv.chunkHdr = (char *) &conn->priv.sendBuff[conn->priv.sendBuffLen];
|
|
|
|
strcpy(conn->priv.chunkHdr, "0000\r\n");
|
|
|
|
conn->priv.sendBuffLen += 6;
|
|
|
|
}
|
|
|
|
if (conn->priv.sendBuffLen + len > HTTPD_MAX_SENDBUFF_LEN) { return 0; }
|
|
|
|
memcpy(conn->priv.sendBuff + conn->priv.sendBuffLen, data, len);
|
|
|
|
conn->priv.sendBuffLen += len;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define httpdSend_orDie(conn, data, len) do { if (!httpdSend((conn), (const uint8_t *)(data), (size_t)(len))) return false; } while (0)
|
|
|
|
|
|
|
|
#define httpdSendStr_orDie(conn, data) do { if (!httpdSendStr((conn), (data))) return false; } while (0)
|
|
|
|
|
|
|
|
/* encode for HTML. returns 0 or 1 - 1 = success */
|
|
|
|
int httpdSend_html(HttpdConnData *conn, const char *data, ssize_t len)
|
|
|
|
{
|
|
|
|
int start = 0, end = 0;
|
|
|
|
char c;
|
|
|
|
if (conn->conn == NULL) { return 0; }
|
|
|
|
if (len < 0) { len = (int) strlen((const char *) data); }
|
|
|
|
if (len == 0) { return 0; }
|
|
|
|
|
|
|
|
for (end = 0; end < len; end++) {
|
|
|
|
c = data[end];
|
|
|
|
if (c == 0) {
|
|
|
|
// we found EOS
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c == '"' || c == '\'' || c == '<' || c == '>') {
|
|
|
|
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, ">"); }
|
|
|
|
}
|
|
|
|
|
|
|
|
if (start < end) {
|
|
|
|
httpdSend_orDie(conn, data + start, end - start);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* encode for JS. returns 0 or 1 - 1 = success */
|
|
|
|
int httpdSend_js(HttpdConnData *conn, const char *data, ssize_t len)
|
|
|
|
{
|
|
|
|
int start = 0, end = 0;
|
|
|
|
char c;
|
|
|
|
if (conn->conn == NULL) { return 0; }
|
|
|
|
if (len < 0) { len = (int) strlen((const char *) data); }
|
|
|
|
if (len == 0) { return 0; }
|
|
|
|
|
|
|
|
for (end = 0; end < len; end++) {
|
|
|
|
c = data[end];
|
|
|
|
if (c == 0) {
|
|
|
|
// we found EOS
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c == '"' || c == '\\' || c == '\'' || c == '<' || c == '>' || c == '\n' || c == '\r') {
|
|
|
|
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 (start < end) {
|
|
|
|
httpdSend_orDie(conn, data + start, end - start);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
//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.
|
|
|
|
bool httpdFlushSendBuffer(HttpdConnData *conn)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
size_t len;
|
|
|
|
if (conn->conn == NULL) { return false; }
|
|
|
|
if (conn->priv.chunkHdr != NULL) {
|
|
|
|
//We're sending chunked data, and the chunk needs fixing up.
|
|
|
|
//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;
|
|
|
|
//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));
|
|
|
|
conn->priv.chunkHdr[2] = httpdHexNibble((uint8_t) (len >> 4));
|
|
|
|
conn->priv.chunkHdr[3] = httpdHexNibble((uint8_t) (len >> 0));
|
|
|
|
//Reset chunk hdr for next call
|
|
|
|
conn->priv.chunkHdr = NULL;
|
|
|
|
}
|
|
|
|
if (conn->priv.flags & HFL_CHUNKED && conn->priv.flags & HFL_SENDINGBODY && conn->cgi == NULL) {
|
|
|
|
//Connection finished sending whatever needs to be sent. Add NULL chunk to indicate this.
|
|
|
|
strcpy((char *) &conn->priv.sendBuff[conn->priv.sendBuffLen], "0\r\n\r\n");
|
|
|
|
conn->priv.sendBuffLen += 5;
|
|
|
|
}
|
|
|
|
if (conn->priv.sendBuffLen != 0) {
|
|
|
|
r = httpdConnSendData(conn->conn, conn->priv.sendBuff, conn->priv.sendBuffLen);
|
|
|
|
if (!r) {
|
|
|
|
//Can't send this for some reason. Dump packet in backlog, we can send it later.
|
|
|
|
if (conn->priv.sendBacklogSize + conn->priv.sendBuffLen > HTTPD_MAX_BACKLOG_SIZE) {
|
|
|
|
http_error("Httpd: Backlog overrun, dropped %dB", (int) conn->priv.sendBuffLen);
|
|
|
|
conn->priv.sendBuffLen = 0;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
HttpdSendBacklogItem *backlogItem = httpdMalloc(sizeof(HttpdSendBacklogItem) + conn->priv.sendBuffLen);
|
|
|
|
if (backlogItem == NULL) {
|
|
|
|
http_error("Httpd: Backlog: malloc failed, out of memory!");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
memcpy(backlogItem->data, conn->priv.sendBuff, conn->priv.sendBuffLen);
|
|
|
|
backlogItem->len = conn->priv.sendBuffLen;
|
|
|
|
backlogItem->next = NULL;
|
|
|
|
if (conn->priv.sendBacklog == NULL) {
|
|
|
|
conn->priv.sendBacklog = backlogItem;
|
|
|
|
} else {
|
|
|
|
HttpdSendBacklogItem *e = conn->priv.sendBacklog;
|
|
|
|
while (e->next != NULL) { e = e->next; }
|
|
|
|
e->next = backlogItem;
|
|
|
|
}
|
|
|
|
conn->priv.sendBacklogSize += conn->priv.sendBuffLen;
|
|
|
|
}
|
|
|
|
conn->priv.sendBuffLen = 0;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void httpdCgiIsDone(HttpdConnData *conn)
|
|
|
|
{
|
|
|
|
conn->cgi = NULL; //no need to call this anymore
|
|
|
|
if (conn->priv.flags & HFL_CHUNKED) {
|
|
|
|
http_dbg("Pool slot %d is done. Cleaning up for next req", conn->slot);
|
|
|
|
httpdFlushSendBuffer(conn);
|
|
|
|
//Note: Do not clean up sendBacklog, it may still contain data at this point.
|
|
|
|
conn->priv.headPos = 0;
|
|
|
|
conn->post.len = -1;
|
|
|
|
conn->priv.flags = 0;
|
|
|
|
if (conn->post.buff) {
|
|
|
|
httpdFree(conn->post.buff);
|
|
|
|
conn->post.buff = NULL;
|
|
|
|
}
|
|
|
|
conn->post.buffLen = 0;
|
|
|
|
conn->post.received = 0;
|
|
|
|
conn->hostName = NULL;
|
|
|
|
} else {
|
|
|
|
//Cannot re-use this connection. Mark to get it killed after all data is sent.
|
|
|
|
conn->priv.flags |= HFL_DISCONAFTERSENT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Callback called when the data on a socket has been successfully
|
|
|
|
//sent.
|
|
|
|
void httpdSentCb(ConnTypePtr rconn, httpd_ipaddr_t remIp, uint16_t remPort)
|
|
|
|
{
|
|
|
|
HttpdConnData *conn = httpdFindConnData(rconn, remIp, remPort);
|
|
|
|
httpdContinue(conn);
|
|
|
|
}
|
|
|
|
|
|
|
|
//Can be called after a CGI function has returned HTTPD_CGI_MORE to
|
|
|
|
//resume handling an open connection asynchronously
|
|
|
|
void httpdContinue(HttpdConnData *conn)
|
|
|
|
{
|
|
|
|
httpdPlatLock();
|
|
|
|
|
|
|
|
if (conn == NULL) { return; }
|
|
|
|
|
|
|
|
if (conn->priv.sendBacklog != NULL) {
|
|
|
|
//We have some backlog to send first.
|
|
|
|
HttpdSendBacklogItem *next = conn->priv.sendBacklog->next;
|
|
|
|
httpdConnSendData(conn->conn, (uint8_t *) conn->priv.sendBacklog->data, conn->priv.sendBacklog->len);
|
|
|
|
conn->priv.sendBacklogSize -= conn->priv.sendBacklog->len;
|
|
|
|
httpdFree(conn->priv.sendBacklog);
|
|
|
|
conn->priv.sendBacklog = next;
|
|
|
|
httpdPlatUnlock();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (conn->priv.flags & HFL_DISCONAFTERSENT) { //Marked for destruction?
|
|
|
|
http_dbg("Pool slot %d is done. Closing.", conn->slot);
|
|
|
|
httpdConnDisconnect(conn->conn);
|
|
|
|
httpdPlatUnlock();
|
|
|
|
return; //No need to call httpdFlushSendBuffer.
|
|
|
|
}
|
|
|
|
|
|
|
|
//If we don't have a CGI function, there's nothing to do but wait for something from the client.
|
|
|
|
if (conn->cgi == NULL) {
|
|
|
|
httpdPlatUnlock();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
conn->priv.sendBuffLen = 0;
|
|
|
|
httpd_cgi_state r = conn->cgi(conn); //Execute cgi fn.
|
|
|
|
if (r == HTTPD_CGI_DONE) {
|
|
|
|
httpdCgiIsDone(conn);
|
|
|
|
}
|
|
|
|
if (r == HTTPD_CGI_NOTFOUND || r == HTTPD_CGI_AUTHENTICATED) {
|
|
|
|
http_error("ERROR! CGI fn returns code %d after sending data! Bad CGI!", r);
|
|
|
|
httpdCgiIsDone(conn);
|
|
|
|
}
|
|
|
|
httpdFlushSendBuffer(conn);
|
|
|
|
httpdPlatUnlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
//This is called when the headers have been received and the connection is ready to send
|
|
|
|
//the result headers and data.
|
|
|
|
//We need to find the CGI function to call, call it, and dependent on what it returns either
|
|
|
|
//find the next cgi function, wait till the cgi data is sent or close up the connection.
|
|
|
|
static void httpdProcessRequest(HttpdConnData *conn)
|
|
|
|
{
|
|
|
|
int i = 0;
|
|
|
|
if (conn->url == NULL) {
|
|
|
|
router_warn("WtF? url = NULL");
|
|
|
|
return; //Shouldn't happen
|
|
|
|
}
|
|
|
|
|
|
|
|
// CORS preflight, allow the token we received before
|
|
|
|
if (conn->requestType == HTTPD_METHOD_OPTIONS) {
|
|
|
|
httpdStartResponse(conn, 200);
|
|
|
|
httpdHeader(conn, "Access-Control-Allow-Headers", conn->priv.corsToken);
|
|
|
|
httpdEndHeaders(conn);
|
|
|
|
httpdCgiIsDone(conn);
|
|
|
|
|
|
|
|
router_dbg("CORS preflight resp sent.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//See if we can find a CGI that's happy to handle the request.
|
|
|
|
while (1) {
|
|
|
|
//Look up URL in the built-in URL table.
|
|
|
|
while (s_builtInUrls[i].url != NULL) {
|
|
|
|
int match = 0;
|
|
|
|
const char *route = s_builtInUrls[i].url;
|
|
|
|
//See if there's a literal match
|
|
|
|
if (streq(route, conn->url)) { match = 1; }
|
|
|
|
//See if there's a wildcard match (*)
|
|
|
|
if (!match && last_char(route) == '*' &&
|
|
|
|
strneq(route, conn->url, strlen(route) - 1)) {
|
|
|
|
match = 1;
|
|
|
|
}
|
|
|
|
// Optional slash (/?)
|
|
|
|
if (!match && last_char(route) == '?' && last_char_n(route, 2) == '/' &&
|
|
|
|
strneq(route, conn->url, strlen(route) - 2) &&
|
|
|
|
strlen(conn->url) <= strlen(route) - 1) {
|
|
|
|
match = 1;
|
|
|
|
}
|
|
|
|
if (match) {
|
|
|
|
router_dbg("Matched route #%d, url=%s", i, route);
|
|
|
|
conn->cgiData = NULL;
|
|
|
|
conn->cgi = s_builtInUrls[i].cgiCb;
|
|
|
|
conn->cgiArg = s_builtInUrls[i].cgiArg;
|
|
|
|
conn->cgiArg2 = s_builtInUrls[i].cgiArg2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
if (s_builtInUrls[i].url == NULL) {
|
|
|
|
//Drat, we're at the end of the URL table. This usually shouldn't happen. Well, just
|
|
|
|
//generate a built-in 404 to handle this.
|
|
|
|
router_warn("%s not found. 404!", conn->url);
|
|
|
|
conn->cgi = cgiNotFound;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Okay, we have a CGI function that matches the URL. See if it wants to handle the
|
|
|
|
//particular URL we're supposed to handle.
|
|
|
|
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) {
|
|
|
|
//Seems the CGI is planning to do some long-term communications with the socket.
|
|
|
|
//Disable the timeout on it, so we won't run into that.
|
|
|
|
httpdPlatDisableTimeout(conn->conn);
|
|
|
|
}
|
|
|
|
httpdFlushSendBuffer(conn);
|
|
|
|
return;
|
|
|
|
} else if (r == HTTPD_CGI_DONE) {
|
|
|
|
//Yep, it's happy to do so and already is done sending data.
|
|
|
|
httpdCgiIsDone(conn);
|
|
|
|
return;
|
|
|
|
} else if (r == HTTPD_CGI_NOTFOUND || r == HTTPD_CGI_AUTHENTICATED) {
|
|
|
|
//URL doesn't want to handle the request: either the data isn't found or there's no
|
|
|
|
//need to generate a login screen.
|
|
|
|
i++; //look at next url the next iteration of the loop.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Parse a line of header data and modify the connection data accordingly.
|
|
|
|
static void httpdParseHeader(char *h, HttpdConnData *conn)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
char firstLine = 0;
|
|
|
|
|
|
|
|
if (strstarts(h, "GET ")) {
|
|
|
|
conn->requestType = HTTPD_METHOD_GET;
|
|
|
|
firstLine = 1;
|
|
|
|
} else if (strstarts(h, "Host:")) {
|
|
|
|
i = 5;
|
|
|
|
while (h[i] == ' ') { i++; }
|
|
|
|
conn->hostName = &h[i];
|
|
|
|
} else if (strstarts(h, "POST ")) {
|
|
|
|
conn->requestType = HTTPD_METHOD_POST;
|
|
|
|
firstLine = 1;
|
|
|
|
} else if (strstarts(h, "PUT ")) {
|
|
|
|
conn->requestType = HTTPD_METHOD_PUT;
|
|
|
|
firstLine = 1;
|
|
|
|
} else if (strstarts(h, "PATCH ")) {
|
|
|
|
conn->requestType = HTTPD_METHOD_PATCH;
|
|
|
|
firstLine = 1;
|
|
|
|
} else if (strstarts(h, "OPTIONS ")) {
|
|
|
|
conn->requestType = HTTPD_METHOD_OPTIONS;
|
|
|
|
firstLine = 1;
|
|
|
|
} else if (strstarts(h, "DELETE ")) {
|
|
|
|
conn->requestType = HTTPD_METHOD_DELETE;
|
|
|
|
firstLine = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstLine) {
|
|
|
|
char *e;
|
|
|
|
|
|
|
|
//Skip past the space after POST/GET
|
|
|
|
i = 0;
|
|
|
|
while (h[i] != ' ') { i++; }
|
|
|
|
conn->url = h + i + 1;
|
|
|
|
|
|
|
|
//Figure out end of url.
|
|
|
|
e = strstr(conn->url, " ");
|
|
|
|
if (e == NULL) { return; } //wtf?
|
|
|
|
*e = 0; //terminate url part
|
|
|
|
e++; //Skip to protocol indicator
|
|
|
|
while (*e == ' ') { e++; } //Skip spaces.
|
|
|
|
//If HTTP/1.1, note that and set chunked encoding
|
|
|
|
if (strcasecmp(e, "HTTP/1.1") == 0) {
|
|
|
|
conn->priv.flags |= HFL_HTTP11 | HFL_CHUNKED;
|
|
|
|
}
|
|
|
|
|
|
|
|
http_info("URL = %s", conn->url);
|
|
|
|
//Parse out the URL part before the GET parameters.
|
|
|
|
conn->getArgs = strstr(conn->url, "?");
|
|
|
|
if (conn->getArgs != 0) {
|
|
|
|
*conn->getArgs = 0;
|
|
|
|
conn->getArgs++;
|
|
|
|
http_dbg("GET args = %s", conn->getArgs);
|
|
|
|
} else {
|
|
|
|
conn->getArgs = NULL;
|
|
|
|
}
|
|
|
|
} else if (strstarts(h, "Connection:")) {
|
|
|
|
i = 11;
|
|
|
|
//Skip trailing spaces
|
|
|
|
while (h[i] == ' ') { i++; }
|
|
|
|
if (strstarts(&h[i], "close")) { conn->priv.flags &= (uint8_t) ~HFL_CHUNKED; } //Don't use chunked conn
|
|
|
|
} else if (strstarts(h, "Content-Length:")) {
|
|
|
|
i = 15;
|
|
|
|
//Skip trailing spaces
|
|
|
|
while (h[i] == ' ') { i++; }
|
|
|
|
//Get POST data length
|
|
|
|
conn->post.len = (int) strtol(h + i, NULL, 10);
|
|
|
|
|
|
|
|
// Allocate the buffer
|
|
|
|
if (conn->post.len > HTTPD_MAX_POST_LEN) {
|
|
|
|
// we'll stream this in in chunks
|
|
|
|
conn->post.buffSize = HTTPD_MAX_POST_LEN;
|
|
|
|
} else {
|
|
|
|
conn->post.buffSize = (size_t) conn->post.len;
|
|
|
|
}
|
|
|
|
http_dbg("Mallocced buffer for %d + 1 bytes of post data.", (int) conn->post.buffSize);
|
|
|
|
conn->post.buff = (char *) httpdMalloc(conn->post.buffSize + 1);
|
|
|
|
if (conn->post.buff == NULL) {
|
|
|
|
http_error("post buf alloc failed");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
conn->post.buffLen = 0;
|
|
|
|
} else if (strstarts(h, "Content-Type:")) {
|
|
|
|
if (strstr(h, "multipart/form-data")) {
|
|
|
|
// It's multipart form data so let's pull out the boundary for future use
|
|
|
|
char *b;
|
|
|
|
if ((b = strstr(h, "boundary=")) != NULL) {
|
|
|
|
conn->post.multipartBoundary = b + 7; // move the pointer 2 chars before boundary then fill them with dashes
|
|
|
|
conn->post.multipartBoundary[0] = '-';
|
|
|
|
conn->post.multipartBoundary[1] = '-';
|
|
|
|
http_dbg("boundary = %s", conn->post.multipartBoundary);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (strstarts(h, "Access-Control-Request-Headers: ")) {
|
|
|
|
// CORS crap that needs to be repeated in the response
|
|
|
|
|
|
|
|
http_info("CORS preflight request.");
|
|
|
|
|
|
|
|
strncpy(conn->priv.corsToken, h + strlen("Access-Control-Request-Headers: "), HTTPD_MAX_CORS_TOKEN_LEN);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Make a connection 'live' so we can do all the things a cgi can do to it.
|
|
|
|
//ToDo: Also make httpdRecvCb/httpdContinue use these?
|
|
|
|
//ToDo: Fail if malloc fails?
|
|
|
|
void httpdConnSendStart(HttpdConnData *conn)
|
|
|
|
{
|
|
|
|
httpdPlatLock();
|
|
|
|
conn->priv.sendBuffLen = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Finish the live-ness of a connection. Always call this after httpdConnStart
|
|
|
|
void httpdConnSendFinish(HttpdConnData *conn)
|
|
|
|
{
|
|
|
|
if (conn->conn) { httpdFlushSendBuffer(conn); }
|
|
|
|
httpdPlatUnlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
//Callback called when there's data available on a socket.
|
|
|
|
void httpdRecvCb(ConnTypePtr rconn, httpd_ipaddr_t remIp, uint16_t remPort, uint8_t *data, size_t len)
|
|
|
|
{
|
|
|
|
httpd_cgi_state r;
|
|
|
|
size_t x;
|
|
|
|
char *p, *e;
|
|
|
|
httpdPlatLock();
|
|
|
|
|
|
|
|
HttpdConnData *conn = httpdFindConnData(rconn, remIp, remPort);
|
|
|
|
if (conn == NULL) {
|
|
|
|
httpdPlatUnlock();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
conn->priv.sendBuffLen = 0;
|
|
|
|
conn->priv.corsToken[0] = 0;
|
|
|
|
|
|
|
|
//This is slightly evil/dirty: we abuse conn->post->len as a state variable for where in the http communications we are:
|
|
|
|
//<0 (-1): Post len unknown because we're still receiving headers
|
|
|
|
//==0: No post data
|
|
|
|
//>0: Need to receive post data
|
|
|
|
//ToDo: See if we can use something more elegant for this.
|
|
|
|
|
|
|
|
for (x = 0; x < len; x++) {
|
|
|
|
if (conn->post.len < 0) {
|
|
|
|
//This byte is a header byte.
|
|
|
|
if (data[x] == '\n') {
|
|
|
|
//Compatibility with clients that send \n only: fake a \r in front of this.
|
|
|
|
if (conn->priv.headPos != 0 && conn->priv.head[conn->priv.headPos - 1] != '\r') {
|
|
|
|
conn->priv.head[conn->priv.headPos++] = '\r';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//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++] = (char) data[x]; }
|
|
|
|
conn->priv.head[conn->priv.headPos] = 0;
|
|
|
|
//Scan for /r/n/r/n. Receiving this indicates the headers end.
|
|
|
|
if (data[x] == '\n' && strstr(conn->priv.head, "\r\n\r\n") != NULL) {
|
|
|
|
//Indicate we're done with the headers.
|
|
|
|
conn->post.len = 0;
|
|
|
|
//Reset url data
|
|
|
|
conn->url = NULL;
|
|
|
|
//Iterate over all received headers and parse them.
|
|
|
|
p = conn->priv.head;
|
|
|
|
while (p < (&conn->priv.head[conn->priv.headPos - 4])) {
|
|
|
|
e = strstr(p, "\r\n"); //Find end of header line
|
|
|
|
if (e == NULL) { break; } //Shouldn't happen.
|
|
|
|
e[0] = 0; //Zero-terminate header
|
|
|
|
httpdParseHeader(p, conn); //and parse it.
|
|
|
|
p = e + 2; //Skip /r/n (now /0/n)
|
|
|
|
}
|
|
|
|
//If we don't need to receive post data, we can send the response now.
|
|
|
|
if (conn->post.len == 0) {
|
|
|
|
httpdProcessRequest(conn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (conn->post.len != 0) {
|
|
|
|
//This byte is a POST byte.
|
|
|
|
conn->post.buff[conn->post.buffLen++] = (char) data[x];
|
|
|
|
conn->post.received++;
|
|
|
|
conn->hostName = NULL;
|
|
|
|
if (conn->post.buffLen >= conn->post.buffSize || (int) conn->post.received == conn->post.len) {
|
|
|
|
//Received a chunk of post data
|
|
|
|
conn->post.buff[conn->post.buffLen] = 0; //zero-terminate, in case the cgi handler knows it can use strings
|
|
|
|
//Process the data
|
|
|
|
if (conn->cgi) {
|
|
|
|
r = conn->cgi(conn);
|
|
|
|
if (r == HTTPD_CGI_DONE) {
|
|
|
|
httpdCgiIsDone(conn);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
//No CGI fn set yet: probably first call. Allow httpdProcessRequest to choose CGI and
|
|
|
|
//call it the first time.
|
|
|
|
httpdProcessRequest(conn);
|
|
|
|
}
|
|
|
|
conn->post.buffLen = 0;
|
|
|
|
conn->post.len = 0; // this causes transfer to the recvHdl branch
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
//Let cgi handle data if it registered a recvHdl callback. If not, ignore.
|
|
|
|
if (conn->recvHdl) {
|
|
|
|
r = conn->recvHdl(conn, data + x, len - x);
|
|
|
|
if (r == HTTPD_CGI_DONE) {
|
|
|
|
http_dbg("Recvhdl returned DONE");
|
|
|
|
httpdCgiIsDone(conn);
|
|
|
|
//We assume the recvhdlr has sent something; we'll kill the sock in the sent callback.
|
|
|
|
}
|
|
|
|
break; //ignore rest of data, recvhdl has parsed it.
|
|
|
|
} else {
|
|
|
|
http_warn("Eh? Got unexpected data from client. %s", data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (conn->conn) {
|
|
|
|
httpdFlushSendBuffer(conn);
|
|
|
|
}
|
|
|
|
httpdPlatUnlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
//The platform layer should ALWAYS call this function, regardless if the connection is closed by the server
|
|
|
|
//or by the client.
|
|
|
|
void httpdDisconCb(ConnTypePtr rconn, httpd_ipaddr_t remIp, uint16_t remPort)
|
|
|
|
{
|
|
|
|
httpdPlatLock();
|
|
|
|
HttpdConnData *hconn = httpdFindConnData(rconn, remIp, remPort);
|
|
|
|
if (hconn == NULL) {
|
|
|
|
httpdPlatUnlock();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
httpdRetireConn(hconn);
|
|
|
|
httpdPlatUnlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clean up cgiData and userData and do any cgi-specific finalizing.
|
|
|
|
*
|
|
|
|
* @param hconn
|
|
|
|
*/
|
|
|
|
static void cleanupCgiAndUserData(HttpdConnData *hconn)
|
|
|
|
{
|
|
|
|
hconn->conn = NULL; //indicate cgi the connection is gone
|
|
|
|
if (hconn->cgi) {
|
|
|
|
//Execute cgi fn if needed
|
|
|
|
hconn->cgi(hconn);
|
|
|
|
hconn->cgi = NULL;
|
|
|
|
}
|
|
|
|
if (hconn->userDataCleanupCb && hconn->userData) {
|
|
|
|
hconn->userDataCleanupCb(hconn->userData);
|
|
|
|
hconn->userData = NULL;
|
|
|
|
hconn->userDataCleanupCb = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int httpdConnectCb(ConnTypePtr conn, httpd_ipaddr_t remIp, uint16_t remPort)
|
|
|
|
{
|
|
|
|
uint8_t ci; // connection index
|
|
|
|
httpdPlatLock();
|
|
|
|
|
|
|
|
//Find empty conndata in pool
|
|
|
|
for (ci = 0; ci < HTTPD_MAX_CONNECTIONS; ci++) {
|
|
|
|
if (!s_connData[ci].occupied) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
http_info("Conn req from %d.%d.%d.%d:%d, using pool slot %d", IP_SPLIT(remIp), remPort, ci);
|
|
|
|
if (ci >= HTTPD_MAX_CONNECTIONS) {
|
|
|
|
http_error("Aiee, conn pool overflow!");
|
|
|
|
httpdPlatUnlock();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(&s_connData[ci], 0, sizeof(HttpdConnData));
|
|
|
|
s_connData[ci].slot = ci;
|
|
|
|
s_connData[ci].occupied = true;
|
|
|
|
s_connData[ci].conn = conn;
|
|
|
|
s_connData[ci].remote_ip = remIp;
|
|
|
|
s_connData[ci].remote_port = remPort;
|
|
|
|
s_connData[ci].post.len = -1;
|
|
|
|
|
|
|
|
httpdPlatUnlock();
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Httpd initialization routine. Call this to kick off webserver functionality.
|
|
|
|
httpd_thread_handle_t *httpdStart(const HttpdBuiltInUrl *fixedUrls, struct httpd_init_options *options)
|
|
|
|
{
|
|
|
|
memset(s_connData, 0, sizeof(s_connData));
|
|
|
|
|
|
|
|
s_builtInUrls = fixedUrls;
|
|
|
|
|
|
|
|
httpdPlatInit();
|
|
|
|
|
|
|
|
http_info("Httpd init");
|
|
|
|
|
|
|
|
return httpdPlatStart(options);
|
|
|
|
}
|
|
|
|
|
|
|
|
void httpdJoin(httpd_thread_handle_t *handle)
|
|
|
|
{
|
|
|
|
if (handle) {
|
|
|
|
httpdPlatJoin(handle);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void httpdSetName(const char *name)
|
|
|
|
{
|
|
|
|
s_serverName = name;
|
|
|
|
}
|