Skip to content

Commit

Permalink
Implemented multipart template substitutions & added ROUTE_* macros f…
Browse files Browse the repository at this point in the history
…or builtInUrls setup
  • Loading branch information
MightyPork committed Jan 22, 2017
1 parent 10a9fa1 commit cc6e7da
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 47 deletions.
5 changes: 3 additions & 2 deletions core/httpd.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,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++;

Expand Down Expand Up @@ -579,6 +579,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++;
Expand Down
203 changes: 164 additions & 39 deletions core/httpdespfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,40 +16,96 @@ Connector to let httpd use the espfs filesystem to serve the files in it.
#include "espfs.h"
#include "espfsformat.h"

#define FILE_CHUNK_LEN 1024

// 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";

/**
* 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);

//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.
httpd_cgi_state ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) {
// 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
* @return file pointer or NULL
*/
EspFsFile *tryOpenIndex(const char *path) {
EspFsFile * file;
// A dot in the filename probably means extension
// 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;

file = tryOpenIndex_do(path, "index.htm");
if (file != NULL) return file;

file = tryOpenIndex_do(path, "index.tpl.html");
if (file != NULL) return file;

file = tryOpenIndex_do(path, "index.tpl");
if (file != NULL) return file;

return NULL; // failed to guess the right name
}

