1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #define FORBIDDEN_SYMBOL_ALLOW_ALL
24
25 #include "backends/networking/sdl_net/localwebserver.h"
26 #include "backends/networking/sdl_net/getclienthandler.h"
27 #include "common/memstream.h"
28 #include "common/str.h"
29 #include "common/system.h"
30 #include "common/timer.h"
31 #include "common/translation.h"
32 #include <SDL_net.h>
33 #include <common/config-manager.h>
34
35 #ifdef POSIX
36 #include <errno.h>
37 #include <unistd.h>
38 #include <arpa/inet.h>
39 #include <net/if.h>
40 #include <netinet/in.h>
41 #include <sys/ioctl.h>
42 #include <sys/socket.h>
43 #include <sys/types.h>
44
45 #ifndef SIOCGIFCONF
46 #include <sys/sockio.h>
47 #endif
48
49 #ifndef _SIZEOF_ADDR_IFREQ
50 #define _SIZEOF_ADDR_IFREQ sizeof
51 #endif
52
53 #define LSSDP_BUFFER_LEN 2048
54 #endif
55
56 namespace Common {
57 class MemoryReadWriteStream;
58
59 DECLARE_SINGLETON(Networking::LocalWebserver);
60
61 }
62
63 namespace Networking {
64
LocalWebserver()65 LocalWebserver::LocalWebserver(): _set(nullptr), _serverSocket(nullptr), _timerStarted(false),
66 _stopOnIdle(false), _minimalMode(false), _clients(0), _idlingFrames(0), _serverPort(DEFAULT_SERVER_PORT) {
67 addPathHandler("/", &_indexPageHandler);
68 addPathHandler("/files", &_filesPageHandler);
69 addPathHandler("/create", &_createDirectoryHandler);
70 addPathHandler("/download", &_downloadFileHandler);
71 addPathHandler("/upload", &_uploadFileHandler);
72 addPathHandler("/list", &_listAjaxHandler);
73 addPathHandler("/filesAJAX", &_filesAjaxPageHandler);
74 _defaultHandler = &_resourceHandler;
75 }
76
~LocalWebserver()77 LocalWebserver::~LocalWebserver() {
78 stop();
79 }
80
localWebserverTimer(void * ignored)81 void localWebserverTimer(void *ignored) {
82 LocalServer.handle();
83 }
84
startTimer(int interval)85 void LocalWebserver::startTimer(int interval) {
86 Common::TimerManager *manager = g_system->getTimerManager();
87 if (manager->installTimerProc(localWebserverTimer, interval, 0, "Networking::LocalWebserver's Timer")) {
88 _timerStarted = true;
89 } else {
90 warning("Failed to install Networking::LocalWebserver's timer");
91 }
92 }
93
stopTimer()94 void LocalWebserver::stopTimer() {
95 Common::TimerManager *manager = g_system->getTimerManager();
96 manager->removeTimerProc(localWebserverTimer);
97 _timerStarted = false;
98 }
99
start(bool useMinimalMode)100 void LocalWebserver::start(bool useMinimalMode) {
101 _handleMutex.lock();
102 _serverPort = getPort();
103 _stopOnIdle = false;
104 if (_timerStarted) {
105 _handleMutex.unlock();
106 return;
107 }
108 _minimalMode = useMinimalMode;
109 startTimer();
110
111 // Create a listening TCP socket
112 IPaddress ip;
113 if (SDLNet_ResolveHost(&ip, NULL, _serverPort) == -1) {
114 error("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError());
115 }
116
117 resolveAddress(&ip);
118
119 _serverSocket = SDLNet_TCP_Open(&ip);
120 if (!_serverSocket) {
121 warning("LocalWebserver: SDLNet_TCP_Open: %s", SDLNet_GetError());
122 stopTimer();
123 g_system->displayMessageOnOSD(_("Failed to start local webserver.\nCheck whether selected port is not used by another application and try again."));
124 _handleMutex.unlock();
125 return;
126 }
127
128 // Create a socket set
129 _set = SDLNet_AllocSocketSet(MAX_CONNECTIONS + 1); //one more for our server socket
130 if (!_set) {
131 error("LocalWebserver: SDLNet_AllocSocketSet: %s\n", SDLNet_GetError());
132 }
133
134 int numused = SDLNet_TCP_AddSocket(_set, _serverSocket);
135 if (numused == -1) {
136 error("LocalWebserver: SDLNet_AddSocket: %s\n", SDLNet_GetError());
137 }
138 _handleMutex.unlock();
139 }
140
stop()141 void LocalWebserver::stop() {
142 _handleMutex.lock();
143 if (_timerStarted)
144 stopTimer();
145
146 if (_serverSocket) {
147 SDLNet_TCP_Close(_serverSocket);
148 _serverSocket = nullptr;
149 }
150
151 for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
152 _client[i].close();
153
154 _clients = 0;
155
156 if (_set) {
157 SDLNet_FreeSocketSet(_set);
158 _set = nullptr;
159 }
160 _handleMutex.unlock();
161 }
162
stopOnIdle()163 void LocalWebserver::stopOnIdle() { _stopOnIdle = true; }
164
addPathHandler(Common::String path,BaseHandler * handler)165 void LocalWebserver::addPathHandler(Common::String path, BaseHandler *handler) {
166 if (_pathHandlers.contains(path))
167 warning("LocalWebserver::addPathHandler: path already had a handler");
168 _pathHandlers[path] = handler;
169 }
170
getAddress()171 Common::String LocalWebserver::getAddress() { return _address; }
172
indexPageHandler()173 IndexPageHandler &LocalWebserver::indexPageHandler() { return _indexPageHandler; }
174
isRunning()175 bool LocalWebserver::isRunning() {
176 bool result = false;
177 _handleMutex.lock();
178 result = _timerStarted;
179 _handleMutex.unlock();
180 return result;
181 }
182
getPort()183 uint32 LocalWebserver::getPort() {
184 #ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE
185 if (ConfMan.hasKey("local_server_port"))
186 return ConfMan.getInt("local_server_port");
187 #endif
188 return DEFAULT_SERVER_PORT;
189 }
190
handle()191 void LocalWebserver::handle() {
192 _handleMutex.lock();
193 int numready = SDLNet_CheckSockets(_set, 0);
194 if (numready == -1) {
195 error("LocalWebserver: SDLNet_CheckSockets: %s\n", SDLNet_GetError());
196 } else if (numready) {
197 acceptClient();
198 }
199
200 for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
201 handleClient(i);
202
203 _clients = 0;
204 for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
205 if (_client[i].state() != INVALID)
206 ++_clients;
207
208 if (_clients == 0)
209 ++_idlingFrames;
210 else
211 _idlingFrames = 0;
212
213 if (_idlingFrames > FRAMES_PER_SECOND && _stopOnIdle) {
214 _handleMutex.unlock();
215 stop();
216 return;
217 }
218
219 _handleMutex.unlock();
220 }
221
handleClient(uint32 i)222 void LocalWebserver::handleClient(uint32 i) {
223 switch (_client[i].state()) {
224 case INVALID:
225 return;
226 case READING_HEADERS:
227 _client[i].readHeaders();
228 break;
229 case READ_HEADERS: {
230 // decide what to do next with that client
231 // check whether we know a handler for such URL
232 BaseHandler *handler = nullptr;
233 if (_pathHandlers.contains(_client[i].path())) {
234 handler = _pathHandlers[_client[i].path()];
235 } else {
236 // try default handler
237 handler = _defaultHandler;
238 }
239
240 // if server's in "minimal mode", only handlers which support it are used
241 if (handler && (!_minimalMode || handler->minimalModeSupported()))
242 handler->handle(_client[i]);
243
244 if (_client[i].state() == BEING_HANDLED || _client[i].state() == INVALID)
245 break;
246
247 // if no handler, answer with default BAD REQUEST
248 }
249 // fall through
250
251 case BAD_REQUEST:
252 setClientGetHandler(_client[i], "<html><head><title>ScummVM - Bad Request</title></head><body>BAD REQUEST</body></html>", 400);
253 break;
254 case BEING_HANDLED:
255 _client[i].handle();
256 break;
257 }
258 }
259
acceptClient()260 void LocalWebserver::acceptClient() {
261 if (!SDLNet_SocketReady(_serverSocket))
262 return;
263
264 TCPsocket client = SDLNet_TCP_Accept(_serverSocket);
265 if (!client)
266 return;
267
268 if (_clients == MAX_CONNECTIONS) { //drop the connection
269 SDLNet_TCP_Close(client);
270 return;
271 }
272
273 ++_clients;
274 for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
275 if (_client[i].state() == INVALID) {
276 _client[i].open(_set, client);
277 break;
278 }
279 }
280
resolveAddress(void * ipAddress)281 void LocalWebserver::resolveAddress(void *ipAddress) {
282 IPaddress *ip = (IPaddress *)ipAddress;
283
284 // not resolved
285 _address = Common::String::format("http://127.0.0.1:%u/ (unresolved)", _serverPort);
286
287 // default way (might work everywhere, surely works on Windows)
288 const char *name = SDLNet_ResolveIP(ip);
289 if (name == NULL) {
290 warning("LocalWebserver: SDLNet_ResolveIP: %s", SDLNet_GetError());
291 } else {
292 IPaddress localIp;
293 if (SDLNet_ResolveHost(&localIp, name, _serverPort) == -1) {
294 warning("LocalWebserver: SDLNet_ResolveHost: %s", SDLNet_GetError());
295 } else {
296 _address = Common::String::format(
297 "http://%u.%u.%u.%u:%u/",
298 localIp.host & 0xFF, (localIp.host >> 8) & 0xFF, (localIp.host >> 16) & 0xFF, (localIp.host >> 24) & 0xFF,
299 _serverPort
300 );
301 }
302 }
303
304 // check that our trick worked
305 if (_address.contains("/127.0.0.1:") || _address.contains("localhost") || _address.contains("/0.0.0.0:"))
306 warning("LocalWebserver: Failed to resolve IP with the default way");
307 else
308 return;
309
310 // if not - try platform-specific
311 #ifdef POSIX
312 void *tmpAddrPtr = NULL;
313
314 int fd = socket(AF_INET, SOCK_DGRAM, 0);
315 if (fd < 0) {
316 warning("LocalWebserver: failed to create socket: %s (%d)", strerror(errno), errno);
317 } else {
318 // get ifconfig
319 char buffer[LSSDP_BUFFER_LEN] = {};
320 struct ifconf ifc;
321 ifc.ifc_len = sizeof(buffer);
322 ifc.ifc_buf = (caddr_t) buffer;
323
324 if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) {
325 warning("LocalWebserver: ioctl SIOCGIFCONF failed: %s (%d)", strerror(errno), errno);
326 } else {
327 struct ifreq *i;
328 for (size_t index = 0; index < (size_t)ifc.ifc_len; index += _SIZEOF_ADDR_IFREQ(*i)) {
329 i = (struct ifreq *)(buffer + index);
330
331 Common::String addr;
332
333 // IPv4
334 if (i->ifr_addr.sa_family == AF_INET) {
335 tmpAddrPtr = &((struct sockaddr_in *)&i->ifr_addr)->sin_addr;
336 char addressBuffer[INET_ADDRSTRLEN];
337 inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
338 debug(9, "%s IP Address %s", i->ifr_name, addressBuffer);
339 addr = addressBuffer;
340 }
341
342 // IPv6
343 /*
344 if (i->ifr_addr.sa_family == AF_INET6) {
345 tmpAddrPtr = &((struct sockaddr_in6 *)&i->ifr_addr)->sin6_addr;
346 char addressBuffer[INET6_ADDRSTRLEN];
347 inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
348 debug(9, "%s IP Address %s", i->ifr_name, addressBuffer);
349 addr = addressBuffer;
350 }
351 */
352
353 if (addr.empty())
354 continue;
355
356 // ignored IPv4 addresses
357 if (addr.equals("127.0.0.1") || addr.equals("0.0.0.0") || addr.equals("localhost"))
358 continue;
359
360 // ignored IPv6 addresses
361 /*
362 if (addr.equals("::1"))
363 continue;
364 */
365
366 // use the address found
367 _address = "http://" + addr + Common::String::format(":%u/", _serverPort);
368 }
369 }
370
371 // close socket
372 if (close(fd) != 0) {
373 warning("LocalWebserver: failed to close socket [fd %d]: %s (%d)", fd, strerror(errno), errno);
374 }
375 }
376 #endif
377 }
378
setClientGetHandler(Client & client,Common::String response,long code,const char * mimeType)379 void LocalWebserver::setClientGetHandler(Client &client, Common::String response, long code, const char *mimeType) {
380 byte *data = new byte[response.size()];
381 memcpy(data, response.c_str(), response.size());
382 Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, response.size(), DisposeAfterUse::YES);
383 setClientGetHandler(client, stream, code, mimeType);
384 }
385
setClientGetHandler(Client & client,Common::SeekableReadStream * responseStream,long code,const char * mimeType)386 void LocalWebserver::setClientGetHandler(Client &client, Common::SeekableReadStream *responseStream, long code, const char *mimeType) {
387 GetClientHandler *handler = new GetClientHandler(responseStream);
388 handler->setResponseCode(code);
389 if (mimeType)
390 handler->setHeader("Content-Type", mimeType);
391 client.setHandler(handler);
392 }
393
setClientRedirectHandler(Client & client,Common::String response,Common::String location,const char * mimeType)394 void LocalWebserver::setClientRedirectHandler(Client &client, Common::String response, Common::String location, const char *mimeType) {
395 byte *data = new byte[response.size()];
396 memcpy(data, response.c_str(), response.size());
397 Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, response.size(), DisposeAfterUse::YES);
398 setClientRedirectHandler(client, stream, location, mimeType);
399 }
400
setClientRedirectHandler(Client & client,Common::SeekableReadStream * responseStream,Common::String location,const char * mimeType)401 void LocalWebserver::setClientRedirectHandler(Client &client, Common::SeekableReadStream *responseStream, Common::String location, const char *mimeType) {
402 GetClientHandler *handler = new GetClientHandler(responseStream);
403 handler->setResponseCode(302); //redirect
404 handler->setHeader("Location", location);
405 if (mimeType)
406 handler->setHeader("Content-Type", mimeType);
407 client.setHandler(handler);
408 }
409
410 namespace {
hexDigit(char c)411 int hexDigit(char c) {
412 if ('0' <= c && c <= '9') return c - '0';
413 if ('A' <= c && c <= 'F') return c - 'A' + 10;
414 if ('a' <= c && c <= 'f') return c - 'a' + 10;
415 return -1;
416 }
417 }
418
urlDecode(Common::String value)419 Common::String LocalWebserver::urlDecode(Common::String value) {
420 Common::String result = "";
421 uint32 size = value.size();
422 for (uint32 i = 0; i < size; ++i) {
423 if (value[i] == '+') {
424 result += ' ';
425 continue;
426 }
427
428 if (value[i] == '%' && i + 2 < size) {
429 int d1 = hexDigit(value[i + 1]);
430 int d2 = hexDigit(value[i + 2]);
431 if (0 <= d1 && d1 < 16 && 0 <= d2 && d2 < 16) {
432 result += (char)(d1 * 16 + d2);
433 i = i + 2;
434 continue;
435 }
436 }
437
438 result += value[i];
439 }
440 return result;
441 }
442
443 namespace {
isQueryUnreserved(char c)444 bool isQueryUnreserved(char c) {
445 return (
446 ('0' <= c && c <= '9') ||
447 ('A' <= c && c <= 'Z') ||
448 ('a' <= c && c <= 'z') ||
449 c == '-' || c == '_' || c == '.' || c == '!' ||
450 c == '~' || c == '*' || c == '\'' || c == '(' || c == ')'
451 );
452 }
453 }
454
urlEncodeQueryParameterValue(Common::String value)455 Common::String LocalWebserver::urlEncodeQueryParameterValue(Common::String value) {
456 //OK chars = alphanum | "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
457 //reserved for query are ";", "/", "?", ":", "@", "&", "=", "+", ","
458 //that means these must be encoded too or otherwise they could malform the query
459 Common::String result = "";
460 char hexChar[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
461 for (uint32 i = 0; i < value.size(); ++i) {
462 char c = value[i];
463 if (isQueryUnreserved(c))
464 result += c;
465 else {
466 result += '%';
467 result += hexChar[(c >> 4) & 0xF];
468 result += hexChar[c & 0xF];
469 }
470 }
471 return result;
472 }
473
474 } // End of namespace Networking
475