CLOUD: Refactor LocalWebserver

Its handlers are now more compact. This commit moves Handler classes in
handlers\ directory.

ResourceHandler ignores "hidden" files in the archive, and these are
used as markup templates in IndexPageHandler and FilesPageHandler.
This commit is contained in:
Alexander Tkachev 2016-07-06 16:48:56 +06:00
parent 627bda9d82
commit 48e3fff6bc
18 changed files with 665 additions and 358 deletions

View file

@ -58,7 +58,11 @@ ifdef USE_SDL_NET
MODULE_OBJS += \ MODULE_OBJS += \
networking/sdl_net/client.o \ networking/sdl_net/client.o \
networking/sdl_net/getclienthandler.o \ networking/sdl_net/getclienthandler.o \
networking/sdl_net/indexpagehandler.o \ networking/sdl_net/handlers/basehandler.o \
networking/sdl_net/handlers/filesbasehandler.o \
networking/sdl_net/handlers/filespagehandler.o \
networking/sdl_net/handlers/indexpagehandler.o \
networking/sdl_net/handlers/resourcehandler.o \
networking/sdl_net/localwebserver.o networking/sdl_net/localwebserver.o
endif endif

View file

@ -20,7 +20,7 @@ def buildArchive(archiveName):
filenames = os.listdir('.') filenames = os.listdir('.')
filenames.sort() filenames.sort()
for filename in filenames: for filename in filenames:
if os.path.isfile(filename) and not filename[0] == '.' and filename.endswith(ARCHIVE_FILE_EXTENSIONS): if os.path.isfile(filename) and filename.endswith(ARCHIVE_FILE_EXTENSIONS):
zf.write(filename, './' + filename) zf.write(filename, './' + filename)
print (" Adding file: " + filename) print (" Adding file: " + filename)

View file

@ -0,0 +1,100 @@
/* 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/handlers/basehandler.h"
#include "common/archive.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "common/unzip.h"
namespace Networking {
#define ARCHIVE_NAME "wwwroot.zip"
BaseHandler::BaseHandler() {}
BaseHandler::~BaseHandler() {}
/// utils
Common::Archive *BaseHandler::getZipArchive() const {
// 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 BaseHandler::listArchive() const {
Common::ArchiveMemberList resultList;
Common::Archive *zipArchive = getZipArchive();
if (zipArchive) {
zipArchive->listMembers(resultList);
delete zipArchive;
}
return resultList;
}
Common::SeekableReadStream *BaseHandler::getArchiveFile(Common::String name) const {
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 BaseHandler::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;
}
} // End of namespace Networking

View file

@ -0,0 +1,50 @@
/* 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_BASEHANDLER_H
#define BACKENDS_NETWORKING_SDL_NET_BASEHANDLER_H
#include "backends/networking/sdl_net/client.h"
#include "common/archive.h"
#include "common/callback.h"
namespace Networking {
typedef Common::BaseCallback<Client &> *ClientHandlerCallback;
class BaseHandler {
protected:
Common::Archive *getZipArchive() const;
Common::ArchiveMemberList listArchive() const;
Common::SeekableReadStream *getArchiveFile(Common::String name) const;
static Common::String readEverythingFromStream(Common::SeekableReadStream *const stream);
public:
BaseHandler();
virtual ~BaseHandler();
virtual ClientHandlerCallback getHandler() = 0;
};
} // End of namespace Networking
#endif

View file

@ -0,0 +1,79 @@
/* 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/handlers/filesbasehandler.h"
#include "backends/saves/default/default-saves.h"
#include "common/config-manager.h"
#include "common/system.h"
namespace Networking {
FilesBaseHandler::FilesBaseHandler() {}
FilesBaseHandler::~FilesBaseHandler() {}
Common::String FilesBaseHandler::parentPath(Common::String path) {
if (path.size() && (path.lastChar() == '/' || path.lastChar() == '\\')) path.deleteLastChar();
if (!path.empty()) {
for (int i = path.size() - 1; i >= 0; --i)
if (i == 0 || path[i] == '/' || path[i] == '\\') {
path.erase(i);
break;
}
}
if (path.size() && path.lastChar() != '/' && path.lastChar() != '\\') path += '/';
return path;
}
bool FilesBaseHandler::transformPath(Common::String &path, Common::String &prefixToRemove, Common::String &prefixToAdd) {
// <path> is not empty, but could lack the trailing slash
if (path.lastChar() != '/' && path.lastChar() != '\\') path += '/';
if (path.hasPrefix("/root")) {
prefixToAdd = "/root/";
prefixToRemove = "";
path.erase(0, 5);
if (path == "") path = "/"; // absolute root is '/'
if (path != "/") path.deleteChar(0); // if that was "/root/ab/c", it becomes "/ab/c", but we need "ab/c"
return true;
}
if (path.hasPrefix("/saves")) {
prefixToAdd = "/saves/";
// determine savepath (prefix to remove)
DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager());
prefixToRemove = (manager ? manager->concatWithSavesPath("") : ConfMan.get("savepath"));
if (prefixToRemove.size() && prefixToRemove.lastChar() != '/' && prefixToRemove.lastChar() != '\\')
prefixToRemove += '/';
path.erase(0, 6);
if (path.size() && (path[0] == '/' || path[0] == '\\'))
path.deleteChar(0);
path = prefixToRemove + path;
return true;
}
return false;
}
} // End of namespace Networking

View file

@ -0,0 +1,50 @@
/* 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_FILESBASEHANDLER_H
#define BACKENDS_NETWORKING_SDL_NET_FILESBASEHANDLER_H
#include "backends/networking/sdl_net/handlers/basehandler.h"
namespace Networking {
class FilesBaseHandler: public BaseHandler {
protected:
Common::String parentPath(Common::String path);
/**
* Transforms virtual <path> into actual file system path.
*
* Fills prefixes with actual file system prefix ("to remove")
* and virtual path prefix ("to add").
*
* Returns true on success.
*/
bool transformPath(Common::String &path, Common::String &prefixToRemove, Common::String &prefixToAdd);
public:
FilesBaseHandler();
virtual ~FilesBaseHandler();
};
} // End of namespace Networking
#endif

