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