168 lines
5.9 KiB
C++
168 lines
5.9 KiB
C++
#include "FilesystemAbstraction.h"
|
|
#include "WebDAV.h"
|
|
|
|
namespace webdav_constants
|
|
{
|
|
constexpr const char *MULTISTATUS_START = "<?xml version=\"1.0\" ?><D:multistatus xmlns:D=\"DAV:\">";
|
|
constexpr const char *MULTISTATUS_END = "</D:multistatus>";
|
|
constexpr const char *RESPONSE_START = "<D:response>";
|
|
constexpr const char *RESPONSE_END = "</D:response>\n";
|
|
constexpr const char *HREF_START = "<D:href>";
|
|
constexpr const char *HREF_END = "</D:href>";
|
|
constexpr const char *PROPSTAT_START = "<D:propstat>";
|
|
constexpr const char *PROPSTAT_END = "</D:propstat>";
|
|
constexpr const char *PROP_START = "<D:prop>";
|
|
constexpr const char *PROP_END = "</D:prop>";
|
|
constexpr const char *RESOURCETYPE_START = "<D:resourcetype>";
|
|
constexpr const char *RESOURCETYPE_END = "</D:resourcetype>";
|
|
constexpr const char *RESOURCE_COLLECTION = "<D:collection/>";
|
|
constexpr const char *HTTP_204_NO_CONTENT = "HTTP/1.1 204 No Content";
|
|
|
|
constexpr const char *CONTENTLEN_START = "<D:getcontentlength>";
|
|
constexpr const char *CONTENTLEN_END = "</D:getcontentlength>";
|
|
constexpr const char *CREATEDATE_START = "<D:creationdate>";
|
|
constexpr const char *CREATEDATE_END = "</D:creationdate>";
|
|
constexpr const char *MODDATE_START = "<D:getlastmodified>";
|
|
constexpr const char *MODDATE_END = "</D:getlastmodified>";
|
|
constexpr const char *STATUS_OK = "<D:status>HTTP/1.1 200 OK</D:status>";
|
|
constexpr const char *USED_BYTES_START = "<D:quota-used-bytes>";
|
|
constexpr const char *USED_BYTES_END = "</D:quota-used-bytes>";
|
|
} // 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)
|
|
Serial.println("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<void(httpd_req_t *)> 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);
|
|
Serial.printf("GET filename %s\n", 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:
|
|
{
|
|
Serial.println("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:
|
|
Serial.printf("Sending 404 %d uri %s\n", req->method, req->uri);
|
|
httpd_resp_send_404(req);
|
|
}
|
|
};
|
|
} |