A simple web server that can be bound to either the EthernetInterface or the WiflyInterface.

Dependents:   Smart-WiFly-WebServer WattEye X10Svr SSDP_Server

SW_HTTPServer.cpp

Committer:
WiredHome
Date:
2019-02-27
Revision:
60:d49a346c386f
Parent:
59:9a71ac02c782

File content as of revision 60:d49a346c386f:

//
// @note Copyright © 2014-2016 by Smartware Computing, all rights reserved.
//     Individuals may use this application for evaluation or non-commercial
//     purposes. Within this restriction, changes may be made to this application
//     as long as this copyright notice is retained. The user shall make
//     clear that their work is a derived work, and not the original.
//     Users of this application and sources accept this application "as is" and
//     shall hold harmless Smartware Computing, for any undesired results while
//     using this application - whether real or imagined.
//
// author David Smart, Smartware Computing
//
#include "mbed.h"
#include "SW_String.h"      // Secure methods and others that were missing

//#define DEBUG "HTTP"
#include <cstdio>
#if (defined(DEBUG) && !defined(TARGET_LPC11U24))
#define DBG(x, ...)  std::printf("[DBG %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#define WARN(x, ...) std::printf("[WRN %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#define ERR(x, ...)  std::printf("[ERR %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#define INFO(x, ...) std::printf("[INF %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#else
#define DBG(x, ...)
#define WARN(x, ...)
#define ERR(x, ...)
#define INFO(x, ...)
#endif

#include "SW_HTTPServer.h"      // define DEBUG before this

#define CHUNK_SIZE 1500     // max size of a single chunk (probably limited by Ethernet to 1500)
#define HANG_TIMEOUT_MS 250 // If we're waiting on the host, which may never respond, this is the timeout

const char * DEFAULT_FILENAME = "index.htm";

// Header information to always send (must be \r\n terminated)
static const char hdr_httpver[] = "HTTP/1.1";                       // supported HTTP/1.1 protocol (sort of)
static const char nl[]          = "\r\n";                           // final \r\n for the termination of the header

// Header items that are sent if the user does not provide their own options.
static const char hdr_age[]     = "Max-age: 0\r\n";                 // expires right away
static const char hdr_server[]  = "Server: Smart_Server v0.2\r\n";  // Server
static const char hdr_close[]   = "Connection: close\r\n";          // tell the client the server closes the connection immediately

static const struct {
    char *ext;
    char *filetype;
} extensions [] = {
    {".gif", "Content-Type: image/gif\r\n"          },
    {".jpg", "Content-Type: image/jpeg\r\n"         },
    {".jpeg","Content-Type: image/jpeg\r\n"         },
    {".ico", "Content-Type: image/x-icon\r\n"       },
    {".bmp", "Content-Type: image/bmp\r\n"          },
    {".png", "Content-Type: image/png\r\n"          },
    {".zip", "Content-Type: image/zip\r\n"          },
    {".gz",  "Content-Type: image/gz\r\n"           },
    {".tar", "Content-Type: image/tar\r\n"          },
    {".txt", "Content-Type: plain/text\r\n"         },
    {".pdf", "Content-Type: application/pdf\r\n"    },
    {".htm", "Content-Type: text/html\r\n"          },
    {".html","Content-Type: text/html\r\n"          },
    {".xml", "Content-Type: text/xml\r\n"           },
    {".js",  "Content-Type: text/javascript\r\n"    },
    {0,0}
};

typedef struct {
    char * queryType;
    int notused;
} QueryMethod;

// Be sure to include a trailing space.
static const QueryMethod QueryMethodList[] = {
    {"GET ", 0},
    {"POST ", 0},
    {"HEAD ", 0},
    {"PUT ", 0},
    {"OPTION ", 0},
    {"DELETE ", 0},
    {"TRACE ", 0},
    {"CONNECT ", 0},
    {NULL, 0}
};

#if 1 && defined(DEBUG)
// Haven't learned anything from this in a long time, so disabled.
// This uses standard library dynamic memory management, but for an
// embedded system there are alternates that may make better sense -
// search the web for embedded system malloc alternates.
void * HTTPServer::MyMalloc(int Bytes, int LINE)
{
    void * pMem = malloc(Bytes);
    pc->printf("[INF %s %4d] MyMalloc %p = malloc(%d)\r\n", DEBUG, LINE, pMem, Bytes);
    return pMem;
}
char HTTPServer::toP(void * x)
{
    char * c = (char *) x;
    if (*c >= ' ' && *c < 0x7F) // isprint()
        return *c;
    else
        return '.';
}
#define mymalloc(Bytes) MyMalloc(Bytes, __LINE__)
#define myfree(x) \
    pc->printf("[INF %s %4d] MyMalloc %p = free(%02x %02x %02x %02x %02x ... %c%c%c%c%c)\r\n", \
        DEBUG, __LINE__, \
        x, \
        *x, *(x+1), *(x+2), *(x+3), *(x+4), \
        toP(x), toP(x+1), toP(x+2), toP(x+3), toP(x+4) ); \
    free(x);
