From 935271510d78bd5f227c421c13832c64218fc300 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= <ondra@ondrovo.com>
Date: Sun, 29 Jan 2023 16:23:42 +0100
Subject: [PATCH] cleaning, add header queuing

---
 demo/server_demo.c                |  16 ++---
 spritehttpd/include/httpd-types.h |   2 +-
 spritehttpd/include/httpd-utils.h |  30 ++++++--
 spritehttpd/include/httpd.h       |  10 +++
 spritehttpd/src/cgi-espfs.c       |  83 +++++++++++-----------
 spritehttpd/src/httpd-utils.c     |  31 +++++++--
 spritehttpd/src/httpd.c           | 110 ++++++++++++++++++++++--------
 7 files changed, 191 insertions(+), 91 deletions(-)

diff --git a/demo/server_demo.c b/demo/server_demo.c
index 2a69b7e..236e1c0 100644
--- a/demo/server_demo.c
+++ b/demo/server_demo.c
@@ -80,13 +80,13 @@ httpd_cgi_state templateReplacer(HttpdConnData *conn, const char *token)
 //    httpdGetHeader(conn, "Cookie", )
 //}
 
-//
-//httpd_cgi_state cgiStartSession(HttpdConnData *conn)
-//{
-//
-//
-//    return HTTPD_CGI_NOTFOUND;
-//}
+httpd_cgi_state cgiStartSession(HttpdConnData *conn)
+{
+    httpdQueueHeader(conn, "X-Foo", "FOO");
+    httpdQueueHeader(conn, "X-Bar", "Bar");
+
+    return HTTPD_CGI_NOTFOUND;
+}
 
 
 
