1 /**
2  * Copyright (c) 2006-2019 LOVE Development Team
3  *
4  * This software is provided 'as-is', without any express or implied
5  * warranty.  In no event will the authors be held liable for any damages
6  * arising from the use of this software.
7  *
8  * Permission is granted to anyone to use this software for any purpose,
9  * including commercial applications, and to alter it and redistribute it
10  * freely, subject to the following restrictions:
11  *
12  * 1. The origin of this software must not be misrepresented; you must not
13  *    claim that you wrote the original software. If you use this software
14  *    in a product, an acknowledgment in the product documentation would be
15  *    appreciated but is not required.
16  * 2. Altered source versions must be plainly marked as such, and must not be
17  *    misrepresented as being the original software.
18  * 3. This notice may not be removed or altered from any source distribution.
19  **/
20 
21 #include <iostream>
22 #include <sstream>
23 #include <algorithm>
24 
25 #include "common/utf8.h"
26 #include "common/b64.h"
27 
28 #include "Filesystem.h"
29 #include "File.h"
30 
31 // PhysFS
32 #include "libraries/physfs/physfs.h"
33 
34 // For great CWD. (Current Working Directory)
35 // Using this instead of boost::filesystem which totally
36 // cramped our style.
37 #ifdef LOVE_WINDOWS
38 #	include <windows.h>
39 #	include <direct.h>
40 #else
41 #	include <sys/param.h>
42 #	include <unistd.h>
43 #endif
44 
45 #ifdef LOVE_IOS
46 #	include "common/ios.h"
47 #endif
48 
49 #include <string>
50 
51 #ifdef LOVE_ANDROID
52 #include <SDL.h>
53 #include "common/android.h"
54 #endif
55 
56 namespace
57 {
getDriveDelim(const std::string & input)58 	size_t getDriveDelim(const std::string &input)
59 	{
60 		for (size_t i = 0; i < input.size(); ++i)
61 			if (input[i] == '/' || input[i] == '\\')
62 				return i;
63 		// Something's horribly wrong
64 		return 0;
65 	}
66 
getDriveRoot(const std::string & input)67 	std::string getDriveRoot(const std::string &input)
68 	{
69 		return input.substr(0, getDriveDelim(input)+1);
70 	}
71 
skipDriveRoot(const std::string & input)72 	std::string skipDriveRoot(const std::string &input)
73 	{
74 		return input.substr(getDriveDelim(input)+1);
75 	}
76 
normalize(const std::string & input)77 	std::string normalize(const std::string &input)
78 	{
79 		std::stringstream out;
80 		bool seenSep = false, isSep = false;
81 		for (size_t i = 0; i < input.size(); ++i)
82 		{
83 			isSep = (input[i] == LOVE_PATH_SEPARATOR[0]);
84 			if (!isSep || !seenSep)
85 				out << input[i];
86 			seenSep = isSep;
87 		}
88 
89 		return out.str();
90 	}
91 
92 }
93 
94 namespace love
95 {
96 namespace filesystem
97 {
98 namespace physfs
99 {
100 
Filesystem()101 Filesystem::Filesystem()
102 	: fused(false)
103 	, fusedSet(false)
104 {
105 	requirePath = {"?.lua", "?/init.lua"};
106 	cRequirePath = {"??"};
107 }
108 
~Filesystem()109 Filesystem::~Filesystem()
110 {
111 	if (PHYSFS_isInit())
112 		PHYSFS_deinit();
113 }
114 
getName() const115 const char *Filesystem::getName() const
116 {
117 	return "love.filesystem.physfs";
118 }
119 
init(const char * arg0)120 void Filesystem::init(const char *arg0)
121 {
122 	if (!PHYSFS_init(arg0))
123 		throw love::Exception("Failed to initialize filesystem: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
124 
125 	// Enable symlinks by default.
126 	setSymlinksEnabled(true);
127 }
128 
setFused(bool fused)129 void Filesystem::setFused(bool fused)
130 {
131 	if (fusedSet)
132 		return;
133 	this->fused = fused;
134 	fusedSet = true;
135 }
136 
isFused() const137 bool Filesystem::isFused() const
138 {
139 	if (!fusedSet)
140 		return false;
141 	return fused;
142 }
143 
setIdentity(const char * ident,bool appendToPath)144 bool Filesystem::setIdentity(const char *ident, bool appendToPath)
145 {
146 	if (!PHYSFS_isInit())
147 		return false;
148 
149 	std::string old_save_path = save_path_full;
150 
151 	// Store the save directory.
152 	save_identity = std::string(ident);
153 
154 	// Generate the relative path to the game save folder.
155 	save_path_relative = std::string(LOVE_APPDATA_PREFIX LOVE_APPDATA_FOLDER LOVE_PATH_SEPARATOR) + save_identity;
156 
157 	// Generate the full path to the game save folder.
158 	save_path_full = std::string(getAppdataDirectory()) + std::string(LOVE_PATH_SEPARATOR);
159 	if (fused)
160 		save_path_full += std::string(LOVE_APPDATA_PREFIX) + save_identity;
161 	else
162 		save_path_full += save_path_relative;
163 
164 	save_path_full = normalize(save_path_full);
165 
166 #ifdef LOVE_ANDROID
167 	if (save_identity == "")
168 		save_identity = "unnamed";
169 
170 	std::string storage_path;
171 	if (isAndroidSaveExternal())
172 		storage_path = SDL_AndroidGetExternalStoragePath();
173 	else
174 		storage_path = SDL_AndroidGetInternalStoragePath();
175 
176 	std::string save_directory = storage_path + "/save";
177 
178 	save_path_full = storage_path + std::string("/save/") + save_identity;
179 
180 	if (!love::android::directoryExists(save_path_full.c_str()) &&
181 			!love::android::mkdir(save_path_full.c_str()))
182 		SDL_Log("Error: Could not create save directory %s!", save_path_full.c_str());
183 #endif
184 
185 	// We now have something like:
186 	// save_identity: game
187 	// save_path_relative: ./LOVE/game
188 	// save_path_full: C:\Documents and Settings\user\Application Data/LOVE/game
189 
190 	// We don't want old read-only save paths to accumulate when we set a new
191 	// identity.
192 	if (!old_save_path.empty())
193 		PHYSFS_unmount(old_save_path.c_str());
194 
195 	// Try to add the save directory to the search path.
196 	// (No error on fail, it means that the path doesn't exist).
197 	PHYSFS_mount(save_path_full.c_str(), nullptr, appendToPath);
198 
199 	// HACK: This forces setupWriteDirectory to be called the next time a file
200 	// is opened for writing - otherwise it won't be called at all if it was
201 	// already called at least once before.
202 	PHYSFS_setWriteDir(nullptr);
203 
204 	return true;
205 }
206 
getIdentity() const207 const char *Filesystem::getIdentity() const
208 {
209 	return save_identity.c_str();
210 }
211 
setSource(const char * source)212 bool Filesystem::setSource(const char *source)
213 {
214 	if (!PHYSFS_isInit())
215 		return false;
216 
217 	// Check whether directory is already set.
218 	if (!game_source.empty())
219 		return false;
220 
221 	std::string new_search_path = source;
222 
223 #ifdef LOVE_ANDROID
224 	if (!love::android::createStorageDirectories())
225 		SDL_Log("Error creating storage directories!");
226 
227 	new_search_path = love::android::getSelectedGameFile();
228 
229 	// try mounting first, if that fails, load to memory and mount
230 	if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1))
231 	{
232 		// PHYSFS cannot yet mount a zip file inside an .apk
233 		SDL_Log("Mounting %s did not work. Loading to memory.",
234 				new_search_path.c_str());
235 		char* game_archive_ptr = NULL;
236 		size_t game_archive_size = 0;
237 		if (!love::android::loadGameArchiveToMemory(
238 					new_search_path.c_str(), &game_archive_ptr,
239 					&game_archive_size))
240 		{
241 			SDL_Log("Failure memory loading archive %s", new_search_path.c_str());
242 			return false;
243 		}
244 		if (!PHYSFS_mountMemory(
245 			    game_archive_ptr, game_archive_size,
246 			    love::android::freeGameArchiveMemory, "archive.zip", "/", 0))
247 		{
248 			SDL_Log("Failure mounting in-memory archive.");
249 			love::android::freeGameArchiveMemory(game_archive_ptr);
250 			return false;
251 		}
252 	}
253 #else
254 	// Add the directory.
255 	if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1))
256 		return false;
257 #endif
258 
259 	// Save the game source.
260 	game_source = new_search_path;
261 
262 	return true;
263 }
264 
getSource() const265 const char *Filesystem::getSource() const
266 {
267 	return game_source.c_str();
268 }
269 
setupWriteDirectory()270 bool Filesystem::setupWriteDirectory()
271 {
272 	if (!PHYSFS_isInit())
273 		return false;
274 
275 	// These must all be set.
276 	if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty())
277 		return false;
278 
279 	// We need to make sure the write directory is created. To do that, we also
280 	// need to make sure all its parent directories are also created.
281 	std::string temp_writedir = getDriveRoot(save_path_full);
282 	std::string temp_createdir = skipDriveRoot(save_path_full);
283 
284 	// On some sandboxed platforms, physfs will break when its write directory
285 	// is the root of the drive and it tries to create a folder (even if the
286 	// folder's path is in a writable location.) If the user's home folder is
287 	// in the save path, we'll try starting from there instead.
288 	if (save_path_full.find(getUserDirectory()) == 0)
289 	{
290 		temp_writedir = getUserDirectory();
291 		temp_createdir = save_path_full.substr(getUserDirectory().length());
292 
293 		// Strip leading '/' characters from the path we want to create.
294 		size_t startpos = temp_createdir.find_first_not_of('/');
295 		if (startpos != std::string::npos)
296 			temp_createdir = temp_createdir.substr(startpos);
297 	}
298 
299 	// Set either '/' or the user's home as a writable directory.
300 	// (We must create the save folder before mounting it).
301 	if (!PHYSFS_setWriteDir(temp_writedir.c_str()))
302 		return false;
303 
304 	// Create the save folder. (We're now "at" either '/' or the user's home).
305 	if (!createDirectory(temp_createdir.c_str()))
306 	{
307 		// Clear the write directory in case of error.
308 		PHYSFS_setWriteDir(nullptr);
309 		return false;
310 	}
311 
312 	// Set the final write directory.
313 	if (!PHYSFS_setWriteDir(save_path_full.c_str()))
314 		return false;
315 
316 	// Add the directory. (Will not be readded if already present).
317 	if (!PHYSFS_mount(save_path_full.c_str(), nullptr, 0))
318 	{
319 		PHYSFS_setWriteDir(nullptr); // Clear the write directory in case of error.
320 		return false;
321 	}
322 
323 	return true;
324 }
325 
mount(const char * archive,const char * mountpoint,bool appendToPath)326 bool Filesystem::mount(const char *archive, const char *mountpoint, bool appendToPath)
327 {
328 	if (!PHYSFS_isInit() || !archive)
329 		return false;
330 
331 	std::string realPath;
332 	std::string sourceBase = getSourceBaseDirectory();
333 
334 	// Check whether the given archive path is in the list of allowed full paths.
335 	auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive);
336 
337 	if (it != allowedMountPaths.end())
338 		realPath = *it;
339 	else if (isFused() && sourceBase.compare(archive) == 0)
340 	{
341 		// Special case: if the game is fused and the archive is the source's
342 		// base directory, mount it even though it's outside of the save dir.
343 		realPath = sourceBase;
344 	}
345 	else
346 	{
347 		// Not allowed for safety reasons.
348 		if (strlen(archive) == 0 || strstr(archive, "..") || strcmp(archive, "/") == 0)
349 			return false;
350 
351 		const char *realDir = PHYSFS_getRealDir(archive);
352 		if (!realDir)
353 			return false;
354 
355 		realPath = realDir;
356 
357 		// Always disallow mounting of files inside the game source, since it
358 		// won't work anyway if the game source is a zipped .love file.
359 		if (realPath.find(game_source) == 0)
360 			return false;
361 
362 		realPath += LOVE_PATH_SEPARATOR;
363 		realPath += archive;
364 	}
365 
366 	if (realPath.length() == 0)
367 		return false;
368 
369 	return PHYSFS_mount(realPath.c_str(), mountpoint, appendToPath) != 0;
370 }
371 
mount(Data * data,const char * archivename,const char * mountpoint,bool appendToPath)372 bool Filesystem::mount(Data *data, const char *archivename, const char *mountpoint, bool appendToPath)
373 {
374 	if (!PHYSFS_isInit())
375 		return false;
376 
377 	if (PHYSFS_mountMemory(data->getData(), data->getSize(), nullptr, archivename, mountpoint, appendToPath) != 0)
378 	{
379 		mountedData[archivename] = data;
380 		return true;
381 	}
382 
383 	return false;
384 }
385 
unmount(const char * archive)386 bool Filesystem::unmount(const char *archive)
387 {
388 	if (!PHYSFS_isInit() || !archive)
389 		return false;
390 
391 	auto datait = mountedData.find(archive);
392 
393 	if (datait != mountedData.end() && PHYSFS_unmount(archive) != 0)
394 	{
395 		mountedData.erase(datait);
396 		return true;
397 	}
398 
399 	std::string realPath;
400 	std::string sourceBase = getSourceBaseDirectory();
401 
402 	// Check whether the given archive path is in the list of allowed full paths.
403 	auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive);
404 
405 	if (it != allowedMountPaths.end())
406 		realPath = *it;
407 	else if (isFused() && sourceBase.compare(archive) == 0)
408 	{
409 		// Special case: if the game is fused and the archive is the source's
410 		// base directory, unmount it even though it's outside of the save dir.
411 		realPath = sourceBase;
412 	}
413 	else
414 	{
415 		// Not allowed for safety reasons.
416 		if (strlen(archive) == 0 || strstr(archive, "..") || strcmp(archive, "/") == 0)
417 			return false;
418 
419 		const char *realDir = PHYSFS_getRealDir(archive);
420 		if (!realDir)
421 			return false;
422 
423 		realPath = realDir;
424 		realPath += LOVE_PATH_SEPARATOR;
425 		realPath += archive;
426 	}
427 
428 	const char *mountPoint = PHYSFS_getMountPoint(realPath.c_str());
429 	if (!mountPoint)
430 		return false;
431 
432 	return PHYSFS_unmount(realPath.c_str()) != 0;
433 }
434 
unmount(Data * data)435 bool Filesystem::unmount(Data *data)
436 {
437 	for (const auto &datapair : mountedData)
438 	{
439 		if (datapair.second.get() == data)
440 		{
441 			std::string archive = datapair.first;
442 			return unmount(archive.c_str());
443 		}
444 	}
445 
446 	return false;
447 }
448 
newFile(const char * filename) const449 love::filesystem::File *Filesystem::newFile(const char *filename) const
450 {
451 	return new File(filename);
452 }
453 
getWorkingDirectory()454 const char *Filesystem::getWorkingDirectory()
455 {
456 	if (cwd.empty())
457 	{
458 #ifdef LOVE_WINDOWS
459 
460 		WCHAR w_cwd[LOVE_MAX_PATH];
461 		_wgetcwd(w_cwd, LOVE_MAX_PATH);
462 		cwd = to_utf8(w_cwd);
463 		replace_char(cwd, '\\', '/');
464 #else
465 		char *cwd_char = new char[LOVE_MAX_PATH];
466 
467 		if (getcwd(cwd_char, LOVE_MAX_PATH))
468 			cwd = cwd_char; // if getcwd fails, cwd_char (and thus cwd) will still be empty
469 
470 		delete [] cwd_char;
471 #endif
472 	}
473 
474 	return cwd.c_str();
475 }
476 
getUserDirectory()477 std::string Filesystem::getUserDirectory()
478 {
479 #ifdef LOVE_IOS
480 	// PHYSFS_getUserDir doesn't give exactly the path we want on iOS.
481 	static std::string userDir = normalize(love::ios::getHomeDirectory());
482 #else
483 	static std::string userDir = normalize(PHYSFS_getUserDir());
484 #endif
485 
486 	return userDir;
487 }
488 
getAppdataDirectory()489 std::string Filesystem::getAppdataDirectory()
490 {
491 	if (appdata.empty())
492 	{
493 #ifdef LOVE_WINDOWS_UWP
494 		appdata = getUserDirectory();
495 #elif defined(LOVE_WINDOWS)
496 		wchar_t *w_appdata = _wgetenv(L"APPDATA");
497 		appdata = to_utf8(w_appdata);
498 		replace_char(appdata, '\\', '/');
499 #elif defined(LOVE_MACOSX)
500 		std::string udir = getUserDirectory();
501 		udir.append("/Library/Application Support");
502 		appdata = normalize(udir);
503 #elif defined(LOVE_IOS)
504 		appdata = normalize(love::ios::getAppdataDirectory());
505 #elif defined(LOVE_LINUX)
506 		char *xdgdatahome = getenv("XDG_DATA_HOME");
507 		if (!xdgdatahome)
508 			appdata = normalize(std::string(getUserDirectory()) + "/.local/share/");
509 		else
510 			appdata = xdgdatahome;
511 #else
512 		appdata = getUserDirectory();
513 #endif
514 	}
515 	return appdata;
516 }
517 
518 
getSaveDirectory()519 const char *Filesystem::getSaveDirectory()
520 {
521 	return save_path_full.c_str();
522 }
523 
getSourceBaseDirectory() const524 std::string Filesystem::getSourceBaseDirectory() const
525 {
526 	size_t source_len = game_source.length();
527 
528 	if (source_len == 0)
529 		return "";
530 
531 	// FIXME: This doesn't take into account parent and current directory
532 	// symbols (i.e. '..' and '.')
533 #ifdef LOVE_WINDOWS
534 	// In windows, delimiters can be either '/' or '\'.
535 	size_t base_end_pos = game_source.find_last_of("/\\", source_len - 2);
536 #else
537 	size_t base_end_pos = game_source.find_last_of('/', source_len - 2);
538 #endif
539 
540 	if (base_end_pos == std::string::npos)
541 		return "";
542 
543 	// If the source is in the unix root (aka '/'), we want to keep the '/'.
544 	if (base_end_pos == 0)
545 		base_end_pos = 1;
546 
547 	return game_source.substr(0, base_end_pos);
548 }
549 
getRealDirectory(const char * filename) const550 std::string Filesystem::getRealDirectory(const char *filename) const
551 {
552 	if (!PHYSFS_isInit())
553 		throw love::Exception("PhysFS is not initialized.");
554 
555 	const char *dir = PHYSFS_getRealDir(filename);
556 
557 	if (dir == nullptr)
558 		throw love::Exception("File does not exist on disk.");
559 
560 	return std::string(dir);
561 }
562 
getInfo(const char * filepath,Info & info) const563 bool Filesystem::getInfo(const char *filepath, Info &info) const
564 {
565 	if (!PHYSFS_isInit())
566 		return false;
567 
568 	PHYSFS_Stat stat = {};
569 	if (!PHYSFS_stat(filepath, &stat))
570 		return false;
571 
572 	info.size = (int64) stat.filesize;
573 	info.modtime = (int64) stat.modtime;
574 
575 	if (stat.filetype == PHYSFS_FILETYPE_REGULAR)
576 		info.type = FILETYPE_FILE;
577 	else if (stat.filetype == PHYSFS_FILETYPE_DIRECTORY)
578 		info.type = FILETYPE_DIRECTORY;
579 	else if (stat.filetype == PHYSFS_FILETYPE_SYMLINK)
580 		info.type = FILETYPE_SYMLINK;
581 	else
582 		info.type = FILETYPE_OTHER;
583 
584 	return true;
585 }
586 
createDirectory(const char * dir)587 bool Filesystem::createDirectory(const char *dir)
588 {
589 	if (!PHYSFS_isInit())
590 		return false;
591 
592 	if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory())
593 		return false;
594 
595 	if (!PHYSFS_mkdir(dir))
596 		return false;
597 
598 	return true;
599 }
600 
remove(const char * file)601 bool Filesystem::remove(const char *file)
602 {
603 	if (!PHYSFS_isInit())
604 		return false;
605 
606 	if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory())
607 		return false;
608 
609 	if (!PHYSFS_delete(file))
610 		return false;
611 
612 	return true;
613 }
614 
read(const char * filename,int64 size) const615 FileData *Filesystem::read(const char *filename, int64 size) const
616 {
617 	File file(filename);
618 
619 	file.open(File::MODE_READ);
620 
621 	// close() is called in the File destructor.
622 	return file.read(size);
623 }
624 
write(const char * filename,const void * data,int64 size) const625 void Filesystem::write(const char *filename, const void *data, int64 size) const
626 {
627 	File file(filename);
628 
629 	file.open(File::MODE_WRITE);
630 
631 	// close() is called in the File destructor.
632 	if (!file.write(data, size))
633 		throw love::Exception("Data could not be written.");
634 }
635 
append(const char * filename,const void * data,int64 size) const636 void Filesystem::append(const char *filename, const void *data, int64 size) const
637 {
638 	File file(filename);
639 
640 	file.open(File::MODE_APPEND);
641 
642 	// close() is called in the File destructor.
643 	if (!file.write(data, size))
644 		throw love::Exception("Data could not be written.");
645 }
646 
getDirectoryItems(const char * dir,std::vector<std::string> & items)647 void Filesystem::getDirectoryItems(const char *dir, std::vector<std::string> &items)
648 {
649 	if (!PHYSFS_isInit())
650 		return;
651 
652 	char **rc = PHYSFS_enumerateFiles(dir);
653 
654 	if (rc == nullptr)
655 		return;
656 
657 	for (char **i = rc; *i != 0; i++)
658 		items.push_back(*i);
659 
660 	PHYSFS_freeList(rc);
661 }
662 
setSymlinksEnabled(bool enable)663 void Filesystem::setSymlinksEnabled(bool enable)
664 {
665 	if (!PHYSFS_isInit())
666 		return;
667 
668 	PHYSFS_permitSymbolicLinks(enable ? 1 : 0);
669 }
670 
areSymlinksEnabled() const671 bool Filesystem::areSymlinksEnabled() const
672 {
673 	if (!PHYSFS_isInit())
674 		return false;
675 
676 	return PHYSFS_symbolicLinksPermitted() != 0;
677 }
678 
getRequirePath()679 std::vector<std::string> &Filesystem::getRequirePath()
680 {
681 	return requirePath;
682 }
683 
getCRequirePath()684 std::vector<std::string> &Filesystem::getCRequirePath()
685 {
686 	return cRequirePath;
687 }
688 
allowMountingForPath(const std::string & path)689 void Filesystem::allowMountingForPath(const std::string &path)
690 {
691 	if (std::find(allowedMountPaths.begin(), allowedMountPaths.end(), path) == allowedMountPaths.end())
692 		allowedMountPaths.push_back(path);
693 }
694 
695 } // physfs
696 } // filesystem
697 } // love
698