improved template to support multipart substitution

master
Ondřej Hruška 8 years ago
parent ed17164e1c
commit b9885c8b1a
  1. 14
      html/multipart.tpl
  2. 34
      libesphttpd/core/httpd.c
  3. 187
      libesphttpd/core/httpdespfs.c
  4. 53
      user/cgi.c
  5. 2
      user/cgi.h
  6. 18
      user/user_main.c

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head><title>ESP8266 web server</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="main">
<h1>Multipart</h1>
<nav>
<ul>
%numbers%
</ul>
</body></html>

@ -5,9 +5,9 @@ Esp8266 http server - core routines
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
* ----------------------------------------------------------------------------
*/
@ -92,7 +92,7 @@ const char ICACHE_FLASH_ATTR *httpdGetMimetype(char *url) {
char *ext=url+(strlen(url)-1);
while (ext!=url && *ext!='.') ext--;
if (*ext=='.') ext++;
//ToDo: strcmp is case sensitive; we may want to do case-intensive matching here...
while (mimeTypes[i].ext!=NULL && strcmp(ext, mimeTypes[i].ext)!=0) i++;
return mimeTypes[i].mimetype;
@ -172,7 +172,7 @@ int ICACHE_FLASH_ATTR httpdUrlDecode(char *val, int valLen, char *ret, int retLe
//Find a specific arg in a string of get- or post-data.
//Line is the string of post/get-data, arg is the name of the value to find. The
//zero-terminated result is written in buff, with at most buffLen bytes used. The
//function returns the length of the result, or -1 if the value wasn't found. The
//function returns the length of the result, or -1 if the value wasn't found. The
//returned string will be urldecoded already.
int ICACHE_FLASH_ATTR httpdFindArg(char *line, char *arg, char *buff, int buffLen) {
char *p, *e;
@ -233,9 +233,9 @@ void ICACHE_FLASH_ATTR httpdDisableTransferEncoding(HttpdConnData *conn) {
void ICACHE_FLASH_ATTR httpdStartResponse(HttpdConnData *conn, int code) {
char buff[256];
int l;
l=sprintf(buff, "HTTP/1.%d %d OK\r\nServer: esp8266-httpd/"HTTPDVER"\r\n%s\r\n",
(conn->priv->flags&HFL_HTTP11)?1:0,
code,
l=sprintf(buff, "HTTP/1.%d %d OK\r\nServer: esp8266-httpd/"HTTPDVER"\r\n%s\r\n",
(conn->priv->flags&HFL_HTTP11)?1:0,
code,
(conn->priv->flags&HFL_CHUNKED)?"Transfer-Encoding: chunked":"Connection: close");
httpdSend(conn, buff, l);
}
@ -524,7 +524,7 @@ static void ICACHE_FLASH_ATTR httpdProcessRequest(HttpdConnData *conn) {
httpd_printf("%s not found. 404!\n", conn->url);
conn->cgi=cgiNotFound;
}
//Okay, we have a CGI function that matches the URL. See if it wants to handle the
//particular URL we're supposed to handle.
r=conn->cgi(conn);
@ -553,7 +553,7 @@ static void ICACHE_FLASH_ATTR httpdProcessRequest(HttpdConnData *conn) {
static void ICACHE_FLASH_ATTR httpdParseHeader(char *h, HttpdConnData *conn) {
int i;
char firstLine=0;
if (strncmp(h, "GET ", 4)==0) {
conn->requestType = HTTPD_METHOD_GET;
firstLine=1;
@ -568,7 +568,7 @@ static void ICACHE_FLASH_ATTR httpdParseHeader(char *h, HttpdConnData *conn) {
if (firstLine) {
char *e;
//Skip past the space after POST/GET
i=0;
while (h[i]!=' ') i++;
@ -737,6 +737,18 @@ int ICACHE_FLASH_ATTR httpdConnectCb(ConnTypePtr conn, char *remIp, int remPort)
httpd_printf("Conn req from %d.%d.%d.%d:%d, using pool slot %d\n", remIp[0]&0xff, remIp[1]&0xff, remIp[2]&0xff, remIp[3]&0xff, remPort, i);
if (i==HTTPD_MAX_CONNECTIONS) {
httpd_printf("Aiee, conn pool overflow!\n");
// added by mightypork
if (system_get_free_heap_size() < 29000) {
// this probably means we got flooded by someone spamming F5
// better restart
httpd_printf("\x1b[31;1mLow heap & con pool overflow, GOING FOR RESTART.\x1b[0m\n");
system_restart();
}
return 0;
}
connData[i]=malloc(sizeof(HttpdConnData));

@ -18,24 +18,32 @@ Connector to let httpd use the espfs filesystem to serve the files in it.
// 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.)
static const char *gzipNonSupportedMessage = "HTTP/1.0 501 Not implemented\r\nServer: esp8266-httpd/"HTTPDVER"\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: 52\r\n\r\nYour browser does not accept gzip-compressed data.\r\n";
static const char *gzipNonSupportedMessage = "HTTP/1.0 501 Not implemented\r\n"
"Server: esp8266-httpd/"HTTPDVER"\r\n"
"Connection: close\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: 52\r\n"
"\r\n"
"Your browser does not accept gzip-compressed data.\r\n";
EspFsFile *tryOpenIndex(char *buff, const char *path) {
EspFsFile *tryOpenIndex(const char *path)
{
// Try appending index.tpl
char fname[100];
size_t url_len = strlen(path);
strcpy(buff, path); // add current path
strcpy(fname, path); // add current path
// add slash if not already ending with slash
if (path[url_len-1] != '/') {
buff[url_len++] = '/';
if (path[url_len - 1] != '/') {
fname[url_len++] = '/';
}
// add index
strcpy(buff + url_len, "index.tpl");
strcpy(fname + url_len, "index.tpl");
return espFsOpen(buff);
return espFsOpen(fname);
}
@ -43,28 +51,29 @@ EspFsFile *tryOpenIndex(char *buff, const char *path) {
//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) {
EspFsFile *file=connData->cgiData;
int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData)
{
EspFsFile *file = connData->cgiData;
int len;
char buff[1024];
char acceptEncodingBuffer[64];
int isGzip;
if (connData->conn==NULL) {
if (connData->conn == NULL) {
//Connection aborted. Clean up.
espFsClose(file);
return HTTPD_CGI_DONE;
}
if (file==NULL) {
if (file == NULL) {
//First call to this cgi. Open the file so we can read it.
file=espFsOpen(connData->url);
if (file==NULL) {
file = espFsOpen(connData->url);
if (file == NULL) {
// file not found
file = tryOpenIndex(buff, connData->url);
file = tryOpenIndex(connData->url);
if (file==NULL) {
if (file == NULL) {
return HTTPD_CGI_NOTFOUND;
}
@ -89,7 +98,7 @@ int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) {
}
}
connData->cgiData=file;
connData->cgiData = file;
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url));
if (isGzip) {
@ -100,9 +109,9 @@ int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) {
return HTTPD_CGI_MORE;
}
len=espFsRead(file, buff, 1024);
if (len>0) httpdSend(connData, buff, len);
if (len!=1024) {
len = espFsRead(file, buff, 1024);
if (len > 0) httpdSend(connData, buff, len);
if (len != 1024) {
//We're done.
espFsClose(file);
return HTTPD_CGI_DONE;
@ -115,23 +124,33 @@ int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) {
//cgiEspFsTemplate can be used as a template.
#define TEMPLATE_CHUNK 1024
typedef struct {
EspFsFile *file;
void *tplArg;
char token[64];
int tokenPos;
char buff[TEMPLATE_CHUNK + 1];
bool chunk_resume;
int buff_len;
int buff_x;
int buff_sp;
char *buff_e;
} TplData;
typedef void (* TplCallback)(HttpdConnData *connData, char *token, void **arg);
typedef int (* TplCallback)(HttpdConnData *connData, char *token, void **arg);
int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) {
TplData *tpd=connData->cgiData;
int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData)
{
TplData *tpd = connData->cgiData;
int len;
int x, sp=0;
char *e=NULL;
char buff[1025];
int x, sp = 0;
char *e = NULL;
if (connData->conn==NULL) {
if (connData->conn == NULL) {
//Connection aborted. Clean up.
((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg);
espFsClose(tpd->file);
@ -139,77 +158,135 @@ int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) {
return HTTPD_CGI_DONE;
}
if (tpd==NULL) {
if (tpd == NULL) {
//First call to this cgi. Open the file so we can read it.
tpd=(TplData *)malloc(sizeof(TplData));
tpd->file=espFsOpen(connData->url);
if (tpd->file==NULL) {
tpd = (TplData *)malloc(sizeof(TplData));
tpd->chunk_resume = false;
tpd->file = espFsOpen(connData->url);
if (tpd->file == NULL) {
// file not found
tpd->file = tryOpenIndex(buff, connData->url);
tpd->file = tryOpenIndex(connData->url);
if (tpd->file==NULL) {
if (tpd->file == NULL) {
espFsClose(tpd->file);
free(tpd);
return HTTPD_CGI_NOTFOUND;
}
}
tpd->tplArg=NULL;
tpd->tokenPos=-1;
tpd->tplArg = NULL;
tpd->tokenPos = -1;
if (espFsFlags(tpd->file) & FLAG_GZIP) {
httpd_printf("cgiEspFsTemplate: Trying to use gzip-compressed file %s as template!\n", connData->url);
espFsClose(tpd->file);
free(tpd);
return HTTPD_CGI_NOTFOUND;
}
connData->cgiData=tpd;
connData->cgiData = tpd;
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url));
httpdEndHeaders(connData);
return HTTPD_CGI_MORE;
}
len=espFsRead(tpd->file, buff, 1024);
if (len>0) {
sp=0;
e=buff;
for (x=0; x<len; x++) {
if (tpd->tokenPos==-1) {
char *buff = tpd->buff;
// resume the parser state from the last token,
// if subst. func wants more data to be sent.
if (tpd->chunk_resume) {
printf("\nResuming chunk...\n");
len = tpd->buff_len;
e = tpd->buff_e;
sp = tpd->buff_sp;
x = tpd->buff_x;
} else {
len = espFsRead(tpd->file, buff, TEMPLATE_CHUNK);
tpd->buff_len = len;
e = buff;
sp = 0;
x = 0;
}
if (len > 0) {
printf("*a");
for (; x < len; x++) {
printf("*b");
if (tpd->tokenPos == -1) {
//Inside ordinary text.
if (buff[x]=='%') {
if (buff[x] == '%') {
//Send raw data up to now
if (sp!=0) httpdSend(connData, e, sp);
sp=0;
if (sp != 0) httpdSend(connData, e, sp);
sp = 0;
//Go collect token chars.
tpd->tokenPos=0;
tpd->tokenPos = 0;
} else {
sp++;
}
} else {
if (buff[x]=='%') {
if (tpd->tokenPos==0) {
printf("*c");
if (buff[x] == '%') {
printf("*d");
if (tpd->tokenPos == 0) {
//This is the second % of a %% escape string.
//Send a single % and resume with the normal program flow.
httpdSend(connData, "%", 1);
} else {
//This is an actual token.
tpd->token[tpd->tokenPos++]=0; //zero-terminate token
((TplCallback)(connData->cgiArg))(connData, tpd->token, &tpd->tplArg);
printf("*e");
if (!tpd->chunk_resume) {
//This is an actual token.
tpd->token[tpd->tokenPos++] = 0; //zero-terminate token
} else {
printf("*f");
}
tpd->chunk_resume = false;
int status = ((TplCallback)(connData->cgiArg))(connData, tpd->token, &tpd->tplArg);
if (status == HTTPD_CGI_MORE) {
printf("\nSaving template pos & continuing in next run..\n");
// wants to send more in this token's place.....
tpd->chunk_resume = true;
tpd->buff_len = len;
tpd->buff_e = e;
tpd->buff_sp = sp;
tpd->buff_x = x;
break;
}
}
//Go collect normal chars again.
e=&buff[x+1];
tpd->tokenPos=-1;
e = &buff[x + 1];
tpd->tokenPos = -1;
} else {
if (tpd->tokenPos<(sizeof(tpd->token)-1)) tpd->token[tpd->tokenPos++]=buff[x];
if (tpd->tokenPos < (sizeof(tpd->token) - 1)) tpd->token[tpd->tokenPos++] = buff[x];
}
}
}
}
if (tpd->chunk_resume) {
return HTTPD_CGI_MORE;
}
//Send remaining bit.
if (sp!=0) httpdSend(connData, e, sp);
if (len!=1024) {
if (sp != 0) httpdSend(connData, e, sp);
if (len != TEMPLATE_CHUNK) {
//We're done.
// let the cgi func clean it's stuff
((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg);
espFsClose(tpd->file);
free(tpd);
return HTTPD_CGI_DONE;

@ -29,15 +29,68 @@ int ICACHE_FLASH_ATTR tplCounter(HttpdConnData *connData, char *token, void **ar
os_sprintf(buff, "%ld", hitCounter);
}
httpdSend(connData, buff, -1);
return HTTPD_CGI_DONE;
}
typedef struct {
uint32_t count_remain;
} RandomNumberState;
//Template code for the counter on the index page.
int ICACHE_FLASH_ATTR tplMultipart(HttpdConnData *connData, char *token, void **arg)
{
if (token == NULL) {
if (*arg != NULL) free(*arg);
return HTTPD_CGI_DONE; // cleanup
}
if (os_strcmp(token, "numbers") == 0) {
RandomNumberState *rns = *arg;
char buff[20];
if (rns == NULL) {
//First call to this cgi. Open the file so we can read it.
rns=(RandomNumberState *)malloc(sizeof(RandomNumberState));
*arg=rns;
// parse count
uint32_t count = 1;
int len = httpdFindArg(connData->getArgs, "count", buff, sizeof(buff));
if (len==-1) {
// no such get arg
} else {
count = (uint32_t)atoi(buff);
}
rns->count_remain = count;
printf("User wants %d numbers.", count);
}
for (int i = 0; i < 100; i++) {
os_sprintf(buff, "<li>%lu\n", os_random());
httpdSend(connData, buff, -1);
if (--rns->count_remain == 0) {
break;
}
}
if (rns->count_remain == 0) {
free(rns);
return HTTPD_CGI_DONE;
}
return HTTPD_CGI_MORE;
}
return HTTPD_CGI_DONE;
}
// better to put it in the fs...
int FLASH_FN cgiRandomNumbers(HttpdConnData *connData) {

@ -8,4 +8,6 @@ int tplCounter(HttpdConnData *connData, char *token, void **arg);
int cgiRandomNumbers(HttpdConnData *connData);
int tplMultipart(HttpdConnData *connData, char *token, void **arg);
#endif

@ -28,6 +28,16 @@
#include "sbmp.h"
#define SHOW_HEAP_USE
#ifdef SHOW_HEAP_USE
static ETSTimer prHeapTimer;
static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg) {
os_printf("Heap: %ld\n", (unsigned long)system_get_free_heap_size());
}
#endif
/**
* @brief BasicAuth name/password checking function.
@ -101,6 +111,8 @@ static HttpdBuiltInUrl builtInUrls[] = {
{"*", cgiRedirectApClientToHostname, "esp8266.nonet"}, // redirect func for the captive portal
{"/", cgiEspFsTemplate, (void *)tplCounter},
{"/multipart.tpl", cgiEspFsTemplate, (void *)tplMultipart},
{"/random.tpl", cgiRandomNumbers, NULL},
//Enable the line below to protect the WiFi configuration with an username/password combo.
@ -166,6 +178,12 @@ void user_init(void)
os_printf("\nReady\n");
#ifdef SHOW_HEAP_USE
os_timer_disarm(&prHeapTimer);
os_timer_setfn(&prHeapTimer, prHeapTimerCb, NULL);
os_timer_arm(&prHeapTimer, 3000, 1);
#endif
// // print TEST on the command interface every 500 ms
// os_timer_disarm(&prTestTimer);
// os_timer_setfn(&prTestTimer, test_timer_task, NULL);

Loading…
Cancel
Save