From 39a4e0ef9113cf2ac0d3efd5cb780220df892af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Fri, 18 Mar 2016 15:06:53 +0100 Subject: [PATCH] improvements in template engine and routes --- Makefile | 4 + esp_iot_sdk_v1.5.2/include/spi_flash.h | 14 ++- esp_meas.pro | 3 +- esp_meas.pro.user | 2 +- esphttpdconfig.mk | 4 +- html/chibi.js | 2 + htmlpreview.sh | 12 -- htmlserver.sh | 4 - libesphttpd/Makefile | 12 +- libesphttpd/core/httpd.c | 11 +- libesphttpd/core/httpdespfs.c | 87 ++++++++++---- libesphttpd/espfs/espfs.c | 158 +++++++++++++------------ libesphttpd/espfs/espfsformat.h | 6 +- libesphttpd/html-minifier-conf.json | 25 ++++ libesphttpd/include/espfs.h | 6 +- libesphttpd/include/httpd.h | 40 ++++++- libesphttpd/include/httpdespfs.h | 8 +- user/user_main.c | 35 +++--- 18 files changed, 281 insertions(+), 152 deletions(-) create mode 100644 html/chibi.js delete mode 100755 htmlpreview.sh delete mode 100755 htmlserver.sh create mode 100644 libesphttpd/html-minifier-conf.json diff --git a/Makefile b/Makefile index 02ff857..7b1fe86 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,10 @@ ESP_FLASH_MODE=0 ESP_FLASH_FREQ_DIV=0 +GZIP_COMPRESSION=yes +USE_HEATSHRINK=yes + + ifeq ("$(OUTPUT_TYPE)","separate") #In case of separate ESPFS and binaries, set the pos and length of the ESPFS here. ESPFS_POS = 0x18000 diff --git a/esp_iot_sdk_v1.5.2/include/spi_flash.h b/esp_iot_sdk_v1.5.2/include/spi_flash.h index d5a16a1..9f88a43 100644 --- a/esp_iot_sdk_v1.5.2/include/spi_flash.h +++ b/esp_iot_sdk_v1.5.2/include/spi_flash.h @@ -1,15 +1,17 @@ -/* +/* * copyright (c) Espressif System 2010 - * + * */ #ifndef SPI_FLASH_H #define SPI_FLASH_H +#include + typedef enum { - SPI_FLASH_RESULT_OK, - SPI_FLASH_RESULT_ERR, - SPI_FLASH_RESULT_TIMEOUT + SPI_FLASH_RESULT_OK, + SPI_FLASH_RESULT_ERR, + SPI_FLASH_RESULT_TIMEOUT } SpiFlashOpResult; typedef struct{ @@ -32,7 +34,7 @@ typedef SpiFlashOpResult (* user_spi_flash_read)( SpiFlashChip *spi, uint32 src_addr, uint32 *des_addr, - uint32 size); + uint32 size); void spi_flash_set_read_func(user_spi_flash_read read); diff --git a/esp_meas.pro b/esp_meas.pro index 592fa9a..783f058 100644 --- a/esp_meas.pro +++ b/esp_meas.pro @@ -3,7 +3,7 @@ CONFIG += console CONFIG -= app_bundle CONFIG -= qt -DEFINES = ESPFS_HEATSHRINK +DEFINES = ESPFS_HEATSHRINK HTTPD_MAX_CONNECTIONS=4 __ets__ INCLUDEPATH = . \ esp_iot_sdk_v1.5.2/include \ @@ -11,6 +11,7 @@ INCLUDEPATH = . \ libesphttpd/include \ libesphttpd/espfs \ libesphttpd/core \ + libesphttpd/lib/heatshrink \ sbmp SOURCES += \ diff --git a/esp_meas.pro.user b/esp_meas.pro.user index 609dacd..5ff4caf 100644 --- a/esp_meas.pro.user +++ b/esp_meas.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/esphttpdconfig.mk b/esphttpdconfig.mk index cdac6de..4067b6d 100644 --- a/esphttpdconfig.mk +++ b/esphttpdconfig.mk @@ -12,13 +12,13 @@ # Adding JPG or PNG files (and any other compressed formats) is not recommended, because GZIP compression does not works effectively on compressed files. #Static gzipping is disabled by default. -GZIP_COMPRESSION ?= no +GZIP_COMPRESSION ?= yes # If COMPRESS_W_YUI is set to "yes" then the static css and js files will be compressed with yui-compressor # This option works only when GZIP_COMPRESSION is set to "yes" # http://yui.github.io/yuicompressor/ #Disabled by default. -COMPRESS_W_YUI ?= no +COMPRESS_W_YUI ?= yes YUI-COMPRESSOR ?= /usr/bin/yui-compressor #If USE_HEATSHRINK is set to "yes" then the espfs files will be compressed with Heatshrink and decompressed diff --git a/html/chibi.js b/html/chibi.js new file mode 100644 index 0000000..2ded180 --- /dev/null +++ b/html/chibi.js @@ -0,0 +1,2 @@ +/*!chibi 3.0.7, Copyright 2012-2016 Kyle Barrow, released under MIT license */ +!function(){"use strict";function e(){var e;for(h=!0,e=0;e=0;n-=1)e(t[n])}function r(e){return e.replace(/-\w/g,function(e){return e.charAt(1).toUpperCase()})}function a(e,t){return e.currentStyle?e.currentStyle[r(t)]:v.getComputedStyle?v.getComputedStyle(e,null).getPropertyValue(t):null}function o(e,t){return encodeURIComponent(e).replace(/%20/g,"+")+"="+encodeURIComponent(t).replace(/%20/g,"+")}function c(e,t,n){try{e.style[r(t)]=n}catch(a){}}function s(e){e.style.display="","none"===a(e,"display")&&(e.style.display="block")}function i(e){var t,r,a,c="";if(e.constructor===Object){for(t in e)if(e.hasOwnProperty(t))if(e[t].constructor===Array)for(r=0;r0&&(c+="&"+o(t.name,t.value));break;case"select-multiple":for(a=0;a0?c.substring(1):""}function u(e,t,r){var a,o,c,s=!1;return e&&(a=e.split(/\s+/),n(function(e){for(c=0;c0?T[0].className.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"").replace(/\s+/," "):void 0},t.setClass=function(e){return(e||""===e)&&n(function(t){t.className=e},T),t},t.addClass=function(e){return e&&n(function(t){t.className+=" "+e},T),t},t.removeClass=function(e){return u(e,"remove",T),t},t.toggleClass=function(e){return u(e,"toggle",T),t},t.hasClass=function(e){return u(e,"has",T)},t.html=function(e){return e||""===e?(n(function(t){t.innerHTML=e},T),t):T[0]?T[0].innerHTML:void 0},t.htmlBefore=function(e){return l(e,"before",T),t},t.htmlAfter=function(e){return l(e,"after",T),t},t.htmlAppend=function(e){return l(e,"append",T),t},t.htmlPrepend=function(e){return l(e,"prepend",T),t},t.attr=function(e,r){if(e){if(e=e.toLowerCase(),r||""===r)return n(function(t){"style"===e?t.style.cssText=r:"class"===e?t.className=r:t.setAttribute(e,r)},T),t;if(T[0])if("style"===e){if(T[0].style.cssText)return T[0].style.cssText}else if("class"===e){if(T[0].className)return T[0].className}else if(T[0].getAttribute(e))return T[0].getAttribute(e)}},t.data=function(e,n){return e?t.attr("data-"+e,n):void 0},t.val=function(e){var r,a,o;if(e||""===e)return n(function(t){switch(t.nodeName){case"SELECT":for(("string"==typeof e||"number"==typeof e)&&(e=[e]),a=0;a1?r:r[0];case"INPUT":case"TEXTAREA":case"BUTTON":return T[0].value}},t.checked=function(e){return"boolean"==typeof e?(n(function(t){"INPUT"!==t.nodeName||"checkbox"!==t.type&&"radio"!==t.type||(t.checked=e)},T),t):!T[0]||"INPUT"!==T[0].nodeName||"checkbox"!==T[0].type&&"radio"!==T[0].type?void 0:!!T[0].checked},t.on=function(r,a){return(e===v||e===g)&&(T=[e]),n(function(e){g.addEventListener?e.addEventListener(r,a,!1):g.attachEvent&&(e[r+a]=function(){return a.apply(e,arguments)},e.attachEvent("on"+r,e[r+a]))},T),t},t.off=function(r,a){return(e===v||e===g)&&(T=[e]),n(function(e){g.addEventListener?e.removeEventListener(r,a,!1):g.attachEvent&&(e.detachEvent("on"+r,e[r+a]),e[r+a]=null)},T),t},t.ajax=function(e,n,r,a,o){var c,s,u=i(T),l=n?n.toUpperCase():"GET",f=new RegExp("http[s]?://(.*?)/","gi"),d=f.exec(e),p="_ts="+ +new Date,h=g.getElementsByTagName("head")[0],m="chibi"+ +new Date+(y+=1);return!u||"GET"!==l&&"DELETE"!==l||(e+=-1===e.indexOf("?")?"?"+u:"&"+u,u=null),"GET"===l&&!o&&d&&v.location.host!==d[1]?(a&&(e+=-1===e.indexOf("?")?"?"+p:"&"+p),e=e.replace("=%3F","=?"),r&&-1!==e.indexOf("=?")&&(e=e.replace("=?","="+m),v[m]=function(e){try{r(e,200)}catch(t){}v[m]=void 0}),s=document.createElement("script"),s.async=!0,s.src=e,s.onload=function(){h.removeChild(s)},h.appendChild(s)):(v.XMLHttpRequest?c=new XMLHttpRequest:v.ActiveXObject&&(c=new ActiveXObject("Microsoft.XMLHTTP")),c&&(a&&(e+=-1===e.indexOf("?")?"?"+p:"&"+p),c.open(l,e,!0),c.onreadystatechange=function(){4===c.readyState&&r&&r(c.responseText,c.status)},c.setRequestHeader("X-Requested-With","XMLHttpRequest"),("POST"===l||"PUT"===l)&&c.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),c.send(u))),t},t.get=function(e,n,r,a){return t.ajax(e,"get",n,r,a)},t.post=function(e,n,r){return t.ajax(e,"post",n,r)},t}var d=[],p=[],h=!1,m=!1,y=0,g=document,v=window;g.addEventListener?(g.addEventListener("DOMContentLoaded",e,!1),v.addEventListener("load",t,!1)):g.attachEvent?(g.attachEvent("onreadystatechange",e),v.attachEvent("onload",t)):v.onload=t,v.$=f}(); \ No newline at end of file diff --git a/htmlpreview.sh b/htmlpreview.sh deleted file mode 100755 index 959b105..0000000 --- a/htmlpreview.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -rm -rf html_preview - -cp -rs "$PWD/html" "$PWD/html_preview/" - -for file in $(find html_preview/ -name "*.tpl") -do - mv $file `echo $file | sed s/.tpl$/.html/` -done - -echo "Html preview updated." diff --git a/htmlserver.sh b/htmlserver.sh deleted file mode 100755 index 11ae1b9..0000000 --- a/htmlserver.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -php -S localhost:8266 -t html_preview - diff --git a/libesphttpd/Makefile b/libesphttpd/Makefile index 2e447de..078c2b0 100644 --- a/libesphttpd/Makefile +++ b/libesphttpd/Makefile @@ -7,8 +7,8 @@ THISDIR:=$(dir $(abspath $(lastword $(MAKEFILE_LIST)))) #Default options. If you want to change them, please create ../esphttpdconfig.mk with the options you want in it. -GZIP_COMPRESSION ?= no -COMPRESS_W_YUI ?= no +GZIP_COMPRESSION ?= yes +COMPRESS_W_YUI ?= yes YUI-COMPRESSOR ?= /usr/bin/yui-compressor USE_HEATSHRINK ?= yes HTTPD_WEBSOCKETS ?= yes @@ -18,6 +18,11 @@ HTTPD_MAX_CONNECTIONS ?= 4 #For FreeRTOS HTTPD_STACKSIZE ?= 2048 +# this works only if you also enable YUI-COMPRESSOR +HTML-MINIFIER ?= html-minifier -c html-minifier-conf.json +COMPRESS_W_HTMLMINIFIER ?= yes + + # Output directors to store intermediate compiled files # relative to the project directory BUILD_BASE = build @@ -162,6 +167,9 @@ ifeq ("$(COMPRESS_W_YUI)","yes") $(Q) echo "Compression assets with yui-compressor. This may take a while..." $(Q) for file in `find html_compressed -type f -name "*.js"`; do $(YUI-COMPRESSOR) --type js $$file -o $$file; done $(Q) for file in `find html_compressed -type f -name "*.css"`; do $(YUI-COMPRESSOR) --type css $$file -o $$file; done +ifeq ("$(COMPRESS_W_HTMLMINIFIER)","yes") + $(Q) for file in `find html_compressed -type f -name "*.html" -o -name "*.htm" -o -name "*.tpl"`; do $(HTML-MINIFIER) $$file -o $$file; done +endif $(Q) awk "BEGIN {printf \"YUI compression ratio was: %.2f%%\\n\", (`du -b -s html_compressed/ | sed 's/\([0-9]*\).*/\1/'`/`du -b -s ../html/ | sed 's/\([0-9]*\).*/\1/'`)*100}" # mkespfsimage will compress html, css, svg and js files with gzip by default if enabled # override with -g cmdline parameter diff --git a/libesphttpd/core/httpd.c b/libesphttpd/core/httpd.c index 176c038..ab0273d 100644 --- a/libesphttpd/core/httpd.c +++ b/libesphttpd/core/httpd.c @@ -86,10 +86,10 @@ static const ICACHE_RODATA_ATTR MimeMap mimeTypes[]={ }; //Returns a static char* to a mime type for a given url to a file. -const char ICACHE_FLASH_ATTR *httpdGetMimetype(char *url) { +const char ICACHE_FLASH_ATTR *httpdGetMimetype(const char *url) { int i=0; //Go find the extension - char *ext=url+(strlen(url)-1); + const char *ext=url+(strlen(url)-1); while (ext!=url && *ext!='.') ext--; if (*ext=='.') ext++; @@ -99,7 +99,7 @@ const char ICACHE_FLASH_ATTR *httpdGetMimetype(char *url) { } //Looks up the connData info for a specific connection -static HttpdConnData ICACHE_FLASH_ATTR *httpdFindConnData(ConnTypePtr conn, char *remIp, int remPort) { +static HttpdConnData ICACHE_FLASH_ATTR *httpdFindConnData(ConnTypePtr conn, const char *remIp, int remPort) { for (int i=0; iremote_port == remPort && memcmp(connData[i]->remote_ip, remIp, 4) == 0) { @@ -154,7 +154,7 @@ int ICACHE_FLASH_ATTR httpdUrlDecode(char *val, int valLen, char *ret, int retLe esced=2; } else if (esced==2) { escVal+=httpdHexVal(val[s]); - ret[d++]=escVal; + ret[d++]=(char)escVal; esced=0; } else if (val[s]=='%') { esced=1; @@ -481,7 +481,7 @@ void ICACHE_FLASH_ATTR httpdSentCb(ConnTypePtr rconn, char *remIp, int remPort) httpdCgiIsDone(conn); } if (r==HTTPD_CGI_NOTFOUND || r==HTTPD_CGI_AUTHENTICATED) { - httpd_printf("ERROR! CGI fn returns code %d after sending data! Bad CGI!\n", r); + error("ERROR! CGI fn returns code %d after sending data! Bad CGI!", r); httpdCgiIsDone(conn); } httpdFlushSendBuffer(conn); @@ -514,6 +514,7 @@ static void ICACHE_FLASH_ATTR httpdProcessRequest(HttpdConnData *conn) { conn->cgiData=NULL; conn->cgi=builtInUrls[i].cgiCb; conn->cgiArg=builtInUrls[i].cgiArg; + conn->cgiArg2=builtInUrls[i].cgiArg2; break; } i++; diff --git a/libesphttpd/core/httpdespfs.c b/libesphttpd/core/httpdespfs.c index 9e474de..ae14289 100644 --- a/libesphttpd/core/httpdespfs.c +++ b/libesphttpd/core/httpdespfs.c @@ -27,7 +27,8 @@ static const char *gzipNonSupportedMessage = "HTTP/1.0 501 Not implemented\r\n" "Your browser does not accept gzip-compressed data.\r\n"; -EspFsFile *tryOpenIndex(const char *path) + +static EspFsFile *tryOpenIndex_do(const char *path, const char *indexname) { // Try appending index.tpl char fname[100]; @@ -41,17 +42,38 @@ EspFsFile *tryOpenIndex(const char *path) } // add index - strcpy(fname + url_len, "index.tpl"); + strcpy(fname + url_len, indexname); return espFsOpen(fname); } +EspFsFile *tryOpenIndex(const char *path) +{ + EspFsFile * file; -//This is a catch-all cgi function. It takes the url passed to it, looks up the corresponding -//path in the filesystem and if it exists, passes the file through. This simulates what a normal -//webserver would do with static files. -int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) + // if there is a dot in the file, assume it's an extension + // no point in trying to find index in this case, abort. + if (strchr(path, '.') != NULL) return NULL; + + + // try index.html + file = tryOpenIndex_do(path, "index.html"); + if (file != NULL) return file; + + // try index.htm + file = tryOpenIndex_do(path, "index.htm"); + if (file != NULL) return file; + + // try index.tpl + file = tryOpenIndex_do(path, "index.tpl"); + if (file != NULL) return file; + + return NULL; // failed to guess the right name +} + + +int ICACHE_FLASH_ATTR serveStaticFile(HttpdConnData *connData, const char* filepath) { EspFsFile *file = connData->cgiData; int len; @@ -65,18 +87,21 @@ int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) return HTTPD_CGI_DONE; } + // invalid call. + if (filepath == NULL) { + printf("serveStaticFile called with NULL path!\n"); + return HTTPD_CGI_NOTFOUND; + } + if (file == NULL) { //First call to this cgi. Open the file so we can read it. - file = espFsOpen(connData->url); + file = espFsOpen(filepath); if (file == NULL) { - // file not found - file = tryOpenIndex(connData->url); - - if (file == NULL) { - return HTTPD_CGI_NOTFOUND; - } + // If this is a folder, look for index file + file = tryOpenIndex(filepath); + if (file == NULL) return HTTPD_CGI_NOTFOUND; } // The gzip checking code is intentionally without #ifdefs because checking @@ -100,7 +125,7 @@ int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) connData->cgiData = file; httpdStartResponse(connData, 200); - httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url)); + httpdHeader(connData, "Content-Type", httpdGetMimetype(filepath)); if (isGzip) { httpdHeader(connData, "Content-Encoding", "gzip"); } @@ -122,6 +147,21 @@ int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) } + +//This is a catch-all cgi function. It takes the url passed to it, looks up the corresponding +//path in the filesystem and if it exists, passes the file through. This simulates what a normal +//webserver would do with static files. +int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) +{ + return serveStaticFile(connData, connData->url); +} + + +int ICACHE_FLASH_ATTR cgiEspFsFile(HttpdConnData *connData) +{ + return serveStaticFile(connData, connData->cgiArg); +} + //cgiEspFsTemplate can be used as a template. #define TEMPLATE_CHUNK 1024 @@ -164,18 +204,25 @@ int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) tpd->chunk_resume = false; - tpd->file = espFsOpen(connData->url); - if (tpd->file == NULL) { - // file not found - tpd->file = tryOpenIndex(connData->url); + const char *filepath = connData->url; + + // check for custom template URL + if (connData->cgiArg2 != NULL) { + filepath = connData->cgiArg2; + printf("Using filepath %s\n", filepath); + } + tpd->file = espFsOpen(filepath); + if (tpd->file == NULL) { + // If this is a folder, look for index file + tpd->file = tryOpenIndex(filepath); if (tpd->file == NULL) { - espFsClose(tpd->file); free(tpd); return HTTPD_CGI_NOTFOUND; } } + tpd->tplArg = NULL; tpd->tokenPos = -1; if (espFsFlags(tpd->file) & FLAG_GZIP) { @@ -186,7 +233,7 @@ int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) } connData->cgiData = tpd; httpdStartResponse(connData, 200); - httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url)); + httpdHeader(connData, "Content-Type", httpdGetMimetype(filepath)); httpdEndHeaders(connData); return HTTPD_CGI_MORE; } diff --git a/libesphttpd/espfs/espfs.c b/libesphttpd/espfs/espfs.c index 1abe721..1517868 100644 --- a/libesphttpd/espfs/espfs.c +++ b/libesphttpd/espfs/espfs.c @@ -39,15 +39,15 @@ It's written for use with httpd, but doesn't need to be used as such. #include "heatshrink_decoder.h" #endif -static char* espFsData = NULL; +static const char* espFsData = NULL; struct EspFsFile { - EspFsHeader *header; + const EspFsHeader *header; char decompressor; int32_t posDecomp; - char *posStart; - char *posComp; + const char *posStart; + const char *posComp; void *decompData; }; @@ -67,9 +67,10 @@ Accessing the flash through the mem emulation at 0x40200000 is a bit hairy: All a memory exception, crashing the program. */ -EspFsInitResult ICACHE_FLASH_ATTR espFsInit(void *flashAddress) { - if((uint32_t)flashAddress > 0x40200000) { - flashAddress = (void*)((uint32_t)flashAddress-0x40200000); +EspFsInitResult ICACHE_FLASH_ATTR espFsInit(const void *flashAddress) +{ + if ((uint32_t)flashAddress > 0x40200000) { + flashAddress = (void*)((uint32_t)flashAddress - 0x40200000); } // base address must be aligned to 4 bytes @@ -84,7 +85,7 @@ EspFsInitResult ICACHE_FLASH_ATTR espFsInit(void *flashAddress) { return ESPFS_INIT_RESULT_NO_IMAGE; } - espFsData = (char *)flashAddress; + espFsData = (const char *)flashAddress; return ESPFS_INIT_RESULT_OK; } @@ -93,20 +94,22 @@ EspFsInitResult ICACHE_FLASH_ATTR espFsInit(void *flashAddress) { //ToDo: perhaps memcpy also does unaligned accesses? #ifdef __ets__ -void ICACHE_FLASH_ATTR readFlashUnaligned(char *dst, char *src, int len) { +void ICACHE_FLASH_ATTR readFlashUnaligned(char *dst, char *src, int len) +{ uint8_t src_offset = ((uint32_t)src) & 3; uint32_t src_address = ((uint32_t)src) - src_offset; - uint32_t tmp_buf[len/4 + 2]; - spi_flash_read((uint32)src_address, (uint32*)tmp_buf, len+src_offset); - memcpy(dst, ((uint8_t*)tmp_buf)+src_offset, len); + uint32_t tmp_buf[len / 4 + 2]; + spi_flash_read((uint32)src_address, (uint32*)tmp_buf, len + src_offset); + memcpy(dst, ((uint8_t*)tmp_buf) + src_offset, len); } #else #define readFlashUnaligned memcpy #endif // Returns flags of opened file. -int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) { +int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) +{ if (fh == NULL) { httpd_printf("File handle not ready\n"); return -1; @@ -118,61 +121,64 @@ int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) { } //Open a file and return a pointer to the file desc struct. -EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) { +EspFsFile ICACHE_FLASH_ATTR *espFsOpen(const char *fileName) +{ + printf("Open file %s\n", fileName); + if (espFsData == NULL) { httpd_printf("Call espFsInit first!\n"); return NULL; } - char *p=espFsData; - char *hpos; + const char *p = espFsData; + const char *hpos; char namebuf[256]; EspFsHeader h; EspFsFile *r; //Strip initial slashes - while(fileName[0]=='/') fileName++; + while (fileName[0] == '/') fileName++; //Go find that file! - while(1) { - hpos=p; + while (1) { + hpos = p; //Grab the next file header. spi_flash_read((uint32)p, (uint32*)&h, sizeof(EspFsHeader)); - if (h.magic!=ESPFS_MAGIC) { + if (h.magic != ESPFS_MAGIC) { httpd_printf("Magic mismatch. EspFS image broken.\n"); return NULL; } - if (h.flags&FLAG_LASTFILE) { - httpd_printf("End of image.\n"); + if (h.flags & FLAG_LASTFILE) { + httpd_printf("File %s not found in EspFS.\n", fileName); return NULL; } //Grab the name of the file. - p+=sizeof(EspFsHeader); + p += sizeof(EspFsHeader); spi_flash_read((uint32)p, (uint32*)&namebuf, sizeof(namebuf)); -// httpd_printf("Found file '%s'. Namelen=%x fileLenComp=%x, compr=%d flags=%d\n", -// namebuf, (unsigned int)h.nameLen, (unsigned int)h.fileLenComp, h.compression, h.flags); - if (strcmp(namebuf, fileName)==0) { +// httpd_printf("Found file '%s'. Namelen=%x fileLenComp=%x, compr=%d flags=%d\n", +// namebuf, (unsigned int)h.nameLen, (unsigned int)h.fileLenComp, h.compression, h.flags); + if (strcmp(namebuf, fileName) == 0) { //Yay, this is the file we need! - p+=h.nameLen; //Skip to content. - r=(EspFsFile *)malloc(sizeof(EspFsFile)); //Alloc file desc mem -// httpd_printf("Alloc %p\n", r); - if (r==NULL) return NULL; - r->header=(EspFsHeader *)hpos; - r->decompressor=h.compression; - r->posComp=p; - r->posStart=p; - r->posDecomp=0; - if (h.compression==COMPRESS_NONE) { - r->decompData=NULL; + p += h.nameLen; //Skip to content. + r = (EspFsFile *)malloc(sizeof(EspFsFile)); //Alloc file desc mem +// httpd_printf("Alloc %p\n", r); + if (r == NULL) return NULL; + r->header = (const EspFsHeader *)hpos; + r->decompressor = h.compression; + r->posComp = p; + r->posStart = p; + r->posDecomp = 0; + if (h.compression == COMPRESS_NONE) { + r->decompData = NULL; #ifdef ESPFS_HEATSHRINK - } else if (h.compression==COMPRESS_HEATSHRINK) { + } else if (h.compression == COMPRESS_HEATSHRINK) { //File is compressed with Heatshrink. char parm; heatshrink_decoder *dec; //Decoder params are stored in 1st byte. - readFlashUnaligned(&parm, r->posComp, 1); + readFlashUnaligned(&parm, (char*)r->posComp, 1); r->posComp++; httpd_printf("Heatshrink compressed file; decode parms = %x\n", parm); - dec=heatshrink_decoder_alloc(16, (parm>>4)&0xf, parm&0xf); - r->decompData=dec; + dec = heatshrink_decoder_alloc(16, (parm >> 4) & 0xf, parm & 0xf); + r->decompData = dec; #endif } else { httpd_printf("Invalid compression: %d\n", h.compression); @@ -181,37 +187,38 @@ EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) { return r; } //We don't need this file. Skip name and file - p+=h.nameLen+h.fileLenComp; - if ((int)p&3) p+=4-((int)p&3); //align to next 32bit val + p += h.nameLen + h.fileLenComp; + if ((int)p & 3) p += 4 - ((int)p & 3); //align to next 32bit val } } //Read len bytes from the given file into buff. Returns the actual amount of bytes read. -int ICACHE_FLASH_ATTR espFsRead(EspFsFile *fh, char *buff, int len) { +int ICACHE_FLASH_ATTR espFsRead(EspFsFile *fh, char *buff, int len) +{ int flen, fdlen; - if (fh==NULL) return 0; + if (fh == NULL) return 0; readFlashUnaligned((char*)&flen, (char*)&fh->header->fileLenComp, 4); //Cache file length. //Do stuff depending on the way the file is compressed. - if (fh->decompressor==COMPRESS_NONE) { + if (fh->decompressor == COMPRESS_NONE) { int toRead; - toRead=flen-(fh->posComp-fh->posStart); - if (len>toRead) len=toRead; -// httpd_printf("Reading %d bytes from %x\n", len, (unsigned int)fh->posComp); - readFlashUnaligned(buff, fh->posComp, len); - fh->posDecomp+=len; - fh->posComp+=len; -// httpd_printf("Done reading %d bytes, pos=%x\n", len, fh->posComp); + toRead = flen - (fh->posComp - fh->posStart); + if (len > toRead) len = toRead; +// httpd_printf("Reading %d bytes from %x\n", len, (unsigned int)fh->posComp); + readFlashUnaligned(buff, (char*)fh->posComp, len); + fh->posDecomp += len; + fh->posComp += len; +// httpd_printf("Done reading %d bytes, pos=%x\n", len, fh->posComp); return len; #ifdef ESPFS_HEATSHRINK - } else if (fh->decompressor==COMPRESS_HEATSHRINK) { + } else if (fh->decompressor == COMPRESS_HEATSHRINK) { readFlashUnaligned((char*)&fdlen, (char*)&fh->header->fileLenDecomp, 4); - int decoded=0; + int decoded = 0; size_t elen, rlen; char ebuff[16]; - heatshrink_decoder *dec=(heatshrink_decoder *)fh->decompData; -// httpd_printf("Alloc %p\n", dec); + heatshrink_decoder *dec = (heatshrink_decoder *)fh->decompData; +// httpd_printf("Alloc %p\n", dec); if (fh->posDecomp == fdlen) { return 0; } @@ -220,26 +227,26 @@ int ICACHE_FLASH_ATTR espFsRead(EspFsFile *fh, char *buff, int len) { // This means even when there is no input data (elen==0) try to poll decoder until // posDecomp equals decompressed file length - while(decodedposComp - fh->posStart); - if (elen>0) { - readFlashUnaligned(ebuff, fh->posComp, 16); - heatshrink_decoder_sink(dec, (uint8_t *)ebuff, (elen>16)?16:elen, &rlen); - fh->posComp+=rlen; + elen = flen - (fh->posComp - fh->posStart); + if (elen > 0) { + readFlashUnaligned(ebuff, (char*)fh->posComp, 16); + heatshrink_decoder_sink(dec, (uint8_t *)ebuff, (elen > 16) ? 16 : elen, &rlen); + fh->posComp += rlen; } //Grab decompressed data and put into buff - heatshrink_decoder_poll(dec, (uint8_t *)buff, len-decoded, &rlen); - fh->posDecomp+=rlen; - buff+=rlen; - decoded+=rlen; + heatshrink_decoder_poll(dec, (uint8_t *)buff, len - decoded, &rlen); + fh->posDecomp += rlen; + buff += rlen; + decoded += rlen; -// httpd_printf("Elen %d rlen %d d %d pd %ld fdl %d\n",elen,rlen,decoded, fh->posDecomp, fdlen); +// httpd_printf("Elen %d rlen %d d %d pd %ld fdl %d\n",elen,rlen,decoded, fh->posDecomp, fdlen); if (elen == 0) { if (fh->posDecomp == fdlen) { -// httpd_printf("Decoder finish\n"); +// httpd_printf("Decoder finish\n"); heatshrink_decoder_finish(dec); } return decoded; @@ -252,16 +259,17 @@ int ICACHE_FLASH_ATTR espFsRead(EspFsFile *fh, char *buff, int len) { } //Close the file. -void ICACHE_FLASH_ATTR espFsClose(EspFsFile *fh) { - if (fh==NULL) return; +void ICACHE_FLASH_ATTR espFsClose(EspFsFile *fh) +{ + if (fh == NULL) return; #ifdef ESPFS_HEATSHRINK - if (fh->decompressor==COMPRESS_HEATSHRINK) { - heatshrink_decoder *dec=(heatshrink_decoder *)fh->decompData; + if (fh->decompressor == COMPRESS_HEATSHRINK) { + heatshrink_decoder *dec = (heatshrink_decoder *)fh->decompData; heatshrink_decoder_free(dec); -// httpd_printf("Freed %p\n", dec); +// httpd_printf("Freed %p\n", dec); } #endif -// httpd_printf("Freed %p\n", fh); +// httpd_printf("Freed %p\n", fh); free(fh); } diff --git a/libesphttpd/espfs/espfsformat.h b/libesphttpd/espfs/espfsformat.h index 8ce5549..e0718a7 100644 --- a/libesphttpd/espfs/espfsformat.h +++ b/libesphttpd/espfs/espfsformat.h @@ -1,10 +1,12 @@ #ifndef ESPROFSFORMAT_H #define ESPROFSFORMAT_H +#include + /* Stupid cpio-like tool to make read-only 'filesystems' that live on the flash SPI chip of the module. Can (will) use lzf compression (when I come around to it) to make shit quicker. Aligns names, files, -headers on 4-byte boundaries so the SPI abstraction hardware in the ESP8266 doesn't crap on itself +headers on 4-byte boundaries so the SPI abstraction hardware in the ESP8266 doesn't crap on itself when trying to do a <4byte or unaligned read. */ @@ -30,4 +32,4 @@ typedef struct { int32_t fileLenDecomp; } __attribute__((packed)) EspFsHeader; -#endif \ No newline at end of file +#endif diff --git a/libesphttpd/html-minifier-conf.json b/libesphttpd/html-minifier-conf.json new file mode 100644 index 0000000..f7c3dce --- /dev/null +++ b/libesphttpd/html-minifier-conf.json @@ -0,0 +1,25 @@ +{ + "removeComments": true, + "removeCommentsFromCDATA": true, + "removeCDATASectionsFromCDATA": true, + "collapseWhitespace": true, + "conservativeCollapse": false, + "collapseBooleanAttributes": true, + "removeTagWhitespace": true, + "removeAttributeQuotes": true, + "removeRedundantAttributes": true, + "useShortDoctype": true, + "removeEmptyAttributes": true, + "removeScriptTypeAttributes": true, + "removeStyleLinkTypeAttributes": true, + "removeOptionalTags": false, + "removeEmptyElements": false, + "lint": false, + "keepClosingSlash": false, + "caseSensitive": false, + "minifyJS": true, + "minifyCSS": true, + "includeAutoGeneratedTags": false, + "ignoreCustomComments": [], + "processScripts": [] +} diff --git a/libesphttpd/include/espfs.h b/libesphttpd/include/espfs.h index c41df0a..399b4f3 100644 --- a/libesphttpd/include/espfs.h +++ b/libesphttpd/include/espfs.h @@ -13,11 +13,11 @@ typedef enum { typedef struct EspFsFile EspFsFile; -EspFsInitResult espFsInit(void *flashAddress); -EspFsFile *espFsOpen(char *fileName); +EspFsInitResult espFsInit(const void *flashAddress); +EspFsFile *espFsOpen(const char *fileName); int espFsFlags(EspFsFile *fh); int espFsRead(EspFsFile *fh, char *buff, int len); void espFsClose(EspFsFile *fh); -#endif \ No newline at end of file +#endif diff --git a/libesphttpd/include/httpd.h b/libesphttpd/include/httpd.h index f89ae69..e176ec4 100644 --- a/libesphttpd/include/httpd.h +++ b/libesphttpd/include/httpd.h @@ -26,9 +26,15 @@ struct HttpdConnData { char requestType; // One of the HTTPD_METHOD_* values char *url; // The URL requested, without hostname or GET arguments char *getArgs; // The GET arguments for this request, if any. + const void *cgiArg; // Argument to the CGI function, as stated as the 3rd argument of // the builtInUrls entry that referred to the CGI function. + + const void *cgiArg2; // Argument to the CGI function, as stated as the 4th argument of + // the builtInUrls entry that referred to the CGI function. + void *cgiData; // Opaque data pointer for the CGI function + char *hostName; // Host name field of request HttpdPriv *priv; // Opaque pointer to data for internal httpd housekeeping cgiSendCallback cgi; // CGI function pointer @@ -55,8 +61,33 @@ typedef struct { const char *url; cgiSendCallback cgiCb; const void *cgiArg; + const void *cgiArg2; } HttpdBuiltInUrl; + +// macros for defining HttpdBuiltInUrl's + +#define ROUTE_CGI_ARG2(path, handler, arg1, arg2) {path, handler, (void *)arg1, (void *)arg2} + +#define ROUTE_CGI_ARG(path, handler, arg1) ROUTE_CGI_ARG2(path, handler, arg1, NULL) +#define ROUTE_CGI(path, handler) ROUTE_CGI_ARG2(path, handler, NULL, NULL) + +#define ROUTE_FILE(path, filepath) ROUTE_CGI_ARG(path, cgiEspFsStaticFile, filepath) + +// the argument of a template route is accessible as cgiArg2 on the connData struct. +#define ROUTE_TPL(path, replacer) ROUTE_CGI_ARG(path, cgiEspFsTemplate, replacer) +#define ROUTE_TPL_FILE(path, replacer, filepath) ROUTE_CGI_ARG2(path, cgiEspFsTemplate, replacer, filepath) + +#define ROUTE_REDIRECT(path, target) ROUTE_CGI_ARG(path, cgiRedirect, target) +#define ROUTE_AUTH(path, passwdFunc) ROUTE_CGI_ARG(path, authBasic, passwdFunc) + +// catch-all route +#define ROUTE_FS(path) ROUTE_CGI(path, cgiEspFsHook) + +#define ROUTE_END() {NULL, NULL, NULL, NULL} + + + int cgiRedirect(HttpdConnData *connData); int cgiRedirectToHostname(HttpdConnData *connData); int cgiRedirectApClientToHostname(HttpdConnData *connData); @@ -64,7 +95,7 @@ void httpdRedirect(HttpdConnData *conn, char *newUrl); int httpdUrlDecode(char *val, int valLen, char *ret, int retLen); int httpdFindArg(char *line, char *arg, char *buff, int buffLen); void httpdInit(HttpdBuiltInUrl *fixedUrls, int port); -const char *httpdGetMimetype(char *url); +const char *httpdGetMimetype(const char *url); void httpdDisableTransferEncoding(HttpdConnData *conn); void httpdStartResponse(HttpdConnData *conn, int code); void httpdHeader(HttpdConnData *conn, const char *field, const char *val); @@ -80,4 +111,11 @@ void httpdDisconCb(ConnTypePtr conn, char *remIp, int remPort); int httpdConnectCb(ConnTypePtr conn, char *remIp, int remPort); +// debugging + +#define LOG_EOL "\n" +#define dbg(fmt, ...) httpd_printf(fmt LOG_EOL, ##__VA_ARGS__); +#define error(fmt, ...) httpd_printf("\x1b[31;1m"fmt"\x1b[0m"LOG_EOL, ##__VA_ARGS__); +#define info(fmt, ...) httpd_printf("\x1b[32;1m"fmt"\x1b[0m"LOG_EOL, ##__VA_ARGS__); + #endif diff --git a/libesphttpd/include/httpdespfs.h b/libesphttpd/include/httpdespfs.h index 5eda335..cc4bff0 100644 --- a/libesphttpd/include/httpdespfs.h +++ b/libesphttpd/include/httpdespfs.h @@ -3,7 +3,13 @@ #include "httpd.h" +/** Catch-all, use in '*' routes */ int cgiEspFsHook(HttpdConnData *connData); + +/** Template route */ int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData); -#endif \ No newline at end of file +/** Static file route with the file as the first arg. */ +int ICACHE_FLASH_ATTR cgiEspFsFile(HttpdConnData *connData); + +#endif diff --git a/user/user_main.c b/user/user_main.c index 8ba72d5..d52c1a4 100644 --- a/user/user_main.c +++ b/user/user_main.c @@ -107,27 +107,28 @@ CgiUploadFlashDef uploadParams = { * general ones. Authorization things (like authBasic) act as a 'barrier' and * should be placed above the URLs they protect. */ -static HttpdBuiltInUrl builtInUrls[] = { - {"*", cgiRedirectApClientToHostname, "esp8266.nonet"}, // redirect func for the captive portal - {"/", cgiEspFsTemplate, (void *)tplCounter}, - {"/multipart.tpl", cgiEspFsTemplate, (void *)tplMultipart}, -// {"/random.tpl", cgiRandomNumbers, NULL}, +static HttpdBuiltInUrl builtInUrls[] = { + ROUTE_CGI_ARG("*", cgiRedirectApClientToHostname, "esp8266.nonet"), // redirect func for the captive portal + + ROUTE_TPL("/", tplCounter), + ROUTE_TPL_FILE("/multipart", tplMultipart, "/multipart.tpl"), //Enable the line below to protect the WiFi configuration with an username/password combo. -// {"/wifi/*", authBasic, (void *)myPassFn}, - - {"/wifi", cgiRedirect, "/wifi/"}, - {"/wifi/", cgiEspFsTemplate, (void *)tplWlan}, - //{"/wifi/", cgiRedirect, "/wifi/wifi.tpl"}, - {"/wifi/wifiscan.cgi", cgiWiFiScan, NULL}, - {"/wifi/connect.cgi", cgiWiFiConnect, NULL}, - {"/wifi/connstatus.cgi", cgiWiFiConnStatus, NULL}, - {"/wifi/setmode.cgi", cgiWiFiSetMode, NULL}, - - {"*", cgiEspFsHook, NULL}, //Catch-all cgi function for the filesystem - {NULL, NULL, NULL} +// ROUTE_AUTH("/wifi/*", myPassFn), + + ROUTE_REDIRECT("/wifi", "/wifi/"), + ROUTE_TPL_FILE("/wifi/", tplWlan, "/wifi/index.tpl"), + + ROUTE_CGI("/wifi/wifiscan.cgi", cgiWiFiScan), + ROUTE_CGI("/wifi/connect.cgi", cgiWiFiConnect), + ROUTE_CGI("/wifi/connstatus.cgi", cgiWiFiConnStatus), + ROUTE_CGI("/wifi/setmode.cgi", cgiWiFiSetMode), + + ROUTE_FS("*"), //Catch-all cgi function for the filesystem + + ROUTE_END() };