View file

@ -0,0 +1,146 @@
/* 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/handlers/filespagehandler.h"
#include "backends/networking/sdl_net/localwebserver.h"
#include "common/file.h"
#include "common/translation.h"
namespace Networking {
#define INDEX_PAGE_NAME ".index.html"
#define FILES_PAGE_NAME ".files.html"
FilesPageHandler::FilesPageHandler() {}
FilesPageHandler::~FilesPageHandler() {}
void FilesPageHandler::handle(Client &client) {
Common::String response = "<html><head><title>ScummVM</title></head><body><table>{content}</table></body></html>"; //TODO: add controls
Common::String itemTemplate = "<tr><td><a href=\"{link}\">{name}</a></td><td>{size}</td></tr>\n"; //TODO: load this template too?
// load stylish response page from the archive
Common::SeekableReadStream *const stream = getArchiveFile(FILES_PAGE_NAME);
if (stream) response = readEverythingFromStream(stream);
Common::String path = client.queryParameter("path");
Common::String content = "";
// show an error message if failed to list directory
if (!listDirectory(path, content, itemTemplate)) {
handleErrorMessage(
client,
Common::String::format(
"%s<br/><a href=\"files?path=/\">%s</a>",
_("ScummVM couldn't list the directory you specified."),
_("Back to the files manager")
)
);
return;
}
//these occur twice:
replace(response, "{create_directory_button}", _("Create directory"));
replace(response, "{create_directory_button}", _("Create directory"));
replace(response, "{upload_files_button}", _("Upload files")); //tab
replace(response, "{upload_file_button}", _("Upload files")); //button in the tab
replace(response, "{create_directory_desc}", _("Type new directory name:"));
replace(response, "{upload_file_desc}", _("Select a file to upload:"));
replace(response, "{content}", content);
LocalWebserver::setClientGetHandler(client, response);
}
void FilesPageHandler::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);
}
bool FilesPageHandler::listDirectory(Common::String path, Common::String &content, const Common::String &itemTemplate) {
if (path == "" || path == "/") {
addItem(content, itemTemplate, true, "/root/", _("File system root"));
addItem(content, itemTemplate, true, "/saves/", _("Saved games"));
return true;
}
Common::String prefixToRemove = "", prefixToAdd = "";
if (!transformPath(path, prefixToRemove, prefixToAdd)) return false;
Common::FSNode node = Common::FSNode(path);
if (path == "/") node = node.getParent(); // absolute root
if (!node.isDirectory()) return false;
// list directory
Common::FSList _nodeContent;
if (!node.getChildren(_nodeContent, Common::FSNode::kListAll, false)) // do not show hidden files
_nodeContent.clear();
else
Common::sort(_nodeContent.begin(), _nodeContent.end());
// add parent directory link
{
Common::String filePath = path;
if (filePath.hasPrefix(prefixToRemove))
filePath.erase(0, prefixToRemove.size());
if (filePath == "" || filePath == "/" || filePath == "\\")
filePath = "/";
else
filePath = parentPath(prefixToAdd + filePath);
addItem(content, itemTemplate, true, filePath, _("Parent directory"));
}
// fill the content
for (Common::FSList::iterator i = _nodeContent.begin(); i != _nodeContent.end(); ++i) {
Common::String name = i->getDisplayName();
if (i->isDirectory()) name += "/";
Common::String filePath = i->getPath();
if (filePath.hasPrefix(prefixToRemove))
filePath.erase(0, prefixToRemove.size());
filePath = prefixToAdd + filePath;
addItem(content, itemTemplate, i->isDirectory(), filePath, name);
}
return true;
}
void FilesPageHandler::addItem(Common::String &content, const Common::String &itemTemplate, bool isDirectory, Common::String path, Common::String name, Common::String size) {
Common::String item = itemTemplate;
replace(item, "{link}", (isDirectory ? "files?path=" : "download?path=") + path);
replace(item, "{name}", name);
replace(item, "{size}", size);
content += item;
}
/// public
ClientHandlerCallback FilesPageHandler::getHandler() {
return new Common::Callback<FilesPageHandler, Client &>(this, &FilesPageHandler::handle);
}
} // End of namespace Networking

View file

@ -20,30 +20,17 @@
* *
*/ */
#ifndef BACKENDS_NETWORKING_SDL_NET_INDEXPAGEHANDLER_H #ifndef BACKENDS_NETWORKING_SDL_NET_FILESPAGEHANDLER_H
#define BACKENDS_NETWORKING_SDL_NET_INDEXPAGEHANDLER_H #define BACKENDS_NETWORKING_SDL_NET_FILESPAGEHANDLER_H
#include "backends/networking/sdl_net/client.h" #include "backends/networking/sdl_net/handlers/filesbasehandler.h"
#include "common/archive.h"
#include "gui/object.h"
namespace Networking { namespace Networking {
class LocalWebserver;
class IndexPageHandler: public GUI::CommandSender {
Common::String _code;
class FilesPageHandler: public FilesBaseHandler {
void handle(Client &client); void handle(Client &client);
void handleFiles(Client &client);
void handleResource(Client &client);
void handleErrorMessage(Client &client, Common::String message); void handleErrorMessage(Client &client, Common::String message);
// "files/"-related
Common::String parentPath(Common::String path);
/** Helper method for adding items into the files list. */
void addItem(Common::String &content, const Common::String &itemTemplate, bool isDirectory, Common::String path, Common::String name, Common::String size = "");
/** /**
* Lists the directory <path>. * Lists the directory <path>.
* *
@ -51,27 +38,14 @@ class IndexPageHandler: public GUI::CommandSender {
*/ */
bool listDirectory(Common::String path, Common::String &content, const Common::String &itemTemplate); bool listDirectory(Common::String path, Common::String &content, const Common::String &itemTemplate);
/** /** Helper method for adding items into the files list. */
* Transforms virtual <path> into actual file system path. void addItem(Common::String &content, const Common::String &itemTemplate, bool isDirectory, Common::String path, Common::String name, Common::String size = "");
*
* Fills prefixes with actual file system prefix ("to remove")
* and virtual path prefix ("to add").
*
* Returns true on success.
*/
bool transformPath(Common::String &path, Common::String &prefixToRemove, Common::String &prefixToAdd);
Common::Archive *getZipArchive();
Common::ArchiveMemberList listArchive();
Common::SeekableReadStream *getArchiveFile(Common::String name);
Common::String readEverythingFromStream(Common::SeekableReadStream *const stream);
public: public:
IndexPageHandler(); FilesPageHandler();
virtual ~IndexPageHandler(); virtual ~FilesPageHandler();
void addPathHandler(LocalWebserver &server); virtual ClientHandlerCallback getHandler();
Common::String code();
}; };
} // End of namespace Networking } // End of namespace Networking

View file

@ -0,0 +1,70 @@
/* 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/handlers/indexpagehandler.h"
#include "backends/networking/sdl_net/localwebserver.h"
#include "backends/saves/default/default-saves.h"
#include "common/archive.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "common/savefile.h"
#include "common/translation.h"
#include "gui/storagewizarddialog.h"
namespace Networking {
#define INDEX_PAGE_NAME ".index.html"
IndexPageHandler::IndexPageHandler(): CommandSender(nullptr) {}
IndexPageHandler::~IndexPageHandler() {}
void IndexPageHandler::handle(Client &client) {
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);
Common::String code = client.queryParameter("code");
if (code == "") {
replace(response, "{message}", _("This is a local webserver index page."));
LocalWebserver::setClientGetHandler(client, response);
return;
}
_code = code;
sendCommand(GUI::kStorageCodePassedCmd, 0);
replace(response, "{message}", _("ScummVM got the code and already connects to your cloud storage!"));
LocalWebserver::setClientGetHandler(client, response);
}
/// public
Common::String IndexPageHandler::code() { return _code; }
ClientHandlerCallback IndexPageHandler::getHandler() {
return new Common::Callback<IndexPageHandler, Client &>(this, &IndexPageHandler::handle);
}
} // End of namespace Networking

View file

@ -0,0 +1,46 @@
/* 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_INDEXPAGEHANDLER_H
#define BACKENDS_NETWORKING_SDL_NET_INDEXPAGEHANDLER_H
#include "backends/networking/sdl_net/handlers/basehandler.h"
#include "gui/object.h"
namespace Networking {
class LocalWebserver;
class IndexPageHandler: public BaseHandler, public GUI::CommandSender {
Common::String _code;
void handle(Client &client);
public:
IndexPageHandler();
virtual ~IndexPageHandler();
Common::String code();
virtual ClientHandlerCallback getHandler();
};
} // End of namespace Networking
#endif

View file

@ -0,0 +1,52 @@
/* 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/handlers/resourcehandler.h"
#include "backends/networking/sdl_net/localwebserver.h"
namespace Networking {
ResourceHandler::ResourceHandler() {}
ResourceHandler::~ResourceHandler() {}
void ResourceHandler::handle(Client &client) {
Common::String filename = client.path();
filename.deleteChar(0);
// if archive hidden file is requested, ignore
if (filename.size() && filename[0] == '.') return;
// if file not found, don't set handler either
Common::SeekableReadStream *file = getArchiveFile(filename);
if (file == nullptr) return;
LocalWebserver::setClientGetHandler(client, file, 200, LocalWebserver::determineMimeType(filename));
}
/// public
ClientHandlerCallback ResourceHandler::getHandler() {
return new Common::Callback<ResourceHandler, Client &>(this, &ResourceHandler::handle);
}
} // End of namespace Networking

View file

@ -0,0 +1,41 @@
/* 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_RESOURCEHANDLER_H
#define BACKENDS_NETWORKING_SDL_NET_RESOURCEHANDLER_H
#include "backends/networking/sdl_net/handlers/basehandler.h"
namespace Networking {
class ResourceHandler: public BaseHandler {
void handle(Client &client);
public:
ResourceHandler();
virtual ~ResourceHandler();
virtual ClientHandlerCallback getHandler();
};
} // End of namespace Networking
#endif

View file

@ -1,312 +0,0 @@
/* 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/indexpagehandler.h"
#include "backends/networking/sdl_net/localwebserver.h"
#include "backends/saves/default/default-saves.h"
#include "common/archive.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "common/savefile.h"
#include "common/translation.h"
#include "common/unzip.h"
#include "gui/storagewizarddialog.h"
namespace Networking {
#define ARCHIVE_NAME "wwwroot.zip"
#define INDEX_PAGE_NAME "index.html"
#define FILES_PAGE_NAME "files.html"
IndexPageHandler::IndexPageHandler(): CommandSender(nullptr) {}
IndexPageHandler::~IndexPageHandler() {
LocalServer.removePathHandler("/");
LocalServer.removePathHandler("/files/");
Common::ArchiveMemberList fileList = listArchive();
for (Common::ArchiveMemberList::iterator it = fileList.begin(); it != fileList.end(); ++it) {
Common::ArchiveMember const &m = **it;
if (m.getName() == INDEX_PAGE_NAME) continue;
LocalServer.removePathHandler("/" + m.getName());
}
}
void IndexPageHandler::handle(Client &client) {
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);
Common::String code = client.queryParameter("code");
if (code == "") {
replace(response, "{message}", _("This is a local webserver index page."));
LocalWebserver::setClientGetHandler(client, response);
return;
}
_code = code;
sendCommand(GUI::kStorageCodePassedCmd, 0);
replace(response, "{message}", _("ScummVM got the code and already connects to your cloud storage!"));
LocalWebserver::setClientGetHandler(client, response);
}
void IndexPageHandler::handleFiles(Client &client) {
Common::String response = "<html><head><title>ScummVM</title></head><body><table>{content}</table></body></html>"; //TODO: add controls
Common::String itemTemplate = "<tr><td><a href=\"{link}\">{name}</a></td><td>{size}</td></tr>\n"; //TODO: load this template too?
// load stylish response page from the archive
Common::SeekableReadStream *const stream = getArchiveFile(FILES_PAGE_NAME);
if (stream) response = readEverythingFromStream(stream);
Common::String path = client.queryParameter("path");
Common::String content = "";
// show an error message if failed to list directory
if (!listDirectory(path, content, itemTemplate)) {
handleErrorMessage(
client,
Common::String::format(
"%s<br/><a href=\"files?path=/\">%s</a>",
_("ScummVM couldn't list the directory you specified."),
_("Back to the files manager")
)
);
return;
}
//these occur twice:
replace(response, "{create_directory_button}", _("Create directory"));
replace(response, "{create_directory_button}", _("Create directory"));
replace(response, "{upload_files_button}", _("Upload files")); //tab
replace(response, "{upload_file_button}", _("Upload files")); //button in the tab
replace(response, "{create_directory_desc}", _("Type new directory name:"));
replace(response, "{upload_file_desc}", _("Select a file to upload:"));
replace(response, "{content}", content);
LocalWebserver::setClientGetHandler(client, response);
}
void IndexPageHandler::handleResource(Client &client) {
Common::String filename = client.path();
filename.deleteChar(0);
LocalWebserver::setClientGetHandler(client, getArchiveFile(filename), 200, LocalWebserver::determineMimeType(filename));
}
void IndexPageHandler::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);
}
/// "files/"-related
Common::String IndexPageHandler::parentPath(Common::String path) {
if (path.size() && (path.lastChar() == '/' || path.lastChar() == '\\')) path.deleteLastChar();
if (!path.empty()) {
for (int i = path.size() - 1; i >= 0; --i)
if (i == 0 || path[i] == '/' || path[i] == '\\') {
path.erase(i);
break;
}
}
if (path.size() && path.lastChar() != '/' && path.lastChar() != '\\') path += '/';
return path;
}
void IndexPageHandler::addItem(Common::String &content, const Common::String &itemTemplate, bool isDirectory, Common::String path, Common::String name, Common::String size) {
Common::String item = itemTemplate;
replace(item, "{link}", (isDirectory ? "files?path=" : "download?path=") + path);
replace(item, "{name}", name);
replace(item, "{size}", size);
content += item;
}
bool IndexPageHandler::listDirectory(Common::String path, Common::String &content, const Common::String &itemTemplate) {
if (path == "" || path == "/") {
addItem(content, itemTemplate, true, "/root/", _("File system root"));
addItem(content, itemTemplate, true, "/saves/", _("Saved games"));
return true;
}
Common::String prefixToRemove = "", prefixToAdd = "";
if (!transformPath(path, prefixToRemove, prefixToAdd)) return false;
Common::FSNode node = Common::FSNode(path);
if (path == "/") node = node.getParent(); // absolute root
if (!node.isDirectory()) return false;
// list directory
Common::FSList _nodeContent;
if (!node.getChildren(_nodeContent, Common::FSNode::kListAll, false)) // do not show hidden files
_nodeContent.clear();
else
Common::sort(_nodeContent.begin(), _nodeContent.end());
// add parent directory link
{
Common::String filePath = path;
if (filePath.hasPrefix(prefixToRemove))
filePath.erase(0, prefixToRemove.size());
if (filePath == "" || filePath == "/" || filePath == "\\")
filePath = "/";
else
filePath = parentPath(prefixToAdd + filePath);
addItem(content, itemTemplate, true, filePath, _("Parent directory"));
}
// fill the content
for (Common::FSList::iterator i = _nodeContent.begin(); i != _nodeContent.end(); ++i) {
Common::String name = i->getDisplayName();
if (i->isDirectory()) name += "/";
Common::String filePath = i->getPath();
if (filePath.hasPrefix(prefixToRemove))
filePath.erase(0, prefixToRemove.size());
filePath = prefixToAdd + filePath;
addItem(content, itemTemplate, i->isDirectory(), filePath, name);
}
return true;
}
bool IndexPageHandler::transformPath(Common::String &path, Common::String &prefixToRemove, Common::String &prefixToAdd) {
// <path> is not empty, but could lack the trailing slash
if (path.lastChar() != '/' && path.lastChar() != '\\') path += '/';
if (path.hasPrefix("/root")) {
prefixToAdd = "/root/";
prefixToRemove = "";
path.erase(0, 5);
if (path == "") path = "/"; // absolute root is '/'
if (path != "/") path.deleteChar(0); // if that was "/root/ab/c", it becomes "/ab/c", but we need "ab/c"
return true;
}
if (path.hasPrefix("/saves")) {
prefixToAdd = "/saves/";
// determine savepath (prefix to remove)
DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager());
prefixToRemove = (manager ? manager->concatWithSavesPath("") : ConfMan.get("savepath"));
if (prefixToRemove.size() && prefixToRemove.lastChar() != '/' && prefixToRemove.lastChar() != '\\')
prefixToRemove += '/';
path.erase(0, 6);
if (path.size() && (path[0] == '/' || path[0] == '\\'))
path.deleteChar(0);
path = prefixToRemove + path;
return true;
}
return false;
}
/// public
void IndexPageHandler::addPathHandler(LocalWebserver &server) {
// we can't use LocalServer yet, because IndexPageHandler is created while LocalWebserver is created
// (thus no _instance is available and it causes stack overflow)
server.addPathHandler("/", new Common::Callback<IndexPageHandler, Client &>(this, &IndexPageHandler::handle));
server.addPathHandler("/files", new Common::Callback<IndexPageHandler, Client &>(this, &IndexPageHandler::handleFiles));
Common::ArchiveMemberList fileList = listArchive();
for (Common::ArchiveMemberList::iterator it = fileList.begin(); it != fileList.end(); ++it) {
Common::ArchiveMember const &m = **it;
if (m.getName() == INDEX_PAGE_NAME) continue;
if (m.getName() == FILES_PAGE_NAME) continue;
server.addPathHandler("/" + m.getName(), new Common::Callback<IndexPageHandler, Client &>(this, &IndexPageHandler::handleResource));
}
}
Common::String IndexPageHandler::code() { return _code; }
/// utils
Common::Archive *IndexPageHandler::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 IndexPageHandler::listArchive() {
Common::ArchiveMemberList resultList;
Common::Archive *zipArchive = getZipArchive();
if (zipArchive) {
zipArchive->listMembers(resultList);
delete zipArchive;
}
return resultList;
}
Common::SeekableReadStream *IndexPageHandler::getArchiveFile(Common::String name) {
Common::SeekableReadStream *result = nullptr;
Common::Archive *zipArchive = getZipArchive();
if (zipArchive) {
const Common::ArchiveMemberPtr ptr = zipArchive->getMember(name);
result = ptr->createReadStream();
delete zipArchive;
}
return result;
}
Common::String IndexPageHandler::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;
}
} // End of namespace Networking

View file

@ -42,7 +42,9 @@ namespace Networking {
LocalWebserver::LocalWebserver(): _set(nullptr), _serverSocket(nullptr), _timerStarted(false), LocalWebserver::LocalWebserver(): _set(nullptr), _serverSocket(nullptr), _timerStarted(false),
_stopOnIdle(false), _clients(0), _idlingFrames(0) { _stopOnIdle(false), _clients(0), _idlingFrames(0) {
_indexPageHandler.addPathHandler(*this); addPathHandler("/", _indexPageHandler.getHandler());
addPathHandler("/files", _filesPageHandler.getHandler());
_defaultHandler = _resourceHandler.getHandler();
} }
LocalWebserver::~LocalWebserver() { LocalWebserver::~LocalWebserver() {
@ -142,7 +144,7 @@ void LocalWebserver::stop() {
void LocalWebserver::stopOnIdle() { _stopOnIdle = true; } void LocalWebserver::stopOnIdle() { _stopOnIdle = true; }
void LocalWebserver::addPathHandler(Common::String path, ClientHandler handler) { void LocalWebserver::addPathHandler(Common::String path, ClientHandlerCallback handler) {
if (_pathHandlers.contains(path)) warning("LocalWebserver::addPathHandler: path already had a handler"); if (_pathHandlers.contains(path)) warning("LocalWebserver::addPathHandler: path already had a handler");
_pathHandlers[path] = handler; _pathHandlers[path] = handler;
} }
@ -199,7 +201,9 @@ void LocalWebserver::handleClient(uint32 i) {
//if GET, check whether we know a handler for such URL //if GET, check whether we know a handler for such URL
//if PUT, check whether we know a handler for that URL //if PUT, check whether we know a handler for that URL
if (_pathHandlers.contains(_client[i].path())) if (_pathHandlers.contains(_client[i].path()))
(*_pathHandlers[_client[i].path()])(_client[i]); (*_pathHandlers[_client[i].path()])(_client[i]);
else if (_defaultHandler)
(*_defaultHandler)(_client[i]); //try default handler
if (_client[i].state() == BEING_HANDLED || _client[i].state() == INVALID) break; if (_client[i].state() == BEING_HANDLED || _client[i].state() == INVALID) break;

View file

@ -24,8 +24,10 @@
#define BACKENDS_NETWORKING_SDL_NET_LOCALWEBSERVER_H #define BACKENDS_NETWORKING_SDL_NET_LOCALWEBSERVER_H
#include "backends/networking/sdl_net/client.h" #include "backends/networking/sdl_net/client.h"
#include "backends/networking/sdl_net/indexpagehandler.h" #include "backends/networking/sdl_net/handlers/basehandler.h"
#include "common/callback.h" #include "backends/networking/sdl_net/handlers/filespagehandler.h"
#include "backends/networking/sdl_net/handlers/indexpagehandler.h"
#include "backends/networking/sdl_net/handlers/resourcehandler.h"
#include "common/hash-str.h" #include "common/hash-str.h"
#include "common/mutex.h" #include "common/mutex.h"
#include "common/singleton.h" #include "common/singleton.h"
@ -46,8 +48,6 @@ class LocalWebserver : public Common::Singleton<LocalWebserver> {
static const uint32 SERVER_PORT = 12345; static const uint32 SERVER_PORT = 12345;
static const uint32 MAX_CONNECTIONS = 10; static const uint32 MAX_CONNECTIONS = 10;
typedef Common::BaseCallback<Client &> *ClientHandler;
friend void localWebserverTimer(void *); //calls handle() friend void localWebserverTimer(void *); //calls handle()
SDLNet_SocketSet _set; SDLNet_SocketSet _set;
@ -55,8 +55,11 @@ class LocalWebserver : public Common::Singleton<LocalWebserver> {
Client _client[MAX_CONNECTIONS]; Client _client[MAX_CONNECTIONS];
int _clients; int _clients;
bool _timerStarted, _stopOnIdle; bool _timerStarted, _stopOnIdle;
Common::HashMap<Common::String, ClientHandler> _pathHandlers; Common::HashMap<Common::String, ClientHandlerCallback> _pathHandlers;
ClientHandlerCallback _defaultHandler;
IndexPageHandler _indexPageHandler; IndexPageHandler _indexPageHandler;
FilesPageHandler _filesPageHandler;
ResourceHandler _resourceHandler;
uint32 _idlingFrames; uint32 _idlingFrames;
Common::Mutex _handleMutex; Common::Mutex _handleMutex;
Common::String _address; Common::String _address;
@ -74,7 +77,7 @@ public:
void start(); void start();
void stop(); void stop();
void stopOnIdle(); void stopOnIdle();
void addPathHandler(Common::String path, ClientHandler handler); void addPathHandler(Common::String path, ClientHandlerCallback handler);
void removePathHandler(Common::String path); void removePathHandler(Common::String path);
Common::String getAddress(); Common::String getAddress();

Binary file not shown.