Common stuff for all my devices' web server pages: css, login, log, ipv4, ipv6, firmware update, clock, reset info etc.

Dependents:   oldheating gps motorhome heating

Security

A password has to be set whenever there has been a software reset. Resets following faults or power on do not require a new password as the hash is restored from the RTC GPREG register.

The password is not saved on the device; instead a 32 bit hash of the password is saved. It would take 2^31 attempts to brute force the password: this could be done in under a month if an attempt were possible every millisecond. To prevent this a 200 ms delay is introduced in the reply to the login form, that gives a more reasonable 13 years to brute force the password.

Once the password is accepted a random session id is created. This is 36 bit to give six base 64 characters but without an extra delay. If an attempt could be made every ms then this would still take over a year to brute force.

The most likely attack would to use a dictionary with, say, 10 million entries against the password which would still take 20 days to do.

Files at this revision

API Documentation at this revision

Comitter:
andrewboyson
Date:
Tue Sep 24 18:16:47 2019 +0000
Parent:
129:6d9bffc72676
Child:
131:a9793a9721c7
Commit message:
Added http

Changed in this revision

base/net-trace/web-trace-ajax.c Show annotated file Show diff for this revision Revisions of this file
http/http-connection.c Show annotated file Show diff for this revision Revisions of this file
http/http-connection.h Show annotated file Show diff for this revision Revisions of this file
http/http.c Show annotated file Show diff for this revision Revisions of this file
http/http.h Show annotated file Show diff for this revision Revisions of this file
http/httpadd.c Show annotated file Show diff for this revision Revisions of this file
http/httpdate.c Show annotated file Show diff for this revision Revisions of this file
http/httpquery.c Show annotated file Show diff for this revision Revisions of this file
http/httprequest.c Show annotated file Show diff for this revision Revisions of this file
http/https.c Show annotated file Show diff for this revision Revisions of this file
http/https.h Show annotated file Show diff for this revision Revisions of this file
http/httpsame.c Show annotated file Show diff for this revision Revisions of this file
http/status/http-not-found.c Show annotated file Show diff for this revision Revisions of this file
http/status/http-not-modified.c Show annotated file Show diff for this revision Revisions of this file
http/status/http-ok.c Show annotated file Show diff for this revision Revisions of this file
web-connection.c Show diff for this revision Revisions of this file
web-connection.h Show diff for this revision Revisions of this file
web.c Show annotated file Show diff for this revision Revisions of this file
web.h Show annotated file Show diff for this revision Revisions of this file
diff -r 6d9bffc72676 -r 9a5b8fe308f1 base/net-trace/web-trace-ajax.c
--- a/base/net-trace/web-trace-ajax.c	Sun Sep 01 18:12:48 2019 +0000
+++ b/base/net-trace/web-trace-ajax.c	Tue Sep 24 18:16:47 2019 +0000
@@ -26,7 +26,7 @@
 #include        "ip6.h"
 #include        "udp.h"
 #include        "tcp.h"
-#include       "http.h"
+#include        "web.h"
 #include       "tftp.h"
 #include  "ntpclient.h"
 
@@ -94,7 +94,7 @@
     nibble = 0; //12
     if (UdpTrace        ) nibble |= 1;
     if (TcpTrace        ) nibble |= 2;
-    if (HttpTrace       ) nibble |= 4;
+    if (WebTrace        ) nibble |= 4;
     if (TftpTrace       ) nibble |= 8;
     HttpAddNibbleAsHex(nibble);
 }