httpd_cgi_state ICACHE_FLASH_ATTR
serveStaticFile(HttpdConnData *connData, const char* filepath) {
EspFsFile *file=connData->cgiData;
int len;
char buff[1024];
char acceptEncodingBuffer[64];
char buff[FILE_CHUNK_LEN+1];
char acceptEncodingBuffer[64+1];
int isGzip;

if (connData->conn==NULL) {
//Connection aborted. Clean up.
espFsClose(file);
return HTTPD_CGI_DONE;
}

// invalid call.
if (filepath == NULL) {
error("serveStaticFile called with NULL path!");
return HTTPD_CGI_NOTFOUND;
}

//First call to this cgi.
if (file==NULL) {
if (connData->cgiArg != NULL) {
//Open a different file than provided in http request.
//Common usage: {"/", cgiEspFsHook, "/index.html"} will show content of index.html without actual redirect to that file if host root was requested
file = espFsOpen((char*)connData->cgiArg);
} else {
//Open the file so we can read it.
file = espFsOpen(connData->url);
}
//First call to this cgi. Open the file so we can read it.
file = espFsOpen(filepath);
if (file == NULL) {
// file not found

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
Expand All @@ -73,7 +129,7 @@ httpd_cgi_state 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");
}
Expand All @@ -82,9 +138,9 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) {
return HTTPD_CGI_MORE;
}

len=espFsRead(file, buff, 1024);
len=espFsRead(file, buff, FILE_CHUNK_LEN);
if (len>0) httpdSend(connData, buff, len);
if (len!=1024) {
if (len!=FILE_CHUNK_LEN) {
//We're done.
espFsClose(file);
return HTTPD_CGI_DONE;
Expand All @@ -95,23 +151,37 @@ httpd_cgi_state 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.
httpd_cgi_state ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) {
const char *filepath = (connData->cgiArg == NULL) ? connData->url : (char *)connData->cgiArg;
return serveStaticFile(connData, filepath);
}


//cgiEspFsTemplate can be used as a template.

typedef struct {
EspFsFile *file;
void *tplArg;
char token[64];
int tokenPos;
} TplData;

typedef void (* TplCallback)(HttpdConnData *connData, char *token, void **arg);
char buff[FILE_CHUNK_LEN + 1];

bool chunk_resume;
int buff_len;
int buff_x;
int buff_sp;
char *buff_e;
} TplData;

httpd_cgi_state ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) {
TplData *tpd=connData->cgiData;
int len;
int x, sp=0;
char *e=NULL;
char buff[1025];

if (connData->conn==NULL) {
//Connection aborted. Clean up.
Expand All @@ -124,15 +194,33 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) {
if (tpd==NULL) {
//First call to this cgi. Open the file so we can read it.
tpd=(TplData *)malloc(sizeof(TplData));
if (tpd==NULL) return HTTPD_CGI_NOTFOUND;
tpd->file=espFsOpen(connData->url);
tpd->tplArg=NULL;
tpd->tokenPos=-1;
if (tpd->file==NULL) {
espFsClose(tpd->file);
free(tpd);
if (tpd==NULL) {
error("Failed to malloc tpl struct");
return HTTPD_CGI_NOTFOUND;
}

tpd->chunk_resume = false;

const char *filepath = connData->url;
// check for custom template URL
if (connData->cgiArg2 != NULL) {
filepath = connData->cgiArg2;
dbg("Using filepath %s", filepath);
}

tpd->file = espFsOpen(filepath);

if (tpd->file == NULL) {
// maybe a folder, look for index file
tpd->file = tryOpenIndex(filepath);
if (tpd->file == NULL) {
free(tpd);
return HTTPD_CGI_NOTFOUND;
}
}

tpd->tplArg=NULL;
tpd->tokenPos=-1;
if (espFsFlags(tpd->file) & FLAG_GZIP) {
error("cgiEspFsTemplate: Trying to use gzip-compressed file %s as template!", connData->url);
espFsClose(tpd->file);
Expand All @@ -146,11 +234,27 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) {
return HTTPD_CGI_MORE;
}

len=espFsRead(tpd->file, buff, 1024);
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) {
//dbg("Resuming tpl parser for multi-part subst");
len = tpd->buff_len;
e = tpd->buff_e;
sp = tpd->buff_sp;
x = tpd->buff_x;
} else {
len = espFsRead(tpd->file, buff, FILE_CHUNK_LEN);
tpd->buff_len = len;

e = buff;
sp = 0;
x = 0;
}

if (len>0) {
sp=0;
e=buff;
for (x=0; x<len; x++) {
for (; x<len; x++) {
if (tpd->tokenPos==-1) {
//Inside ordinary text.
if (buff[x]=='%') {
Expand All @@ -169,9 +273,24 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) {
//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);
if (!tpd->chunk_resume) {
//This is an actual token.
tpd->token[tpd->tokenPos++] = 0; //zero-terminate token
}

tpd->chunk_resume = false;

httpd_cgi_state status = ((TplCallback)(connData->cgiArg))(connData, tpd->token, &tpd->tplArg);
if (status == HTTPD_CGI_MORE) {
// dbg("Multi-part tpl subst, saving parser state");
// 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];
Expand All @@ -182,11 +301,17 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) {
}
}
}

if (tpd->chunk_resume) {
return HTTPD_CGI_MORE;
}

//Send remaining bit.
if (sp!=0) httpdSend(connData, e, sp);
if (len!=1024) {
if (len!=FILE_CHUNK_LEN) {
//We're done.
((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg);
info("Template sent.");
espFsClose(tpd->file);
free(tpd);
return HTTPD_CGI_DONE;
Expand Down
5 changes: 3 additions & 2 deletions include/cgiwebsocket.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef CGIWEBSOCKET_H
#define CGIWEBSOCKET_H

#include <esp8266.h>
#include "httpd.h"

#define WEBSOCK_FLAG_NONE 0
Expand Down Expand Up @@ -28,10 +29,10 @@ struct Websock {
};

httpd_cgi_state ICACHE_FLASH_ATTR cgiWebsocket(HttpdConnData *connData);
httpd_cgi_state ICACHE_FLASH_ATTR cgiWebsocketSend(Websock *ws, char *data, int len, int flags);
int ICACHE_FLASH_ATTR cgiWebsocketSend(Websock *ws, char *data, int len, int flags);
void ICACHE_FLASH_ATTR cgiWebsocketClose(Websock *ws, int reason);
httpd_cgi_state ICACHE_FLASH_ATTR cgiWebSocketRecv(HttpdConnData *connData, char *data, int len);
httpd_cgi_state ICACHE_FLASH_ATTR cgiWebsockBroadcast(char *resource, char *data, int len, int flags);
int ICACHE_FLASH_ATTR cgiWebsockBroadcast(char *resource, char *data, int len, int flags);


#endif
Loading

0 comments on commit cc6e7da

Please sign in to comment.