2018-11-26 12:58:15 +01:00
/*
* This file is part of the " bluetoothheater " distribution
* ( https : //gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright ( C ) 2018 Ray Jones < ray @ mrjones . id . au >
* Copyright ( C ) 2018 James Clark
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
*
*/
2019-05-30 12:31:34 +02:00
# define USE_EMBEDDED_WEBUPDATECODE
2020-05-13 02:37:31 +02:00
# define HTTPS_LOGLEVEL 2
2018-11-07 05:07:11 +01:00
2019-07-07 09:18:38 +02:00
# include <Arduino.h>
# include "BTCWifi.h"
2018-11-07 05:07:11 +01:00
# include "BTCWebServer.h"
2019-07-09 14:19:21 +02:00
# include "BTCota.h"
2018-12-16 08:34:39 +01:00
# include "../Utility/DebugPort.h"
# include "../Protocol/TxManage.h"
2019-06-16 01:09:29 +02:00
# include "../Utility/helpers.h"
2018-12-16 08:34:39 +01:00
# include "../cfg/pins.h"
2019-05-18 10:49:22 +02:00
# include "../cfg/BTCConfig.h"
2018-12-16 08:34:39 +01:00
# include "../Utility/BTC_JSON.h"
# include "../Utility/Moderator.h"
2019-07-25 09:40:23 +02:00
# include "../../lib/WiFiManager-dev/WiFiManager.h"
2019-01-20 11:14:45 +01:00
# include <SPIFFS.h>
2019-05-16 13:12:29 +02:00
# include "../Utility/NVStorage.h"
2019-07-07 09:18:38 +02:00
# include <WiFiClient.h>
# include <WebServer.h>
# include <ESPmDNS.h>
2019-07-18 14:28:40 +02:00
# include "BrowserUpload.h"
2019-07-07 09:18:38 +02:00
# include <Update.h>
2020-03-24 11:14:41 +01:00
# include "WebContentDL.h"
2019-01-11 09:50:27 +01:00
2020-04-22 08:21:24 +02:00
# include <HTTPSServer.hpp>
# include <SSLCert.hpp>
# include <HTTPRequest.hpp>
# include <HTTPResponse.hpp>
# include <HTTPBodyParser.hpp>
# include <HTTPMultipartBodyParser.hpp>
# include <HTTPURLEncodedBodyParser.hpp>
# include <WebsocketHandler.hpp>
# include <FreeRTOS.h>
# include "../OLED/ScreenManager.h"
2020-05-13 02:37:31 +02:00
# include "esp_task_wdt.h"
2020-04-22 08:21:24 +02:00
2020-05-03 13:59:56 +02:00
// Max clients to be connected to the JSON handler
# define MAX_CLIENTS 4
2020-04-22 08:21:24 +02:00
extern CScreenManager ScreenManager ;
using namespace httpsserver ;
2019-01-11 09:50:27 +01:00
extern WiFiManager wm ;
2019-07-06 15:46:20 +02:00
extern const char * stdHeader ;
extern const char * formatIndex ;
extern const char * updateIndex ;
extern const char * formatDoneContent ;
2019-07-15 11:56:36 +02:00
extern const char * rebootIndex ;
2018-11-07 05:07:11 +01:00
2020-04-26 08:15:08 +02:00
2019-08-03 04:42:49 +02:00
extern void checkSplashScreenUpdate ( ) ;
2019-08-01 14:57:18 +02:00
2020-04-22 08:21:24 +02:00
size_t streamFileSSL ( fs : : File & file , const String & contentType , httpsserver : : HTTPResponse * pSSL ) ;
void streamFileCoreSSL ( const size_t fileSize , const String & fileName , const String & contentType ) ;
2020-05-03 13:59:56 +02:00
void processWebsocketQueue ( ) ;
2020-04-22 08:21:24 +02:00
2020-05-03 13:59:56 +02:00
QueueHandle_t webSocketQueue = NULL ;
2020-05-13 02:37:31 +02:00
QueueHandle_t JSONcommandQueue = NULL ;
2020-05-06 02:41:26 +02:00
TaskHandle_t handleWebServerTask ;
2020-05-03 13:59:56 +02:00
# if USE_HTTPS == 1
2020-04-22 08:21:24 +02:00
SSLCert * pCert ;
HTTPSServer * secureServer ;
2020-05-03 13:59:56 +02:00
# endif
2020-04-22 08:21:24 +02:00
HTTPServer * insecureServer ;
HTTPServer * WSserver ;
void SSLloopTask ( void * ) ;
2019-07-18 14:28:40 +02:00
sBrowserUpload BrowserUpload ;
2020-04-22 08:21:24 +02:00
# ifdef OLD_SERVER
// WebServer server(80);
// WebSocketsServer webSocket = WebSocketsServer(81);
# endif
2020-03-25 10:28:12 +01:00
CGetWebContent GetWebContent ;
2018-12-12 10:03:44 +01:00
bool bRxWebData = false ; // flags for OLED animation
2018-12-01 19:25:10 +01:00
bool bTxWebData = false ;
2020-04-22 08:21:24 +02:00
bool bUpdateAccessed = false ; // flag used to ensure browser update always starts via GET /update. direct accesses to POST /update will FAIL
2019-07-06 15:46:20 +02:00
bool bFormatAccessed = false ;
2019-07-11 10:55:31 +02:00
bool bFormatPerformed = false ;
2020-05-09 11:17:21 +02:00
bool bStopWebServer = false ;
2019-05-22 22:35:09 +02:00
long _SuppliedFileSize = 0 ;
2018-11-07 05:07:11 +01:00
2019-06-30 02:37:24 +02:00
bool checkFile ( File & file ) ;
2019-07-06 15:46:20 +02:00
void addTableData ( String & HTML , String dta ) ;
String getContentType ( String filename ) ;
2020-04-22 08:21:24 +02:00
bool handleFileRead ( String path , HTTPResponse * res = NULL ) ;
2019-07-07 09:18:38 +02:00
void onNotFound ( ) ;
2020-04-22 08:21:24 +02:00
void onReboot ( HTTPRequest * req , HTTPResponse * res ) ;
void onFormatSPIFFS ( HTTPRequest * req , HTTPResponse * res ) ;
void onFormatNow ( HTTPRequest * req , HTTPResponse * res ) ;
void onFormatDone ( HTTPRequest * req , HTTPResponse * res ) ;
void rootRedirect ( HTTPRequest * req , HTTPResponse * res ) ;
void onUpload ( HTTPRequest * req , HTTPResponse * res ) ;
void onUploadBegin ( HTTPRequest * req , HTTPResponse * res ) ;
void onUploadProgression ( HTTPRequest * req , HTTPResponse * res ) ;
void onUploadCompletion ( HTTPRequest * req , HTTPResponse * res ) ;
void onWMConfig ( HTTPRequest * req , HTTPResponse * res ) ;
void onResetWifi ( HTTPRequest * req , HTTPResponse * res ) ;
void doDefaultWebHandler ( HTTPRequest * req , HTTPResponse * res ) ;
void build404Response ( HTTPRequest * req , String & content , String file ) ;
2019-07-06 15:46:20 +02:00
void build500Response ( String & content , String file ) ;
2020-04-22 08:21:24 +02:00
bool checkAuthentication ( HTTPRequest * req , HTTPResponse * res , int credID = 0 ) ;
2020-05-13 02:37:31 +02:00
bool addRxJSONcommand ( const char * str ) ;
bool checkRxJSONcommand ( ) ;
2020-04-22 08:21:24 +02:00
// As websockets are more complex, they need a custom class that is derived from WebsocketHandler
class JSONHandler : public WebsocketHandler {
public :
// This method is called by the webserver to instantiate a new handler for each
// client that connects to the websocket endpoint
static WebsocketHandler * create ( ) ;
// This method is called when a message arrives
void onMessage ( WebsocketInputStreambuf * input ) ;
// Handler function on connection close
void onClose ( ) ;
} ;
// Simple array to store the active clients:
JSONHandler * activeClients [ MAX_CLIENTS ] ;
// In the create function of the handler, we create a new Handler and keep track
// of it using the activeClients array
WebsocketHandler * JSONHandler : : create ( ) {
DebugPort . println ( " Creating new JSON client! " ) ;
JSONHandler * handler = new JSONHandler ( ) ;
for ( int i = 0 ; i < MAX_CLIENTS ; i + + ) {
if ( activeClients [ i ] = = nullptr ) {
activeClients [ i ] = handler ;
break ;
}
}
return handler ;
}
// When the websocket is closing, we remove the client from the array
2020-05-03 13:59:56 +02:00
void
JSONHandler : : onClose ( ) {
2020-04-22 08:21:24 +02:00
for ( int i = 0 ; i < MAX_CLIENTS ; i + + ) {
if ( activeClients [ i ] = = this ) {
activeClients [ i ] = nullptr ;
}
}
}
2020-05-03 13:59:56 +02:00
void
JSONHandler : : onMessage ( WebsocketInputStreambuf * inbuf ) {
2020-04-22 08:21:24 +02:00
// Get the input message
std : : ostringstream ss ;
std : : string msg ;
ss < < inbuf ;
msg = ss . str ( ) ;
bRxWebData = true ;
2020-05-03 13:59:56 +02:00
2020-05-13 02:37:31 +02:00
// use a queue to hand over messages - ensures any commands that affect the I2C bus
// (typ. various RTC operations) are performed in line with all other accesses
addRxJSONcommand ( msg . c_str ( ) ) ;
}
bool addRxJSONcommand ( const char * str )
{
if ( JSONcommandQueue ) {
char * pMsg = new char [ strlen ( str ) + 1 ] ;
strcpy ( pMsg , str ) ;
xQueueSend ( JSONcommandQueue , & pMsg , 0 ) ;
return true ;
2020-04-22 08:21:24 +02:00
}
2020-05-13 02:37:31 +02:00
return false ;
}
bool checkRxJSONcommand ( )
{
char * pMsg = NULL ;
if ( xQueueReceive ( JSONcommandQueue , & pMsg , 0 ) ) {
interpretJsonCommand ( pMsg ) ;
delete [ ] pMsg ;
return true ;
}
return false ;
2020-04-22 08:21:24 +02:00
}
2020-05-03 13:59:56 +02:00
// websocket handler, purely for performing /update handshaking
class updateWSHandler : public WebsocketHandler {
String renameFrom ;
String renameTo ;
public :
// This method is called by the webserver to instantiate a new handler for each
// client that connects to the websocket endpoint
static WebsocketHandler * create ( ) ;
// This method is called when a message arrives
void onMessage ( WebsocketInputStreambuf * input ) ;
// Handler function on connection close
void onClose ( ) ;
void interpret ( const char * cmd ) ;
void decode ( const char * cmd , String & payload ) ;
} ;
updateWSHandler * pUpdateHandler = NULL ;
WebsocketHandler *
updateWSHandler : : create ( )
{
if ( pUpdateHandler ) {
delete pUpdateHandler ;
DebugPort . println ( " deleting old /update websocket! " ) ;
}
DebugPort . println ( " Creating new /update websocket! " ) ;
pUpdateHandler = new updateWSHandler ( ) ;
return pUpdateHandler ;
}
void
updateWSHandler : : onMessage ( WebsocketInputStreambuf * inbuf )
{
// DebugPort.printf("updateWSHandler::onMessage...");
// Get the input message
std : : ostringstream ss ;
std : : string msg ;
ss < < inbuf ;
msg = ss . str ( ) ;
char cmd [ 256 ] ;
memset ( cmd , 0 , 256 ) ;
if ( msg . length ( ) < 256 ) {
strcpy ( cmd , msg . c_str ( ) ) ;
interpret ( cmd ) ; // send to the decode routine
}
}
// When the websocket is closing, we remove the client from the array
void
updateWSHandler : : onClose ( )
{
DebugPort . println ( " updateWSHandler::onClose() " ) ;
pUpdateHandler = nullptr ;
}
void
updateWSHandler : : interpret ( const char * cmd )
{
if ( strlen ( cmd ) = = 0 )
return ;
// DebugPort.printf("updateWSHandler::interpret %s...", cmd);
StaticJsonBuffer < 512 > jsonBuffer ; // create a JSON buffer on the heap
JsonObject & obj = jsonBuffer . parseObject ( cmd ) ;
if ( ! obj . success ( ) ) {
// DebugPort.println(" FAILED");
return ;
}
// DebugPort.println(" OK");
JsonObject : : iterator it ;
for ( it = obj . begin ( ) ; it ! = obj . end ( ) ; + + it ) {
String payload ( it - > value . as < const char * > ( ) ) ;
decode ( it - > key , payload ) ;
}
}
void
updateWSHandler : : decode ( const char * cmd , String & payload )
{
if ( strcmp ( " erase " , cmd ) = = 0 ) {
String filename ( payload ) ;
filename . replace ( " %20 " , " " ) ; // convert HTML spaces to real spaces
if ( filename . length ( ) ! = 0 ) {
DebugPort . printf ( " WS onErase: %s " , filename . c_str ( ) ) ;
if ( SPIFFS . exists ( filename . c_str ( ) ) ) {
SPIFFS . remove ( filename . c_str ( ) ) ;
DebugPort . println ( " ERASED \r \n " ) ;
}
else
DebugPort . println ( " NOT FOUND \r \n " ) ;
}
if ( pUpdateHandler )
pUpdateHandler - > send ( " { \" updateReload \" :100} " , WebsocketHandler : : SEND_TYPE_TEXT ) ;
// activeClients[i]->send(pMsg, WebsocketHandler::SEND_TYPE_TEXT);
}
else if ( strcmp ( " renameFrom " , cmd ) = = 0 ) {
renameFrom = payload ;
renameFrom . replace ( " %20 " , " " ) ; // convert html spaces to real spaces
}
else if ( strcmp ( " renameTo " , cmd ) = = 0 ) {
renameTo = payload ;
renameTo . replace ( " %20 " , " " ) ; // convert html spaces to real spaces
if ( renameFrom ! = " " & & renameTo ! = " " ) {
DebugPort . printf ( " Renaming %s to %s \r \n " , renameFrom . c_str ( ) , renameTo . c_str ( ) ) ;
SPIFFS . rename ( renameFrom . c_str ( ) , renameTo . c_str ( ) ) ;
checkSplashScreenUpdate ( ) ;
}
if ( pUpdateHandler )
pUpdateHandler - > send ( " { \" updateReload \" :100} " , WebsocketHandler : : SEND_TYPE_TEXT ) ;
}
else if ( strcmp ( " updateSize " , cmd ) = = 0 ) {
setUploadSize ( payload . toInt ( ) ) ;
}
}
2020-03-25 10:28:12 +01:00
const char * getWebContent ( bool start ) {
2020-04-07 23:51:52 +02:00
if ( isWifiSTAConnected ( ) & & start ) {
2020-03-25 10:28:12 +01:00
GetWebContent . start ( ) ;
}
return GetWebContent . getFilename ( ) ;
}
2020-05-03 13:59:56 +02:00
# if USE_HTTPS == 1
2020-04-22 08:21:24 +02:00
SemaphoreHandle_t SSLSemaphore = NULL ;
void SSLkeyTask ( void * ) {
DebugPort . println ( " SSL creation starting " ) ;
pCert = new SSLCert ( ) ;
ABpreferences SSLkeyStore ;
SSLkeyStore . begin ( " SSLkeys " ) ;
if ( SSLkeyStore . hasBytes ( " Certificate " ) ) {
ScreenManager . showBootMsg ( " Loading SSL cert. " ) ;
DebugPort . println ( " Using stored SSL certificate " ) ;
int len ;
len = SSLkeyStore . getBytesLength ( " Certificate " ) ;
unsigned char * pCertData = new unsigned char [ len ] ; // POTENTIAL LEAK HERE DUE TO LAME SSL LIBARY POINTER COPY
SSLkeyStore . getBytes ( " Certificate " , pCertData , len ) ;
pCert - > setCert ( pCertData , len ) ;
len = SSLkeyStore . getBytesLength ( " PrivateKey " ) ;
unsigned char * pPKData = new unsigned char [ len ] ; // POTENTIAL LEAK HERE DUE TO LAME SSL LIBARY POINTER COPY
SSLkeyStore . getBytes ( " PrivateKey " , pPKData , len ) ;
pCert - > setPK ( pPKData , len ) ;
// vTaskDelay(10000); // TEST
}
else {
DebugPort . println ( " Creating SSL certificate - this may take a while... " ) ;
ScreenManager . showBootMsg ( " Creating SSL cert. " ) ;
int createCertResult = createSelfSignedCert (
* pCert ,
KEYSIZE_2048 ,
" CN=myesp.local,O=acme,C=US " ) ;
DebugPort . println ( " SSL certificate created " ) ;
if ( createCertResult ! = 0 ) {
DebugPort . printf ( " Error generating certificate " ) ;
}
else {
SSLkeyStore . putBytes ( " Certificate " , pCert - > getCertData ( ) , pCert - > getCertLength ( ) ) ;
SSLkeyStore . putBytes ( " PrivateKey " , pCert - > getPKData ( ) , pCert - > getPKLength ( ) ) ;
}
}
SSLkeyStore . end ( ) ;
DebugPort . printf ( " Certificate: length = %d \r \n " , pCert - > getCertLength ( ) ) ;
2020-05-03 13:59:56 +02:00
hexDump ( pCert - > getCertData ( ) , pCert - > getCertLength ( ) , 32 ) ;
2020-04-22 08:21:24 +02:00
DebugPort . println ( " " ) ;
DebugPort . printf ( " Private key: length = %d \r \n " , pCert - > getPKLength ( ) ) ;
2020-05-03 13:59:56 +02:00
hexDump ( pCert - > getPKData ( ) , pCert - > getPKLength ( ) , 32 ) ;
2020-04-22 08:21:24 +02:00
DebugPort . println ( " " ) ;
xSemaphoreGive ( SSLSemaphore ) ;
vTaskDelete ( NULL ) ;
}
2020-05-03 13:59:56 +02:00
# endif
2019-07-06 15:46:20 +02:00
void initWebServer ( void ) {
2019-09-21 01:58:51 +02:00
if ( MDNS . begin ( " Afterburner " ) ) {
DebugPort . println ( " MDNS responder started " ) ;
}
2020-04-22 08:21:24 +02:00
// ScreenManager.showBootMsg("Preparing SSL cert.");
2020-05-03 13:59:56 +02:00
# if USE_HTTPS == 1
2020-04-22 08:21:24 +02:00
// create SSL certificate, but off load to a task with a BIG stack;
SSLSemaphore = xSemaphoreCreateBinary ( ) ;
xTaskCreate ( SSLkeyTask ,
" SSLkeyTask " ,
16384 ,
NULL ,
2020-05-03 13:59:56 +02:00
TASK_PRIORITY_SSL_CERT , // low priority as this blocks BIG time
2020-05-06 02:41:26 +02:00
& handleWebServerTask ) ;
2020-04-22 08:21:24 +02:00
while ( ! xSemaphoreTake ( SSLSemaphore , 250 ) ) {
ScreenManager . showBootWait ( 1 ) ;
}
ScreenManager . showBootWait ( 0 ) ;
ScreenManager . showBootMsg ( " Starting web server " ) ;
vSemaphoreDelete ( SSLSemaphore ) ;
secureServer = new HTTPSServer ( pCert ) ;
2020-05-03 13:59:56 +02:00
# else
ScreenManager . showBootMsg ( " Starting web server " ) ;
# endif
2020-04-22 08:21:24 +02:00
2020-05-03 13:59:56 +02:00
WSserver = new HTTPServer ( 81 ) ;
insecureServer = new HTTPServer ( ) ;
2020-04-22 08:21:24 +02:00
DebugPort . println ( " HTTPS server created " ) ;
ResourceNode * WSnodeRoot = new ResourceNode ( " / " , " GET " , [ ] ( HTTPRequest * req , HTTPResponse * res ) {
res - > print ( " Insecure websocket lives here!!! " ) ;
} ) ;
WebsocketNode * WebsktNode = new WebsocketNode ( " / " , & JSONHandler : : create ) ;
2020-05-03 13:59:56 +02:00
WebsocketNode * WebsktUpdateNode = new WebsocketNode ( " /update " , & updateWSHandler : : create ) ;
// setup limited websocket only capability on port 81
WSserver - > registerNode ( WSnodeRoot ) ;
2020-04-22 08:21:24 +02:00
WSserver - > registerNode ( WebsktNode ) ;
2020-05-03 13:59:56 +02:00
// setup websocket capability on port 80 & 443
2020-04-22 08:21:24 +02:00
insecureServer - > registerNode ( WebsktNode ) ;
2020-05-03 13:59:56 +02:00
# if USE_HTTPS == 1
2020-04-22 08:21:24 +02:00
secureServer - > registerNode ( WebsktNode ) ; // associated secure websocket
2020-05-03 13:59:56 +02:00
# endif
2020-04-22 08:21:24 +02:00
ResourceNode * rebootNode = new ResourceNode ( " /reboot " , " " , & onReboot ) ;
ResourceNode * formatNode = new ResourceNode ( " /formatspiffs " , " " , & onFormatNow ) ;
ResourceNode * updateNode = new ResourceNode ( " /update " , " " , & onUpload ) ;
ResourceNode * wmconfigNode = new ResourceNode ( " /wmconfig " , " GET " , & onWMConfig ) ;
ResourceNode * resetwifiNode = new ResourceNode ( " /resetwifi " , " GET " , & onResetWifi ) ;
2020-05-03 13:59:56 +02:00
ResourceNode * defaultGet = new ResourceNode ( " / " , " GET " , & doDefaultWebHandler ) ;
2020-04-22 08:21:24 +02:00
insecureServer - > registerNode ( rebootNode ) ;
insecureServer - > registerNode ( formatNode ) ;
insecureServer - > registerNode ( updateNode ) ;
2020-05-03 13:59:56 +02:00
insecureServer - > registerNode ( WebsktUpdateNode ) ;
2020-04-22 08:21:24 +02:00
insecureServer - > registerNode ( resetwifiNode ) ;
insecureServer - > registerNode ( wmconfigNode ) ;
insecureServer - > setDefaultNode ( defaultGet ) ;
2020-05-03 13:59:56 +02:00
# if USE_HTTPS == 1
secureServer - > registerNode ( rebootNode ) ;
secureServer - > registerNode ( formatNode ) ;
secureServer - > registerNode ( updateNode ) ;
secureServer - > registerNode ( WebsktUpdateNode ) ;
secureServer - > registerNode ( resetwifiNode ) ;
secureServer - > registerNode ( wmconfigNode ) ;
2020-04-22 08:21:24 +02:00
secureServer - > setDefaultNode ( defaultGet ) ;
2020-05-03 13:59:56 +02:00
# endif
2020-04-22 08:21:24 +02:00
WSserver - > start ( ) ;
insecureServer - > start ( ) ;
2020-05-03 13:59:56 +02:00
# if USE_HTTPS == 1
2020-04-22 08:21:24 +02:00
secureServer - > start ( ) ;
2020-05-03 13:59:56 +02:00
# endif
2020-04-22 08:21:24 +02:00
2020-05-03 13:59:56 +02:00
DebugPort . println ( " HTTPS started " ) ;
2019-07-06 15:46:20 +02:00
2020-05-13 02:37:31 +02:00
JSONcommandQueue = xQueueCreate ( 50 , sizeof ( char * ) ) ;
2019-07-06 15:46:20 +02:00
2020-05-03 13:59:56 +02:00
// setup task to handle webserver
webSocketQueue = xQueueCreate ( 50 , sizeof ( char * ) ) ;
2020-05-09 11:17:21 +02:00
bStopWebServer = false ;
2020-04-22 08:21:24 +02:00
xTaskCreate ( SSLloopTask ,
2020-05-06 02:41:26 +02:00
" Web server task " ,
2020-05-03 13:59:56 +02:00
8192 ,
// 16384,
2020-04-22 08:21:24 +02:00
NULL ,
2020-05-03 13:59:56 +02:00
TASK_PRIORITY_SSL_LOOP , // low priority as this potentially blocks BIG time
2020-05-06 02:41:26 +02:00
& handleWebServerTask ) ;
2019-07-06 15:46:20 +02:00
2020-04-22 08:21:24 +02:00
DebugPort . println ( " HTTP task started " ) ;
2019-07-06 15:46:20 +02:00
}
2020-04-22 08:21:24 +02:00
void SSLloopTask ( void * ) {
2020-05-09 11:17:21 +02:00
while ( ! bStopWebServer ) {
2020-04-22 08:21:24 +02:00
WSserver - > loop ( ) ;
insecureServer - > loop ( ) ;
2020-05-03 13:59:56 +02:00
# if USE_HTTPS == 1
2020-04-22 08:21:24 +02:00
secureServer - > loop ( ) ;
2020-05-03 13:59:56 +02:00
# endif
processWebsocketQueue ( ) ;
2020-04-22 08:21:24 +02:00
vTaskDelay ( 1 ) ;
}
2020-05-09 11:17:21 +02:00
WSserver - > stop ( ) ;
insecureServer - > stop ( ) ;
# if USE_HTTPS == 1
secureServer - > stop ( ) ;
# endif
vTaskDelete ( NULL ) ;
bStopWebServer = false ;
2020-04-22 08:21:24 +02:00
}
2019-07-06 15:46:20 +02:00
// called by main sketch loop()
bool doWebServer ( void )
{
2020-03-25 10:28:12 +01:00
GetWebContent . manage ( ) ;
2020-05-13 02:37:31 +02:00
BrowserUpload . queueProcess ( ) ; // manage data queued from web update
checkRxJSONcommand ( ) ;
2019-07-06 15:46:20 +02:00
return true ;
}
2019-01-20 11:14:45 +01:00
2020-05-09 11:17:21 +02:00
void stopWebServer ( )
{
DebugPort . println ( " Requesting web server stop " ) ;
bStopWebServer = true ;
delay ( 100 ) ;
}
2019-01-20 11:14:45 +01:00
String getContentType ( String filename ) { // convert the file extension to the MIME type
if ( filename . endsWith ( " .html " ) ) return " text/html " ;
else if ( filename . endsWith ( " .css " ) ) return " text/css " ;
else if ( filename . endsWith ( " .js " ) ) return " application/javascript " ;
else if ( filename . endsWith ( " .ico " ) ) return " image/x-icon " ;
2019-05-21 14:01:42 +02:00
else if ( filename . endsWith ( " .bin " ) ) return " application/octet-stream " ;
2019-07-15 11:56:36 +02:00
else if ( filename . endsWith ( " .zip " ) ) return " application/x-zip " ;
else if ( filename . endsWith ( " .gz " ) ) return " application/x-gzip " ;
2019-01-20 11:14:45 +01:00
return " text/plain " ;
}
2020-04-22 08:21:24 +02:00
bool
findFormArg ( HTTPRequest * req , const char * Arg , std : : string & value )
{
HTTPBodyParser * pParser = new HTTPMultipartBodyParser ( req ) ;
while ( pParser - > nextField ( ) ) {
std : : string name = pParser - > getFieldName ( ) ;
DebugPort . printf ( " findArg: %s \r \n " , name . c_str ( ) ) ;
if ( name = = Arg ) {
DebugPort . println ( " found desired Arg " ) ;
char buf [ 512 ] ;
size_t readLength = pParser - > read ( ( byte * ) buf , 512 ) ;
value = std : : string ( buf , readLength ) ;
delete pParser ;
return true ;
}
}
delete pParser ;
return false ;
}
void
doDefaultWebHandler ( HTTPRequest * req , HTTPResponse * res )
{
String path = req - > getRequestString ( ) . c_str ( ) ;
if ( path . endsWith ( " / " ) ) path + = " index.html " ; // If a folder is requested, send the index file
if ( path . indexOf ( " index.html " ) > = 0 ) {
if ( ! checkAuthentication ( req , res , 1 ) ) {
return ;
}
}
if ( ! handleFileRead ( req - > getRequestString ( ) . c_str ( ) , res ) ) { // send it if it exists
String message ;
build404Response ( req , message , path ) ;
res - > setStatusCode ( 404 ) ;
res - > setStatusText ( " Not found " ) ;
res - > print ( message . c_str ( ) ) ;
}
}
bool checkAuthentication ( HTTPRequest * req , HTTPResponse * res , int credID ) {
// Get login information from request
// If you use HTTP Basic Auth, you can retrieve the values from the request.
// The return values will be empty strings if the user did not provide any data,
// or if the format of the Authorization header is invalid (eg. no Basic Method
// for Authorization, or an invalid Base64 token)
2020-05-03 13:59:56 +02:00
sCredentials creds = NVstore . getCredentials ( ) ;
if ( credID = = 0 & & strlen ( creds . webUpdatePassword ) = = 0 ) return true ;
if ( credID = = 1 & & strlen ( creds . webPassword ) = = 0 ) return true ;
2020-04-22 08:21:24 +02:00
std : : string reqUsername = req - > getBasicAuthUser ( ) ;
std : : string reqPassword = req - > getBasicAuthPassword ( ) ;
// If the user entered login information, we will check it
if ( reqUsername . length ( ) > 0 & & reqPassword . length ( ) > 0 ) {
if ( credID = = 0 & & reqUsername = = creds . webUpdateUsername & & reqPassword = = creds . webUpdatePassword ) {
return true ;
}
if ( credID = = 1 & & reqUsername = = creds . webUsername & & reqPassword = = creds . webPassword ) {
return true ;
}
}
res - > setStatusCode ( 401 ) ;
res - > setStatusText ( " Unauthorized " ) ;
res - > setHeader ( " WWW-Authenticate " , " Basic realm= \" Login Required \" " ) ;
res - > setHeader ( " Content-Type " , " text/html " ) ;
res - > println ( " 401. Not authorized " ) ;
return false ;
}
bool handleFileRead ( String path , HTTPResponse * res ) { // send the right file to the client (if it exists)
DebugPort . println ( " handleFileRead original request: " + path ) ;
2019-01-20 11:14:45 +01:00
if ( path . endsWith ( " / " ) ) path + = " index.html " ; // If a folder is requested, send the index file
2020-03-23 06:54:15 +01:00
path . replace ( " %20 " , " " ) ; // convert HTML spaces to normal spaces
2019-01-20 11:14:45 +01:00
String contentType = getContentType ( path ) ; // Get the MIME type
2019-07-15 11:56:36 +02:00
String pathWithGz = path + " .gz " ;
2020-04-22 08:21:24 +02:00
DebugPort . println ( " handleFileRead conditioned request: " + path ) ;
2019-07-15 11:56:36 +02:00
if ( SPIFFS . exists ( pathWithGz ) | | SPIFFS . exists ( path ) ) { // If the file exists as a compressed archive, or normal
2020-04-22 08:21:24 +02:00
if ( SPIFFS . exists ( pathWithGz ) ) { // If the compressed file exists
2019-07-15 11:56:36 +02:00
path + = " .gz " ;
2020-04-22 08:21:24 +02:00
DebugPort . println ( " handleFileRead now .gz request: " + path ) ;
}
2019-01-20 11:14:45 +01:00
File file = SPIFFS . open ( path , " r " ) ; // Open it
2019-06-30 02:37:24 +02:00
if ( ! checkFile ( file ) ) { // check it is readable
2019-07-06 15:46:20 +02:00
file . close ( ) ; // if not, close the file
2019-06-30 02:37:24 +02:00
}
if ( ! file ) {
2019-07-06 15:46:20 +02:00
DebugPort . println ( " \t File exists, but could not be read? " ) ; // dodgy file - throw error back to client
String content ;
build500Response ( content , path ) ;
2020-04-22 08:21:24 +02:00
if ( res ) {
res - > setStatusCode ( 500 ) ;
res - > setStatusText ( " Internal server error " ) ;
res - > print ( content . c_str ( ) ) ;
}
2019-07-06 15:46:20 +02:00
return false ; // If the file is broken, return false
2019-06-30 02:37:24 +02:00
}
else {
2020-04-22 08:21:24 +02:00
if ( res ) {
streamFileSSL ( file , contentType , res ) ;
}
2019-07-06 15:46:20 +02:00
file . close ( ) ; // Then close the file
return true ;
2019-06-30 02:37:24 +02:00
}
2019-01-20 11:14:45 +01:00
}
DebugPort . println ( " \t File Not Found " ) ;
return false ; // If the file doesn't exist, return false
}
2020-04-22 08:21:24 +02:00
size_t streamFileSSL ( fs : : File & file , const String & contentType , HTTPResponse * res ) {
String filename = file . name ( ) ;
if ( filename . endsWith ( " gz " ) & &
contentType ! = String ( " application/x-gzip " ) & &
contentType ! = String ( " application/octet-stream " ) ) {
res - > setHeader ( " Content-Encoding " , " gzip " ) ;
}
res - > setHeader ( " Content-Type " , contentType . c_str ( ) ) ;
res - > setHeader ( " Content-Length " , httpsserver : : intToString ( file . size ( ) ) ) ;
DebugPort . print ( " Streaming " ) ;
// Read the file and write it to the response
uint8_t buffer [ 256 ] ;
size_t progressdot = 0 ;
size_t done = 0 ;
size_t length = file . read ( buffer , 256 ) ;
while ( length > 0 ) {
size_t wr = res - > write ( buffer , length ) ;
if ( wr > 0 ) {
done + = wr ;
if ( done > progressdot ) {
DebugPort . print ( " . " ) ;
progressdot + = 1024 ;
}
}
if ( wr < = 0 ) break ;
length = file . read ( buffer , 256 ) ;
}
return done ;
}
2019-07-06 15:46:20 +02:00
const char * stdHeader = R " =====(
< ! DOCTYPE html >
< html lang = " en " >
< head >
< meta charset = " utf-8 " / >
< meta http - equiv = " Pragma " content = " no-cache " >
< meta http - equiv = " Expires " content = " -1 " >
< meta http - equiv = " CACHE-CONTROL " content = " NO-CACHE " >
< style >
2019-07-11 10:55:31 +02:00
body {
font - family : Arial , Helvetica , sans - serif ;
}
button {
2020-05-03 13:59:56 +02:00
background - color : royalblue ;
color : white ;
2019-07-11 10:55:31 +02:00
border - radius : 25 px ;
2019-07-15 11:56:36 +02:00
height : 30 px ;
2019-07-11 10:55:31 +02:00
}
. del {
color : white ;
font - weight : bold ;
background - color : red ;
border - radius : 50 % ;
height : 30 px ;
width : 30 px ;
}
2019-07-15 11:56:36 +02:00
. redbutton {
2019-07-11 10:55:31 +02:00
color : white ;
font - weight : bold ;
background - color : red ;
}
th {
text - align : left ;
}
. throb {
animation : throbber 1 s linear infinite ;
}
@ keyframes throbber {
50 % {
opacity : 0 ;
}
}
2019-07-06 15:46:20 +02:00
< / style >
2019-07-15 11:56:36 +02:00
< script >
function _ ( el ) {
return document . getElementById ( el ) ;
}
< / script >
2019-07-06 15:46:20 +02:00
) = = = = = " ;
const char * updateIndex = R " =====(
2019-07-11 10:55:31 +02:00
< style >
body {
background - color : yellowgreen ;
}
. inputfile {
width : 0.1 px ;
height : 0.1 px ;
opacity : 0 ;
overflow : hidden ;
position : absolute ;
z - index : - 1 ;
}
. inputfile + label {
color : # fff ;
2020-05-03 13:59:56 +02:00
background - color : royalblue ;
2019-07-11 10:55:31 +02:00
display : inline - block ;
border - style : solid ;
border - radius : 25 px ;
border - width : medium ;
border - top - color : # E3E3E3 ;
border - left - color : # E3E3E3 ;
border - right - color : # 979797 ;
border - bottom - color : # 979797 ;
}
# filename {
font - weight : bold ;
font - style : italic ;
}
# upload {
font - weight : bold ;
font - style : italic ;
}
< / style >
2019-07-06 15:46:20 +02:00
< script >
// globals
var sendSize ;
2020-05-03 13:59:56 +02:00
var ws = null ;
2019-07-11 10:55:31 +02:00
var timeDown ;
var timeUp ;
2019-07-17 11:35:34 +02:00
var ajax ;
var uploadErr ;
2020-05-03 13:59:56 +02:00
var timedReload ;
2019-07-06 15:46:20 +02:00
function onWebSocket ( event ) {
2020-05-03 13:59:56 +02:00
console . log ( event . data ) ;
var res = JSON . parse ( event . data ) ;
2019-07-06 15:46:20 +02:00
var key ;
2020-05-03 13:59:56 +02:00
for ( key in res ) {
2019-07-06 15:46:20 +02:00
switch ( key ) {
2020-05-03 13:59:56 +02:00
case ' updateDone ' :
setTimeout ( function ( ) { location . replace ( ' / ' ) ; } , 10000 ) ;
break ;
case ' updateReload ' :
console . log ( " updateReload /update " ) ;
clearTimeout ( timedReload ) ;
setTimeout ( function ( ) { location . reload ( true ) ; } , res [ key ] ) ;
2020-04-22 08:21:24 +02:00
break ;
2020-05-03 13:59:56 +02:00
case ' updateProgress ' :
2019-07-06 15:46:20 +02:00
// actual data bytes received as fed back via web socket
2020-05-03 13:59:56 +02:00
var progress = res [ key ] ;
2019-07-18 14:28:40 +02:00
if ( progress > = 0 ) {
2019-07-17 11:35:34 +02:00
// normal progression
2019-07-18 14:28:40 +02:00
_ ( ' loaded_n_total ' ) . innerHTML = ' Uploaded ' + progress + ' bytes of ' + sendSize ;
var percent = Math . round ( 100 * ( progress / sendSize ) ) ;
2019-07-17 11:35:34 +02:00
_ ( ' progressBar ' ) . value = percent ;
_ ( ' status ' ) . innerHTML = percent + ' % uploaded . . please wait ' ;
uploadErr = ' ' ;
}
else {
// upload failure
_ ( ' progressBar ' ) . value = 0 ;
2019-07-18 14:28:40 +02:00
switch ( progress ) {
case - 1 : uploadErr = ' File too large - SPIFFS upload ABORTED ' ; break ;
case - 2 : uploadErr = ' Write error - SPIFFS upload ABORTED ' ; break ;
case - 3 : uploadErr = ' Update error - Firmware upload ABORTED ' ; break ;
case - 4 : uploadErr = ' Invalid file - Firmware upload ABORTED ' ; break ;
}
2019-07-17 11:35:34 +02:00
ajax . abort ( ) ;
}
2019-07-06 15:46:20 +02:00
break ;
}
}
}
function init ( ) {
2020-05-03 13:59:56 +02:00
console . log ( window . location ) ;
disableAll ( true ) ;
startWS ( ) ;
}
function startWS ( )
{
if ( ws ! = null )
delete ws ;
if ( window . location . protocol = = = ' https : ' ) {
ws = new WebSocket ( ' wss : //' + window.location.hostname + window.location.pathname);
}
else {
ws = new WebSocket ( ' ws : //' + window.location.hostname + window.location.pathname);
}
2019-07-06 15:46:20 +02:00
ws . onmessage = onWebSocket ;
2020-05-03 13:59:56 +02:00
ws . onerror = function ( ) { setTimeout ( startWS , 2000 ) ; } ;
ws . onopen = function ( ) { disableAll ( false ) ; } ;
2019-07-06 15:46:20 +02:00
}
2020-05-03 13:59:56 +02:00
2019-07-06 15:46:20 +02:00
function uploadFile ( ) {
2019-07-18 14:28:40 +02:00
_ ( ' upload_form ' ) . hidden = true ;
2019-07-17 11:35:34 +02:00
_ ( ' cancel ' ) . hidden = true ;
_ ( ' upload ' ) . hidden = true ;
_ ( ' progressBar ' ) . hidden = false ;
var file = _ ( ' file1 ' ) . files [ 0 ] ;
2019-07-06 15:46:20 +02:00
sendSize = file . size ;
2019-07-11 10:55:31 +02:00
console . log ( file ) ;
2019-07-06 15:46:20 +02:00
var JSONmsg = { } ;
2020-05-03 13:59:56 +02:00
JSONmsg . updateSize = sendSize ;
JSONmsg . updateFilename = file . name ;
2019-07-06 15:46:20 +02:00
var str = JSON . stringify ( JSONmsg ) ;
2019-07-17 11:35:34 +02:00
console . log ( ' JSON Tx : ' , str ) ;
2019-07-06 15:46:20 +02:00
ws . send ( str ) ;
2020-05-03 13:59:56 +02:00
var form = new FormData ( ) ;
form . append ( ' update ' , file ) ;
xhr = new XMLHttpRequest ( ) ;
2019-07-17 11:35:34 +02:00
// progress feedback is handled via websocket JSON sent from controller
2019-07-06 15:46:20 +02:00
// using server side progress only shows the buffer filling, not actual delivery.
2020-05-03 13:59:56 +02:00
xhr . open ( ' POST ' , ' / update ' ) ;
xhr . onload = completeHandler ;
xhr . onerror = errorHandler ;
xhr . onabort = abortHandler ;
xhr . send ( form ) ;
disableAll ( true ) ;
}
function disableAll ( en )
{
var x = document . getElementsByClassName ( " rename " ) ;
l = x . length ;
for ( i = 0 ; i < l ; i + + ) {
if ( en = = false )
enBtn ( x [ i ] ) ;
else
disEl ( x [ i ] ) ;
}
var x = document . getElementsByClassName ( " del " ) ;
l = x . length ;
for ( i = 0 ; i < l ; i + + ) {
if ( en = = false )
enDel ( x [ i ] )
else
disEl ( x [ i ] ) ;
}
if ( en = = false ) {
_ ( ' upload_form ' ) . hidden = false ;
_ ( ' cancel ' ) . hidden = false ;
_ ( ' status ' ) . innerHTML = ' ' ;
_ ( ' status ' ) . hidden = true ;
document . body . style . backgroundColor = ' yellowgreen ' ;
}
else {
_ ( ' upload_form ' ) . hidden = true ;
_ ( ' cancel ' ) . hidden = true ;
_ ( ' status ' ) . innerHTML = ' Please wait ' ;
_ ( ' status ' ) . hidden = false ;
document . body . style . backgroundColor = ' lightgrey ' ;
}
}
function disEl ( el )
{
el . disabled = true ;
el . style . color = ' darkgrey ' ;
el . style . backgroundColor = ' grey ' ;
}
function enBtn ( el )
{
el . disabled = false ;
el . style . color = ' white ' ;
el . style . backgroundColor = ' royalblue ' ;
}
function enDel ( el )
{
el . disabled = false ;
el . style . color = ' white ' ;
el . style . backgroundColor = ' red ' ;
}
function startReload ( tm )
{
// timedReload = setTimeout(function () { location.replace('/update'); }, tm);
timedReload = setTimeout ( function ( ) {
console . log ( ' initiating reload ' ) ;
ws . close ( ) ;
location . assign ( " /update " ) ;
} , tm ) ;
2019-07-06 15:46:20 +02:00
}
function completeHandler ( event ) {
2019-07-17 11:35:34 +02:00
_ ( ' status ' ) . innerHTML = event . target . responseText ;
_ ( ' progressBar ' ) . hidden = true ;
_ ( ' progressBar ' ) . value = 0 ;
_ ( ' loaded_n_total ' ) . innerHTML = ' Uploaded ' + sendSize + ' bytes of ' + sendSize ;
var file = _ ( ' file1 ' ) . files [ 0 ] ;
if ( file . name . endsWith ( ' . bin ' ) ) {
2019-07-23 13:11:29 +02:00
_ ( ' status ' ) . innerHTML = ' Rebooting NOW ' ;
setTimeout ( function ( ) { _ ( ' status ' ) . innerHTML = ' Rebooted ' ; } , 2000 ) ;
setTimeout ( function ( ) { _ ( ' status ' ) . innerHTML = ' Initialising . . . ' ; } , 4000 ) ;
2020-05-03 13:59:56 +02:00
setTimeout ( function ( ) { _ ( ' status ' ) . innerHTML = ' Loading / index . html . . . ' ; location . replace ( ' / ' ) ; } , 7500 ) ;
2019-07-06 15:46:20 +02:00
}
else {
2020-05-03 13:59:56 +02:00
startReload ( 500 ) ;
2019-07-06 15:46:20 +02:00
}
}
function errorHandler ( event ) {
2020-05-03 13:59:56 +02:00
console . log ( ' Error Handler ' , event ) ;
console . log ( ' Error Handler ' ) ;
_ ( ' status ' ) . innerHTML = ' Upload Error ? ' ;
_ ( ' status ' ) . style . color = ' red ' ;
startReload ( 2000 ) ;
2019-07-06 15:46:20 +02:00
}
function abortHandler ( event ) {
2019-07-17 11:35:34 +02:00
console . log ( ' Abort Handler ' + event ) ;
_ ( ' status ' ) . innerHTML = uploadErr ;
_ ( ' status ' ) . style . color = ' red ' ;
2020-05-03 13:59:56 +02:00
startReload ( 2000 ) ;
2019-07-06 15:46:20 +02:00
}
2020-05-03 13:59:56 +02:00
function ajaxSuccess ( ) {
console . log ( this . responseText ) ;
}
2019-07-06 15:46:20 +02:00
function onErase ( fn ) {
if ( confirm ( ' Do you really want to erase ' + fn + ' ? ' ) ) {
2020-05-03 13:59:56 +02:00
var JSONmsg = { } ;
JSONmsg . erase = fn ;
var str = JSON . stringify ( JSONmsg ) ;
console . log ( ' JSON Tx : ' , str ) ;
ws . send ( str ) ;
startReload ( 10000 ) ;
disableAll ( true ) ;
2019-07-06 15:46:20 +02:00
}
}
2019-07-15 11:56:36 +02:00
function onRename ( fn ) {
2020-05-03 13:59:56 +02:00
var nm = prompt ( ' Enter new file name ' , fn ) ;
if ( nm ! = null & & nm ! = ' ' ) {
var JSONmsg = { } ;
JSONmsg . renameFrom = fn ;
JSONmsg . renameTo = nm ;
var str = JSON . stringify ( JSONmsg ) ;
console . log ( ' JSON Tx : ' , str ) ;
ws . send ( str ) ;
startReload ( 10000 ) ;
disableAll ( true ) ;
}
2019-07-15 11:56:36 +02:00
}
2019-07-06 15:46:20 +02:00
function onBrowseChange ( ) {
2019-07-17 11:35:34 +02:00
_ ( ' uploaddiv ' ) . hidden = false ;
_ ( ' upload ' ) . hidden = false ;
_ ( ' status ' ) . hidden = false ;
_ ( ' loaded_n_total ' ) . hidden = false ;
_ ( ' spacer ' ) . hidden = false ;
var file = _ ( ' file1 ' ) . files [ 0 ] ;
2019-07-15 11:56:36 +02:00
_ ( ' filename ' ) . innerHTML = file . name ;
2019-07-11 10:55:31 +02:00
}
function onformatClick ( ) {
2020-05-03 13:59:56 +02:00
location . replace ( ' / formatspiffs ' ) ;
2019-07-06 15:46:20 +02:00
}
2019-07-11 10:55:31 +02:00
2019-07-06 15:46:20 +02:00
< / script >
< title > Afterburner update < / title >
2019-07-11 10:55:31 +02:00
< / head >
2019-07-17 11:35:34 +02:00
< body onload = ' javascript : init ( ) ' >
2019-07-06 15:46:20 +02:00
< h1 > Afterburner update < / h1 >
2019-07-11 10:55:31 +02:00
< form id = ' upload_form ' method = ' POST ' enctype = ' multipart / form - data ' autocomplete = ' off ' >
< input type = ' file ' name = ' file1 ' id = ' file1 ' class = ' inputfile ' onchange = ' onBrowseChange ( ) ' / >
< label for = ' file1 ' > & nbsp ; & nbsp ; Select a file to upload & nbsp ; & nbsp ; < / label >
2019-07-06 15:46:20 +02:00
< / form >
2019-07-11 10:55:31 +02:00
< p >
2019-07-17 11:35:34 +02:00
< div id = ' uploaddiv ' hidden > < span id = ' filename ' > < / span > & nbsp ; < button id = ' upload ' class = ' throb ' onclick = ' uploadFile ( ) ' hidden > Upload < / button >
2019-07-11 10:55:31 +02:00
< progress id = ' progressBar ' value = ' 0 ' max = ' 100 ' style = ' width : 300 px ; ' hidden > < / progress > < p > < / div >
< p id = ' spacer ' hidden > < / p >
2020-05-03 13:59:56 +02:00
< div > < button onclick = location . replace ( ' / ' ) id = ' cancel ' > Cancel < / button > < / div >
2019-07-11 10:55:31 +02:00
< h3 id = ' status ' hidden > < / h3 >
< div id = ' loaded_n_total ' hidden > < / div >
2019-07-06 15:46:20 +02:00
) = = = = = " ;
2020-05-09 11:17:21 +02:00
const char * wmConfigIndex = R " =====(
< ! DOCTYPE html >
< html lang = " en " >
< head >
< meta charset = " utf-8 " / >
< meta http - equiv = " Pragma " content = " no-cache " >
< meta http - equiv = " Expires " content = " -1 " >
< meta http - equiv = " CACHE-CONTROL " content = " NO-CACHE " >
< script >
function init ( ) {
setTimeout ( function ( ) { location . assign ( " / " ) ; } , 15000 ) ;
}
< / script >
< title > Launching Afterburner Wifi Manager < / title >
< / head >
< body onload = ' javascript : init ( ) ' >
< h1 > Launching Afterburner Wifi Manager < / h1 >
< p >
< h2 > This page will automatically reload in 15 seconds , please wait . < / h2 >
< i > If auto reload fails , try manually refreshing the web page < / i >
< h1 > You may need to reconnect to the Afterburner ' s AP following the reboot < / h1 >
< / body >
< / html >
) = = = = = " ;
2019-07-06 15:46:20 +02:00
2020-04-22 08:21:24 +02:00
void onWMConfig ( HTTPRequest * req , httpsserver : : HTTPResponse * res )
2019-06-30 02:37:24 +02:00
{
2019-05-22 13:08:38 +02:00
DebugPort . println ( " WEB: GET /wmconfig " ) ;
2020-05-09 11:17:21 +02:00
res - > print ( wmConfigIndex ) ;
2019-09-21 01:58:51 +02:00
DebugPort . println ( " Starting web portal for wifi config " ) ;
2020-05-09 11:17:21 +02:00
wmReboot newMode ( true ) ;
newMode . startPortal = true ;
newMode . eraseCreds = false ;
newMode . delay = 500 ;
scheduleWMreboot ( newMode ) ;
2019-01-11 09:28:22 +01:00
}
2019-01-13 20:59:32 +01:00
2020-04-22 08:21:24 +02:00
void onResetWifi ( HTTPRequest * req , httpsserver : : HTTPResponse * res )
2019-06-30 02:37:24 +02:00
{
2019-05-22 13:08:38 +02:00
DebugPort . println ( " WEB: GET /resetwifi " ) ;
2020-04-22 08:21:24 +02:00
res - > print ( " Start Config Portal - Resetting Wifi credentials! " ) ;
2019-09-21 01:58:51 +02:00
DebugPort . println ( " diconnecting client and wifi, then rebooting " ) ;
2020-05-09 11:17:21 +02:00
wmReboot newMode ( true ) ;
newMode . startPortal = true ;
newMode . eraseCreds = true ;
newMode . delay = 500 ;
scheduleWMreboot ( newMode ) ;
2019-01-11 09:28:22 +01:00
}
2019-02-11 09:34:11 +01:00
2020-04-22 08:21:24 +02:00
void rootRedirect ( HTTPRequest * req , httpsserver : : HTTPResponse * res )
2019-06-30 02:37:24 +02:00
{
2020-04-22 08:21:24 +02:00
res - > setHeader ( " Location " , " / " ) ; // reselect the update page
res - > setStatusCode ( 303 ) ;
2018-11-07 05:07:11 +01:00
}
2020-05-03 13:59:56 +02:00
// pass new data for websocket send via a queue
2019-07-07 09:18:38 +02:00
bool sendWebSocketString ( const char * Str )
2018-12-19 13:07:51 +01:00
{
2020-05-03 13:59:56 +02:00
if ( webSocketQueue ) {
char * pMsg = new char [ strlen ( Str ) + 1 ] ;
strcpy ( pMsg , Str ) ;
xQueueSend ( webSocketQueue , & pMsg , 0 ) ;
2020-04-22 08:21:24 +02:00
return true ;
2019-09-21 01:58:51 +02:00
}
2018-12-01 19:25:10 +01:00
return false ;
2020-05-03 13:59:56 +02:00
}
2020-04-22 08:21:24 +02:00
2020-05-03 13:59:56 +02:00
// query queue for new messages to send to websocket(s)
void processWebsocketQueue ( )
{
char * pMsg ;
if ( webSocketQueue ) {
if ( xQueueReceive ( webSocketQueue , & pMsg , 0 ) ) {
if ( pMsg = = NULL ) {
2020-05-07 02:12:20 +02:00
// DebugPort.println("websocket send NULL averted");
2020-05-03 13:59:56 +02:00
}
else {
2020-05-07 02:12:20 +02:00
// DebugPort.printf("websocket len=%d\r\n", strlen(pMsg));
2020-05-03 13:59:56 +02:00
for ( int i = 0 ; i < MAX_CLIENTS ; i + + ) {
if ( activeClients [ i ] ) {
bTxWebData = true ; // OLED tx data animation flag
activeClients [ i ] - > send ( pMsg , WebsocketHandler : : SEND_TYPE_TEXT ) ;
// DebugPort.println("->");
}
}
2020-05-13 02:37:31 +02:00
delete [ ] pMsg ;
2020-05-03 13:59:56 +02:00
}
2020-04-22 08:21:24 +02:00
}
}
2018-11-07 05:07:11 +01:00
}
2018-12-19 13:07:51 +01:00
2019-07-07 09:18:38 +02:00
bool isWebSocketClientChange ( )
{
2019-09-21 01:58:51 +02:00
static int prevNumClients = - 1 ;
2020-04-22 08:21:24 +02:00
# ifdef OLD_SERVER
2019-09-21 01:58:51 +02:00
int numClients = webSocket . connectedClients ( ) ;
2020-04-22 08:21:24 +02:00
# else
int numClients = 0 ;
for ( int i = 0 ; i < MAX_CLIENTS ; i + + ) {
if ( activeClients [ i ] ) numClients + + ;
}
# endif
2019-09-21 01:58:51 +02:00
if ( numClients ! = prevNumClients ) {
bool retval = numClients > prevNumClients ;
prevNumClients = numClients ;
if ( retval ) {
DebugPort . println ( " Increased number of web socket clients, should reset JSON moderator " ) ;
return true ;
}
}
2019-07-07 09:18:38 +02:00
return false ;
}
2018-12-01 19:25:10 +01:00
bool hasWebClientSpoken ( bool reset )
{
2019-09-21 01:58:51 +02:00
bool retval = bRxWebData ;
if ( reset )
bRxWebData = false ;
return retval ;
2018-12-01 19:25:10 +01:00
}
bool hasWebServerSpoken ( bool reset )
{
2019-09-21 01:58:51 +02:00
bool retval = bTxWebData ;
if ( reset )
bTxWebData = false ;
return retval ;
2018-11-26 11:26:38 +01:00
}
2018-12-11 11:19:02 +01:00
2019-05-22 22:35:09 +02:00
void setUploadSize ( long val )
{
_SuppliedFileSize = val ;
} ;
2019-06-30 02:37:24 +02:00
// Sometimes SPIFFS gets corrupted (WTF?)
// When this happens, you can see the files exist, but you cannot read them
// This routine checks the file is readable.
// Typical failure mechanism is read returns 0, and the WifiClient upload never progresses
// The software watchdog then steps in after 15 seconds of that nonsense
bool checkFile ( File & file )
{
uint8_t buf [ 128 ] ;
bool bOK = true ;
size_t available = file . available ( ) ;
while ( available ) {
int toRead = ( available > 128 ) ? 128 : available ;
int Read = file . read ( buf , toRead ) ;
if ( Read ! = toRead ) {
bOK = false ;
DebugPort . printf ( " SPIFFS precautionary file check failed for %s \r \n " , file . name ( ) ) ;
break ;
}
available = file . available ( ) ;
}
file . seek ( 0 ) ;
return bOK ;
}
2019-07-07 09:18:38 +02:00
void listSPIFFS ( const char * dirname , uint8_t levels , String & HTMLreport , int withHTMLanchors )
2019-06-30 02:37:24 +02:00
{
char msg [ 128 ] ;
2019-07-07 09:18:38 +02:00
File root = SPIFFS . open ( dirname ) ;
2019-06-30 02:37:24 +02:00
if ( ! root ) {
sprintf ( msg , " Failed to open directory \" %s \" " , dirname ) ;
DebugPort . println ( msg ) ;
HTMLreport + = msg ; HTMLreport + = " <br> " ;
return ;
}
if ( ! root . isDirectory ( ) ) {
sprintf ( msg , " \" %s \" is not a directory " , dirname ) ;
DebugPort . println ( msg ) ;
HTMLreport + = msg ; HTMLreport + = " <br> " ;
return ;
}
2019-07-06 15:46:20 +02:00
HTMLreport + = " <hr><h3>Current SPIFFS contents:</h3> " ;
// create HTML table header
HTMLreport + = R " =====(<table>
< tr >
< th > < / th >
< th style = " width:200px " > Name < / th >
< th style = " width:60px " > Size < / th >
< th > < / th >
2019-07-15 11:56:36 +02:00
< th > < / th >
2019-07-06 15:46:20 +02:00
< / tr >
) = = = = = " ;
2019-06-30 02:37:24 +02:00
File file = root . openNextFile ( ) ;
while ( file ) {
2019-07-06 15:46:20 +02:00
HTMLreport + = " <tr> \n " ;
2019-06-30 02:37:24 +02:00
if ( file . isDirectory ( ) ) {
2019-07-06 15:46:20 +02:00
addTableData ( HTMLreport , " DIR " ) ;
addTableData ( HTMLreport , file . name ( ) ) ;
addTableData ( HTMLreport , " " ) ;
addTableData ( HTMLreport , " " ) ;
2019-07-15 11:56:36 +02:00
addTableData ( HTMLreport , " " ) ;
2019-07-06 15:46:20 +02:00
2019-06-30 02:37:24 +02:00
sprintf ( msg , " DIR : %s " , file . name ( ) ) ;
DebugPort . println ( msg ) ;
2019-07-06 15:46:20 +02:00
2019-06-30 02:37:24 +02:00
if ( levels ) {
2019-07-07 09:18:38 +02:00
listSPIFFS ( file . name ( ) , levels - 1 , HTMLreport ) ;
2019-06-30 02:37:24 +02:00
}
} else {
String fn = file . name ( ) ;
2019-07-06 15:46:20 +02:00
String ers ;
2019-07-15 11:56:36 +02:00
String rename ;
if ( withHTMLanchors = = 2 ) {
2020-03-23 06:54:15 +01:00
String htmlNm = fn ;
htmlNm . replace ( " " , " %20 " ) ;
rename = " <button class='rename' onClick=onRename(' " + htmlNm + " ')>Rename</button> " ;
ers = " <input class='del' type='button' value='X' onClick=onErase(' " + htmlNm + " ')> " ;
2019-07-15 11:56:36 +02:00
}
2019-06-30 02:37:24 +02:00
if ( withHTMLanchors ) {
2019-07-15 11:56:36 +02:00
String fn2 ;
2019-07-17 11:35:34 +02:00
if ( fn . endsWith ( " .html " ) ) {
2019-07-15 22:29:23 +02:00
// can hyperlink .html files
2019-07-15 11:56:36 +02:00
fn2 = fn ;
}
2019-07-17 11:35:34 +02:00
else if ( fn . endsWith ( " .html.gz " ) ) {
// we can hyperlink .html.gz files but we must strip .gz extension for
2019-07-15 22:29:23 +02:00
// the hyperlink otherwise you get asked if you want to download the .gz, not view web page!
fn2 = fn ;
fn2 . remove ( fn2 . length ( ) - 3 , 3 ) ; // strip trailing ".gz"
2019-07-15 11:56:36 +02:00
}
if ( fn2 . length ( ) ! = 0 ) {
2020-03-23 06:54:15 +01:00
fn2 . replace ( " " , " %20 " ) ;
2019-07-15 22:29:23 +02:00
// create hyperlink if web page file
2019-07-15 11:56:36 +02:00
fn = " <a href= \" " + fn2 + " \" > " + file . name ( ) + " </a> " ;
2019-06-30 02:37:24 +02:00
}
}
2019-07-15 22:29:23 +02:00
String sz ( int ( file . size ( ) ) ) ;
2019-07-06 15:46:20 +02:00
addTableData ( HTMLreport , " " ) ;
addTableData ( HTMLreport , fn ) ;
addTableData ( HTMLreport , sz ) ;
2019-07-15 11:56:36 +02:00
addTableData ( HTMLreport , rename ) ;
2019-07-06 15:46:20 +02:00
addTableData ( HTMLreport , ers ) ;
sprintf ( msg , " FILE: %s SIZE: %d " , fn . c_str ( ) , file . size ( ) ) ;
2019-06-30 02:37:24 +02:00
DebugPort . println ( msg ) ;
}
2019-07-06 15:46:20 +02:00
HTMLreport + = " </tr> \n " ;
2019-06-30 02:37:24 +02:00
file = root . openNextFile ( ) ;
}
2019-07-06 15:46:20 +02:00
HTMLreport + = " </table> \n " ;
if ( withHTMLanchors ) {
char usage [ 128 ] ;
int used = SPIFFS . usedBytes ( ) ;
int total = SPIFFS . totalBytes ( ) ;
float percent = used * 100. / total ;
2019-07-07 09:18:38 +02:00
sprintf ( usage , " <p><b>Usage</b><br> %d / %d bytes (%.1f%%) \n <p> " , used , total , percent ) ;
2019-07-06 15:46:20 +02:00
HTMLreport + = usage ;
}
2019-06-30 02:37:24 +02:00
}
2019-07-06 15:46:20 +02:00
void addTableData ( String & HTML , String dta )
{
HTML + = " <td> " ;
HTML + = dta ;
HTML + = " </td> \n " ;
}
2020-04-22 08:21:24 +02:00
2019-07-06 15:46:20 +02:00
// function called upon completion of file (form) upload
2020-04-22 08:21:24 +02:00
void onUploadCompletion ( HTTPRequest * req , HTTPResponse * res )
2019-07-06 15:46:20 +02:00
{
2019-07-09 14:19:21 +02:00
_SuppliedFileSize = 0 ;
2019-07-06 15:46:20 +02:00
DebugPort . println ( " WEB: POST /updatenow completion " ) ;
// completion functionality
2019-07-18 14:28:40 +02:00
if ( BrowserUpload . isSPIFFSupload ( ) ) {
if ( BrowserUpload . isOK ( ) ) {
2019-08-03 04:42:49 +02:00
checkSplashScreenUpdate ( ) ;
2019-07-06 15:46:20 +02:00
DebugPort . println ( " WEB: SPIFFS OK " ) ;
2020-04-22 08:21:24 +02:00
// server.send(200, "text/plain", "OK - File uploaded to SPIFFS");
res - > setStatusCode ( 200 ) ;
res - > setHeader ( " Content-Type " , " text/plain " ) ;
res - > print ( " OK - File uploaded to SPIFFS " ) ;
2019-07-06 15:46:20 +02:00
// javascript reselects the /update page!
}
else {
DebugPort . println ( " WEB: SPIFFS FAIL " ) ;
2020-04-22 08:21:24 +02:00
// server.send(500, "text/plain", "500: couldn't create file");
res - > setStatusCode ( 500 ) ;
res - > setHeader ( " Content-Type " , " text/plain " ) ;
res - > print ( " 500: couldn't create file " ) ;
2019-07-06 15:46:20 +02:00
}
2019-07-18 14:28:40 +02:00
BrowserUpload . reset ( ) ;
2020-04-22 08:21:24 +02:00
# if USE_SSL_LOOP_TASK != 1
2019-07-28 12:59:26 +02:00
ShowOTAScreen ( - 1 , eOTAbrowser ) ; // browser update
2020-04-22 08:21:24 +02:00
# endif
2020-05-03 13:59:56 +02:00
if ( pUpdateHandler )
pUpdateHandler - > send ( " { \" updateReload \" :1000} " , WebsocketHandler : : SEND_TYPE_TEXT ) ;
2019-07-06 15:46:20 +02:00
}
else {
2019-07-18 14:28:40 +02:00
if ( BrowserUpload . isOK ( ) ) {
DebugPort . println ( " WEB: FIRMWARE UPDATE OK " ) ;
2020-04-22 08:21:24 +02:00
// server.send(200, "text/plain", "OK - Afterburner will reboot shortly");
res - > setStatusCode ( 200 ) ;
res - > setHeader ( " Content-Type " , " text/plain " ) ;
res - > print ( " OK - Afterburner will reboot shortly " ) ;
2019-07-06 15:46:20 +02:00
}
else {
2019-07-18 14:28:40 +02:00
DebugPort . println ( " WEB: FIRMWARE UPDATE FAIL " ) ;
2020-04-22 08:21:24 +02:00
// server.send(200, "text/plain", "FAIL - Afterburner will reboot shortly");
res - > setStatusCode ( 200 ) ;
res - > setHeader ( " Content-Type " , " text/plain " ) ;
res - > print ( " FAIL - Afterburner will reboot shortly " ) ;
2019-07-06 15:46:20 +02:00
}
2020-04-22 08:21:24 +02:00
// rootRedirect(req, res);
2019-07-25 13:27:57 +02:00
forceBootInit ( ) ;
2020-05-03 13:59:56 +02:00
// initate reboot
const char * content [ 2 ] ;
content [ 0 ] = " New firmware upload " ;
content [ 1 ] = " completed " ;
ScreenManager . showRebootMsg ( content , 1000 ) ;
2019-07-06 15:46:20 +02:00
}
}
2020-04-22 08:21:24 +02:00
void onUpload ( HTTPRequest * req , HTTPResponse * res )
2019-07-06 15:46:20 +02:00
{
2020-04-22 08:21:24 +02:00
if ( req - > getMethod ( ) = = " GET " ) {
onUploadBegin ( req , res ) ;
}
if ( req - > getMethod ( ) = = " POST " ) {
onUploadProgression ( req , res ) ;
2019-07-06 15:46:20 +02:00
}
2020-04-22 08:21:24 +02:00
}
void onUploadBegin ( HTTPRequest * req , HTTPResponse * res )
{
DebugPort . println ( " WEB: GET /update " ) ;
if ( ! checkAuthentication ( req , res ) )
return ;
2020-05-03 13:59:56 +02:00
2019-07-06 15:46:20 +02:00
bUpdateAccessed = true ;
bFormatAccessed = false ;
2019-07-11 10:55:31 +02:00
bFormatPerformed = false ;
2019-07-06 15:46:20 +02:00
# ifdef USE_EMBEDDED_WEBUPDATECODE
String SPIFFSinfo ;
2019-07-07 09:18:38 +02:00
listSPIFFS ( " / " , 2 , SPIFFSinfo , 2 ) ;
2019-07-06 15:46:20 +02:00
String content = stdHeader ;
2020-05-03 13:59:56 +02:00
content + = updateIndex ;
// content += "<div id='spiffs'>" + SPIFFSinfo + "</div>";
content + = SPIFFSinfo ;
2019-07-15 11:56:36 +02:00
content + = " <p><button class='redbutton' onclick='onformatClick()'>Format SPIFFS</button> " ;
2019-07-06 15:46:20 +02:00
content + = " </body></html> " ;
2020-04-22 08:21:24 +02:00
res - > setStatusCode ( 200 ) ;
res - > setHeader ( " Content-Type " , " text/html " ) ;
res - > print ( content ) ;
2020-05-03 13:59:56 +02:00
res - > finalize ( ) ;
2020-04-22 08:21:24 +02:00
2019-07-06 15:46:20 +02:00
# else
handleFileRead ( " /uploadfirmware.html " ) ;
# endif
}
2020-04-22 08:21:24 +02:00
void onUploadProgression ( HTTPRequest * req , httpsserver : : HTTPResponse * res )
2019-07-06 15:46:20 +02:00
{
2019-07-18 14:28:40 +02:00
char JSON [ 64 ] ;
2020-04-22 08:21:24 +02:00
if ( ! bUpdateAccessed ) { // only allow progression via /update, attempts to directly access /updatenow will fail
2020-05-03 13:59:56 +02:00
DebugPort . println ( " WEB: POST /update forbidden entry " ) ;
res - > setHeader ( " Location " , " /update " ) ; // reselect the update page
res - > setStatusCode ( 303 ) ;
2020-04-22 08:21:24 +02:00
}
else {
if ( ! checkAuthentication ( req , res ) ) {
// attempt to POST without using /update - forced redirect to root
bUpdateAccessed = false ;
return ;
}
HTTPUpload upload ;
HTTPBodyParser * parser ;
std : : string contentType = req - > getHeader ( " Content-Type " ) ;
size_t semicolonPos = contentType . find ( " ; " ) ;
if ( semicolonPos ! = std : : string : : npos ) {
contentType = contentType . substr ( 0 , semicolonPos ) ;
}
if ( contentType = = " multipart/form-data " ) {
parser = new HTTPMultipartBodyParser ( req ) ;
} else {
Serial . printf ( " Unknown POST Content-Type: %s \n " , contentType . c_str ( ) ) ;
return ;
}
2019-07-09 14:19:21 +02:00
2020-04-22 08:21:24 +02:00
// We iterate over the fields. Any field with a filename is uploaded
while ( parser - > nextField ( ) ) {
std : : string name = parser - > getFieldName ( ) ;
std : : string sfilename = parser - > getFieldFilename ( ) ;
std : : string mimeType = parser - > getFieldMimeType ( ) ;
DebugPort . printf ( " onUploadProgression: field name='%s', filename='%s', mimetype='%s' \n " , name . c_str ( ) , sfilename . c_str ( ) , mimeType . c_str ( ) ) ;
// Double check that it is what we expect
if ( name ! = " update " ) {
Serial . println ( " Skipping unexpected field " ) ;
break ;
}
// Should check file name validity and all that, but we skip that.
String filename = sfilename . c_str ( ) ;
if ( filename [ 0 ] ! = ' / ' )
filename = " / " + filename ;
upload . filename = filename ;
upload . name = name . c_str ( ) ;
upload . type = mimeType . c_str ( ) ;
upload . totalSize = 0 ;
upload . currentSize = 0 ;
int sts = BrowserUpload . begin ( filename , _SuppliedFileSize ) ; // _SuppliedFileSize come in via websocket
2020-05-03 13:59:56 +02:00
if ( sts < 0 ) {
break ;
}
else {
2020-05-07 03:51:15 +02:00
if ( pUpdateHandler ) {
sprintf ( JSON , " { \" updateProgress \" :%d} " , sts ) ;
2020-05-03 13:59:56 +02:00
pUpdateHandler - > send ( JSON , WebsocketHandler : : SEND_TYPE_TEXT ) ;
2020-05-07 03:51:15 +02:00
}
2020-05-03 13:59:56 +02:00
}
2019-07-06 15:46:20 +02:00
2020-04-22 08:21:24 +02:00
while ( ! parser - > endOfField ( ) ) {
2020-05-13 02:37:31 +02:00
// file upload and writing to SPIFFS is not a happy combination as the web server is running at an elevated level here
// best to pass the data to the normal Arduino processing task via a queue, but maintain synchronism with the processing
// by spinning here until ready.
while ( ! BrowserUpload . Ready ( ) ) {
taskYIELD ( ) ;
}
esp_task_wdt_reset ( ) ;
2020-04-22 08:21:24 +02:00
upload . currentSize = parser - > read ( upload . buf , HTTP_UPLOAD_BUFLEN ) ;
2020-05-13 02:37:31 +02:00
BrowserUpload . queueFragment ( upload ) ; // let user task process the fresh data
while ( ! BrowserUpload . Ready ( ) ) {
taskYIELD ( ) ;
}
esp_task_wdt_reset ( ) ;
// sts = BrowserUpload.fragment(upload, res);
sts = BrowserUpload . queueResult ( ) ;
2020-04-22 08:21:24 +02:00
if ( sts < 0 ) {
2020-05-07 03:51:15 +02:00
if ( pUpdateHandler ) {
sprintf ( JSON , " { \" updateProgress \" :%d} " , sts ) ;
2020-05-03 13:59:56 +02:00
pUpdateHandler - > send ( JSON , WebsocketHandler : : SEND_TYPE_TEXT ) ;
2020-05-07 03:51:15 +02:00
}
2020-05-13 02:37:31 +02:00
DebugPort . printf ( " Upload code %d \r \n " , sts ) ;
2020-04-22 08:21:24 +02:00
break ;
}
else {
// upload still in progress?
if ( BrowserUpload . bUploadActive ) { // show progress unless a write error has occured
2020-05-13 02:37:31 +02:00
// DebugPort.printf(" p%d ", uxTaskPriorityGet(NULL));
// DebugPort.print(".");
2020-04-22 08:21:24 +02:00
if ( upload . totalSize ) {
// feed back bytes received over web socket for progressbar update on browser (via javascript)
2020-05-07 03:51:15 +02:00
if ( pUpdateHandler ) {
sprintf ( JSON , " { \" updateProgress \" :%d} " , upload . totalSize ) ;
2020-05-03 13:59:56 +02:00
pUpdateHandler - > send ( JSON , WebsocketHandler : : SEND_TYPE_TEXT ) ;
2020-05-07 03:51:15 +02:00
}
2020-04-22 08:21:24 +02:00
}
2020-05-13 02:37:31 +02:00
/* // show percentage on OLED
2020-04-22 08:21:24 +02:00
int percent = 0 ;
if ( _SuppliedFileSize )
percent = 100 * upload . totalSize / _SuppliedFileSize ;
# if USE_SSL_LOOP_TASK != 1
ShowOTAScreen ( percent , eOTAbrowser ) ; // browser update
# endif
2020-05-13 02:37:31 +02:00
*/
2019-07-18 14:28:40 +02:00
}
2019-07-06 15:46:20 +02:00
}
}
2020-05-03 13:59:56 +02:00
sts = BrowserUpload . end ( upload ) ;
2020-05-07 03:51:15 +02:00
if ( pUpdateHandler ) {
sprintf ( JSON , " { \" updateProgress \" :%d} " , sts ) ;
2020-05-03 13:59:56 +02:00
pUpdateHandler - > send ( JSON , WebsocketHandler : : SEND_TYPE_TEXT ) ;
2020-05-07 03:51:15 +02:00
if ( ! BrowserUpload . isSPIFFSupload ( ) ) {
sprintf ( JSON , " { \" updateDone \" :1} " ) ;
2020-05-03 13:59:56 +02:00
pUpdateHandler - > send ( JSON , WebsocketHandler : : SEND_TYPE_TEXT ) ;
2020-05-07 03:51:15 +02:00
}
2020-05-03 13:59:56 +02:00
}
2020-04-22 08:21:24 +02:00
}
2019-07-17 11:35:34 +02:00
2020-04-22 08:21:24 +02:00
bUpdateAccessed = false ; // close gate on POST to /updatenow
delete parser ;
onUploadCompletion ( req , res ) ;
2020-05-03 13:59:56 +02:00
res - > finalize ( ) ;
2020-04-22 08:21:24 +02:00
2019-07-06 15:46:20 +02:00
}
}
2020-04-22 08:21:24 +02:00
2019-07-06 15:46:20 +02:00
/***************************************************************************************
* FORMAT SPIFFS HANDLING
*
* User must first access / formatspiffs .
* If not already authenticated , an Username / Password challenge is presented
* If that passes , bFormatAccessed is set , unlocking access to the / formatnow path
* The presneted web page offers Format and Cancel button .
* Cancel will immediatly return to the file upload path ' / update '
* Format will then present a confirmation dialog , user must press Yes to proceed .
*
* Assuming Yes was pressed , a HTTP POST to / format now with the payload ' confirm ' = ' yes ' is performed
* The / formatnow handler will check that confirm does equal yes , and that bFormatAccessed was set
* If all good SPIFFS is re - formatted - no response is set .
* The javascript though from the / formatspiffs page performs a reload shortly after the post ( 200 ms timeout )
*
* As bFormatAccessed is still set , a confimration page is the presented advising files now need to be uploaded
* A button allows direct access to / update
*/
2020-04-22 08:21:24 +02:00
void onFormatSPIFFS ( HTTPRequest * req , HTTPResponse * res )
{
DebugPort . println ( " WEB: GET /formatspiffs " ) ;
bUpdateAccessed = false ;
String content = stdHeader ;
if ( ! bFormatPerformed ) {
if ( checkAuthentication ( req , res ) ) {
bFormatAccessed = true ; // only set after we pass authentication
content + = formatIndex ;
res - > setStatusCode ( 200 ) ;
res - > setHeader ( " Content-Type " , " text/html " ) ;
res - > print ( content ) ;
}
}
else {
bFormatAccessed = false ;
bFormatPerformed = false ;
content + = formatDoneContent ;
res - > setStatusCode ( 200 ) ;
res - > setHeader ( " Content-Type " , " text/html " ) ;
res - > print ( content ) ;
}
2020-05-03 13:59:56 +02:00
res - > finalize ( ) ;
2020-04-22 08:21:24 +02:00
}
2020-05-03 13:59:56 +02:00
2019-07-06 15:46:20 +02:00
2019-07-11 10:55:31 +02:00
const char * formatDoneContent = R " =====(
< style >
body {
background - color : yellow ;
}
< / style >
< / head >
< body >
2019-07-06 15:46:20 +02:00
< h1 > SPIFFS partition has been formatted < / h1 >
< h3 > You must now upload the web content . < / h3 >
2020-03-25 10:28:12 +01:00
< p > Latest web content can be downloaded from < a href = ' http : //afterburner.mrjones.id.au/firmware.html' target='_blank'>http://afterburner.mrjones.id.au/firmware.html</a>
2019-07-06 15:46:20 +02:00
< h4 class = " throb " >Please ensure you unzip the web page content, then upload all the files contained.</h4>
2019-07-17 11:35:34 +02:00
< p > < button onclick = location . assign ( ' / update ' ) > Upload web content < / button >
2019-07-06 15:46:20 +02:00
< / body >
< / html >
) = = = = = " ;
const char * formatIndex = R " =====(
2019-07-11 10:55:31 +02:00
< style >
body {
background - color : orangered ;
}
< / style >
2019-07-06 15:46:20 +02:00
< script >
function init ( ) {
}
function onFormat ( ) {
2020-05-03 13:59:56 +02:00
var form = new FormData ( ) ;
2019-07-06 15:46:20 +02:00
if ( confirm ( ' Do you really want to reformat the SPIFFS partition ? ' ) ) {
2019-07-15 11:56:36 +02:00
_ ( ' throb ' ) . innerHTML = ' FORMATTING - Please wait ' ;
2020-05-03 13:59:56 +02:00
form . append ( ' confirm ' , ' yes ' ) ;
timedReload = setTimeout ( function ( ) { location . assign ( ' / update ' ) ; } , 10000 ) ;
2019-07-06 15:46:20 +02:00
}
else {
2020-05-03 13:59:56 +02:00
form . append ( ' confirm ' , ' no ' ) ;
timedReload = setTimeout ( function ( ) { location . assign ( ' / update ' ) ; } , 20 ) ;
2019-07-06 15:46:20 +02:00
}
2020-05-03 13:59:56 +02:00
var xhr = new XMLHttpRequest ( ) ;
xhr . open ( ' POST ' , ' / formatspiffs ' ) ;
xhr . send ( form ) ;
2019-07-06 15:46:20 +02:00
}
< / script >
< title > Afterburner SPIFFS format < / title >
2019-07-11 10:55:31 +02:00
< / head >
2019-07-17 11:35:34 +02:00
< body onload = ' javascript : init ( ) ' >
2019-07-06 15:46:20 +02:00
< h1 > Format SPIFFS partition < / h1 >
2019-07-11 10:55:31 +02:00
< h3 class = ' throb ' id = ' throb ' > CAUTION ! This will erase all web content < / h1 >
2019-07-15 11:56:36 +02:00
< p > < button class = ' redbutton ' onClick = ' onFormat ( ) ' > Format < / button > < br >
2019-07-11 10:55:31 +02:00
< p > < a href = ' / update ' > < button > Cancel < / button > < / a >
2019-07-06 15:46:20 +02:00
< / body >
< / html >
) = = = = = " ;
2020-04-22 08:21:24 +02:00
void onFormatNow ( HTTPRequest * req , httpsserver : : HTTPResponse * res )
2019-07-06 15:46:20 +02:00
{
2020-04-22 08:21:24 +02:00
if ( req - > getMethod ( ) = = " GET " ) {
onFormatSPIFFS ( req , res ) ;
// DebugPort.println("WEB: GET /formatnow - ILLEGAL - root redirect");
// rootRedirect(req, res);
2019-07-06 15:46:20 +02:00
}
2020-04-22 08:21:24 +02:00
if ( req - > getMethod ( ) = = " POST " ) {
// HTTP POST handler, do not need to return a web page!
DebugPort . println ( " WEB: POST /formatnow " ) ;
std : : string value ;
findFormArg ( req , " confirm " , value ) ;
if ( value = = " yes " & & bFormatAccessed ) { // confirm user agrees, and we did pass thru /formatspiffs first
DebugPort . println ( " Formatting SPIFFS partition " ) ;
SPIFFS . format ( ) ; // re-format the SPIFFS partition
bFormatPerformed = true ;
}
else {
bFormatAccessed = false ; // user cancelled upon last confirm popup, or not authenticated access
bFormatPerformed = false ;
rootRedirect ( req , res ) ;
}
2019-07-06 15:46:20 +02:00
}
}
2020-04-22 08:21:24 +02:00
void onReboot ( HTTPRequest * req , httpsserver : : HTTPResponse * res )
{
if ( req - > getMethod ( ) = = " GET " ) {
DebugPort . println ( " WEB: GET /reboot " ) ;
String content = stdHeader ;
content + = rebootIndex ;
res - > print ( content ) ;
2020-05-03 13:59:56 +02:00
res - > finalize ( ) ;
2020-04-22 08:21:24 +02:00
}
if ( req - > getMethod ( ) = = " POST " ) {
// HTTP POST handler, do not need to return a web page!
DebugPort . println ( " WEB: POST /reboot " ) ;
// First, we need to check the encoding of the form that we have received.
// The browser will set the Content-Type request header, so we can use it for that purpose.
std : : string value ;
if ( findFormArg ( req , " reboot " , value ) ) {
if ( value = = " yes " ) { // confirm user agrees, and we did pass thru /formatspiffs first
DebugPort . println ( " Rebooting via /reboot " ) ;
2020-05-03 13:59:56 +02:00
// initate reboot
const char * content [ 2 ] ;
content [ 0 ] = " /reboot " ;
content [ 1 ] = " initiated " ;
ScreenManager . showRebootMsg ( content , 1000 ) ;
// ESP.restart();
2020-04-22 08:21:24 +02:00
}
}
}
2019-07-15 11:56:36 +02:00
}
const char * rebootIndex = R " =====(
< style >
body {
background - color : orangered ;
}
< / style >
< script >
function onReboot ( ) {
if ( confirm ( ' Do you really want to reboot the Afterburner ? ' ) ) {
2019-07-23 13:11:29 +02:00
_ ( ' info ' ) . innerHTML = ' Rebooting NOW ' ;
setTimeout ( function ( ) { _ ( ' info ' ) . innerHTML = ' Rebooted ' ; } , 2000 ) ;
setTimeout ( function ( ) { _ ( ' info ' ) . innerHTML = ' Initialising . . . ' ; } , 4000 ) ;
2019-07-23 14:56:04 +02:00
setTimeout ( function ( ) { _ ( ' info ' ) . innerHTML = ' Loading / index . html . . . ' ; location . assign ( ' / ' ) ; } , 7500 ) ;
2020-05-03 13:59:56 +02:00
var form = new FormData ( ) ;
form . append ( ' reboot ' , ' yes ' ) ;
var xhr = new XMLHttpRequest ( ) ;
xhr . open ( ' POST ' , ' / reboot ' ) ;
xhr . send ( form ) ;
2019-07-15 11:56:36 +02:00
_ ( ' info ' ) . hidden = false ;
}
else {
location . assign ( ' / ' ) ;
}
}
< / script >
< title > Afterburner Reboot < / title >
< / head >
< body >
< h1 > Reboot Afterburner < / h1 >
< p >
< h3 class = ' throb ' id = ' info ' hidden > Rebooting - will re - direct to root index < / h3 >
< button class = ' redbutton ' onClick = ' onReboot ( ) ' > Reboot < / button >
& nbsp ; & nbsp ; & nbsp ; & nbsp ; < a href = ' / ' > < button > Cancel < / button > < / a >
< / body >
< / html >
) = = = = = " ;
2020-04-22 08:21:24 +02:00
2019-07-06 15:46:20 +02:00
/***************************************************************************************
* HTTP RESPONSE 404 - FILE NOT FOUND HANDLING
*/
2020-04-22 08:21:24 +02:00
void build404Response ( HTTPRequest * req , String & content , String file )
2019-07-06 15:46:20 +02:00
{
content + = stdHeader ;
2019-07-11 10:55:31 +02:00
content + = R " =====(</head>
< body >
2019-07-06 15:46:20 +02:00
< h1 > 404 : File Not Found < / h1 >
< p > URI : < b > < i > ) = = = = = " ;
content + = file ;
content + = R " =====(</i></b><br>
Method : ) = = = = = " ;
2020-04-22 08:21:24 +02:00
content + = req - > getMethod ( ) . c_str ( ) ;
content + = " <br>Arguments: " ;
for ( auto it = req - > getParams ( ) - > beginQueryParameters ( ) ; it ! = req - > getParams ( ) - > endQueryParameters ( ) ; + + it ) {
std : : string nm ( it - > first ) ;
std : : string val ( it - > second ) ;
content + = " " ;
content + = nm . c_str ( ) ;
content + = " : " ;
content + = val . c_str ( ) ;
content + = " <br> " ;
}
2019-07-06 15:46:20 +02:00
content + = R " =====(<hr>
< p > Please check the URL . < br >
If OK please try uploading the file from the web content .
2020-03-25 10:28:12 +01:00
< p > Latest web content can be downloaded from < a href = " http://afterburner.mrjones.id.au/firmware.html " target = " _blank " > http : //afterburner.mrjones.id.au/firmware.html</a>
2019-07-06 15:46:20 +02:00
< h4 class = " throb " >Please ensure you unzip the web page content, then upload all the files contained.</h4>
< p > < a href = " /update " > < button > Upload web content < / button > < / a > < br >
) = = = = = " ;
String SPIFFSinfo ;
2019-07-07 09:18:38 +02:00
listSPIFFS ( " / " , 2 , SPIFFSinfo , 1 ) ;
2019-07-06 15:46:20 +02:00
content + = SPIFFSinfo ;
content + = " </body> " ;
content + = " </html> " ;
}
/***************************************************************************************
* HTTP RESPONSE 500 - SERVER ERROR HANDLING
*/
void build500Response ( String & content , String file )
{
content = stdHeader ;
2019-07-11 10:55:31 +02:00
content + = R " =====(</head>
< body >
2019-07-06 15:46:20 +02:00
< h1 > 500 : Internal Server Error < / h1 >
< h3 class = " throb " >Sorry, cannot open file</h3>
< p > < b > < i > " )===== " ;
content + = file ;
content + = R " =====( " < / i > < / b > exists , but cannot be streamed ?
< hr >
< p > Recommended remedy is to re - format the SPIFFS partition , then reload the web content files .
2020-03-25 10:28:12 +01:00
< br > Latest web content can be downloaded from < a href = " http://afterburner.mrjones.id.au/firmware.html " target = " _blank " > http : //afterburner.mrjones.id.au/firmware.html</a> <i>(opens in new page)</i>.
2019-07-15 11:56:36 +02:00
< p > To format the SPIFFS partition , press < button class = ' redbutton ' onClick = location . assign ( ' / formatspiffs ' ) > Format SPIFFS < / button >
2019-07-06 15:46:20 +02:00
< p > You will then need to upload each file of the web content by using the subsequent " <b>Upload</b> " button .
< hr >
< h4 class = " throb " >Please ensure you unzip the web page content, then upload all the files contained.</h4>
< / body >
< / html >
) = = = = = " ;
}
2020-03-24 11:14:41 +01:00