#else
#define mymalloc(x) malloc(x)
#define myfree(x) free(x)
#endif



HTTPServer::HTTPServer(
    int port,
    const char * _webroot,
    int _maxheaderParams,
    int _maxqueryParams,
    int _maxdynamicpages,
    int blockingtime,
    PC * _pc,
    int _allocforheader,
    int _allocforfile
    )
{
    int wrSize = strlen(_webroot)+1;
    
    webroot = (char *)malloc(wrSize);
    strcpy_s(webroot, wrSize, _webroot);
    if (strlen(webroot)>1 && webroot[strlen(webroot)-1] == '/') // remove trailing '/'
        webroot[strlen(webroot)-1] = '\0';
    filenameAliasList = NULL;
    maxheaderParams = _maxheaderParams;
    headerParams = (namevalue *)malloc(maxheaderParams * sizeof(namevalue));

    maxqueryParams = _maxqueryParams;
    queryParams = (namevalue *)malloc(maxqueryParams * sizeof(namevalue));
    queryParamCount = 0;
    
    maxPostParams = _maxqueryParams;   // Same as Query params, but for post method
    postParams = (namevalue *)malloc(maxPostParams * sizeof(namevalue));
    postParamCount = 0;
    maxdynamicpages = _maxdynamicpages;
    handlers = (handler *)malloc(maxdynamicpages * sizeof(handler));
    handlercount = 0;
    ndxActiveHandler = -1;
    headerbuffersize = _allocforheader;
    headerbuffer = (char *)malloc(headerbuffersize);
    pc = _pc;
    queryType = NULL;
    queryString = NULL;
    postQueryString = NULL;
    maxheaderbytes = 0;
    server = new TCPSocketServer();
    server->bind(port);
    server->listen(5);
    server->set_blocking(false, blockingtime);
    client.set_blocking(true,  blockingtime);  //@TODO client is separate from server. any way to combine?
    ResetPerformanceData();
    PerformanceTimer.start();
}

HTTPServer::~HTTPServer()
{
    int i;

    for (i=0; i<handlercount; i++)
        myfree(handlers[i].path);
    myfree(headerbuffer);
    myfree(handlers);
    myfree(queryParams);
    myfree(webroot);
    webroot = NULL;
}

void HTTPServer::RegisterFilenameAliasList(const namevalue * namevaluelist)
{
    filenameAliasList = namevaluelist;
}

int HTTPServer::GetMaxHeaderSize()
{
    return maxheaderbytes;
}

bool HTTPServer::RegisterHandler(const char * path, Handler callback)
{
    if (handlercount < maxdynamicpages && path && callback) {
        handlers[handlercount].path = (char *)mymalloc(strlen(path)+1);
        memcpy(handlers[handlercount].path, path, strlen(path)+1);
        handlers[handlercount].callback = callback;
        handlercount++;
        return true;
    } else {
        return false;
    }
}

const char * HTTPServer::GetActiveHandlerPath(void)
{
    if (ndxActiveHandler >= 0)
        return handlers[ndxActiveHandler].path;
    else
        return NULL;
}

