diff options
author | Alexander Tkachev | 2016-07-09 15:49:18 +0600 |
---|---|---|
committer | Alexander Tkachev | 2016-08-24 16:07:55 +0600 |
commit | e4bb7c4e750d53434140a8d1a401e7fa6ee221ab (patch) | |
tree | 4b6d555163353d71b51e64308e5a4dccc85f2216 | |
parent | a424ebbc28549c0e06b9a8ca4985bf799179c404 (diff) | |
download | scummvm-rg350-e4bb7c4e750d53434140a8d1a401e7fa6ee221ab.tar.gz scummvm-rg350-e4bb7c4e750d53434140a8d1a401e7fa6ee221ab.tar.bz2 scummvm-rg350-e4bb7c4e750d53434140a8d1a401e7fa6ee221ab.zip |
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.
-rw-r--r-- | backends/module.mk | 4 | ||||
-rw-r--r-- | backends/networking/sdl_net/client.cpp | 64 | ||||
-rw-r--r-- | backends/networking/sdl_net/client.h | 13 | ||||
-rw-r--r-- | backends/networking/sdl_net/handlers/uploadfilehandler.cpp | 138 | ||||
-rw-r--r-- | backends/networking/sdl_net/handlers/uploadfilehandler.h | 11 | ||||
-rw-r--r-- | backends/networking/sdl_net/handlerutils.cpp | 121 | ||||
-rw-r--r-- | backends/networking/sdl_net/handlerutils.h | 44 | ||||
-rw-r--r-- | backends/networking/sdl_net/reader.cpp | 115 | ||||
-rw-r--r-- | backends/networking/sdl_net/reader.h | 17 | ||||
-rw-r--r-- | backends/networking/sdl_net/uploadfileclienthandler.cpp | 176 | ||||
-rw-r--r-- | backends/networking/sdl_net/uploadfileclienthandler.h | 57 |
11 files changed, 597 insertions, 163 deletions
diff --git a/backends/module.mk b/backends/module.mk index 0647d36b4f..8b51a2894f 100644 --- a/backends/module.mk +++ b/backends/module.mk @@ -66,8 +66,10 @@ MODULE_OBJS += \ networking/sdl_net/handlers/indexpagehandler.o \ networking/sdl_net/handlers/resourcehandler.o \ networking/sdl_net/handlers/uploadfilehandler.o \ + networking/sdl_net/handlerutils.o \ networking/sdl_net/localwebserver.o \ - networking/sdl_net/reader.o + networking/sdl_net/reader.o \ + networking/sdl_net/uploadfileclienthandler.o endif ifdef USE_ELF_LOADER diff --git a/backends/networking/sdl_net/client.cpp b/backends/networking/sdl_net/client.cpp index 13543a6c44..89b50faf86 100644 --- a/backends/networking/sdl_net/client.cpp +++ b/backends/networking/sdl_net/client.cpp @@ -26,12 +26,13 @@ #include "backends/networking/sdl_net/localwebserver.h" #include "common/textconsole.h" #include <SDL/SDL_net.h> +#include <common/memstream.h> namespace Networking { -Client::Client() : _state(INVALID), _set(nullptr), _socket(nullptr), _handler(nullptr) {} +Client::Client() : _state(INVALID), _set(nullptr), _socket(nullptr), _handler(nullptr), _previousHandler(nullptr), _stream(nullptr) {} -Client::Client(SDLNet_SocketSet set, TCPsocket socket) : _state(INVALID), _set(nullptr), _socket(nullptr), _handler(nullptr) { +Client::Client(SDLNet_SocketSet set, TCPsocket socket) : _state(INVALID), _set(nullptr), _socket(nullptr), _handler(nullptr), _previousHandler(nullptr), _stream(nullptr) { open(set, socket); } @@ -46,7 +47,11 @@ void Client::open(SDLNet_SocketSet set, TCPsocket socket) { _set = set; Reader cleanReader; _reader = cleanReader; + if (_handler) delete _handler; _handler = nullptr; + if (_previousHandler) delete _previousHandler; + _previousHandler = nullptr; + _stream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES); if (set) { int numused = SDLNet_TCP_AddSocket(set, socket); if (numused == -1) { @@ -55,9 +60,11 @@ void Client::open(SDLNet_SocketSet set, TCPsocket socket) { } } -void Client::readHeaders() { - if (!_socket) return; - if (!SDLNet_SocketReady(_socket)) return; +bool Client::readMoreIfNeeded() { + if (_stream == nullptr) return false; //nothing to read into + if (_stream->size() - _stream->pos() > 0) return true; //not needed, some data left in the stream + if (!_socket) return false; + if (!SDLNet_SocketReady(_socket)) return false; const uint32 BUFFER_SIZE = 16 * 1024; byte buffer[BUFFER_SIZE]; @@ -65,16 +72,48 @@ void Client::readHeaders() { if (bytes <= 0) { warning("Client::readHeaders recv fail"); close(); - return; + return false; + } + + if (_stream->write(buffer, bytes) != bytes) { + warning("failed to write() into MemoryReadWriteStream"); + close(); + return false; } - - _reader.setContent(buffer, bytes); - if (_reader.readRequest()) + + return true; +} + +void Client::readHeaders() { + if (!readMoreIfNeeded()) return; + _reader.setContent(_stream); + if (_reader.readFirstHeaders()) _state = (_reader.badRequest() ? BAD_REQUEST : READ_HEADERS); } +bool Client::readContent(Common::WriteStream *stream) { + if (!readMoreIfNeeded()) return false; + _reader.setContent(_stream); + return _reader.readFirstContent(stream); +} + +bool Client::readBlockHeaders(Common::WriteStream *stream) { + if (!readMoreIfNeeded()) return false; + _reader.setContent(_stream); + return _reader.readBlockHeaders(stream); +} + +bool Client::readBlockContent(Common::WriteStream *stream) { + if (!readMoreIfNeeded()) return false; + _reader.setContent(_stream); + return _reader.readBlockContent(stream); +} + void Client::setHandler(ClientHandler *handler) { - if (_handler) delete _handler; + if (_handler) { + if (_previousHandler) delete _previousHandler; + _previousHandler = _handler; //can't just delete it, as setHandler() could've been called by handler itself + } _state = BEING_HANDLED; _handler = handler; } @@ -100,6 +139,11 @@ void Client::close() { _socket = nullptr; } + if (_stream) { + delete _stream; + _stream = nullptr; + } + _state = INVALID; } diff --git a/backends/networking/sdl_net/client.h b/backends/networking/sdl_net/client.h index cd10adc780..6a0dea32fe 100644 --- a/backends/networking/sdl_net/client.h +++ b/backends/networking/sdl_net/client.h @@ -27,6 +27,10 @@ #include "common/str.h" #include "reader.h" +namespace Common { +class MemoryReadWriteStream; +} + typedef struct _SDLNet_SocketSet *SDLNet_SocketSet; typedef struct _TCPsocket *TCPsocket; @@ -53,7 +57,10 @@ class Client { SDLNet_SocketSet _set; TCPsocket _socket; Reader _reader; - ClientHandler *_handler; + ClientHandler *_handler, *_previousHandler; + Common::MemoryReadWriteStream *_stream; + + bool readMoreIfNeeded(); public: Client(); @@ -62,7 +69,11 @@ public: void open(SDLNet_SocketSet set, TCPsocket socket); void readHeaders(); + bool readContent(Common::WriteStream *stream); + bool readBlockHeaders(Common::WriteStream *stream); + bool readBlockContent(Common::WriteStream *stream); void setHandler(ClientHandler *handler); + void dropHandler(); void handle(); void close(); diff --git a/backends/networking/sdl_net/handlers/uploadfilehandler.cpp b/backends/networking/sdl_net/handlers/uploadfilehandler.cpp index 1eabf1d926..eeb4fad1ac 100644 --- a/backends/networking/sdl_net/handlers/uploadfilehandler.cpp +++ b/backends/networking/sdl_net/handlers/uploadfilehandler.cpp @@ -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; - } - - // 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; + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Can't upload into a file!")); + return; } - - delete tempStream; - delete fileStream; - return true; + + // if all OK, set special handler + client.setHandler(new UploadFileClientHandler(path)); } /// public diff --git a/backends/networking/sdl_net/handlers/uploadfilehandler.h b/backends/networking/sdl_net/handlers/uploadfilehandler.h index 6300a13e8d..bba7fdfc02 100644 --- a/backends/networking/sdl_net/handlers/uploadfilehandler.h +++ b/backends/networking/sdl_net/handlers/uploadfilehandler.h @@ -29,16 +29,7 @@ namespace Networking { class UploadFileHandler: public FilesBaseHandler { void handle(Client &client); - void handleErrorMessage(Client &client, Common::String message); - - /** - * Uploads file. - * - * Fills <errorMessage> on failure. - * - * Returns true on success. - */ - bool uploadFile(Client &client, Common::String &errorMessage); + public: UploadFileHandler(); virtual ~UploadFileHandler(); diff --git a/backends/networking/sdl_net/handlerutils.cpp b/backends/networking/sdl_net/handlerutils.cpp new file mode 100644 index 0000000000..f249ee1e29 --- /dev/null +++ b/backends/networking/sdl_net/handlerutils.cpp @@ -0,0 +1,121 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/archive.h" +#include "common/config-manager.h" +#include "common/file.h" +#include "common/translation.h" +#include "common/unzip.h" + +namespace Networking { + +#define ARCHIVE_NAME "wwwroot.zip" + +#define INDEX_PAGE_NAME ".index.html" + +Common::Archive *HandlerUtils::getZipArchive() { + // first search in themepath + if (ConfMan.hasKey("themepath")) { + const Common::FSNode &node = Common::FSNode(ConfMan.get("themepath")); + if (!node.exists() || !node.isReadable() || !node.isDirectory()) + return nullptr; + + Common::FSNode fileNode = node.getChild(ARCHIVE_NAME); + if (fileNode.exists() && fileNode.isReadable() && !fileNode.isDirectory()) { + Common::SeekableReadStream *const stream = fileNode.createReadStream(); + Common::Archive *zipArchive = Common::makeZipArchive(stream); + if (zipArchive) return zipArchive; + } + } + + // then use SearchMan to find it + Common::ArchiveMemberList fileList; + SearchMan.listMatchingMembers(fileList, ARCHIVE_NAME); + for (Common::ArchiveMemberList::iterator it = fileList.begin(); it != fileList.end(); ++it) { + Common::ArchiveMember const &m = **it; + Common::SeekableReadStream *const stream = m.createReadStream(); + Common::Archive *zipArchive = Common::makeZipArchive(stream); + if (zipArchive) return zipArchive; + } + + return nullptr; +} + +Common::ArchiveMemberList HandlerUtils::listArchive() { + Common::ArchiveMemberList resultList; + Common::Archive *zipArchive = getZipArchive(); + if (zipArchive) { + zipArchive->listMembers(resultList); + delete zipArchive; + } + return resultList; +} + +Common::SeekableReadStream *HandlerUtils::getArchiveFile(Common::String name) { + Common::SeekableReadStream *result = nullptr; + Common::Archive *zipArchive = getZipArchive(); + if (zipArchive) { + const Common::ArchiveMemberPtr ptr = zipArchive->getMember(name); + if (ptr.get() == nullptr) return nullptr; + result = ptr->createReadStream(); + delete zipArchive; + } + return result; +} + +Common::String HandlerUtils::readEverythingFromStream(Common::SeekableReadStream *const stream) { + Common::String result; + char buf[1024]; + uint32 readBytes; + while (!stream->eos()) { + readBytes = stream->read(buf, 1024); + result += Common::String(buf, readBytes); + } + return result; +} + +void HandlerUtils::setMessageHandler(Client &client, Common::String message, Common::String redirectTo) { + 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); + if (redirectTo.empty()) LocalWebserver::setClientGetHandler(client, response); + else LocalWebserver::setClientRedirectHandler(client, response, redirectTo); +} + +void HandlerUtils::setFilesManagerErrorMessageHandler(Client &client, Common::String message, Common::String redirectTo) { + setMessageHandler(client, + Common::String::format( + "%s<br/><a href=\"files?path=%s\">%s</a>", + message.c_str(), + "%2F", //that's encoded "/" + _("Back to the files manager") + ), redirectTo + ); +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlerutils.h b/backends/networking/sdl_net/handlerutils.h new file mode 100644 index 0000000000..c6722688e8 --- /dev/null +++ b/backends/networking/sdl_net/handlerutils.h @@ -0,0 +1,44 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_HANDLERUTILS_H +#define BACKENDS_NETWORKING_SDL_NET_HANDLERUTILS_H + +#include "backends/networking/sdl_net/client.h" +#include "common/archive.h" + +namespace Networking { + +class HandlerUtils { +public: + static Common::Archive *getZipArchive(); + static Common::ArchiveMemberList listArchive(); + static Common::SeekableReadStream *getArchiveFile(Common::String name); + static Common::String readEverythingFromStream(Common::SeekableReadStream *const stream); + + static void setMessageHandler(Client &client, Common::String message, Common::String redirectTo = ""); + static void setFilesManagerErrorMessageHandler(Client &client, Common::String message, Common::String redirectTo = ""); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/reader.cpp b/backends/networking/sdl_net/reader.cpp index e08d5db566..d61eff2e68 100644 --- a/backends/networking/sdl_net/reader.cpp +++ b/backends/networking/sdl_net/reader.cpp @@ -135,14 +135,14 @@ void Reader::cleanup() { } } -bool Reader::readRequest() { +bool Reader::readWholeRequest() { if (_state == RS_NONE) _state = RS_READING_HEADERS; while (true) { if (!bytesLeft()) return false; if (_state == RS_READING_HEADERS) - if (!readHeaders()) + if (!readWholeHeaders()) return false; if (_boundary.empty()) return true; //not POST multipart @@ -150,7 +150,7 @@ bool Reader::readRequest() { if (!bytesLeft()) return false; if (_state == RS_READING_CONTENT) - if (!readContent()) + if (!readWholeContent()) return false; if (_availableBytes >= 2) { @@ -171,7 +171,69 @@ bool Reader::readRequest() { return true; } -bool Reader::readHeaders() { +bool Reader::readFirstHeaders() { + if (_state == RS_NONE) _state = RS_READING_HEADERS; + + if (!bytesLeft()) return false; + + if (_state == RS_READING_HEADERS) + return readWholeHeaders(); + + warning("Reader::readFirstHeaders(): bad state"); + return false; +} + +bool Reader::readFirstContent(Common::WriteStream *stream) { + if (_state != RS_READING_CONTENT) { + warning("Reader::readFirstContent(): bad state"); + return false; + } + + if (!bytesLeft()) return false; + + return readWholeContentIntoStream(stream); +} + +bool Reader::readBlockHeaders(Common::WriteStream *stream) { + if (_state != RS_READING_HEADERS) { + warning("Reader::readBlockHeaders(): bad state"); + return false; + } + + if (!bytesLeft()) return false; + + return readWholeHeadersIntoStream(stream); +} + +bool Reader::readBlockContent(Common::WriteStream *stream) { + if (_state != RS_READING_CONTENT) { + warning("Reader::readBlockContent(): bad state"); + return false; + } + + if (!bytesLeft()) return false; + + if (!readWholeContentIntoStream(stream)) + return false; + + /* + if (_availableBytes >= 2) { + Common::String bts; + bts += readOne(); + bts += readOne(); + if (bts == "--") _isOver = true; + else if (bts != "\r\n") + warning("strange bytes: \"%s\"", bts.c_str()); + } else { + warning("strange ending"); + _isOver = true; + } + */ + + return true; +} + +bool Reader::readWholeHeaders() { Common::String boundary = "\r\n\r\n"; if (_window == nullptr) { makeWindow(boundary.size()); @@ -188,6 +250,20 @@ bool Reader::readHeaders() { return true; } +bool Reader::readWholeHeadersIntoStream(Common::WriteStream *stream) { + Common::String boundary = "\r\n\r\n"; + if (_window == nullptr) makeWindow(boundary.size()); + + while (readOneByteInStream(stream, boundary)) { + if (!bytesLeft()) return false; + } + if (stream) stream->flush(); + + freeWindow(); + _state = RS_READING_CONTENT; + return true; +} + namespace { void readFromThatUntilLineEnd(const char *cstr, Common::String needle, Common::String &result) { const char *position = strstr(cstr, needle.c_str()); @@ -383,7 +459,7 @@ Common::String generateTempFileName(Common::String originalFilename, Common::Ran } } -bool Reader::readContent() { +bool Reader::readWholeContent() { Common::String boundary = "--" + _boundary; if (!_firstBlock) boundary = "\r\n" + boundary; if (_window == nullptr) { @@ -435,6 +511,23 @@ bool Reader::readContent() { return true; } +bool Reader::readWholeContentIntoStream(Common::WriteStream *stream) { + Common::String boundary = "--" + _boundary; + if (!_firstBlock) boundary = "\r\n" + boundary; + if (_window == nullptr) makeWindow(boundary.size()); + + while (readOneByteInStream(stream, boundary)) { + if (!bytesLeft()) return false; + } + + _firstBlock = false; + if (stream) stream->flush(); + + freeWindow(); + _state = RS_READING_HEADERS; + return true; +} + void Reader::handleFileContent(Common::String filename) { debug("\nHANDLE FILE CONTENT:\nFILE >>%s<< SAVED INTO >>%s<<", _currentFileName.c_str(), filename.c_str()); _attachedFiles[_currentFileName] = filename; @@ -469,7 +562,7 @@ bool Reader::readOneByteInStream(Common::WriteStream *stream, const Common::Stri return false; //if not, add the first byte of the window to the string - stream->writeByte(_window[0]); + if (stream) stream->writeByte(_window[0]); for (uint32 i = 1; i < _windowSize; ++i) _window[i - 1] = _window[i]; --_windowUsed; @@ -494,8 +587,8 @@ bool Reader::readOneByteInString(Common::String &buffer, const Common::String &b } byte Reader::readOne() { - byte b = _content[0]; - ++_content; + byte b; + _content->read(&b, 1); --_availableBytes; --_bytesLeft; return b; @@ -503,9 +596,9 @@ byte Reader::readOne() { uint32 Reader::bytesLeft() { return _bytesLeft; } -void Reader::setContent(byte *buffer, uint32 size) { - _content = buffer; - _bytesLeft = size; +void Reader::setContent(Common::MemoryReadWriteStream *stream) { + _content = stream; + _bytesLeft = stream->size() - stream->pos(); } bool Reader::badRequest() { return _isBadRequest; } diff --git a/backends/networking/sdl_net/reader.h b/backends/networking/sdl_net/reader.h index 244d619f5f..5f951a39b1 100644 --- a/backends/networking/sdl_net/reader.h +++ b/backends/networking/sdl_net/reader.h @@ -30,6 +30,7 @@ #include "common/random.h" namespace Common { +class MemoryReadWriteStream; class WriteStream; } @@ -45,7 +46,7 @@ class Reader { Common::RandomSource _randomSource; //for temp file names ReaderState _state; - byte *_content; + Common::MemoryReadWriteStream *_content; uint32 _bytesLeft; byte *_window; @@ -68,8 +69,10 @@ class Reader { void cleanup(); - bool readHeaders(); //true when ended reading - bool readContent(); //true when ended reading + bool readWholeHeaders(); //true when ended reading + bool readWholeContent(); //true when ended reading + bool readWholeHeadersIntoStream(Common::WriteStream *stream); //true when ended reading + bool readWholeContentIntoStream(Common::WriteStream *stream); //true when ended reading void handleHeaders(Common::String headers); void handleFileContent(Common::String filename); void handleValueContent(Common::String value); @@ -92,8 +95,12 @@ public: Reader &operator=(Reader &r); - bool readRequest(); //true when ended reading - void setContent(byte *buffer, uint32 size); + bool readWholeRequest(); //true when ended reading + bool readFirstHeaders(); //true when ended reading + bool readFirstContent(Common::WriteStream *stream); //true when ended reading + bool readBlockHeaders(Common::WriteStream *stream); //true when ended reading + bool readBlockContent(Common::WriteStream *stream); //true when ended reading + void setContent(Common::MemoryReadWriteStream *stream); bool badRequest(); Common::String method() const; diff --git a/backends/networking/sdl_net/uploadfileclienthandler.cpp b/backends/networking/sdl_net/uploadfileclienthandler.cpp new file mode 100644 index 0000000000..ae795da25c --- /dev/null +++ b/backends/networking/sdl_net/uploadfileclienthandler.cpp @@ -0,0 +1,176 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#include "backends/networking/sdl_net/uploadfileclienthandler.h" +#include "common/textconsole.h" +#include <common/memstream.h> +#include <common/translation.h> +#include <common/system.h> +#include <backends/fs/fs-factory.h> +#include <common/debug.h> +#include "handlerutils.h" +#include "localwebserver.h" + +namespace Networking { + +UploadFileClientHandler::UploadFileClientHandler(Common::String parentDirectoryPath): + _state(UFH_READING_CONTENT), _headersStream(nullptr), _contentStream(nullptr), + _parentDirectoryPath(parentDirectoryPath) {} + +UploadFileClientHandler::~UploadFileClientHandler() { + delete _headersStream; + delete _contentStream; +} + +namespace { +void readFromThatUntilLineEnd(const char *cstr, Common::String needle, Common::String &result) { + const char *position = strstr(cstr, needle.c_str()); + + if (position) { + char c; + for (const char *i = position + needle.size(); c = *i, c != 0; ++i) { + if (c == '\n' || c == '\r') break; + result += c; + } + } +} + +void readFromThatUntilDoubleQuote(const char *cstr, Common::String needle, Common::String &result) { + const char *position = strstr(cstr, needle.c_str()); + + if (position) { + char c; + for (const char *i = position + needle.size(); c = *i, c != 0; ++i) { + if (c == '"') break; + result += c; + } + } +} + +Common::String readEverythingFromMemoryStream(Common::MemoryReadWriteStream *stream) { + Common::String result; + char buf[1024]; + uint32 readBytes; + while (true) { + readBytes = stream->read(buf, 1024); + if (readBytes == 0) break; + result += Common::String(buf, readBytes); + } + return result; +} +} + +void UploadFileClientHandler::handleBlockHeaders(Client *client) { + // search for "upload_file" field + Common::String headers = readEverythingFromMemoryStream(_headersStream); + Common::String fieldName = ""; + readFromThatUntilDoubleQuote(headers.c_str(), "name=\"", fieldName); + if (fieldName != "upload_file") return; + + Common::String filename = ""; + readFromThatUntilDoubleQuote(headers.c_str(), "filename=\"", filename); + + // check that <filename> is not empty + if (filename.empty()) { + setErrorMessageHandler(*client, _("Invalid filename!")); + return; + } + + // check that <path>/<filename> doesn't exist + Common::String path = _parentDirectoryPath; + if (path.lastChar() != '/' && path.lastChar() != '\\') path += '/'; + AbstractFSNode *originalNode = g_system->getFilesystemFactory()->makeFileNodePath(path + filename); + if (originalNode->exists()) { + setErrorMessageHandler(*client, _("There is a file with that name in the parent directory!")); + return; + } + + // create file stream + _contentStream = originalNode->createWriteStream(); + if (_contentStream == nullptr) { + setErrorMessageHandler(*client, _("Failed to upload the file!")); + return; + } +} + +void UploadFileClientHandler::handle(Client *client) { + if (client == nullptr) { + warning("UploadFileClientHandler::handle(): empty client pointer"); + return; + } + + while (true) { + switch (_state) { + case UFH_READING_CONTENT: + if (client->readContent(nullptr)) { + _state = UFH_READING_BLOCK_HEADERS; + continue; + } + break; + + case UFH_READING_BLOCK_HEADERS: + if (_headersStream == nullptr) + _headersStream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES); + + if (client->readBlockHeaders(_headersStream)) { + handleBlockHeaders(client); + _state = UFH_READING_BLOCK_CONTENT; + continue; + } + break; + + case UFH_READING_BLOCK_CONTENT: + if (client->readBlockContent(_contentStream)) { + if (_contentStream) { + _contentStream->flush(); + // success - redirect back to directory listing + HandlerUtils::setMessageHandler(*client, + Common::String::format( + "%s<br/><a href=\"files?path=%s\">%s</a>", + _("Uploaded successfully!"), + client->queryParameter("path").c_str(), + _("Back to parent directory") + ), + "/files?path=" + LocalWebserver::urlEncodeQueryParameterValue(client->queryParameter("path")) + ); + return; + } + _state = UFH_READING_BLOCK_HEADERS; + continue; + } + break; + + case UFH_ERROR: + return; + } + + break; + } +} + + +void UploadFileClientHandler::setErrorMessageHandler(Client &client, Common::String message) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, message); + _state = UFH_ERROR; +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/uploadfileclienthandler.h b/backends/networking/sdl_net/uploadfileclienthandler.h new file mode 100644 index 0000000000..4f2ce621fb --- /dev/null +++ b/backends/networking/sdl_net/uploadfileclienthandler.h @@ -0,0 +1,57 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_UPLOADFILECLIENTHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_UPLOADFILECLIENTHANDLER_H + +#include "backends/networking/sdl_net/client.h" +#include "common/stream.h" + +namespace Networking { + +enum UploadFileHandlerState { + UFH_READING_CONTENT, + UFH_READING_BLOCK_HEADERS, + UFH_READING_BLOCK_CONTENT, + UFH_ERROR +}; + +class UploadFileClientHandler: public ClientHandler { + UploadFileHandlerState _state; + Common::MemoryReadWriteStream *_headersStream; + Common::WriteStream *_contentStream; + Common::String _parentDirectoryPath; + + void handleBlockHeaders(Client *client); + bool validFilename(Client &client, Common::String filename, Common::String &errorMessage); + void setErrorMessageHandler(Client &client, Common::String message); + +public: + UploadFileClientHandler(Common::String parentDirectoryPath); + virtual ~UploadFileClientHandler(); + + virtual void handle(Client *client); +}; + +} // End of namespace Networking + +#endif |