scummvm/backends/networking/sdl_net/localwebserver.cpp
Alexander Tkachev 126fe9c845 CLOUD: Add "minimal mode" in LocalWebserver
StorageWizardDialog now runs LocalWebserver in "minimal mode" for
security reasons. In this mode server uses only those handlers which
state to support it.

There are two handlers which support minimal mode: IndexPageHandler
(which handles `code` requests needed by StorageWizardDialog) and
ResourceHandler (which provides inner resources like `style.css` or
`logo.png` from `wwwroot.zip` archive).
2016-08-24 16:07:55 +06:00

446 lines
12 KiB
C++

/* 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.
*
*/
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "backends/networking/sdl_net/localwebserver.h"
#include "backends/networking/sdl_net/getclienthandler.h"
#include "common/memstream.h"
#include "common/str.h"
#include "common/system.h"
#include "common/timer.h"
#include "common/translation.h"
#include <SDL/SDL_net.h>
#include <common/config-manager.h>
#ifdef POSIX
#include <sys/types.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
namespace Common {
class MemoryReadWriteStream;
DECLARE_SINGLETON(Networking::LocalWebserver);
}
namespace Networking {
LocalWebserver::LocalWebserver(): _set(nullptr), _serverSocket(nullptr), _timerStarted(false),
_stopOnIdle(false), _minimalMode(false), _clients(0), _idlingFrames(0), _serverPort(DEFAULT_SERVER_PORT) {
addPathHandler("/", &_indexPageHandler);
addPathHandler("/files", &_filesPageHandler);
addPathHandler("/create", &_createDirectoryHandler);
addPathHandler("/download", &_downloadFileHandler);
addPathHandler("/upload", &_uploadFileHandler);
addPathHandler("/list", &_listAjaxHandler);
addPathHandler("/filesAJAX", &_filesAjaxPageHandler);
_defaultHandler = &_resourceHandler;
}
LocalWebserver::~LocalWebserver() {
stop();
}
void localWebserverTimer(void *ignored) {
LocalServer.handle();
}
void LocalWebserver::startTimer(int interval) {
Common::TimerManager *manager = g_system->getTimerManager();
if (manager->installTimerProc(localWebserverTimer, interval, 0, "Networking::LocalWebserver's Timer")) {
_timerStarted = true;
} else {
warning("Failed to install Networking::LocalWebserver's timer");
}
}
void LocalWebserver::stopTimer() {
Common::TimerManager *manager = g_system->getTimerManager();
manager->removeTimerProc(localWebserverTimer);
_timerStarted = false;
}
void LocalWebserver::start(bool useMinimalMode) {
_handleMutex.lock();
_serverPort = getPort();
_stopOnIdle = false;
if (_timerStarted) {
_handleMutex.unlock();
return;
}
_minimalMode = useMinimalMode;
startTimer();
// Create a listening TCP socket
IPaddress ip;
if (SDLNet_ResolveHost(&ip, NULL, _serverPort) == -1) {
error("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError());
}
resolveAddress(&ip);
_serverSocket = SDLNet_TCP_Open(&ip);
if (!_serverSocket) {
warning("LocalWebserver: SDLNet_TCP_Open: %s", SDLNet_GetError());
stopTimer();
g_system->displayMessageOnOSD(_("Failed to start local webserver.\nCheck whether selected port is not used by another application and try again."));
_handleMutex.unlock();
return;
}
// Create a socket set
_set = SDLNet_AllocSocketSet(MAX_CONNECTIONS + 1); //one more for our server socket
if (!_set) {
error("LocalWebserver: SDLNet_AllocSocketSet: %s\n", SDLNet_GetError());
}
int numused = SDLNet_TCP_AddSocket(_set, _serverSocket);
if (numused == -1) {
error("LocalWebserver: SDLNet_AddSocket: %s\n", SDLNet_GetError());
}
_handleMutex.unlock();
}
void LocalWebserver::stop() {
_handleMutex.lock();
if (_timerStarted)
stopTimer();
if (_serverSocket) {
SDLNet_TCP_Close(_serverSocket);
_serverSocket = nullptr;
}
for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
_client[i].close();
_clients = 0;
if (_set) {
SDLNet_FreeSocketSet(_set);
_set = nullptr;
}
_handleMutex.unlock();
}
void LocalWebserver::stopOnIdle() { _stopOnIdle = true; }
void LocalWebserver::addPathHandler(Common::String path, BaseHandler *handler) {
if (_pathHandlers.contains(path))
warning("LocalWebserver::addPathHandler: path already had a handler");
_pathHandlers[path] = handler;
}
Common::String LocalWebserver::getAddress() { return _address; }
IndexPageHandler &LocalWebserver::indexPageHandler() { return _indexPageHandler; }
bool LocalWebserver::isRunning() {
bool result = false;
_handleMutex.lock();
result = _timerStarted;
_handleMutex.unlock();
return result;
}
uint32 LocalWebserver::getPort() {
#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE
if (ConfMan.hasKey("local_server_port"))
return ConfMan.getInt("local_server_port");
#endif
return DEFAULT_SERVER_PORT;
}
void LocalWebserver::handle() {
_handleMutex.lock();
int numready = SDLNet_CheckSockets(_set, 0);
if (numready == -1) {
error("LocalWebserver: SDLNet_CheckSockets: %s\n", SDLNet_GetError());
} else if (numready) {
acceptClient();
}
for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
handleClient(i);
_clients = 0;
for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
if (_client[i].state() != INVALID)
++_clients;
if (_clients == 0)
++_idlingFrames;
else
_idlingFrames = 0;
if (_idlingFrames > FRAMES_PER_SECOND && _stopOnIdle) {
_handleMutex.unlock();
stop();
return;
}
_handleMutex.unlock();
}
void LocalWebserver::handleClient(uint32 i) {
switch (_client[i].state()) {
case INVALID:
return;
case READING_HEADERS:
_client[i].readHeaders();
break;
case READ_HEADERS: {
// decide what to do next with that client
// check whether we know a handler for such URL
BaseHandler *handler = nullptr;
if (_pathHandlers.contains(_client[i].path())) {
handler = _pathHandlers[_client[i].path()];
} else {
// try default handler
handler = _defaultHandler;
}
// if server's in "minimal mode", only handlers which support it are used
if (handler && (!_minimalMode || handler->minimalModeSupported()))
handler->handle(_client[i]);
if (_client[i].state() == BEING_HANDLED || _client[i].state() == INVALID)
break;
// if no handler, answer with default BAD REQUEST
// fallthrough
}
case BAD_REQUEST:
setClientGetHandler(_client[i], "<html><head><title>ScummVM - Bad Request</title></head><body>BAD REQUEST</body></html>", 400);
break;
case BEING_HANDLED:
_client[i].handle();
break;
}
}
void LocalWebserver::acceptClient() {
if (!SDLNet_SocketReady(_serverSocket))
return;
TCPsocket client = SDLNet_TCP_Accept(_serverSocket);
if (!client)
return;
if (_clients == MAX_CONNECTIONS) { //drop the connection
SDLNet_TCP_Close(client);
return;
}
++_clients;
for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
if (_client[i].state() == INVALID) {
_client[i].open(_set, client);
break;
}
}
void LocalWebserver::resolveAddress(void *ipAddress) {
IPaddress *ip = (IPaddress *)ipAddress;
// not resolved
_address = Common::String::format("http://127.0.0.1:%u/ (unresolved)", _serverPort);
// default way (might work everywhere, surely works on Windows)
const char *name = SDLNet_ResolveIP(ip);
if (name == NULL) {
warning("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError());
} else {
IPaddress localIp;
if (SDLNet_ResolveHost(&localIp, name, _serverPort) == -1) {
warning("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError());
} else {
_address = Common::String::format(
"http://%u.%u.%u.%u:%u/",
localIp.host & 0xFF, (localIp.host >> 8) & 0xFF, (localIp.host >> 16) & 0xFF, (localIp.host >> 24) & 0xFF,
_serverPort
);
}
}
// check that our trick worked
if (_address.contains("/127.0.0.1:") || _address.contains("localhost") || _address.contains("/0.0.0.0:"))
warning("LocalWebserver: Failed to resolve IP with the default way");
else
return;
// if not - try platform-specific
#ifdef POSIX
struct ifaddrs *ifAddrStruct = NULL;
void *tmpAddrPtr = NULL;
getifaddrs(&ifAddrStruct);
for (struct ifaddrs *i = ifAddrStruct; i != NULL; i = i->ifa_next) {
if (!i->ifa_addr) {
continue;
}
Common::String addr;
// IPv4
if (i->ifa_addr->sa_family == AF_INET) {
tmpAddrPtr = &((struct sockaddr_in *)i->ifa_addr)->sin_addr;
char addressBuffer[INET_ADDRSTRLEN];
inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
debug(9, "%s IP Address %s", i->ifa_name, addressBuffer);
addr = addressBuffer;
}
// IPv6
/*
if (i->ifa_addr->sa_family == AF_INET6) {
tmpAddrPtr = &((struct sockaddr_in6 *)i->ifa_addr)->sin6_addr;
char addressBuffer[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
debug(9, "%s IP Address %s", i->ifa_name, addressBuffer);
addr = addressBuffer;
}
*/
if (addr.empty())
continue;
// ignored IPv4 addresses
if (addr.equals("127.0.0.1") || addr.equals("0.0.0.0") || addr.equals("localhost"))
continue;
// ignored IPv6 addresses
/*
if (addr.equals("::1"))
continue;
*/
// use the address found
_address = "http://" + addr + Common::String::format(":%u/", _serverPort);
}
if (ifAddrStruct != NULL) freeifaddrs(ifAddrStruct);
#endif
}
void LocalWebserver::setClientGetHandler(Client &client, Common::String response, long code, const char *mimeType) {
byte *data = new byte[response.size()];
memcpy(data, response.c_str(), response.size());
Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, response.size(), DisposeAfterUse::YES);
setClientGetHandler(client, stream, code, mimeType);
}
void LocalWebserver::setClientGetHandler(Client &client, Common::SeekableReadStream *responseStream, long code, const char *mimeType) {
GetClientHandler *handler = new GetClientHandler(responseStream);
handler->setResponseCode(code);
if (mimeType)
handler->setHeader("Content-Type", mimeType);
client.setHandler(handler);
}
void LocalWebserver::setClientRedirectHandler(Client &client, Common::String response, Common::String location, const char *mimeType) {
byte *data = new byte[response.size()];
memcpy(data, response.c_str(), response.size());
Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, response.size(), DisposeAfterUse::YES);
setClientRedirectHandler(client, stream, location, mimeType);
}
void LocalWebserver::setClientRedirectHandler(Client &client, Common::SeekableReadStream *responseStream, Common::String location, const char *mimeType) {
GetClientHandler *handler = new GetClientHandler(responseStream);
handler->setResponseCode(302); //redirect
handler->setHeader("Location", location);
if (mimeType)
handler->setHeader("Content-Type", mimeType);
client.setHandler(handler);
}
namespace {
int hexDigit(char c) {
if ('0' <= c && c <= '9') return c - '0';
if ('A' <= c && c <= 'F') return c - 'A' + 10;
if ('a' <= c && c <= 'f') return c - 'a' + 10;
return -1;
}
}
Common::String LocalWebserver::urlDecode(Common::String value) {
Common::String result = "";
uint32 size = value.size();
for (uint32 i = 0; i < size; ++i) {
if (value[i] == '+') {
result += ' ';
continue;
}
if (value[i] == '%' && i + 2 < size) {
int d1 = hexDigit(value[i + 1]);
int d2 = hexDigit(value[i + 2]);
if (0 <= d1 && d1 < 16 && 0 <= d2 && d2 < 16) {
result += (char)(d1 * 16 + d2);
i = i + 2;
continue;
}
}
result += value[i];
}
return result;
}
namespace {
bool isQueryUnreserved(char c) {
return (
('0' <= c && c <= '9') ||
('A' <= c && c <= 'Z') ||
('a' <= c && c <= 'z') ||
c == '-' || c == '_' || c == '.' || c == '!' ||
c == '~' || c == '*' || c == '\'' || c == '(' || c == ')'
);
}
}
Common::String LocalWebserver::urlEncodeQueryParameterValue(Common::String value) {
//OK chars = alphanum | "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
//reserved for query are ";", "/", "?", ":", "@", "&", "=", "+", ","
//that means these must be encoded too or otherwise they could malform the query
Common::String result = "";
char hexChar[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
for (uint32 i = 0; i < value.size(); ++i) {
char c = value[i];
if (isQueryUnreserved(c))
result += c;
else {
result += '%';
result += hexChar[(c >> 4) & 0xF];
result += hexChar[c & 0xF];
}
}
return result;
}
} // End of namespace Networking