// Poll()
//
// *OPEN*GET /x=1 HTTP/1.1
// Host: 192.168.1.140
// Connection: keep-alive
// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
// User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36
// Accept-Encoding: gzip,deflate,sdch
// Accept-Language: en-US,en;q=0.8
//
void HTTPServer::Poll()
{
    typedef enum {
        Idle,               // waiting for a connection
        ReceivingHeader,    // receiving header
        ReceivingPayload,   // receiving a section after the Header
        Sending,            // send the response
        WaitingToClose,     // small timeout to close
        Reset
    } state;
    static state op = Idle;
    static char * bPtr = headerbuffer;
    int n;
    static unsigned int t_ref;       // reference point for the PerformanceTimer

#if defined(DEBUG)
    static state lastOp = Reset;
    if (lastOp != op) {
        const char *states[] = {"Idle", "ReceivingHeader", "ReceivingPayload", "Sending", "WaitingToClose", "Reset"};
        INFO("Poll: %-30s   ######## %8u us", states[op], (unsigned int)PerformanceTimer.read_us()-t_ref);
        lastOp = op;
    }
#endif
    switch(op) {
        default:                    // not expected to arrive here
            op = Idle;
            break;

        case Idle:
            PerformanceTimer.reset();
            t_ref = (unsigned int)PerformanceTimer.read_us();
            bPtr = headerbuffer;
            if (0 == server->accept(client)) {
                op = ReceivingHeader;
                t_ref = RecordPerformanceData(&perfData.ConnectionAccepted, t_ref);
                INFO("Accepted at                                     %8u us", 
                    (unsigned int)PerformanceTimer.read_us()-t_ref);
            } else {
                //INFO("Timeout waiting for accept()");
            }
            break;

        case ReceivingHeader:
            n = client.receive(bPtr, headerbuffersize - (bPtr - headerbuffer));
            INFO("client.receive() returned %d, from %s", n, client.get_address());
            INFO("  ReceivingHeader: %s", bPtr);
            if (n == -2) {
                //   was hang here waiting ... op = Sending; which causes misses
                op = WaitingToClose;
            } else if (n < 0) {
                op = WaitingToClose;    // bail out...
            } else if (n) {
                bPtr[n] = '\0';
                switch (ParseHeader(headerbuffer)) {
                    case ACCEPT_ERROR:
                        break;
                    case ACCEPT_COMPLETE:
                        op = Sending;
                        t_ref = RecordPerformanceData(&perfData.HeaderParsed, t_ref);
                        INFO("Header Parsed at                                %8u us", 
                            (unsigned int)PerformanceTimer.read_us()-t_ref);
                        break;
                    case ACCEPT_CONTINUE:
                        op = ReceivingPayload;
                        break;
                }
                bPtr += n;
            }
            break;

        case ReceivingPayload:
            // After the header, there is a payload that will be handled
#if 1
            INFO("ReceivingPayload                                %8u us", 
                (unsigned int)PerformanceTimer.read_us()-t_ref);
            n = client.receive(bPtr, headerbuffersize - (bPtr - headerbuffer));
            if (n < 0) {
                op = Sending;
                INFO("*** client.receive() => %d", n);
            } else if (n) {
                bPtr[n] = '\0';
                INFO("*** payload size %d", n);
            }
#else
            op = Sending;
#endif
            break;

        case Sending:
            INFO("SendResponse at                                 %8u us", 
                (unsigned int)PerformanceTimer.read_us()-t_ref);
            SendResponse();
            op = WaitingToClose;
            t_ref = RecordPerformanceData(&perfData.ResponseSent, t_ref);
            INFO("Response Sent at                                %8u us", 
                (unsigned int)PerformanceTimer.read_us()-t_ref);
            break;

        case WaitingToClose:
            INFO("close_connection                                %8u us", 
                (unsigned int)PerformanceTimer.read_us()-t_ref);
            close_connection();
            op = Idle;
            RecordPerformanceData(&perfData.ConnectionClosed, t_ref);
            INFO("Connection closed exit:                         %8u us\r\n\r\n", 
                (unsigned int)PerformanceTimer.read_us()-t_ref);
            break;
    }
}


const char * HTTPServer::GetSupportedType(const char * filename)
{
    int i;
    int buflen = strlen(filename);
    int extlen;

    for (i=0; extensions[i].ext != 0; i++) {
        extlen = strlen(extensions[i].ext);
        if ( !strncmp(&filename[buflen-extlen], extensions[i].ext, extlen)) {
            return extensions[i].filetype;
        }
    }
    return NULL;
}


int HTTPServer::send(const char * msg, int bytes)
{
    if (bytes == -1)
        bytes = strlen(msg);
    INFO("Sending %d bytes", bytes);
    //INFO("send:\r\n%s", msg);
    int r = client.send_all((char *)msg, bytes);
    INFO("client.send returned: %d", r);
    return r;
}

const char * HTTPServer::FindAlias(const HTTPServer::namevalue * haystack, const char * needle)
{
    while (haystack && haystack->name) {
        if (strcmp(haystack->name,needle) == 0)
            return haystack->value;
        haystack++;
    }
    return needle;
}