@@ -96,7 +96,7 @@ httpd_cgi_state templateReplacer(HttpdConnData *conn, const char *token)
  */
 const HttpdBuiltInUrl routes[] = {
         // TODO password lock ...
-//        ROUTE_CGI("*", cgiStartSession),
+        ROUTE_CGI("*", cgiStartSession),
 
         // --- Web pages ---
 //        ROUTE_TPL_FILE("/", tplIndex, "/index.tpl"),
diff --git a/spritehttpd/include/httpd-types.h b/spritehttpd/include/httpd-types.h
index 1a411db..f3366f2 100644
--- a/spritehttpd/include/httpd-types.h
+++ b/spritehttpd/include/httpd-types.h
@@ -30,7 +30,7 @@ typedef enum {
   HTTPD_CGI_NOTFOUND = 2,
   /// This is, in effect, identical to NOTFOUND, it's returned by auth functions when a fall-through is allowed.
   /// The next route in the route list will be attempted.
-  HTTPD_CGI_AUTHENTICATED = 3,
+  HTTPD_CGI_AUTHENTICATED = 3, // TODO rename to PASS?
 } httpd_cgi_state;
 
 /**
diff --git a/spritehttpd/include/httpd-utils.h b/spritehttpd/include/httpd-utils.h
index 35129fd..c6e89ca 100644
--- a/spritehttpd/include/httpd-utils.h
+++ b/spritehttpd/include/httpd-utils.h
@@ -9,18 +9,40 @@
 #include "httpd-types.h"
 
 // Custom helpers
+
+/// Test string equality
 #define streq(a, b) (strcmp((const char*)(a), (const char*)(b)) == 0)
+/// Test string equality, case-insensitive
+#define strcaseeq(a, b) (strcasecmp((const char*)(a), (const char*)(b)) == 0)
+/// Test string equality up to N chars
 #define strneq(a, b, n) (strncmp((const char*)(a), (const char*)(b), (n)) == 0)
+/// Test if string A starts with string B
 #define strstarts(a, b) strneq((a), (b), strlen((b)))
+/// Get nth char from the end of string (1 = last)
 #define last_char_n(str, n) ((str))[strlen((str)) - (n)]
+/// Get last char of string
 #define last_char(str) last_char_n((str), 1)
 
+/// The container_of macro from the linux kernel
 #ifndef container_of
 #define container_of(ptr, type, member) ({                      \
         const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
         (type *)( (char *)__mptr - offsetof(type,member) );})
 #endif
 
+/// Get length of a static or stack array
+#define array_len(a) (sizeof((a)) / sizeof((a)[0]))
+
+/**
+ * Strstr up to N chars into the searched string
+ *
+ * @param str - haystack
+ * @param substr - needle
+ * @param n - haystack len to search
+ * @return pointer to the matching substring or NULL
+ */
+const char *strnstr(const char *str, const char *substr, size_t n);
+
 /**
  * Turn a nibble (0-15) to a hex char.
  *
@@ -83,12 +105,10 @@ const char *httpdGetMimetype(const char *url);
  * @param fallback - fallback mime if none was resolved
  * @return mime string
  */
-static inline const char *httpdGetMimetypeOr(const char *url, const char *fallback) {
+static inline const char *httpdGetMimetypeOr(const char *url, const char *fallback)
+{
     const char *mime = httpdGetMimetype(url);
-    if (!mime) {
-        mime = fallback;
-    }
-    return mime;
+    return mime ? mime : fallback;
 }
 
 /**
diff --git a/spritehttpd/include/httpd.h b/spritehttpd/include/httpd.h
index 60a397f..d6b53cf 100644
--- a/spritehttpd/include/httpd.h
+++ b/spritehttpd/include/httpd.h
@@ -149,6 +149,16 @@ void httpdEndHeaders(HttpdConnData *conn);
  */
 int httpdGetHeader(HttpdConnData *conn, const char *header, char *buff, size_t buffLen);
 
+/**
+ * Queue a header to be sent with the response.
+ *
+ * @param conn
+ * @param header - name
+ * @param value - value
+ * @return 1 = OK
+ */
+void httpdQueueHeader(HttpdConnData *conn, const char *header, const char *value);
+
 /**
  * Send binary data
  *
diff --git a/spritehttpd/src/cgi-espfs.c b/spritehttpd/src/cgi-espfs.c
index b6990a7..6686ebf 100644
--- a/spritehttpd/src/cgi-espfs.c
+++ b/spritehttpd/src/cgi-espfs.c
@@ -21,7 +21,12 @@ Connector to let httpd use the espfs filesystem to serve the files in it.
 #include "espfs.h"
 #include "espfsformat.h"
 
-#define FILE_CHUNK_LEN 512
+/// EspFs CGI filename len buffer size
+#define ESPFS_FILENAME_LEN 100
+/// EspFs CGI file chunk buffer size
+#define ESPFS_FILE_CHUNK_LEN 512
+/// EspFs CGI header buffer (used to detect gzip)
+#define ESPFS_HEADER_LEN 64
 
 // The static files marked with FLAG_GZIP are compressed and will be served with GZIP compression.
 // If the client does not advertise that he accepts GZIP send following warning message (telnet users for e.g.)
@@ -32,29 +37,6 @@ static const char *gzipNonSupportedMessage = "HTTP/1.0 501 Not implemented\r\n"
                                              "\r\n"
                                              "Your browser does not accept gzip-compressed data.\r\n";
 
-/**
- * Try to open a file
- * @param path - path to the file, may end with slash
- * @param indexname - filename at the path
- * @return file pointer or NULL
- */
-static EspFsFile *tryOpenIndex_do(const char *path, const char *indexname)
-{
-    char fname[100];
-    size_t url_len = strlen(path);
-    strncpy(fname, path, 99);
-
-    // Append slash if missing
-    if (path[url_len - 1] != '/') {
-        fname[url_len++] = '/';
-    }
-
-    strcpy(fname + url_len, indexname);
-
-    // Try to open, returns NULL if failed
-    return espFsOpen(fname);
-}
-
 /**
  * Try to find index file on a path
  * @param path - directory
@@ -67,17 +49,29 @@ EspFsFile *tryOpenIndex(const char *path)
     // no point in trying to look for index.
     if (strchr(path, '.') != NULL) { return NULL; }
 
-    file = tryOpenIndex_do(path, "index.html");
-    if (file != NULL) { return file; }
+    char fname[ESPFS_FILENAME_LEN];
+    size_t url_len = strlen(path);
+    if (url_len >= ESPFS_FILENAME_LEN) {
+        // too long to append anything
+        return NULL;
+    }
+    strncpy(fname, path, ESPFS_FILENAME_LEN - 1);
 
-    file = tryOpenIndex_do(path, "index.htm");
-    if (file != NULL) { return file; }
+    // Append slash if missing
+    if (path[url_len - 1] != '/') {
+        fname[url_len++] = '/';
+    }
 
-    file = tryOpenIndex_do(path, "index.tpl.html");
-    if (file != NULL) { return file; }
+    const char *index_names[4] = {
+            "index.html", "index.htm", "index.tpl.html", "index.tpl"
+    };
 
-    file = tryOpenIndex_do(path, "index.tpl");
-    if (file != NULL) { return file; }
+    for (size_t i = 0; i < array_len(index_names); i++) {
+        strcpy(fname + url_len, index_names[i]);
+        // Try to open, returns NULL if failed
+        file = espFsOpen(fname);
+        if (file != NULL) { return file; }
+    }
 
     return NULL; // failed to guess the right name
 }
@@ -86,8 +80,8 @@ static httpd_cgi_state serveStaticFile(HttpdConnData *hconn, const char *filepat
 {
     EspFsFile *file = hconn->cgiData;
     size_t len;
-    uint8_t buff[FILE_CHUNK_LEN + 1];
-    char acceptEncodingBuffer[64 + 1];
+    uint8_t buff[ESPFS_FILE_CHUNK_LEN + 1];
+    char acceptEncodingBuffer[ESPFS_HEADER_LEN + 1];
 
     if (hconn->conn == NULL) {
         //Connection aborted. Clean up.
@@ -123,7 +117,7 @@ static httpd_cgi_state serveStaticFile(HttpdConnData *hconn, const char *filepat
         if (isGzip) {
             // Check the browser's "Accept-Encoding" header. If the client does not
             // advertise that he accepts GZIP send a warning message (telnet users for e.g.)
-            httpdGetHeader(hconn, "Accept-Encoding", acceptEncodingBuffer, 64);
+            httpdGetHeader(hconn, "Accept-Encoding", acceptEncodingBuffer, ESPFS_HEADER_LEN);
             if (strstr(acceptEncodingBuffer, "gzip") == NULL) {
                 //No Accept-Encoding: gzip header present
                 httpdSendStr(hconn, gzipNonSupportedMessage);
@@ -144,12 +138,12 @@ static httpd_cgi_state serveStaticFile(HttpdConnData *hconn, const char *filepat
         return HTTPD_CGI_MORE;
     }
 
-    len = espFsRead(file, buff, FILE_CHUNK_LEN);
+    len = espFsRead(file, buff, ESPFS_FILE_CHUNK_LEN);
     if (len > 0) {
         espfs_dbg("[EspFS] Read file chunk: %d bytes", len);
         httpdSend(hconn, buff, len);
     }
-    if (len != FILE_CHUNK_LEN) {
+    if (len != ESPFS_FILE_CHUNK_LEN) {
         //We're done.
         espFsClose(file);
         return HTTPD_CGI_DONE;
@@ -182,7 +176,7 @@ typedef struct {
   EspFsFile *file;
 
   ssize_t tokenPos;
-  char buff[FILE_CHUNK_LEN + 1];
+  char buff[ESPFS_FILE_CHUNK_LEN + 1];
   char token[HTTPD_ESPFS_TOKEN_LEN];
   char *pToken;
 
@@ -301,7 +295,7 @@ httpd_cgi_state cgiEspFsTemplate(HttpdConnData *conn)
         sp = tdi->buff_sp;
         x = tdi->buff_x;
     } else {
-        len = espFsRead(tdi->file, (uint8_t *) buff, FILE_CHUNK_LEN);
+        len = espFsRead(tdi->file, (uint8_t *) buff, ESPFS_FILE_CHUNK_LEN);
         tdi->buff_len = len;
 
         e = buff;
@@ -337,19 +331,20 @@ httpd_cgi_state cgiEspFsTemplate(HttpdConnData *conn)
 
                             int prefixLen = 0;
                             tdi->tokEncode = ENCODE_PLAIN;
-                            if (strneq(tdi->token, "html:", 5)) {
+                            if (strstarts(tdi->token, "html:")) {
                                 prefixLen = 5;
                                 tdi->tokEncode = ENCODE_HTML;
-                            } else if (strneq(tdi->token, "h:", 2)) {
+                            } else if (strstarts(tdi->token, "h:")) {
                                 prefixLen = 2;
                                 tdi->tokEncode = ENCODE_HTML;
-                            } else if (strneq(tdi->token, "js:", 3)) {
+                            } else if (strstarts(tdi->token, "js:")) {
                                 prefixLen = 3;
                                 tdi->tokEncode = ENCODE_JS;
-                            } else if (strneq(tdi->token, "j:", 2)) {
+                            } else if (strstarts(tdi->token, "j:")) {
                                 prefixLen = 2;
                                 tdi->tokEncode = ENCODE_JS;
                             }
+                            // TODO implement "include" tokens?
 
                             tdi->pToken = &tdi->token[prefixLen];
                         }
@@ -410,7 +405,7 @@ httpd_cgi_state cgiEspFsTemplate(HttpdConnData *conn)
         httpdSendStrN(conn, e, (size_t) sp);
     }
 
-    if (len != FILE_CHUNK_LEN) {
+    if (len != ESPFS_FILE_CHUNK_LEN) {
         //We're done.
 
         TplCallback callback = (TplCallback) conn->cgiArg;
diff --git a/spritehttpd/src/httpd-utils.c b/spritehttpd/src/httpd-utils.c
index 3aa4749..376d2ec 100644
--- a/spritehttpd/src/httpd-utils.c
+++ b/spritehttpd/src/httpd-utils.c
@@ -1,6 +1,23 @@
 #include "httpd-utils.h"
 #include "httpd-logging.h"
 
+const char *strnstr(const char *str, const char *substr, size_t n)
+{
+    if (!str || !substr) {
+        return NULL;
+    }
+    size_t substr_len = strlen(substr);
+    if (0 == substr_len || substr_len > n) { return NULL; }
+
+    const char *pEnd = str + n + 1 - substr_len;
+    for (const char *p = str; p < pEnd; p++) {
+        if (0 == strncmp(p, substr, substr_len)) {
+            return p;
+        }
+    }
+    return NULL;
+}
+
 char httpdHexNibble(uint8_t val)
 {
     val &= 0xf;
@@ -25,7 +42,7 @@ size_t httpdUrlDecode(const char *val, size_t valLen, char *buff, size_t buffLen
     uint8_t escVal = 0;
     while (s < valLen && d < buffLen) {
         if (esced == 1) {
-            escVal = httpdHexVal(val[s]) << 4;
+            escVal = (uint8_t) (httpdHexVal(val[s]) << 4);
             esced = 2;
         } else if (esced == 2) {
             escVal |= httpdHexVal(val[s]);
@@ -58,7 +75,7 @@ int httpdFindArg(const char *line, const char *arg, char *buff, size_t buffLen)
             e = strstr(p, "&");
             if (e == NULL) { e = p + strlen(p); }
             router_dbg("findArg: val %s len %d", p, (int) (e - p));
-            return (int) httpdUrlDecode(p, (size_t)(e - p), buff, buffLen);
+            return (int) httpdUrlDecode(p, (size_t) (e - p), buff, buffLen);
         }
         p = strstr(p, "&");
         if (p != NULL) { p += 1; }
@@ -95,21 +112,23 @@ static const MimeMap MIME_TYPES[] = {
         {"svg",  "image/svg+xml"},
         {"xml",  "text/xml"},
         {"json", "application/json"},
-        {NULL,   NULL}, //default value
 };
 
 
 const char *httpdGetMimetype(const char *url)
 {
-    int i = 0;
     //Go find the extension
     const char *ext = url + (strlen(url) - 1);
     while (ext != url && *ext != '.') { ext--; }
     if (*ext == '.') { ext++; }
 
-    while (MIME_TYPES[i].ext != NULL && strcasecmp(ext, MIME_TYPES[i].ext) != 0) { i++; }
+    for (size_t i = 0; i < array_len(MIME_TYPES); i++) {
+        if (strcaseeq(ext, MIME_TYPES[i].ext)) {
+            return MIME_TYPES[i].mimetype;
+        }
+    }
 
-    return MIME_TYPES[i].mimetype;
+    return NULL;
 }
 
 
diff --git a/spritehttpd/src/httpd.c b/spritehttpd/src/httpd.c
index f8f199c..5a796a6 100644
--- a/spritehttpd/src/httpd.c
+++ b/spritehttpd/src/httpd.c
@@ -19,6 +19,7 @@ Esp8266 http server - core routines
 #include "httpd-logging.h"
 
 static void cleanupCgiAndUserData(HttpdConnData *hconn);
+
 static void httpdRetireConn(HttpdConnData *hconn);
 
 _Static_assert(HTTPD_MAX_CONNECTIONS < 256, "HTTPD_MAX_CONNECTIONS must be at most 255");
@@ -27,6 +28,14 @@ _Static_assert(HTTPD_MAX_CONNECTIONS < 256, "HTTPD_MAX_CONNECTIONS must be at mo
 static const HttpdBuiltInUrl *s_builtInUrls;
 static const char *s_serverName = HTTPD_SERVERNAME;
 
+struct HttpdQueuedHeader;
+typedef struct HttpdQueuedHeader {
+  /// Pointer to the next queued header
+  struct HttpdQueuedHeader *next;
+  /// Text of the header, including CRLF
+  char headerLine[];
+} HttpdQueuedHeader;
+
 typedef struct HttpSendBacklogItem HttpSendBacklogItem;
 
 struct HttpSendBacklogItem {
@@ -52,6 +61,7 @@ struct HttpdPriv {
   size_t sendBuffLen;
   char *chunkHdr;
   HttpSendBacklogItem *sendBacklog;
+  HttpdQueuedHeader *headersToSend; // Linked list of headers to send with the response. Will be freed with the request.
   size_t sendBacklogSize;
   uint8_t flags;
 };
@@ -60,7 +70,7 @@ struct HttpdPriv {
 //Connection pool
 HttpdConnData *s_connData[HTTPD_MAX_CONNECTIONS];
 
-void httpdInternalCloseAllSockets()
+void httpdInternalCloseAllSockets(void)
 {
     httpdPlatLock();
     /*release data connection*/
@@ -89,8 +99,7 @@ void httpdAddCacheHeaders(HttpdConnData *connData, const char *mime)
     if (streq(mime, "text/html")
         || streq(mime, "text/plain")
         || streq(mime, "text/csv")
-        || streq(mime, "application/json")
-    ) {
+        || streq(mime, "application/json")) {
         return;
     }
 
@@ -191,6 +200,37 @@ int httpdGetHeader(HttpdConnData *conn, const char *header, char *buff, size_t b
     return 0;
 }
 
+void httpdQueueHeader(HttpdConnData *conn, const char *header, const char *value)
+{
+    if (!conn || !header || !value) {
+        return;
+    }
+    if (conn->priv->flags & HFL_SENDINGBODY) {
+        http_error("Headers already sent.");
+        return;
+    }
+
+    HttpdQueuedHeader *queEntry = httpdPlatMalloc(sizeof(void *) + strlen(header) + strlen(value) + 5);
+    if (!queEntry) {
+        http_error("httpdQueueHeader - no mem");
+        return;
+    }
+
+    queEntry->next = NULL;
+    queEntry->headerLine[0] = 0;
+    strcat(queEntry->headerLine, header);
+    strcat(queEntry->headerLine, ": ");
+    strcat(queEntry->headerLine, value);
+    strcat(queEntry->headerLine, "\r\n");
+
+    // Attach it to the linked list
+    HttpdQueuedHeader **ph = &conn->priv->headersToSend;
+    while (*ph) {
+        ph = &(*ph)->next;
+    }
+    *ph = queEntry;
+}
+
 void httdSetTransferMode(HttpdConnData *conn, httpd_transfer_opt mode)
 {
     if (mode == HTTPD_TRANSFER_CLOSE) {
@@ -224,12 +264,12 @@ void httpdStartResponse(HttpdConnData *conn, int code)
         }
     }
 
-    size_t l = (size_t)sprintf(buff, "HTTP/1.%d %d %s\r\nServer: %s\r\n%s",
-                ((conn->priv->flags & HFL_HTTP11) ? 1 : 0),
-                code,
-                httpdStatusName(code),
-                s_serverName,
-                connStr);
+    size_t l = (size_t) sprintf(buff, "HTTP/1.%d %d %s\r\nServer: %s\r\n%s",
+                                ((conn->priv->flags & HFL_HTTP11) ? 1 : 0),
+                                code,
+                                httpdStatusName(code),
+                                s_serverName,
+                                connStr);
 
     httpdSendStrN(conn, buff, l);
 
@@ -252,6 +292,15 @@ void httpdHeader(HttpdConnData *conn, const char *field, const char *val)
 //Finish the headers.
 void httpdEndHeaders(HttpdConnData *conn)
 {
+    // Add queued headers
+    HttpdQueuedHeader *qh = conn->priv->headersToSend;
+    while (qh) {
+        httpdSendStr(conn, qh->headerLine);
+        HttpdQueuedHeader *next = qh->next;
+        httpdPlatFree(qh);
+        qh = next;
+    }
+
     httpdSendStr(conn, "\r\n");
     conn->priv->flags |= HFL_SENDINGBODY;
 }
@@ -309,17 +358,21 @@ int httpdSend_html(HttpdConnData *conn, const char *data, ssize_t len)
         }
 
         if (c == '"' || c == '\'' || c == '<' || c == '>') {
-            if (start < end) httpdSend_orDie(conn, data + start, end - start);
+            if (start < end) {
+                httpdSend_orDie(conn, data + start, end - start);
+            }
             start = end + 1;
         }
 
-        if (c == '"') httpdSendStr_orDie(conn, "&#34;");
-        else if (c == '\'') httpdSendStr_orDie(conn, "&#39;");
-        else if (c == '<') httpdSendStr_orDie(conn, "&lt;");
-        else if (c == '>') httpdSendStr_orDie(conn, "&gt;");
+        if (c == '"') { httpdSendStr_orDie(conn, "&#34;"); }
+        else if (c == '\'') { httpdSendStr_orDie(conn, "&#39;"); }
+        else if (c == '<') { httpdSendStr_orDie(conn, "&lt;"); }
+        else if (c == '>') { httpdSendStr_orDie(conn, "&gt;"); }
     }
 
