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 #include "backends/networking/sdl_net/handlerutils.h"
24 #include "backends/networking/sdl_net/localwebserver.h"
25 #include "backends/saves/default/default-saves.h"
26 #include "common/archive.h"
27 #include "common/config-manager.h"
28 #include "common/file.h"
29 #include "common/translation.h"
30 #include "common/unzip.h"
31
32 namespace Networking {
33
34 #define ARCHIVE_NAME "wwwroot.zip"
35
36 #define INDEX_PAGE_NAME ".index.html"
37
getZipArchive()38 Common::Archive *HandlerUtils::getZipArchive() {
39 // first search in themepath
40 if (ConfMan.hasKey("themepath")) {
41 const Common::FSNode &node = Common::FSNode(ConfMan.get("themepath"));
42 if (node.exists() && node.isReadable() && node.isDirectory()) {
43 Common::FSNode fileNode = node.getChild(ARCHIVE_NAME);
44 if (fileNode.exists() && fileNode.isReadable() && !fileNode.isDirectory()) {
45 Common::SeekableReadStream *const stream = fileNode.createReadStream();
46 Common::Archive *zipArchive = Common::makeZipArchive(stream);
47 if (zipArchive)
48 return zipArchive;
49 }
50 }
51 }
52
53 // then use SearchMan to find it
54 Common::ArchiveMemberList fileList;
55 SearchMan.listMatchingMembers(fileList, ARCHIVE_NAME);
56 for (Common::ArchiveMemberList::iterator it = fileList.begin(); it != fileList.end(); ++it) {
57 Common::ArchiveMember const &m = **it;
58 Common::SeekableReadStream *const stream = m.createReadStream();
59 Common::Archive *zipArchive = Common::makeZipArchive(stream);
60 if (zipArchive)
61 return zipArchive;
62 }
63
64 return nullptr;
65 }
66
listArchive()67 Common::ArchiveMemberList HandlerUtils::listArchive() {
68 Common::ArchiveMemberList resultList;
69 Common::Archive *zipArchive = getZipArchive();
70 if (zipArchive) {
71 zipArchive->listMembers(resultList);
72 delete zipArchive;
73 }
74 return resultList;
75 }
76
getArchiveFile(Common::String name)77 Common::SeekableReadStream *HandlerUtils::getArchiveFile(Common::String name) {
78 Common::SeekableReadStream *result = nullptr;
79 Common::Archive *zipArchive = getZipArchive();
80 if (zipArchive) {
81 const Common::ArchiveMemberPtr ptr = zipArchive->getMember(name);
82 if (ptr.get() == nullptr)
83 return nullptr;
84 result = ptr->createReadStream();
85 delete zipArchive;
86 }
87 return result;
88 }
89
readEverythingFromStream(Common::SeekableReadStream * const stream)90 Common::String HandlerUtils::readEverythingFromStream(Common::SeekableReadStream *const stream) {
91 Common::String result;
92 char buf[1024];
93 uint32 readBytes;
94 while (!stream->eos()) {
95 readBytes = stream->read(buf, 1024);
96 result += Common::String(buf, readBytes);
97 }
98 return result;
99 }
100
normalizePath(const Common::String & path)101 Common::String HandlerUtils::normalizePath(const Common::String &path) {
102 Common::String normalized;
103 bool slash = false;
104 for (uint32 i = 0; i < path.size(); ++i) {
105 char c = path[i];
106 if (c == '\\' || c == '/') {
107 slash = true;
108 continue;
109 }
110
111 if (slash) {
112 normalized += '/';
113 slash = false;
114 }
115
116 if ('A' <= c && c <= 'Z') {
117 normalized += c - 'A' + 'a';
118 } else {
119 normalized += c;
120 }
121 }
122 if (slash) normalized += '/';
123 return normalized;
124 }
125
hasForbiddenCombinations(const Common::String & path)126 bool HandlerUtils::hasForbiddenCombinations(const Common::String &path) {
127 return (path.contains("/../") || path.contains("\\..\\") || path.contains("\\../") || path.contains("/..\\"));
128 }
129
isBlacklisted(const Common::String & path)130 bool HandlerUtils::isBlacklisted(const Common::String &path) {
131 const char *blacklist[] = {
132 "/etc",
133 "/bin",
134 "c:/windows" // just saying: I know guys who install windows on another drives
135 };
136
137 // normalize path
138 Common::String normalized = normalizePath(path);
139
140 uint32 size = sizeof(blacklist) / sizeof(const char *);
141 for (uint32 i = 0; i < size; ++i)
142 if (normalized.hasPrefix(blacklist[i]))
143 return true;
144
145 return false;
146 }
147
hasPermittedPrefix(const Common::String & path)148 bool HandlerUtils::hasPermittedPrefix(const Common::String &path) {
149 // normalize path
150 Common::String normalized = normalizePath(path);
151
152 // prefix for /root/
153 Common::String prefix;
154 if (ConfMan.hasKey("rootpath", "cloud")) {
155 prefix = normalizePath(ConfMan.get("rootpath", "cloud"));
156 if (prefix == "/" || normalized.hasPrefix(prefix))
157 return true;
158 }
159
160 // prefix for /saves/
161 #ifdef USE_LIBCURL
162 DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager());
163 prefix = (manager ? manager->concatWithSavesPath("") : ConfMan.get("savepath"));
164 #else
165 prefix = ConfMan.get("savepath");
166 #endif
167 return normalized.hasPrefix(normalizePath(prefix))
168 || normalizePath(prefix).compareTo(normalized + "/") == 0;
169 }
170
permittedPath(const Common::String path)171 bool HandlerUtils::permittedPath(const Common::String path) {
172 return hasPermittedPrefix(path) && !isBlacklisted(path);
173 }
174
setMessageHandler(Client & client,Common::String message,Common::String redirectTo)175 void HandlerUtils::setMessageHandler(Client &client, Common::String message, Common::String redirectTo) {
176 Common::String response = "<html><head><title>ScummVM</title><meta charset=\"utf-8\"/></head><body>{message}</body></html>";
177
178 // load stylish response page from the archive
179 Common::SeekableReadStream *const stream = getArchiveFile(INDEX_PAGE_NAME);
180 if (stream)
181 response = readEverythingFromStream(stream);
182
183 replace(response, "{message}", message);
184 if (redirectTo.empty())
185 LocalWebserver::setClientGetHandler(client, response);
186 else
187 LocalWebserver::setClientRedirectHandler(client, response, redirectTo);
188 }
189
setFilesManagerErrorMessageHandler(Client & client,Common::String message,Common::String redirectTo)190 void HandlerUtils::setFilesManagerErrorMessageHandler(Client &client, Common::String message, Common::String redirectTo) {
191 setMessageHandler(
192 client,
193 Common::String::format(
194 "%s<br/><a href=\"files%s?path=%s\">%s</a>",
195 message.c_str(),
196 client.queryParameter("ajax") == "true" ? "AJAX" : "",
197 "%2F", //that's encoded "/"
198 Common::convertFromU32String(_("Back to the files manager")).c_str()
199 ),
200 redirectTo
201 );
202 }
203
204 } // End of namespace Networking
205