uint32_t HTTPServer::FileSize(const char * filename)
{
    uint32_t size = 0;
    FILE * fh;

    filename = FindAlias(filenameAliasList, filename);
    fh = fopen(filename, "r");
    if (fh) {
        fseek(fh, 0, SEEK_END); // seek to end of file
        size = ftell(fh);       // get current file pointer
        fclose(fh);
    }
    return size;
}

bool HTTPServer::SendFile(const char * filename, const char * filetype)
{
    FILE * fp;

    INFO("SendFile(%s,...)", filename);
    filename = FindAlias(filenameAliasList, filename);
    INFO("   Alias(%s,...)", filename);
    fp  = fopen(filename,"rb");
    if (fp) { // can open it
        char *fbuffer = (char *)mymalloc(FILESEND_BUF_SIZE);
        int bytes;

        if (fbuffer) {
            char ContentLen[30];
            snprintf(ContentLen, sizeof(ContentLen), "Content-Length: %u\r\n", FileSize(filename));
            header(OK, "OK", filetype, ContentLen);
            header("");
            bytes = fread(fbuffer,sizeof(char),FILESEND_BUF_SIZE-1,fp);
            INFO("Start with %d bytes", bytes);
            int retryCount = 2;
            while (bytes > 0) {
                int r = send(fbuffer, bytes);
                if (r >= 0) {
                    bytes = fread(fbuffer,sizeof(char),FILESEND_BUF_SIZE-1,fp);
                    INFO("      Next %d bytes", bytes);
                    //INFO("::%s", fbuffer);
                } else if (r == -2) {
                    if (retryCount-- == 0) {
                        ERR("send failed with %d [retries] - terminating SendFile().", r);
                        break;
                    }
                    INFO("send failed with %d (timeout), hopefully it will recover.", r);
                } else {
                    ERR("send failed with %d, but why? - terminating SendFile().", r);
                    break;
                }
            }
            myfree(fbuffer);
        } else {
            header(Server_Error, "Server Error", NULL, "Pragma: err - insufficient memory\r\n");
            header("");
        }
        fclose(fp);
        return true;
    } else {
        header(Not_Found, "Not Found", NULL, "Pragma: err - Can't open file\r\n");
        header("");
        return false;
    }
}

int HTTPServer::HexCharToInt(char c)
{
    if (c >= 'a' && c <= 'f')
        return (c - 'a' + 10);
    else if (c >= 'A' && c <= 'F')
        return (c - 'A' + 10);
    else if (c >= '0' && c <= '9')
        return c - '0';
    else
        return 0;
}

char HTTPServer::HexPairToChar(char * p)
{
    return 16 * HexCharToInt(*p) + HexCharToInt(*(p+1));
}

// modifies in-place
void HTTPServer::UnescapeString(char * encoded)
{
    char *p;

    // first convert '+' to ' '
    p = strchr(encoded, '+');
    while (p) {
        *p = ' ';
        p = strchr(encoded, '+');
    }
    // then convert hex '%xx' to char 'x'
    p = strchr(encoded, '%');
    while (p) {
        if (strchr("0123456789ABCDEFabcdef", *(p+1))
                && strchr("0123456789ABCDEFabcdef", *(p+2)) ) {
            *p = HexPairToChar(p+1);
            p++;    // advance past the %
            char * a = p;
            char * b = p + 2;
            do {
                *a++ = *b++;
            } while (*b);
            *a = '\0';
        }
        p = strchr(p, '%');
    }
}

const char * HTTPServer::GetParameter(const char * name)
{
    INFO("GetParameter(%s)", name);
    for (int i=0; i<queryParamCount; i++) {
        INFO("  %d: %s = %s", i, queryParams[i].name, queryParams[i].value);
        if (strcmp(queryParams[i].name, name) == 0) {
            INFO("  value {%s}", queryParams[i].value);
            return queryParams[i].value;
        }
    }
    return NULL;
}

const HTTPServer::namevalue * HTTPServer::GetParameter(int index)
{
    if (index < queryParamCount)
        return &queryParams[index];
    else
        return NULL;
}

const char * HTTPServer::GetPostParameter(const char * name)
{
    INFO("GetPostParameter(%s)", name);
    for (int i=0; i<postParamCount; i++) {
        INFO("  %d: %s = %s", i, postParams[i].name, postParams[i].value);
        if (strcmp(postParams[i].name, name) == 0) {
            INFO("  value {%s}", postParams[i].value);
            return postParams[i].value;
        }
    }
    return NULL;
}

HTTPServer::namevalue * HTTPServer::GetPostParameter(int index)
{
    if (index < postParamCount)
        return &postParams[index];
    else
        return NULL;
}

