mirror of
https://git.mirrors.martin98.com/https://github.com/luc-github/ESP3D.git
synced 2025-06-06 02:36:49 +08:00
554 lines
17 KiB
C++
554 lines
17 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;
|
|
|
|
#ifndef USE_AS_UPDATER_ONLY
|
|
|
|
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_esp3d("Purge Serial");
|
|
while (ESPCOM::available(DEFAULT_PRINTER_PIPE) > 0 ) {
|
|
if ((millis() - start ) > 2000) {
|
|
log_esp3d("Purge timeout");
|
|
return false;
|
|
}
|
|
size_t len = ESPCOM::readBytes (DEFAULT_PRINTER_PIPE, buf, 50);
|
|
buf[len] = '\0';
|
|
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_esp3d("Purge interrupted");
|
|
}
|
|
CONFIG::wait (5);
|
|
}
|
|
CONFIG::wait (0);
|
|
log_esp3d("Purge done");
|
|
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_esp3d("Line requested is %d", l);
|
|
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_esp3d ("Send line %d");
|
|
String line2send;
|
|
String sok = "ok";
|
|
String sresend = "Resend:";
|
|
if (newlinenb) {
|
|
*newlinenb = linenb;
|
|
}
|
|
if ( CONFIG::GetFirmwareTarget() == SMOOTHIEWARE) {
|
|
sresend = "rs N";
|
|
}
|
|
#ifdef DISABLE_SERIAL_CHECKSUM
|
|
linenb = -1;
|
|
#endif
|
|
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();
|
|
//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;
|
|
//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_esp3d ("Wrong line requested");
|
|
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_esp3d ("Resend detected");
|
|
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 !=(uint32_t)linenb) {
|
|
log_esp3d ("Wrong line requested");
|
|
count = 5;
|
|
}
|
|
}
|
|
count++;
|
|
if (count > 5) {
|
|
free(sbuf);
|
|
log_esp3d ("Exit too many resend or wrong line");
|
|
return false;
|
|
}
|
|
purge_serial();
|
|
log_esp3d ("Resend x%d", count);
|
|
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_esp3d ("Got ok");
|
|
purge_serial();
|
|
return true;
|
|
}
|
|
}
|
|
free(sbuf);
|
|
}
|
|
//no answer or over buffer exit
|
|
if ( (millis() - timeout > 2000) || (response.length() >200)) {
|
|
log_esp3d("Time out");
|
|
done = true;
|
|
}
|
|
CONFIG::wait (5);
|
|
}
|
|
}
|
|
log_esp3d ("Send line error");
|
|
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;
|
|
}
|
|
#endif //USE_AS_UPDATER_ONLY
|
|
|
|
//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;
|