-    if (start < end) httpdSend_orDie(conn, data + start, end - start);
+    if (start < end) {
+        httpdSend_orDie(conn, data + start, end - start);
+    }
     return 1;
 }
 
@@ -340,20 +393,24 @@ int httpdSend_js(HttpdConnData *conn, const char *data, ssize_t len)
         }
 
         if (c == '"' || c == '\\' || c == '\'' || c == '<' || c == '>' || c == '\n' || c == '\r') {
-            if (start < end) httpdSend_orDie(conn, data + start, end - start);
+            if (start < end) {
+                httpdSend_orDie(conn, data + start, end - start);
+            }
             start = end + 1;
         }
 
-        if (c == '"') httpdSendStr_orDie(conn, "\\\"");
-        else if (c == '\'') httpdSendStr_orDie(conn, "\\'");
-        else if (c == '\\') httpdSendStr_orDie(conn, "\\\\");
-        else if (c == '<') httpdSendStr_orDie(conn, "\\u003C");
-        else if (c == '>') httpdSendStr_orDie(conn, "\\u003E");
-        else if (c == '\n') httpdSendStr_orDie(conn, "\\n");
-        else if (c == '\r') httpdSendStr_orDie(conn, "\\r");
+        if (c == '"') { httpdSendStr_orDie(conn, "\\\""); }
+        else if (c == '\'') { httpdSendStr_orDie(conn, "\\'"); }
+        else if (c == '\\') { httpdSendStr_orDie(conn, "\\\\"); }
+        else if (c == '<') { httpdSendStr_orDie(conn, "\\u003C"); }
+        else if (c == '>') { httpdSendStr_orDie(conn, "\\u003E"); }
+        else if (c == '\n') { httpdSendStr_orDie(conn, "\\n"); }
+        else if (c == '\r') { httpdSendStr_orDie(conn, "\\r"); }
     }
 
