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