Moved httpd files to separate dir

This commit is contained in:
Jindra Dolezy
2015-04-11 00:35:54 +02:00
parent c3f3cbcf9c
commit 546056695c
9 changed files with 1 additions and 1 deletions
+69
View File
@@ -0,0 +1,69 @@
/*
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 <string.h>
#include <osapi.h>
#include "user_interface.h"
#include "mem.h"
#include "httpd.h"
#include "cgi.h"
#include "auth.h"
#include "io.h"
#include "base64.h"
#include <ip_addr.h>
#include "espmissingincludes.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
// os_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 &&
os_strncmp(userpass, user, strlen(user))==0 &&
userpass[strlen(user)]==':' &&
os_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;
}
+20
View File
@@ -0,0 +1,20 @@
#ifndef AUTH_H
#define AUTH_H
#ifndef HTTP_AUTH_REALM
#define HTTP_AUTH_REALM "Protected"
#endif
#define HTTPD_AUTH_SINGLE 0
#define HTTPD_AUTH_CALLBACK 1
#define AUTH_MAX_USER_LEN 32
#define AUTH_MAX_PASS_LEN 32
//Parameter given to authWhatever functions. This callback returns the usernames/passwords the device
//has.
typedef int (* AuthGetUserPw)(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen);
int ICACHE_FLASH_ATTR authBasic(HttpdConnData *connData);
#endif
+116
View File
@@ -0,0 +1,116 @@
/* base64.c : base-64 / MIME encode/decode */
/* PUBLIC DOMAIN - Jon Mayo - November 13, 2003 */
#include "c_types.h"
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <ctype.h>
#include "base64.h"
static const uint8_t base64dec_tab[256]= {
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;
}
//Only need decode functions for now.
#if 0
static const uint8_t base64enc_tab[64]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
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)];
}
int base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out) {
unsigned ii, io;
uint_least32_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;
}
#endif
+1
View File
@@ -0,0 +1 @@
int base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out);
+589
View File
@@ -0,0 +1,589 @@
/*
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 "espmissingincludes.h"
#include "c_types.h"
#include "user_interface.h"
#include "espconn.h"
#include "mem.h"
#include "osapi.h"
#include "httpd.h"
//Max length of request head
#define MAX_HEAD_LEN 1024
//Max amount of connections
#define MAX_CONN 8
//Max post buffer len
#define MAX_POST 1024
//Max send buffer len
#define MAX_SENDBUFF_LEN 2048
//This gets set at init time.
static HttpdBuiltInUrl *builtInUrls;
//Private data for http connection
struct HttpdPriv {
char head[MAX_HEAD_LEN];
int headPos;
char *sendBuff;
int sendBuffLen;
};
//Connection pool
static HttpdPriv connPrivData[MAX_CONN];
static HttpdConnData connData[MAX_CONN];
static HttpdPostData connPostData[MAX_CONN];
//Listening connection data
static struct espconn httpdConn;
static esp_tcp httpdTcp;
//Struct to keep extension->mime data in
typedef struct {
const char *ext;
const char *mimetype;
} MimeMap;
//The mappings from file extensions to mime types. If you need an extra mime type,
//add it here.
static const 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"},
{NULL, "text/html"}, //default value
};
// The static files with the following extensions from the HTML folder will be compressed and served with GZIP compression
// Add any other file types you want compressed here (and don't forget to modify the Makefile too)
#ifdef GZIP_COMPRESSION
static const char * gzippedFileTypes[] = {
"js",
"html",
"css",
NULL
};
static const char gzipNonSupportedMessage[] = "<html><head></head><body>Your browser does not accept gzip-compressed data.</body></html>";
#endif
//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: os_strcmp is case sensitive; we may want to do case-intensive matching here...
while (mimeTypes[i].ext!=NULL && os_strcmp(ext, mimeTypes[i].ext)!=0) i++;
return mimeTypes[i].mimetype;
}
#ifdef GZIP_COMPRESSION
//Sends Content-encoding header if the requested file was GZIP compressed
//If the client does not sent the Accept-encoding, send out a static html message.
const char* sendGZIPEncodingIfNeeded(HttpdConnData *connData) {
int i=0;
char acceptEncodingBuffer[64];
//Go find the extension
char *ext=connData->url+(strlen(connData->url)-1);
while (ext!=connData->url && *ext!='.') ext--;
if (*ext=='.') ext++;
//ToDo: os_strcmp is case sensitive; we may want to do case-intensive matching here...
while (gzippedFileTypes[i]!=NULL) {
if (os_strcmp(ext, gzippedFileTypes[i])==0) {
//when serving gzipped files check the browser's "Accept-Encoding" header
//if the client does not advertises that he accepts GZIP send a warning message (telnet users for e.g.)
httpdGetHeader(connData, "Accept-Encoding", acceptEncodingBuffer, 64);
if (os_strstr(acceptEncodingBuffer, "gzip") == NULL) {
//No Accept-Encoding: gzip header present
return gzipNonSupportedMessage;
} else {
httpdHeader(connData, "Content-Encoding", "gzip");
return NULL;
}
}
i++;
}
return NULL;
}
#endif
//Looks up the connData info for a specific esp connection
static HttpdConnData ICACHE_FLASH_ATTR *httpdFindConnData(void *arg) {
int i;
for (i=0; i<MAX_CONN; i++) {
if (connData[i].conn==(struct espconn *)arg) return &connData[i];
}
//Shouldn't happen.
os_printf("FindConnData: Huh? Couldn't find connection for %p\n", arg);
return NULL;
}
//Retires a connection for re-use
static void ICACHE_FLASH_ATTR httpdRetireConn(HttpdConnData *conn) {
if (conn->post->buff!=NULL) os_free(conn->post->buff);
conn->post->buff=NULL;
conn->cgi=NULL;
conn->conn=NULL;
}
//Stupid li'l helper function that returns the value of a hex char.
static int 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 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 0;
p=line;
while(p!=NULL && *p!='\n' && *p!='\r' && *p!=0) {
os_printf("findArg: %s\n", p);
if (os_strncmp(p, arg, os_strlen(arg))==0 && p[strlen(arg)]=='=') {
p+=os_strlen(arg)+1; //move p to start of value
e=(char*)os_strstr(p, "&");
if (e==NULL) e=p+os_strlen(p);
os_printf("findArg: val %s len %d\n", p, (e-p));
return httpdUrlDecode(p, (e-p), buff, buffLen);
}
p=(char*)os_strstr(p, "&");
if (p!=NULL) p+=1;
}
os_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
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 (os_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;
}
//Start the response headers.
void ICACHE_FLASH_ATTR httpdStartResponse(HttpdConnData *conn, int code) {
char buff[128];
int l;
l=os_sprintf(buff, "HTTP/1.0 %d OK\r\nServer: esp8266-httpd/"HTTPDVER"\r\n", code);
httpdSend(conn, buff, l);
}
//Send a http header.
void ICACHE_FLASH_ATTR httpdHeader(HttpdConnData *conn, const char *field, const char *val) {
char buff[256];
int l;
l=os_sprintf(buff, "%s: %s\r\n", field, val);
httpdSend(conn, buff, l);
}
//Finish the headers.
void ICACHE_FLASH_ATTR httpdEndHeaders(HttpdConnData *conn) {
httpdSend(conn, "\r\n", -1);
}
//ToDo: sprintf->snprintf everywhere... esp doesn't have snprintf tho' :/
//Redirect to the given URL.
void ICACHE_FLASH_ATTR httpdRedirect(HttpdConnData *conn, char *newUrl) {
char buff[1024];
int l;
l=os_sprintf(buff, "HTTP/1.1 302 Found\r\nLocation: %s\r\n\r\nMoved to %s\r\n", newUrl, newUrl);
httpdSend(conn, buff, l);
}
//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;
}
//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 (len<0) len=strlen(data);
if (conn->priv->sendBuffLen+len>MAX_SENDBUFF_LEN) return 0;
os_memcpy(conn->priv->sendBuff+conn->priv->sendBuffLen, data, len);
conn->priv->sendBuffLen+=len;
return 1;
}
//Helper function to send any data in conn->priv->sendBuff
static void ICACHE_FLASH_ATTR xmitSendBuff(HttpdConnData *conn) {
if (conn->priv->sendBuffLen!=0) {
espconn_sent(conn->conn, (uint8_t*)conn->priv->sendBuff, conn->priv->sendBuffLen);
conn->priv->sendBuffLen=0;
}
}
//Callback called when the data on a socket has been successfully
//sent.
static void ICACHE_FLASH_ATTR httpdSentCb(void *arg) {
int r;
HttpdConnData *conn=httpdFindConnData(arg);
char sendBuff[MAX_SENDBUFF_LEN];
if (conn==NULL) return;
conn->priv->sendBuff=sendBuff;
conn->priv->sendBuffLen=0;
if (conn->cgi==NULL) { //Marked for destruction?
os_printf("Conn %p is done. Closing.\n", conn->conn);
espconn_disconnect(conn->conn);
httpdRetireConn(conn);
return; //No need to call xmitSendBuff.
}
r=conn->cgi(conn); //Execute cgi fn.
if (r==HTTPD_CGI_DONE) {
conn->cgi=NULL; //mark for destruction.
}
if (r==HTTPD_CGI_NOTFOUND || r==HTTPD_CGI_AUTHENTICATED) {
os_printf("ERROR! CGI fn returns code %d after sending data! Bad CGI!\n", r);
conn->cgi=NULL; //mark for destruction.
}
xmitSendBuff(conn);
}
static const char *httpNotFoundHeader="HTTP/1.0 404 Not Found\r\nServer: esp8266-httpd/0.1\r\nContent-Type: text/plain\r\nContent-Length: 12\r\n\r\nNot Found.\r\n";
//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) {
os_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 (os_strcmp(builtInUrls[i].url, conn->url)==0) match=1;
//See if there's a wildcard match
if (builtInUrls[i].url[os_strlen(builtInUrls[i].url)-1]=='*' &&
os_strncmp(builtInUrls[i].url, conn->url, os_strlen(builtInUrls[i].url)-1)==0) match=1;
if (match) {
os_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.
os_printf("%s not found. 404!\n", conn->url);
httpdSend(conn, httpNotFoundHeader, -1);
xmitSendBuff(conn);
conn->cgi=NULL; //mark for destruction
return;
}
//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.
xmitSendBuff(conn);
return;
} else if (r==HTTPD_CGI_DONE) {
//Yep, it's happy to do so and already is done sending data.
xmitSendBuff(conn);
conn->cgi=NULL; //mark conn for destruction
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 first_line = false;
if (os_strncmp(h, "GET ", 4)==0) {
conn->requestType = HTTPD_METHOD_GET;
first_line = true;
} else if (os_strncmp(h, "POST ", 5)==0) {
conn->requestType = HTTPD_METHOD_POST;
first_line = true;
}
if (first_line) {
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*)os_strstr(conn->url, " ");
if (e==NULL) return; //wtf?
*e=0; //terminate url part
os_printf("URL = %s\n", conn->url);
//Parse out the URL part before the GET parameters.
conn->getArgs=(char*)os_strstr(conn->url, "?");
if (conn->getArgs!=0) {
*conn->getArgs=0;
conn->getArgs++;
os_printf("GET args = %s\n", conn->getArgs);
} else {
conn->getArgs=NULL;
}
} else if (os_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;
}
os_printf("Mallocced buffer for %d + 1 bytes of post data.\n", conn->post->buffSize);
conn->post->buff=(char*)os_malloc(conn->post->buffSize + 1);
conn->post->buffLen=0;
} else if (os_strncmp(h, "Content-Type: ", 14)==0) {
if (os_strstr(h, "multipart/form-data")) {
// It's multipart form data so let's pull out the boundary for future use
char *b;
if ((b = os_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] = '-';
os_printf("boundary = %s\n", conn->post->multipartBoundary);
}
}
}
}
//Callback called when there's data available on a socket.
static void ICACHE_FLASH_ATTR httpdRecvCb(void *arg, char *data, unsigned short len) {
int x;
char *p, *e;
char sendBuff[MAX_SENDBUFF_LEN];
HttpdConnData *conn=httpdFindConnData(arg);
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 (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 *)os_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 *)os_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++;
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
//Send the response.
httpdProcessRequest(conn);
conn->post->buffLen = 0;
}
}
}
}
static void ICACHE_FLASH_ATTR httpdReconCb(void *arg, sint8 err) {
HttpdConnData *conn=httpdFindConnData(arg);
os_printf("ReconCb\n");
if (conn==NULL) return;
//Yeah... No idea what to do here. ToDo: figure something out.
}
static void ICACHE_FLASH_ATTR httpdDisconCb(void *arg) {
//The esp sdk passes through wrong arg here, namely the one of the *listening* socket.
//That is why we can't use (HttpdConnData)arg->sock here.
//Just look at all the sockets and kill the slot if needed.
int i;
for (i=0; i<MAX_CONN; i++) {
if (connData[i].conn!=NULL) {
//Why the >=ESPCONN_CLOSE and not ==? Well, seems the stack sometimes de-allocates
//espconns under our noses, especially when connections are interrupted. The memory
//is then used for something else, and we can use that to capture *most* of the
//disconnect cases.
if (connData[i].conn->state==ESPCONN_NONE || connData[i].conn->state>=ESPCONN_CLOSE) {
connData[i].conn=NULL;
if (connData[i].cgi!=NULL) connData[i].cgi(&connData[i]); //flush cgi data
httpdRetireConn(&connData[i]);
}
}
}
}
static void ICACHE_FLASH_ATTR httpdConnectCb(void *arg) {
struct espconn *conn=arg;
int i;
//Find empty conndata in pool
for (i=0; i<MAX_CONN; i++) if (connData[i].conn==NULL) break;
os_printf("Con req, conn=%p, pool slot %d\n", conn, i);
if (i==MAX_CONN) {
os_printf("Aiee, conn pool overflow!\n");
espconn_disconnect(conn);
return;
}
connData[i].priv=&connPrivData[i];
connData[i].conn=conn;
connData[i].priv->headPos=0;
connData[i].post=&connPostData[i];
connData[i].post->buff=NULL;
connData[i].post->buffLen=0;
connData[i].post->received=0;
connData[i].post->len=-1;
espconn_regist_recvcb(conn, httpdRecvCb);
espconn_regist_reconcb(conn, httpdReconCb);
espconn_regist_disconcb(conn, httpdDisconCb);
espconn_regist_sentcb(conn, httpdSentCb);
}
//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<MAX_CONN; i++) {
connData[i].conn=NULL;
}
httpdConn.type=ESPCONN_TCP;
httpdConn.state=ESPCONN_NONE;
httpdTcp.local_port=port;
httpdConn.proto.tcp=&httpdTcp;
builtInUrls=fixedUrls;
os_printf("Httpd init, conn=%p\n", &httpdConn);
espconn_regist_connectcb(&httpdConn, httpdConnectCb);
espconn_accept(&httpdConn);
}
+71
View File
@@ -0,0 +1,71 @@
#ifndef HTTPD_H
#define HTTPD_H
#include <c_types.h>
#include <ip_addr.h>
#include <espconn.h>
#define HTTPDVER "0.3"
#define HTTPD_CGI_MORE 0
#define HTTPD_CGI_DONE 1
#define HTTPD_CGI_NOTFOUND 2
#define HTTPD_CGI_AUTHENTICATED 3
#define HTTPD_METHOD_GET 1
#define HTTPD_METHOD_POST 2
typedef struct HttpdPriv HttpdPriv;
typedef struct HttpdConnData HttpdConnData;
typedef struct HttpdPostData HttpdPostData;
typedef int (* cgiSendCallback)(HttpdConnData *connData);
//A struct describing a http connection. This gets passed to cgi functions.
struct HttpdConnData {
struct espconn *conn;
char requestType;
char *url;
char *getArgs;
const void *cgiArg;
void *cgiData;
void *cgiPrivData; // Used for streaming handlers storing state between requests
HttpdPriv *priv;
cgiSendCallback cgi;
HttpdPostData *post;
};
//A struct describing the POST data sent inside the http connection. This is used by the CGI functions
struct HttpdPostData {
int len; // POST Content-Length
int buffSize; // The maximum length of the post buffer
int buffLen; // The amount of bytes in the current post buffer
int received; // The total amount of bytes received so far
char *buff; // Actual POST data buffer
char *multipartBoundary;
};
//A struct describing an url. This is the main struct that's used to send different URL requests to
//different routines.
typedef struct {
const char *url;
cgiSendCallback cgiCb;
const void *cgiArg;
} HttpdBuiltInUrl;
int ICACHE_FLASH_ATTR cgiRedirect(HttpdConnData *connData);
void ICACHE_FLASH_ATTR httpdRedirect(HttpdConnData *conn, char *newUrl);
int httpdUrlDecode(char *val, int valLen, char *ret, int retLen);
int ICACHE_FLASH_ATTR httpdFindArg(char *line, char *arg, char *buff, int buffLen);
void ICACHE_FLASH_ATTR httpdInit(HttpdBuiltInUrl *fixedUrls, int port);
const char *httpdGetMimetype(char *url);
#ifdef GZIP_COMPRESSION
const char* sendGZIPEncodingIfNeeded(HttpdConnData *connData);
#endif
void ICACHE_FLASH_ATTR httpdStartResponse(HttpdConnData *conn, int code);
void ICACHE_FLASH_ATTR httpdHeader(HttpdConnData *conn, const char *field, const char *val);
void ICACHE_FLASH_ATTR httpdEndHeaders(HttpdConnData *conn);
int ICACHE_FLASH_ATTR httpdGetHeader(HttpdConnData *conn, char *header, char *ret, int retLen);
int ICACHE_FLASH_ATTR httpdSend(HttpdConnData *conn, const char *data, int len);
#endif
+167
View File
@@ -0,0 +1,167 @@
/*
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 "espmissingincludes.h"
#include <string.h>
#include <osapi.h>
#include "c_types.h"
#include "user_interface.h"
#include "espconn.h"
#include "mem.h"
#include "httpdespfs.h"
//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];
#ifdef GZIP_COMPRESSION
const char *gzipSendResult = NULL;
#endif
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) {
return HTTPD_CGI_NOTFOUND;
}
connData->cgiData=file;
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url));
#ifdef GZIP_COMPRESSION
gzipSendResult = sendGZIPEncodingIfNeeded(connData);
if (gzipSendResult != NULL) {
httpdEndHeaders(connData);
httpdSend(connData, gzipSendResult, os_strlen(gzipSendResult));
return HTTPD_CGI_DONE;
}
#endif
httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate");
httpdEndHeaders(connData);
return HTTPD_CGI_MORE;
}
len=espFsRead(file, buff, 1024);
if (len>0) espconn_sent(connData->conn, (uint8 *)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);
os_free(tpd);
return HTTPD_CGI_DONE;
}
if (tpd==NULL) {
//First call to this cgi. Open the file so we can read it.
tpd=(TplData *)os_malloc(sizeof(TplData));
tpd->file=espFsOpen(connData->url);
tpd->tplArg=NULL;
tpd->tokenPos=-1;
if (tpd->file==NULL) {
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);
return HTTPD_CGI_DONE;
} else {
//Ok, till next time.
return HTTPD_CGI_MORE;
}
}
+10
View File
@@ -0,0 +1,10 @@
#ifndef HTTPDESPFS_H
#define HTTPDESPFS_H
#include "httpd.h"
#include "espfs.h"
int cgiEspFsHook(HttpdConnData *connData);
int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData);
#endif