-    if (start < end) httpdSend_orDie(conn, data + start, end - start);
+    if (start < end) {
+        httpdSend_orDie(conn, data + start, end - start);
+    }
     return 1;
 }
 
@@ -371,7 +428,7 @@ bool httpdFlushSendBuffer(HttpdConnData *conn)
         //Finish chunk with cr/lf
         httpdSendStr(conn, "\r\n");
         //Calculate length of chunk
-        len = (size_t) ((char *)(&conn->priv->sendBuff[conn->priv->sendBuffLen]) - conn->priv->chunkHdr) - 8;
+        len = (size_t) ((char *) (&conn->priv->sendBuff[conn->priv->sendBuffLen]) - conn->priv->chunkHdr) - 8;
         //Fix up chunk header to correct value
         conn->priv->chunkHdr[0] = httpdHexNibble((uint8_t) (len >> 12));
         conn->priv->chunkHdr[1] = httpdHexNibble((uint8_t) (len >> 8));
@@ -507,7 +564,6 @@ void httpdContinue(HttpdConnData *conn)
 //find the next cgi function, wait till the cgi data is sent or close up the connection.
 static void httpdProcessRequest(HttpdConnData *conn)
 {
-    int r;
     int i = 0;
     if (conn->url == NULL) {
         router_warn("WtF? url = NULL");
@@ -564,7 +620,7 @@ static void httpdProcessRequest(HttpdConnData *conn)
 
         //Okay, we have a CGI function that matches the URL. See if it wants to handle the
         //particular URL we're supposed to handle.
-        r = conn->cgi(conn);
+        httpd_cgi_state r = conn->cgi(conn);
         if (r == HTTPD_CGI_MORE) {
             //Yep, it's happy to do so and has more data to send.
             if (conn->recvHdl) {