ESP3D/esp3d/webinterface.cpp
Luc 1cf2744bad Add Notifications
Add EEPROM version Check
Update embedded page content and build process
Move check sum ESP commands from ESP6XX to ESP5XX to let Notifications use ESP6XX
Update command.txt with latest commands
2019-06-07 11:52:57 +02:00

548 lines
18 KiB
C++

/*
webinterface.cpp - ESP3D configuration class
Copyright (c) 2014 Luc Lebosse. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <pgmspace.h>
#include "config.h"
#include "webinterface.h"
#include "wificonf.h"
#include <WiFiClient.h>
#include <WiFiServer.h>
#include <WiFiUdp.h>
#include <StreamString.h>
#ifndef FS_NO_GLOBALS
#define FS_NO_GLOBALS
#endif
#include <FS.h>
#if defined (ASYNCWEBSERVER)
#include <ESPAsyncWebServer.h>
#endif
#ifdef ARDUINO_ARCH_ESP8266
#include "ESP8266WiFi.h"
#if defined (ASYNCWEBSERVER)
#include <ESPAsyncTCP.h>
#else
#include <ESP8266WebServer.h>
#endif
#else //ESP32
#include <WiFi.h>
#if defined (ASYNCWEBSERVER)
#include <AsyncTCP.h>
#else
#include <WebServer.h>
#endif
#include "SPIFFS.h"
#include "Update.h"
#endif
#include "GenLinkedList.h"
#include "command.h"
#include "espcom.h"
#ifdef SSDP_FEATURE
#ifdef ARDUINO_ARCH_ESP32
#include <ESP32SSDP.h>
#else
#include <ESP8266SSDP.h>
#endif
#endif
#if defined(ASYNCWEBSERVER)
#include "asyncwebserver.h"
#else
#include "syncwebserver.h"
#endif
#define MAX_AUTH_IP 10
long id_connection = 0;
uint8_t Checksum(const char * line, uint16_t lineSize)
{
uint8_t checksum_val =0;
for (uint16_t i=0; i < lineSize; i++) checksum_val = checksum_val ^ ((uint8_t)line[i]);
return checksum_val;
}
String CheckSumLine(const char* line, uint32_t linenb){
String linechecksum = "N" + String(linenb)+ " " + line;
uint8_t crc = Checksum(linechecksum.c_str(), linechecksum.length());
linechecksum+="*"+String(crc);
return linechecksum;
}
bool purge_serial(){
uint32_t start = millis();
uint8_t buf [51];
ESPCOM::flush (DEFAULT_PRINTER_PIPE);
CONFIG::wait (5);
LOG("Purge Serial\r\n")
while (ESPCOM::available(DEFAULT_PRINTER_PIPE) > 0 ){
if ((millis() - start ) > 2000) {
LOG("Purge timeout\r\n")
return false;
}
size_t len = ESPCOM::readBytes (DEFAULT_PRINTER_PIPE, buf, 50);
buf[len] = '\0';
LOG("Purge: \r\n************\r\n")
LOG((const char *)buf)
LOG("\r\n************\r\n")
if ( ( CONFIG::GetFirmwareTarget() == REPETIER4DV) || (CONFIG::GetFirmwareTarget() == REPETIER) ) {
String s = (const char *)buf;
//repetier never stop sending data so no need to wait if have 'wait' or 'busy'
if((s.indexOf ("wait") > -1) || (s.indexOf ("busy") > -1))return true;
LOG("Purge interrupted\r\n")
}
CONFIG::wait (5);
}
CONFIG::wait (0);
LOG("Purge done\r\n")
return true;
}
size_t wait_for_data(uint32_t timeout){
uint32_t start = millis();
while ((ESPCOM::available(DEFAULT_PRINTER_PIPE) < 2) && ((millis()-start) < timeout))CONFIG::wait (10);
return ESPCOM::available(DEFAULT_PRINTER_PIPE);
}
uint32_t Get_lineNumber(String & response){
int32_t l = 0;
String sresend = "Resend:";
if ( CONFIG::GetFirmwareTarget() == SMOOTHIEWARE){
sresend = "rs N";
}
int pos = response.indexOf(sresend);
if (pos == -1 ) {
return -1;
}
pos+=sresend.length();
int pos2 = response.indexOf("\n", pos);
String snum = response.substring(pos, pos2);
//remove potential unwished char
snum.replace("\r", "");
l = snum.toInt();
LOG("Line requested is ")
LOG(String(l))
LOG("\r\n")
return l;
}
//function to send line to serial///////////////////////////////////////
//if newlinenb is NULL no auto correction of line number in case of resend
bool sendLine2Serial (String & line, int32_t linenb, int32_t * newlinenb)
{
LOG ("Send line ")
LOG (line )
LOG (" NB:")
LOG (String(linenb))
LOG ("\r\n")
String line2send;
String sok = "ok";
String sresend = "Resend:";
if (newlinenb) *newlinenb = linenb;
if ( CONFIG::GetFirmwareTarget() == SMOOTHIEWARE)sresend = "rs N";
if (linenb != -1) {
if ( ( CONFIG::GetFirmwareTarget() == REPETIER4DV) || (CONFIG::GetFirmwareTarget() == REPETIER) ) sok+=" " + String(linenb);
line2send = CheckSumLine(line.c_str(),linenb);
} else line2send = line;
//purge serial as nothing is supposed to interfere with upload
purge_serial();
LOG ("Send line ")
LOG (line2send )
LOG ("\r\n")
//send line
ESPCOM::println (line2send, DEFAULT_PRINTER_PIPE);
ESPCOM::flush(DEFAULT_PRINTER_PIPE);
//check answer
if (wait_for_data(2000) > 0 ) {
bool done = false;
uint32_t timeout = millis();
uint8_t count = 0;
//got data check content
String response ;
while (!done) {
size_t len = ESPCOM::available(DEFAULT_PRINTER_PIPE);
//get size of buffer
if (len > 0) {
uint8_t * sbuf = (uint8_t *)malloc(len+1);
if(!sbuf){
return false;
}
//read buffer
ESPCOM::readBytes (DEFAULT_PRINTER_PIPE, sbuf, len);
//convert buffer to zero end array
sbuf[len] = '\0';
//use string because easier to handle and allow to re-assemble cutted answer
response += (const char*) sbuf;
LOG ("Response:\r\n************\r\n")
LOG (response)
LOG ("\r\n************\r\n")
//in that case there is no way to know what is the right number to use and so send should be failed
if (( ( CONFIG::GetFirmwareTarget() == REPETIER4DV) || (CONFIG::GetFirmwareTarget() == REPETIER) ) && (response.indexOf ("skip") != -1)) {
LOG ("Wrong line requested\r\n")
count = 5;
}
//it is resend ?
int pos = response.indexOf (sresend);
//be sure we get full line to be able to process properly
if (( pos > -1) && (response.lastIndexOf("\n") > pos)){
LOG ("Resend detected\r\n")
uint32_t line_number = Get_lineNumber(response);
//this part is only if have newlinenb variable
if (newlinenb != nullptr){
*newlinenb = line_number;
free(sbuf);
//no need newlinenb in this one in theory, but just in case...
return sendLine2Serial (line, line_number, newlinenb);
} else {
//the line requested is not the current one so we stop
if (line_number !=linenb) {
LOG ("Wrong line requested\r\n")
count = 5;
}
}
count++;
if (count > 5) {
free(sbuf);
LOG ("Exit too many resend or wrong line\r\n")
return false;
}
purge_serial();
LOG ("Resend ")
LOG (String(count))
LOG ("\r\n")
response="";
ESPCOM::println (line2send, DEFAULT_PRINTER_PIPE);
ESPCOM::flush (DEFAULT_PRINTER_PIPE);
wait_for_data(1000);
timeout = millis();
} else {
if ( (response.indexOf (sok) > -1) ) { //we have ok so it is done
free(sbuf);
LOG ("Got ok\r\n")
purge_serial();
return true;
}
}
free(sbuf);
}
//no answer or over buffer exit
if ( (millis() - timeout > 2000) || (response.length() >200)){
LOG ("Time out\r\n")
done = true;
}
CONFIG::wait (5);
}
}
LOG ("Send line error\r\n")
return false;
}
//send M29 / M30 command to close file on SD////////////////////////////
void CloseSerialUpload (bool iserror, String & filename , int32_t linenb)
{
purge_serial();
String command = "M29";
purge_serial();
if (!sendLine2Serial (command,linenb, &linenb)) {
wait_for_data(2000);
if (!sendLine2Serial (command,linenb, &linenb)) {
if ((CONFIG::GetFirmwareTarget() == REPETIER4DV) || (CONFIG::GetFirmwareTarget() == REPETIER) ) {
sendLine2Serial (command,-1, NULL);
}
}
}
if (iserror) {
String cmdfilename = "M30 " + filename;
sendLine2Serial (cmdfilename,-1, NULL);
ESPCOM::println (F ("SD upload failed"), PRINTER_PIPE);
ESPCOM::flush (DEFAULT_PRINTER_PIPE);
web_interface->_upload_status = UPLOAD_STATUS_FAILED;
} else {
ESPCOM::println (F ("SD upload done"), PRINTER_PIPE);
ESPCOM::flush (DEFAULT_PRINTER_PIPE);
web_interface->_upload_status = UPLOAD_STATUS_SUCCESSFUL;
}
//lets give time to FW to proceed
wait_for_data(2000);
purge_serial();
web_interface->blockserial = false;
}
//constructor
WEBINTERFACE_CLASS::WEBINTERFACE_CLASS (int port) : web_server (port)
#if defined(ASYNCWEBSERVER)
, web_events("/events")
#ifdef WS_DATA_FEATURE
, web_socket("/ws")
#endif
#endif
{
//that handle "/" and default index.html.gz
#if defined(ASYNCWEBSERVER)
//trick to catch command line on "/" before file being processed
web_server.serveStatic ("/", SPIFFS, "/").setDefaultFile ("index.html").setFilter (filterOnRoot);
web_server.serveStatic ("/", SPIFFS, "/Nowhere");
//events functions
web_events.onConnect(handle_onevent_connect);
//events management
web_server.addHandler(&web_events);
#ifdef WS_DATA_FEATURE
//Websocket function
web_socket.onEvent(handle_Websocket_Event);
//Websocket management
web_server.addHandler(&web_socket);
#endif
#else
web_server.on("/",HTTP_ANY, handle_web_interface_root);
#endif
//need to be there even no authentication to say to UI no authentication
web_server.on("/login", HTTP_ANY, handle_login);
#ifdef SSDP_FEATURE
web_server.on ("/description.xml", HTTP_GET, handle_SSDP);
#endif
#ifdef CAPTIVE_PORTAL_FEATURE
#if defined(ASYNCWEBSERVER)
web_server.on ("/generate_204", HTTP_ANY, [] (AsyncWebServerRequest * request) {
request->redirect ("/");
});
web_server.on ("/gconnectivitycheck.gstatic.com", HTTP_ANY, [] (AsyncWebServerRequest * request) {
request->redirect ("/");
});
//do not forget the / at the end
web_server.on ("/fwlink/", HTTP_ANY, [] (AsyncWebServerRequest * request) {
request->redirect ("/");
});
#else
web_server.on("/generate_204",HTTP_ANY, handle_web_interface_root);
web_server.on("/gconnectivitycheck.gstatic.com",HTTP_ANY, handle_web_interface_root);
//do not forget the / at the end
web_server.on("/fwlink/",HTTP_ANY, handle_web_interface_root);
#endif
#endif
//SPIFFS
web_server.on ("/files", HTTP_ANY, handleFileList, SPIFFSFileupload);
#ifdef WEB_UPDATE_FEATURE
web_server.on ("/updatefw", HTTP_ANY, handleUpdate, WebUpdateUpload);
#endif
//Page not found handler
web_server.onNotFound ( handle_not_found);
//web commands
web_server.on ("/command", HTTP_ANY, handle_web_command);
web_server.on ("/command_silent", HTTP_ANY, handle_web_command_silent);
//Serial SD management
web_server.on ("/upload_serial", HTTP_ANY, handle_serial_SDFileList, SDFile_serial_upload);
blockserial = false;
restartmodule = false;
_head = NULL;
_nb_ip = 0;
_upload_status = UPLOAD_STATUS_NONE;
}
//Destructor
WEBINTERFACE_CLASS::~WEBINTERFACE_CLASS()
{
while (_head) {
auth_ip * current = _head;
_head = _head->_next;
delete current;
}
_nb_ip = 0;
}
//check authentification
level_authenticate_type WEBINTERFACE_CLASS::is_authenticated()
{
#ifdef AUTHENTICATION_FEATURE
if (web_server.hasHeader ("Cookie") ) {
String cookie = web_server.header ("Cookie");
int pos = cookie.indexOf ("ESPSESSIONID=");
if (pos != -1) {
int pos2 = cookie.indexOf (";", pos);
String sessionID = cookie.substring (pos + strlen ("ESPSESSIONID="), pos2);
IPAddress ip = web_server.client().remoteIP();
//check if cookie can be reset and clean table in same time
return ResetAuthIP (ip, sessionID.c_str() );
}
}
return LEVEL_GUEST;
#else
return LEVEL_ADMIN;
#endif
}
#ifdef AUTHENTICATION_FEATURE
//add the information in the linked list if possible
bool WEBINTERFACE_CLASS::AddAuthIP (auth_ip * item)
{
if (_nb_ip > MAX_AUTH_IP) {
return false;
}
item->_next = _head;
_head = item;
_nb_ip++;
return true;
}
//Session ID based on IP and time using 16 char
char * WEBINTERFACE_CLASS::create_session_ID()
{
static char sessionID[17];
//reset SESSIONID
for (int i = 0; i < 17; i++) {
sessionID[i] = '\0';
}
//get time
uint32_t now = millis();
//get remote IP
IPAddress remoteIP = web_server.client().remoteIP();
//generate SESSIONID
if (0 > sprintf (sessionID, "%02X%02X%02X%02X%02X%02X%02X%02X", remoteIP[0], remoteIP[1], remoteIP[2], remoteIP[3], (uint8_t) ( (now >> 0) & 0xff), (uint8_t) ( (now >> 8) & 0xff), (uint8_t) ( (now >> 16) & 0xff), (uint8_t) ( (now >> 24) & 0xff) ) ) {
strcpy (sessionID, "NONE");
}
return sessionID;
}
bool WEBINTERFACE_CLASS::ClearAuthIP (IPAddress ip, const char * sessionID)
{
auth_ip * current = _head;
auth_ip * previous = NULL;
bool done = false;
while (current) {
if ( (ip == current->ip) && (strcmp (sessionID, current->sessionID) == 0) ) {
//remove
done = true;
if (current == _head) {
_head = current->_next;
_nb_ip--;
delete current;
current = _head;
} else {
previous->_next = current->_next;
_nb_ip--;
delete current;
current = previous->_next;
}
} else {
previous = current;
current = current->_next;
}
}
return done;
}
//Get info
auth_ip * WEBINTERFACE_CLASS::GetAuth (IPAddress ip, const char * sessionID)
{
auth_ip * current = _head;
auth_ip * previous = NULL;
//get time
//uint32_t now = millis();
while (current) {
if (ip == current->ip) {
if (strcmp (sessionID, current->sessionID) == 0) {
//found
return current;
}
}
previous = current;
current = current->_next;
}
return NULL;
}
//Review all IP to reset timers
level_authenticate_type WEBINTERFACE_CLASS::ResetAuthIP (IPAddress ip, const char * sessionID)
{
auth_ip * current = _head;
auth_ip * previous = NULL;
//get time
//uint32_t now = millis();
while (current) {
if ( (millis() - current->last_time) > 180000) {
//remove
if (current == _head) {
_head = current->_next;
_nb_ip--;
delete current;
current = _head;
} else {
previous->_next = current->_next;
_nb_ip--;
delete current;
current = previous->_next;
}
} else {
if (ip == current->ip) {
if (strcmp (sessionID, current->sessionID) == 0) {
//reset time
current->last_time = millis();
return (level_authenticate_type) current->level;
}
}
previous = current;
current = current->_next;
}
}
return LEVEL_GUEST;
}
#endif
//Check what is the content tye according extension file
String WEBINTERFACE_CLASS::getContentType (String filename)
{
if (filename.endsWith (".htm") ) {
return "text/html";
} else 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 (".png") ) {
return "image/png";
} else if (filename.endsWith (".gif") ) {
return "image/gif";
} else if (filename.endsWith (".jpeg") ) {
return "image/jpeg";
} else if (filename.endsWith (".jpg") ) {
return "image/jpeg";
} else if (filename.endsWith (".ico") ) {
return "image/x-icon";
} else if (filename.endsWith (".xml") ) {
return "text/xml";
} else if (filename.endsWith (".pdf") ) {
return "application/x-pdf";
} else if (filename.endsWith (".zip") ) {
return "application/x-zip";
} else if (filename.endsWith (".gz") ) {
return "application/x-gzip";
} else if (filename.endsWith (".txt") ) {
return "text/plain";
}
return "application/octet-stream";
}
WEBINTERFACE_CLASS * web_interface;