diff -r 6d9bffc72676 -r 9a5b8fe308f1 http/http-connection.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/http-connection.c	Tue Sep 24 18:16:47 2019 +0000
@@ -0,0 +1,74 @@
+#include <stdlib.h>
+
+#include "http-connection.h"
+#include "mstimer.h"
+
+#define MAX_CONNECTIONS 10
+
+static struct HttpConnection connections[MAX_CONNECTIONS];
+
+static void zeroConnection(struct HttpConnection* p)
+{
+    p->id           = 0;
+    p->lastUsed     = 0;
+    p->toDo         = 0;
+    p->postComplete = false;
+    p->delayUntil   = 0;
+}
+
+struct HttpConnection* HttpConnectionNew(int connectionId) //Never fails so never returns NULL
+{
+    struct HttpConnection* p;
+    
+    //Look for an existing connection
+    for (p = connections; p < connections + MAX_CONNECTIONS; p++)
+    {
+        if (p->id == connectionId) goto end;
+    }
+    
+    //look for an empty connection
+    {
+        struct HttpConnection* pOldest = 0;
+        uint32_t ageOldest = 0;
+        for (p = connections; p < connections + MAX_CONNECTIONS; p++)
+        {
+            if (!p->id) goto end;
+            
+            //Otherwise record the oldest and keep going
+            uint32_t age = MsTimerCount - p->lastUsed;
+            if (age >= ageOldest)
+            {
+                ageOldest = age;
+                  pOldest = p;
+            }
+        }
+    
+        //No empty ones found so use the oldest
+        p = pOldest;
+    }
+    
+end:
+    zeroConnection(p);
+    p->id           = connectionId;
+    p->lastUsed     = MsTimerCount;
+    return p;
+}
+struct HttpConnection* HttpConnectionOrNull(int connectionId)
+{
+    for (struct HttpConnection* p = connections; p < connections + MAX_CONNECTIONS; p++)
+    {
+        if (p->id == connectionId)
+        {
+            p->lastUsed = MsTimerCount;
+            return p;
+        }
+    }
+    return NULL;
+}
+void HttpConnectionReset(int connectionId)
+{
+    for (struct HttpConnection* p = connections; p < connections + MAX_CONNECTIONS; p++)
+    {
+        if (p->id == connectionId) zeroConnection(p);
+    }
+}
diff -r 6d9bffc72676 -r 9a5b8fe308f1 http/http-connection.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/http-connection.h	Tue Sep 24 18:16:47 2019 +0000
@@ -0,0 +1,15 @@
+#include <stdint.h>
+#include <stdbool.h>
+
+struct HttpConnection
+{
+    int      id; //An id of zero means the record is empty
+    uint32_t lastUsed;
+    int      toDo;
+    bool     postComplete;
+    uint32_t delayUntil;
+};
+
+extern struct HttpConnection* HttpConnectionNew   (int connectionId); //Never fails so never returns NULL
+extern struct HttpConnection* HttpConnectionOrNull(int connectionId);
+extern void                   HttpConnectionReset (int connectionId);
\ No newline at end of file
diff -r 6d9bffc72676 -r 9a5b8fe308f1 http/http.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/http.c	Tue Sep 24 18:16:47 2019 +0000
@@ -0,0 +1,105 @@
+#include "http.h"
+#include "http-connection.h"
+#include "web.h"
+#include "mstimer.h"
+#include "log.h"
+
+bool HttpGetTrace()
+{
+    return WebTrace;
+}
+
+void HttpReset(int connection)
+{
+    HttpConnectionReset(connection);
+}
+bool HttpResponse(int connection, bool clientFinished, int* pWindowSize, char* pWindow, uint32_t windowPositionInStream)
+{
+    int status = HttpPoll(connection, clientFinished);
+    bool finished;
+    switch (status)
+    {
+        case HTTP_WAIT:                   finished = false;             *pWindowSize = 0;                               break;
+        case HTTP_FINISHED:               finished = true;              *pWindowSize = 0;                               break;
+        case HTTP_HAVE_SOMETHING_TO_SEND: finished = HttpAdd(connection, pWindowSize, pWindow, windowPositionInStream); break;
+    }
+    return finished;
+}
+bool HttpAdd(int connectionId, int* pWindowSize, char* pWindow, uint32_t windowPositionInStream)
+{
+    HttpAddStart(windowPositionInStream, *pWindowSize, pWindow); //We have something to send so start the buffer
+    *pWindowSize = 0;
+    
+    struct HttpConnection* pConnection = HttpConnectionOrNull(connectionId);
+    if (!pConnection) return false; //Protects the gap between the connection being established and the request being started
+    
+    int todo = pConnection->toDo; //Make a copy so that we don't modify todo in the state
+    
+    WebAddResponse(todo);
+    
+    *pWindowSize = HttpAddLength();
+    
+    return !HttpAddFilled(); //If we haven't used a full buffer then we have finished
+}
+int HttpPoll(int connectionId, bool clientFinished)
+{
+    struct HttpConnection* pConnection = HttpConnectionOrNull(connectionId);
+    if (!pConnection) return HTTP_WAIT; //Protects the gap between the connection being established and the request being started
+    
+    if (!pConnection->toDo)
+    {
+        if (clientFinished) return HTTP_FINISHED; //The client hasn't requested anything and never will so finish
+        else                return HTTP_WAIT;     //The client hasn't requested anything yet but still could
+    }
+    if (!pConnection->postComplete               ) return HTTP_WAIT;  //Wait for the request (usually a POST) to finish
+    if (!MsTimerAbsolute(pConnection->delayUntil)) return HTTP_WAIT;  //Wait a while (usually after a LOGIN attempt)
+    
+    return HTTP_HAVE_SOMETHING_TO_SEND;
+}
+void HttpRequest(int connectionId, int windowSize, char* pWindow, uint32_t windowPositionInStream)
+{
+    struct HttpConnection* pConnection;
+    if (!windowPositionInStream)
+    {
+        pConnection = HttpConnectionNew(connectionId);
+    }
+    else
+    {
+        pConnection = HttpConnectionOrNull(connectionId);
+        if (!pConnection)
+        {
+            LogTimeF("WebRequest - no connection corresponds to id %d\r\n", connectionId);
+            return;
+        }
+    }
+    
+    pConnection->delayUntil = MsTimerCount; //Default to no delay unless modified;
+    
+    //Handle request for the first packet of data received but leave todo the same after that.
+    int contentLength = 0;
+    int contentStart  = 0;
+    if (windowSize && windowPositionInStream == 0)
+    {
+        //Read the headers
+        char* pMethod;
+        char* pPath;
+        char* pQuery;
+        char* pLastModified;
+        char* pCookies;
+        contentStart = HttpRequestRead(pWindow, windowSize, &pMethod, &pPath, &pQuery, &pLastModified, &pCookies, &contentLength);
+        
+        //Ask the web server what to do
+        pConnection->toDo = WebDecideWhatToDo(pPath, pLastModified);
+        
+        //Handle the query
+        int stop = WebHandleQuery(pQuery, pCookies, &pConnection->toDo, &pConnection->delayUntil); //return -1 on stop; 0 on continue
+        if (stop)
+        {
+            pConnection->postComplete = true;
+            return;
+        }
+    }
+    
+    //Do the upload of anything that needs it. Todos it doesn't understand are ignored.
+    if (!pConnection->postComplete) WebHandlePost(pConnection->toDo, contentLength, contentStart, windowSize, pWindow, windowPositionInStream, &pConnection->postComplete);
+}
diff -r 6d9bffc72676 -r 9a5b8fe308f1 http/http.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/http.h	Tue Sep 24 18:16:47 2019 +0000
@@ -0,0 +1,62 @@
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <time.h>
+
+#define HTTP_WAIT                    0
+#define HTTP_FINISHED                1
+#define HTTP_HAVE_SOMETHING_TO_SEND  2
+
+extern void   HttpReset   (int connectionId);
+extern bool   HttpResponse(int connectionId, bool clientFinished, int* pWindowSize, char* pWindow, uint32_t windowPositionInStream);
+extern bool   HttpAdd     (int connectionId,                      int* pWindowSize, char* pWindow, uint32_t windowPositionInStream); //returns true if finished; false if not
+extern int    HttpPoll    (int connectionId, bool clientFinished);  //returns true if something to send; false if not
+extern void   HttpRequest (int connectionId,                      int   windowSize, char* pWindow, uint32_t windowPositionInStream);
+
+extern bool   HttpGetTrace(void);
+
+extern void   HttpAddStart        (uint32_t position, int mss, char *pData);
+extern int    HttpAddLength       (void);
+extern bool   HttpAddFilled       (void);
+
+extern void   HttpAddChar         (char c);
+extern void   HttpAddFillChar     (char c, int length);
+extern int    HttpAddText         (const char* text);
+extern int    HttpAddV            (char *fmt, va_list argptr);
+extern int    HttpAddF            (char *fmt, ...);
+extern void   HttpAddData         (const char* data, int length);
+extern void   HttpAddStream       (void (*startFunction)(void), int (*enumerateFunction)(void));
+extern void   HttpAddNibbleAsHex  (int value);
+extern void   HttpAddByteAsHex    (int value);
+extern void   HttpAddInt12AsHex   (int value);
+extern void   HttpAddInt16AsHex   (int value);
+extern void   HttpAddInt32AsHex   (int value);
+extern void   HttpAddInt64AsHex   (int64_t value);
+extern void   HttpAddBytesAsHex   (const uint8_t* value, int size);
+extern void   HttpAddBytesAsHexRev(const uint8_t* value, int size);
+extern void   HttpAddTm           (struct tm* ptm);
+
+extern void   HttpOk(const char* contentType, const char* cacheControl, const char* lastModifiedDate, const char* lastModifiedTime);
+extern char*  HttpOkCookieName;
+extern char*  HttpOkCookieValue;
+extern int    HttpOkCookieMaxAge;
+
+extern void   HttpNotFound        (void);
+extern void   HttpNotModified     (void);
+
+extern int    HttpRequestRead(char *p, int len, char** ppMethod, char** ppPath, char** ppQuery, char** ppLastModified, char** ppCookies, int* pContentLength);
+
+extern char*  HttpCookiesSplit   (char* pCookies, char** ppName, char** ppValue);
+extern char*  HttpQuerySplit     (char* pQuery,   char** ppName, char** ppValue);
+extern int    HttpQueryValueAsInt(char* pValue);
+extern void   HttpQueryUnencode  (char* pValue);
+
+extern void   HttpDateFromDateTime(const char* date, const char *ptime, char* ptext);
+extern void   HttpDateFromNow(char* pText);
+
+extern bool HttpSameStr               (const char* pa,   const char* pb);
+extern bool HttpSameStrCaseInsensitive(const char* pa,   const char* pb);
+extern bool HttpSameDate              (const char* date, const char* time, const char* pOtherDate);
+
+#define HTTP_DATE_LENGTH 30
+
diff -r 6d9bffc72676 -r 9a5b8fe308f1 http/httpadd.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/httpadd.c	Tue Sep 24 18:16:47 2019 +0000
@@ -0,0 +1,190 @@
+#include <time.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdarg.h>
+
+static uint32_t currentPositionInMessage;
+static uint32_t bufferPositionInMessage;
+static int bufferLength;
+static char* pBuffer;
+static char* p;
+
+void HttpAddStart(uint32_t position, int mss, char *pData)
+{
+    currentPositionInMessage = 0;
+    bufferPositionInMessage = position;
+    bufferLength = mss;
+    pBuffer = pData;
+    p       = pData;
+}
+int HttpAddLength()
+{
+    return p - pBuffer;
+}
+bool HttpAddFilled()
+{
+    return p - pBuffer >= bufferLength;
+}
+
+void HttpAddChar(char c)
+{
+    //Add character if the current position is within the buffer
+    if (currentPositionInMessage >= bufferPositionInMessage &&
+        currentPositionInMessage <  bufferPositionInMessage + bufferLength) *p++ = c;
+    
+    currentPositionInMessage++;
+}
+
+void HttpAddFillChar (char c, int length)
+{
+    while (length > 0)
+    {
+        HttpAddChar(c);
+        length--;
+    }
+}
+int  HttpAddText  (const char* text)
+{
+    const char* start = text;
+    while (*text)
+    {
+        HttpAddChar(*text);
+        text++;
+    }
+    return text - start;
+}
+int  HttpAddV     (char *fmt, va_list argptr)
+{
+    int size  = vsnprintf(NULL, 0, fmt, argptr); //Find the size required
+    char text[size + 1];                         //Allocate enough memory for the size required with an extra byte for the terminating null
+    vsprintf(text, fmt, argptr);                 //Fill the new buffer
+    return HttpAddText(text);                    //Add the text
+}
+int  HttpAddF     (char *fmt, ...)
+{
+    va_list argptr;
+    va_start(argptr, fmt);
+    int size = HttpAddV(fmt, argptr);
+    va_end(argptr);
+    return size;
+}
+void HttpAddData  (const char* data, int length)
+{
+    while (length > 0)
+    {
+        HttpAddChar(*data);
+        data++;
+        length--;
+    }
+}
+void HttpAddStream(void (*startFunction)(void), int (*enumerateFunction)(void))
+{
+    startFunction();
+    while (true)
+    {
+        int c = enumerateFunction();
+        if (c == EOF) break;
+        HttpAddChar(c);
+    }
+}
+void HttpAddNibbleAsHex(int nibble)
+{
+    nibble &= 0x0F;
+    char c;
+    if      (nibble < 0x0A) c = nibble + '0';
+    else if (nibble < 0x10) c = nibble - 0xA + 'A';
+    else                    c = '0';
+    HttpAddChar(c);
+}
+void HttpAddByteAsHex(int value)
+{
+    HttpAddNibbleAsHex(value >> 4);
+    HttpAddNibbleAsHex(value >> 0);
+}
+void HttpAddInt12AsHex(int value)
+{   
+    HttpAddNibbleAsHex(value >> 8);
+    HttpAddNibbleAsHex(value >> 4);
+    HttpAddNibbleAsHex(value >> 0);
+}
+void HttpAddInt16AsHex(int value)
+{   
+    HttpAddNibbleAsHex(value >> 12);
+    HttpAddNibbleAsHex(value >>  8);
+    HttpAddNibbleAsHex(value >>  4);
+    HttpAddNibbleAsHex(value >>  0);
+}
+void HttpAddInt32AsHex(int value)
+{   
+    HttpAddNibbleAsHex(value >> 28);
+    HttpAddNibbleAsHex(value >> 24);
+    HttpAddNibbleAsHex(value >> 20);
+    HttpAddNibbleAsHex(value >> 16);
+    HttpAddNibbleAsHex(value >> 12);
+    HttpAddNibbleAsHex(value >>  8);
+    HttpAddNibbleAsHex(value >>  4);
+    HttpAddNibbleAsHex(value >>  0);
+}
+void HttpAddInt64AsHex(int64_t value)
+{   
+    HttpAddNibbleAsHex(value >> 60);
+    HttpAddNibbleAsHex(value >> 56);
+    HttpAddNibbleAsHex(value >> 52);
+    HttpAddNibbleAsHex(value >> 48);
+    HttpAddNibbleAsHex(value >> 44);
+    HttpAddNibbleAsHex(value >> 40);
+    HttpAddNibbleAsHex(value >> 36);
+    HttpAddNibbleAsHex(value >> 32);
+    HttpAddNibbleAsHex(value >> 28);
+    HttpAddNibbleAsHex(value >> 24);
+    HttpAddNibbleAsHex(value >> 20);
+    HttpAddNibbleAsHex(value >> 16);
+    HttpAddNibbleAsHex(value >> 12);
+    HttpAddNibbleAsHex(value >>  8);
+    HttpAddNibbleAsHex(value >>  4);
+    HttpAddNibbleAsHex(value >>  0);
+}
+void HttpAddBytesAsHex(const uint8_t* value, int size)
+{
+    int i = 0;
+    while(true)
+    {
+        HttpAddByteAsHex(value[i]);
+        i++;
+        if (i >= size) break;
+        if (i % 16 == 0) HttpAddText("\r\n");
+        else             HttpAddChar(' ');
+    }
+}
+void HttpAddBytesAsHexRev(const uint8_t* value, int size)
+{
+    int i = 0;
+    while(true)
+    {
+        HttpAddByteAsHex(value[size - i - 1]);
+        i++;
+        if (i >= size) break;
+        if (i % 16 == 0) HttpAddText("\r\n");
+        else             HttpAddChar(' ');
+    }
+}
+void HttpAddTm(struct tm* ptm)
+{
+    HttpAddF("%d-%02d-%02d ", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday);
+    switch(ptm->tm_wday)
+    {
+        case  0: HttpAddText("Sun"); break;
+        case  1: HttpAddText("Mon"); break;
+        case  2: HttpAddText("Tue"); break;
+        case  3: HttpAddText("Wed"); break;
+        case  4: HttpAddText("Thu"); break;
+        case  5: HttpAddText("Fri"); break;
+        case  6: HttpAddText("Sat"); break;
+        default: HttpAddText("???"); break;
+    }
+    HttpAddF(" %02d:%02d:%02d", ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
+    if      (ptm->tm_isdst  > 0) HttpAddText(" BST");
+    else if (ptm->tm_isdst == 0) HttpAddText(" GMT");
+    else                         HttpAddText(" UTC");
+}
diff -r 6d9bffc72676 -r 9a5b8fe308f1 http/httpdate.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/httpdate.c	Tue Sep 24 18:16:47 2019 +0000
@@ -0,0 +1,22 @@
+#include <stdlib.h>
+
+#include   "tm.h"
+#include "http.h"
+#include  "clk.h"
+
+static void dateFromTm(struct tm* ptm, char* ptext)
+{
+    size_t size = strftime(ptext, HTTP_DATE_LENGTH, "%a, %d %b %Y %H:%M:%S GMT", ptm);//Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
+}
+void HttpDateFromNow(char* pText)
+{
+    struct tm tm;
+    ClkNowTmUtc(&tm);
+    dateFromTm(&tm, pText);
+}
+void HttpDateFromDateTime(const char* date, const char *ptime, char* ptext)
+{
+    struct tm tm;
+    TmFromAsciiDateTime(date, ptime, &tm);
+    dateFromTm(&tm, ptext);
+}
diff -r 6d9bffc72676 -r 9a5b8fe308f1 http/httpquery.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/httpquery.c	Tue Sep 24 18:16:47 2019 +0000
@@ -0,0 +1,91 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+static int hexToInt(char c)
+{
+    int nibble;
+    if (c >= '0' && c <= '9') nibble = c - '0';
+    if (c >= 'A' && c <= 'F') nibble = c - 'A' + 0xA;
+    if (c >= 'a' && c <= 'f') nibble = c - 'a' + 0xA;
+    return nibble;
+}
+void HttpQueryUnencode(char* pValue)
+{
+    char* pDst = pValue;
+    int a;
+    for (char* pSrc = pValue; *pSrc; pSrc++)
+    {
+        char c = *pSrc;
+        switch (c)
+        {
+            case '+':
+                c = ' ';
+                break;
+            case '%':
+                c = *++pSrc;
+                if (c == 0) break;
+                a = hexToInt(c);
+                a <<= 4;
+                c = *++pSrc;
+                if (c == 0) break;
+                a += hexToInt(c);
+                c = a;
+                break;
+            default:
+                c = *pSrc;
+                break;
+        }
+        *pDst++ = c;
+    }
+    *pDst = 0;
+}
+char* HttpQuerySplit(char* p, char** ppName, char** ppValue) //returns the start of the next name value pair
+{    
+    *ppName    = p;                     //Record the start of the name
+    *ppValue   = NULL;
+
+    while (*p != '=')                   //Loop to an '='
+    {
+        if (*p == 0)    return 0;
+        p++;
+    }
+    *p = 0;                             //Terminate the name by replacing the '=' with a NUL char
+    p++;                                //Move on to the start of the value
+    *ppValue = p;                       //Record the start of the value
+    while (*p != '&')                   //Loop to a '&'
+    {
+        if (*p == 0)    return 0;
+        p++;
+    }
+    *p = 0;                            //Terminate the value by replacing the '&' with a NULL
+    return p + 1;
+}
+int HttpQueryValueAsInt(char* pValue)
+{
+    return (int)strtol(pValue, NULL, 10);
+}
+char* HttpCookiesSplit(char* p, char** ppName, char** ppValue) //returns the start of the next name value pair
+{    
+    *ppValue   = NULL;
+    *ppName    = NULL;
+
+    *ppName    = p;                     //Record the start of the name
+    while (*p != '=')                   //Loop to an '='
+    {
+        if (*p == 0) return 0;
+        p++;
+    }
+    *p = 0;                             //Terminate the name by replacing the '=' with a NUL char
+    p++;                                //Move on to the start of the value
+    *ppValue = p;                       //Record the start of the value
+    while (*p != ';')                   //Loop to a ';'
+    {
+        if (*p == 0) return 0;
+        p++;
+    }
+    *p = 0;                            //Terminate the value by replacing the ';' with a NULL
+    p++;
+    if (*p == 0)     return 0;
+    while (*p == ' ') p++;             //Move past any spaces after the ';'
+    return p;
+}
diff -r 6d9bffc72676 -r 9a5b8fe308f1 http/httprequest.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/httprequest.c	Tue Sep 24 18:16:47 2019 +0000
@@ -0,0 +1,105 @@
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "http.h"
+#include "log.h"
+
+static char* terminateThisAndGetNextLine(char* p, char* pE) //Terminates this line and returns the start of the next line or NULL if none
+{
+    while (true)
+    {
+        if ( p == pE)   { *p = 0; return NULL;  } //no more buffer - relies on having designed the buffer to be bigger than an ethernet packet
+        if (*p == 0)              return NULL;    //there are no more lines
+        if (*p == '\n') { *p = 0; return p + 1; } //return the start of the next line
+        if (*p < ' ')     *p = 0;                 //terminate the line at any invalid characters but keep going to find the start of the next line
+        if (*p >= 0x7f)   *p = 0;                 //terminate the line at any invalid characters but keep going to find the start of the next line
+        p++;
+    }
+}
+
+static void splitRequest(char* p, char** ppMethod, char** ppPath, char** ppQuery)
+{        
+    *ppMethod   = NULL;
+    *ppPath     = NULL;
+    *ppQuery    = NULL;
+
+    while (*p == ' ')         //Move past any leading spaces
+    {
+        if (*p == 0) return;
+        p++;
+    }
+    *ppMethod = p;            //Record the start of the method (GET or POST)
+ 
+    while (*p != ' ')         //Move past the method
+    {
+        if (*p == 0) return;
+        p++;
+    } 
+    *p = 0;                   //Terminate the method
+    p++;                      //Start at next character
+
+    while (*p == ' ')         //Move past any spaces
+    {
+        if (*p == 0) return;
+        p++;
+    } 
+    *ppPath = p;              //Record the start of the path
+    
+    while (*p != ' ')         //Move past the path and query
+    {
+        if (*p == 0) return;
+        if (*p == '?')
+        {
+            *p = 0;           //Terminate the path
+            *ppQuery = p + 1; //Record the start of the query
+        }
+        p++;
+    }
+    *p = 0;                   //Terminate the path or query
+}
+static void splitHeader(char* p, char** ppName, char** ppValue)
+{
+    *ppName    = p;                     //Record the start of the name
+    *ppValue   = NULL;
+
+    while (*p != ':')                   //Loop to an ':'
+    {
+        if (!*p) return;
+        p++;
+    }
+    *p = 0;                             //Terminate the name by replacing the ':' with a NUL char
+    p++;
+    while (*p == ' ')                   //Move past any spaces
+    {
+        if (*p == 0) return;
+        p++;
+    }
+    *ppValue = p;                       //Record the start of the value
+}
+int HttpRequestRead(char *pData, int len, char** ppMethod, char** ppPath, char** ppQuery, char** ppLastModified, char** ppCookies, int* pContentLength)
+{
+    char* pEnd = pData + len;
+    char* pThis = pData;
+    char* pNext = terminateThisAndGetNextLine(pThis, pEnd);
+    splitRequest(pThis, ppMethod, ppPath, ppQuery);
+
+    *ppLastModified = NULL; //Return NULL if no 'If-Modified-Since' line
+    *ppCookies      = NULL; //Return NULL if no 'Cookie'            line
+    *pContentLength = 0;    //Return 0    if no 'Content-Length'    line
+    while(pNext)
+    {
+        pThis = pNext;
+        pNext = terminateThisAndGetNextLine(pThis, pEnd);
+        if (*pThis == 0) break;     //This line is empty ie no more headers
+        char* pName;
+        char* pValue;
+        splitHeader(pThis, &pName, &pValue);
+        if (HttpSameStrCaseInsensitive(pName, "If-Modified-Since")) *ppLastModified = pValue;
+        if (HttpSameStrCaseInsensitive(pName, "Cookie"           )) *ppCookies      = pValue;
+        if (HttpSameStrCaseInsensitive(pName, "Content-Length"   )) *pContentLength = atoi(pValue);
+    }
+    if (pNext) return pNext - pData;
+    else       return len;
+}
diff -r 6d9bffc72676 -r 9a5b8fe308f1 http/https.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/https.c	Tue Sep 24 18:16:47 2019 +0000
@@ -0,0 +1,22 @@
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "tls.h"
+#include "http.h"
+
+bool HttpsGetTrace()
+{
+    return HttpGetTrace() || TlsTrace;
+}
+void HttpsReset(int connectionId)
+{
+    TlsReset(connectionId);
+}
+bool HttpsResponse(int connectionId, bool clientFinished, int* pWindowSize, uint8_t* pWindow, uint32_t windowPositionInStream)
+{
+    return TlsResponse(connectionId, clientFinished, pWindowSize, pWindow, windowPositionInStream);
+}
+void HttpsRequest (int connectionId, int windowSize, uint8_t* pWindow, uint32_t windowPositionInStream)
+{
+    TlsRequest(connectionId, windowSize, pWindow, windowPositionInStream);
+}
\ No newline at end of file
diff -r 6d9bffc72676 -r 9a5b8fe308f1 http/https.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/https.h	Tue Sep 24 18:16:47 2019 +0000
@@ -0,0 +1,7 @@
+#include <stdint.h>
+#include <stdbool.h>
+
+extern bool HttpsGetTrace(void);
+extern void HttpsReset   (int connectionId);
+extern bool HttpsResponse(int connectionId, bool clientFinished, int* pWindowSize, uint8_t* pWindow, uint32_t windowPositionInStream);
+extern void HttpsRequest (int connectionId,                      int   windowSize, uint8_t* pWindow, uint32_t windowPositionInStream);
\ No newline at end of file
diff -r 6d9bffc72676 -r 9a5b8fe308f1 http/httpsame.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/httpsame.c	Tue Sep 24 18:16:47 2019 +0000
@@ -0,0 +1,37 @@
+#include <stdbool.h>
+#include <ctype.h>
+#include "http.h"
+
+bool HttpSameStr(const char* pa, const char* pb)
+{
+    if (!pa || !pb) return false; //Handle NULL references
+    
+    while(true)
+    {
+        if ( *pa != *pb) return false; //If they are not the same return false
+        if (!*pa)        return true;  //If finished return true;
+        pa++;
+        pb++;
+    }
+}
+bool HttpSameStrCaseInsensitive(const char* pa, const char* pb)
+{
+    if (!pa || !pb) return false; //Handle NULL references
+    
+    while(true)
+    {
+        if ( toupper(*pa) != toupper(*pb)) return false; //If they are not the same return false
+        if (!*pa)        return true;  //If finished return true;
+        pa++;
+        pb++;
+    }
+}
+bool HttpSameDate(const char* date, const char* time, const char* pOtherDate)
+{
+    if (!pOtherDate) return false; //Not the same if no lastModified
+    
+    char pFileDate[HTTP_DATE_LENGTH];
+    HttpDateFromDateTime(date, time, pFileDate);
+    
+    return HttpSameStr(pFileDate, pOtherDate);
+}
\ No newline at end of file
diff -r 6d9bffc72676 -r 9a5b8fe308f1 http/status/http-not-found.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/status/http-not-found.c	Tue Sep 24 18:16:47 2019 +0000
@@ -0,0 +1,7 @@
+#include "http.h"
+
+void HttpNotFound()
+{
+    HttpAddText("HTTP/1.1 404 Not Found\r\n"
+                    "\r\n");
+}
diff -r 6d9bffc72676 -r 9a5b8fe308f1 http/status/http-not-modified.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/status/http-not-modified.c	Tue Sep 24 18:16:47 2019 +0000
@@ -0,0 +1,12 @@
+#include "http.h"
+
+void HttpNotModified()
+{
+    HttpAddText("HTTP/1.1 304 Not Modified\r\n"
+                     "Date: ");
+    char pDate[HTTP_DATE_LENGTH];
+    HttpDateFromNow(pDate);
+    HttpAddText(pDate);
+    HttpAddText("\r\n"
+                     "\r\n");
+}
diff -r 6d9bffc72676 -r 9a5b8fe308f1 http/status/http-ok.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/status/http-ok.c	Tue Sep 24 18:16:47 2019 +0000
@@ -0,0 +1,38 @@
+#include "log.h"
+#include "http.h"
+
+char* HttpOkCookieName;
+char* HttpOkCookieValue;
+int   HttpOkCookieMaxAge = -1; //-1 = none, 0 = clear, +ve values = number of seconds
+
+void HttpOk(const char* contentType, const char* cacheControl, const char* lastModifiedDate, const char* lastModifiedTime)
+{
+    char pDate[HTTP_DATE_LENGTH];
+    
+    if (!contentType)  LogTimeF("HtmlOk - missing Content-Type info\r\n");
+    if (!cacheControl) LogTimeF("HtmlOk - missing Cache-Control info\r\n");
+    
+    HttpAddF("HTTP/1.1 200 OK\r\n"
+             "Connection: close\r\n"
+             "Content-Type: %s\r\n", contentType);
+                
+    HttpAddF("Cache-Control: %s\r\n", cacheControl);
+    
+    HttpDateFromNow(pDate);
+    HttpAddF("Date: %s\r\n", pDate);
+    
+    if (lastModifiedDate)
+    {
+        HttpDateFromDateTime(lastModifiedDate, lastModifiedTime, pDate);
+        HttpAddF("Last-Modified: %s\r\n", pDate);
+    }
+    
+    if (HttpOkCookieName && HttpOkCookieValue)
+    {
+        HttpAddF("Set-Cookie: %s=%s", HttpOkCookieName, HttpOkCookieValue );
+        if (HttpOkCookieMaxAge >= 0) HttpAddF("; Max-Age=%d",   HttpOkCookieMaxAge);
+        HttpAddText("\r\n");
+    }
+    
+    HttpAddText("\r\n");
+}
diff -r 6d9bffc72676 -r 9a5b8fe308f1 web-connection.c
--- a/web-connection.c	Sun Sep 01 18:12:48 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-#include <stdlib.h>
-
-#include "web-connection.h"
-#include "mstimer.h"
-
-#define MAX_CONNECTIONS 10
-
-static struct WebConnection connections[MAX_CONNECTIONS];
-
-static void zeroConnection(struct WebConnection* p)
-{
-    p->id           = 0;
-    p->lastUsed     = 0;
-    p->toDo         = 0;
-    p->postComplete = false;
-    p->delayUntil   = 0;
-}
-
-struct WebConnection* WebConnectionNew(int connectionId) //Never fails so never returns NULL
-{
-    struct WebConnection* p;
-    
-    //Look for an existing connection
-    for (p = connections; p < connections + MAX_CONNECTIONS; p++)
-    {
-        if (p->id == connectionId) goto end;
-    }
-    
-    //look for an empty connection
-    {
-        struct WebConnection* pOldest = 0;
-        uint32_t ageOldest = 0;
-        for (p = connections; p < connections + MAX_CONNECTIONS; p++)
-        {
-            if (!p->id) goto end;
-            
-            //Otherwise record the oldest and keep going
-            uint32_t age = MsTimerCount - p->lastUsed;
-            if (age >= ageOldest)
-            {
-                ageOldest = age;
-                  pOldest = p;
-            }
-        }
-    
-        //No empty ones found so use the oldest
-        p = pOldest;
-    }
-    
-end:
-    zeroConnection(p);
-    p->id           = connectionId;
-    p->lastUsed     = MsTimerCount;
-    return p;
-}
-struct WebConnection* WebConnectionOrNull(int connectionId)
-{
-    for (struct WebConnection* p = connections; p < connections + MAX_CONNECTIONS; p++)
-    {
-        if (p->id == connectionId)
-        {
-            p->lastUsed = MsTimerCount;
-            return p;
-        }
-    }
-    return NULL;
-}
-void WebConnectionReset(int connectionId)
-{
-    for (struct WebConnection* p = connections; p < connections + MAX_CONNECTIONS; p++)
-    {
-        if (p->id == connectionId) zeroConnection(p);
-    }
-}
diff -r 6d9bffc72676 -r 9a5b8fe308f1 web-connection.h
--- a/web-connection.h	Sun Sep 01 18:12:48 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-#include <stdint.h>
-#include <stdbool.h>
-
-struct WebConnection
-{
-    int      id; //An id of zero means the record is empty
-    uint32_t lastUsed;
-    int      toDo;
-    bool     postComplete;
-    uint32_t delayUntil;
-};
-
-extern struct WebConnection* WebConnectionNew   (int connectionId); //Never fails so never returns NULL
-extern struct WebConnection* WebConnectionOrNull(int connectionId);
-extern void                  WebConnectionReset (int connectionId);
\ No newline at end of file
diff -r 6d9bffc72676 -r 9a5b8fe308f1 web.c
--- a/web.c	Sun Sep 01 18:12:48 2019 +0000
+++ b/web.c	Tue Sep 24 18:16:47 2019 +0000
@@ -3,15 +3,17 @@
 #include "web-server-this.h"
 #include "web.h"
 #include "web-pages-base.h"
-#include "web-connection.h"
+#include "http-connection.h"
+#include "log.h"
 #include "mstimer.h"
-#include "log.h"
 
 #define LOGIN_DELAY_MS 200
 
 #define DO_LOGIN DO_SERVER + 0
 
-static int decideWhatToDo(char *pPath, char* pLastModified)
+bool WebTrace = false;
+
+int WebDecideWhatToDo(char *pPath, char* pLastModified)
 {
     if (HttpSameStr(pPath, "/login")) return DO_LOGIN;
     
@@ -20,124 +22,49 @@
     todo = WebServerThisDecideWhatToDo(pPath, pLastModified); if (todo != DO_NOT_FOUND) return todo;
     return DO_NOT_FOUND;
 }
-static void handleQuery(int todo, char* pQuery)
+int WebHandleQuery(char* pQuery, char* pCookies, int* pTodo, uint32_t* pDelayUntil) //return -1 on stop; 0 on continue
 {
-    switch (todo)
+    //If what to do is NOTHING, NOT_FOUND or NOT_MODIFIED then no query or post will be valid so stop now
+    if (*pTodo < DO_LOGIN) return -1;
+    
+    //If what to do is LOGIN then the user has just returned the login form
+    if (*pTodo == DO_LOGIN)
     {
-        case DO_LOGIN:         WebLoginQuery   (pQuery); return;
+        WebLoginQuery(pQuery);                    //Read the password and the original location
+        if (WebLoginQueryPasswordOk)
+        {
+            if (!WebLoginSessionIdIsSet())           //If there isn't a session id already
+            {
+                WebLoginSessionIdNew();              //Create a new session id
+            }
+            *pTodo =  WebLoginOriginalToDo;          //Load the original todo and SEND_SESSION_ID
+            *pTodo += DO_SEND_SESSION_ID;
+        }
+        *pDelayUntil = MsTimerCount + LOGIN_DELAY_MS; //To prevent brute forcing the hash delay the reply to the login
+        return -1;                                    //Either way no query or post will be valid
     }
-    if (WebServerBaseHandleQuery(todo, pQuery)) return;
-    if (WebServerThisHandleQuery(todo, pQuery)) return;
+    
+    //Have a normal request so authenticate
+    if (!WebLoginCookiesContainValidSessionId(pCookies))
+    {
+        WebLoginOriginalToDo = *pTodo; //Record the original destination for redirection
+        *pTodo = DO_LOGIN;
+        return -1; //Ignore any query or post as the user is not authenticated
+    }
+
+    if (WebServerBaseHandleQuery(*pTodo, pQuery)) return 0;
+    if (WebServerThisHandleQuery(*pTodo, pQuery)) return 0;
+    return 0;
 }
-static void handlePost(int todo, int contentLength, int contentStart, int size, char* pRequestStream, uint32_t positionInRequestStream, bool* pComplete)
+void WebHandlePost(int todo, int contentLength, int contentStart, int size, char* pRequestStream, uint32_t positionInRequestStream, bool* pComplete)
 {
     if (WebServerBasePost(todo, contentLength, contentStart, size, pRequestStream, positionInRequestStream, pComplete)) return;
     if (WebServerThisPost(todo, contentLength, contentStart, size, pRequestStream, positionInRequestStream, pComplete)) return;
     *pComplete = true;
 }
 
-static void reply(int todo)
-{
-    //Try all the base modules
-    switch (todo)
-    {
-        case DO_LOGIN:           WebLoginHtml     (); return;
-        case DO_NOT_FOUND:       HttpNotFound     (); return;
-        case DO_NOT_MODIFIED:    HttpNotModified  (); return;
-    }
-    
-    //If not called then call the derived (child) module
-    if (WebServerBaseReply(todo)) return;
-    if (WebServerThisReply(todo)) return;
-}
-static void handleRequest(int connectionId, int size, char* pRequestStream, uint32_t positionInRequestStream)
+void WebAddResponse(int todo)
 {
-    struct WebConnection* pConnection;
-    if (!positionInRequestStream)
-    {
-        pConnection = WebConnectionNew(connectionId);
-    }
-    else
-    {
-        pConnection = WebConnectionOrNull(connectionId);
-        if (!pConnection)
-        {
-            LogTimeF("WebRequest - no connection corresponds to id %d\r\n", connectionId);
-            return;
-        }
-    }
-    
-    pConnection->delayUntil = MsTimerCount; //Default to no delay unless modified;
-    
-    //Handle request for the first packet of data received but leave todo the same after that.
-    int contentLength = 0;
-    int contentStart  = 0;
-    if (size && positionInRequestStream == 0)
-    {
-        //Read the headers
-        char* pMethod;
-        char* pPath;
-        char* pQuery;
-        char* pLastModified;
-        char* pCookies;
-        contentStart = HttpRequestRead(pRequestStream, size, &pMethod, &pPath, &pQuery, &pLastModified, &pCookies, &contentLength);
-        
-        //Ask the web server what to do
-        pConnection->toDo = decideWhatToDo(pPath, pLastModified);
-        
-        //If what to do is NOTHING, NOT_FOUND or NOT_MODIFIED then no query or post will be valid so stop now
-        if (pConnection->toDo < DO_LOGIN) { pConnection->postComplete = true; return; }
-        
-        //If what to do is LOGIN then the user has just returned the login form
-        if (pConnection->toDo == DO_LOGIN)
-        {
-            handleQuery(pConnection->toDo, pQuery);                  //Read the password and the original location
-            if (WebLoginQueryPasswordOk)
-            {
-                if (!WebLoginSessionIdIsSet())           //If there isn't a session id already
-                {
-                    WebLoginSessionIdNew();              //Create a new session id
-                }
-                pConnection->toDo =  WebLoginOriginalToDo;          //Load the original todo and SEND_SESSION_ID
-                pConnection->toDo += DO_SEND_SESSION_ID;
-            }
-            pConnection->delayUntil = MsTimerCount + LOGIN_DELAY_MS; //To prevent brute forcing the hash delay the reply to the login
-            pConnection->postComplete = true;
-            return;                                       //Either way no query or post will be valid
-        }
-        
-        //Have a normal request so authenticate
-        if (!WebLoginCookiesContainValidSessionId(pCookies))
-        {
-            WebLoginOriginalToDo = pConnection->toDo; //Record the original destination for redirection
-            pConnection->toDo = DO_LOGIN;
-            pConnection->postComplete = true;
-            return; //Ignore any query or post as the user is not authenticated
-        }
-        
-        //Handle the query
-        handleQuery(pConnection->toDo, pQuery);
-    }
-    
-    //Do the upload of anything that needs it. Todos it doesn't understand are ignored.
-    if (!pConnection->postComplete) handlePost(pConnection->toDo, contentLength, contentStart, size, pRequestStream, positionInRequestStream, &pConnection->postComplete);
-}
-
-static bool pollSomethingToSend(int connectionId, bool clientFinished) //returns true if finished; false if not; 
-{
-    struct WebConnection* pConnection = WebConnectionOrNull(connectionId);
-    if (!pConnection) return false; //Protects the gap between the connection being established and the request being started
-    
-    if (!pConnection->toDo)
-    {
-        if (clientFinished) return true;  //The client hasn't requested anything and never will so finish
-        else                return false; //The client hasn't requested anything yet but still could
-    }
-    if (!pConnection->postComplete               ) return false; //Wait for the request (usually a POST) to finish
-    if (!MsTimerAbsolute(pConnection->delayUntil)) return false; //Wait a while (usually after a LOGIN attempt)
-    
-    int todo = pConnection->toDo; //Make a copy so that we don't modify todo in the state
-
     //Check if todo includes the need to send a cookie
     if (todo >= DO_SEND_SESSION_ID)
     {
@@ -153,18 +80,20 @@
         HttpOkCookieMaxAge = -1;
     }
     
-    reply(todo);
+    //Try all the base modules
+    switch (todo)
+    {
+        case DO_LOGIN:           WebLoginHtml     (); return;
+        case DO_NOT_FOUND:       HttpNotFound     (); return;
+        case DO_NOT_MODIFIED:    HttpNotModified  (); return;
+    }
     
-    return !HttpBufFilled(); //If we haven't used a full buffer then we have finished
+    //If not called then call the derived (child) module
+    if (WebServerBaseReply(todo)) return;
+    if (WebServerThisReply(todo)) return;
 }
 
-int WebInit()
-{
-    HttpFunctionReset   = WebConnectionReset;
-    HttpFunctionRequest = handleRequest;
-    HttpFunctionPoll    = pollSomethingToSend;
-    
-    WebLoginInit();
-    
-    return 0;
+void WebInit()
+{   
+    WebLoginInit();  
 }
\ No newline at end of file
diff -r 6d9bffc72676 -r 9a5b8fe308f1 web.h
--- a/web.h	Sun Sep 01 18:12:48 2019 +0000
+++ b/web.h	Tue Sep 24 18:16:47 2019 +0000
@@ -7,4 +7,11 @@
 #define DO_THIS            200
 #define DO_SEND_SESSION_ID 300
 
-extern int  WebInit(void);
\ No newline at end of file
+extern bool WebTrace;
+
+extern void WebInit    (void);
+
+extern void WebAddResponse   (int todo);
+extern  int WebHandleQuery   (char* pQuery, char* pCookies, int* pTodo, uint32_t* pDelayUntil); //return -1 on stop; 0 on continue
+extern void WebHandlePost    (int todo, int contentLength, int contentStart, int size, char* pRequestStream, uint32_t positionInRequestStream, bool* pComplete);
+extern int  WebDecideWhatToDo(char *pPath, char* pLastModified);