diff --git a/ESPWebDAV.cpp b/ESPWebDAV.cpp index 63666d9..ba81e85 100644 --- a/ESPWebDAV.cpp +++ b/ESPWebDAV.cpp @@ -1,569 +1,643 @@ -// WebDAV server using ESP8266 and SD card filesystem -// Targeting Windows 7 Explorer WebDav - -#include -#include -#include -#include -#include -#include "ESPWebDAV.h" - -// 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"}; - - -// ------------------------ -bool ESPWebDAV::init(int chipSelectPin, SPISettings spiSettings, int serverPort) { -// ------------------------ - // start the wifi server - server = new WiFiServer(serverPort); - server->begin(); - - // initialize the SD card - return sd.begin(chipSelectPin, spiSettings); -} - -// ------------------------ -bool ESPWebDAV::initSD(int chipSelectPin, SPISettings spiSettings) { - // initialize the SD card - return sd.begin(chipSelectPin, spiSettings); -} - -// ------------------------ -bool ESPWebDAV::startServer() { -// ------------------------ - // start the wifi server - server->begin(); -} - -// ------------------------ -void ESPWebDAV::handleNotFound() { -// ------------------------ - String message = "Not found\n"; - message += "URI: "; - message += uri; - message += " Method: "; - message += method; - message += "\n"; - - sendHeader("Allow", "OPTIONS,MKCOL,POST,PUT"); - send("404 Not Found", "text/plain", message); - DBG_PRINTLN("404 Not Found"); -} - - - -// ------------------------ -void ESPWebDAV::handleReject(String rejectMessage) { -// ------------------------ - DBG_PRINT("Rejecting request: "); DBG_PRINTLN(rejectMessage); - - // handle options - if(method.equals("OPTIONS")) - return handleOptions(RESOURCE_NONE); - - // handle properties - if(method.equals("PROPFIND")) { - sendHeader("Allow", "PROPFIND,OPTIONS,DELETE,COPY,MOVE"); - setContentLength(CONTENT_LENGTH_UNKNOWN); - send("207 Multi-Status", "application/xml;charset=utf-8", ""); - sendContent(F("/HTTP/1.1 200 OKFri, 30 Nov 1979 00:00:00 GMT\"3333333333333333333333333333333333333333\"")); - - if(depthHeader.equals("1")) { - sendContent(F("/")); - sendContent(rejectMessage); - sendContent(F("HTTP/1.1 200 OKFri, 01 Apr 2016 16:07:40 GMT\"2222222222222222222222222222222222222222\"0application/octet-stream")); - } - - sendContent(F("")); - return; - } - else - // if reached here, means its a 404 - handleNotFound(); -} - - - - -// set http_proxy=http://localhost:36036 -// curl -v -X PROPFIND -H "Depth: 1" http://Rigidbot/Old/PipeClip.gcode -// Test PUT a file: curl -v -T c.txt -H "Expect:" http://Rigidbot/c.txt -// C:\Users\gsbal>curl -v -X LOCK http://Rigidbot/EMA_CPP_TRCC_Tutorial/Consumer.cpp -d "CARBON2\gsbal" -// ------------------------ -void ESPWebDAV::handleRequest(String blank) { -// ------------------------ - ResourceType resource = RESOURCE_NONE; - - // does uri refer to a file or directory or a null? - FatFile tFile; - if(tFile.open(sd.vwd(), uri.c_str(), O_READ)) { - resource = tFile.isDir() ? RESOURCE_DIR : RESOURCE_FILE; - tFile.close(); - } - - DBG_PRINT("\r\nm: "); DBG_PRINT(method); - DBG_PRINT(" r: "); DBG_PRINT(resource); - DBG_PRINT(" u: "); DBG_PRINTLN(uri); - - // add header that gets sent everytime - sendHeader("DAV", "2"); - - // handle properties - if(method.equals("PROPFIND")) - return handleProp(resource); - - if(method.equals("GET")) - return handleGet(resource, true); - - if(method.equals("HEAD")) - return handleGet(resource, false); - - // handle options - if(method.equals("OPTIONS")) - return handleOptions(resource); - - // handle file create/uploads - if(method.equals("PUT")) - return handlePut(resource); - - // handle file locks - if(method.equals("LOCK")) - return handleLock(resource); - - if(method.equals("UNLOCK")) - return handleUnlock(resource); - - if(method.equals("PROPPATCH")) - return handlePropPatch(resource); - - // directory creation - if(method.equals("MKCOL")) - return handleDirectoryCreate(resource); - - // move a file or directory - if(method.equals("MOVE")) - return handleMove(resource); - - // delete a file or directory - if(method.equals("DELETE")) - return handleDelete(resource); - - // if reached here, means its a 404 - handleNotFound(); -} - - - -// ------------------------ -void ESPWebDAV::handleOptions(ResourceType resource) { -// ------------------------ - DBG_PRINTLN("Processing OPTION"); - sendHeader("Allow", "PROPFIND,GET,DELETE,PUT,COPY,MOVE"); - send("200 OK", NULL, ""); -} - - - -// ------------------------ -void ESPWebDAV::handleLock(ResourceType resource) { -// ------------------------ - DBG_PRINTLN("Processing LOCK"); - - // does URI refer to an existing resource - if(resource == RESOURCE_NONE) - return handleNotFound(); - - sendHeader("Allow", "PROPPATCH,PROPFIND,OPTIONS,DELETE,UNLOCK,COPY,LOCK,MOVE,HEAD,POST,PUT,GET"); - sendHeader("Lock-Token", "urn:uuid:26e57cb3-834d-191a-00de-000042bdecf9"); - - size_t contentLen = contentLengthHeader.toInt(); - uint8_t buf[1024]; - size_t numRead = readBytesWithTimeout(buf, sizeof(buf), contentLen); - - if(numRead == 0) - return handleNotFound(); - - buf[contentLen] = 0; - String inXML = String((char*) buf); - int startIdx = inXML.indexOf(""); - int endIdx = inXML.indexOf(""); - if(startIdx < 0 || endIdx < 0) - return handleNotFound(); - - String lockUser = inXML.substring(startIdx + 8, endIdx); - String resp1 = F("urn:uuid:26e57cb3-834d-191a-00de-000042bdecf9"); - String resp2 = F("infinity"); - String resp3 = F("Second-3600"); - - send("200 OK", "application/xml;charset=utf-8", resp1 + uri + resp2 + lockUser + resp3); -} - - - -// ------------------------ -void ESPWebDAV::handleUnlock(ResourceType resource) { -// ------------------------ - DBG_PRINTLN("Processing UNLOCK"); - sendHeader("Allow", "PROPPATCH,PROPFIND,OPTIONS,DELETE,UNLOCK,COPY,LOCK,MOVE,HEAD,POST,PUT,GET"); - sendHeader("Lock-Token", "urn:uuid:26e57cb3-834d-191a-00de-000042bdecf9"); - send("204 No Content", NULL, ""); -} - - - -// ------------------------ -void ESPWebDAV::handlePropPatch(ResourceType resource) { -// ------------------------ - DBG_PRINTLN("PROPPATCH forwarding to PROPFIND"); - handleProp(resource); -} - - - -// ------------------------ -void ESPWebDAV::handleProp(ResourceType resource) { -// ------------------------ - DBG_PRINTLN("Processing PROPFIND"); - // check depth header - DepthType depth = DEPTH_NONE; - if(depthHeader.equals("1")) - depth = DEPTH_CHILD; - else if(depthHeader.equals("infinity")) - depth = DEPTH_ALL; - - DBG_PRINT("Depth: "); DBG_PRINTLN(depth); - - // does URI refer to an existing resource - if(resource == RESOURCE_NONE) - return handleNotFound(); - - if(resource == RESOURCE_FILE) - sendHeader("Allow", "PROPFIND,OPTIONS,DELETE,COPY,MOVE,HEAD,POST,PUT,GET"); - else - sendHeader("Allow", "PROPFIND,OPTIONS,DELETE,COPY,MOVE"); - - setContentLength(CONTENT_LENGTH_UNKNOWN); - send("207 Multi-Status", "application/xml;charset=utf-8", ""); - sendContent(F("")); - sendContent(F("")); - - // open this resource - SdFile baseFile; - baseFile.open(uri.c_str(), O_READ); - sendPropResponse(false, &baseFile); - - if((resource == RESOURCE_DIR) && (depth == DEPTH_CHILD)) { - // append children information to message - SdFile childFile; - while(childFile.openNext(&baseFile, O_READ)) { - yield(); - sendPropResponse(true, &childFile); - childFile.close(); - } - } - - baseFile.close(); - sendContent(F("")); -} - - - -// ------------------------ -void ESPWebDAV::sendPropResponse(boolean recursing, FatFile *curFile) { -// ------------------------ - char buf[255]; - curFile->getName(buf, sizeof(buf)); - -// String fullResPath = "http://" + hostHeader + uri; - String fullResPath = uri; - - if(recursing) - if(fullResPath.endsWith("/")) - fullResPath += String(buf); - else - fullResPath += "/" + String(buf); - - // get file modified time - dir_t dir; - curFile->dirEntry(&dir); - - // convert to required format - tm tmStr; - tmStr.tm_hour = FAT_HOUR(dir.lastWriteTime); - tmStr.tm_min = FAT_MINUTE(dir.lastWriteTime); - tmStr.tm_sec = FAT_SECOND(dir.lastWriteTime); - tmStr.tm_year = FAT_YEAR(dir.lastWriteDate) - 1900; - tmStr.tm_mon = FAT_MONTH(dir.lastWriteDate) - 1; - tmStr.tm_mday = FAT_DAY(dir.lastWriteDate); - time_t t2t = mktime(&tmStr); - tm *gTm = gmtime(&t2t); - - // Tue, 13 Oct 2015 17:07:35 GMT - sprintf(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); - String fileTimeStamp = String(buf); - - - // send the XML information about thyself to client - sendContent(F("")); - // append full file path - sendContent(fullResPath); - sendContent(F("HTTP/1.1 200 OK")); - // append modified date - sendContent(fileTimeStamp); - sendContent(F("")); - // append unique tag generated from full path - sendContent("\"" + sha1(fullResPath + fileTimeStamp) + "\""); - sendContent(F("")); - - if(curFile->isDir()) - sendContent(F("")); - else { - sendContent(F("")); - // append the file size - sendContent(String(curFile->fileSize())); - sendContent(F("")); - // append correct file mime type - sendContent(getMimeType(fullResPath)); - sendContent(F("")); - } - sendContent(F("")); -} - - - - -// ------------------------ -void ESPWebDAV::handleGet(ResourceType resource, bool isGet) { -// ------------------------ - DBG_PRINTLN("Processing GET"); - - // does URI refer to an existing file resource - if(resource != RESOURCE_FILE) - return handleNotFound(); - - SdFile rFile; - long tStart = millis(); - uint8_t buf[1460]; - rFile.open(uri.c_str(), O_READ); - - sendHeader("Allow", "PROPFIND,OPTIONS,DELETE,COPY,MOVE,HEAD,POST,PUT,GET"); - size_t fileSize = rFile.fileSize(); - setContentLength(fileSize); - String contentType = getMimeType(uri); - if(uri.endsWith(".gz") && contentType != "application/x-gzip" && contentType != "application/octet-stream") - sendHeader("Content-Encoding", "gzip"); - - send("200 OK", contentType.c_str(), ""); - - if(isGet) { - // disable Nagle if buffer size > TCP MTU of 1460 - // client.setNoDelay(1); - - // send the file - while(rFile.available()) { - // SD read speed ~ 17sec for 4.5MB file - int numRead = rFile.read(buf, sizeof(buf)); - client.write(buf, numRead); - } - } - - rFile.close(); - DBG_PRINT("File "); DBG_PRINT(fileSize); DBG_PRINT(" bytes sent in: "); DBG_PRINT((millis() - tStart)/1000); DBG_PRINTLN(" sec"); -} - - - - -// ------------------------ -void ESPWebDAV::handlePut(ResourceType resource) { -// ------------------------ - DBG_PRINTLN("Processing Put"); - - // does URI refer to a directory - if(resource == RESOURCE_DIR) - return handleNotFound(); - - SdFile nFile; - sendHeader("Allow", "PROPFIND,OPTIONS,DELETE,COPY,MOVE,HEAD,POST,PUT,GET"); - - // if file does not exist, create it - if(resource == RESOURCE_NONE) { - if(!nFile.open(uri.c_str(), O_CREAT | O_WRITE)) - return handleWriteError("Unable to create a new file", &nFile); - } - - // file is created/open for writing at this point - DBG_PRINT(uri); DBG_PRINTLN(" - ready for data"); - // did server send any data in put - size_t contentLen = contentLengthHeader.toInt(); - - if(contentLen != 0) { - // buffer size is critical *don't change* - const size_t WRITE_BLOCK_CONST = 512; - uint8_t buf[WRITE_BLOCK_CONST]; - long tStart = millis(); - size_t numRemaining = contentLen; - - // high speed raw write implementation - // close any previous file - nFile.close(); - // delete old file - sd.remove(uri.c_str()); - - // create a contiguous file - size_t contBlocks = (contentLen/WRITE_BLOCK_CONST + 1); - uint32_t bgnBlock, endBlock; - - if (!nFile.createContiguous(sd.vwd(), uri.c_str(), contBlocks * WRITE_BLOCK_CONST)) - return handleWriteError("File create contiguous sections failed", &nFile); - - // get the location of the file's blocks - if (!nFile.contiguousRange(&bgnBlock, &endBlock)) - return handleWriteError("Unable to get contiguous range", &nFile); - - if (!sd.card()->writeStart(bgnBlock, contBlocks)) - return handleWriteError("Unable to start writing contiguous range", &nFile); - - // read data from stream and write to the file - while(numRemaining > 0) { - size_t numToRead = (numRemaining > WRITE_BLOCK_CONST) ? WRITE_BLOCK_CONST : numRemaining; - size_t numRead = readBytesWithTimeout(buf, sizeof(buf), numToRead); - if(numRead == 0) - break; - - // store whole buffer into file regardless of numRead - if (!sd.card()->writeData(buf)) - return handleWriteError("Write data failed", &nFile); - - // reduce the number outstanding - numRemaining -= numRead; - } - - // stop writing operation - if (!sd.card()->writeStop()) - return handleWriteError("Unable to stop writing contiguous range", &nFile); - - // detect timeout condition - if(numRemaining) - return handleWriteError("Timed out waiting for data", &nFile); - - // truncate the file to right length - if(!nFile.truncate(contentLen)) - return handleWriteError("Unable to truncate the file", &nFile); - - DBG_PRINT("File "); DBG_PRINT(contentLen - numRemaining); DBG_PRINT(" bytes stored in: "); DBG_PRINT((millis() - tStart)/1000); DBG_PRINTLN(" sec"); - } - - if(resource == RESOURCE_NONE) - send("201 Created", NULL, ""); - else - send("200 OK", NULL, ""); - - nFile.close(); -} - - - - -// ------------------------ -void ESPWebDAV::handleWriteError(String message, FatFile *wFile) { -// ------------------------ - // close this file - wFile->close(); - // delete the wrile being written - sd.remove(uri.c_str()); - // send error - send("500 Internal Server Error", "text/plain", message); - DBG_PRINTLN(message); -} - - -// ------------------------ -void ESPWebDAV::handleDirectoryCreate(ResourceType resource) { -// ------------------------ - DBG_PRINTLN("Processing MKCOL"); - - // does URI refer to anything - if(resource != RESOURCE_NONE) - return handleNotFound(); - - // create directory - if (!sd.mkdir(uri.c_str(), true)) { - // send error - send("500 Internal Server Error", "text/plain", "Unable to create directory"); - DBG_PRINTLN("Unable to create directory"); - return; - } - - DBG_PRINT(uri); DBG_PRINTLN(" directory created"); - sendHeader("Allow", "OPTIONS,MKCOL,LOCK,POST,PUT"); - send("201 Created", NULL, ""); -} - - - -// ------------------------ -void ESPWebDAV::handleMove(ResourceType resource) { -// ------------------------ - DBG_PRINTLN("Processing MOVE"); - - // does URI refer to anything - if(resource == RESOURCE_NONE) - return handleNotFound(); - - if(destinationHeader.length() == 0) - return handleNotFound(); - - String dest = urlToUri(destinationHeader); - - DBG_PRINT("Move destination: "); DBG_PRINTLN(dest); - - // move file or directory - if ( !sd.rename(uri.c_str(), dest.c_str()) ) { - // send error - send("500 Internal Server Error", "text/plain", "Unable to move"); - DBG_PRINTLN("Unable to move file/directory"); - return; - } - - DBG_PRINTLN("Move successful"); - sendHeader("Allow", "OPTIONS,MKCOL,LOCK,POST,PUT"); - send("201 Created", NULL, ""); -} - - - - -// ------------------------ -void ESPWebDAV::handleDelete(ResourceType resource) { -// ------------------------ - DBG_PRINTLN("Processing DELETE"); - - // does URI refer to anything - if(resource == RESOURCE_NONE) - return handleNotFound(); - - bool retVal; - - if(resource == RESOURCE_FILE) - // delete a file - retVal = sd.remove(uri.c_str()); - else - // delete a directory - retVal = sd.rmdir(uri.c_str()); - - if(!retVal) { - // send error - send("500 Internal Server Error", "text/plain", "Unable to delete"); - DBG_PRINTLN("Unable to delete file/directory"); - return; - } - - DBG_PRINTLN("Delete successful"); - sendHeader("Allow", "OPTIONS,MKCOL,LOCK,POST,PUT"); - send("200 OK", NULL, ""); -} - -ESPWebDAV dav; +// WebDAV server using ESP8266 and SD card filesystem +// Targeting Windows 7 Explorer WebDav + +#include +#include +#include +#include +#include +#include "ESPWebDAV.h" + +// 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"}; + + +// ------------------------ +bool ESPWebDAV::init(int chipSelectPin, SPISettings spiSettings, int serverPort) { +// ------------------------ + // start the wifi server + server = new WiFiServer(serverPort); + server->begin(); + + // initialize the SD card + return sd.begin(chipSelectPin, spiSettings); +} + +// ------------------------ +bool ESPWebDAV::initSD(int chipSelectPin, SPISettings spiSettings) { + // initialize the SD card + return sd.begin(chipSelectPin, spiSettings); +} + +// ------------------------ +bool ESPWebDAV::startServer() { +// ------------------------ + // start the wifi server + server->begin(); +} + +// ------------------------ +void ESPWebDAV::handleNotFound() { +// ------------------------ + String message = "Not found\n"; + message += "URI: "; + message += uri; + message += " Method: "; + message += method; + message += "\n"; + + sendHeader("Allow", "OPTIONS,MKCOL,POST,PUT"); + send("404 Not Found", "text/plain", message); + DBG_PRINTLN("404 Not Found"); +} + + + +// ------------------------ +void ESPWebDAV::handleReject(String rejectMessage) { +// ------------------------ + DBG_PRINT("Rejecting request: "); DBG_PRINTLN(rejectMessage); + + // handle options + if(method.equals("OPTIONS")) + return handleOptions(RESOURCE_NONE); + + // handle properties + if(method.equals("PROPFIND")) { + sendHeader("Allow", "PROPFIND,OPTIONS,DELETE,COPY,MOVE"); + setContentLength(CONTENT_LENGTH_UNKNOWN); + send("207 Multi-Status", "application/xml;charset=utf-8", ""); + sendContent(F("/HTTP/1.1 200 OKFri, 30 Nov 1979 00:00:00 GMT\"3333333333333333333333333333333333333333\"")); + + if(depthHeader.equals("1")) { + sendContent(F("/")); + sendContent(rejectMessage); + sendContent(F("HTTP/1.1 200 OKFri, 01 Apr 2016 16:07:40 GMT\"2222222222222222222222222222222222222222\"0application/octet-stream")); + } + + sendContent(F("")); + return; + } + else + // if reached here, means its a 404 + handleNotFound(); +} + + + + +// set http_proxy=http://localhost:36036 +// curl -v -X PROPFIND -H "Depth: 1" http://Rigidbot/Old/PipeClip.gcode +// Test PUT a file: curl -v -T c.txt -H "Expect:" http://Rigidbot/c.txt +// C:\Users\gsbal>curl -v -X LOCK http://Rigidbot/EMA_CPP_TRCC_Tutorial/Consumer.cpp -d "CARBON2\gsbal" +// ------------------------ +void ESPWebDAV::handleRequest(String blank) { +// ------------------------ + ResourceType resource = RESOURCE_NONE; + + // does uri refer to a file or directory or a null? + FatFile tFile; + if(tFile.open(sd.vwd(), uri.c_str(), O_READ)) { + resource = tFile.isDir() ? RESOURCE_DIR : RESOURCE_FILE; + tFile.close(); + } + + DBG_PRINT("\r\nm: "); DBG_PRINT(method); + DBG_PRINT(" r: "); DBG_PRINT(resource); + DBG_PRINT(" u: "); DBG_PRINTLN(uri); + + // add header that gets sent everytime + sendHeader("DAV", "1, 2"); + + // these headers are for webdavfs (https://github.com/miquels/webdavfs) + // pretend to be enough like Apache that we get read/write support + sendHeader("Server", "ESPWebDAV (use Apache put range)"); + sendHeader("DAV", ""); + + // handle properties + if(method.equals("PROPFIND")) + return handleProp(resource); + + if(method.equals("GET")) + return handleGet(resource, true); + + if(method.equals("HEAD")) + return handleGet(resource, false); + + // handle options + if(method.equals("OPTIONS")) + return handleOptions(resource); + + // handle file create/uploads + if(method.equals("PUT")) + return handlePut(resource); + + // handle file locks + if(method.equals("LOCK")) + return handleLock(resource); + + if(method.equals("UNLOCK")) + return handleUnlock(resource); + + if(method.equals("PROPPATCH")) + return handlePropPatch(resource); + + // directory creation + if(method.equals("MKCOL")) + return handleDirectoryCreate(resource); + + // move a file or directory + if(method.equals("MOVE")) + return handleMove(resource); + + // delete a file or directory + if(method.equals("DELETE")) + return handleDelete(resource); + + // if reached here, means its a 404 + handleNotFound(); +} + + + +// ------------------------ +void ESPWebDAV::handleOptions(ResourceType resource) { +// ------------------------ + DBG_PRINTLN("Processing OPTION"); + sendHeader("Allow", "PROPFIND,GET,DELETE,PUT,COPY,MOVE"); + send("200 OK", NULL, ""); +} + + + +// ------------------------ +void ESPWebDAV::handleLock(ResourceType resource) { +// ------------------------ + DBG_PRINTLN("Processing LOCK"); + + // does URI refer to an existing resource + if(resource == RESOURCE_NONE) + return handleNotFound(); + + sendHeader("Allow", "PROPPATCH,PROPFIND,OPTIONS,DELETE,UNLOCK,COPY,LOCK,MOVE,HEAD,POST,PUT,GET"); + sendHeader("Lock-Token", "urn:uuid:26e57cb3-834d-191a-00de-000042bdecf9"); + + size_t contentLen = contentLengthHeader.toInt(); + uint8_t buf[1024]; + size_t numRead = readBytesWithTimeout(buf, sizeof(buf), contentLen); + + if(numRead == 0) + return handleNotFound(); + + buf[contentLen] = 0; + String inXML = String((char*) buf); + int startIdx = inXML.indexOf(""); + int endIdx = inXML.indexOf(""); + if(startIdx < 0 || endIdx < 0) + return handleNotFound(); + + String lockUser = inXML.substring(startIdx + 8, endIdx); + String resp1 = F("urn:uuid:26e57cb3-834d-191a-00de-000042bdecf9"); + String resp2 = F("infinity"); + String resp3 = F("Second-3600"); + + send("200 OK", "application/xml;charset=utf-8", resp1 + uri + resp2 + lockUser + resp3); +} + + + +// ------------------------ +void ESPWebDAV::handleUnlock(ResourceType resource) { +// ------------------------ + DBG_PRINTLN("Processing UNLOCK"); + sendHeader("Allow", "PROPPATCH,PROPFIND,OPTIONS,DELETE,UNLOCK,COPY,LOCK,MOVE,HEAD,POST,PUT,GET"); + sendHeader("Lock-Token", "urn:uuid:26e57cb3-834d-191a-00de-000042bdecf9"); + send("204 No Content", NULL, ""); +} + + + +// ------------------------ +void ESPWebDAV::handlePropPatch(ResourceType resource) { +// ------------------------ + DBG_PRINTLN("PROPPATCH forwarding to PROPFIND"); + handleProp(resource); +} + + + +// ------------------------ +void ESPWebDAV::handleProp(ResourceType resource) { +// ------------------------ + DBG_PRINTLN("Processing PROPFIND"); + // check depth header + DepthType depth = DEPTH_NONE; + if(depthHeader.equals("1")) + depth = DEPTH_CHILD; + else if(depthHeader.equals("infinity")) + depth = DEPTH_ALL; + + DBG_PRINT("Depth: "); DBG_PRINTLN(depth); + + // does URI refer to an existing resource + if(resource == RESOURCE_NONE) + return handleNotFound(); + + if(resource == RESOURCE_FILE) + sendHeader("Allow", "PROPFIND,OPTIONS,DELETE,COPY,MOVE,HEAD,POST,PUT,GET"); + else + sendHeader("Allow", "PROPFIND,OPTIONS,DELETE,COPY,MOVE"); + + setContentLength(CONTENT_LENGTH_UNKNOWN); + send("207 Multi-Status", "application/xml;charset=utf-8", ""); + sendContent(F("")); + sendContent(F("")); + + // open this resource + SdFile baseFile; + baseFile.open(uri.c_str(), O_READ); + sendPropResponse(false, &baseFile); + + if((resource == RESOURCE_DIR) && (depth == DEPTH_CHILD)) { + // append children information to message + SdFile childFile; + while(childFile.openNext(&baseFile, O_READ)) { + yield(); + sendPropResponse(true, &childFile); + childFile.close(); + } + } + + baseFile.close(); + sendContent(F("")); +} + + + +// ------------------------ +void ESPWebDAV::sendPropResponse(boolean recursing, FatFile *curFile) { +// ------------------------ + char buf[255]; + curFile->getName(buf, sizeof(buf)); + +// String fullResPath = "http://" + hostHeader + uri; + String fullResPath = uri; + + if(recursing) + if(fullResPath.endsWith("/")) + fullResPath += String(buf); + else + fullResPath += "/" + String(buf); + + // get file modified time + dir_t dir; + curFile->dirEntry(&dir); + + // convert to required format + tm tmStr; + tmStr.tm_hour = FAT_HOUR(dir.lastWriteTime); + tmStr.tm_min = FAT_MINUTE(dir.lastWriteTime); + tmStr.tm_sec = FAT_SECOND(dir.lastWriteTime); + tmStr.tm_year = FAT_YEAR(dir.lastWriteDate) - 1900; + tmStr.tm_mon = FAT_MONTH(dir.lastWriteDate) - 1; + tmStr.tm_mday = FAT_DAY(dir.lastWriteDate); + time_t t2t = mktime(&tmStr); + tm *gTm = gmtime(&t2t); + + // Tue, 13 Oct 2015 17:07:35 GMT + sprintf(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); + String fileTimeStamp = String(buf); + + + // send the XML information about thyself to client + sendContent(F("")); + // append full file path + sendContent(fullResPath); + sendContent(F("HTTP/1.1 200 OK")); + // append modified date + sendContent(fileTimeStamp); + sendContent(F("")); + // append unique tag generated from full path + sendContent("\"" + sha1(fullResPath + fileTimeStamp) + "\""); + sendContent(F("")); + + if(curFile->isDir()) + sendContent(F("")); + else { + sendContent(F("")); + // append the file size + sendContent(String(curFile->fileSize())); + sendContent(F("")); + // append correct file mime type + sendContent(getMimeType(fullResPath)); + sendContent(F("")); + } + sendContent(F("")); +} + + + + +// ------------------------ +void ESPWebDAV::handleGet(ResourceType resource, bool isGet) { +// ------------------------ + DBG_PRINTLN("Processing GET"); + + // does URI refer to an existing file resource + if(resource != RESOURCE_FILE) + return handleNotFound(); + + SdFile rFile; + long tStart = millis(); + uint8_t buf[1460]; + rFile.open(uri.c_str(), O_READ); + + sendHeader("Allow", "PROPFIND,OPTIONS,DELETE,COPY,MOVE,HEAD,POST,PUT,GET"); + size_t fileSize; + if (_contentRangeStart==CONTENT_RANGE_NOT_SET && _contentRangeEnd==CONTENT_RANGE_NOT_SET) + { + fileSize=rFile.fileSize(); + } + else + { + fileSize=_contentRangeEnd-_contentRangeStart+1; + sendHeader("Accept-Ranges", "bytes"); + sendHeader("Content-Range", "bytes "+String(_contentRangeStart)+"-"+String(_contentRangeEnd)+"/"+String(rFile.fileSize())); + } + setContentLength(fileSize); + + String contentType = getMimeType(uri); + if(uri.endsWith(".gz") && contentType != "application/x-gzip" && contentType != "application/octet-stream") + sendHeader("Content-Encoding", "gzip"); + + if (!isGet) + send("200 OK", contentType.c_str(), ""); + + if(isGet) + { + + // disable Nagle if buffer size > TCP MTU of 1460 + // client.setNoDelay(1); + + if (_contentRangeStart==CONTENT_RANGE_NOT_SET && _contentRangeEnd==CONTENT_RANGE_NOT_SET) + { + send("200 OK", contentType.c_str(), ""); + // send the whole file + while(rFile.available()) + { + // SD read speed ~ 17sec for 4.5MB file + int numRead = rFile.read(buf, sizeof(buf)); + client.write(buf, numRead); + } + } + else + { + send("206 Partial Content", contentType.c_str(), ""); + // send the selected range + rFile.seekSet(_contentRangeStart); + size_t remaining=fileSize; + while (remaining>0) + { + int numRead=rFile.read(buf, (sizeof(buf)writeStart(bgnBlock, contBlocks)) + return handleWriteError("Unable to start writing contiguous range", &nFile); + + // read data from stream and write to the file + while(numRemaining > 0) { + size_t numToRead = (numRemaining > WRITE_BLOCK_CONST) ? WRITE_BLOCK_CONST : numRemaining; + size_t numRead = readBytesWithTimeout(buf, sizeof(buf), numToRead); + if(numRead == 0) + break; + + // store whole buffer into file regardless of numRead + if (!sd.card()->writeData(buf)) + return handleWriteError("Write data failed", &nFile); + + // reduce the number outstanding + numRemaining -= numRead; + } + + // stop writing operation + if (!sd.card()->writeStop()) + return handleWriteError("Unable to stop writing contiguous range", &nFile); + + // detect timeout condition + if(numRemaining) + return handleWriteError("Timed out waiting for data", &nFile); + + // truncate the file to right length + if(!nFile.truncate(contentLen)) + return handleWriteError("Unable to truncate the file", &nFile); + + DBG_PRINT("File "); DBG_PRINT(contentLen - numRemaining); DBG_PRINT(" bytes stored in: "); DBG_PRINT((millis() - tStart)/1000); DBG_PRINTLN(" sec"); + } + } + else + { + // reopen file so we can seek within it + nFile.close(); + nFile.open(uri.c_str(), O_RDWR); + + // set up buffer + const size_t WRITE_BLOCK_CONST = 512; + uint8_t buf[WRITE_BLOCK_CONST]; + long tStart = millis(); + size_t numRemaining = contentLen; + + // seek to beginning of range + nFile.seekSet(_contentRangeStart); + + // update file + while (numRemaining>0) + { + size_t numToRead = (numRemaining > WRITE_BLOCK_CONST) ? WRITE_BLOCK_CONST : numRemaining; + size_t numRead = readBytesWithTimeout(buf, sizeof(buf), numToRead); + if(numRead == 0) + break; + nFile.write(buf, numRead); + numRemaining-=numRead; + } + + // close + if (!nFile.close()) + return handleWriteError("Unable to close file after write", &nFile); + + // timed out? + if (numRemaining) + return handleWriteError("Timed out waiting for data", &nFile); + + DBG_PRINT("File "); DBG_PRINT(contentLen - numRemaining); DBG_PRINT(" bytes stored in: "); DBG_PRINT((millis() - tStart)/1000); DBG_PRINTLN(" sec"); + } + + if(resource == RESOURCE_NONE) + send("201 Created", NULL, ""); + else + send("200 OK", NULL, ""); + + nFile.close(); +} + + + + +// ------------------------ +void ESPWebDAV::handleWriteError(String message, FatFile *wFile) { +// ------------------------ + // close this file + wFile->close(); + // delete the wrile being written + sd.remove(uri.c_str()); + // send error + send("500 Internal Server Error", "text/plain", message); + DBG_PRINTLN(message); +} + + +// ------------------------ +void ESPWebDAV::handleDirectoryCreate(ResourceType resource) { +// ------------------------ + DBG_PRINTLN("Processing MKCOL"); + + // does URI refer to anything + if(resource != RESOURCE_NONE) + return handleNotFound(); + + // create directory + if (!sd.mkdir(uri.c_str(), true)) { + // send error + send("500 Internal Server Error", "text/plain", "Unable to create directory"); + DBG_PRINTLN("Unable to create directory"); + return; + } + + DBG_PRINT(uri); DBG_PRINTLN(" directory created"); + sendHeader("Allow", "OPTIONS,MKCOL,LOCK,POST,PUT"); + send("201 Created", NULL, ""); +} + + + +// ------------------------ +void ESPWebDAV::handleMove(ResourceType resource) { +// ------------------------ + DBG_PRINTLN("Processing MOVE"); + + // does URI refer to anything + if(resource == RESOURCE_NONE) + return handleNotFound(); + + if(destinationHeader.length() == 0) + return handleNotFound(); + + String dest = urlToUri(destinationHeader); + + DBG_PRINT("Move destination: "); DBG_PRINTLN(dest); + + // move file or directory + if ( !sd.rename(uri.c_str(), dest.c_str()) ) { + // send error + send("500 Internal Server Error", "text/plain", "Unable to move"); + DBG_PRINTLN("Unable to move file/directory"); + return; + } + + DBG_PRINTLN("Move successful"); + sendHeader("Allow", "OPTIONS,MKCOL,LOCK,POST,PUT"); + send("201 Created", NULL, ""); +} + + + + +// ------------------------ +void ESPWebDAV::handleDelete(ResourceType resource) { +// ------------------------ + DBG_PRINTLN("Processing DELETE"); + + // does URI refer to anything + if(resource == RESOURCE_NONE) + return handleNotFound(); + + bool retVal; + + if(resource == RESOURCE_FILE) + // delete a file + retVal = sd.remove(uri.c_str()); + else + // delete a directory + retVal = sd.rmdir(uri.c_str()); + + if(!retVal) { + // send error + send("500 Internal Server Error", "text/plain", "Unable to delete"); + DBG_PRINTLN("Unable to delete file/directory"); + return; + } + + DBG_PRINTLN("Delete successful"); + sendHeader("Allow", "OPTIONS,MKCOL,LOCK,POST,PUT"); + send("200 OK", NULL, ""); +} + +ESPWebDAV dav; diff --git a/ESPWebDAV.h b/ESPWebDAV.h index 89e06a3..1845846 100644 --- a/ESPWebDAV.h +++ b/ESPWebDAV.h @@ -1,84 +1,86 @@ -#include -#include - -#define DEBUG - -#ifdef DEBUG - #define DBG_PRINT(...) { Serial.print(__VA_ARGS__); } - #define DBG_PRINTLN(...) { Serial.println(__VA_ARGS__); } -#else - #define DBG_PRINT(...) {} - #define DBG_PRINTLN(...) {} -#endif - -// constants for WebServer -#define CONTENT_LENGTH_UNKNOWN ((size_t) -1) -#define CONTENT_LENGTH_NOT_SET ((size_t) -2) -#define HTTP_MAX_POST_WAIT 5000 - -enum ResourceType { RESOURCE_NONE, RESOURCE_FILE, RESOURCE_DIR }; -enum DepthType { DEPTH_NONE, DEPTH_CHILD, DEPTH_ALL }; - - -class ESPWebDAV { -public: - bool init(int chipSelectPin, SPISettings spiSettings, int serverPort); - bool initSD(int chipSelectPin, SPISettings spiSettings); - bool startServer(); - bool isClientWaiting(); - void handleClient(String blank = ""); - void rejectClient(String rejectMessage); - -protected: - typedef void (ESPWebDAV::*THandlerFunction)(String); - - void processClient(THandlerFunction handler, String message); - void handleNotFound(); - void handleReject(String rejectMessage); - void handleRequest(String blank); - void handleOptions(ResourceType resource); - void handleLock(ResourceType resource); - void handleUnlock(ResourceType resource); - void handlePropPatch(ResourceType resource); - void handleProp(ResourceType resource); - void sendPropResponse(boolean recursing, FatFile *curFile); - void handleGet(ResourceType resource, bool isGet); - void handlePut(ResourceType resource); - void handleWriteError(String message, FatFile *wFile); - void handleDirectoryCreate(ResourceType resource); - void handleMove(ResourceType resource); - void handleDelete(ResourceType resource); - - // Sections are copied from ESP8266Webserver - String getMimeType(String path); - String urlDecode(const String& text); - String urlToUri(String url); - bool parseRequest(); - void sendHeader(const String& name, const String& value, bool first = false); - void send(String code, const char* content_type, const String& content); - void _prepareHeader(String& response, String code, const char* content_type, size_t contentLength); - void sendContent(const String& content); - void sendContent_P(PGM_P content); - void setContentLength(size_t len); - size_t readBytesWithTimeout(uint8_t *buf, size_t bufSize); - size_t readBytesWithTimeout(uint8_t *buf, size_t bufSize, size_t numToRead); - - - // variables pertaining to current most HTTP request being serviced - WiFiServer *server; - SdFat sd; - - WiFiClient client; - String method; - String uri; - String contentLengthHeader; - String depthHeader; - String hostHeader; - String destinationHeader; - - String _responseHeaders; - bool _chunked; - int _contentLength; -}; - -extern ESPWebDAV dav; +#include +#include + +#define DEBUG + +#ifdef DEBUG + #define DBG_PRINT(...) { Serial.print(__VA_ARGS__); } + #define DBG_PRINTLN(...) { Serial.println(__VA_ARGS__); } +#else + #define DBG_PRINT(...) {} + #define DBG_PRINTLN(...) {} +#endif + +// constants for WebServer +#define CONTENT_LENGTH_UNKNOWN ((size_t) -1) +#define CONTENT_LENGTH_NOT_SET ((size_t) -2) +#define CONTENT_RANGE_NOT_SET ((size_t) -1) +#define HTTP_MAX_POST_WAIT 5000 + +enum ResourceType { RESOURCE_NONE, RESOURCE_FILE, RESOURCE_DIR }; +enum DepthType { DEPTH_NONE, DEPTH_CHILD, DEPTH_ALL }; + + +class ESPWebDAV { +public: + bool init(int chipSelectPin, SPISettings spiSettings, int serverPort); + bool initSD(int chipSelectPin, SPISettings spiSettings); + bool startServer(); + bool isClientWaiting(); + void handleClient(String blank = ""); + void rejectClient(String rejectMessage); + +protected: + typedef void (ESPWebDAV::*THandlerFunction)(String); + + void processClient(THandlerFunction handler, String message); + void handleNotFound(); + void handleReject(String rejectMessage); + void handleRequest(String blank); + void handleOptions(ResourceType resource); + void handleLock(ResourceType resource); + void handleUnlock(ResourceType resource); + void handlePropPatch(ResourceType resource); + void handleProp(ResourceType resource); + void sendPropResponse(boolean recursing, FatFile *curFile); + void handleGet(ResourceType resource, bool isGet); + void handlePut(ResourceType resource); + void handleWriteError(String message, FatFile *wFile); + void handleDirectoryCreate(ResourceType resource); + void handleMove(ResourceType resource); + void handleDelete(ResourceType resource); + + // Sections are copied from ESP8266Webserver + String getMimeType(String path); + String urlDecode(const String& text); + String urlToUri(String url); + bool parseRequest(); + void sendHeader(const String& name, const String& value, bool first = false); + void send(String code, const char* content_type, const String& content); + void _prepareHeader(String& response, String code, const char* content_type, size_t contentLength); + void sendContent(const String& content); + void sendContent_P(PGM_P content); + void setContentLength(size_t len); + size_t readBytesWithTimeout(uint8_t *buf, size_t bufSize); + size_t readBytesWithTimeout(uint8_t *buf, size_t bufSize, size_t numToRead); + + + // variables pertaining to current most HTTP request being serviced + WiFiServer *server; + SdFat sd; + + WiFiClient client; + String method; + String uri; + String contentLengthHeader; + String contentRangeHeader; + String depthHeader; + String hostHeader; + String destinationHeader; + + String _responseHeaders; + bool _chunked; + int _contentLength, _contentRangeStart, _contentRangeEnd; +}; + +extern ESPWebDAV dav; diff --git a/ESPWebDAV.ino b/ESPWebDAV.ino index 885de86..0468aa5 100644 --- a/ESPWebDAV.ino +++ b/ESPWebDAV.ino @@ -25,19 +25,21 @@ void setup() { if(config.load() == 1) { // Connected before if(!network.start()) { SERIAL_ECHOLN("Connect fail, please check your INI file or set the wifi config and connect again"); - SERIAL_ECHOLN("- M50: Set the wifi ssid , 'M50 ssid-name'"); - SERIAL_ECHOLN("- M51: Set the wifi password , 'M51 password'"); - SERIAL_ECHOLN("- M52: Start to connect the wifi"); - SERIAL_ECHOLN("- M53: Check the connection status"); + SERIAL_ECHOLN("- M50: Set WiFi SSID"); + SERIAL_ECHOLN("- M51: Set WiFi Password"); + SERIAL_ECHOLN("- M52: Connect"); + SERIAL_ECHOLN("- M53: Connection Status"); + SERIAL_ECHOLN("- M54: Set Hostname"); } } else { SERIAL_ECHOLN("Welcome to FYSETC: www.fysetc.com"); SERIAL_ECHOLN("Please set the wifi config first"); - SERIAL_ECHOLN("- M50: Set the wifi ssid , 'M50 ssid-name'"); - SERIAL_ECHOLN("- M51: Set the wifi password , 'M51 password'"); - SERIAL_ECHOLN("- M52: Start to connect the wifi"); - SERIAL_ECHOLN("- M53: Check the connection status"); + SERIAL_ECHOLN("- M50: Set WiFi SSID"); + SERIAL_ECHOLN("- M51: Set WiFi Password"); + SERIAL_ECHOLN("- M52: Connect"); + SERIAL_ECHOLN("- M53: Connection Status"); + SERIAL_ECHOLN("- M54: Set Hostname"); } } diff --git a/README.md b/README.md index a3029db..a34421b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ GCode can be directly uploaded from the slicer (Cura) to this remote drive, ther ## Dependencies: 1. [ESP8266 Arduino Core version 2.4](https://github.com/esp8266/Arduino) -2. [SdFat library](https://github.com/greiman/SdFat) +2. [SdFat library version 1.0.16](https://github.com/greiman/SdFat) ## Use: diff --git a/WebSrv.cpp b/WebSrv.cpp index b253ed6..578048c 100644 --- a/WebSrv.cpp +++ b/WebSrv.cpp @@ -1,343 +1,364 @@ -#include "ESPWebDAV.h" - -// Sections are copied from ESP8266Webserver - -// ------------------------ -String ESPWebDAV::getMimeType(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; -} - - - - - -// ------------------------ -String ESPWebDAV::urlToUri(String url) { -// ------------------------ - if(url.startsWith("http://")) { - int uriStart = url.indexOf('/', 7); - return url.substring(uriStart); - } - else - return url; -} - - - -// ------------------------ -bool ESPWebDAV::isClientWaiting() { -// ------------------------ - return server->hasClient(); -} - - - - -// ------------------------ -void ESPWebDAV::handleClient(String blank) { -// ------------------------ - processClient(&ESPWebDAV::handleRequest, blank); -} - - - -// ------------------------ -void ESPWebDAV::rejectClient(String rejectMessage) { -// ------------------------ - processClient(&ESPWebDAV::handleReject, rejectMessage); -} - - - -// ------------------------ -void ESPWebDAV::processClient(THandlerFunction handler, String message) { -// ------------------------ - // Check if a client has connected - client = server->available(); - if(!client) - return; - - // Wait until the client sends some data - while(!client.available()) - delay(1); - - // reset all variables - _chunked = false; - _responseHeaders = String(); - _contentLength = CONTENT_LENGTH_NOT_SET; - method = String(); - uri = String(); - contentLengthHeader = String(); - depthHeader = String(); - hostHeader = String(); - destinationHeader = String(); - - // extract uri, headers etc - if(parseRequest()) - // invoke the handler - (this->*handler)(message); - - // finalize the response - if(_chunked) - sendContent(""); - - // send all data before closing connection - client.flush(); - // close the connection - client.stop(); -} - - - - - -// ------------------------ -bool ESPWebDAV::parseRequest() { -// ------------------------ - // Read the first line of HTTP request - String req = client.readStringUntil('\r'); - client.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)); - // DBG_PRINT("method: "); DBG_PRINT(method); DBG_PRINT(" url: "); DBG_PRINTLN(uri); - - // parse and finish all headers - String headerName; - String headerValue; - - while(1) { - 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); - // DBG_PRINT("\t"); DBG_PRINT(headerName); DBG_PRINT(": "); DBG_PRINTLN(headerValue); - - if(headerName.equalsIgnoreCase("Host")) - hostHeader = headerValue; - else if(headerName.equalsIgnoreCase("Depth")) - depthHeader = headerValue; - else if(headerName.equalsIgnoreCase("Content-Length")) - contentLengthHeader = headerValue; - else if(headerName.equalsIgnoreCase("Destination")) - destinationHeader = headerValue; - } - - return true; -} - - - - -// ------------------------ -void ESPWebDAV::sendHeader(const String& name, const String& value, bool first) { -// ------------------------ - String headerLine = name + ": " + value + "\r\n"; - - if (first) - _responseHeaders = headerLine + _responseHeaders; - else - _responseHeaders += headerLine; -} - - - -// ------------------------ -void ESPWebDAV::send(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()); - if(content.length()) - sendContent(content); -} - - - -// ------------------------ -void ESPWebDAV::_prepareHeader(String& response, 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(_contentLength == CONTENT_LENGTH_NOT_SET) - sendHeader("Content-Length", String(contentLength)); - else if(_contentLength != CONTENT_LENGTH_UNKNOWN) - sendHeader("Content-Length", String(_contentLength)); - else if(_contentLength == CONTENT_LENGTH_UNKNOWN) { - _chunked = true; - sendHeader("Accept-Ranges","none"); - sendHeader("Transfer-Encoding","chunked"); - } - sendHeader("Connection", "close"); - - response += _responseHeaders; - response += "\r\n"; -} - - - -// ------------------------ -void ESPWebDAV::sendContent(const String& content) { -// ------------------------ - const char * footer = "\r\n"; - size_t size = content.length(); - - if(_chunked) { - char * chunkSize = (char *) malloc(11); - if(chunkSize) { - sprintf(chunkSize, "%x%s", size, footer); - client.write(chunkSize, strlen(chunkSize)); - free(chunkSize); - } - } - - client.write(content.c_str(), size); - - if(_chunked) { - client.write(footer, 2); - if (size == 0) { - _chunked = false; - } - } -} - - - -// ------------------------ -void ESPWebDAV::sendContent_P(PGM_P content) { -// ------------------------ - const char * footer = "\r\n"; - size_t size = strlen_P(content); - - if(_chunked) { - char * chunkSize = (char *) malloc(11); - if(chunkSize) { - sprintf(chunkSize, "%x%s", size, footer); - client.write(chunkSize, strlen(chunkSize)); - free(chunkSize); - } - } - - client.write_P(content, size); - - if(_chunked) { - client.write(footer, 2); - if (size == 0) { - _chunked = false; - } - } -} - - - -// ------------------------ -void ESPWebDAV::setContentLength(size_t len) { -// ------------------------ - _contentLength = len; -} - - -// ------------------------ -size_t ESPWebDAV::readBytesWithTimeout(uint8_t *buf, size_t bufSize) { -// ------------------------ - int timeout_ms = HTTP_MAX_POST_WAIT; - size_t numAvailable = 0; - while(!(numAvailable = client.available()) && client.connected() && timeout_ms--) - delay(1); - - if(!numAvailable) - return 0; - - return client.read(buf, bufSize); -} - - -// ------------------------ -size_t ESPWebDAV::readBytesWithTimeout(uint8_t *buf, size_t bufSize, size_t numToRead) { -// ------------------------ - int timeout_ms = HTTP_MAX_POST_WAIT; - size_t numAvailable = 0; - - while(((numAvailable = client.available()) < numToRead) && client.connected() && timeout_ms--) - delay(1); - - if(!numAvailable) - return 0; - - return client.read(buf, bufSize); -} - - +#include "ESPWebDAV.h" + +// Sections are copied from ESP8266Webserver + +// ------------------------ +String ESPWebDAV::getMimeType(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; +} + + + + + +// ------------------------ +String ESPWebDAV::urlToUri(String url) { +// ------------------------ + if(url.startsWith("http://")) { + int uriStart = url.indexOf('/', 7); + return url.substring(uriStart); + } + else + return url; +} + + + +// ------------------------ +bool ESPWebDAV::isClientWaiting() { +// ------------------------ + return server->hasClient(); +} + + + + +// ------------------------ +void ESPWebDAV::handleClient(String blank) { +// ------------------------ + processClient(&ESPWebDAV::handleRequest, blank); +} + + + +// ------------------------ +void ESPWebDAV::rejectClient(String rejectMessage) { +// ------------------------ + processClient(&ESPWebDAV::handleReject, rejectMessage); +} + + + +// ------------------------ +void ESPWebDAV::processClient(THandlerFunction handler, String message) { +// ------------------------ + // Check if a client has connected + client = server->available(); + if(!client) + return; + + // Wait until the client sends some data + while(!client.available()) + delay(1); + + // reset all variables + _chunked = false; + _responseHeaders = String(); + _contentLength = CONTENT_LENGTH_NOT_SET; + _contentRangeStart = _contentRangeEnd = CONTENT_RANGE_NOT_SET; + method = String(); + uri = String(); + contentLengthHeader = String(); + contentRangeHeader = String(); + depthHeader = String(); + hostHeader = String(); + destinationHeader = String(); + + // extract uri, headers etc + if(parseRequest()) + // invoke the handler + (this->*handler)(message); + + // finalize the response + if(_chunked) + sendContent(""); + + // send all data before closing connection + client.flush(); + // close the connection + client.stop(); +} + + + + + +// ------------------------ +bool ESPWebDAV::parseRequest() { +// ------------------------ + // Read the first line of HTTP request + String req = client.readStringUntil('\r'); + client.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)); + // DBG_PRINT("method: "); DBG_PRINT(method); DBG_PRINT(" url: "); DBG_PRINTLN(uri); + + // parse and finish all headers + String headerName; + String headerValue; + + while(1) { + 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); + // DBG_PRINT("\t"); DBG_PRINT(headerName); DBG_PRINT(": "); DBG_PRINTLN(headerValue); + + if(headerName.equalsIgnoreCase("Host")) + hostHeader = headerValue; + else if(headerName.equalsIgnoreCase("Depth")) + depthHeader = headerValue; + else if(headerName.equalsIgnoreCase("Content-Length")) + contentLengthHeader = headerValue; + else if(headerName.equalsIgnoreCase("Destination")) + destinationHeader = headerValue; + else if (headerName.equalsIgnoreCase("Content-Range") || headerName.equalsIgnoreCase("Range")) + { + contentRangeHeader = headerValue; + _contentRangeStart = _contentRangeEnd = 0; + bool bDashReached=false; + for (int i=0; i='0' && headerValue.c_str()[i]<='9') + { + if (!bDashReached) + _contentRangeStart=_contentRangeStart*10+(headerValue[i]-'0'); + else + _contentRangeEnd=_contentRangeEnd*10+(headerValue[i]-'0'); + } + } + } + } + + return true; +} + + + + +// ------------------------ +void ESPWebDAV::sendHeader(const String& name, const String& value, bool first) { +// ------------------------ + String headerLine = name + ": " + value + "\r\n"; + + if (first) + _responseHeaders = headerLine + _responseHeaders; + else + _responseHeaders += headerLine; +} + + + +// ------------------------ +void ESPWebDAV::send(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()); + if(content.length()) + sendContent(content); +} + + + +// ------------------------ +void ESPWebDAV::_prepareHeader(String& response, 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(_contentLength == CONTENT_LENGTH_NOT_SET) + sendHeader("Content-Length", String(contentLength)); + else if(_contentLength != CONTENT_LENGTH_UNKNOWN) + sendHeader("Content-Length", String(_contentLength)); + else if(_contentLength == CONTENT_LENGTH_UNKNOWN) { + _chunked = true; + sendHeader("Accept-Ranges","bytes"); + sendHeader("Transfer-Encoding","chunked"); + } + sendHeader("Connection", "close"); + + response += _responseHeaders; + response += "\r\n"; +} + + + +// ------------------------ +void ESPWebDAV::sendContent(const String& content) { +// ------------------------ + const char * footer = "\r\n"; + size_t size = content.length(); + + if(_chunked) { + char * chunkSize = (char *) malloc(11); + if(chunkSize) { + sprintf(chunkSize, "%x%s", size, footer); + client.write(chunkSize, strlen(chunkSize)); + free(chunkSize); + } + } + + client.write(content.c_str(), size); + + if(_chunked) { + client.write(footer, 2); + if (size == 0) { + _chunked = false; + } + } +} + + + +// ------------------------ +void ESPWebDAV::sendContent_P(PGM_P content) { +// ------------------------ + const char * footer = "\r\n"; + size_t size = strlen_P(content); + + if(_chunked) { + char * chunkSize = (char *) malloc(11); + if(chunkSize) { + sprintf(chunkSize, "%x%s", size, footer); + client.write(chunkSize, strlen(chunkSize)); + free(chunkSize); + } + } + + client.write_P(content, size); + + if(_chunked) { + client.write(footer, 2); + if (size == 0) { + _chunked = false; + } + } +} + + + +// ------------------------ +void ESPWebDAV::setContentLength(size_t len) { +// ------------------------ + _contentLength = len; +} + + +// ------------------------ +size_t ESPWebDAV::readBytesWithTimeout(uint8_t *buf, size_t bufSize) { +// ------------------------ + int timeout_ms = HTTP_MAX_POST_WAIT; + size_t numAvailable = 0; + while(!(numAvailable = client.available()) && client.connected() && timeout_ms--) + delay(1); + + if(!numAvailable) + return 0; + + return client.read(buf, bufSize); +} + + +// ------------------------ +size_t ESPWebDAV::readBytesWithTimeout(uint8_t *buf, size_t bufSize, size_t numToRead) { +// ------------------------ + int timeout_ms = HTTP_MAX_POST_WAIT; + size_t numAvailable = 0; + + while(((numAvailable = client.available()) < numToRead) && client.connected() && timeout_ms--) + delay(1); + + if(!numAvailable) + return 0; + + return client.read(buf, bufSize); +} diff --git a/config.cpp b/config.cpp index 24abee8..4b37a6d 100644 --- a/config.cpp +++ b/config.cpp @@ -66,11 +66,23 @@ int Config::loadSD() { goto FAIL; } } + else if(sKEY == "HOSTNAME") { + SERIAL_ECHOLN("INI file : HOSTNAME found"); + if(sValue.length()>0) { + memset(data._hostname,'\0',HOSTNAME_LEN); + sValue.toCharArray(data._hostname,HOSTNAME_LEN); + step++; + } + else { + rst = -7; + goto FAIL; + } + } else continue; // Bad line } - if(step != 2) { // We miss ssid or password + if(step != 3) { // We miss ssid or password //memset(data,) // TODO: do we need to empty the data? - SERIAL_ECHOLN("Please check your SSDI or PASSWORD in ini file"); + SERIAL_ECHOLN("Please check your SSID, PASSWORD, and HOSTNAME in ini file"); rst = -6; goto FAIL; } @@ -123,14 +135,24 @@ void Config::password(char* password) { strncpy(data.psw,password,WIFI_PASSWD_LEN); } -void Config::save(const char*ssid,const char*password) { - if(ssid ==NULL || password==NULL) +char* Config::hostname() { + return data._hostname; +} + +void Config::hostname(char* hostname) { + if (hostname==NULL) return; + strncpy(data._hostname, hostname, HOSTNAME_LEN); +} + +void Config::save(const char*ssid,const char*password, const char* hostname) { + if(ssid ==NULL || password==NULL || hostname==NULL) return; EEPROM.begin(EEPROM_SIZE); data.flag = 1; strncpy(data.ssid, ssid, WIFI_SSID_LEN); strncpy(data.psw, password, WIFI_PASSWD_LEN); + strncpy(data._hostname, hostname, HOSTNAME_LEN); uint8_t *p = (uint8_t*)(&data); for (int i = 0; i < sizeof(data); i++) { @@ -140,7 +162,7 @@ void Config::save(const char*ssid,const char*password) { } void Config::save() { - if(data.ssid == NULL || data.psw == NULL) + if(data.ssid == NULL || data.psw == NULL || data._hostname==NULL) return; EEPROM.begin(EEPROM_SIZE); diff --git a/config.h b/config.h index 0101ad7..71ad5bd 100644 --- a/config.h +++ b/config.h @@ -6,14 +6,16 @@ #define WIFI_SSID_LEN 32 #define WIFI_PASSWD_LEN 64 +#define HOSTNAME_LEN 32 #define EEPROM_SIZE 512 typedef struct config_type { unsigned char flag; // Was saved before? - char ssid[32]; - char psw[64]; + char ssid[WIFI_SSID_LEN]; + char psw[WIFI_PASSWD_LEN]; + char _hostname[HOSTNAME_LEN]; }CONFIG_TYPE; class Config { @@ -24,7 +26,9 @@ class Config { void ssid(char* ssid); char* password(); void password(char* password); - void save(const char*ssid,const char*password); + char* hostname(); + void hostname(char* hostname); + void save(const char*ssid,const char*password, const char* hostname); void save(); int save_ip(const char *ip); diff --git a/gcode.cpp b/gcode.cpp index 77faffb..583e8f7 100644 --- a/gcode.cpp +++ b/gcode.cpp @@ -122,10 +122,11 @@ void Gcode::gcode_M51() { void Gcode::gcode_M52() { if(!network.start()) { SERIAL_ECHOLN("Connect fail, please check your INI file or set the wifi config and connect again"); - SERIAL_ECHOLN("- M50: Set the wifi ssid , 'M50 ssid-name'"); - SERIAL_ECHOLN("- M51: Set the wifi password , 'M51 password'"); - SERIAL_ECHOLN("- M52: Start to connect the wifi"); - SERIAL_ECHOLN("- M53: Check the connection status"); + SERIAL_ECHOLN("- M50: Set WiFi SSID"); + SERIAL_ECHOLN("- M51: Set WiFi Password"); + SERIAL_ECHOLN("- M52: Connect"); + SERIAL_ECHOLN("- M53: Connection Status"); + SERIAL_ECHOLN("- M54: Set Hostname"); } } @@ -135,10 +136,11 @@ void Gcode::gcode_M52() { void Gcode::gcode_M53() { if(WiFi.status() != WL_CONNECTED) { SERIAL_ECHOLN("Wifi not connected"); - SERIAL_ECHOLN("- M50: Set the wifi ssid , 'M50 ssid-name'"); - SERIAL_ECHOLN("- M51: Set the wifi password , 'M51 password'"); - SERIAL_ECHOLN("- M52: Start to connect the wifi"); - SERIAL_ECHOLN("- M53: Check the connection status"); + SERIAL_ECHOLN("- M50: Set WiFi SSID"); + SERIAL_ECHOLN("- M51: Set WiFi Password"); + SERIAL_ECHOLN("- M52: Connect"); + SERIAL_ECHOLN("- M53: Connection Status"); + SERIAL_ECHOLN("- M54: Set Hostname"); } else { SERIAL_ECHOLN(""); @@ -146,10 +148,20 @@ void Gcode::gcode_M53() { SERIAL_ECHO("IP address: "); SERIAL_ECHOLN(WiFi.localIP()); SERIAL_ECHO("RSSI: "); SERIAL_ECHOLN(WiFi.RSSI()); SERIAL_ECHO("Mode: "); SERIAL_ECHOLN(WiFi.getPhyMode()); - SERIAL_ECHO("Asscess to SD at the Run prompt : \\\\"); SERIAL_ECHO(WiFi.localIP());SERIAL_ECHOLN("\\DavWWWRoot"); + SERIAL_ECHO("Access to SD at the Run prompt : \\\\"); SERIAL_ECHO(config.hostname());SERIAL_ECHOLN("\\DavWWWRoot"); } } +/** + * M54: Set the hostname + */ +void Gcode::gcode_M54() { + for (char *fn = parser.string_arg; *fn; ++fn); + config.hostname(parser.string_arg); + SERIAL_ECHO("hostname: "); + SERIAL_ECHOLN(config.hostname()); +} + /** * Process the parsed command and dispatch it to its handler */ @@ -169,6 +181,7 @@ void Gcode::process_parsed_command() { case 51: gcode_M51(); break; case 52: gcode_M52(); break; case 53: gcode_M53(); break; + case 54: gcode_M54(); break; default: parser.unknown_command_error(); } break; diff --git a/gcode.h b/gcode.h index dcf04a6..e16736a 100644 --- a/gcode.h +++ b/gcode.h @@ -21,6 +21,7 @@ class Gcode { void gcode_M51(); void gcode_M52(); void gcode_M53(); + void gcode_M54(); void process_parsed_command(); void process_next_command(); diff --git a/ini/SETUP.INI b/ini/SETUP.INI index 3a48cd8..a3e06b6 100644 --- a/ini/SETUP.INI +++ b/ini/SETUP.INI @@ -1,2 +1,3 @@ SSID=xxxx -PASSWORD=xxxx \ No newline at end of file +PASSWORD=xxxx +HOSTNAME=xxxx diff --git a/network.cpp b/network.cpp index 6db1083..444f9c6 100644 --- a/network.cpp +++ b/network.cpp @@ -19,7 +19,7 @@ bool Network::start() { wifiConnecting = true; // Set hostname first - WiFi.hostname(HOSTNAME); + WiFi.hostname(config.hostname()); // Reduce startup surge current WiFi.setAutoConnect(false); WiFi.mode(WIFI_STA); @@ -46,7 +46,7 @@ bool Network::start() { SERIAL_ECHO("IP address: "); SERIAL_ECHOLN(WiFi.localIP()); SERIAL_ECHO("RSSI: "); SERIAL_ECHOLN(WiFi.RSSI()); SERIAL_ECHO("Mode: "); SERIAL_ECHOLN(WiFi.getPhyMode()); - SERIAL_ECHO("Asscess to SD at the Run prompt : \\\\"); SERIAL_ECHO(WiFi.localIP());SERIAL_ECHOLN("\\DavWWWRoot"); + SERIAL_ECHO("Access to SD at the Run prompt : \\\\"); SERIAL_ECHO(config.hostname());SERIAL_ECHOLN("\\DavWWWRoot"); wifiConnected = true; diff --git a/network.h b/network.h index 1757897..cf1081c 100644 --- a/network.h +++ b/network.h @@ -1,7 +1,6 @@ #ifndef _NETWORK_H_ #define _NETWORK_H_ -#define HOSTNAME "FYSETC" #define SERVER_PORT 80 #define WIFI_CONNECT_TIMEOUT 30000UL