Added files

This commit is contained in:
2016-03-10 23:49:47 +01:00
commit adea64ef9c
1492 changed files with 13182 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
/*
HTTP auth implementation. Only does basic authentication for now.
*/
/*
* ----------------------------------------------------------------------------
* "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.
* ----------------------------------------------------------------------------
*/
#include <esp8266.h>
#include "auth.h"
#include "base64.h"
int ICACHE_FLASH_ATTR authBasic(HttpdConnData *connData) {
const char *forbidden="401 Forbidden.";
int no=0;
int r;
char hdr[(AUTH_MAX_USER_LEN+AUTH_MAX_PASS_LEN+2)*10];
char userpass[AUTH_MAX_USER_LEN+AUTH_MAX_PASS_LEN+2];
char user[AUTH_MAX_USER_LEN];
char pass[AUTH_MAX_PASS_LEN];
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
r=httpdGetHeader(connData, "Authorization", hdr, sizeof(hdr));
if (r && strncmp(hdr, "Basic", 5)==0) {
r=base64_decode(strlen(hdr)-6, hdr+6, sizeof(userpass), (unsigned char *)userpass);
if (r<0) r=0; //just clean out string on decode error
userpass[r]=0; //zero-terminate user:pass string
// printf("Auth: %s\n", userpass);
while (((AuthGetUserPw)(connData->cgiArg))(connData, no,
user, AUTH_MAX_USER_LEN, pass, AUTH_MAX_PASS_LEN)) {
//Check user/pass against auth header
if (strlen(userpass)==strlen(user)+strlen(pass)+1 &&
strncmp(userpass, user, strlen(user))==0 &&
userpass[strlen(user)]==':' &&
strcmp(userpass+strlen(user)+1, pass)==0) {
//Authenticated. Yay!
return HTTPD_CGI_AUTHENTICATED;
}
no++; //Not authenticated with this user/pass. Check next user/pass combo.
}
}
//Not authenticated. Go bug user with login screen.
httpdStartResponse(connData, 401);
httpdHeader(connData, "Content-Type", "text/plain");
httpdHeader(connData, "WWW-Authenticate", "Basic realm=\""HTTP_AUTH_REALM"\"");
httpdEndHeaders(connData);
httpdSend(connData, forbidden, -1);
//Okay, all done.
return HTTPD_CGI_DONE;
}
+109
View File
@@ -0,0 +1,109 @@
/* base64.c : base-64 / MIME encode/decode */
/* PUBLIC DOMAIN - Jon Mayo - November 13, 2003 */
#include <esp8266.h>
#include "base64.h"
static const int base64dec_tab[256] ICACHE_RODATA_ATTR={
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255,
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
};
#if 0
static int ICACHE_FLASH_ATTR base64decode(const char in[4], char out[3]) {
uint8_t v[4];
v[0]=base64dec_tab[(unsigned)in[0]];
v[1]=base64dec_tab[(unsigned)in[1]];
v[2]=base64dec_tab[(unsigned)in[2]];
v[3]=base64dec_tab[(unsigned)in[3]];
out[0]=(v[0]<<2)|(v[1]>>4);
out[1]=(v[1]<<4)|(v[2]>>2);
out[2]=(v[2]<<6)|(v[3]);
return (v[0]|v[1]|v[2]|v[3])!=255 ? in[3]=='=' ? in[2]=='=' ? 1 : 2 : 3 : 0;
}
#endif
/* decode a base64 string in one shot */
int ICACHE_FLASH_ATTR base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out) {
unsigned int ii, io;
uint32_t v;
unsigned int rem;
for(io=0,ii=0,v=0,rem=0;ii<in_len;ii++) {
unsigned char ch;
if(isspace((int)in[ii])) continue;
if(in[ii]=='=') break; /* stop at = */
ch=base64dec_tab[(unsigned int)in[ii]];
if(ch==255) break; /* stop at a parse error */
v=(v<<6)|ch;
rem+=6;
if(rem>=8) {
rem-=8;
if(io>=out_len) return -1; /* truncation is failure */
out[io++]=(v>>rem)&255;
}
}
if(rem>=8) {
rem-=8;
if(io>=out_len) return -1; /* truncation is failure */
out[io++]=(v>>rem)&255;
}
return io;
}
static const uint8_t base64enc_tab[64]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#if 0
void base64encode(const unsigned char in[3], unsigned char out[4], int count) {
out[0]=base64enc_tab[(in[0]>>2)];
out[1]=base64enc_tab[((in[0]&3)<<4)|(in[1]>>4)];
out[2]=count<2 ? '=' : base64enc_tab[((in[1]&15)<<2)|(in[2]>>6)];
out[3]=count<3 ? '=' : base64enc_tab[(in[2]&63)];
}
#endif
int ICACHE_FLASH_ATTR base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out) {
unsigned ii, io;
uint32_t v;
unsigned rem;
for(io=0,ii=0,v=0,rem=0;ii<in_len;ii++) {
unsigned char ch;
ch=in[ii];
v=(v<<8)|ch;
rem+=8;
while(rem>=6) {
rem-=6;
if(io>=out_len) return -1; /* truncation is failure */
out[io++]=base64enc_tab[(v>>rem)&63];
}
}
if(rem) {
v<<=(6-rem);
if(io>=out_len) return -1; /* truncation is failure */
out[io++]=base64enc_tab[v&63];
}
while(io&3) {
if(io>=out_len) return -1; /* truncation is failure */
out[io++]='=';
}
if(io>=out_len) return -1; /* no room for null terminator */
out[io]=0;
return io;
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef BASE64_H
#define BASE64_H
int base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out);
int base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out);
#endif
+243
View File
@@ -0,0 +1,243 @@
/*
ESP8266 web server - platform-dependent routines, FreeRTOS version
Thanks to my collague at Espressif for writing the foundations of this code.
*/
#ifdef FREERTOS
#include <esp8266.h>
#include "httpd.h"
#include "platform.h"
#include "httpd-platform.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "lwip/lwip/sockets.h"
static int httpPort;
static int httpMaxConnCt;
struct RtosConnType{
int fd;
int needWriteDoneNotif;
int needsClose;
int port;
char ip[4];
};
static RtosConnType rconn[HTTPD_MAX_CONNECTIONS];
int ICACHE_FLASH_ATTR httpdPlatSendData(ConnTypePtr conn, char *buff, int len) {
conn->needWriteDoneNotif=1;
return (write(conn->fd, buff, len)>=0);
}
void ICACHE_FLASH_ATTR httpdPlatDisconnect(ConnTypePtr conn) {
conn->needsClose=1;
conn->needWriteDoneNotif=1; //because the real close is done in the writable select code
}
void httpdPlatDisableTimeout(ConnTypePtr conn) {
//Unimplemented for FreeRTOS
}
#define RECV_BUF_SIZE 2048
static void platHttpServerTask(void *pvParameters) {
int32 listenfd;
int32 remotefd;
int32 len;
int32 ret;
int x;
int maxfdp = 0;
char *precvbuf;
fd_set readset,writeset;
struct sockaddr name;
//struct timeval timeout;
struct sockaddr_in server_addr;
struct sockaddr_in remote_addr;
for (x=0; x<HTTPD_MAX_CONNECTIONS; x++) {
rconn[x].fd=-1;
}
/* Construct local address structure */
memset(&server_addr, 0, sizeof(server_addr)); /* Zero out structure */
server_addr.sin_family = AF_INET; /* Internet address family */
server_addr.sin_addr.s_addr = INADDR_ANY; /* Any incoming interface */
server_addr.sin_len = sizeof(server_addr);
server_addr.sin_port = htons(httpPort); /* Local port */
/* Create socket for incoming connections */
do{
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1) {
httpd_printf("platHttpServerTask: failed to create sock!\n");
vTaskDelay(1000/portTICK_RATE_MS);
}
} while(listenfd == -1);
/* Bind to the local port */
do{
ret = bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret != 0) {
httpd_printf("platHttpServerTask: failed to bind!\n");
vTaskDelay(1000/portTICK_RATE_MS);
}
} while(ret != 0);
do{
/* Listen to the local connection */
ret = listen(listenfd, HTTPD_MAX_CONNECTIONS);
if (ret != 0) {
httpd_printf("platHttpServerTask: failed to listen!\n");
vTaskDelay(1000/portTICK_RATE_MS);
}
} while(ret != 0);
httpd_printf("esphttpd: active and listening to connections.\n");
while(1){
// clear fdset, and set the select function wait time
int socketsFull=1;
maxfdp = 0;
FD_ZERO(&readset);
FD_ZERO(&writeset);
//timeout.tv_sec = 2;
//timeout.tv_usec = 0;
for(x=0; x<HTTPD_MAX_CONNECTIONS; x++){
if (rconn[x].fd!=-1) {
FD_SET(rconn[x].fd, &readset);
if (rconn[x].needWriteDoneNotif) FD_SET(rconn[x].fd, &writeset);
if (rconn[x].fd>maxfdp) maxfdp=rconn[x].fd;
} else {
socketsFull=0;
}
}
if (!socketsFull) {
FD_SET(listenfd, &readset);
if (listenfd>maxfdp) maxfdp=listenfd;
}
//polling all exist client handle,wait until readable/writable
ret = select(maxfdp+1, &readset, &writeset, NULL, NULL);//&timeout
if(ret > 0){
//See if we need to accept a new connection
if (FD_ISSET(listenfd, &readset)) {
len=sizeof(struct sockaddr_in);
remotefd = accept(listenfd, (struct sockaddr *)&remote_addr, (socklen_t *)&len);
if (remotefd<0) {
httpd_printf("platHttpServerTask: Huh? Accept failed.\n");
continue;
}
for(x=0; x<HTTPD_MAX_CONNECTIONS; x++) if (rconn[x].fd==-1) break;
if (x==HTTPD_MAX_CONNECTIONS) {
httpd_printf("platHttpServerTask: Huh? Got accept with all slots full.\n");
continue;
}
int keepAlive = 1; //enable keepalive
int keepIdle = 60; //60s
int keepInterval = 5; //5s
int keepCount = 3; //retry times
setsockopt(remotefd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
setsockopt(remotefd, IPPROTO_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));
setsockopt(remotefd, IPPROTO_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
setsockopt(remotefd, IPPROTO_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
rconn[x].fd=remotefd;
rconn[x].needWriteDoneNotif=0;
rconn[x].needsClose=0;
len=sizeof(name);
getpeername(remotefd, &name, (socklen_t *)&len);
struct sockaddr_in *piname=(struct sockaddr_in *)&name;
rconn[x].port=piname->sin_port;
memcpy(&rconn[x].ip, &piname->sin_addr.s_addr, sizeof(rconn[x].ip));
httpdConnectCb(&rconn[x], rconn[x].ip, rconn[x].port);
//os_timer_disarm(&connData[x].conn->stop_watch);
//os_timer_setfn(&connData[x].conn->stop_watch, (os_timer_func_t *)httpserver_conn_watcher, connData[x].conn);
//os_timer_arm(&connData[x].conn->stop_watch, STOP_TIMER, 0);
// httpd_printf("httpserver acpt index %d sockfd %d!\n", x, remotefd);
}
//See if anything happened on the existing connections.
for(x=0; x < HTTPD_MAX_CONNECTIONS; x++){
//Skip empty slots
if (rconn[x].fd==-1) continue;
//Check for write availability first: the read routines may write needWriteDoneNotif while
//the select didn't check for that.
if (rconn[x].needWriteDoneNotif && FD_ISSET(rconn[x].fd, &writeset)) {
rconn[x].needWriteDoneNotif=0; //Do this first, httpdSentCb may write something making this 1 again.
if (rconn[x].needsClose) {
//Do callback and close fd.
httpdDisconCb(&rconn[x], rconn[x].ip, rconn[x].port);
close(rconn[x].fd);
rconn[x].fd=-1;
} else {
httpdSentCb(&rconn[x], rconn[x].ip, rconn[x].port);
}
}
if (FD_ISSET(rconn[x].fd, &readset)) {
precvbuf=(char*)malloc(RECV_BUF_SIZE);
if (precvbuf==NULL) httpd_printf("platHttpServerTask: memory exhausted!\n");
ret=recv(rconn[x].fd, precvbuf, RECV_BUF_SIZE,0);
if (ret > 0) {
//Data received. Pass to httpd.
httpdRecvCb(&rconn[x], rconn[x].ip, rconn[x].port, precvbuf, ret);
} else {
//recv error,connection close
httpdDisconCb(&rconn[x], rconn[x].ip, rconn[x].port);
close(rconn[x].fd);
rconn[x].fd=-1;
}
if (precvbuf) free(precvbuf);
}
}
}
}
#if 0
//Deinit code, not used here.
/*release data connection*/
for(x=0; x < HTTPD_MAX_CONNECTIONS; x++){
//find all valid handle
if(connData[x].conn == NULL) continue;
if(connData[x].conn->sockfd >= 0){
os_timer_disarm((os_timer_t *)&connData[x].conn->stop_watch);
close(connData[x].conn->sockfd);
connData[x].conn->sockfd = -1;
connData[x].conn = NULL;
if(connData[x].cgi!=NULL) connData[x].cgi(&connData[x]); //flush cgi data
httpdRetireConn(&connData[x]);
}
}
/*release listen socket*/
close(listenfd);
vTaskDelete(NULL);
#endif
}
//Initialize listening socket, do general initialization
void ICACHE_FLASH_ATTR httpdPlatInit(int port, int maxConnCt) {
httpPort=port;
httpMaxConnCt=maxConnCt;
xTaskCreate(platHttpServerTask, (const signed char *)"esphttpd", HTTPD_STACKSIZE, NULL, 4, NULL);
}
#endif
+76
View File
@@ -0,0 +1,76 @@
/*
ESP8266 web server - platform-dependent routines, nonos version
*/
#include <esp8266.h>
#include "httpd.h"
#include "platform.h"
#include "httpd-platform.h"
#ifndef FREERTOS
//Listening connection data
static struct espconn httpdConn;
static esp_tcp httpdTcp;
static void ICACHE_FLASH_ATTR platReconCb(void *arg, sint8 err) {
//Yeah, this is pretty useless...
}
static void ICACHE_FLASH_ATTR platDisconCb(void *arg) {
ConnTypePtr conn=arg;
httpdDisconCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port);
}
static void ICACHE_FLASH_ATTR platRecvCb(void *arg, char *data, unsigned short len) {
ConnTypePtr conn=arg;
httpdRecvCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port, data, len);
}
static void ICACHE_FLASH_ATTR platSentCb(void *arg) {
ConnTypePtr conn=arg;
httpdSentCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port);
}
static void ICACHE_FLASH_ATTR platConnCb(void *arg) {
ConnTypePtr conn=arg;
if (httpdConnectCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port)) {
espconn_regist_recvcb(conn, platRecvCb);
espconn_regist_reconcb(conn, platReconCb);
espconn_regist_disconcb(conn, platDisconCb);
espconn_regist_sentcb(conn, platSentCb);
} else {
espconn_disconnect(conn);
}
}
int ICACHE_FLASH_ATTR httpdPlatSendData(ConnTypePtr conn, char *buff, int len) {
int r;
r=espconn_sent(conn, (uint8_t*)buff, len);
return (r>=0);
}
void ICACHE_FLASH_ATTR httpdPlatDisconnect(ConnTypePtr conn) {
espconn_disconnect(conn);
}
void ICACHE_FLASH_ATTR httpdPlatDisableTimeout(ConnTypePtr conn) {
//Can't disable timeout; set to 2 hours instead.
espconn_regist_time(conn, 7199, 1);
}
//Initialize listening socket, do general initialization
void ICACHE_FLASH_ATTR httpdPlatInit(int port, int maxConnCt) {
httpdConn.type=ESPCONN_TCP;
httpdConn.state=ESPCONN_NONE;
httpdTcp.local_port=port;
httpdConn.proto.tcp=&httpdTcp;
espconn_regist_connectcb(&httpdConn, platConnCb);
espconn_accept(&httpdConn);
espconn_tcp_set_max_con_allow(&httpdConn, maxConnCt);
}
#endif
+9
View File
@@ -0,0 +1,9 @@
#ifndef HTTPD_PLATFORM_H
#define HTTPD_PLATFORM_H
int httpdPlatSendData(ConnTypePtr conn, char *buff, int len);
void httpdPlatDisconnect(ConnTypePtr conn);
void httpdPlatDisableTimeout(ConnTypePtr conn);
void httpdPlatInit(int port, int maxConnCt);
#endif
+775
View File
@@ -0,0 +1,775 @@
/*
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.
* ----------------------------------------------------------------------------
*/
#include <esp8266.h>
#include "httpd.h"
#include "httpd-platform.h"
//Max length of request head. This is statically allocated for each connection.
#define MAX_HEAD_LEN 1024
//Max post buffer len. This is dynamically malloc'ed if needed.
#define MAX_POST 1024
//Max send buffer len. This is allocated on the stack.
#define MAX_SENDBUFF_LEN 2048
//If some data can't be sent because the underlaying socket doesn't accept the data (like the nonos
//layer is prone to do), we put it in a backlog that is dynamically malloc'ed. This defines the max
//size of the backlog.
#define MAX_BACKLOG_SIZE (4*1024)
//This gets set at init time.
static HttpdBuiltInUrl *builtInUrls;
typedef struct HttpSendBacklogItem HttpSendBacklogItem;
struct HttpSendBacklogItem {
int len;
HttpSendBacklogItem *next;
char data[];
};
//Flags
#define HFL_HTTP11 (1<<0)
#define HFL_CHUNKED (1<<1)
#define HFL_SENDINGBODY (1<<2)
#define HFL_DISCONAFTERSENT (1<<3)
//Private data for http connection
struct HttpdPriv {
char head[MAX_HEAD_LEN];
int headPos;
char *sendBuff;
int sendBuffLen;
char *chunkHdr;
HttpSendBacklogItem *sendBacklog;
int sendBacklogSize;
int flags;
};
//Connection pool
static HttpdConnData *connData[HTTPD_MAX_CONNECTIONS];
//Struct to keep extension->mime data in
typedef struct {
const char *ext;
const char *mimetype;
} MimeMap;
//#define RSTR(a) ((const char)(a))
//The mappings from file extensions to mime types. If you need an extra mime type,
//add it here.
static const ICACHE_RODATA_ATTR MimeMap mimeTypes[]={
{"htm", "text/htm"},
{"html", "text/html"},
{"css", "text/css"},
{"js", "text/javascript"},
{"txt", "text/plain"},
{"jpg", "image/jpeg"},
{"jpeg", "image/jpeg"},
{"png", "image/png"},
{"svg", "image/svg+xml"},
{NULL, "text/html"}, //default value
};
//Returns a static char* to a mime type for a given url to a file.
const char ICACHE_FLASH_ATTR *httpdGetMimetype(char *url) {
int i=0;
//Go find the extension
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;
}
//Looks up the connData info for a specific connection
static HttpdConnData ICACHE_FLASH_ATTR *httpdFindConnData(ConnTypePtr conn, char *remIp, int remPort) {
for (int i=0; i<HTTPD_MAX_CONNECTIONS; i++) {
if (connData[i] && connData[i]->remote_port == remPort &&
memcmp(connData[i]->remote_ip, remIp, 4) == 0) {
connData[i]->conn=conn;
return connData[i];
}
}
//Shouldn't happen.
httpd_printf("*** Unknown connection %d.%d.%d.%d:%d\n", remIp[0]&0xff, remIp[1]&0xff, remIp[2]&0xff, remIp[3]&0xff, remPort);
httpdPlatDisconnect(conn);
return NULL;
}
//Retires a connection for re-use
static void ICACHE_FLASH_ATTR httpdRetireConn(HttpdConnData *conn) {
if (conn->priv->sendBacklog!=NULL) {
HttpSendBacklogItem *i, *j;
i=conn->priv->sendBacklog;
do {
j=i;
i=i->next;
free(j);
} while (i!=NULL);
}
if (conn->post->buff!=NULL) free(conn->post->buff);
if (conn->post!=NULL) free(conn->post);
if (conn->priv!=NULL) free(conn->priv);
if (conn) free(conn);
for (int i=0; i<HTTPD_MAX_CONNECTIONS; i++) {
if (connData[i]==conn) connData[i]=NULL;
}
}
//Stupid li'l helper function that returns the value of a hex char.
static int ICACHE_FLASH_ATTR httpdHexVal(char c) {
if (c>='0' && c<='9') return c-'0';
if (c>='A' && c<='F') return c-'A'+10;
if (c>='a' && c<='f') return c-'a'+10;
return 0;
}
//Decode a percent-encoded value.
//Takes the valLen bytes stored in val, and converts it into at most retLen bytes that
//are stored in the ret buffer. Returns the actual amount of bytes used in ret. Also
//zero-terminates the ret buffer.
int ICACHE_FLASH_ATTR httpdUrlDecode(char *val, int valLen, char *ret, int retLen) {
int s=0, d=0;
int esced=0, escVal=0;
while (s<valLen && d<retLen) {
if (esced==1) {
escVal=httpdHexVal(val[s])<<4;
esced=2;
} else if (esced==2) {
escVal+=httpdHexVal(val[s]);
ret[d++]=escVal;
esced=0;
} else if (val[s]=='%') {
esced=1;
} else if (val[s]=='+') {
ret[d++]=' ';
} else {
ret[d++]=val[s];
}
s++;
}
if (d<retLen) ret[d]=0;
return d;
}
//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
//returned string will be urldecoded already.
int ICACHE_FLASH_ATTR httpdFindArg(char *line, char *arg, char *buff, int buffLen) {
char *p, *e;
if (line==NULL) return -1;
p=line;
while(p!=NULL && *p!='\n' && *p!='\r' && *p!=0) {
// httpd_printf("findArg: %s\n", p);
if (strncmp(p, arg, strlen(arg))==0 && p[strlen(arg)]=='=') {
p+=strlen(arg)+1; //move p to start of value
e=(char*)strstr(p, "&");
if (e==NULL) e=p+strlen(p);
// httpd_printf("findArg: val %s len %d\n", p, (e-p));
return httpdUrlDecode(p, (e-p), buff, buffLen);
}
p=(char*)strstr(p, "&");
if (p!=NULL) p+=1;
}
httpd_printf("Finding %s in %s: Not found :/\n", arg, line);
return -1; //not found
}
//Get the value of a certain header in the HTTP client head
//Returns true when found, false when not found.
int ICACHE_FLASH_ATTR httpdGetHeader(HttpdConnData *conn, char *header, char *ret, int retLen) {
char *p=conn->priv->head;
p=p+strlen(p)+1; //skip GET/POST part
p=p+strlen(p)+1; //skip HTTP part
while (p<(conn->priv->head+conn->priv->headPos)) {
while(*p<=32 && *p!=0) p++; //skip crap at start
//See if this is the header
if (strncmp(p, header, strlen(header))==0 && p[strlen(header)]==':') {
//Skip 'key:' bit of header line
p=p+strlen(header)+1;
//Skip past spaces after the colon
while(*p==' ') p++;
//Copy from p to end
while (*p!=0 && *p!='\r' && *p!='\n' && retLen>1) {
*ret++=*p++;
retLen--;
}
//Zero-terminate string
*ret=0;
//All done :)
return 1;
}
p+=strlen(p)+1; //Skip past end of string and \0 terminator
}
return 0;
}
//Call before calling httpdStartResponse to disable automatically-chosen transfer
//encodings (specifically, for now, chunking) and fall back on Connection: Close.
void ICACHE_FLASH_ATTR httpdDisableTransferEncoding(HttpdConnData *conn) {
conn->priv->flags&=~HFL_CHUNKED;
}
//Start the response headers.
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,
(conn->priv->flags&HFL_CHUNKED)?"Transfer-Encoding: chunked":"Connection: close");
httpdSend(conn, buff, l);
}
//Send a http header.
void ICACHE_FLASH_ATTR httpdHeader(HttpdConnData *conn, const char *field, const char *val) {
httpdSend(conn, field, -1);
httpdSend(conn, ": ", -1);
httpdSend(conn, val, -1);
httpdSend(conn, "\r\n", -1);
}
//Finish the headers.
void ICACHE_FLASH_ATTR httpdEndHeaders(HttpdConnData *conn) {
httpdSend(conn, "\r\n", -1);
conn->priv->flags|=HFL_SENDINGBODY;
}
//Redirect to the given URL.
void ICACHE_FLASH_ATTR httpdRedirect(HttpdConnData *conn, char *newUrl) {
httpdStartResponse(conn, 302);
httpdHeader(conn, "Location", newUrl);
httpdEndHeaders(conn);
httpdSend(conn, "Moved to ", -1);
httpdSend(conn, newUrl, -1);
}
//Use this as a cgi function to redirect one url to another.
int ICACHE_FLASH_ATTR cgiRedirect(HttpdConnData *connData) {
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
httpdRedirect(connData, (char*)connData->cgiArg);
return HTTPD_CGI_DONE;
}
//Used to spit out a 404 error
static int ICACHE_FLASH_ATTR cgiNotFound(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE;
httpdStartResponse(connData, 404);
httpdEndHeaders(connData);
httpdSend(connData, "404 File not found.", -1);
return HTTPD_CGI_DONE;
}
//This CGI function redirects to a fixed url of http://[hostname]/ if hostname field of request isn't
//already that hostname. Use this in combination with a DNS server that redirects everything to the
//ESP in order to load a HTML page as soon as a phone, tablet etc connects to the ESP. Watch out:
//this will also redirect connections when the ESP is in STA mode, potentially to a hostname that is not
//in the 'official' DNS and so will fail.
int ICACHE_FLASH_ATTR cgiRedirectToHostname(HttpdConnData *connData) {
static const char hostFmt[]="http://%s/";
char *buff;
int isIP=0;
int x;
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
if (connData->hostName==NULL) {
httpd_printf("Huh? No hostname.\n");
return HTTPD_CGI_NOTFOUND;
}
//Quick and dirty code to see if host is an IP
if (strlen(connData->hostName)>8) {
isIP=1;
for (x=0; x<strlen(connData->hostName); x++) {
if (connData->hostName[x]!='.' && (connData->hostName[x]<'0' || connData->hostName[x]>'9')) isIP=0;
}
}
if (isIP) return HTTPD_CGI_NOTFOUND;
//Check hostname; pass on if the same
if (strcmp(connData->hostName, (char*)connData->cgiArg)==0) return HTTPD_CGI_NOTFOUND;
//Not the same. Redirect to real hostname.
buff=malloc(strlen((char*)connData->cgiArg)+sizeof(hostFmt));
sprintf(buff, hostFmt, (char*)connData->cgiArg);
httpd_printf("Redirecting to hostname url %s\n", buff);
httpdRedirect(connData, buff);
free(buff);
return HTTPD_CGI_DONE;
}
//Same as above, but will only redirect clients with an IP that is in the range of
//the SoftAP interface. This should preclude clients connected to the STA interface
//to be redirected to nowhere.
int ICACHE_FLASH_ATTR cgiRedirectApClientToHostname(HttpdConnData *connData) {
#ifndef FREERTOS
uint32 *remadr;
struct ip_info apip;
int x=wifi_get_opmode();
//Check if we have an softap interface; bail out if not
if (x!=2 && x!=3) return HTTPD_CGI_NOTFOUND;
remadr=(uint32 *)connData->remote_ip;
wifi_get_ip_info(SOFTAP_IF, &apip);
if ((*remadr & apip.netmask.addr) == (apip.ip.addr & apip.netmask.addr)) {
return cgiRedirectToHostname(connData);
} else {
return HTTPD_CGI_NOTFOUND;
}
#else
return HTTPD_CGI_NOTFOUND;
#endif
}
//Add data to the send buffer. len is the length of the data. If len is -1
//the data is seen as a C-string.
//Returns 1 for success, 0 for out-of-memory.
int ICACHE_FLASH_ATTR httpdSend(HttpdConnData *conn, const char *data, int len) {
if (conn->conn==NULL) return 0;
if (len<0) len=strlen(data);
if (len==0) return 0;
if (conn->priv->flags&HFL_CHUNKED && conn->priv->flags&HFL_SENDINGBODY && conn->priv->chunkHdr==NULL) {
if (conn->priv->sendBuffLen+len+6>MAX_SENDBUFF_LEN) return 0;
//Establish start of chunk
conn->priv->chunkHdr=&conn->priv->sendBuff[conn->priv->sendBuffLen];
strcpy(conn->priv->chunkHdr, "0000\r\n");
conn->priv->sendBuffLen+=6;
}
if (conn->priv->sendBuffLen+len>MAX_SENDBUFF_LEN) return 0;
memcpy(conn->priv->sendBuff+conn->priv->sendBuffLen, data, len);
conn->priv->sendBuffLen+=len;
return 1;
}
static char ICACHE_FLASH_ATTR httpdHexNibble(int val) {
val&=0xf;
if (val<10) return '0'+val;
return 'A'+(val-10);
}
//Function to send any data in conn->priv->sendBuff. Do not use in CGIs unless you know what you
//are doing! Also, if you do set conn->cgi to NULL to indicate the connection is closed, do it BEFORE
//calling this.
void ICACHE_FLASH_ATTR httpdFlushSendBuffer(HttpdConnData *conn) {
int r, len;
if (conn->conn==NULL) return;
if (conn->priv->chunkHdr!=NULL) {
//We're sending chunked data, and the chunk needs fixing up.
//Finish chunk with cr/lf
httpdSend(conn, "\r\n", 2);
//Calculate length of chunk
len=((&conn->priv->sendBuff[conn->priv->sendBuffLen])-conn->priv->chunkHdr)-8;
//Fix up chunk header to correct value
conn->priv->chunkHdr[0]=httpdHexNibble(len>>12);
conn->priv->chunkHdr[1]=httpdHexNibble(len>>8);
conn->priv->chunkHdr[2]=httpdHexNibble(len>>4);
conn->priv->chunkHdr[3]=httpdHexNibble(len>>0);
//Reset chunk hdr for next call
conn->priv->chunkHdr=NULL;
}
if (conn->priv->flags&HFL_CHUNKED && conn->priv->flags&HFL_SENDINGBODY && conn->cgi==NULL) {
//Connection finished sending whatever needs to be sent. Add NULL chunk to indicate this.
strcpy(&conn->priv->sendBuff[conn->priv->sendBuffLen], "0\r\n\r\n");
conn->priv->sendBuffLen+=5;
}
if (conn->priv->sendBuffLen!=0) {
r=httpdPlatSendData(conn->conn, conn->priv->sendBuff, conn->priv->sendBuffLen);
if (!r) {
//Can't send this for some reason. Dump packet in backlog, we can send it later.
if (conn->priv->sendBacklogSize+conn->priv->sendBuffLen>MAX_BACKLOG_SIZE) {
httpd_printf("Httpd: Backlog: Exceeded max backlog size, dropped %d bytes instead of sending them.\n", conn->priv->sendBuffLen);
conn->priv->sendBuffLen=0;
return;
}
HttpSendBacklogItem *i=malloc(sizeof(HttpSendBacklogItem)+conn->priv->sendBuffLen);
if (i==NULL) {
httpd_printf("Httpd: Backlog: malloc failed, out of memory!\n");
return;
}
memcpy(i->data, conn->priv->sendBuff, conn->priv->sendBuffLen);
i->len=conn->priv->sendBuffLen;
i->next=NULL;
if (conn->priv->sendBacklog==NULL) {
conn->priv->sendBacklog=i;
} else {
HttpSendBacklogItem *e=conn->priv->sendBacklog;
while (e->next!=NULL) e=e->next;
e->next=i;
}
conn->priv->sendBacklogSize+=conn->priv->sendBuffLen;
}
conn->priv->sendBuffLen=0;
}
}
void ICACHE_FLASH_ATTR httpdCgiIsDone(HttpdConnData *conn) {
conn->cgi=NULL; //no need to call this anymore
if (conn->priv->flags&HFL_CHUNKED) {
httpd_printf("Pool slot %d is done. Cleaning up for next req\n", conn->slot);
httpdFlushSendBuffer(conn);
//Note: Do not clean up sendBacklog, it may still contain data at this point.
conn->priv->headPos=0;
conn->post->len=-1;
conn->priv->flags=0;
if (conn->post->buff) free(conn->post->buff);
conn->post->buff=NULL;
conn->post->buffLen=0;
conn->post->received=0;
conn->hostName=NULL;
} else {
//Cannot re-use this connection. Mark to get it killed after all data is sent.
conn->priv->flags|=HFL_DISCONAFTERSENT;
}
}
//Callback called when the data on a socket has been successfully
//sent.
void ICACHE_FLASH_ATTR httpdSentCb(ConnTypePtr rconn, char *remIp, int remPort) {
int r;
HttpdConnData *conn=httpdFindConnData(rconn, remIp, remPort);
char *sendBuff;
if (conn==NULL) return;
if (conn->priv->sendBacklog!=NULL) {
//We have some backlog to send first.
HttpSendBacklogItem *next=conn->priv->sendBacklog->next;
httpdPlatSendData(conn->conn, conn->priv->sendBacklog->data, conn->priv->sendBacklog->len);
conn->priv->sendBacklogSize-=conn->priv->sendBacklog->len;
free(conn->priv->sendBacklog);
conn->priv->sendBacklog=next;
return;
}
if (conn->priv->flags&HFL_DISCONAFTERSENT) { //Marked for destruction?
httpd_printf("Pool slot %d is done. Closing.\n", conn->slot);
httpdPlatDisconnect(conn->conn);
return; //No need to call httpdFlushSendBuffer.
}
//If we don't have a CGI function, there's nothing to do but wait for something from the client.
if (conn->cgi==NULL) return;
sendBuff=malloc(MAX_SENDBUFF_LEN);
conn->priv->sendBuff=sendBuff;
conn->priv->sendBuffLen=0;
r=conn->cgi(conn); //Execute cgi fn.
if (r==HTTPD_CGI_DONE) {
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);
httpdCgiIsDone(conn);
}
httpdFlushSendBuffer(conn);
free(sendBuff);
}
//This is called when the headers have been received and the connection is ready to send
//the result headers and data.
//We need to find the CGI function to call, call it, and dependent on what it returns either
//find the next cgi function, wait till the cgi data is sent or close up the connection.
static void ICACHE_FLASH_ATTR httpdProcessRequest(HttpdConnData *conn) {
int r;
int i=0;
if (conn->url==NULL) {
httpd_printf("WtF? url = NULL\n");
return; //Shouldn't happen
}
//See if we can find a CGI that's happy to handle the request.
while (1) {
//Look up URL in the built-in URL table.
while (builtInUrls[i].url!=NULL) {
int match=0;
//See if there's a literal match
if (strcmp(builtInUrls[i].url, conn->url)==0) match=1;
//See if there's a wildcard match
if (builtInUrls[i].url[strlen(builtInUrls[i].url)-1]=='*' &&
strncmp(builtInUrls[i].url, conn->url, strlen(builtInUrls[i].url)-1)==0) match=1;
if (match) {
httpd_printf("Is url index %d\n", i);
conn->cgiData=NULL;
conn->cgi=builtInUrls[i].cgiCb;
conn->cgiArg=builtInUrls[i].cgiArg;
break;
}
i++;
}
if (builtInUrls[i].url==NULL) {
//Drat, we're at the end of the URL table. This usually shouldn't happen. Well, just
//generate a built-in 404 to handle this.
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);
if (r==HTTPD_CGI_MORE) {
//Yep, it's happy to do so and has more data to send.
if (conn->recvHdl) {
//Seems the CGI is planning to do some long-term communications with the socket.
//Disable the timeout on it, so we won't run into that.
httpdPlatDisableTimeout(conn->conn);
}
httpdFlushSendBuffer(conn);
return;
} else if (r==HTTPD_CGI_DONE) {
//Yep, it's happy to do so and already is done sending data.
httpdCgiIsDone(conn);
return;
} else if (r==HTTPD_CGI_NOTFOUND || r==HTTPD_CGI_AUTHENTICATED) {
//URL doesn't want to handle the request: either the data isn't found or there's no
//need to generate a login screen.
i++; //look at next url the next iteration of the loop.
}
}
}
//Parse a line of header data and modify the connection data accordingly.
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;
} else if (strncmp(h, "Host:", 5)==0) {
i=5;
while (h[i]==' ') i++;
conn->hostName=&h[i];
} else if (strncmp(h, "POST ", 5)==0) {
conn->requestType = HTTPD_METHOD_POST;
firstLine=1;
}
if (firstLine) {
char *e;
//Skip past the space after POST/GET
i=0;
while (h[i]!=' ') i++;
conn->url=h+i+1;
//Figure out end of url.
e=(char*)strstr(conn->url, " ");
if (e==NULL) return; //wtf?
*e=0; //terminate url part
e++; //Skip to protocol indicator
while (*e==' ') e++; //Skip spaces.
//If HTTP/1.1, note that and set chunked encoding
if (strcasecmp(e, "HTTP/1.1")==0) conn->priv->flags|=HFL_HTTP11|HFL_CHUNKED;
httpd_printf("URL = %s\n", conn->url);
//Parse out the URL part before the GET parameters.
conn->getArgs=(char*)strstr(conn->url, "?");
if (conn->getArgs!=0) {
*conn->getArgs=0;
conn->getArgs++;
httpd_printf("GET args = %s\n", conn->getArgs);
} else {
conn->getArgs=NULL;
}
} else if (strncmp(h, "Connection:", 11)==0) {
i=11;
//Skip trailing spaces
while (h[i]==' ') i++;
if (strncmp(&h[i], "close", 5)==0) conn->priv->flags&=~HFL_CHUNKED; //Don't use chunked conn
} else if (strncmp(h, "Content-Length:", 15)==0) {
i=15;
//Skip trailing spaces
while (h[i]==' ') i++;
//Get POST data length
conn->post->len=atoi(h+i);
// Allocate the buffer
if (conn->post->len > MAX_POST) {
// we'll stream this in in chunks
conn->post->buffSize = MAX_POST;
} else {
conn->post->buffSize = conn->post->len;
}
httpd_printf("Mallocced buffer for %d + 1 bytes of post data.\n", conn->post->buffSize);
conn->post->buff=(char*)malloc(conn->post->buffSize + 1);
conn->post->buffLen=0;
} else if (strncmp(h, "Content-Type: ", 14)==0) {
if (strstr(h, "multipart/form-data")) {
// It's multipart form data so let's pull out the boundary for future use
char *b;
if ((b = strstr(h, "boundary=")) != NULL) {
conn->post->multipartBoundary = b + 7; // move the pointer 2 chars before boundary then fill them with dashes
conn->post->multipartBoundary[0] = '-';
conn->post->multipartBoundary[1] = '-';
httpd_printf("boundary = %s\n", conn->post->multipartBoundary);
}
}
}
}
//Callback called when there's data available on a socket.
void httpdRecvCb(ConnTypePtr rconn, char *remIp, int remPort, char *data, unsigned short len) {
int x, r;
char *p, *e;
char *sendBuff=malloc(MAX_SENDBUFF_LEN);
HttpdConnData *conn=httpdFindConnData(rconn, remIp, remPort);
if (conn==NULL) return;
conn->priv->sendBuff=sendBuff;
conn->priv->sendBuffLen=0;
//This is slightly evil/dirty: we abuse conn->post->len as a state variable for where in the http communications we are:
//<0 (-1): Post len unknown because we're still receiving headers
//==0: No post data
//>0: Need to receive post data
//ToDo: See if we can use something more elegant for this.
for (x=0; x<len; x++) {
if (conn->post->len<0) {
//This byte is a header byte.
if (data[x]=='\n') {
//Compatibility with clients that send \n only: fake a \r in front of this.
if (conn->priv->headPos!=0 && conn->priv->head[conn->priv->headPos-1]!='\r') {
conn->priv->head[conn->priv->headPos++]='\r';
}
}
//ToDo: return http error code 431 (request header too long) if this happens
if (conn->priv->headPos!=MAX_HEAD_LEN) conn->priv->head[conn->priv->headPos++]=data[x];
conn->priv->head[conn->priv->headPos]=0;
//Scan for /r/n/r/n. Receiving this indicate the headers end.
if (data[x]=='\n' && (char *)strstr(conn->priv->head, "\r\n\r\n")!=NULL) {
//Indicate we're done with the headers.
conn->post->len=0;
//Reset url data
conn->url=NULL;
//Iterate over all received headers and parse them.
p=conn->priv->head;
while(p<(&conn->priv->head[conn->priv->headPos-4])) {
e=(char *)strstr(p, "\r\n"); //Find end of header line
if (e==NULL) break; //Shouldn't happen.
e[0]=0; //Zero-terminate header
httpdParseHeader(p, conn); //and parse it.
p=e+2; //Skip /r/n (now /0/n)
}
//If we don't need to receive post data, we can send the response now.
if (conn->post->len==0) {
httpdProcessRequest(conn);
}
}
} else if (conn->post->len!=0) {
//This byte is a POST byte.
conn->post->buff[conn->post->buffLen++]=data[x];
conn->post->received++;
conn->hostName=NULL;
if (conn->post->buffLen >= conn->post->buffSize || conn->post->received == conn->post->len) {
//Received a chunk of post data
conn->post->buff[conn->post->buffLen]=0; //zero-terminate, in case the cgi handler knows it can use strings
//Process the data
if (conn->cgi) {
r=conn->cgi(conn);
if (r==HTTPD_CGI_DONE) {
httpdCgiIsDone(conn);
}
} else {
//No CGI fn set yet: probably first call. Allow httpdProcessRequest to choose CGI and
//call it the first time.
httpdProcessRequest(conn);
}
conn->post->buffLen = 0;
}
} else {
//Let cgi handle data if it registered a recvHdl callback. If not, ignore.
if (conn->recvHdl) {
r=conn->recvHdl(conn, data+x, len-x);
if (r==HTTPD_CGI_DONE) {
httpd_printf("Recvhdl returned DONE\n");
httpdCgiIsDone(conn);
//We assume the recvhdlr has sent something; we'll kill the sock in the sent callback.
}
break; //ignore rest of data, recvhdl has parsed it.
} else {
httpd_printf("Eh? Got unexpected data from client. %s\n", data);
}
}
}
if (conn->conn) httpdFlushSendBuffer(conn);
free(sendBuff);
}
//The platform layer should ALWAYS call this function, regardless if the connection is closed by the server
//or by the client.
void ICACHE_FLASH_ATTR httpdDisconCb(ConnTypePtr rconn, char *remIp, int remPort) {
HttpdConnData *hconn=httpdFindConnData(rconn, remIp, remPort);
if (hconn==NULL) return;
httpd_printf("Pool slot %d: socket closed.\n", hconn->slot);
hconn->conn=NULL; //indicate cgi the connection is gone
if (hconn->cgi) hconn->cgi(hconn); //Execute cgi fn if needed
httpdRetireConn(hconn);
}
int ICACHE_FLASH_ATTR httpdConnectCb(ConnTypePtr conn, char *remIp, int remPort) {
int i;
//Find empty conndata in pool
for (i=0; i<HTTPD_MAX_CONNECTIONS; i++) if (connData[i]==NULL) break;
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");
return 0;
}
connData[i]=malloc(sizeof(HttpdConnData));
memset(connData[i], 0, sizeof(HttpdConnData));
connData[i]->priv=malloc(sizeof(HttpdPriv));
memset(connData[i]->priv, 0, sizeof(HttpdPriv));
connData[i]->conn=conn;
connData[i]->slot=i;
connData[i]->priv->headPos=0;
connData[i]->post=malloc(sizeof(HttpdPostData));
memset(connData[i]->post, 0, sizeof(HttpdPostData));
connData[i]->post->buff=NULL;
connData[i]->post->buffLen=0;
connData[i]->post->received=0;
connData[i]->post->len=-1;
connData[i]->hostName=NULL;
connData[i]->remote_port=remPort;
connData[i]->priv->sendBacklog=NULL;
connData[i]->priv->sendBacklogSize=0;
memcpy(connData[i]->remote_ip, remIp, 4);
return 1;
}
//Httpd initialization routine. Call this to kick off webserver functionality.
void ICACHE_FLASH_ATTR httpdInit(HttpdBuiltInUrl *fixedUrls, int port) {
int i;
for (i=0; i<HTTPD_MAX_CONNECTIONS; i++) {
connData[i]=NULL;
}
builtInUrls=fixedUrls;
httpdPlatInit(port, HTTPD_MAX_CONNECTIONS);
httpd_printf("Httpd init\n");
}
+221
View File
@@ -0,0 +1,221 @@
/*
Connector to let httpd use the espfs filesystem to serve the files in it.
*/
/*
* ----------------------------------------------------------------------------
* "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.
* ----------------------------------------------------------------------------
*/
#include <esp8266.h>
#include "httpdespfs.h"
#include "espfs.h"
#include "espfsformat.h"
// 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";
EspFsFile *tryOpenIndex(char *buff, const char *path) {
// Try appending index.tpl
size_t url_len = strlen(path);
strcpy(buff, path); // add current path
// add slash if not already ending with slash
if (path[url_len-1] != '/') {
buff[url_len++] = '/';
}
// add index
strcpy(buff + url_len, "index.tpl");
return espFsOpen(buff);
}
//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 len;
char buff[1024];
char acceptEncodingBuffer[64];
int isGzip;
if (connData->conn==NULL) {
//Connection aborted. Clean up.
espFsClose(file);
return HTTPD_CGI_DONE;
}
if (file==NULL) {
//First call to this cgi. Open the file so we can read it.
file=espFsOpen(connData->url);
if (file==NULL) {
// file not found
file = tryOpenIndex(buff, connData->url);
if (file==NULL) {
return HTTPD_CGI_NOTFOUND;
}
}
// The gzip checking code is intentionally without #ifdefs because checking
// for FLAG_GZIP (which indicates gzip compressed file) is very easy, doesn't
// mean additional overhead and is actually safer to be on at all times.
// If there are no gzipped files in the image, the code bellow will not cause any harm.
// Check if requested file was GZIP compressed
isGzip = espFsFlags(file) & FLAG_GZIP;
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(connData, "Accept-Encoding", acceptEncodingBuffer, 64);
if (strstr(acceptEncodingBuffer, "gzip") == NULL) {
//No Accept-Encoding: gzip header present
httpdSend(connData, gzipNonSupportedMessage, -1);
espFsClose(file);
return HTTPD_CGI_DONE;
}
}
connData->cgiData=file;
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url));
if (isGzip) {
httpdHeader(connData, "Content-Encoding", "gzip");
}
httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate");
httpdEndHeaders(connData);
return HTTPD_CGI_MORE;
}
len=espFsRead(file, buff, 1024);
if (len>0) httpdSend(connData, buff, len);
if (len!=1024) {
//We're done.
espFsClose(file);
return HTTPD_CGI_DONE;
} else {
//Ok, till next time.
return HTTPD_CGI_MORE;
}
}
//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);
int 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.
((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg);
espFsClose(tpd->file);
free(tpd);
return HTTPD_CGI_DONE;
}
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) {
// file not found
tpd->file = tryOpenIndex(buff, connData->url);
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) {
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;
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) {
//Inside ordinary text.
if (buff[x]=='%') {
//Send raw data up to now
if (sp!=0) httpdSend(connData, e, sp);
sp=0;
//Go collect token chars.
tpd->tokenPos=0;
} else {
sp++;
}
} else {
if (buff[x]=='%') {
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);
}
//Go collect normal chars again.
e=&buff[x+1];
tpd->tokenPos=-1;
} else {
if (tpd->tokenPos<(sizeof(tpd->token)-1)) tpd->token[tpd->tokenPos++]=buff[x];
}
}
}
}
//Send remaining bit.
if (sp!=0) httpdSend(connData, e, sp);
if (len!=1024) {
//We're done.
((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg);
espFsClose(tpd->file);
free(tpd);
return HTTPD_CGI_DONE;
} else {
//Ok, till next time.
return HTTPD_CGI_MORE;
}
}
+168
View File
@@ -0,0 +1,168 @@
/* This code is public-domain - it is based on libcrypt
* placed in the public domain by Wei Dai and other contributors.
*/
// gcc -Wall -DSHA1TEST -o sha1test sha1.c && ./sha1test
#include <esp8266.h>
#include <stdint.h>
#include <string.h>
#include "sha1.h"
//according to http://ip.cadence.com/uploads/pdf/xtensalx_overview_handbook.pdf
// the cpu is normally defined as little ending, but can be big endian too.
// for the esp this seems to work
//#define SHA_BIG_ENDIAN
/* code */
#define SHA1_K0 0x5a827999
#define SHA1_K20 0x6ed9eba1
#define SHA1_K40 0x8f1bbcdc
#define SHA1_K60 0xca62c1d6
void ICACHE_FLASH_ATTR sha1_init(sha1nfo *s) {
s->state[0] = 0x67452301;
s->state[1] = 0xefcdab89;
s->state[2] = 0x98badcfe;
s->state[3] = 0x10325476;
s->state[4] = 0xc3d2e1f0;
s->byteCount = 0;
s->bufferOffset = 0;
}
uint32_t ICACHE_FLASH_ATTR sha1_rol32(uint32_t number, uint8_t bits) {
return ((number << bits) | (number >> (32-bits)));
}
void ICACHE_FLASH_ATTR sha1_hashBlock(sha1nfo *s) {
uint8_t i;
uint32_t a,b,c,d,e,t;
a=s->state[0];
b=s->state[1];
c=s->state[2];
d=s->state[3];
e=s->state[4];
for (i=0; i<80; i++) {
if (i>=16) {
t = s->buffer[(i+13)&15] ^ s->buffer[(i+8)&15] ^ s->buffer[(i+2)&15] ^ s->buffer[i&15];
s->buffer[i&15] = sha1_rol32(t,1);
}
if (i<20) {
t = (d ^ (b & (c ^ d))) + SHA1_K0;
} else if (i<40) {
t = (b ^ c ^ d) + SHA1_K20;
} else if (i<60) {
t = ((b & c) | (d & (b | c))) + SHA1_K40;
} else {
t = (b ^ c ^ d) + SHA1_K60;
}
t+=sha1_rol32(a,5) + e + s->buffer[i&15];
e=d;
d=c;
c=sha1_rol32(b,30);
b=a;
a=t;
}
s->state[0] += a;
s->state[1] += b;
s->state[2] += c;
s->state[3] += d;
s->state[4] += e;
}
void ICACHE_FLASH_ATTR sha1_addUncounted(sha1nfo *s, uint8_t data) {
uint8_t * const b = (uint8_t*) s->buffer;
#ifdef SHA_BIG_ENDIAN
b[s->bufferOffset] = data;
#else
b[s->bufferOffset ^ 3] = data;
#endif
s->bufferOffset++;
if (s->bufferOffset == BLOCK_LENGTH) {
sha1_hashBlock(s);
s->bufferOffset = 0;
}
}
void ICACHE_FLASH_ATTR sha1_writebyte(sha1nfo *s, uint8_t data) {
++s->byteCount;
sha1_addUncounted(s, data);
}
void ICACHE_FLASH_ATTR sha1_write(sha1nfo *s, const char *data, size_t len) {
for (;len--;) sha1_writebyte(s, (uint8_t) *data++);
}
void ICACHE_FLASH_ATTR sha1_pad(sha1nfo *s) {
// Implement SHA-1 padding (fips180-2 §5.1.1)
// Pad with 0x80 followed by 0x00 until the end of the block
sha1_addUncounted(s, 0x80);
while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00);
// Append length in the last 8 bytes
sha1_addUncounted(s, 0); // We're only using 32 bit lengths
sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths
sha1_addUncounted(s, 0); // So zero pad the top bits
sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8
sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as
sha1_addUncounted(s, s->byteCount >> 13); // byte.
sha1_addUncounted(s, s->byteCount >> 5);
sha1_addUncounted(s, s->byteCount << 3);
}
uint8_t* ICACHE_FLASH_ATTR sha1_result(sha1nfo *s) {
// Pad to complete the last block
sha1_pad(s);
#ifndef SHA_BIG_ENDIAN
// Swap byte order back
int i;
for (i=0; i<5; i++) {
s->state[i]=
(((s->state[i])<<24)& 0xff000000)
| (((s->state[i])<<8) & 0x00ff0000)
| (((s->state[i])>>8) & 0x0000ff00)
| (((s->state[i])>>24)& 0x000000ff);
}
#endif
// Return pointer to hash (20 characters)
return (uint8_t*) s->state;
}
#define HMAC_IPAD 0x36
#define HMAC_OPAD 0x5c
void ICACHE_FLASH_ATTR sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength) {
uint8_t i;
memset(s->keyBuffer, 0, BLOCK_LENGTH);
if (keyLength > BLOCK_LENGTH) {
// Hash long keys
sha1_init(s);
for (;keyLength--;) sha1_writebyte(s, *key++);
memcpy(s->keyBuffer, sha1_result(s), HASH_LENGTH);
} else {
// Block length keys are used as is
memcpy(s->keyBuffer, key, keyLength);
}
// Start inner hash
sha1_init(s);
for (i=0; i<BLOCK_LENGTH; i++) {
sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_IPAD);
}
}
uint8_t* ICACHE_FLASH_ATTR sha1_resultHmac(sha1nfo *s) {
uint8_t i;
// Complete inner hash
memcpy(s->innerHash,sha1_result(s),HASH_LENGTH);
// Calculate outer hash
sha1_init(s);
for (i=0; i<BLOCK_LENGTH; i++) sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_OPAD);
for (i=0; i<HASH_LENGTH; i++) sha1_writebyte(s, s->innerHash[i]);
return sha1_result(s);
}