// Get the list of files in the specified directory in JSON format. // If flagDirs is true then we prefix each directory with a * character. OutputBuffer *RepRap::GetFilesResponse(const char *dir, bool flagsDirs) { // Need something to write to... OutputBuffer *response; if (!OutputBuffer::Allocate(response)) { return nullptr; } response->copy("{\"dir\":"); response->EncodeString(dir, strlen(dir), false); response->cat(",\"files\":["); FileInfo fileInfo; bool firstFile = true; bool gotFile = platform->GetMassStorage()->FindFirst(dir, fileInfo); size_t bytesLeft = OutputBuffer::GetBytesLeft(response); // don't write more bytes than we can char filename[FILENAME_LENGTH]; filename[0] = '*'; const char *fname; while (gotFile) { if (fileInfo.fileName[0] != '.') // ignore Mac resource files and Linux hidden files { // Get the long filename if possible if (flagsDirs && fileInfo.isDirectory) { strncpy(filename + sizeof(char), fileInfo.fileName, FILENAME_LENGTH - 1); filename[FILENAME_LENGTH - 1] = 0; fname = filename; } else { fname = fileInfo.fileName; } // Make sure we can end this response properly if (bytesLeft < strlen(fname) * 2 + 4) { // No more space available - stop here break; } // Write separator and filename if (!firstFile) { bytesLeft -= response->cat(','); } bytesLeft -= response->EncodeString(fname, FILENAME_LENGTH, false); firstFile = false; } gotFile = platform->GetMassStorage()->FindNext(fileInfo); } response->cat("]}"); return response; }
OutputBuffer *RepRap::GetConfigResponse() { // We need some resources to return a valid config response... OutputBuffer *response; if (!OutputBuffer::Allocate(response)) { return nullptr; } const size_t numAxes = reprap.GetGCodes()->GetNumAxes(); // Axis minima response->copy("{\"axisMins\":"); char ch = '['; for (size_t axis = 0; axis < numAxes; axis++) { response->catf("%c%.2f", ch, platform->AxisMinimum(axis)); ch = ','; } // Axis maxima response->cat("],\"axisMaxes\":"); ch = '['; for (size_t axis = 0; axis < numAxes; axis++) { response->catf("%c%.2f", ch, platform->AxisMaximum(axis)); ch = ','; } // Accelerations response->cat("],\"accelerations\":"); ch = '['; for (size_t drive = 0; drive < DRIVES; drive++) { response->catf("%c%.2f", ch, platform->Acceleration(drive)); ch = ','; } // Motor currents response->cat("],\"currents\":"); ch = '['; for (size_t drive = 0; drive < DRIVES; drive++) { response->catf("%c%.2f", ch, platform->GetMotorCurrent(drive, false)); ch = ','; } // Firmware details response->catf("],\"firmwareElectronics\":\"%s", platform->GetElectronicsString()); #ifdef DUET_NG const char* expansionName = DuetExpansion::GetExpansionBoardName(); if (expansionName != nullptr) { response->catf(" + %s", expansionName); } #endif response->catf("\",\"firmwareName\":\"%s\"", FIRMWARE_NAME); response->catf(",\"firmwareVersion\":\"%s\"", VERSION); #if defined(DUET_NG) && defined(DUET_WIFI) response->catf(",\"dwsVersion\":\"%s\"", network->GetWiFiServerVersion()); #endif response->catf(",\"firmwareDate\":\"%s\"", DATE); // Motor idle parameters response->catf(",\"idleCurrentFactor\":%.1f", platform->GetIdleCurrentFactor() * 100.0); response->catf(",\"idleTimeout\":%.1f", move->IdleTimeout()); // Minimum feedrates response->cat(",\"minFeedrates\":"); ch = '['; for (size_t drive = 0; drive < DRIVES; drive++) { response->catf("%c%.2f", ch, platform->ConfiguredInstantDv(drive)); ch = ','; } // Maximum feedrates response->cat("],\"maxFeedrates\":"); ch = '['; for (size_t drive = 0; drive < DRIVES; drive++) { response->catf("%c%.2f", ch, platform->MaxFeedrate(drive)); ch = ','; } // Config file is no longer included, because we can use rr_configfile or M503 instead response->cat("]}"); return response; }
// Get a JSON-style filelist including file types and sizes OutputBuffer *RepRap::GetFilelistResponse(const char *dir) { // Need something to write to... OutputBuffer *response; if (!OutputBuffer::Allocate(response)) { return nullptr; } // If the requested volume is not mounted, report an error if (!platform->GetMassStorage()->CheckDriveMounted(dir)) { response->copy("{\"err\":1}"); return response; } // Check if the directory exists if (!platform->GetMassStorage()->DirectoryExists(dir)) { response->copy("{\"err\":2}"); return response; } response->copy("{\"dir\":"); response->EncodeString(dir, strlen(dir), false); response->cat(",\"files\":["); FileInfo fileInfo; bool firstFile = true; bool gotFile = platform->GetMassStorage()->FindFirst(dir, fileInfo); size_t bytesLeft = OutputBuffer::GetBytesLeft(response); // don't write more bytes than we can while (gotFile) { if (fileInfo.fileName[0] != '.') // ignore Mac resource files and Linux hidden files { // Make sure we can end this response properly if (bytesLeft < strlen(fileInfo.fileName) + 70) { // No more space available - stop here break; } // Write delimiter if (!firstFile) { bytesLeft -= response->cat(','); } firstFile = false; // Write another file entry bytesLeft -= response->catf("{\"type\":\"%c\",\"name\":", fileInfo.isDirectory ? 'd' : 'f'); bytesLeft -= response->EncodeString(fileInfo.fileName, FILENAME_LENGTH, false); bytesLeft -= response->catf(",\"size\":%u", fileInfo.size); const struct tm * const timeInfo = gmtime(&fileInfo.lastModified); if (timeInfo->tm_year <= /*19*/80) { // Don't send the last modified date if it is invalid bytesLeft -= response->cat('}'); } else { bytesLeft -= response->catf(",\"date\":\"%04u-%02u-%02uT%02u:%02u:%02u\"}", timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday, timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec); } } gotFile = platform->GetMassStorage()->FindNext(fileInfo); } response->cat("]}"); return response; }
OutputBuffer *RepRap::GetConfigResponse() { // We need some resources to return a valid config response... OutputBuffer *response; if (!OutputBuffer::Allocate(response)) { return nullptr; } // Axis minima response->copy("{\"axisMins\":"); char ch = '['; for (size_t axis = 0; axis < AXES; axis++) { response->catf("%c%.2f", ch, platform->AxisMinimum(axis)); ch = ','; } // Axis maxima response->cat("],\"axisMaxes\":"); ch = '['; for (size_t axis = 0; axis < AXES; axis++) { response->catf("%c%.2f", ch, platform->AxisMaximum(axis)); ch = ','; } // Accelerations response->cat("],\"accelerations\":"); ch = '['; for (size_t drive = 0; drive < DRIVES; drive++) { response->catf("%c%.2f", ch, platform->Acceleration(drive)); ch = ','; } // Motor currents response->cat("],\"currents\":"); ch = '['; for (size_t drive = 0; drive < DRIVES; drive++) { response->catf("%c%.2f", ch, platform->MotorCurrent(drive)); ch = ','; } // Firmware details response->catf("],\"firmwareElectronics\":\"%s\"", ELECTRONICS); response->catf(",\"firmwareName\":\"%s\"", NAME); response->catf(",\"firmwareVersion\":\"%s\"", VERSION); response->catf(",\"firmwareDate\":\"%s\"", DATE); // Motor idle parameters response->catf(",\"idleCurrentFactor\":%.1f", platform->GetIdleCurrentFactor() * 100.0); response->catf(",\"idleTimeout\":%.1f", move->IdleTimeout()); // Minimum feedrates response->cat(",\"minFeedrates\":"); ch = '['; for (size_t drive = 0; drive < DRIVES; drive++) { response->catf("%c%.2f", ch, platform->ConfiguredInstantDv(drive)); ch = ','; } // Maximum feedrates response->cat("],\"maxFeedrates\":"); ch = '['; for (size_t drive = 0; drive < DRIVES; drive++) { response->catf("%c%.2f", ch, platform->MaxFeedrate(drive)); ch = ','; } // Configuration File (whitespaces are skipped, otherwise we easily risk overflowing the response buffer) response->cat("],\"configFile\":\""); FileStore *configFile = platform->GetFileStore(platform->GetSysDir(), platform->GetConfigFile(), false); if (configFile == nullptr) { response->cat("not found"); } else { char c, esc; bool readingWhitespace = false; size_t bytesWritten = 0, bytesLeft = OutputBuffer::GetBytesLeft(response); while (configFile->Read(c) && bytesWritten + 4 < bytesLeft) // need 4 bytes to finish this response { if (!readingWhitespace || (c != ' ' && c != '\t')) { switch (c) { case '\r': esc = 'r'; break; case '\n': esc = 'n'; break; case '\t': esc = 't'; break; case '"': esc = '"'; break; case '\\': esc = '\\'; break; default: esc = 0; break; } if (esc) { response->catf("\\%c", esc); bytesWritten += 2; } else { response->cat(c); bytesWritten++; } } readingWhitespace = (c == ' ' || c == '\t'); } configFile->Close(); } response->cat("\"}"); return response; }
// Process the first fragment of input from the client. // Return true if the session should be kept open. bool Webserver::ProcessFirstFragment(HttpSession& session, const char* command, bool isOnlyFragment) { // Process connect messages first if (StringEquals(command, "connect") && GetKeyValue("password") != nullptr) { OutputBuffer *response; if (OutputBuffer::Allocate(response)) { if (session.isAuthenticated || reprap.CheckPassword(GetKeyValue("password"))) { // Password is OK, see if we can update the current RTC date and time const char *timeVal = GetKeyValue("time"); if (timeVal != nullptr && !platform->IsDateTimeSet()) { struct tm timeInfo; memset(&timeInfo, 0, sizeof(timeInfo)); if (strptime(timeVal, "%Y-%m-%dT%H:%M:%S", &timeInfo) != nullptr) { time_t newTime = mktime(&timeInfo); platform->SetDateTime(newTime); } } // Client has logged in session.isAuthenticated = true; response->printf("{\"err\":0,\"sessionTimeout\":%u,\"boardType\":\"%s\"}", httpSessionTimeout, platform->GetBoardString()); } else { // Wrong password response->copy("{\"err\":1}"); } network->SendReply(session.ip, 200 | rcJson, response); } else { // If we failed to allocate an output buffer, send back an error string network->SendReply(session.ip, 200 | rcJson, "{\"err\":2}"); } return false; } if (StringEquals(command, "disconnect")) { network->SendReply(session.ip, 200 | rcJson, "{\"err\":0}"); DeleteSession(session.ip); return false; } // Try to authorise the user automatically to retain compatibility with the old web interface if (!session.isAuthenticated && reprap.NoPasswordSet()) { session.isAuthenticated = true; } // If the client is still not authenticated, stop here if (!session.isAuthenticated) { network->SendReply(session.ip, 500, "Not authorized"); return false; } if (StringEquals(command, "reply")) { SendGCodeReply(session); return false; } // rr_configfile sends the config as plain text well if (StringEquals(command, "configfile")) { const char *configPath = platform->GetMassStorage()->CombineName(platform->GetSysDir(), platform->GetConfigFile()); char fileName[FILENAME_LENGTH]; strncpy(fileName, configPath, FILENAME_LENGTH); SendFile(fileName, session); return false; } if (StringEquals(command, "download") && GetKeyValue("name") != nullptr) { SendFile(GetKeyValue("name"), session); return false; } if (StringEquals(command, "upload")) { const char* nameVal = GetKeyValue("name"); const char* lengthVal = GetKeyValue("length"); const char* timeVal = GetKeyValue("time"); if (nameVal != nullptr && lengthVal != nullptr) { // Try to obtain the last modified time time_t fileLastModified = 0; struct tm timeInfo; memset(&timeInfo, 0, sizeof(timeInfo)); if (timeVal != nullptr && strptime(timeVal, "%Y-%m-%dT%H:%M:%S", &timeInfo) != nullptr) { fileLastModified = mktime(&timeInfo); } // Deal with file upload request uint32_t fileLength = atol(lengthVal); StartUpload(session, nameVal, fileLength, fileLastModified); if (session.uploadState == uploading) { if (isOnlyFragment) { FinishUpload(session); return false; } else { network->DiscardMessage(); // no reply needed yet return true; // expecting more data } } } network->SendReply(session.ip, 200 | rcJson, "{\"err\":1}"); // TODO return the cause of the error return false; } if (StringEquals(command, "move")) { const char* response = "{\"err\":1}"; // assume failure const char* const oldVal = GetKeyValue("old"); const char* const newVal = GetKeyValue("new"); if (oldVal != nullptr && newVal != nullptr) { const bool success = platform->GetMassStorage()->Rename(oldVal, newVal); if (success) { response = "{\"err\":0}"; } } network->SendReply(session.ip, 200 | rcJson, response); return false; } if (StringEquals(command, "mkdir")) { const char* response = "{\"err\":1}"; // assume failure const char* dirVal = GetKeyValue("dir"); if (dirVal != nullptr) { bool ok = (platform->GetMassStorage()->MakeDirectory(dirVal)); if (ok) { response = "{\"err\":0}"; } } network->SendReply(session.ip, 200 | rcJson, response); return false; } if (StringEquals(command, "delete")) { const char* response = "{\"err\":1}"; // assume failure const char* nameVal = GetKeyValue("name"); if (nameVal != nullptr) { bool ok = platform->GetMassStorage()->Delete("0:/", nameVal); if (ok) { response = "{\"err\":0}"; } } network->SendReply(session.ip, 200 | rcJson, response); return false; } // The remaining commands use an OutputBuffer for the response OutputBuffer *response = nullptr; if (StringEquals(command, "status")) { const char* typeVal = GetKeyValue("type"); if (typeVal != nullptr) { // New-style JSON status responses int type = atoi(typeVal); if (type < 1 || type > 3) { type = 1; } response = reprap.GetStatusResponse(type, ResponseSource::HTTP); } else { response = reprap.GetLegacyStatusResponse(1, 0); } } else if (StringEquals(command, "gcode")) { const char* gcodeVal = GetKeyValue("gcode"); if (gcodeVal != nullptr) { RegularGCodeInput * const httpInput = reprap.GetGCodes()->GetHTTPInput(); httpInput->Put(HTTP_MESSAGE, gcodeVal); if (OutputBuffer::Allocate(response)) { response->printf("{\"buff\":%u}", httpInput->BufferSpaceLeft()); } } else { network->SendReply(session.ip, 200 | rcJson, "{\"err\":1}"); return false; } } else if (StringEquals(command, "filelist") && GetKeyValue("dir") != nullptr) { response = reprap.GetFilelistResponse(GetKeyValue("dir")); } else if (StringEquals(command, "files")) { const char* dir = GetKeyValue("dir"); if (dir == nullptr) { dir = platform->GetGCodeDir(); } const char* const flagDirsVal = GetKeyValue("flagDirs"); const bool flagDirs = flagDirsVal != nullptr && atoi(flagDirsVal) == 1; response = reprap.GetFilesResponse(dir, flagDirs); } else if (StringEquals(command, "fileinfo")) { const char* const nameVal = GetKeyValue("name"); if (reprap.GetPrintMonitor()->GetFileInfoResponse(nameVal, response)) { processingDeferredRequest = false; } else { processingDeferredRequest = true; } } else if (StringEquals(command, "config")) { response = reprap.GetConfigResponse(); } else { platform->MessageF(HOST_MESSAGE, "KnockOut request: %s not recognised\n", command); network->SendReply(session.ip, 400, "Unknown rr_ command"); return false; } if (response != nullptr) { network->SendReply(session.ip, 200 | rcJson, response); } else if (!processingDeferredRequest) { network->SendReply(session.ip, 500, "No buffer available"); } return processingDeferredRequest; }