#include "FilesystemAbstraction.h" #include "WebDAV.h" #include "Logger.h" namespace webdav_constants { constexpr const char *MULTISTATUS_START = ""; constexpr const char *MULTISTATUS_END = ""; constexpr const char *RESPONSE_START = ""; constexpr const char *RESPONSE_END = "\n"; constexpr const char *HREF_START = ""; constexpr const char *HREF_END = ""; constexpr const char *PROPSTAT_START = ""; constexpr const char *PROPSTAT_END = ""; constexpr const char *PROP_START = ""; constexpr const char *PROP_END = ""; constexpr const char *RESOURCETYPE_START = ""; constexpr const char *RESOURCETYPE_END = ""; constexpr const char *RESOURCE_COLLECTION = ""; constexpr const char *HTTP_204_NO_CONTENT = "HTTP/1.1 204 No Content"; constexpr const char *CONTENTLEN_START = ""; constexpr const char *CONTENTLEN_END = ""; constexpr const char *CREATEDATE_START = ""; constexpr const char *CREATEDATE_END = ""; constexpr const char *MODDATE_START = ""; constexpr const char *MODDATE_END = ""; constexpr const char *STATUS_OK = "HTTP/1.1 200 OK"; constexpr const char *USED_BYTES_START = ""; constexpr const char *USED_BYTES_END = ""; } // namespace webdav_constants size_t webdavFileListingSpiffs(char *buffer, size_t maxLength, const char *spiffsPath) { using namespace webdav_constants; size_t bytesWritten = 0; auto toBuffer = [&buffer, &bytesWritten](const char *text) { auto len = strlen(text); memcpy(buffer, text, len); buffer += len; bytesWritten += len; }; // crude upper bound on how much space a file entry + footer needs constexpr size_t sizePerFile = 512; assert(maxLength >= sizePerFile); int fileIdx = 0; auto dir = portablefs::openDir(spiffsPath); toBuffer(MULTISTATUS_START); bool incomplete = false; while (dir.next()) { const auto freeSpace = maxLength - bytesWritten; if (freeSpace < sizePerFile) { incomplete = true; break; } fileIdx += 1; toBuffer(RESPONSE_START); toBuffer(HREF_START); const auto fileName = dir.fileName(); const auto fileNameWithoutDir = fileName.substring(fileName.lastIndexOf("/") + 1); toBuffer((fileNameWithoutDir + ".st").c_str()); toBuffer(HREF_END); toBuffer(PROPSTAT_START); toBuffer(PROP_START); if (dir.isDirectory()) { toBuffer(RESOURCETYPE_START); toBuffer(RESOURCE_COLLECTION); toBuffer(RESOURCETYPE_END); } else { toBuffer(CONTENTLEN_START); String fileSizeStr(dir.fileSize()); toBuffer(fileSizeStr.c_str()); toBuffer(CONTENTLEN_END); } toBuffer(PROP_END); toBuffer(STATUS_OK); toBuffer(PROPSTAT_END); toBuffer(webdav_constants::RESPONSE_END); } toBuffer(MULTISTATUS_END); if (incomplete) LOG_WARNING("WebDAV listing response is incomplete, because buffer was too small"); return bytesWritten; } String uriToFileName(const String &uriStr, const char *spiffsFolder) { String filename; if (uriStr.endsWith(".st")) filename = uriStr.substring(0, uriStr.length() - strlen(".st")); filename = spiffsFolder + String("/") + filename; return filename; } std::function webdavHandler(const char *uriPrefix, const char *spiffsFolder) { return [=](httpd_req_t *req) { String uri = String(req->uri).substring(strlen(uriPrefix)); constexpr size_t WEBDAV_BUFF_LEN = 1024 * 256; static char *webdavBuffer = (char *)heap_caps_malloc(WEBDAV_BUFF_LEN, MALLOC_CAP_SPIRAM); httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); switch (req->method) { case HTTP_GET: { auto filename = uriToFileName(uri, spiffsFolder); LOG_INFO("GET filename %s", filename.c_str()); auto file = portablefs::open(filename.c_str(), "r"); if (file.available()) { uint8_t *buffer = (uint8_t *)heap_caps_malloc(file.size(), MALLOC_CAP_SPIRAM); file.read(buffer, file.size()); httpd_resp_set_hdr(req, "Content-Type", "application/x-msgpack"); httpd_resp_send(req, (char *)buffer, file.size()); free(buffer); } else httpd_resp_send_404(req); break; } case HTTP_PROPFIND: { size_t bytesWritten = webdavFileListingSpiffs(webdavBuffer, WEBDAV_BUFF_LEN, spiffsFolder); httpd_resp_send(req, webdavBuffer, bytesWritten); break; } case HTTP_DELETE: { httpd_resp_set_hdr(req, "Content-Type", "text/plain"); if (portablefs::remove(uriToFileName(uri, spiffsFolder).c_str())) httpd_resp_send(req, "Deleted file", -1); else httpd_resp_send_404(req); break; } case HTTP_OPTIONS: { LOG_INFO("Options request"); httpd_resp_set_status(req, "204 No Content"); httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "GET, PROPFIND, DELETE, OPTIONS"); httpd_resp_send(req, "", 0); break; } default: LOG_INFO("Sending 404 %d uri %s", req->method, req->uri); httpd_resp_send_404(req); } }; }