626 lines
12 KiB
C
626 lines
12 KiB
C
//Copyright 2015 <>< Charles Lohr Under the MIT/x11 License, NewBSD License or
|
|
// ColorChord License. You Choose.
|
|
|
|
#include "http.h"
|
|
#include "mystuff.h"
|
|
#include "esp8266_rom.h"
|
|
|
|
#define HTDEBUG( x... ) printf( x )
|
|
|
|
//#define ISKEEPALIVE "keep-alive"
|
|
#define ISKEEPALIVE "close"
|
|
|
|
struct HTTPConnection HTTPConnections[HTTP_CONNECTIONS];
|
|
struct HTTPConnection * curhttp;
|
|
uint8 * curdata;
|
|
uint16 curlen;
|
|
uint8 wsmask[4];
|
|
uint8 wsmaskplace;
|
|
|
|
|
|
ICACHE_FLASH_ATTR void InternalStartHTTP( );
|
|
ICACHE_FLASH_ATTR void HTTPHandleInternalCallback( );
|
|
|
|
ICACHE_FLASH_ATTR void HTTPClose( )
|
|
{
|
|
//This is dead code, but it is a testament to Charles.
|
|
//Do not do this here. Wait for the ESP to tell us the
|
|
//socket is successfully closed.
|
|
//curhttp->state = HTTP_STATE_NONE;
|
|
curhttp->state = HTTP_WAIT_CLOSE;
|
|
espconn_disconnect( curhttp->socket );
|
|
}
|
|
|
|
|
|
void ICACHE_FLASH_ATTR HTTPGotData( )
|
|
{
|
|
uint8 c;
|
|
curhttp->timeout = 0;
|
|
while( curlen-- )
|
|
{
|
|
c = HTTPPOP;
|
|
// sendhex2( h->state ); sendchr( ' ' );
|
|
|
|
switch( curhttp->state )
|
|
{
|
|
case HTTP_STATE_WAIT_METHOD:
|
|
if( c == ' ' )
|
|
{
|
|
curhttp->state = HTTP_STATE_WAIT_PATH;
|
|
curhttp->state_deets = 0;
|
|
}
|
|
break;
|
|
case HTTP_STATE_WAIT_PATH:
|
|
curhttp->pathbuffer[curhttp->state_deets++] = c;
|
|
if( curhttp->state_deets == MAX_PATHLEN )
|
|
{
|
|
curhttp->state_deets--;
|
|
}
|
|
|
|
if( c == ' ' )
|
|
{
|
|
//Tricky: If we're a websocket, we need the whole header.
|
|
curhttp->pathbuffer[curhttp->state_deets-1] = 0;
|
|
curhttp->state_deets = 0;
|
|
|
|
if( strncmp( (const char*)curhttp->pathbuffer, "/d/ws", 5 ) == 0 )
|
|
{
|
|
curhttp->state = HTTP_STATE_DATA_WEBSOCKET;
|
|
curhttp->state_deets = 0;
|
|
}
|
|
else
|
|
{
|
|
curhttp->state = HTTP_STATE_WAIT_PROTO;
|
|
}
|
|
}
|
|
break;
|
|
case HTTP_STATE_WAIT_PROTO:
|
|
if( c == '\n' )
|
|
{
|
|
curhttp->state = HTTP_STATE_WAIT_FLAG;
|
|
}
|
|
break;
|
|
case HTTP_STATE_WAIT_FLAG:
|
|
if( c == '\n' )
|
|
{
|
|
curhttp->state = HTTP_STATE_DATA_XFER;
|
|
InternalStartHTTP( );
|
|
}
|
|
else if( c != '\r' )
|
|
{
|
|
curhttp->state = HTTP_STATE_WAIT_INFLAG;
|
|
}
|
|
break;
|
|
case HTTP_STATE_WAIT_INFLAG:
|
|
if( c == '\n' )
|
|
{
|
|
curhttp->state = HTTP_STATE_WAIT_FLAG;
|
|
curhttp->state_deets = 0;
|
|
}
|
|
break;
|
|
case HTTP_STATE_DATA_XFER:
|
|
//Ignore any further data?
|
|
curlen = 0;
|
|
break;
|
|
case HTTP_STATE_DATA_WEBSOCKET:
|
|
WebSocketGotData( c );
|
|
break;
|
|
case HTTP_WAIT_CLOSE:
|
|
if( curhttp->keep_alive )
|
|
{
|
|
curhttp->state = HTTP_STATE_WAIT_METHOD;
|
|
}
|
|
else
|
|
{
|
|
HTTPClose( );
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
|
|
static void DoHTTP( uint8_t timed )
|
|
{
|
|
switch( curhttp->state )
|
|
{
|
|
case HTTP_STATE_NONE: //do nothing if no state.
|
|
break;
|
|
case HTTP_STATE_DATA_XFER:
|
|
if( TCPCanSend( curhttp->socket, 1024 ) ) //TCPDoneSend
|
|
{
|
|
if( curhttp->is_dynamic )
|
|
{
|
|
HTTPCustomCallback( );
|
|
}
|
|
else
|
|
{
|
|
HTTPHandleInternalCallback( );
|
|
}
|
|
}
|
|
break;
|
|
case HTTP_WAIT_CLOSE:
|
|
if( TCPDoneSend( curhttp->socket ) )
|
|
{
|
|
if( curhttp->keep_alive )
|
|
{
|
|
curhttp->state = HTTP_STATE_WAIT_METHOD;
|
|
}
|
|
else
|
|
{
|
|
HTTPClose( );
|
|
}
|
|
}
|
|
break;
|
|
case HTTP_STATE_DATA_WEBSOCKET:
|
|
if( TCPCanSend( curhttp->socket, 1024 ) ) //TCPDoneSend
|
|
{
|
|
WebSocketTickInternal();
|
|
}
|
|
break;
|
|
default:
|
|
if( timed )
|
|
{
|
|
if( curhttp->timeout++ > HTTP_SERVER_TIMEOUT )
|
|
{
|
|
HTTPClose( );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void HTTPTick( uint8_t timed )
|
|
{
|
|
uint8_t i;
|
|
for( i = 0; i < HTTP_CONNECTIONS; i++ )
|
|
{
|
|
if( curhttp ) { printf( "Unexpected Race Condition\n" );}
|
|
curhttp = &HTTPConnections[i];
|
|
DoHTTP( timed );
|
|
curhttp = 0;
|
|
}
|
|
}
|
|
|
|
void ICACHE_FLASH_ATTR HTTPHandleInternalCallback( )
|
|
{
|
|
uint16_t i, bytestoread;
|
|
|
|
if( curhttp->isdone )
|
|
{
|
|
HTTPClose( );
|
|
return;
|
|
}
|
|
if( curhttp->is404 )
|
|
{
|
|
START_PACK
|
|
PushString("HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\nFile not found.");
|
|
EndTCPWrite( curhttp->socket );
|
|
curhttp->isdone = 1;
|
|
return;
|
|
}
|
|
if( curhttp->isfirst )
|
|
{
|
|
char stto[10];
|
|
uint8_t slen = os_strlen( curhttp->pathbuffer );
|
|
const char * k;
|
|
|
|
START_PACK;
|
|
//TODO: Content Length? MIME-Type?
|
|
PushString("HTTP/1.1 200 Ok\r\n");
|
|
|
|
if( curhttp->bytesleft < 0xfffffffe )
|
|
{
|
|
PushString("Connection: "ISKEEPALIVE"\r\nContent-Length: ");
|
|
Uint32To10Str( stto, curhttp->bytesleft );
|
|
PushBlob( stto, os_strlen( stto ) );
|
|
curhttp->keep_alive = 1;
|
|
}
|
|
else
|
|
{
|
|
PushString("Connection: close\r\n");
|
|
curhttp->keep_alive = 0;
|
|
}
|
|
|
|
PushString( "\r\nContent-Type: " );
|
|
//Content-Type?
|
|
while( slen && ( curhttp->pathbuffer[--slen] != '.' ) );
|
|
k = &curhttp->pathbuffer[slen+1];
|
|
if( strcmp( k, "mp3" ) == 0 )
|
|
{
|
|
PushString( "audio/mpeg3" );
|
|
}
|
|
else if( strcmp( k, "gz" ) == 0 )
|
|
{
|
|
PushString( "text/plain\r\nContent-Encoding: gzip\r\nCache-Control: public, max-age=3600" );
|
|
}
|
|
else if( curhttp->bytesleft == 0xfffffffe )
|
|
{
|
|
PushString( "text/plain" );
|
|
}
|
|
else
|
|
{
|
|
PushString( "text/html" );
|
|
}
|
|
|
|
PushString( "\r\n\r\n" );
|
|
EndTCPWrite( curhttp->socket );
|
|
curhttp->isfirst = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
START_PACK
|
|
|
|
for( i = 0; i < 4 && curhttp->bytesleft; i++ )
|
|
{
|
|
int bpt = curhttp->bytesleft;
|
|
if( bpt > MFS_SECTOR ) bpt = MFS_SECTOR;
|
|
curhttp->bytesleft = MFSReadSector( generic_ptr, &curhttp->data.filedescriptor );
|
|
generic_ptr += bpt;
|
|
}
|
|
|
|
EndTCPWrite( curhttp->socket );
|
|
|
|
if( !curhttp->bytesleft )
|
|
curhttp->isdone = 1;
|
|
}
|
|
|
|
void InternalStartHTTP( )
|
|
{
|
|
int32_t clusterno;
|
|
int8_t i;
|
|
const char * path = &curhttp->pathbuffer[0];
|
|
|
|
if( curhttp->pathbuffer[0] == '/' )
|
|
path++;
|
|
|
|
if( path[0] == 'd' && path[1] == '/' )
|
|
{
|
|
curhttp->is_dynamic = 1;
|
|
curhttp->isfirst = 1;
|
|
curhttp->isdone = 0;
|
|
curhttp->is404 = 0;
|
|
HTTPCustomStart();
|
|
return;
|
|
}
|
|
|
|
if( !path[0] )
|
|
{
|
|
path = "index.html";
|
|
}
|
|
|
|
i = MFSOpenFile( path, &curhttp->data.filedescriptor );
|
|
curhttp->bytessofar = 0;
|
|
|
|
if( i < 0 )
|
|
{
|
|
HTDEBUG( "404(%s)\n", path );
|
|
curhttp->is404 = 1;
|
|
curhttp->isfirst = 1;
|
|
curhttp->isdone = 0;
|
|
curhttp->is_dynamic = 0;
|
|
}
|
|
else
|
|
{
|
|
curhttp->isfirst = 1;
|
|
curhttp->isdone = 0;
|
|
curhttp->is404 = 0;
|
|
curhttp->is_dynamic = 0;
|
|
curhttp->bytesleft = curhttp->data.filedescriptor.filelen;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LOCAL void ICACHE_FLASH_ATTR
|
|
http_disconnetcb(void *arg) {
|
|
struct espconn *pespconn = (struct espconn *) arg;
|
|
((struct HTTPConnection * )pespconn->reverse)->state = 0;
|
|
}
|
|
|
|
LOCAL void ICACHE_FLASH_ATTR
|
|
http_recvcb(void *arg, char *pusrdata, unsigned short length)
|
|
{
|
|
struct espconn *pespconn = (struct espconn *) arg;
|
|
|
|
//Though it might be possible for this to interrupt the other
|
|
//tick task, I don't know if this is actually a probelem.
|
|
//I'm adding this back-up-the-register just in case.
|
|
if( curhttp ) { printf( "Unexpected Race Condition\n" );}
|
|
|
|
curhttp = (struct HTTPConnection * )pespconn->reverse;
|
|
curdata = (uint8*)pusrdata;
|
|
curlen = length;
|
|
HTTPGotData();
|
|
curhttp = 0 ;
|
|
|
|
}
|
|
|
|
void ICACHE_FLASH_ATTR
|
|
httpserver_connectcb(void *arg)
|
|
{
|
|
int i;
|
|
struct espconn *pespconn = (struct espconn *)arg;
|
|
|
|
for( i = 0; i < HTTP_CONNECTIONS; i++ )
|
|
{
|
|
if( HTTPConnections[i].state == 0 )
|
|
{
|
|
pespconn->reverse = &HTTPConnections[i];
|
|
HTTPConnections[i].socket = pespconn;
|
|
HTTPConnections[i].state = HTTP_STATE_WAIT_METHOD;
|
|
break;
|
|
}
|
|
}
|
|
if( i == HTTP_CONNECTIONS )
|
|
{
|
|
espconn_disconnect( pespconn );
|
|
return;
|
|
}
|
|
|
|
// espconn_set_opt(pespconn, ESPCONN_NODELAY);
|
|
// espconn_set_opt(pespconn, ESPCONN_COPY);
|
|
|
|
espconn_regist_recvcb( pespconn, http_recvcb );
|
|
espconn_regist_disconcb( pespconn, http_disconnetcb );
|
|
|
|
}
|
|
|
|
|
|
int ICACHE_FLASH_ATTR URLDecode( char * decodeinto, int maxlen, const char * buf )
|
|
{
|
|
int i = 0;
|
|
|
|
for( ; buf && *buf; buf++ )
|
|
{
|
|
char c = *buf;
|
|
if( c == '+' )
|
|
{
|
|
decodeinto[i++] = ' ';
|
|
}
|
|
else if( c == '?' || c == '&' )
|
|
{
|
|
break;
|
|
}
|
|
else if( c == '%' )
|
|
{
|
|
if( *(buf+1) && *(buf+2) )
|
|
{
|
|
decodeinto[i++] = hex2byte( buf+1 );
|
|
buf += 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
decodeinto[i++] = c;
|
|
}
|
|
if( i >= maxlen -1 ) break;
|
|
|
|
}
|
|
decodeinto[i] = 0;
|
|
return i;
|
|
}
|
|
|
|
|
|
|
|
void ICACHE_FLASH_ATTR WebSocketGotData( uint8_t c )
|
|
{
|
|
switch( curhttp->state_deets )
|
|
{
|
|
case 0:
|
|
{
|
|
int i = 0;
|
|
char inkey[120];
|
|
unsigned char hash[SHA1_HASH_LEN];
|
|
SHA1_CTX c;
|
|
int inkeylen = 0;
|
|
|
|
curhttp->is_dynamic = 1;
|
|
while( curlen > 20 )
|
|
{
|
|
curdata++; curlen--;
|
|
if( strncmp( curdata, "Sec-WebSocket-Key: ", 19 ) == 0 )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( curlen <= 21 )
|
|
{
|
|
HTDEBUG( "No websocket key found.\n" );
|
|
curhttp->state = HTTP_WAIT_CLOSE;
|
|
return;
|
|
}
|
|
|
|
curdata+= 19;
|
|
curlen -= 19;
|
|
|
|
|
|
#define WS_KEY_LEN 36
|
|
#define WS_KEY "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
#define WS_RETKEY_SIZEM1 32
|
|
|
|
while( curlen > 1 )
|
|
{
|
|
uint8_t lc = *(curdata++);
|
|
inkey[i] = lc;
|
|
curlen--;
|
|
if( lc == '\r' )
|
|
{
|
|
inkey[i] = 0;
|
|
break;
|
|
}
|
|
i++;
|
|
if( i >= sizeof( inkey ) - WS_KEY_LEN - 5 )
|
|
{
|
|
HTDEBUG( "Websocket key too big.\n" );
|
|
curhttp->state = HTTP_WAIT_CLOSE;
|
|
return;
|
|
}
|
|
}
|
|
if( curlen <= 1 )
|
|
{
|
|
HTDEBUG( "Invalid websocket key found.\n" );
|
|
curhttp->state = HTTP_WAIT_CLOSE;
|
|
return;
|
|
}
|
|
|
|
if( i + WS_KEY_LEN + 1 >= sizeof( inkey ) )
|
|
{
|
|
HTDEBUG( "WSKEY Too Big.\n" );
|
|
curhttp->state = HTTP_WAIT_CLOSE;
|
|
return;
|
|
}
|
|
|
|
ets_memcpy( &inkey[i], WS_KEY, WS_KEY_LEN + 1 );
|
|
i += WS_KEY_LEN;
|
|
SHA1_Init( &c );
|
|
SHA1_Update( &c, inkey, i );
|
|
SHA1_Final( hash, &c );
|
|
|
|
#if (WS_RETKEY_SIZE > MAX_PATHLEN - 10 )
|
|
#error MAX_PATHLEN too short.
|
|
#endif
|
|
|
|
my_base64_encode( hash, SHA1_HASH_LEN, curhttp->pathbuffer + (MAX_PATHLEN-WS_RETKEY_SIZEM1) );
|
|
|
|
curhttp->bytessofar = 0;
|
|
curhttp->bytesleft = 0;
|
|
|
|
NewWebSocket();
|
|
|
|
//Respond...
|
|
curhttp->state_deets = 1;
|
|
break;
|
|
}
|
|
case 1:
|
|
if( c == '\n' ) curhttp->state_deets = 2;
|
|
break;
|
|
case 2:
|
|
if( c == '\r' ) curhttp->state_deets = 3;
|
|
else curhttp->state_deets = 1;
|
|
break;
|
|
case 3:
|
|
if( c == '\n' ) curhttp->state_deets = 4;
|
|
else curhttp->state_deets = 1;
|
|
break;
|
|
case 5: //Established connection.
|
|
{
|
|
//XXX TODO: Seems to malfunction on large-ish packets. I know it has problems with 140-byte payloads.
|
|
|
|
if( curlen < 5 ) //Can't interpret packet.
|
|
break;
|
|
|
|
uint8_t fin = c & 1;
|
|
uint8_t opcode = c << 4;
|
|
uint16_t payloadlen = *(curdata++);
|
|
curlen--;
|
|
if( !(payloadlen & 0x80) )
|
|
{
|
|
HTDEBUG( "Unmasked packet.\n" );
|
|
curhttp->state = HTTP_WAIT_CLOSE;
|
|
break;
|
|
}
|
|
|
|
payloadlen &= 0x7f;
|
|
|
|
if( payloadlen == 127 )
|
|
{
|
|
//Very long payload.
|
|
//Not supported.
|
|
HTDEBUG( "Unsupported payload packet.\n" );
|
|
curhttp->state = HTTP_WAIT_CLOSE;
|
|
break;
|
|
}
|
|
else if( payloadlen == 126 )
|
|
{
|
|
payloadlen = (curdata[0] << 8) | curdata[1];
|
|
curdata += 2;
|
|
curlen -= 2;
|
|
}
|
|
|
|
wsmask[0] = curdata[0];
|
|
wsmask[1] = curdata[1];
|
|
wsmask[2] = curdata[2];
|
|
wsmask[3] = curdata[3];
|
|
curdata += 4;
|
|
curlen -= 4;
|
|
wsmaskplace = 0;
|
|
|
|
//XXX Warning: When packets get larger, they may split the
|
|
//websockets packets into multiple parts. We could handle this
|
|
//but at the cost of prescious RAM. I am chosing to just drop those
|
|
//packets on the floor, and restarting the connection.
|
|
if( curlen < payloadlen )
|
|
{
|
|
HTDEBUG( "Websocket Fragmented. %d %d\n", curlen, payloadlen );
|
|
curhttp->state = HTTP_WAIT_CLOSE;
|
|
return;
|
|
}
|
|
|
|
WebSocketData( payloadlen );
|
|
curlen -= payloadlen;
|
|
curdata += payloadlen;
|
|
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ICACHE_FLASH_ATTR WebSocketTickInternal()
|
|
{
|
|
switch( curhttp->state_deets )
|
|
{
|
|
case 4: //Has key full HTTP header, etc. wants response.
|
|
START_PACK;
|
|
PushString( "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: " );
|
|
PushString( curhttp->pathbuffer + (MAX_PATHLEN-WS_RETKEY_SIZEM1) );
|
|
PushString( "\r\n\r\n" );
|
|
EndTCPWrite( curhttp->socket );
|
|
curhttp->state_deets = 5;
|
|
curhttp->keep_alive = 0;
|
|
break;
|
|
case 5:
|
|
WebSocketTick();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ICACHE_FLASH_ATTR WebSocketSend( uint8_t * data, int size )
|
|
{
|
|
START_PACK;
|
|
PushByte( 0x81 );
|
|
if( size >= 126 )
|
|
{
|
|
PushByte( 0x00 | 126 );
|
|
PushByte( size>>8 );
|
|
PushByte( size&0xff );
|
|
}
|
|
else
|
|
{
|
|
PushByte( 0x00 | size );
|
|
}
|
|
PushBlob( data, size );
|
|
EndTCPWrite( curhttp->socket );
|
|
}
|
|
|
|
uint8_t WSPOPMASK()
|
|
{
|
|
uint8_t mask = wsmask[wsmaskplace];
|
|
wsmaskplace = (wsmaskplace+1)&3;
|
|
return (*curdata++)^(mask);
|
|
}
|
|
|
|
|