int HTTPServer::ParsePostParameters(char * pPostString)
{
    return ParseParameters(postParams, &postParamCount, maxPostParams, pPostString);
}


// this=that&who=what&more=stuff...
// ^   ^    ^
int HTTPServer::ParseParameters(namevalue * qP, int * qpCount, int maxP, char * pName)
{
    char * pVal;
    char * pNextName;

    // Parse queryParams
    pVal = strchr(pName, '#');      // If there is a '#fragment_id', we can ignore it
    if (pVal)
        *pVal = '\0';
    do {
        INFO("ParseParameters(%s), qpCount: %d", pName, *qpCount);
        qP->name = pName;
        pVal = strchr(pName, '=');
        pNextName = strchr(pName,'&');
        if (pVal) {
            if (pNextName == NULL || (pNextName && pNextName > pVal)) {
                *pVal++ = '\0';
                qP->value = pVal;
                pName = pVal;
            }
        }
        if (pNextName) {
            pName = pNextName;
            *pName++ = '\0';
        } else {
            pName = NULL;
        }
        INFO("  param{%s}={%s}", qP->name, qP->value);
        *qpCount += 1;
        qP++;
    } while (pName && *qpCount < maxP);
    INFO("  count %d", *qpCount);
    return *qpCount;
}


void HTTPServer::header(HTTPServer::HeaderCodes code, const char * code_text, const char * content_type, const char * optional_text)
{
    char http[100];

    if (optional_text == NULL)
        optional_text = "";
    INFO("header(%d, %s, %s, %s)", code, code_text, content_type, optional_text);
    snprintf(http, sizeof(http), "%s %i %s\r\n", hdr_httpver, code, code_text);
    send(http);
    if (content_type) {
        send(content_type);
    }
    if (*optional_text != '\0') {
        send(optional_text);
    } else {
        if (strlen(hdr_age) + strlen(hdr_server) + strlen(hdr_close) + strlen(nl) < sizeof(http)) {
            strcpy_s(http, 100, hdr_age);
            strcat_s(http, 100, hdr_server);
            strcat_s(http, 100, hdr_close);
            strcat_s(http, 100, nl);
            send(http);
        } else {
            send(hdr_age);
            send(hdr_server);
            send(hdr_close);
            send(nl);
        }
    }
}

void HTTPServer::header(const char * partialheader)
{
    if (!partialheader || *partialheader == '\0')
        send(nl);
    else
        send(partialheader);
}

bool HTTPServer::close_connection()
{
    bool res;

    res = client.close();
    INFO("close connection returned %d", res);
    return res;
}


bool HTTPServer::Extract(char * haystack, char * needle, char ** string)
{
    bool ret = false;       // assume failure until proven otherwise
    char * qs = NULL;
    char * eqs = NULL;
    char * container = NULL;
    char * get = strstr(haystack, needle);    // what if not at the front?
    
    if (get) {
        // Seems to be a valid "GET /QueryString HTTP/1.1"
        // or                  "POST /upnp/control/metainfo1 HTTP/1.0"
        // or "...<needle>param..."
        qs = get + strlen(needle);  // in case the needle didn't have space delimiters
        while (*qs == ' ')
            qs++;
        // /QueryString\0HTTP/1.1\0\0
        if (*string)            // recycle old string when working a new one
            myfree(*string);
        int ctSize = strlen(qs);
        container = (char *)mymalloc(ctSize);
        if (container) {
            strcpy_s(container, ctSize, qs);
            eqs = strchr(container, ' ');
            if (eqs)
                *eqs = '\0';
            *string = container;
            INFO("Extract(%s) = %s", needle, container);
            ret = true;
        } else {
            *string = NULL;     // something bad happened... no memory
        }
    }
    return ret;
}


char * HTTPServer::rewriteWithDefaultFile(char * queryString)
{
    int tSize = strlen(queryString) + strlen(DEFAULT_FILENAME) + 1;
    char * temp = (char *)mymalloc(tSize);

    if (temp) {
        *temp = '\0';
        strcpy_s(temp, tSize, queryString);
        strcat_s(temp, tSize, DEFAULT_FILENAME);
        myfree(queryString);
        return temp;
    } else {
        return queryString;
    }
}


