From 59e766b0b809e1a0d91d4cd0d9581b27e2395639 Mon Sep 17 00:00:00 2001 From: Luc Date: Sun, 24 Jan 2021 13:22:41 +0100 Subject: [PATCH] webdav part 1 --- docs/Commands.txt | 9 + docs/esp3dcnf.ini | 6 + esp3d/configuration.h | 18 +- esp3d/src/core/commands.cpp | 12 + esp3d/src/core/commands.h | 4 + esp3d/src/core/debug_esp3d.h | 8 +- esp3d/src/core/espcmd/ESP0.cpp | 8 + esp3d/src/core/espcmd/ESP190.cpp | 74 + esp3d/src/core/espcmd/ESP191.cpp | 66 + esp3d/src/core/espcmd/ESP400.cpp | 18 + esp3d/src/core/espcmd/ESP420.cpp | 40 + esp3d/src/core/settings_esp3d.cpp | 29 +- esp3d/src/core/settings_esp3d.h | 3 +- esp3d/src/include/defines.h | 4 + esp3d/src/include/sanity_esp3d.h | 21 + esp3d/src/modules/filesystem/esp_filesystem.h | 2 + esp3d/src/modules/filesystem/esp_globalFS.cpp | 35 + esp3d/src/modules/filesystem/esp_globalFS.h | 10 +- esp3d/src/modules/filesystem/esp_sd.h | 2 + .../filesystem/flash/fat_esp32_filesystem.cpp | 10 + .../flash/littlefs_esp8266_filesystem .cpp | 10 + .../flash/spiffs_esp32_filesystem.cpp | 10 + .../flash/spiffs_esp8266_filesystem.cpp | 10 + .../modules/filesystem/sd/sd_native_esp32.cpp | 10 + .../filesystem/sd/sd_native_esp8266.cpp | 10 + .../modules/filesystem/sd/sd_sdfat_esp32.cpp | 13 + .../src/modules/filesystem/sd/sdio_esp32.cpp | 10 + esp3d/src/modules/network/netconfig.h | 2 + esp3d/src/modules/network/netservices.cpp | 21 + esp3d/src/modules/update/update_service.cpp | 4 + esp3d/src/modules/webdav/ESPWebDAV.cpp | 1651 +++++++++++++++++ esp3d/src/modules/webdav/ESPWebDAV.h | 230 +++ .../src/modules/webdav/PolledTimeout_esp32.h | 27 + esp3d/src/modules/webdav/WebSrv.cpp | 162 ++ esp3d/src/modules/webdav/webdav_server.cpp | 116 ++ esp3d/src/modules/webdav/webdav_server.h | 54 + 36 files changed, 2704 insertions(+), 15 deletions(-) create mode 100644 esp3d/src/core/espcmd/ESP190.cpp create mode 100644 esp3d/src/core/espcmd/ESP191.cpp create mode 100644 esp3d/src/modules/webdav/ESPWebDAV.cpp create mode 100644 esp3d/src/modules/webdav/ESPWebDAV.h create mode 100644 esp3d/src/modules/webdav/PolledTimeout_esp32.h create mode 100644 esp3d/src/modules/webdav/WebSrv.cpp create mode 100644 esp3d/src/modules/webdav/webdav_server.cpp create mode 100644 esp3d/src/modules/webdav/webdav_server.h diff --git a/docs/Commands.txt b/docs/Commands.txt index 0f61fa0d..95f6f7f1 100644 --- a/docs/Commands.txt +++ b/docs/Commands.txt @@ -72,6 +72,12 @@ label can be: light/framesize/quality/contrast/brightness/saturation/gainceiling * Get/Set Ftp ports [ESP181]ctrl= active= passive= pwd= +* Get/Set WebDav state which can be ON, OFF, CLOSE +[ESP190]pwd= + +* Get/Set WebDav port +[ESP191]pwd= + * Get SD Card Status [ESP200] pwd= @@ -169,6 +175,9 @@ ESP_FTP_DATA_ACTIVE_PORT 1013 //4 bytes = int ESP_FTP_DATA_PASSIVE_PORT 1017 //4 bytes = int ESP_FTP_ON 1021 //1 byte = flag ESP_AUTO_NOTIFICATION 1022 //1 byte = flag +ESP_VERBOSE_BOOT 1023 //1 byte = flag +ESP_WEBDAV_ON 1024 //1 byte = flag +ESP_WEBDAV_PORT 1025 //4 bytes = int * Get/Set Check update at boot state which can be ON, OFF [ESP402]pwd= diff --git a/docs/esp3dcnf.ini b/docs/esp3dcnf.ini index d3404f6f..5d2fe2f1 100644 --- a/docs/esp3dcnf.ini +++ b/docs/esp3dcnf.ini @@ -54,6 +54,12 @@ WebSocket_active = Yes #WebSocket Port WebSocket_Port = 8282 +#Active or not WebDav Yes / No +WebDav_active = Yes + +#WebSocket Port +WebDav_Port = 8282 + #Active or not FTP Yes / No FTP_active = Yes diff --git a/esp3d/configuration.h b/esp3d/configuration.h index c64a4338..8d94a94f 100644 --- a/esp3d/configuration.h +++ b/esp3d/configuration.h @@ -61,7 +61,7 @@ #define TELNET_FEATURE //WS_DATA_FEATURE: allow to connect serial from Websocket -#define WS_DATA_FEATURE +//#define WS_DATA_FEATURE //DISPLAY_DEVICE: allow screen output //OLED_I2C_SSD1306 1 @@ -157,14 +157,22 @@ #define FILESYSTEM_FEATURE ESP_LITTLEFS_FILESYSTEM //Allows to mount /FS and /SD under / for FTP server -#define GLOBAL_FILESYSTEM_FEATURE +//#define GLOBAL_FILESYSTEM_FEATURE + +//WEBDAV_FEATURE : enable WebDav feature +//FS_ROOT mount all FS +//FS_FLASH mount Flash FS +//FS_SD mount SD FS +//FS_USBDISK mount USB disk FS + +#define WEBDAV_FEATURE FS_FLASH //FTP_FEATURE : enable FTP feature //FS_ROOT mount all FS //FS_FLASH mount Flash FS //FS_SD mount SD FS //FS_USBDISK mount USB disk FS -#define FTP_FEATURE FS_ROOT +//#define FTP_FEATURE FS_ROOT //DIRECT_PIN_FEATURE: allow to access pin using ESP201 command #define DIRECT_PIN_FEATURE @@ -190,7 +198,7 @@ #define CAPTIVE_PORTAL_FEATURE //OTA_FEATURE: this feature is arduino update over the air -#define OTA_FEATURE +//#define OTA_FEATURE //WEB_UPDATE_FEATURE: allow to flash fw using web UI #define WEB_UPDATE_FEATURE @@ -252,7 +260,7 @@ //DEBUG_OUTPUT_SERIAL2 3 //DEBUG_OUTPUT_TELNET 4 //DEBUG_OUTPUT_WEBSOCKET 5 -//#define ESP_DEBUG_FEATURE DEBUG_OUTPUT_TELNET +#define ESP_DEBUG_FEATURE DEBUG_OUTPUT_SERIAL0 #ifdef ESP_DEBUG_FEATURE #define DEBUG_BAUDRATE 115200 diff --git a/esp3d/src/core/commands.cpp b/esp3d/src/core/commands.cpp index 85526307..3a9de560 100644 --- a/esp3d/src/core/commands.cpp +++ b/esp3d/src/core/commands.cpp @@ -408,6 +408,18 @@ bool Commands::execute_internal_command (int cmd, const char* cmd_params, level_ response = ESP181(cmd_params, auth_type, output); break; #endif //FTP_FEATURE +#ifdef WEBDAV_FEATURE + //Set webdav state which can be ON, OFF + //[ESP190]pwd= + case 190: + response = ESP190(cmd_params, auth_type, output); + break; + //Set/get webdav port + //[ESP191]ctrl= active= passive= pwd= + case 191: + response = ESP191(cmd_params, auth_type, output); + break; +#endif //WEBDAV_FEATURE #if defined (SD_DEVICE) //Get SD Card Status //[ESP200] pwd= diff --git a/esp3d/src/core/commands.h b/esp3d/src/core/commands.h index 4dd7d4d0..8b85292e 100644 --- a/esp3d/src/core/commands.h +++ b/esp3d/src/core/commands.h @@ -84,6 +84,10 @@ public: bool ESP180(const char* cmd_params, level_authenticate_type auth_level, ESP3DOutput * output); bool ESP181(const char* cmd_params, level_authenticate_type auth_level, ESP3DOutput * output); #endif //FTP_FEATURE +#if defined(WEBDAV_FEATURE) + bool ESP190(const char* cmd_params, level_authenticate_type auth_level, ESP3DOutput * output); + bool ESP191(const char* cmd_params, level_authenticate_type auth_level, ESP3DOutput * output); +#endif //WEBDAV_FEATURE #if defined (SD_DEVICE) bool ESP200(const char* cmd_params, level_authenticate_type auth_level, ESP3DOutput * output); bool ESP202(const char* cmd_params, level_authenticate_type auth_level, ESP3DOutput * output); diff --git a/esp3d/src/core/debug_esp3d.h b/esp3d/src/core/debug_esp3d.h index 913d37e8..bd95ee35 100644 --- a/esp3d/src/core/debug_esp3d.h +++ b/esp3d/src/core/debug_esp3d.h @@ -53,7 +53,7 @@ extern void initDebug(); #undef DEBUG_ESP3D_INIT #define DEBUG_ESP3D_INIT initDebug(); #define log_esp3d(format, ...) DEBUG_OUTPUT_SERIAL.printf("[ESP3D][%s:%u] %s(): " format "\r\n", pathToFileName(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__) -#define log_esp3dS(format, ...) DEBUG_OUTPUT_SERIAL.printf(format "\r\n", ##__VA_ARGS__) +#define log_esp3ds(format, ...) DEBUG_OUTPUT_SERIAL.printf(format, ##__VA_ARGS__) #endif //DEBUG_OUTPUT_SERIAL0 || DEBUG_OUTPUT_SERIAL1 || DEBUG_OUTPUT_SERIAL2 //Telnet @@ -67,7 +67,7 @@ extern Telnet_Server telnet_debug; #define DEBUG_ESP3D_NETWORK_HANDLE telnet_debug.handle(); #define DEBUG_ESP3D_NETWORK_END telnet_debug.end(); #define log_esp3d(format, ...) if(telnet_debug.isConnected())telnet_debug.printf("[ESP3D][%s:%u] %s(): " format "\r\n", pathToFileName(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__) -#define log_esp3dS(format, ...) if(telnet_debug.isConnected())telnet_debug.printf(format "\r\n", ##__VA_ARGS__) +#define log_esp3dS(format, ...) if(telnet_debug.isConnected())telnet_debug.printf(format , ##__VA_ARGS__) #endif // DEBUG_OUTPUT_TELNET //Telnet @@ -81,11 +81,11 @@ extern WebSocket_Server websocket_debug; #define DEBUG_ESP3D_NETWORK_HANDLE websocket_debug.handle(); #define DEBUG_ESP3D_NETWORK_END websocket_debug.end(); #define log_esp3d(format, ...) websocket_debug.printf("[ESP3D][%s:%u] %s(): " format "\r\n", pathToFileName(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__) -#define log_esp3dS(format, ...) websocket_debug.printf(format "\r\n", ##__VA_ARGS__) +#define log_esp3dS(format, ...) websocket_debug.printf(format, ##__VA_ARGS__) #endif // DEBUG_OUTPUT_WEBSOCKET #else #define log_esp3d(format, ...) -#define log_esp3dS(format, ...) +#define log_esp3ds(format, ...) #endif //ESP_DEBUG_FEATURE #endif //_DEBUG_ESP3D_H diff --git a/esp3d/src/core/espcmd/ESP0.cpp b/esp3d/src/core/espcmd/ESP0.cpp index 64618b9e..5c0b758d 100644 --- a/esp3d/src/core/espcmd/ESP0.cpp +++ b/esp3d/src/core/espcmd/ESP0.cpp @@ -70,6 +70,10 @@ const char * help[]= {"[ESP] - display this help", "[ESP180](State) - display/set FTP state which can be ON, OFF", "[ESP181](ctrl=xxxx) (active=xxxx) (passive=xxxx) - display/set FTP ports", #endif //FTP_FEATURE +#if defined(WEBDAV_FEATURE) + "[ESP190](State) - display/set WebDav state which can be ON, OFF", + "[ESP191](Port) - display/set WebDav port", +#endif //WEBDAV_FEATURE #if defined (SD_DEVICE) "[ESP200] - display SD Card Status", #endif //SD_DEVICE @@ -191,6 +195,10 @@ const uint cmdlist[]= {0, 180, 181, #endif //FTP_FEATURE +#if defined(WEBDAV_FEATURE) + 190, + 111, +#endif //WEBDAV_FEATURE #if defined (SD_DEVICE) 200, #endif //SD_DEVICE diff --git a/esp3d/src/core/espcmd/ESP190.cpp b/esp3d/src/core/espcmd/ESP190.cpp new file mode 100644 index 00000000..13520c84 --- /dev/null +++ b/esp3d/src/core/espcmd/ESP190.cpp @@ -0,0 +1,74 @@ +/* + ESP190.cpp - ESP3D command class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This code 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 code 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 code; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "../../include/esp3d_config.h" +#if defined (WEBDAV_FEATURE) +#include "../commands.h" +#include "../esp3doutput.h" +#include "../settings_esp3d.h" +#include "../../modules/authentication/authentication_service.h" +#include "../../modules/webdav/webdav_server.h" +//Set WebDav state which can be ON, OFF, CLOSE +//[ESP190]pwd= +bool Commands::ESP190(const char* cmd_params, level_authenticate_type auth_type, ESP3DOutput * output) +{ + bool response = true; + String parameter; +#ifdef AUTHENTICATION_FEATURE + if (auth_type == LEVEL_GUEST) { + output->printERROR("Wrong authentication!", 401); + return false; + } +#else + (void)auth_type; +#endif //AUTHENTICATION_FEATURE + parameter = get_param (cmd_params, ""); + //get + if (parameter.length() == 0) { + output->printMSG((Settings_ESP3D::read_byte(ESP_WEBDAV_ON) == 0)?"OFF":"ON"); + webdav_server.dir(); + } else { //set +#ifdef AUTHENTICATION_FEATURE + if (auth_type != LEVEL_ADMIN) { + output->printERROR("Wrong authentication!", 401); + return false; + } +#endif //AUTHENTICATION_FEATURE + parameter.toUpperCase(); + if (!((parameter == "ON") || (parameter == "OFF") || (parameter == "CLOSE"))) { + output->printERROR("Only ON or OFF or CLOSE mode supported!"); + return false; + } else { + if (parameter == "CLOSE") { + webdav_server.closeClient(); + output->printMSG ("ok"); + } else { + if (!Settings_ESP3D::write_byte (ESP_WEBDAV_ON, (parameter == "ON")?1:0)) { + output->printERROR ("Set failed!"); + response = false; + } + + output->printMSG ("ok"); + } + } + } + return response; +} + +#endif //WEBDAV_FEATURE diff --git a/esp3d/src/core/espcmd/ESP191.cpp b/esp3d/src/core/espcmd/ESP191.cpp new file mode 100644 index 00000000..5a77a5eb --- /dev/null +++ b/esp3d/src/core/espcmd/ESP191.cpp @@ -0,0 +1,66 @@ +/* + ESP191.cpp - ESP3D command class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This code 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 code 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 code; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "../../include/esp3d_config.h" +#if defined (WEBDAV_FEATURE) +#include "../commands.h" +#include "../esp3doutput.h" +#include "../settings_esp3d.h" +#include "../../modules/authentication/authentication_service.h" +//Set webdav port +//[ESP191]pwd= +bool Commands::ESP191(const char* cmd_params, level_authenticate_type auth_type, ESP3DOutput * output) +{ + bool response = true; + String parameter; +#ifdef AUTHENTICATION_FEATURE + if (auth_type == LEVEL_GUEST) { + output->printERROR("Wrong authentication!", 401); + return false; + } +#else + (void)auth_type; +#endif //AUTHENTICATION_FEATURE + parameter = get_param (cmd_params, ""); + //get + if (parameter.length() == 0) { + output->printMSG(String(Settings_ESP3D::read_uint32(ESP_WEBDAV_PORT)).c_str()); + } else { //set +#ifdef AUTHENTICATION_FEATURE + if (auth_type != LEVEL_ADMIN) { + output->printERROR("Wrong authentication!", 401); + return false; + } +#endif //AUTHENTICATION_FEATURE + uint ibuf = parameter.toInt(); + if ((ibuf > Settings_ESP3D::get_max_int32_value(ESP_WEBDAV_PORT)) || (ibuf < Settings_ESP3D::get_min_int32_value(ESP_WEBDAV_PORT))) { + output->printERROR ("Incorrect port!"); + return false; + } + if (!Settings_ESP3D::write_uint32 (ESP_WEBDAV_PORT, ibuf)) { + output->printERROR ("Set failed!"); + response = false; + } else { + output->printMSG ("ok"); + } + } + return response; +} + +#endif //WEBDAV_FEATURE diff --git a/esp3d/src/core/espcmd/ESP400.cpp b/esp3d/src/core/espcmd/ESP400.cpp index 4d000beb..e60bf769 100644 --- a/esp3d/src/core/espcmd/ESP400.cpp +++ b/esp3d/src/core/espcmd/ESP400.cpp @@ -262,7 +262,25 @@ bool Commands::ESP400(const char* cmd_params, level_authenticate_type auth_type, output->print (Settings_ESP3D::get_min_int32_value(ESP_WEBSOCKET_PORT)); output->print ("\"}"); #endif //WS_DATA_FEATURE +#ifdef WEBDAV_FEATURE + //WebDav On service + output->print (",{\"F\":\"service/webdavp\",\"P\":\""); + output->print (ESP_WEBDAV_ON); + output->print ("\",\"T\":\"B\",\"V\":\""); + output->print (Settings_ESP3D::read_byte(ESP_WEBDAV_ON)); + output->print ("\",\"H\":\"enable\",\"O\":[{\"no\":\"0\"},{\"yes\":\"1\"}]}"); + //WebDav Port + output->print (",{\"F\":\"service/webdavp\",\"P\":\""); + output->print (ESP_WEBDAV_PORT); + output->print ("\",\"T\":\"I\",\"V\":\""); + output->print (Settings_ESP3D::read_uint32(ESP_WEBDAV_PORT)); + output->print ("\",\"H\":\"port\",\"S\":\""); + output->print (Settings_ESP3D::get_max_int32_value(ESP_WEBDAV_PORT)); + output->print ("\",\"M\":\""); + output->print (Settings_ESP3D::get_min_int32_value(ESP_WEBDAV_PORT)); + output->print ("\"}"); +#endif //WEBDAV_FEATURE #ifdef FTP_FEATURE //FTP On service/ftp output->print (",{\"F\":\"service/ftp\",\"P\":\""); diff --git a/esp3d/src/core/espcmd/ESP420.cpp b/esp3d/src/core/espcmd/ESP420.cpp index f6776f15..e83d56cf 100644 --- a/esp3d/src/core/espcmd/ESP420.cpp +++ b/esp3d/src/core/espcmd/ESP420.cpp @@ -50,6 +50,9 @@ #ifdef WS_DATA_FEATURE #include "../../modules/websocket/websocket_server.h" #endif //WS_DATA_FEATURE +#ifdef WEBDAV_FEATURE +#include "../../modules/webdav/webdav_server.h" +#endif //WEBDAV_FEATURE #if defined (TIMESTAMP_FEATURE) #include "../../modules/time/time_server.h" #endif //TIMESTAMP_FEATURE @@ -418,6 +421,43 @@ bool Commands::ESP420(const char* cmd_params, level_authenticate_type auth_type, } } #endif //TELNET_FEATURE +#if defined (WEBDAV_FEATURE) + if (webdav_server.started()) { + //WebDav port + if (!plain) { + output->print (",{\"id\":\""); + } + output->print ("WebDav port"); + if (!plain) { + output->print ("\",\"value\":\""); + } else { + output->print (": "); + } + output->printf ("%d",webdav_server.port()); + if (!plain) { + output->print ("\"}"); + } else { + output->printLN(""); + } + } + if (webdav_server.isConnected()) { + if (!plain) { + output->print (",{\"id\":\""); + } + output->print ("WebDav Client"); + if (!plain) { + output->print ("\",\"value\":\""); + } else { + output->print (": "); + } + output->printf ("%s",webdav_server.clientIPAddress()); + if (!plain) { + output->print ("\"}"); + } else { + output->printLN(""); + } + } +#endif //WEBDAV_FEATURE #if defined (FTP_FEATURE) if (ftp_server.started()) { //ftp ports diff --git a/esp3d/src/core/settings_esp3d.cpp b/esp3d/src/core/settings_esp3d.cpp index 15fd7d02..95dd2515 100644 --- a/esp3d/src/core/settings_esp3d.cpp +++ b/esp3d/src/core/settings_esp3d.cpp @@ -110,6 +110,7 @@ #define DEFAULT_SENSOR_TYPE NO_SENSOR_DEVICE #define DEFAULT_HTTP_ON 1 #define DEFAULT_FTP_ON 1 +#define DEFAULT_WEBDAV_ON 1 #define DEFAULT_TELNET_ON 1 #define DEFAULT_WEBSOCKET_ON 1 #define DEFAULT_NOTIFICATION_TYPE 0 @@ -127,6 +128,7 @@ #define DEFAULT_FTP_ACTIVE_PORT 20L #define DEFAULT_FTP_PASSIVE_PORT 55600L #define DEFAULT_WEBSOCKET_PORT 8282L +#define DEFAULT_WEBDAV_PORT 8181L #define DEFAULT_TELNET_PORT 23L #define DEFAULT_SENSOR_INTERVAL 30000L #define DEFAULT_BOOT_DELAY 10000L @@ -316,6 +318,11 @@ uint8_t Settings_ESP3D::get_default_byte_value(int pos) res = DEFAULT_WEBSOCKET_ON; break; #endif //WS_DATA_FEATURE +#ifdef WEBDAV_FEATURE + case ESP_WEBDAV_ON: + res = DEFAULT_WEBDAV_ON; + break; +#endif //WEBDAV_FEATURE #ifdef SD_DEVICE case ESP_SD_SPEED_DIV: res = DEFAULT_SDREADER_SPEED; @@ -413,6 +420,11 @@ uint32_t Settings_ESP3D::get_default_int32_value(int pos) res = DEFAULT_WEBSOCKET_PORT; break; #endif //WS_DATA_FEATURE +#ifdef WEBDAV_FEATURE + case ESP_WEBDAV_PORT: + res = DEFAULT_WEBDAV_PORT; + break; +#endif //WEBDAV_FEATURE #if defined(SENSOR_DEVICE) case ESP_SENSOR_INTERVAL: res = DEFAULT_SENSOR_INTERVAL; @@ -449,6 +461,11 @@ uint32_t Settings_ESP3D::get_max_int32_value(int pos) res = MAX_TELNET_PORT; break; #endif //TELNET_FEATURE +#ifdef WEBDAV_FEATURE + case ESP_WEBDAV_PORT: + res = MAX_WEBDAV_PORT; + break; +#endif //WEBDAV_FEATURE #ifdef WS_DATA_FEATURE case ESP_WEBSOCKET_PORT: res = MAX_WEBSOCKET_PORT; @@ -495,6 +512,11 @@ uint32_t Settings_ESP3D::get_min_int32_value(int pos) res = MIN_WEBSOCKET_PORT; break; #endif //WS_DATA_FEATURE +#ifdef WEBDAV_FEATURE + case ESP_WEBDAV_PORT: + res = MIN_WEBDAV_PORT; + break; +#endif //WEBDAV_FEATURE #if defined(SENSOR_DEVICE) case ESP_SENSOR_INTERVAL: res = MIN_SENSOR_INTERVAL; @@ -1125,13 +1147,18 @@ bool Settings_ESP3D::reset(bool networkonly) //TELNET Port Settings_ESP3D::write_uint32 (ESP_TELNET_PORT, Settings_ESP3D::get_default_int32_value(ESP_TELNET_PORT)); #endif //TELNET - #ifdef WS_DATA_FEATURE //Websocket On Settings_ESP3D::write_byte(ESP_WEBSOCKET_ON,Settings_ESP3D::get_default_byte_value(ESP_WEBSOCKET_ON)); //Websocket Port Settings_ESP3D::write_uint32 (ESP_WEBSOCKET_PORT, Settings_ESP3D::get_default_int32_value(ESP_WEBSOCKET_PORT)); #endif //WS_DATA_FEATURE +#ifdef WEBDAV_FEATURE + //WebDav On + Settings_ESP3D::write_byte(ESP_WEBDAV_ON,Settings_ESP3D::get_default_byte_value(ESP_WEBDAV_ON)); + //WebDav Port + Settings_ESP3D::write_uint32 (ESP_WEBDAV_PORT, Settings_ESP3D::get_default_int32_value(ESP_WEBDAV_PORT)); +#endif //WEBDAV_FEATURE #ifdef AUTHENTICATION_FEATURE //Admin password Settings_ESP3D::write_string(ESP_ADMIN_PWD,Settings_ESP3D::get_default_string_value(ESP_ADMIN_PWD).c_str()); diff --git a/esp3d/src/core/settings_esp3d.h b/esp3d/src/core/settings_esp3d.h index ac8432a7..c5d2a77d 100644 --- a/esp3d/src/core/settings_esp3d.h +++ b/esp3d/src/core/settings_esp3d.h @@ -102,7 +102,8 @@ #define ESP_FTP_ON 1021 //1 byte = flag #define ESP_AUTO_NOTIFICATION 1022 //1 byte = flag #define ESP_VERBOSE_BOOT 1023 //1 byte = flag - +#define ESP_WEBDAV_ON 1024 //1 byte = flag +#define ESP_WEBDAV_PORT 1025 //4 bytes= int //Hidden password #define HIDDEN_PASSWORD "********" diff --git a/esp3d/src/include/defines.h b/esp3d/src/include/defines.h index 21466e2b..e83856c0 100644 --- a/esp3d/src/include/defines.h +++ b/esp3d/src/include/defines.h @@ -151,6 +151,10 @@ #define ESP_FILE_WRITE 1 #define ESP_FILE_APPEND 2 +#define ESP_SEEK_SET 0 +#define ESP_SEEK_CUR 1 +#define ESP_SEEK_END 2 + #define FS_ROOT 0 #define FS_FLASH 1 #define FS_SD 2 diff --git a/esp3d/src/include/sanity_esp3d.h b/esp3d/src/include/sanity_esp3d.h index 33b5c8f2..845fa2cb 100644 --- a/esp3d/src/include/sanity_esp3d.h +++ b/esp3d/src/include/sanity_esp3d.h @@ -133,6 +133,27 @@ #endif #endif +/************************** + * WebDav + * ***********************/ +#if defined(WEBDAV_FEATURE) && !defined(GLOBAL_FILESYSTEM_FEATURE) +#if WEBDAV_FEATURE == FS_ROOT +#error WEBDAV_FEATURE == FS_ROOT is not available because GLOBAL_FILESYSTEM_FEATURE is not enabled +#endif +#endif + +#if defined(WEBDAV_FEATURE) && !defined(FILESYSTEM_FEATURE) +#if WEBDAV_FEATURE == FS_FLASH +#error WEBDAV_FEATURE == FS_FLASH is not available because FILESYSTEM_FEATURE is not enabled +#endif +#endif + +#if defined(WEBDAV_FEATURE) && !defined(SD_DEVICE) +#if WEBDAV_FEATURE == FS_SD +#error WEBDAV_FEATURE == FS_SD is not available because SD_DEVICE is not enabled +#endif +#endif + /************************** * Update * ***********************/ diff --git a/esp3d/src/modules/filesystem/esp_filesystem.h b/esp3d/src/modules/filesystem/esp_filesystem.h index 6f478ef5..dff048d5 100644 --- a/esp3d/src/modules/filesystem/esp_filesystem.h +++ b/esp3d/src/modules/filesystem/esp_filesystem.h @@ -35,6 +35,7 @@ public: ~ESP_File(); operator bool() const; bool isDirectory(); + bool seek(uint32_t pos, uint8_t mode = ESP_SEEK_SET); const char* name() const; const char* filename() const; void close(); @@ -70,6 +71,7 @@ public: static size_t totalBytes(); static size_t usedBytes(); static size_t freeBytes(); + static uint maxPathLength(); static size_t max_update_size(); static const char * FilesystemName(); static bool format(); diff --git a/esp3d/src/modules/filesystem/esp_globalFS.cpp b/esp3d/src/modules/filesystem/esp_globalFS.cpp index ba2efb58..f24f6ba0 100644 --- a/esp3d/src/modules/filesystem/esp_globalFS.cpp +++ b/esp3d/src/modules/filesystem/esp_globalFS.cpp @@ -102,6 +102,22 @@ uint64_t ESP_GBFS::usedBytes(uint8_t FS) return 0; } +uint ESP_GBFS::maxPathLength() +{ + uint size = 255; +#ifdef FILESYSTEM_FEATURE + if(size >32) { + size =32; + } +#endif //FILESYSTEM_FEATURE +#ifdef SD_DEVICE + if(size >255) { + size =255; + } +#endif //SD_DEVICE + return size; +} + uint64_t ESP_GBFS::freeBytes(uint8_t FS) { #ifdef FILESYSTEM_FEATURE @@ -431,6 +447,25 @@ ESP_GBFile::operator bool() const return false; } +bool ESP_GBFile::seek(uint32_t pos, uint8_t mode) +{ +#if defined(FILESYSTEM_FEATURE) || defined(SD_DEVICE) + if (_type == FS_ROOT) { + //TBD + } +#endif //FILESYSTEM_FEATURE || SD_DEVICE +#ifdef FILESYSTEM_FEATURE + if (_type == FS_FLASH) { + _flashFile.seek(pos,mode); + } +#endif //FILESYSTEM_FEATURE +#ifdef SD_DEVICE + if (_type == FS_SD) { + return _sdFile.seek(pos,mode); + } +#endif //SD_DEVICE +} + void ESP_GBFile::close() { #if defined(FILESYSTEM_FEATURE) || defined(SD_DEVICE) diff --git a/esp3d/src/modules/filesystem/esp_globalFS.h b/esp3d/src/modules/filesystem/esp_globalFS.h index 79ab6b01..130ca625 100644 --- a/esp3d/src/modules/filesystem/esp_globalFS.h +++ b/esp3d/src/modules/filesystem/esp_globalFS.h @@ -45,6 +45,7 @@ public: ~ESP_GBFile(); operator bool() const; bool isDirectory(); + bool seek(uint32_t pos, uint8_t mode = ESP_SEEK_SET); const char* name() const; const char* shortname() const; const char* filename() const; @@ -80,10 +81,11 @@ private: class ESP_GBFS { public: - static bool isavailable(uint8_t FS); - static uint64_t totalBytes(uint8_t FS); - static uint64_t usedBytes(uint8_t FS); - static uint64_t freeBytes(uint8_t FS); + static bool isavailable(uint8_t FS=FS_UNKNOWN); + static uint64_t totalBytes(uint8_t FS=FS_UNKNOWN); + static uint64_t usedBytes(uint8_t FS=FS_UNKNOWN); + static uint64_t freeBytes(uint8_t FS=FS_UNKNOWN); + static uint maxPathLength(); static bool format(uint8_t FS, ESP3DOutput * output = nullptr); static ESP_GBFile open(const char* path, uint8_t mode = ESP_FILE_READ); static bool exists(const char* path); diff --git a/esp3d/src/modules/filesystem/esp_sd.h b/esp3d/src/modules/filesystem/esp_sd.h index d5cb3d17..31fb237e 100644 --- a/esp3d/src/modules/filesystem/esp_sd.h +++ b/esp3d/src/modules/filesystem/esp_sd.h @@ -36,6 +36,7 @@ public: ~ESP_SDFile(); operator bool() const; bool isDirectory(); + bool seek(uint32_t pos, uint8_t mode = ESP_SEEK_SET); const char* name() const; const char* shortname() const; const char* filename() const; @@ -76,6 +77,7 @@ public: static uint64_t totalBytes(); static uint64_t usedBytes(); static uint64_t freeBytes(); + static uint maxPathLength(); static const char * FilesystemName(); static bool format(ESP3DOutput * output = nullptr); static ESP_SDFile open(const char* path, uint8_t mode = ESP_FILE_READ); diff --git a/esp3d/src/modules/filesystem/flash/fat_esp32_filesystem.cpp b/esp3d/src/modules/filesystem/flash/fat_esp32_filesystem.cpp index ac75266f..34d8e830 100644 --- a/esp3d/src/modules/filesystem/flash/fat_esp32_filesystem.cpp +++ b/esp3d/src/modules/filesystem/flash/fat_esp32_filesystem.cpp @@ -53,6 +53,11 @@ size_t ESP_FileSystem::usedBytes() return (FFat.totalBytes() - FFat.freeBytes()); } +uint ESP_FileSystem::maxPathLength() +{ + return 32; +} + bool ESP_FileSystem::rename(const char *oldpath, const char *newpath) { return FFat.rename(oldpath,newpath); @@ -235,6 +240,11 @@ ESP_File::ESP_File(void* handle, bool isdir, bool iswritemode, const char * path } } +bool ESP_File::seek(uint32_t pos, uint8_t mode) +{ + return tFile_handle[_index].seek(pos, (SeekMode)mode); +} + void ESP_File::close() { if (_index != -1) { diff --git a/esp3d/src/modules/filesystem/flash/littlefs_esp8266_filesystem .cpp b/esp3d/src/modules/filesystem/flash/littlefs_esp8266_filesystem .cpp index f766f7c6..ce1e879c 100644 --- a/esp3d/src/modules/filesystem/flash/littlefs_esp8266_filesystem .cpp +++ b/esp3d/src/modules/filesystem/flash/littlefs_esp8266_filesystem .cpp @@ -57,6 +57,11 @@ size_t ESP_FileSystem::usedBytes() return info.usedBytes; } +uint ESP_FileSystem::maxPathLength() +{ + return 32; +} + bool ESP_FileSystem::rename(const char *oldpath, const char *newpath) { return LittleFS.rename(oldpath,newpath); @@ -264,6 +269,11 @@ ESP_File::ESP_File(void* handle, bool isdir, bool iswritemode, const char * path } } +bool ESP_File::seek(uint32_t pos, uint8_t mode) +{ + return tFile_handle[_index].seek(pos, (SeekMode)mode); +} + void ESP_File::close() { if (_index != -1) { diff --git a/esp3d/src/modules/filesystem/flash/spiffs_esp32_filesystem.cpp b/esp3d/src/modules/filesystem/flash/spiffs_esp32_filesystem.cpp index 7d2a6701..36394fd7 100644 --- a/esp3d/src/modules/filesystem/flash/spiffs_esp32_filesystem.cpp +++ b/esp3d/src/modules/filesystem/flash/spiffs_esp32_filesystem.cpp @@ -51,6 +51,11 @@ size_t ESP_FileSystem::usedBytes() return SPIFFS.usedBytes(); } +uint ESP_FileSystem::maxPathLength() +{ + return 32; +} + bool ESP_FileSystem::rename(const char *oldpath, const char *newpath) { return SPIFFS.rename(oldpath,newpath); @@ -259,6 +264,11 @@ ESP_File::ESP_File(void* handle, bool isdir, bool iswritemode, const char * path } } +bool ESP_File::seek(uint32_t pos, uint8_t mode) +{ + return tFile_handle[_index].seek(pos, (SeekMode)mode); +} + void ESP_File::close() { if (_index != -1) { diff --git a/esp3d/src/modules/filesystem/flash/spiffs_esp8266_filesystem.cpp b/esp3d/src/modules/filesystem/flash/spiffs_esp8266_filesystem.cpp index 7bf35162..1feacdac 100644 --- a/esp3d/src/modules/filesystem/flash/spiffs_esp8266_filesystem.cpp +++ b/esp3d/src/modules/filesystem/flash/spiffs_esp8266_filesystem.cpp @@ -55,6 +55,11 @@ size_t ESP_FileSystem::usedBytes() return info.usedBytes; } +uint ESP_FileSystem::maxPathLength() +{ + return 32; +} + bool ESP_FileSystem::rename(const char *oldpath, const char *newpath) { return SPIFFS.rename(oldpath,newpath); @@ -284,6 +289,11 @@ ESP_File::ESP_File(void* handle, bool isdir, bool iswritemode, const char * path } } +bool ESP_File::seek(uint32_t pos, uint8_t mode) +{ + return tFile_handle[_index].seek(pos, (SeekMode)mode); +} + void ESP_File::close() { if (_index != -1) { diff --git a/esp3d/src/modules/filesystem/sd/sd_native_esp32.cpp b/esp3d/src/modules/filesystem/sd/sd_native_esp32.cpp index b7a1557f..b3b1edf4 100644 --- a/esp3d/src/modules/filesystem/sd/sd_native_esp32.cpp +++ b/esp3d/src/modules/filesystem/sd/sd_native_esp32.cpp @@ -115,6 +115,11 @@ uint64_t ESP_SD::freeBytes() return (SD.totalBytes() - SD.usedBytes()); } +uint ESP_SD::maxPathLength() +{ + return 255; +} + bool ESP_SD::rename(const char *oldpath, const char *newpath) { return SD.rename(oldpath,newpath); @@ -283,6 +288,11 @@ ESP_SDFile::ESP_SDFile(void* handle, bool isdir, bool iswritemode, const char * } } +bool ESP_SDFile::seek(uint32_t pos, uint8_t mode) +{ + return tSDFile_handle[_index].seek(pos, (SeekMode)mode); +} + void ESP_SDFile::close() { if (_index != -1) { diff --git a/esp3d/src/modules/filesystem/sd/sd_native_esp8266.cpp b/esp3d/src/modules/filesystem/sd/sd_native_esp8266.cpp index 815969e8..05cfc210 100644 --- a/esp3d/src/modules/filesystem/sd/sd_native_esp8266.cpp +++ b/esp3d/src/modules/filesystem/sd/sd_native_esp8266.cpp @@ -171,6 +171,11 @@ uint64_t ESP_SD::freeBytes() return volFree * blocks * 512; } +uint ESP_SD::maxPathLength() +{ + return 255; +} + bool ESP_SD::rename(const char *oldpath, const char *newpath) { return SD.rename(oldpath,newpath); @@ -712,6 +717,11 @@ bool ESP_SD::rmdir(const char *path) return res; } +bool ESP_SDFile::seek(uint32_t pos, uint8_t mode) +{ + return tSDFile_handle[_index].seek(pos, (SeekMode)mode); +} + void ESP_SD::closeAll() { for (uint8_t i = 0; i < ESP_MAX_SD_OPENHANDLE; i++) { diff --git a/esp3d/src/modules/filesystem/sd/sd_sdfat_esp32.cpp b/esp3d/src/modules/filesystem/sd/sd_sdfat_esp32.cpp index b663ed1d..ee4feca1 100644 --- a/esp3d/src/modules/filesystem/sd/sd_sdfat_esp32.cpp +++ b/esp3d/src/modules/filesystem/sd/sd_sdfat_esp32.cpp @@ -150,6 +150,11 @@ uint64_t ESP_SD::usedBytes() return totalBytes() - freeBytes(); } +uint ESP_SD::maxPathLength() +{ + return 255; +} + uint64_t ESP_SD::freeBytes() { static uint64_t volFree; @@ -711,6 +716,14 @@ void ESP_SD::closeAll() } } +bool ESP_SDFile::seek(uint32_t pos, uint8_t mode) +{ + if (mode == SeekEnd) { + return tSDFile_handle[_index].seek(-pos); //based on SDFS comment + } + return tSDFile_handle[_index].seek(pos); +} + ESP_SDFile::ESP_SDFile(void* handle, bool isdir, bool iswritemode, const char * path) { _isdir = isdir; diff --git a/esp3d/src/modules/filesystem/sd/sdio_esp32.cpp b/esp3d/src/modules/filesystem/sd/sdio_esp32.cpp index 7a742a48..1e4d59c0 100644 --- a/esp3d/src/modules/filesystem/sd/sdio_esp32.cpp +++ b/esp3d/src/modules/filesystem/sd/sdio_esp32.cpp @@ -134,6 +134,11 @@ uint64_t ESP_SD::freeBytes() return (SD_MMC.totalBytes() - SD_MMC.usedBytes()); } +uint ESP_SD::maxPathLength() +{ + return 255; +} + bool ESP_SD::rename(const char *oldpath, const char *newpath) { return SD_MMC.rename(oldpath,newpath); @@ -305,6 +310,11 @@ ESP_SDFile::ESP_SDFile(void* handle, bool isdir, bool iswritemode, const char * } } +bool ESP_SDFile::seek(uint32_t pos, uint8_t mode) +{ + return tSDFile_handle[_index].seek(pos, (SeekMode)mode); +} + void ESP_SDFile::close() { if (_index != -1) { diff --git a/esp3d/src/modules/network/netconfig.h b/esp3d/src/modules/network/netconfig.h index ca8de027..d7c8510f 100644 --- a/esp3d/src/modules/network/netconfig.h +++ b/esp3d/src/modules/network/netconfig.h @@ -21,6 +21,8 @@ //boundaries +#define MAX_WEBDAV_PORT 65001 +#define MIN_WEBDAV_PORT 1 #define MAX_HTTP_PORT 65001 #define MIN_HTTP_PORT 1 #define MAX_FTP_PORT 65001 diff --git a/esp3d/src/modules/network/netservices.cpp b/esp3d/src/modules/network/netservices.cpp index ce61ea43..c3cbdbc4 100644 --- a/esp3d/src/modules/network/netservices.cpp +++ b/esp3d/src/modules/network/netservices.cpp @@ -52,6 +52,9 @@ #ifdef FTP_FEATURE #include "../ftp/FtpServer.h" #endif //FP_FEATURE +#ifdef WEBDAV_FEATURE +#include "../webdav/webdav_server.h" +#endif //WEBDAV_FEATURE #ifdef HTTP_FEATURE #include "../http/http_server.h" #endif //HTTP_FEATURE @@ -253,6 +256,18 @@ bool NetServices::begin() } } #endif //WS_DATA_FEATURE +#ifdef WEBDAV_FEATURE + if (!webdav_server.begin()) { + output.printMSG("Failed start Terminal Web Socket"); + } else { + if (webdav_server.started()) { + String stmp = "WebDav server started port " + String(webdav_server.port()); + if (Settings_ESP3D::isVerboseBoot()) { + output.printMSG(stmp.c_str()); + } + } + } +#endif //WEBDAV_FEATURE #if defined(HTTP_FEATURE) if (!websocket_terminal_server.begin()) { output.printMSG("Failed start Terminal Web Socket"); @@ -366,6 +381,9 @@ void NetServices::end() #if defined(HTTP_FEATURE) websocket_terminal_server.end(); #endif //HTTP_FEATURE +#ifdef WEBDAV_FEATURE + webdav_server.end(); +#endif //WEBDAV_FEATURE #ifdef HTTP_FEATURE HTTP_Server::end(); #endif //HTTP_FEATURE @@ -402,6 +420,9 @@ void NetServices::handle() #ifdef HTTP_FEATURE HTTP_Server::handle(); #endif //HTTP_FEATURE +#ifdef WEBDAV_FEATURE + webdav_server.handle(); +#endif //WEBDAV_FEATURE #ifdef WS_DATA_FEATURE websocket_data_server.handle(); #endif //WS_DATA_FEATURE diff --git a/esp3d/src/modules/update/update_service.cpp b/esp3d/src/modules/update/update_service.cpp index 077210d9..ff312e74 100644 --- a/esp3d/src/modules/update/update_service.cpp +++ b/esp3d/src/modules/update/update_service.cpp @@ -95,6 +95,7 @@ const char * ServintKeysVal[] = { "TELNET_Port", "SENSOR_INTERVAL", "WebSocket_Port", + "WebDav_Port", "FTP_Control_Port", "FTP_Active_Port ", "FTP_Passive_Port" @@ -105,6 +106,7 @@ const uint16_t ServintKeysPos[] = { ESP_TELNET_PORT, ESP_SENSOR_INTERVAL, ESP_WEBSOCKET_PORT, + ESP_WEBDAV_PORT, ESP_FTP_CTRL_PORT, ESP_FTP_DATA_ACTIVE_PORT, ESP_FTP_DATA_PASSIVE_PORT @@ -121,6 +123,7 @@ const uint16_t SysintKeysPos[] = {ESP_BAUD_RATE, const char * ServboolKeysVal[] = {"HTTP_active", "TELNET_active", "WebSocket_active", + "WebDav_active", "Time_DST", "CHECK_FOR_UPDATE", "Active_buzzer", @@ -130,6 +133,7 @@ const char * ServboolKeysVal[] = {"HTTP_active", const uint16_t ServboolKeysPos[] = {ESP_HTTP_ON, ESP_TELNET_ON, ESP_WEBSOCKET_ON, + ESP_WEBDAV_ON, ESP_TIME_IS_DST, ESP_SD_CHECK_UPDATE_AT_BOOT, ESP_BUZZER, diff --git a/esp3d/src/modules/webdav/ESPWebDAV.cpp b/esp3d/src/modules/webdav/ESPWebDAV.cpp new file mode 100644 index 00000000..5adf7b2c --- /dev/null +++ b/esp3d/src/modules/webdav/ESPWebDAV.cpp @@ -0,0 +1,1651 @@ +/* + Copyright (c) 2018 Gurpreet Bal https://github.com/ardyesp/ESPWebDAV + Copyright (c) 2020 David Gauchard https://github.com/d-a-v/ESPWebDAV + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. + Modified 22 Jan 2021 by Luc Lebosse (ESP3D Integration) + +*/ +#include "../../include/esp3d_config.h" + +#if defined (WEBDAV_FEATURE) +#include +#include "ESPWebDAV.h" + +#if defined(ARDUINO_ARCH_ESP8266) +#include +#include // crc32() +#include +typedef esp8266::polledTimeout::oneShotFastMs PolledTimeout +#define BUFFER_SIZE 128 +#endif //ARDUINO_ARCH_ESP8266 +#if defined(ARDUINO_ARCH_ESP32) +#include +#include "PolledTimeout_esp32.h" +#include +#undef crc32 +#define crc32(a, len) mz_crc32( 0xffffffff,(const unsigned char *)a, len) +#define BUFFER_SIZE 1024 +#endif //ARDUINO_ARCH_ESP32 + + +// define cal constants +const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; +const char *wdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + +#define ALLOW "PROPPATCH,PROPFIND,OPTIONS,DELETE" SCUNLOCK ",COPY" SCLOCK ",MOVE,HEAD,POST,PUT,GET" + +#if WEBDAV_LOCK_SUPPORT +#define SLOCK "LOCK" +#define SCLOCK ",LOCK" +#define SUNLOCK "UNLOCK" +#define SCUNLOCK ",UNLOCK" +#else +#define SLOCK "" +#define SCLOCK "" +#define SUNLOCK "" +#define SCUNLOCK "" +#endif + +#define DEBUG_LEN 160 + +#define PROC "proc" // simple virtual file. TODO XXX real virtual fs with user callbacks + +void ESPWebDAVCore::begin() +{ + _maxPathLength = WebDavFS::maxPathLength(); +} + +void ESPWebDAVCore::stripSlashes(String& name) +{ + size_t i = 0; + while (i < name.length()) + if (name[i] == '/' && name.length() > 1 && ((i == name.length() - 1) || name[i + 1] == '/')) { + name.remove(i, 1); + } else { + i++; + } +} + +#if WEBDAV_LOCK_SUPPORT + + +void ESPWebDAVCore::makeToken(String& ret, uint32_t pash, uint32_t ownash) +{ + char lock_token[17]; + snprintf(lock_token, sizeof(lock_token), "%08x%08x", pash, ownash); + ret = lock_token; +} + + +int ESPWebDAVCore::extractLockToken(const String& someHeader, const char* start, const char* end, uint32_t& pash, uint32_t& ownash) +{ + // If: (<46dd353d7e585af1>) + // => + // IfToken: path:0x46dd353d / owner:0x7e585af1 + + pash = 0; + ownash = 0; + + log_esp3d("extracting lockToken from '%s'", someHeader.c_str()); + // extract "... <:[lock > + int startIdx = someHeader.indexOf(start); + if (startIdx < 0) { + log_esp3d("lock: can't find '%s'", start); + return 412; // fail with precondition failed + } + startIdx += strlen(start); + int endIdx = someHeader.indexOf(end, startIdx); + if (endIdx < 0) { + log_esp3d("lock: can't find '%s'", end); + return 412; // fail with precondition fail + } + log_esp3d("found in [%d..%d[ (%d)", startIdx, endIdx, endIdx - startIdx); + int len = endIdx - startIdx; + if (len < 1 || len > 16) { + log_esp3d("lock: format error (1-16 hex chars)"); + return 423; // fail with lock + } + char cp [len + 1]; + memcpy(cp, &(someHeader.c_str()[startIdx]), len); + cp[len] = 0; + log_esp3d("IfToken: '%s'", cp); + int ownIdx = std::max(len - 8, 0); + ownash = strtoul(&cp[ownIdx], nullptr, 16); + cp[ownIdx] = 0; + pash = strtoul(cp, nullptr, 16); + log_esp3d("IfToken: path:0x%08x / owner:0x%08x", pash, ownash); + return 200; +} + +#endif // WEBDAV_LOCK_SUPPORT + + +int ESPWebDAVCore::allowed(const String& uri, uint32_t ownash) +{ +#if WEBDAV_LOCK_SUPPORT > 1 + + String test = uri; + while (test.length()) { + stripSlashes(test); + log_esp3d("lock: testing '%s'", test.c_str()); + uint32_t hash = crc32(test.c_str(), test.length()); + const auto& lock = _locks.find(hash); + if (lock != _locks.end()) { + log_esp3d("lock: found lock, %sowner!", lock->second == ownash ? "" : "not"); + return lock->second == ownash ? 200 : 423; + } + int s = test.lastIndexOf('/'); + if (s < 0) { + break; + } + test.remove(s); + } + log_esp3d("lock: none found"); + return 200; + +#else + + (void)uri; + (void)ownash; + return 200; + +#endif +} + + +int ESPWebDAVCore::allowed(const String& uri, const String& xml /* = emptyString */) +{ + uint32_t hpash, anyownash; + if (ifHeader.length()) { + int code = extractLockToken(ifHeader, "(<", ">", hpash, anyownash); + if (code != 200) { + return code; + } + if (anyownash == 0) + // malformed + { + return 412; // PUT failed with 423 not 412 + } + } else { + int startIdx = xml.indexOf(""); + int endIdx = xml.indexOf(""); + anyownash = startIdx > 0 && endIdx > 0 ? crc32(&(xml.c_str()[startIdx + 7]), endIdx - startIdx - 7) : 0; + } + return allowed(uri, anyownash); +} + + +void ESPWebDAVCore::stripName(String& name) +{ + if (name.length() > (size_t)_maxPathLength) { + int dot = name.lastIndexOf('.'); + int newDot = _maxPathLength - (name.length() - dot); + if (dot <= 0 || newDot < 0) { + name.remove(_maxPathLength); + } else { + name.remove(newDot, dot - newDot); + } + } +} + + +void ESPWebDAVCore::stripHost(String& name) +{ + int remove = name.indexOf(hostHeader); + if (remove >= 0) { + name.remove(0, remove + hostHeader.length()); + } +} + + +void ESPWebDAVCore::dir(const String& path, Print* out) +{ + dirAction(path, true, [out](int depth, const String & parent, WebDavFile & entry)->bool { + (void)parent; + for (int i = 0; i < depth; i++) + out->print(" "); + if (entry.isDirectory()) + out->printf("[%s]\n", entry.name()); + else + out->printf("%-40s%4dMiB %6dKiB %d\n", + entry.name(), + ((int)entry.size() + (1 << 19)) >> 20, + ((int)entry.size() + (1 << 9)) >> 10, + (int)entry.size()); + return true; + }, /*false=subdir first*/false); +} + + +size_t ESPWebDAVCore::makeVirtual(virt_e v, String& internal) +{ + if (v == VIRT_PROC) { +#if defined(ARDUINO_ARCH_ESP8266) + internal = ESP.getFullVersion(); +#endif //ARDUINO_ARCH_ESP8266 +#if defined(ARDUINO_ARCH_ESP32) + internal = "SDK:"; + internal += ESP.getSdkVersion(); +#endif //ARDUINO_ARCH_ESP32 + internal += '\n'; + } + return internal.length(); +} + + +ESPWebDAVCore::virt_e ESPWebDAVCore::isVirtual(const String& uri) +{ + const char* n = &(uri.c_str()[0]); + while (*n && *n == '/') { + n++; + } + if (strcmp(n, PROC) == 0) { + return VIRT_PROC; + } + return VIRT_NONE; +} + + +bool ESPWebDAVCore::getPayload(StreamString& payload) +{ + log_esp3d("content length=%d", (int)contentLengthHeader); + payload.clear(); + if (contentLengthHeader > 0) { + payload.reserve(contentLengthHeader); + PolledTimeout timeout(HTTP_MAX_POST_WAIT); + while (payload.length() < (size_t)contentLengthHeader) { + uint8_t buf[16]; + auto n = client->read(buf, std::min((size_t)client->available(), sizeof(buf))); + if (n <= 0 && timeout) { + log_esp3d("get content: short read (%d < %d)", + (int)payload.length(), (int)contentLengthHeader); + return false; + } + if (n > 0) { + payload.write(buf, n); + timeout.reset(); + } + } + log_esp3d(">>>>>>>>>>> CONTENT:"); + log_esp3ds("%s",payload.c_str()); + log_esp3ds("\n"); + log_esp3d("<<<<<<<<<<< CONTENT"); + } + return true; +} + + +bool ESPWebDAVCore::dirAction(const String& path, + bool recursive, + const std::function& cb, + bool callAfter, + int depth) +{ + log_esp3d("diraction: scanning dir '%s'", path.c_str()); + WebDavFile root = WebDavFS::open(path.c_str()); + WebDavFile entry = root.openNextFile(); + while(entry) { + if (!entry.isDirectory()) { + log_esp3d("diraction: %s/%s (%d B): ", path.c_str(), entry.name(), (int)entry.size()); + if (cb(depth, path, entry)) { + log_esp3d("(file-OK)"); + } else { + log_esp3d("(file-abort)"); + return false; + } + } + entry.close(); + entry = root.openNextFile(); + } + root.close(); + if (recursive) { + root = WebDavFS::open(path.c_str()); + entry = root.openNextFile(); + while(entry) { + if (entry.isDirectory()) { + log_esp3d("diraction: -------- %s/%s/", path.c_str(), entry.name()); + if ((callAfter || cb(depth, path, entry)) + && dirAction(path + '/' + entry.name(), recursive, cb, callAfter, depth + 1) + && (!callAfter || cb(depth, path, entry))) { + log_esp3d("(dir-OK)"); + } else { + log_esp3d("(dir-abort)"); + return false; + } + } + entry.close(); + entry = root.openNextFile(); + } + root.close(); + } + + return true; +} + + +void ESPWebDAVCore::handleIssue(int code, const char* text) +{ + String message; + message.reserve(strlen(text) + uri.length() + method.length() + 32); + message += text; + message += "\nURI: "; + message += uri; + message += " Method: "; + message += method; + message += "\n"; + + String err; + err.reserve(strlen(text) + 32); + err += code; + err += ' '; + err += text; + + log_esp3d("Issue:\ntext='%s'", text); + log_esp3d("message='%s'", message.c_str()); + log_esp3d("err='%s'", err.c_str()); + + send(err, "text/plain", message); +} + + +void ESPWebDAVCore::handleRequest() +{ + payload.clear(); + + ResourceType resource = RESOURCE_NONE; + + // check depth header + depth = DEPTH_NONE; + if (depthHeader.length()) { + if (depthHeader.equals("1")) { + depth = DEPTH_CHILD; + } else if (depthHeader.equals("infinity")) { + depth = DEPTH_ALL; + } + log_esp3d("Depth: %d",depth); + } + WebDavFile file; + if (WebDavFS::exists(uri.c_str()) || (uri=="/")) { + // does uri refer to a file or directory or a null? + file = WebDavFS::open(uri.c_str()); + if (file) { + resource = file.isDirectory() ? RESOURCE_DIR : RESOURCE_FILE; + log_esp3d("resource: '%s' is %s", uri.c_str(), resource == RESOURCE_DIR ? "dir" : "file"); + } else { + log_esp3d("resource: '%s': no file nor dir", uri.c_str()); + } + } else { + log_esp3d("resource: '%s': not exists", uri.c_str()); + } + + log_esp3d("m: %s",method.c_str()); + log_esp3d(" r: %d", resource); + log_esp3d(" u: %s", uri.c_str()); + + // add header that gets sent everytime +#if WEBDAV_LOCK_SUPPORT + sendHeader("DAV", "1, 2"); +#else + sendHeader("DAV", "1"); +#endif + sendHeader("Accept-Ranges", "bytes"); + sendHeader("Allow", ALLOW); + + // handle file create/uploads + if (method.equals("PUT")) + // payload is managed + { + return handlePut(resource); + } + + // swallow content + if (!getPayload(payload)) { + handleIssue(408, "Request Time-out"); + client->stop(); + return; + } + + // handle properties + if (method.equals("PROPFIND")) { + return handleProp(resource, file); + } + + if (method.equals("GET")) { + return handleGet(resource, file, true); + } + + if (method.equals("HEAD")) { + return handleGet(resource, file, false); + } + + // handle options + if (method.equals("OPTIONS")) { + return handleOptions(resource); + } + +#if WEBDAV_LOCK_SUPPORT + // handle file locks + if (method.equals("LOCK")) { + return handleLock(resource); + } + + if (method.equals("UNLOCK")) { + return handleUnlock(resource); + } +#endif + + if (method.equals("PROPPATCH")) { + return handlePropPatch(resource, file); + } + + // directory creation + if (method.equals("MKCOL")) { + return handleDirectoryCreate(resource); + } + + // move a file or directory + if (method.equals("MOVE")) { + return handleMove(resource, file); + } + + // delete a file or directory + if (method.equals("DELETE")) { + return handleDelete(resource); + } + + // delete a file or directory + if (method.equals("COPY")) { + return handleCopy(resource, file); + } + + // if reached here, means its a unhandled + handleIssue(404, "Not found"); + + //return false; +} + + +void ESPWebDAVCore::handleOptions(ResourceType resource) +{ + (void)resource; + log_esp3d("Processing OPTION"); + + send("200 OK", NULL, ""); +} + + +#if WEBDAV_LOCK_SUPPORT + +void ESPWebDAVCore::handleLock(ResourceType resource) +{ + log_esp3d("Processing LOCK"); + + // does URI refer to an existing resource + (void)resource; + log_esp3d("r=%d/%d", resource, RESOURCE_NONE); + +#if WEBDAV_LOCK_SUPPORT > 1 + // lock owner + uint32_t hpash, ownash; + if (ifHeader.length()) { + int code; + if ((code = extractLockToken(ifHeader, "(<", ">", hpash, ownash)) != 200) { + return handleIssue(code, "Lock error"); + } + } else { + int startIdx, endIdx; + startIdx = payload.indexOf(""); + endIdx = payload.indexOf(""); + ownash = startIdx > 0 && endIdx > 0 ? crc32(&payload[startIdx + 7], endIdx - startIdx - 7) : 0; + } + + if (!ownash) { + /* XXXFIXME xml extraction should be improved (on macOS) + 0:10:08.058253: + 0:10:08.058391: http://www.apple.com/webdav_fs/ + 0:10:08.058898: + */ + ownash = 0xdeadbeef; + } + uint32_t pash = crc32(uri.c_str(), uri.length()); + const auto& lock = _locks.find(pash); + if (lock == _locks.end()) { + _locks[pash] = ownash; + } else { + if (lock->second != ownash) { + log_esp3d("cannot relock '%s' (owner is 0x%08x)", uri.c_str(), lock->second); + return handleIssue(423, "Locked"); + } + log_esp3d("owner has relocked"); + } +#else + const char* lock_token = "0"; +#endif + + String lock_token; + makeToken(lock_token, pash, ownash); + sendHeader("Lock-Token", lock_token); + +#if 1 + String resp; + resp.reserve(500 + uri.length()); + resp += F("" + "" + "" + "" + "" + ""); + resp += lock_token; + resp += F("" + "" +#if 0 + "" + "" + "" + "" + "" + "" + "" + ""); + resp += uri; + resp += F("" + "" + "" + "infinity" + ""); +#if 0 + if (href.length()) { + resp += F("" + ""); + resp += href; + resp += F("" + ""); + } +#endif + resp += F("" + "Second-3600" + "" +#endif + "" + "" + ""); + send("200 OK", "application/xml;charset=utf-8", resp); +#else + send("200 OK", "application/xml;charset=utf-8", ""); +#endif +} + + +void ESPWebDAVCore::handleUnlock(ResourceType resource) +{ +#if WEBDAV_LOCK_SUPPORT > 1 + uint32_t pash = crc32(uri.c_str(), uri.length()); + + uint32_t hpash, hownash; + (void)extractLockToken(lockTokenHeader, "<", ">", hpash, hownash); + + auto lock = _locks.find(pash); + if (lock == _locks.end()) { + log_esp3d("wasn't locked: '%s'", uri.c_str()); + return handleIssue(423, "Locked"); + } + if (lock->second != hownash) { + log_esp3d("lock found, bad owner 0x%08x != 0x%08x", hownash, lock->second); + return handleIssue(423, "Locked"); + } + _locks.erase(lock); +#endif + + (void)resource; + log_esp3d("Processing UNLOCK"); + send("204 No Content", NULL, ""); +} + +#endif // WEBDAV_LOCK_SUPPORT + + +void ESPWebDAVCore::handlePropPatch(ResourceType resource, WebDavFile& file) +{ + log_esp3d("PROPPATCH forwarding to PROPFIND"); + handleProp(resource, file); +} + + +void ESPWebDAVCore::handleProp(ResourceType resource, WebDavFile& file) +{ + log_esp3d("Processing PROPFIND"); + auto v = isVirtual(uri); + + if (v) { + resource = RESOURCE_FILE; + } + // does URI refer to an existing resource + else if (resource == RESOURCE_NONE) { + return handleIssue(404, "Not found"); + } + + int code; + if (payload.indexOf("lockdiscovery") < 0 && (code = allowed(uri)) != 200) { + return handleIssue(code, "Locked"); + } + + setContentLength(CONTENT_LENGTH_UNKNOWN); + send("207 Multi-Status", "application/xml;charset=utf-8", ""); + sendContent(F("")); + sendContent(F("")); + + if (v) { + // virtual file + sendPropResponse(false, uri.c_str(), 1024, time(nullptr), 0); + } else if (!file.isDirectory() || depth == DEPTH_NONE) { + log_esp3d("----- PROP FILE '%s':", uri.c_str()); + sendPropResponse(file.isDirectory(), uri.c_str(), file.size(), file.getLastWrite(), file.getLastWrite()); + } else { + log_esp3d("----- PROP DIR '%s':", uri.c_str()); + sendPropResponse(true, uri,0, time(nullptr), 0); + + WebDavFile root = WebDavFS::open(uri.c_str()); + WebDavFile entry = root.openNextFile(); + while(entry) { + yield(); + String path; + path.reserve(uri.length() + 1 + strlen(entry.name())); + path += uri; + path += '/'; + path += entry.name(); + stripSlashes(path); + log_esp3d("Path: %s", path.c_str()); + sendPropResponse(entry.isDirectory(), path.c_str(), entry.size(), entry.getLastWrite(), entry.getLastWrite()); + entry.close(); + entry = root.openNextFile(); + } + root.close(); + } + if (payload.indexOf(F("quota-available-bytes")) >= 0 || + payload.indexOf(F("quota-used-bytes")) >= 0) { + + sendContentProp(F("quota-available-bytes"), String(1.0 * (WebDavFS::totalBytes() - WebDavFS::usedBytes()), 0)); + sendContentProp(F("quota-used-bytes"), String(1.0 * WebDavFS::usedBytes(), 0)); + } + sendContent(F("")); +} + + +void ESPWebDAVCore::sendContentProp(const String& what, const String& response) +{ + String one; + one.reserve(100 + 2 * what.length() + response.length()); + one += F(""); + one += response; + one += F(""); + sendContent(one); +} + + +String ESPWebDAVCore::date2date(time_t date) +{ + // get & convert time to required format + // Tue, 13 Oct 2015 17:07:35 GMT + tm* gTm = gmtime(&date); + char buf[40]; + snprintf(buf, sizeof(buf), "%s, %02d %s %04d %02d:%02d:%02d GMT", wdays[gTm->tm_wday], gTm->tm_mday, months[gTm->tm_mon], gTm->tm_year + 1900, gTm->tm_hour, gTm->tm_min, gTm->tm_sec); + return buf; +} + + +void ESPWebDAVCore::sendPropResponse(bool isDir, const String& fullResPath, size_t size, time_t lastWrite, time_t creationDate) +{ + String blah; + blah.reserve(100); + blah += F(""); + blah += fullResPath; + blah += F("HTTP/1.1 200 OK"); + sendContent(blah); + + sendContentProp(F("getlastmodified"), date2date(lastWrite)); + sendContentProp(F("creationdate"), date2date(creationDate)); + + log_esp3d("-----\nentry: '%s'(dir:%d)\n-----", + fullResPath.c_str(), isDir); + + if (isDir) { + sendContentProp(F("resourcetype"), F("")); + } else { + sendContentProp(F("getcontentlength"), String(size)); + sendContentProp(F("getcontenttype"), contentTypeFn(fullResPath)); + + sendContent(""); + + char entityTag [uri.length() + 32]; + sprintf(entityTag, "%s%lu", uri.c_str(), (unsigned long)lastWrite); + uint32_t crc = crc32(entityTag, strlen(entityTag)); + sprintf(entityTag, "\"%08x\"", crc); + sendContentProp(F("getetag"), entityTag); + } + + sendContentProp(F("displayname"), fullResPath); + + sendContent(F("")); +} + + +void ESPWebDAVCore::handleGet(ResourceType resource, WebDavFile& file, bool isGet) +{ + log_esp3d("Processing GET (ressource=%d)", (int)resource); + auto v = isVirtual(uri); + + // does URI refer to an existing file resource + if (resource != RESOURCE_FILE && !v) { + return handleIssue(404, "Not found"); + } + + // no lock on GET + +#if defined(ESP_DEBUG_FEATURE) + long tStart = millis(); +#endif + + size_t fileSize = file.size(); + String contentType = contentTypeFn(uri); + if (uri.endsWith(".gz") && contentType != "application/x-gzip" && contentType != "application/octet-stream") { + sendHeader("Content-Encoding", "gzip"); + } + + String internal = emptyString; + if (v) { + fileSize = makeVirtual(v, internal); + } else if (!fileSize) { + setContentLength(0); + send("200 OK", contentType.c_str(), ""); + log_esp3d("send empty file"); + return; + } + + char buf[BUFFER_SIZE]; /// XXX use stream::to(): file.to(client); + + // Content-Range: bytes 0-1023/146515 + // Content-Length: 1024 + + constexpr bool chunked = false; + + int remaining; + if (_rangeStart == 0 && (_rangeEnd < 0 || _rangeEnd == (int)fileSize - 1)) { + _rangeEnd = fileSize - 1; + remaining = fileSize; + setContentLength(chunked ? CONTENT_LENGTH_UNKNOWN : remaining); + send("200 OK", contentType.c_str(), ""); + } else { + if (_rangeEnd == -1 || _rangeEnd >= (int)fileSize) { + _rangeEnd = _rangeStart + (2 * TCP_MSS - 100); + if (_rangeEnd >= (int)fileSize) { + _rangeEnd = fileSize - 1; + } + } + snprintf(buf, sizeof(buf), "bytes %d-%d/%d", _rangeStart, _rangeEnd, (int)fileSize); + sendHeader("Content-Range", buf); + remaining = _rangeEnd - _rangeStart + 1; + setContentLength(chunked ? CONTENT_LENGTH_UNKNOWN : remaining); + send("206 Partial Content", contentType.c_str(), ""); + } + + if (isGet && (internal.length() || file.seek(_rangeStart))) { + log_esp3d("GET: (%d bytes, chunked=%d, remain=%d)", remaining, chunked, remaining); + + if (internal.length()) { + if (transferStatusFn) { + transferStatusFn(file.name(), (100 * _rangeStart) / fileSize, false); + } + if ((chunked && !sendContent(&internal.c_str()[_rangeStart], remaining)) + || (!chunked && client->write(&internal.c_str()[_rangeStart], remaining) != (size_t)remaining)) { + log_esp3d("file->net short transfer"); + } else if (transferStatusFn) { + transferStatusFn(file.name(), (100 * (_rangeStart + remaining)) / fileSize, false); + } + } else { + if (transferStatusFn) { + transferStatusFn(file.name(), 0, false); + } + int percent = 0; + + while (remaining > 0 && file.available()) { + size_t toRead = (size_t)remaining > sizeof(buf) ? sizeof(buf) : remaining; + size_t numRead = file.read((uint8_t*)buf, toRead); + log_esp3d("read %d bytes from file", (int)numRead); + + if ((chunked && !sendContent(buf, numRead)) + || (!chunked && client->write(buf, numRead) != numRead)) { + log_esp3d("file->net short transfer"); + ///XXXX transmit error ? + //return handleWriteRead("Unable to send file content", &file); + break; + } + +#if defined(ESP_DEBUG_FEATURE) + for (size_t i = 0; i < 80 && i < numRead; i++) { + log_esp3ds("%c", buf[i] < 32 || buf[i] > 127 ? '.' : buf[i]); + } +#endif + + remaining -= numRead; + if (transferStatusFn) { + int p = (100 * (file.size() - remaining)) / file.size(); + if (p != percent) { + transferStatusFn(file.name(), percent = p, false); + } + } + log_esp3d("wrote %d bytes to http client", (int)numRead); + } + } + } + + log_esp3d("File %d bytes sent in: %d sec", fileSize,(millis() - tStart) / 1000); +} + + +void ESPWebDAVCore::handlePut(ResourceType resource) +{ + log_esp3d("Processing Put"); + + // does URI refer to a directory + if (resource == RESOURCE_DIR) { + return handleIssue(404, "Not found"); + } + + int code ; + if ((code = allowed(uri)) != 200) { + return handleIssue(code, "Lock error"); + } + + WebDavFile file; + stripName(uri); + log_esp3d("create file '%s'", uri.c_str()); + String s = uri; + if (uri[0]!='/') { + s = "/" + uri; + } + log_esp3d("Create file %s", s.c_str()); + if (!(file = WebDavFS::open(s.c_str(), ESP_FILE_WRITE))) { + return handleWriteError("Unable to create a new file", file); + } + + // file is created/open for writing at this point + // did server send any data in put + log_esp3d("%s - ready for data (%i bytes)", uri.c_str(),(int)contentLengthHeader); + + if (contentLengthHeader != 0) { + uint8_t buf[BUFFER_SIZE]; +#if defined(ESP_DEBUG_FEATURE) + long tStart = millis(); +#endif + size_t numRemaining = contentLengthHeader; + + if (transferStatusFn) { + transferStatusFn(file.name(), 0, true); + } + int percent = 0; + + // read data from stream and write to the file + while (numRemaining > 0) { + size_t numToRead = numRemaining; + if (numToRead > sizeof(buf)) { + numToRead = sizeof(buf); + } + auto numRead = readBytesWithTimeout(buf, numToRead); + if (numRead == 0) { + break; + } + + size_t written = 0; + while (written < numRead) { + auto numWrite = file.write(buf + written, numRead - written); + if (numWrite == 0 || (int)numWrite == -1) { + log_esp3d("error: numread=%d write=%d written=%d", (int)numRead, (int)numWrite, (int)written); + return handleWriteError("Write data failed", file); + } + written += numWrite; + } + + // reduce the number outstanding + numRemaining -= numRead; + if (transferStatusFn) { + int p = (100 * (contentLengthHeader - numRemaining)) / contentLengthHeader; + if (p != percent) { + transferStatusFn(file.name(), percent = p, true); + } + } + } + + // detect timeout condition + if (numRemaining) { + return handleWriteError("Timed out waiting for data", file); + } + + log_esp3d("File %d bytes stored in: %d sec",(contentLengthHeader - numRemaining), ((millis() - tStart) / 1000)); + } + + log_esp3d("file written ('%s': %d = %d bytes)", String(file.name()).c_str(), (int)contentLengthHeader, (int)file.size()); + + if (resource == RESOURCE_NONE) { + send("201 Created", NULL, ""); + } else { + send("200 OK", NULL, ""); + } +} + + +void ESPWebDAVCore::handleWriteError(const String& message, WebDavFile& file) +{ + // close this file + file.close(); + // delete the wrile being written + WebDavFS::remove(uri.c_str()); + // send error + send("500 Internal Server Error", "text/plain", message); + log_esp3d("%s",message.c_str()); +} + + +void ESPWebDAVCore::handleDirectoryCreate(ResourceType resource) +{ + log_esp3d("Processing MKCOL (r=%d uri='%s' cl=%d)", (int)resource, uri.c_str(), (int)contentLengthHeader); + + if (contentLengthHeader) { + return handleIssue(415, "Unsupported Media Type"); + } + + // does URI refer to anything + if (resource != RESOURCE_NONE) { + return handleIssue(405, "Not allowed"); + } + + int parentLastIndex = uri.lastIndexOf('/'); + if (parentLastIndex > 0) { + WebDavFile testParent = WebDavFS::open(uri.substring(0, parentLastIndex).c_str()); + if (!testParent.isDirectory()) { + return handleIssue(409, "Conflict"); + } + } + + if (!WebDavFS::mkdir(uri.c_str())) { + // send error + send("500 Internal Server Error", "text/plain", "Unable to create directory"); + log_esp3d("Unable to create directory"); + return; + } + + log_esp3d("%s directory created", uri.c_str()); + send("201 Created", NULL, ""); +} + + +String ESPWebDAVCore::urlToUri(const String& url) +{ + int index; + if (url.startsWith("http") && (index = url.indexOf("://")) <= 5) { + int uriStart = url.indexOf('/', index + 3); + return url.substring(uriStart); + } + return url; +} + + +void ESPWebDAVCore::handleMove(ResourceType resource, WebDavFile& src) +{ + const char* successCode = "201 Created"; + + log_esp3d("Processing MOVE"); + + // does URI refer to anything + if (resource == RESOURCE_NONE + || destinationHeader.length() == 0) { + return handleIssue(404, "Not found"); + } + + String dest = enc2c(urlToUri(destinationHeader)); + stripHost(dest); + stripSlashes(dest); + stripName(dest); + log_esp3d("Move destination: %s", dest.c_str()); + + int code; + if ((code = allowed(uri)) != 200 || (code = allowed(dest)) != 200) { + return handleIssue(code, "Locked"); + } + + WebDavFile destFile; + if (WebDavFS::exists(dest.c_str()) || (dest=="/")) { + destFile = WebDavFS::open(dest.c_str()); + } + if (destFile && destFile.isDirectory()) { + dest += '/'; + dest += src.name(); + stripSlashes(dest); + stripName(dest); + destFile.close(); + destFile = WebDavFS::open(dest.c_str()); + successCode = "204 No Content"; // MOVE to existing collection resource didn't give 204 + } + + if (destFile) { + if (overwrite.equalsIgnoreCase("F")) { + return handleIssue(412, "Precondition Failed"); + } + if (destFile.isDirectory()) { + destFile.close(); + deleteDir(dest); + } else { + destFile.close(); + WebDavFS::remove(dest.c_str()); + } + } + + src.close(); + + log_esp3d("finally rename '%s' -> '%s'", uri.c_str(), dest.c_str()); + + if (!WebDavFS::rename(uri.c_str(), dest.c_str())) { + // send error + send("500 Internal Server Error", "text/plain", "Unable to move"); + log_esp3d("Unable to move file/directory"); + return; + } + + log_esp3d("Move successful"); + send(successCode, NULL, ""); +} + + +bool ESPWebDAVCore::mkFullDir(String fullDir) +{ + bool ret = true; + stripSlashes(fullDir); + for (int idx = 0; (idx = fullDir.indexOf('/', idx + 1)) > 0;) { + ///XXXoptiomizeme without substr + if (!WebDavFS::mkdir(fullDir.substring(0, idx).c_str())) { + ret = false; + break; + } + } + return ret; +} + + +bool ESPWebDAVCore::deleteDir(const String& dir) +{ + dirAction(dir, true, [this](int depth, const String & parent, WebDavFile & entry)->bool { + (void)depth; + String toRemove; + toRemove.reserve(parent.length() + strlen(entry.name()) + 2); + toRemove += parent; + toRemove += '/'; + toRemove += entry.name(); + bool ok = !!(entry.isDirectory() ? WebDavFS::rmdir(toRemove.c_str()) : WebDavFS::remove(toRemove.c_str())); + log_esp3d("DELETE %s %s: %s", entry.isDirectory() ? "[ dir]" : "[file]", toRemove.c_str(), ok ? "ok" : "bad"); + return ok; + }); + + log_esp3d("delete dir '%s'", uri.c_str()); + WebDavFS::rmdir(uri.c_str()); + // observation: with littleFS, when the last file of a directory is + // removed, the parent directory is removed, hierarchy must be rebuilded. + mkFullDir(uri); + + return true; +} + + +void ESPWebDAVCore::handleDelete(ResourceType resource) +{ + log_esp3d("Processing DELETE '%s'", uri.c_str()); + + // does URI refer to anything + if (resource == RESOURCE_NONE) { + return handleIssue(404, "Not found"); + } + + int code; + if ((code = allowed(uri)) != 200) { + return handleIssue(code, "Locked"); + } + + bool retVal; + if (resource == RESOURCE_FILE) + // delete a file + { + retVal = WebDavFS::remove(uri.c_str()); + } else { + retVal = deleteDir(uri); + } + + // for some reason, parent dir can be removed if empty + // need to leave it there (also to pass compliance tests). + int parentIdx = uri.lastIndexOf('/'); + uri.remove(parentIdx); + mkFullDir(uri); + + if (!retVal) { + // send error + send("500 Internal Server Error", "text/plain", "Unable to delete"); + log_esp3d("Unable to delete file/directory"); + return; + } + + log_esp3d("Delete successful"); + send("200 OK", NULL, ""); +} + + +bool ESPWebDAVCore::copyFile(WebDavFile srcFile, const String& destName) +{ + WebDavFile dest; + if (overwrite.equalsIgnoreCase("F")) { + dest = WebDavFS::open(destName.c_str()); + if (dest) { + log_esp3d("copy dest '%s' already exists and overwrite is false", destName.c_str()); + handleIssue(412, "Precondition Failed"); + return false; + } + } + String s = destName; + if (destName[0]!='/') { + s = "/" + destName; + } + dest = WebDavFS::open(s.c_str(), ESP_FILE_WRITE); + log_esp3d("Create file %s", s.c_str()); + if (!dest) { + handleIssue(413, "Request Entity Too Large"); + return false; + } + while (srcFile.available()) { + ///XXX USE STREAMTO + yield(); + char cp[128]; + int nb = srcFile.read((uint8_t*)cp, sizeof(cp)); + if (!nb) { + log_esp3d("copy: short read"); + handleIssue(500, "Internal Server Error"); + return false; + } + int wr = dest.write((const uint8_t*)cp, nb); + if (wr != nb) { + log_esp3d("copy: short write wr=%d != rd=%d", (int)wr, (int)nb); + handleIssue(500, "Internal Server Error"); + return false; + } + } + dest.close(); + return true; +} + + +void ESPWebDAVCore::handleCopy(ResourceType resource, WebDavFile& src) +{ + const char* successCode = "201 Created"; + + log_esp3d("Processing COPY"); + + if (resource == RESOURCE_NONE) { + return handleIssue(404, "Not found"); + } + + if (!src) { // || resource != RESOURCE_FILE) + return handleIssue(413, "Request Entity Too Large"); + } + + String destParentPath = destinationHeader; + { + int j = -1; + for (int i = 0; i < 3; i++) { + j = destParentPath.indexOf('/', j + 1); + } + destParentPath.remove(0, j); + } + + String destPath = destParentPath; + if (destPath.length()) { + if (destPath[destPath.length() - 1] == '/') { + // add file name + destPath += src.name(); + successCode = "204 No Content"; // COPY to existing resource should give 204 (RFC2518:S8.8.5) + } else { + // remove last part + int lastSlash = destParentPath.lastIndexOf('/'); + if (lastSlash > 0) { + destParentPath.remove(lastSlash); + } + } + } + + log_esp3d("copy: src='%s'=>'%s' dest='%s'=>'%s' parent:'%s'", + uri.c_str(), src.filename(), + destinationHeader.c_str(), destPath.c_str(), + destParentPath.c_str()); + WebDavFile destParent = WebDavFS::open(destParentPath.c_str()); + + stripName(destPath); + int code; + if (/*(code = allowed(uri)) != 200 ||*/ (code = allowed(destParentPath)) != 200 || (code = allowed(destPath)) != 200) { + return handleIssue(code, "Locked"); + } + + // copy directory + if (src.isDirectory()) { + log_esp3d("Source is directory"); + if (!destParent.isDirectory()) { + log_esp3d("'%s' is not a directory", destParentPath.c_str()); + return handleIssue(409, "Conflict"); + } + + if (!dirAction(src.filename(), depth == DEPTH_ALL, [this, destParentPath](int depth, const String & parent, WebDavFile & source)->bool { + (void)depth; + (void)parent; + String destNameX = destParentPath + '/'; + destNameX += source.name(); + stripName(destNameX); + log_esp3d("COPY: '%s' -> '%s", source.name(), destNameX.c_str()); + return copyFile(WebDavFS::open(source.name()), destNameX); + })) { + return; // handleIssue already called by failed copyFile() handleIssue(409, "Conflict"); + } + } else { + log_esp3d("Source is file"); + + // (COPY into non-existant collection '/litmus/nonesuch' succeeded) + if (!destParent || !destParent.isDirectory()) { + log_esp3d("dest dir '%s' not existing", destParentPath.c_str()); + return handleIssue(409, "Conflict"); + } + + // copy file + + if (!copyFile(src, destPath)) { + return; + } + } + + log_esp3d("COPY successful"); + send(successCode, NULL, ""); +} + + +void ESPWebDAVCore::_prepareHeader(String& response, const String& code, const char* content_type, size_t contentLength) +{ + response = "HTTP/1.1 " + code + "\r\n"; + + if (content_type) { + sendHeader("Content-Type", content_type, true); + } + + if ((size_t)_contentLengthAnswer == CONTENT_LENGTH_NOT_SET) { + sendHeader("Content-Length", String(contentLength)); + } else if ((size_t)_contentLengthAnswer != CONTENT_LENGTH_UNKNOWN) { + sendHeader("Content-Length", String(_contentLengthAnswer)); + } else { //if ((size_t)_contentLengthAnswer == CONTENT_LENGTH_UNKNOWN) + _chunked = true; + //sendHeader("Accept-Ranges", "none"); + sendHeader("Transfer-Encoding", "chunked"); + } + if (m_persistent) { + sendHeader("Connection", "keep-alive"); + } else { + sendHeader("Connection", "close"); + } + + response += _responseHeaders; + response += "\r\n"; +} + + +bool ESPWebDAVCore::parseRequest(const String& givenMethod, + const String& givenUri, + WiFiClient* givenClient, + ContentTypeFunction givenContentTypeFn) +{ + method = givenMethod; + uri = enc2c(givenUri); + stripSlashes(uri); + client = givenClient; + contentTypeFn = givenContentTypeFn; + + log_esp3d("############################################"); + log_esp3d(">>>>>>>>>> RECV"); + + log_esp3d("method: %s",method.c_str()); + log_esp3d(" url: %s",uri.c_str()); + + // parse and finish all headers + String headerName; + String headerValue; + _rangeStart = 0; + _rangeEnd = -1; + + log_esp3d("INPUT"); + // no new client is waiting, allow more time to current client + m_persistent_timer_ms = millis(); + + m_persistent = ((millis() - m_persistent_timer_ms) < m_persistent_timer_init_ms); + + // reset all variables + _chunked = false; + _responseHeaders.clear(); + _contentLengthAnswer = (int)CONTENT_LENGTH_NOT_SET; + contentLengthHeader = 0; + depthHeader.clear(); + hostHeader.clear(); + destinationHeader.clear(); + overwrite.clear(); + ifHeader.clear(); + lockTokenHeader.clear(); + + while (1) { + String req = client->readStringUntil('\r'); + client->readStringUntil('\n'); + if (req == "") + // no more headers + { + break; + } + + int headerDiv = req.indexOf(':'); + if (headerDiv == -1) { + break; + } + + headerName = req.substring(0, headerDiv); + headerValue = req.substring(headerDiv + 2); + log_esp3d("\t%s: %s", headerName.c_str(), headerValue.c_str()); + + if (headerName.equalsIgnoreCase("Host")) { + hostHeader = headerValue; + } else if (headerName.equalsIgnoreCase("Depth")) { + depthHeader = headerValue; + } else if (headerName.equalsIgnoreCase("Content-Length")) { + contentLengthHeader = headerValue.toInt(); + } else if (headerName.equalsIgnoreCase("Destination")) { + destinationHeader = headerValue; + } else if (headerName.equalsIgnoreCase("Range")) { + processRange(headerValue); + } else if (headerName.equalsIgnoreCase("Overwrite")) { + overwrite = headerValue; + } else if (headerName.equalsIgnoreCase("If")) { + ifHeader = headerValue; + } else if (headerName.equalsIgnoreCase("Lock-Token")) { + lockTokenHeader = headerValue; + } + } + log_esp3d("<<<<<<<<<< RECV"); + + bool ret = true; + /*ret =*/ handleRequest(); + + // finalize the response + if (_chunked) { + sendContent(""); + } + + return ret; +} + + +size_t ESPWebDAVCore::readBytesWithTimeout(uint8_t *buf, size_t size) +{ + size_t where = 0; + + while (where < size) { + int timeout_ms = HTTP_MAX_POST_WAIT; + while (!client->available() && client->connected() && timeout_ms--) { + delay(1); + } + + if (!client->available()) { + break; + } + + where += client->read(buf + where, size - where); + } + + return where; +} + + +void ESPWebDAVCore::sendHeader(const String& name, const String& value, bool first) +{ + String headerLine = name + ": " + value + "\r\n"; + + if (first) { + _responseHeaders = headerLine + _responseHeaders; + } else { + _responseHeaders += headerLine; + } +} + + +void ESPWebDAVCore::send(const String& code, const char* content_type, const String& content) +{ + String header; + _prepareHeader(header, code, content_type, content.length()); + + client->write(header.c_str(), header.length()); + + //log_esp3d(">>>>>>>>>> SENT"); + //log_esp3d("---- header: \n%s", header.c_str()); + + if (content.length()) { + sendContent(content); +#if defined(ESP_DEBUG_FEATURE) + log_esp3d("send content (%d bytes):", (int)content.length()); + for (size_t i = 0; i < DEBUG_LEN && i < content.length(); i++) { + log_esp3ds("%c", content[i] < 32 || content[i] > 127 ? '.' : content[i]); + } + if (content.length() > DEBUG_LEN) { + log_esp3ds("..."); + } + log_esp3ds("\n"); +#endif + } + //log_esp3d("<<<<<<<<<< SENT"); +} + + +bool ESPWebDAVCore::sendContent(const String& content) +{ + return sendContent(content.c_str(), content.length()); +} + + +bool ESPWebDAVCore::sendContent(const char* data, size_t size) +{ + if (_chunked) { + char chunkSize[32]; + snprintf(chunkSize, sizeof(chunkSize), "%x\r\n", (int)size); + size_t l = strlen(chunkSize); + if (client->write(chunkSize, l) != l) { + return false; + } + log_esp3d("---- chunk %s", chunkSize); + } + +#if defined(ESP_DEBUG_FEATURE) + log_esp3d("---- %scontent (%d bytes):", _chunked ? "chunked " : "", (int)size); + for (size_t i = 0; i < DEBUG_LEN && i < size; i++) { + log_esp3ds("%c", data[i] < 32 || data[i] > 127 ? '.' : data[i]); + } + if (size > DEBUG_LEN) { + log_esp3ds("..."); + } + log_esp3ds("\n"); +#endif + + if (client->write(data, size) != size) { + log_esp3d("SHORT WRITE"); + return false; + } + + if (_chunked) { + if (client->write("\r\n", 2) != 2) { + log_esp3d("SHORT WRITE 2"); + return false; + } + if (size == 0) { + log_esp3d("END OF CHUNKS"); + _chunked = false; + } + } + + log_esp3d("OK with sendContent"); + return true; +} + + +bool ESPWebDAVCore::sendContent_P(PGM_P content) +{ + const char * footer = "\r\n"; + size_t size = strlen_P(content); + + if (_chunked) { + char chunkSize[32]; + snprintf(chunkSize, sizeof(chunkSize), "%x%s", (int)size, footer); + size_t l = strlen(chunkSize); + if (client->write(chunkSize, l) != l) { + return false; + } + } + + if (client->write_P(content, size) != size) { + log_esp3d("SHORT WRITE"); + return false; + } + + if (_chunked) { + if (client->write(footer, 2) != 2) { + log_esp3d("SHORT WRITE 2"); + return false; + } + if (size == 0) { + log_esp3d("END OF CHUNKS"); + _chunked = false; + } + } + + log_esp3d("OK with sendContent_P"); + return true; +} + + +void ESPWebDAVCore::setContentLength(size_t len) +{ + _contentLengthAnswer = len; +} + + +void ESPWebDAVCore::processRange(const String& range) +{ + // "Range": "bytes=0-5" + // "Range": "bytes=0-" + + size_t i = 0; + while (i < range.length() && (range[i] < '0' || range[i] > '9')) { + i++; + } + size_t j = i; + while (j < range.length() && range[j] >= '0' && range[j] <= '9') { + j++; + } + if (j > i) { + _rangeStart = atoi(&range.c_str()[i]); + if (range.c_str()[j + 1]) { + _rangeEnd = atoi(&range.c_str()[j + 1]); + } else { + _rangeEnd = -1; + } + } + log_esp3d("Range: %d -> %d", _rangeStart, _rangeEnd); +} + + +int ESPWebDAVCore::htoi(char c) +{ + c = tolower(c); + return c >= '0' && c <= '9' ? c - '0' : + c >= 'a' && c <= 'f' ? c - 'a' + 10 : + -1; +} + + +char ESPWebDAVCore::itoH(int c) +{ + return c <= 9 ? c + '0' : c - 10 + 'A'; +} + + +int ESPWebDAVCore::hhtoi(const char* c) +{ + int h = htoi(*c); + int l = htoi(*(c + 1)); + return h < 0 || l < 0 ? -1 : (h << 4) + l; +} + + +String ESPWebDAVCore::enc2c(const String& encoded) +{ + String ret = encoded; + for (size_t i = 0; ret.length() >= 2 && i < ret.length() - 2; i++) + if (ret[i] == '%') { + int v = hhtoi(ret.c_str() + i + 1); + if (v > 0) { + ret[i] = v < 128 ? (char)v : '='; + ret.remove(i + 1, 2); + } + } + return ret; +} + + +String ESPWebDAVCore::c2enc(const String& decoded) +{ + size_t l = decoded.length(); + for (size_t i = 0; i < decoded.length() - 2; i++) + if (!isalnum(decoded[i])) { + l += 2; + } + String ret; + ret.reserve(l); + for (size_t i = 0; i < decoded.length(); i++) { + char c = decoded[i]; + if (isalnum(c) || c == '.' || c == '-' || c == '_') { + ret += c; + } else { + ret += '%'; + ret += itoH(decoded[i] >> 4); + ret += itoH(decoded[i] & 0xf); + } + } + return ret; +} +#endif //WEBDAV_FEATURE diff --git a/esp3d/src/modules/webdav/ESPWebDAV.h b/esp3d/src/modules/webdav/ESPWebDAV.h new file mode 100644 index 00000000..7a59e595 --- /dev/null +++ b/esp3d/src/modules/webdav/ESPWebDAV.h @@ -0,0 +1,230 @@ +/* + Copyright (c) 2018 Gurpreet Bal https://github.com/ardyesp/ESPWebDAV + Copyright (c) 2020 David Gauchard https://github.com/d-a-v/ESPWebDAV + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. + +*/ + +#ifndef __ESPWEBDAV_H +#define __ESPWEBDAV_H + +#if defined(WEBDAV_LOCK_SUPPORT) || defined(DBG_WEBDAV) +#error WEBDAV_LOCK_SUPPORT or DBG_WEBDAV: cannot be defined by user +#endif + +// LOCK support is not mandatory +// WEBDAV_LOCK_SUPPORT +// = 0: no support +// = 1: fake support +// > 1: supported with a std::map<> +#define WEBDAV_LOCK_SUPPORT 2 + +// constants for WebServer +#define CONTENT_LENGTH_UNKNOWN ((size_t) -1) +#define CONTENT_LENGTH_NOT_SET ((size_t) -2) +#define HTTP_MAX_POST_WAIT 5000 + +#if WEBDAV_LOCK_SUPPORT > 1 +#include +#endif +#include +#include +#include "../../include/esp3d_config.h" +class WiFiServer; +class WiFiClient; + +#if WEBDAV_FEATURE == FS_ROOT +#include "../filesystem/esp_globalFS.h" +typedef ESP_GBFile WebDavFile; +typedef ESP_GBFS WebDavFS; +#endif //WEBDAV_FEATURE == FS_ROOT + +#if WEBDAV_FEATURE == FS_FLASH +#include "../filesystem/esp_filesystem.h" +typedef ESP_File WebDavFile; +typedef ESP_FileSystem WebDavFS; +#endif //WEBDAV_FEATURE == FS_FLASH + +#if WEBDAV_FEATURE == FS_SD +#include "../filesystem/esp_sd.h" +typedef ESP_SDFile WebDavFile; +typedef ESP_SD WebDavFS; +#endif //WEBDAV_FEATURE == FS_SD + +class ESPWebDAVCore +{ +public: + + enum ResourceType { RESOURCE_NONE, RESOURCE_FILE, RESOURCE_DIR }; + enum DepthType { DEPTH_NONE, DEPTH_CHILD, DEPTH_ALL }; + + typedef String(*ContentTypeFunction)(const String&); + using TransferStatusCallback = std::function; + void begin(); + bool dirAction( + const String& path, + bool recursive, + const std::function& cb, + bool callAfter = true, + int depth = 0); + + void dir(const String& path, Print* out); + + bool parseRequest(const String& method, const String& uri, WiFiClient* client, ContentTypeFunction contentType); + + void setTransferStatusCallback(const TransferStatusCallback& cb) + { + transferStatusFn = cb; + } + + static void stripSlashes(String& name); + static String date2date(time_t date); + static String enc2c(const String& encoded); + static String c2enc(const String& decoded); + +protected: + + static int htoi(char c); + static int hhtoi(const char* c); + static char itoH(int c); + + //XXXFIXME this function must be replaced by some Stream::to() + size_t readBytesWithTimeout(uint8_t *buf, size_t size); + + typedef void (ESPWebDAVCore::*THandlerFunction)(const String&); + + bool copyFile(WebDavFile file, const String& destName); + bool deleteDir(const String& dir); + bool mkFullDir(String fullDir); + + void processClient(THandlerFunction handler, const String& message); + void handleIssue(int code, const char* text); + //void handleReject(const String& rejectMessage); + void handleRequest(); + void handleOptions(ResourceType resource); + void handleLock(ResourceType resource); + void handleUnlock(ResourceType resource); + void handlePropPatch(ResourceType resource, WebDavFile& file); + void handleProp(ResourceType resource, WebDavFile& file); + void handleGet(ResourceType resource, WebDavFile& file, bool isGet); + void handlePut(ResourceType resource); + void handleWriteError(const String& message, WebDavFile& wFile); + void handleDirectoryCreate(ResourceType resource); + void handleMove(ResourceType resource, WebDavFile& file); + void handleDelete(ResourceType resource); + void handleCopy(ResourceType resource, WebDavFile& file); + + void sendPropResponse(bool isDir, const String& name, size_t size, time_t lastWrite, time_t creationTime); + void sendContentProp(const String& what, const String& response); + + void sendHeader(const String& name, const String& value, bool first = false); + void send(const String& code, const char* content_type, const String& content); + void _prepareHeader(String& response, const String& code, const char* content_type, size_t contentLength); + bool sendContent(const String& content); + bool sendContent_P(PGM_P content); + bool sendContent(const char* data, size_t size); + void setContentLength(size_t len); + void processRange(const String& range); + + int allowed(const String& uri, uint32_t ownash); + int allowed(const String& uri, const String& xml = emptyString); + void makeToken(String& ret, uint32_t pash, uint32_t ownash); + int extractLockToken(const String& someHeader, const char* start, const char* end, uint32_t& pash, uint32_t& ownash); + bool getPayload(StreamString& payload); + void stripName(String& name); + void stripHost(String& name); + String urlToUri(const String& url); + + enum virt_e { VIRT_NONE, VIRT_PROC }; + virt_e isVirtual(const String& uri); + size_t makeVirtual(virt_e v, String& internal); + + // variables pertaining to current most HTTP request being serviced + constexpr static int m_persistent_timer_init_ms = 5000; + long unsigned int m_persistent_timer_ms; + bool m_persistent; + int _maxPathLength; + + String method; + String uri; + StreamString payload; + WiFiClient* client = nullptr; + + size_t contentLengthHeader; + String depthHeader; + String hostHeader; + String destinationHeader; + String overwrite; + String ifHeader; + String lockTokenHeader; + DepthType depth; + + String _responseHeaders; + bool _chunked; + int _contentLengthAnswer; + int _rangeStart; + int _rangeEnd; + +#if WEBDAV_LOCK_SUPPORT > 1 + // infinite-depth exclusive locks + // map + std::map _locks; +#endif + + ContentTypeFunction contentTypeFn = nullptr; + TransferStatusCallback transferStatusFn = nullptr; +}; + +class ESPWebDAV: public ESPWebDAVCore +{ +public: + + void begin(WiFiServer* srv) + { + ESPWebDAVCore::begin(); + this->server = srv; + } + void end() + { + this->server = nullptr; + } + void handleClient(); + WiFiClient & Client() + { + return locClient; + } +protected: + + // Sections are copied from ESP8266Webserver + static String getMimeType(const String& path); + String urlDecode(const String& text); + + bool parseRequest(); + + WiFiServer* server = nullptr; + WiFiClient locClient; +}; + +#endif // __ESPWEBDAV_H diff --git a/esp3d/src/modules/webdav/PolledTimeout_esp32.h b/esp3d/src/modules/webdav/PolledTimeout_esp32.h new file mode 100644 index 00000000..8723e41a --- /dev/null +++ b/esp3d/src/modules/webdav/PolledTimeout_esp32.h @@ -0,0 +1,27 @@ +#ifndef __POLLEDTIMING_H__ +#define __POLLEDTIMING_H__ + +#include + +class PolledTimeout +{ +public: + PolledTimeout(uint32_t timeout) + { + _timeOut = timeout; + _startMS = millis(); + } + operator bool() const + { + return ((millis() - _startMS) > _timeOut); + } + void reset() + { + _startMS = millis(); + } +private: + uint32_t _startMS; + uint32_t _timeOut; +}; + +#endif diff --git a/esp3d/src/modules/webdav/WebSrv.cpp b/esp3d/src/modules/webdav/WebSrv.cpp new file mode 100644 index 00000000..0f577eea --- /dev/null +++ b/esp3d/src/modules/webdav/WebSrv.cpp @@ -0,0 +1,162 @@ +/* + Dead simple web-server. + Supports only one simultaneous client, knows how to handle GET and POST. + + Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. + Simplified/Adapted for ESPWebDav: + Copyright (c) 2018 Gurpreet Bal https://github.com/ardyesp/ESPWebDAV + Copyright (c) 2020 David Gauchard https://github.com/d-a-v/ESPWebDAV + + 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 + Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling) + Modified 22 Jan 2021 by Luc Lebosse (ESP3D Integration) +*/ +#include "../../include/esp3d_config.h" + +#if defined (WEBDAV_FEATURE) +#include "ESPWebDAV.h" + +// Sections are copied from ESP8266Webserver + +String ESPWebDAV::getMimeType(const String& path) +{ + if (path.endsWith(".html")) { + return "text/html"; + } else if (path.endsWith(".htm")) { + return "text/html"; + } else if (path.endsWith(".css")) { + return "text/css"; + } else if (path.endsWith(".txt")) { + return "text/plain"; + } else if (path.endsWith(".js")) { + return "application/javascript"; + } else if (path.endsWith(".json")) { + return "application/json"; + } else if (path.endsWith(".png")) { + return "image/png"; + } else if (path.endsWith(".gif")) { + return "image/gif"; + } else if (path.endsWith(".jpg")) { + return "image/jpeg"; + } else if (path.endsWith(".ico")) { + return "image/x-icon"; + } else if (path.endsWith(".svg")) { + return "image/svg+xml"; + } else if (path.endsWith(".ttf")) { + return "application/x-font-ttf"; + } else if (path.endsWith(".otf")) { + return "application/x-font-opentype"; + } else if (path.endsWith(".woff")) { + return "application/font-woff"; + } else if (path.endsWith(".woff2")) { + return "application/font-woff2"; + } else if (path.endsWith(".eot")) { + return "application/vnd.ms-fontobject"; + } else if (path.endsWith(".sfnt")) { + return "application/font-sfnt"; + } else if (path.endsWith(".xml")) { + return "text/xml"; + } else if (path.endsWith(".pdf")) { + return "application/pdf"; + } else if (path.endsWith(".zip")) { + return "application/zip"; + } else if (path.endsWith(".gz")) { + return "application/x-gzip"; + } else if (path.endsWith(".appcache")) { + return "text/cache-manifest"; + } + + return "application/octet-stream"; +} + + + + +String ESPWebDAV::urlDecode(const String& text) +{ + String decoded = ""; + char temp[] = "0x00"; + unsigned int len = text.length(); + unsigned int i = 0; + while (i < len) { + char decodedChar; + char encodedChar = text.charAt(i++); + if ((encodedChar == '%') && (i + 1 < len)) { + temp[2] = text.charAt(i++); + temp[3] = text.charAt(i++); + decodedChar = strtol(temp, NULL, 16); + } else { + if (encodedChar == '+') { + decodedChar = ' '; + } else { + decodedChar = encodedChar; // normal ascii char + } + } + decoded += decodedChar; + } + return decoded; +} + +void ESPWebDAV::handleClient() +{ + if (!server) { + return; + } + + if (server->hasClient()) { + if (!locClient || !locClient.available()) { + // no or sleeping current client + // take it over + locClient = server->available(); + m_persistent_timer_ms = millis(); + log_esp3d("NEW CLIENT-------------------------------------------------------"); + } + } + + if (!locClient || !locClient.available()) { + return; + } + + // extract uri, headers etc + parseRequest(); + + if (!m_persistent) + // close the connection + { + locClient.stop(); + } +} + + +bool ESPWebDAV::parseRequest() +{ + // Read the first line of HTTP request + String req = locClient.readStringUntil('\r'); + locClient.readStringUntil('\n'); + + // First line of HTTP request looks like "GET /path HTTP/1.1" + // Retrieve the "/path" part by finding the spaces + int addr_start = req.indexOf(' '); + int addr_end = req.indexOf(' ', addr_start + 1); + if (addr_start == -1 || addr_end == -1) { + return false; + } + + method = req.substring(0, addr_start); + uri = urlDecode(req.substring(addr_start + 1, addr_end)); + return ESPWebDAVCore::parseRequest(method, uri, &locClient, getMimeType); +} + +#endif //WEBDAV_FEATURE diff --git a/esp3d/src/modules/webdav/webdav_server.cpp b/esp3d/src/modules/webdav/webdav_server.cpp new file mode 100644 index 00000000..87b30817 --- /dev/null +++ b/esp3d/src/modules/webdav/webdav_server.cpp @@ -0,0 +1,116 @@ +/* + webdav_server.cpp - webdav server functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This code 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 code 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 code; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "../../include/esp3d_config.h" + +#if defined (WEBDAV_FEATURE) +#include +#include +#include "webdav_server.h" +#include "../../core/settings_esp3d.h" +#include "../../core/esp3doutput.h" +#include "../../core/commands.h" + +Webdav_Server webdav_server; + +void Webdav_Server::closeClient() +{ + if(_dav.Client()) { + _dav.Client().stop(); + } +} + +void Webdav_Server::dir() +{ + _dav.dir("/", &Serial); +}; + +bool Webdav_Server::isConnected() +{ + if ( !_started) { + return false; + } + if (_dav.Client()) { + return (_dav.Client().connected()); + } + return false; +} + +const char* Webdav_Server::clientIPAddress() +{ + static String res; + res = "0.0.0.0"; + if (_dav.Client() && _dav.Client().connected()) { + res = _dav.Client().remoteIP().toString(); + } + return res.c_str(); +} + + +Webdav_Server::Webdav_Server() +{ + _started = false; + _port = 0; +} + +Webdav_Server::~Webdav_Server() +{ + end(); +} + +/** + * begin WebDav setup + */ +bool Webdav_Server::begin() +{ + end(); + if (Settings_ESP3D::read_byte(ESP_WEBDAV_ON) !=1) { + return true; + } + _port = Settings_ESP3D::read_uint32(ESP_WEBDAV_PORT); + + _tcpserver.begin(_port); + _dav.begin(&_tcpserver); + _started = true; + return _started; +} +/** + * End WebDav + */ +void Webdav_Server::end() +{ + _started = false; + _port = 0; + closeClient(); + _tcpserver.stop(); + _dav.end(); +} + +bool Webdav_Server::started() +{ + return _started; +} + +void Webdav_Server::handle() +{ + _dav.handleClient(); +} + +#endif //WEBDAV_FEATURE diff --git a/esp3d/src/modules/webdav/webdav_server.h b/esp3d/src/modules/webdav/webdav_server.h new file mode 100644 index 00000000..747b20b8 --- /dev/null +++ b/esp3d/src/modules/webdav/webdav_server.h @@ -0,0 +1,54 @@ +/* + webdav_server.h - webdav service functions class + + Copyright (c) 2014 Luc Lebosse. All rights reserved. + + This code 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 code 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 code; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _WEBDAV_SERVER_H +#define _WEBDAV_SERVER_H + +#include "ESPWebDAV.h" +class WiFiServer; + +class Webdav_Server +{ +public: + Webdav_Server(); + ~Webdav_Server(); + bool begin(); + void end(); + void handle(); + bool started(); + bool isConnected(); + const char* clientIPAddress(); + uint16_t port() + { + return _port; + } + void dir(); + void closeClient(); +private: + bool _started; + uint16_t _port; + WiFiServer _tcpserver; + ESPWebDAV _dav; +}; + +extern Webdav_Server webdav_server; + +#endif +