// 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;
}
Exemple #2
0
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;
}
Exemple #3
0
// 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;
}