char * HTTPServer::rewritePrependWebroot(char * queryString)
{
    int tSize = strlen(webroot) + strlen(queryString) + 2;
    char * temp = (char *)mymalloc(tSize);  // save room for '/'

    if (temp) {
        char *lastC;
        *temp = '\0';
        strcpy_s(temp, tSize, webroot);
        lastC = &temp[strlen(temp)-1];
        if (*lastC == '/' && *queryString == '/')
            queryString++;  // don't create two '/'
        else if (*lastC != '/' && *queryString != '/')
            strcat_s(temp, tSize, "/");
        strcat_s(temp, tSize, queryString);
        myfree(queryString);
        return temp;
    } else {
        return queryString;
    }
}


bool HTTPServer::CheckDynamicHandlers()
{
    bool regHandled = false;

    // If this queryString is in the list of registered handlers, call that
    for (ndxActiveHandler=0; ndxActiveHandler<handlercount; ndxActiveHandler++) {
        if (strcmp(handlers[ndxActiveHandler].path, queryString) == 0) {
            INFO("CheckDynamicHandlers - SEND_PAGE %s", handlers[ndxActiveHandler].path);
            (*handlers[ndxActiveHandler].callback)(this, SEND_PAGE, queryString, queryParams, queryParamCount);
            regHandled = true;
            ndxActiveHandler = -1;
            break;      // we only execute the first one
        }
    }
    return regHandled;
}


void HTTPServer::SendResponse()
{
    INFO("SendResponse(%5s) at                          %8u us", 
        queryType, (unsigned int)PerformanceTimer.read_us());
    if (strcmp(queryType, "GET ") == 0 ||  strcmp(queryType, "POST ") == 0) {
        if (!(queryString[0] == '.' && queryString[1] == '.')) {
            const char * fType;

            if (!CheckDynamicHandlers()) {
                // Otherwise, this queryString must be trying to reference a static file
                if (queryString[strlen(queryString)-1] == '/') {
                    queryString = rewriteWithDefaultFile(queryString);
                }
                INFO("  queryString: %s", queryString);
                // see if we support this file type
                fType = GetSupportedType(queryString);
                if (fType) {
                    queryString = rewritePrependWebroot(queryString);
                    INFO("  SendFile(%s,%s)", queryString, fType);
                    SendFile(queryString, fType);
                } else {
                    header(Not_Found, "Not Found", NULL, "Pragma: err - Unsupported type\r\n");
                    header("");
                }
            }
        } else {
            header(Bad_Request, "Bad Request", NULL, "Pragma: err - Unsupported path\r\n");
            header("");
        }
    } else {
        header(Bad_Request, "Bad Request", NULL, "Pragma: err - Unsupported query type\r\n");
        header("");
    }
    INFO("  SendResponse complete at                      %8u us", 
        (unsigned int)PerformanceTimer.read_us());

    if (postQueryString) {
        myfree(postQueryString);
        postQueryString = NULL;
    }
    if (queryType) {
        myfree(queryType);
        queryType = NULL;
    }
    if (queryString) {
        myfree(queryString);
        queryString = NULL;
    }
    INFO("  SendResponse free at                          %8u us", 
        (unsigned int)PerformanceTimer.read_us());
}


