1 #include "fz_paths.h"
2 
3 #include "xml_file.h"
4 #include <libfilezilla/buffer.hpp>
5 #include <libfilezilla/local_filesys.hpp>
6 
7 #include <cstdlib>
8 #include <cstring>
9 
10 #ifdef FZ_MAC
11 	#include <CoreFoundation/CFBundle.h>
12 	#include <CoreFoundation/CFURL.h>
13 #elif defined(FZ_WINDOWS)
14 	#include <shlobj.h>
15 	#include <objbase.h>
16 
17 	// Needed for MinGW:
18 	#ifndef SHGFP_TYPE_CURRENT
19 		#define SHGFP_TYPE_CURRENT 0
20 	#endif
21 #else
22 	#include <unistd.h>
23 	#include <wordexp.h>
24 #endif
25 
26 using namespace std::literals;
27 
28 #ifndef FZ_WINDOWS
29 namespace {
TryDirectory(std::wstring path,std::wstring const & suffix,bool check_exists)30 static std::wstring TryDirectory(std::wstring path, std::wstring const& suffix, bool check_exists)
31 {
32 	if (!path.empty() && path[0] == '/') {
33 		if (path[path.size() - 1] != '/') {
34 			path += '/';
35 		}
36 
37 		path += suffix;
38 
39 		if (check_exists) {
40 			if (!CLocalPath(path).Exists(nullptr)) {
41 				path.clear();
42 			}
43 		}
44 	}
45 	else {
46 		path.clear();
47 	}
48 	return path;
49 }
50 }
51 #endif
52 
GetHomeDir()53 CLocalPath GetHomeDir()
54 {
55 	CLocalPath ret;
56 
57 #ifdef FZ_WINDOWS
58 	wchar_t* out{};
59 	if (SHGetKnownFolderPath(FOLDERID_Profile, 0, 0, &out) == S_OK) {
60 		ret.SetPath(out);
61 		CoTaskMemFree(out);
62 	}
63 #else
64 	ret.SetPath(GetEnv("HOME"));
65 #endif
66 	return ret;
67 }
68 
GetTempDir()69 CLocalPath GetTempDir()
70 {
71 	CLocalPath ret;
72 
73 #ifdef FZ_WINDOWS
74 	DWORD len = GetTempPathW(0, nullptr);
75 	if (len) {
76 		std::wstring buf;
77 		buf.resize(len);
78 		DWORD len2 = GetTempPathW(len, buf.data());
79 		if (len2 == len - 1) {
80 			buf.pop_back();
81 			ret.SetPath(buf);
82 		}
83 	}
84 #elif FZ_MAC
85 #else
86 	if (!ret.SetPath(GetEnv("TMPDIR"))) {
87 		if (!ret.SetPath(GetEnv("TMP"))) {
88 			if (!ret.SetPath(GetEnv("TEMP"))) {
89 				ret.SetPath(L"/tmp");
90 			}
91 		}
92 	}
93 #endif
94 	return ret;
95 }
GetUnadjustedSettingsDir()96 CLocalPath GetUnadjustedSettingsDir()
97 {
98 	CLocalPath ret;
99 
100 #ifdef FZ_WINDOWS
101 	wchar_t* out{};
102 	if (SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, 0, &out) == S_OK) {
103 		ret.SetPath(out);
104 		CoTaskMemFree(out);
105 	}
106 	if (!ret.empty()) {
107 		ret.AddSegment(L"FileZilla");
108 	}
109 
110 	if (ret.empty()) {
111 		// Fall back to directory where the executable is
112 		std::wstring const& dir = GetOwnExecutableDir();
113 		ret.SetPath(dir);
114 	}
115 #else
116 	std::wstring cfg = TryDirectory(GetEnv("XDG_CONFIG_HOME"), L"filezilla/", true);
117 	if (cfg.empty()) {
118 		cfg = TryDirectory(GetEnv("HOME"), L".config/filezilla/", true);
119 	}
120 	if (cfg.empty()) {
121 		cfg = TryDirectory(GetEnv("HOME"), L".filezilla/", true);
122 	}
123 	if (cfg.empty()) {
124 		cfg = TryDirectory(GetEnv("XDG_CONFIG_HOME"), L"filezilla/", false);
125 	}
126 	if (cfg.empty()) {
127 		cfg = TryDirectory(GetEnv("HOME"), L".config/filezilla/", false);
128 	}
129 	if (cfg.empty()) {
130 		cfg = TryDirectory(GetEnv("HOME"), L".filezilla/", false);
131 	}
132 	ret.SetPath(cfg);
133 #endif
134 	return ret;
135 }
136 
137 #ifdef FZ_MAC
138 namespace {
fromCFURLRef(CFURLRef url)139 std::wstring fromCFURLRef(CFURLRef url)
140 {
141 	std::wstring ret;
142 	if (url) {
143 		CFURLRef absolute = CFURLCopyAbsoluteURL(url);
144 		if (absolute) {
145 			CFStringRef path = CFURLCopyFileSystemPath(absolute, kCFURLPOSIXPathStyle);
146 			if (path) {
147 				CFIndex inlen = CFStringGetLength(path);
148 				CFIndex outlen = 0;
149 				CFStringGetBytes(path, CFRangeMake(0, inlen), kCFStringEncodingUTF32, 0, false, nullptr, 0, &outlen);
150 				if (outlen) {
151 					ret.resize((outlen + 3) / 4);
152 					CFStringGetBytes(path, CFRangeMake(0, inlen), kCFStringEncodingUTF32, 0, false, reinterpret_cast<UInt8*>(ret.data()), outlen, &outlen);
153 				}
154 				CFRelease(path);
155 			}
156 			CFRelease(absolute);
157 		}
158 		CFRelease(url);
159 	}
160 
161 	return ret;
162 }
163 }
164 #endif
165 
GetOwnExecutableDir()166 std::wstring GetOwnExecutableDir()
167 {
168 #ifdef FZ_WINDOWS
169 	// Add executable path
170 	std::wstring path;
171 	path.resize(4095);
172 	DWORD res;
173 	while (true) {
174 		res = GetModuleFileNameW(0, &path[0], static_cast<DWORD>(path.size() - 1));
175 		if (!res) {
176 			// Failure
177 			return std::wstring();
178 		}
179 
180 		if (res < path.size() - 1) {
181 			path.resize(res);
182 			break;
183 		}
184 
185 		path.resize(path.size() * 2 + 1);
186 	}
187 	size_t pos = path.find_last_of(L"\\/");
188 	if (pos != std::wstring::npos) {
189 		return path.substr(0, pos + 1);
190 	}
191 #elif defined(FZ_MAC)
192 	CFBundleRef bundle = CFBundleGetMainBundle();
193 	if (bundle) {
194 		std::wstring const executable = fromCFURLRef(CFBundleCopyExecutableURL(bundle));
195 		size_t pos = executable.rfind('/');
196 		if (pos != std::wstring::npos) {
197 			return executable.substr(0, pos + 1);
198 		}
199 	}
200 #else
201 	std::string path;
202 	path.resize(4095);
203 	while (true) {
204 		int res = readlink("/proc/self/exe", &path[0], path.size());
205 		if (res < 0) {
206 			return std::wstring();
207 		}
208 		if (static_cast<size_t>(res) < path.size()) {
209 			path.resize(res);
210 			break;
211 		}
212 		path.resize(path.size() * 2 + 1);
213 	}
214 	size_t pos = path.rfind('/');
215 	if (pos != std::wstring::npos) {
216 		return fz::to_wstring(path.substr(0, pos + 1));
217 	}
218 #endif
219 	return std::wstring();
220 }
221 
222 #ifdef FZ_MAC
223 std::wstring mac_data_path();
224 #endif
225 
226 namespace {
227 
228 #if FZ_WINDOWS
229 std::wstring const PATH_SEP = L";";
230 #define L_DIR_SEP L"\\"
231 #else
232 std::wstring const PATH_SEP = L":";
233 #define L_DIR_SEP L"/"
234 #endif
235 }
236 
GetFZDataDir(std::vector<std::wstring> const & fileToFind,std::wstring const & prefixSub,bool searchSelfDir)237 CLocalPath GetFZDataDir(std::vector<std::wstring> const& fileToFind, std::wstring const& prefixSub, bool searchSelfDir)
238 {
239 	/*
240 	 * Finding the resources in all cases is a difficult task,
241 	 * due to the huge variety of different systems and their filesystem
242 	 * structure.
243 	 * Basically we just check a couple of paths for presence of the resources,
244 	 * and hope we find them. If not, the user can still specify on the cmdline
245 	 * and using environment variables where the resources are.
246 	 *
247 	 * At least on OS X it's simple: All inside application bundle.
248 	 */
249 
250 	CLocalPath ret;
251 
252 	auto testPath = [&](std::wstring const& path) {
253 		ret = CLocalPath(path);
254 		if (ret.empty()) {
255 			return false;
256 		}
257 
258 		for (auto const& file : fileToFind) {
259 			if (FileExists(ret.GetPath() + file)) {
260 				return true;
261 			}
262 		}
263 		return false;
264 	};
265 
266 #ifdef FZ_MAC
267 	(void)prefixSub;
268 	if (searchSelfDir) {
269 		CFBundleRef bundle = CFBundleGetMainBundle();
270 		if (bundle) {
271 			if (testPath(fromCFURLRef(CFBundleCopySharedSupportURL(bundle)))) {
272 				return ret;
273 			}
274 		}
275 	}
276 
277 	return CLocalPath();
278 #else
279 
280 	// First try the user specified data dir.
281 	if (searchSelfDir) {
282 		if (testPath(GetEnv("FZ_DATADIR"))) {
283 			return ret;
284 		}
285 	}
286 
287 	std::wstring selfDir = GetOwnExecutableDir();
288 	if (!selfDir.empty()) {
289 		if (searchSelfDir && testPath(selfDir)) {
290 			return ret;
291 		}
292 
293 		if (!prefixSub.empty() && selfDir.size() > 5 && fz::ends_with(selfDir, std::wstring(L_DIR_SEP L"bin" L_DIR_SEP))) {
294 			std::wstring path = selfDir.substr(0, selfDir.size() - 4) + prefixSub + L_DIR_SEP;
295 			if (testPath(path)) {
296 				return ret;
297 			}
298 		}
299 
300 		// Development paths
301 		if (searchSelfDir && selfDir.size() > 7 && fz::ends_with(selfDir, std::wstring(L_DIR_SEP L".libs" L_DIR_SEP))) {
302 			std::wstring path = selfDir.substr(0, selfDir.size() - 6);
303 			if (FileExists(path + L"Makefile")) {
304 				if (testPath(path)) {
305 					return ret;
306 				}
307 			}
308 		}
309 	}
310 
311 	// Now scan through the path
312 	if (!prefixSub.empty()) {
313 		std::wstring path = GetEnv("PATH");
314 		auto const segments = fz::strtok(path, PATH_SEP);
315 
316 		for (auto const& segment : segments) {
317 			auto const cur = CLocalPath(segment).GetPath();
318 			if (cur.size() > 5 && fz::ends_with(cur, std::wstring(L_DIR_SEP L"bin" L_DIR_SEP))) {
319 				std::wstring path = cur.substr(0, cur.size() - 4) + prefixSub + L_DIR_SEP;
320 				if (testPath(path)) {
321 					return ret;
322 				}
323 			}
324 		}
325 	}
326 
327 	ret.clear();
328 	return ret;
329 #endif
330 }
331 
GetDefaultsDir()332 CLocalPath GetDefaultsDir()
333 {
334 	static CLocalPath path = [] {
335 		CLocalPath path;
336 #ifdef FZ_UNIX
337 		path = GetUnadjustedSettingsDir();
338 		if (path.empty() || !FileExists(path.GetPath() + L"fzdefaults.xml")) {
339 			if (FileExists(L"/etc/filezilla/fzdefaults.xml")) {
340 				path.SetPath(L"/etc/filezilla");
341 			}
342 			else {
343 				path.clear();
344 			}
345 		}
346 
347 #endif
348 		if (path.empty()) {
349 			path = GetFZDataDir({ L"fzdefaults.xml" }, L"share/filezilla");
350 		}
351 		return path;
352 	}();
353 
354 	return path;
355 }
356 
357 
GetSettingFromFile(std::wstring const & xmlfile,std::string const & name)358 std::wstring GetSettingFromFile(std::wstring const& xmlfile, std::string const& name)
359 {
360 	CXmlFile file(xmlfile);
361 	if (!file.Load()) {
362 		return L"";
363 	}
364 
365 	auto element = file.GetElement();
366 	if (!element) {
367 		return L"";
368 	}
369 
370 	auto settings = element.child("Settings");
371 	if (!settings) {
372 		return L"";
373 	}
374 
375 	for (auto setting = settings.child("Setting"); setting; setting = setting.next_sibling("Setting")) {
376 		const char* nodeVal = setting.attribute("name").value();
377 		if (!nodeVal || strcmp(nodeVal, name.c_str())) {
378 			continue;
379 		}
380 
381 		return fz::to_wstring_from_utf8(setting.child_value());
382 	}
383 
384 	return L"";
385 }
386 
ReadSettingsFromDefaults(CLocalPath const & defaultsDir)387 std::wstring ReadSettingsFromDefaults(CLocalPath const& defaultsDir)
388 {
389 	if (defaultsDir.empty()) {
390 		return L"";
391 	}
392 
393 	std::wstring dir = GetSettingFromFile(defaultsDir.GetPath() + L"fzdefaults.xml", "Config Location");
394 	auto result = ExpandPath(dir);
395 
396 	if (!FileExists(result)) {
397 		return L"";
398 	}
399 
400 	if (result[result.size() - 1] != '/') {
401 		result += '/';
402 	}
403 
404 	return result;
405 }
406 
407 
GetSettingsDir()408 CLocalPath GetSettingsDir()
409 {
410 	CLocalPath p;
411 
412 	CLocalPath const defaults_dir = GetDefaultsDir();
413 	std::wstring dir = ReadSettingsFromDefaults(defaults_dir);
414 	if (!dir.empty()) {
415 		dir = ExpandPath(dir);
416 		p.SetPath(defaults_dir.GetPath());
417 		p.ChangePath(dir);
418 	}
419 	else {
420 		p = GetUnadjustedSettingsDir();
421 	}
422 	return p;
423 }
424 
425 namespace {
426 template<typename String>
ExpandPathImpl(String dir)427 String ExpandPathImpl(String dir)
428 {
429 	if (dir.empty()) {
430 		return dir;
431 	}
432 
433 	String result;
434 	while (!dir.empty()) {
435 		String token;
436 #ifdef FZ_WINDOWS
437 		size_t pos = dir.find_first_of(fzS(typename String::value_type, "\\/"));
438 #else
439 		size_t pos = dir.find('/');
440 #endif
441 		if (pos == String::npos) {
442 			token.swap(dir);
443 		}
444 		else {
445 			token = dir.substr(0, pos);
446 			dir = dir.substr(pos + 1);
447 		}
448 
449 		if (token[0] == '$') {
450 			if (token[1] == '$') {
451 				result += token.substr(1);
452 			}
453 			else if (token.size() > 1) {
454 				char* env = getenv(fz::to_string(token.substr(1)).c_str());
455 				if (env) {
456 					result += fz::toString<String>(env);
457 				}
458 			}
459 		}
460 		else {
461 			result += token;
462 		}
463 
464 #ifdef FZ_WINDOWS
465 		result += '\\';
466 #else
467 		result += '/';
468 #endif
469 	}
470 
471 	return result;
472 }
473 }
474 
ExpandPath(std::string const & dir)475 std::string ExpandPath(std::string const& dir) {
476 	return ExpandPathImpl(dir);
477 }
478 
ExpandPath(std::wstring const & dir)479 std::wstring ExpandPath(std::wstring const& dir) {
480 	return ExpandPathImpl(dir);
481 }
482 
483 
484 #if defined FZ_MAC
485 char const* GetDownloadDirImpl();
486 #elif !defined(FZ_WINDOWS)
487 
488 namespace {
ShellUnescape(std::string const & path)489 std::string ShellUnescape(std::string const& path)
490 {
491 	std::string ret;
492 
493 	wordexp_t p;
494 	int res = wordexp(path.c_str(), &p, WRDE_NOCMD);
495 	if (!res && p.we_wordc == 1 && p.we_wordv) {
496 		ret = p.we_wordv[0];
497 	}
498 	wordfree(&p);
499 
500 	return ret;
501 }
502 
next_line(fz::file & f,fz::buffer & buf,size_t maxlen=16* 1024)503 size_t next_line(fz::file& f, fz::buffer& buf, size_t maxlen = 16 * 1024)
504 {
505 	if (!buf.empty() && buf[0] == '\n') {
506 		buf.consume(1);
507 	}
508 	for (size_t i = 0; i < buf.size(); ++i) {
509 		if (buf[i] == '\n') {
510 			return i;
511 		}
512 	}
513 
514 	while (buf.size() < maxlen) {
515 		size_t const oldSize = buf.size();
516 		unsigned char* p = buf.get(maxlen - oldSize);
517 		int read = f.read(p, maxlen - oldSize);
518 		if (read < 0) {
519 			return std::string::npos;
520 		}
521 		if (!read) {
522 			return buf.size();
523 		}
524 		buf.add(read);
525 		for (size_t i = 0; i < static_cast<size_t>(read); ++i) {
526 			if (p[i] == '\n') {
527 				return i = oldSize;
528 			}
529 		}
530 	}
531 
532 	return std::string::npos;
533 }
534 
GetXdgUserDir(std::string_view type)535 CLocalPath GetXdgUserDir(std::string_view type)
536 {
537 	CLocalPath confdir(GetEnv("XDG_CONFIG_HOME"));
538 	if (confdir.empty()) {
539 		confdir = GetHomeDir();
540 		if (!confdir.empty()) {
541 			confdir.AddSegment(L".config");
542 		}
543 	}
544 	if (confdir.empty()) {
545 		return {};
546 	}
547 
548 	fz::file f(fz::to_native(confdir.GetPath()) + "/user-dirs.dirs", fz::file::reading, fz::file::existing);
549 	if (!f.opened()) {
550 		return {};
551 	}
552 
553 	fz::buffer buf;
554 	while (true) {
555 		size_t const nl = next_line(f, buf);
556 		if (nl == std::string::npos) {
557 			break;
558 		}
559 
560 		std::string_view line(reinterpret_cast<char const*>(buf.get()), nl);
561 		fz::trim(line);
562 		if (fz::starts_with(line, type)) {
563 			size_t pos = line.find('=');
564 			if (pos != std::string::npos) {
565 				CLocalPath path(fz::to_wstring(ShellUnescape(std::string(line.substr(pos + 1)))));
566 				if (!path.empty()) {
567 					return path;
568 				}
569 			}
570 		}
571 		buf.consume(nl);
572 	}
573 
574 	return {};
575 }
576 }
577 #endif
578 
GetDownloadDir()579 CLocalPath GetDownloadDir()
580 {
581 #if FZ_WINDOWS
582 	PWSTR path;
583 	HRESULT result = SHGetKnownFolderPath(FOLDERID_Downloads, 0, 0, &path);
584 	if (result == S_OK) {
585 		std::wstring dir = path;
586 		CoTaskMemFree(path);
587 		return CLocalPath(dir);
588 	}
589 	return {};
590 #elif FZ_MAC
591 	CLocalPath ret;
592 	char const* url = GetDownloadDirImpl();
593 	ret.SetPath(fz::to_wstring_from_utf8(url));
594 	return ret;
595 #else
596 	CLocalPath dl = GetXdgUserDir("XDG_DOWNLOAD_DIR"sv);
597 	if (dl.empty() || !dl.Exists()) {
598 		dl = GetXdgUserDir("XDG_DOCUMENTS_DIR"sv);
599 	}
600 
601 	return dl;
602 #endif
603 }
604 
605