CLOUD: Add UploadFileClientHandler

Now Client reads the first headers block, then LocalWebserver decides
which Handler to use. In case of "/upload", UploadFileHandler is used.

But now it only knows the "path" parameter. If that's valid, actual
UploadFileClientHandler is created, which reads the contents of the
request and, when finds there  an "upload_file" field, starts saving it
in the directory specified by "path".

With that we don't need temp files approach from Reader class.
This commit is contained in:
Alexander Tkachev 2016-07-09 15:49:18 +06:00
parent a424ebbc28
commit e4bb7c4e75
11 changed files with 597 additions and 163 deletions

View file

@ -21,161 +21,49 @@
*/
#include "backends/networking/sdl_net/handlers/uploadfilehandler.h"
#include "backends/networking/sdl_net/handlerutils.h"
#include "backends/networking/sdl_net/localwebserver.h"
#include "backends/networking/sdl_net/uploadfileclienthandler.h"
#include "backends/fs/fs-factory.h"
#include "common/file.h"
#include "common/translation.h"
namespace Networking {
#define INDEX_PAGE_NAME ".index.html"
UploadFileHandler::UploadFileHandler() {}
UploadFileHandler::~UploadFileHandler() {}
void UploadFileHandler::handle(Client &client) {
Common::String errorMessage = "";
// show an error message if failed to upload the file
if (!uploadFile(client, errorMessage)) {
handleErrorMessage(
client,
Common::String::format(
"%s<br/><a href=\"files?path=%s\">%s</a>",
errorMessage.c_str(),
"%2F", //that's encoded "/"
_("Back to the files manager")
)
);
return;
}
Common::String response = "<html><head><title>ScummVM</title></head><body>{message}</body></html>";
// load stylish response page from the archive
Common::SeekableReadStream *const stream = getArchiveFile(INDEX_PAGE_NAME);
if (stream) response = readEverythingFromStream(stream);
replace(response, "{message}", Common::String::format(
"%s<br/><a href=\"files?path=%s\">%s</a>",
_("Uploaded successfully!"),
client.queryParameter("path").c_str(),
_("Back to parent directory")
)
);
LocalWebserver::setClientRedirectHandler(
client, response,
"/files?path=" + LocalWebserver::urlEncodeQueryParameterValue(client.queryParameter("path"))
);
}
void UploadFileHandler::handleErrorMessage(Client &client, Common::String message) {
Common::String response = "<html><head><title>ScummVM</title></head><body>{message}</body></html>";
// load stylish response page from the archive
Common::SeekableReadStream *const stream = getArchiveFile(INDEX_PAGE_NAME);
if (stream) response = readEverythingFromStream(stream);
replace(response, "{message}", message);
LocalWebserver::setClientGetHandler(client, response);
}
namespace {
bool copyStream(Common::ReadStream *from, Common::WriteStream *into) {
assert(from);
assert(into);
const uint32 BUFFER_SIZE = 1 * 1024 * 1024;
void *buffer = malloc(BUFFER_SIZE);
bool success = true;
assert(buffer);
while (!from->eos()) {
uint32 readBytes = from->read(buffer, BUFFER_SIZE);
if (from->err()) {
warning("copyStream: failed to read bytes from the stream");
success = false;
break;
}
if (readBytes != 0)
if (into->write(buffer, readBytes) != readBytes || into->err()) {
warning("copyStream: failed to write all bytes into the file");
success = false;
break;
}
}
free(buffer);
return success;
}
}
bool UploadFileHandler::uploadFile(Client &client, Common::String &errorMessage) {
Common::String path = client.queryParameter("path");
Common::String originalFilename = client.queryParameter("upload_file");
Common::String tempFilename = client.attachedFile(originalFilename);
debug("path = <%s>", path.c_str());
debug("filename = <%s>", originalFilename.c_str());
debug("tempfile = <%s>", tempFilename.c_str());
// check that <path> is not an absolute root
if (path == "" || path == "/" || path == "\\") {
errorMessage = _("Invalid path!");
return false;
HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
return;
}
// transform virtual path to actual file system one
Common::String prefixToRemove = "", prefixToAdd = "";
if (!transformPath(path, prefixToRemove, prefixToAdd, false) || path.empty()) {
errorMessage = _("Invalid path!");
return false;
HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
return;
}
// check that <path> exists and is directory
AbstractFSNode *node = g_system->getFilesystemFactory()->makeFileNodePath(path);
if (!node->exists()) {
errorMessage = _("The parent directory doesn't exist!");
return false;
HandlerUtils::setFilesManagerErrorMessageHandler(client, _("The parent directory doesn't exist!"));
return;
}
if (!node->isDirectory()) {
errorMessage = _("Can't upload into a file!");
return false;
HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Can't upload into a file!"));
return;
}
// check that <path>/<originalFilename> doesn't exist
if (path.lastChar() != '/' && path.lastChar() != '\\') path += '/';
AbstractFSNode *originalNode = g_system->getFilesystemFactory()->makeFileNodePath(path + originalFilename);
if (originalNode->exists()) {
errorMessage = _("There is a file with that name in the parent directory!");
return false;
}
// check that <tempFilename> exists
AbstractFSNode *tempNode = g_system->getFilesystemFactory()->makeFileNodePath(tempFilename);
if (!tempNode->exists() || tempNode->isDirectory()) {
errorMessage = _("Failed to upload the file!");
return false;
}
// copy <tempFilename> into <path>/<originalFilename>
// FIXME: I think we should move/rename file with some system call
// even though that might be less portable, that is much better than
// making an actual copy of data (because user might had enough place
// for one copy of the file, but not for two of them)
Common::ReadStream *tempStream = tempNode->createReadStream();
Common::WriteStream *fileStream = originalNode->createWriteStream();
if (tempStream == nullptr || fileStream == nullptr || !copyStream(tempStream, fileStream)) {
delete tempStream;
delete fileStream;
errorMessage = _("Failed to upload the file!");
return false;
}
delete tempStream;
delete fileStream;
return true;
// if all OK, set special handler
client.setHandler(new UploadFileClientHandler(path));
}
/// public