HTTPServer::CallBackResults HTTPServer::ParseHeader(char * buffer)
{
    char * dblCR;
    CallBackResults advanceState = ACCEPT_ERROR;
    int bytecount;

    // Buffer could have partial, but the double \r\n is the key
    //      GET /QueryString?this=that&sky=blue HTTP/1.1\r\n
    //      GET /QueryString HTTP/1.1\r\nHost: 192.168.1.140\r\nCache-Con
    //      GET /QueryString HTTP/1.1\r\nHost: 192.168.1.140\r\nCache-Control: max-age=0\r\n\r\n
    //      POST /dyn2 HTTP/1.2\r\nAccept: text/html, application/xhtml+xml, */*\r\n\r\n
    dblCR = strstr(buffer,"\r\n\r\n");
    if (dblCR) {  // Have to scan from the beginning in case split on \r
        INFO("\r\n==\r\n%s\r\n==", buffer);
        char * soRec = buffer;                  // start of the next record of text
        char * eoRec = strchr(soRec, '\n');     // search for end of the current record

        headerParamCount = 0;
        bytecount = strlen(buffer);
        if (bytecount > maxheaderbytes)
            maxheaderbytes = bytecount;
        while (eoRec) {
            *eoRec = '\0';
            if (*(eoRec-1) == '\r')
                *(eoRec-1) = '\0';
            INFO("method: %s", soRec);
            #if 1
            const QueryMethod * qm = QueryMethodList;
            while (*qm->queryType) {
                if (strstr(soRec, qm->queryType) == soRec) {
                    Extract(soRec, qm->queryType, &queryString);
                    if (queryString) {
                        int qSize = strlen(qm->queryType)+1;
                        queryType = (char *)mymalloc(qSize);
                        strcpy_s(queryType, qSize, qm->queryType);
                    }
                }
                qm++;
            }
            #else
            // Inspect the supported query types (GET, POST) and ignore (HEAD, PUT, OPTION, DELETE, TRACE, CONNECT]
            // This is presently very clumsy
            if (strstr(soRec, "GET ") == soRec) {
                Extract(soRec, "GET", &queryString);
                if (queryString) {
                    int qSize = strlen("GET")+1;
                    queryType = (char *)mymalloc(qSize);
                    strcpy_s(queryType, qSize, "GET");
                }
            } else if (strstr(soRec, "POST ") == soRec) {
                Extract(soRec, "POST", &queryString);
                if (queryString) {
                    int qSize = strlen("POST")+1;
                    queryType = (char *)mymalloc(qSize);
                    strcpy_s(queryType, qSize, "POST");
                }
            }
            #endif
            
            // if there is a ": " delimiter, we have a header item to parse into name,value pair
            // "Connection: keep-alive" becomes "Connection" "keep-alive"
            char *delim = strstr(soRec, ": ");
            char *chkSpace = strchr(soRec, ' ');        // a field-name has no space ahead of the ":"
            //INFO("hpc:%d,mhp:%d, {%s}", headerParamCount, maxheaderParams, soRec);
            if (delim
                    && (!chkSpace || (chkSpace && delim < chkSpace))
                    && headerParamCount < maxheaderParams) {
                *delim++ = '\0';        // replace ": " with null
                *delim++ = '\0';
                headerParams[headerParamCount].name = soRec;
                headerParams[headerParamCount].value = delim;
                INFO("%d: headerParams[%s] = {%s}", headerParamCount,
                     headerParams[headerParamCount].name, headerParams[headerParamCount].value);
                headerParamCount++;
            }
            soRec = eoRec + 1;
            eoRec = strchr(soRec, '\n');
            if (soRec > dblCR)      // Just walked past the end of the header
                break;
        }
        
        if (queryString) {
            // We have enough to try to reply
            INFO("create reply queryType{%s}, queryString{%s}", queryType, queryString);
            // parse queryParams - if any
            // /file.htm?name1=value1&name2=value2...
            // /file.htm?name1&name2=value2...
            queryParamCount = 0;
            char * paramDelim = strchr(queryString, '?');
            if (paramDelim) {
                *paramDelim++ = '\0';
                UnescapeString(paramDelim);   // everything after the '?'
                ParseParameters(queryParams, &queryParamCount, maxqueryParams, paramDelim);  // pointing past the NULL, and there are queryParams here
            }
        } else {
            ERR("queryString not found in (%s) [this should never happen]", soRec);
        }
        advanceState = ACCEPT_COMPLETE; // Should be ACCEPT_CONTINUE and the stuff below moves out of here
        buffer[0] = 0;

        // This part parses the extra data on a POST method.
        // Since there has to be a dynamic handler registered for this
        // it would make sense to move some of this responsibility to
        // that handler. It could then choose if it wanted to allocate
        // the requested 'Content-Length' amount of memory.
        int postBytes = atoi(GetHeaderValue("Content-Length"));
        CallBackResults acceptIt = ACCEPT_ERROR;
        INFO("Content-Length: %d", postBytes);
        if (strcmp(queryType, "POST ") == 0 ) {
            INFO("parse POST data %d bytes", postBytes);        // We might have no idea how much data is coming...
            int ndxHandler = 0;
            bool regHandled = false;
            // Registered Dynamic Handler
            // Callback and ask if they want to accept this data
            for (ndxHandler=0; ndxHandler<handlercount; ndxHandler++) {
                INFO("is '%s' a handler for '%s' ?", handlers[ndxHandler].path, queryString);
                if (strcmp(handlers[ndxHandler].path, queryString) == 0) {
                    acceptIt = (*handlers[ndxHandler].callback)(this, CONTENT_LENGTH_REQUEST, queryString, queryParams, queryParamCount);
                    regHandled = true;
                    break;      // only one callback per path allowed
                }
            }
            INFO("reghandled: %d, acceptIt: %d", regHandled, acceptIt);
            if (regHandled && acceptIt != ACCEPT_ERROR) {
                // @todo need to refactor - if the thing is bigger than the buffer,
                //       then we can receive it a chunk at a time, and hand off
                //       the chunks to the callback. May need callbacks that
                //       are: DATA_TRANSFER: self-detect to extract the filename/object name,
                //            DATA_TRANSFER: subsequent chunk of data,
                //            DATA_TRANSFER_END:   signals that last chunk is enclosed.
                //
                // If so, we'll make space for it
                postQueryString = (char *)mymalloc(CHUNK_SIZE);
                //INFO("Free space %d", Free());
                INFO("postQueryString %p", postQueryString);
                if (postQueryString) {
                    int len = 0;
                    int ttlCount;
                    Timer escapePlan;
                    bool escape = false;
                    
                    INFO("Processing tail...");
                    escapePlan.start();
                    dblCR += 4;     // There may be some after the double CR that we need
                    ttlCount = strlen(dblCR);
                    strcpy_s(postQueryString, CHUNK_SIZE, dblCR);
                    INFO("  {%s}", postQueryString);
                    while ((!postBytes || ttlCount < postBytes) && !escape) {
                        INFO("ttlCount: %d < postBytes: %d, of max chunk alloc %d", ttlCount, postBytes, CHUNK_SIZE);
                        len = client.receive(postQueryString + ttlCount, CHUNK_SIZE - ttlCount);
                        if (len > 0) {
                            ttlCount += len;
                            postQueryString[ttlCount] = '\0';    // Whether binary or ASCII, this is ok as it's after the data
                            INFO("  postBytes %d: [%s], [%d]", postBytes, postQueryString, ndxHandler);
                            escapePlan.reset();
                        } else if (len < 0) {
                            INFO("*** connection closed ***");
                            break;      // no more data, before the plan
                        } else { // n == 0
                            ;
                        }
                        if (escapePlan.read_ms() > HANG_TIMEOUT_MS) {   // if no Content-Length, we wait...
                            escape = true;
                            WARN("Escape plan activated.");
                        }
                        if (postBytes > 0 && ttlCount >= postBytes)
                            break;
                    }
                    //postParamCount = 0;
                    INFO("post: %s", postQueryString);
                    //We're after the header, so there is "body" stuff which could be anything...
                    //but probably html or xml stuff...
                    //ParseParameters(postParams, &postParamCount, maxPostParams, postQueryString);
                    acceptIt = (*handlers[ndxHandler].callback)(this, DATA_TRANSFER, postQueryString, NULL, ttlCount);
                    INFO("..processing exit [%d]", acceptIt);
                    acceptIt = (*handlers[ndxHandler].callback)(this, DATA_TRANSFER_END, NULL, NULL, 0);
                    INFO("..transfer end[%d]", acceptIt);
                } else {
                    ERR("attempt to allocate %d failed.", CHUNK_SIZE);
                }
            } else {
                // Simply copy it to the bitbucket
                WARN("No handler, so to the bit bucket it goes ...");
                int bytesToDump = postBytes;
                char * bitbucket = (char *)mymalloc(201);
                
                dblCR += 4;
                while (*dblCR && *dblCR <= ' ')
                    dblCR++;
                bytesToDump -= strlen(dblCR);
                while (bytesToDump > 0) {
                    int n = (bytesToDump > 200) ? 200 : bytesToDump;
                    n = client.receive(bitbucket, n);
                    if (n < 0) {
                        ERR("to the bitbucket.");
                        break;
                    }
                    bytesToDump -= n;
                }
                myfree(bitbucket);
            }
        }
    }
    return advanceState;
}



const char * HTTPServer::GetHeaderValue(const char * hdr)
{
    int i;

    for (i=0; i<headerParamCount; i++) {
        if (strcmp(hdr, headerParams[i].name) == 0)
            return headerParams[i].value;
    }
    return NULL;
}


void HTTPServer::GetPerformanceData(SW_PerformanceData * p)
{
    memcpy(p, &perfData, sizeof(perfData));
}

unsigned int HTTPServer::GetPerformanceClock()
{
    return (unsigned int)PerformanceTimer.read_us();
}

unsigned int HTTPServer::RecordPerformanceData(SW_PerformanceParam * param, unsigned int refTime)
{
    unsigned int t_now = (unsigned int)PerformanceTimer.read_us();
    param->TotalTime_us += (t_now - refTime);
    param->Samples++;
    if ((t_now - refTime) > param->MaxTime_us)
        param->MaxTime_us = (t_now - refTime);
    return t_now;
}


void HTTPServer::ResetPerformanceData()
{
    memset(&perfData, 0, sizeof(perfData));
}