Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / xpcom / io / SpecialSystemDirectory.cpp
blob4b1055f3fe3fa0390f64ccac6a7428733877ab22
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "SpecialSystemDirectory.h"
8 #include "mozilla/Try.h"
9 #include "nsString.h"
10 #include "nsDependentString.h"
11 #include "nsIXULAppInfo.h"
13 #if defined(XP_WIN)
15 # include <windows.h>
16 # include <stdlib.h>
17 # include <stdio.h>
18 # include <string.h>
19 # include <direct.h>
20 # include <shlobj.h>
21 # include <knownfolders.h>
22 # include <guiddef.h>
24 #elif defined(XP_UNIX)
26 # include <limits.h>
27 # include <unistd.h>
28 # include <stdlib.h>
29 # include <sys/param.h>
30 # include "prenv.h"
31 # if defined(MOZ_WIDGET_COCOA)
32 # include "CFTypeRefPtr.h"
33 # include "CocoaFileUtils.h"
34 # endif
35 # if defined(MOZ_WIDGET_GTK)
36 # include "mozilla/WidgetUtilsGtk.h"
37 # endif
39 #endif
41 #ifndef MAXPATHLEN
42 # ifdef PATH_MAX
43 # define MAXPATHLEN PATH_MAX
44 # elif defined(MAX_PATH)
45 # define MAXPATHLEN MAX_PATH
46 # elif defined(_MAX_PATH)
47 # define MAXPATHLEN _MAX_PATH
48 # elif defined(CCHMAXPATH)
49 # define MAXPATHLEN CCHMAXPATH
50 # else
51 # define MAXPATHLEN 1024
52 # endif
53 #endif
55 #if defined(XP_WIN)
57 static nsresult GetKnownFolder(GUID* aGuid, nsIFile** aFile) {
58 if (!aGuid) {
59 return NS_ERROR_FAILURE;
62 PWSTR path = nullptr;
63 SHGetKnownFolderPath(*aGuid, 0, nullptr, &path);
65 if (!path) {
66 return NS_ERROR_FAILURE;
69 nsresult rv = NS_NewLocalFile(nsDependentString(path), true, aFile);
71 CoTaskMemFree(path);
72 return rv;
75 static nsresult GetWindowsFolder(int aFolder, nsIFile** aFile) {
76 WCHAR path_orig[MAX_PATH + 3];
77 WCHAR* path = path_orig + 1;
78 BOOL result = SHGetSpecialFolderPathW(nullptr, path, aFolder, true);
80 if (!result) {
81 return NS_ERROR_FAILURE;
84 // Append the trailing slash
85 int len = wcslen(path);
86 if (len == 0) {
87 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
89 if (len > 1 && path[len - 1] != L'\\') {
90 path[len] = L'\\';
91 path[++len] = L'\0';
94 return NS_NewLocalFile(nsDependentString(path, len), true, aFile);
97 # if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
99 * Return the default save-to location for the Windows Library passed in
100 * through aFolderId.
102 static nsresult GetLibrarySaveToPath(int aFallbackFolderId,
103 REFKNOWNFOLDERID aFolderId,
104 nsIFile** aFile) {
105 RefPtr<IShellLibrary> shellLib;
106 RefPtr<IShellItem> savePath;
107 SHLoadLibraryFromKnownFolder(aFolderId, STGM_READ, IID_IShellLibrary,
108 getter_AddRefs(shellLib));
110 if (shellLib && SUCCEEDED(shellLib->GetDefaultSaveFolder(
111 DSFT_DETECT, IID_IShellItem, getter_AddRefs(savePath)))) {
112 wchar_t* str = nullptr;
113 if (SUCCEEDED(savePath->GetDisplayName(SIGDN_FILESYSPATH, &str))) {
114 nsAutoString path;
115 path.Assign(str);
116 path.Append('\\');
117 nsresult rv = NS_NewLocalFile(path, false, aFile);
118 CoTaskMemFree(str);
119 return rv;
123 return GetWindowsFolder(aFallbackFolderId, aFile);
125 # endif
128 * Provides a fallback for getting the path to APPDATA or LOCALAPPDATA by
129 * querying the registry when the call to SHGetSpecialFolderPathW is unable to
130 * provide these paths (Bug 513958).
132 static nsresult GetRegWindowsAppDataFolder(bool aLocal, nsIFile** aFile) {
133 HKEY key;
134 LPCWSTR keyName =
135 L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
136 DWORD res = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &key);
137 if (res != ERROR_SUCCESS) {
138 return NS_ERROR_FAILURE;
141 WCHAR path[MAX_PATH + 2];
142 DWORD type, size;
143 res = RegQueryValueExW(key, (aLocal ? L"Local AppData" : L"AppData"), nullptr,
144 &type, (LPBYTE)&path, &size);
145 ::RegCloseKey(key);
146 // The call to RegQueryValueExW must succeed, the type must be REG_SZ, the
147 // buffer size must not equal 0, and the buffer size be a multiple of 2.
148 if (res != ERROR_SUCCESS || type != REG_SZ || size == 0 || size % 2 != 0) {
149 return NS_ERROR_FAILURE;
152 // Append the trailing slash
153 int len = wcslen(path);
154 if (len > 1 && path[len - 1] != L'\\') {
155 path[len] = L'\\';
156 path[++len] = L'\0';
159 return NS_NewLocalFile(nsDependentString(path, len), true, aFile);
162 #endif // XP_WIN
164 #if defined(XP_UNIX)
165 static nsresult GetUnixHomeDir(nsIFile** aFile) {
166 # if defined(ANDROID)
167 // XXX no home dir on android; maybe we should return the sdcard if present?
168 return NS_ERROR_FAILURE;
169 # else
170 return NS_NewNativeLocalFile(nsDependentCString(PR_GetEnv("HOME")), true,
171 aFile);
172 # endif
175 static nsresult GetUnixSystemConfigDir(nsIFile** aFile) {
176 # if defined(ANDROID)
177 return NS_ERROR_FAILURE;
178 # else
179 nsAutoCString appName;
180 if (nsCOMPtr<nsIXULAppInfo> appInfo =
181 do_GetService("@mozilla.org/xre/app-info;1")) {
182 MOZ_TRY(appInfo->GetName(appName));
183 } else {
184 appName.AssignLiteral(MOZ_APP_BASENAME);
187 ToLowerCase(appName);
189 nsDependentCString sysConfigDir;
190 if (PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) {
191 const char* mozSystemConfigDir = PR_GetEnv("MOZ_SYSTEM_CONFIG_DIR");
192 if (mozSystemConfigDir) {
193 sysConfigDir.Assign(nsDependentCString(mozSystemConfigDir));
196 # if defined(MOZ_WIDGET_GTK)
197 if (sysConfigDir.IsEmpty() && mozilla::widget::IsRunningUnderFlatpak()) {
198 sysConfigDir.Assign(nsLiteralCString("/app/etc"));
200 # endif
201 if (sysConfigDir.IsEmpty()) {
202 sysConfigDir.Assign(nsLiteralCString("/etc"));
204 MOZ_TRY(NS_NewNativeLocalFile(sysConfigDir, true, aFile));
205 MOZ_TRY((*aFile)->AppendNative(appName));
206 return NS_OK;
207 # endif
211 The following license applies to the xdg_user_dir_lookup function:
213 Copyright (c) 2007 Red Hat, Inc.
215 Permission is hereby granted, free of charge, to any person
216 obtaining a copy of this software and associated documentation files
217 (the "Software"), to deal in the Software without restriction,
218 including without limitation the rights to use, copy, modify, merge,
219 publish, distribute, sublicense, and/or sell copies of the Software,
220 and to permit persons to whom the Software is furnished to do so,
221 subject to the following conditions:
223 The above copyright notice and this permission notice shall be
224 included in all copies or substantial portions of the Software.
226 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
227 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
228 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
229 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
230 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
231 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
232 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
233 SOFTWARE.
236 static char* xdg_user_dir_lookup(const char* aType) {
237 FILE* file;
238 char* home_dir;
239 char* config_home;
240 char* config_file;
241 char buffer[512];
242 char* user_dir;
243 char* p;
244 char* d;
245 int len;
246 int relative;
248 home_dir = getenv("HOME");
250 if (!home_dir) {
251 goto error;
254 config_home = getenv("XDG_CONFIG_HOME");
255 if (!config_home || config_home[0] == 0) {
256 config_file =
257 (char*)malloc(strlen(home_dir) + strlen("/.config/user-dirs.dirs") + 1);
258 if (!config_file) {
259 goto error;
262 strcpy(config_file, home_dir);
263 strcat(config_file, "/.config/user-dirs.dirs");
264 } else {
265 config_file =
266 (char*)malloc(strlen(config_home) + strlen("/user-dirs.dirs") + 1);
267 if (!config_file) {
268 goto error;
271 strcpy(config_file, config_home);
272 strcat(config_file, "/user-dirs.dirs");
275 file = fopen(config_file, "r");
276 free(config_file);
277 if (!file) {
278 goto error;
281 user_dir = nullptr;
282 while (fgets(buffer, sizeof(buffer), file)) {
283 /* Remove newline at end */
284 len = strlen(buffer);
285 if (len > 0 && buffer[len - 1] == '\n') {
286 buffer[len - 1] = 0;
289 p = buffer;
290 while (*p == ' ' || *p == '\t') {
291 p++;
294 if (strncmp(p, "XDG_", 4) != 0) {
295 continue;
297 p += 4;
298 if (strncmp(p, aType, strlen(aType)) != 0) {
299 continue;
301 p += strlen(aType);
302 if (strncmp(p, "_DIR", 4) != 0) {
303 continue;
305 p += 4;
307 while (*p == ' ' || *p == '\t') {
308 p++;
311 if (*p != '=') {
312 continue;
314 p++;
316 while (*p == ' ' || *p == '\t') {
317 p++;
320 if (*p != '"') {
321 continue;
323 p++;
325 relative = 0;
326 if (strncmp(p, "$HOME/", 6) == 0) {
327 p += 6;
328 relative = 1;
329 } else if (*p != '/') {
330 continue;
333 if (relative) {
334 user_dir = (char*)malloc(strlen(home_dir) + 1 + strlen(p) + 1);
335 if (!user_dir) {
336 goto error2;
339 strcpy(user_dir, home_dir);
340 strcat(user_dir, "/");
341 } else {
342 user_dir = (char*)malloc(strlen(p) + 1);
343 if (!user_dir) {
344 goto error2;
347 *user_dir = 0;
350 d = user_dir + strlen(user_dir);
351 while (*p && *p != '"') {
352 if ((*p == '\\') && (*(p + 1) != 0)) {
353 p++;
355 *d++ = *p++;
357 *d = 0;
359 error2:
360 fclose(file);
362 if (user_dir) {
363 return user_dir;
366 error:
367 return nullptr;
370 static const char xdg_user_dirs[] =
371 "DESKTOP\0"
372 "DOCUMENTS\0"
373 "DOWNLOAD\0"
374 "MUSIC\0"
375 "PICTURES\0"
376 "PUBLICSHARE\0"
377 "TEMPLATES\0"
378 "VIDEOS";
380 static const uint8_t xdg_user_dir_offsets[] = {0, 8, 18, 27, 33, 42, 54, 64};
382 static nsresult GetUnixXDGUserDirectory(SystemDirectories aSystemDirectory,
383 nsIFile** aFile) {
384 char* dir = xdg_user_dir_lookup(
385 xdg_user_dirs +
386 xdg_user_dir_offsets[aSystemDirectory - Unix_XDG_Desktop]);
388 nsresult rv;
389 nsCOMPtr<nsIFile> file;
390 bool exists;
391 if (dir) {
392 rv = NS_NewNativeLocalFile(nsDependentCString(dir), true,
393 getter_AddRefs(file));
394 free(dir);
396 if (NS_FAILED(rv)) {
397 return rv;
400 rv = file->Exists(&exists);
401 if (NS_FAILED(rv)) {
402 return rv;
405 if (!exists) {
406 rv = file->Create(nsIFile::DIRECTORY_TYPE, 0755);
407 if (NS_FAILED(rv)) {
408 return rv;
411 } else if (Unix_XDG_Desktop == aSystemDirectory) {
412 // for the XDG desktop dir, fall back to HOME/Desktop
413 // (for historical compatibility)
414 nsCOMPtr<nsIFile> home;
415 rv = GetUnixHomeDir(getter_AddRefs(home));
416 if (NS_FAILED(rv)) {
417 return rv;
420 rv = home->Clone(getter_AddRefs(file));
421 if (NS_FAILED(rv)) {
422 return rv;
425 rv = file->AppendNative("Desktop"_ns);
426 if (NS_FAILED(rv)) {
427 return rv;
430 rv = file->Exists(&exists);
431 if (NS_FAILED(rv)) {
432 return rv;
435 // fallback to HOME only if HOME/Desktop doesn't exist
436 if (!exists) {
437 file = home;
439 } else {
440 // no fallback for the other XDG dirs
441 return NS_ERROR_FAILURE;
444 *aFile = nullptr;
445 file.swap(*aFile);
447 return NS_OK;
449 #endif
451 nsresult GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory,
452 nsIFile** aFile) {
453 #if defined(XP_WIN)
454 WCHAR path[MAX_PATH];
455 #else
456 char path[MAXPATHLEN];
457 #endif
459 switch (aSystemSystemDirectory) {
460 case OS_CurrentWorkingDirectory:
461 #if defined(XP_WIN)
462 if (!_wgetcwd(path, MAX_PATH)) {
463 return NS_ERROR_FAILURE;
465 return NS_NewLocalFile(nsDependentString(path), true, aFile);
466 #else
467 if (!getcwd(path, MAXPATHLEN)) {
468 return NS_ERROR_FAILURE;
470 #endif
472 #if !defined(XP_WIN)
473 return NS_NewNativeLocalFile(nsDependentCString(path), true, aFile);
474 #endif
476 case OS_TemporaryDirectory:
477 #if defined(XP_WIN)
479 DWORD len = ::GetTempPathW(MAX_PATH, path);
480 if (len == 0) {
481 break;
483 return NS_NewLocalFile(nsDependentString(path, len), true, aFile);
485 #elif defined(MOZ_WIDGET_COCOA)
487 return GetOSXFolderType(kUserDomain, kTemporaryFolderType, aFile);
490 #elif defined(XP_UNIX)
492 static const char* tPath = nullptr;
493 if (!tPath) {
494 tPath = PR_GetEnv("TMPDIR");
495 if (!tPath || !*tPath) {
496 tPath = PR_GetEnv("TMP");
497 if (!tPath || !*tPath) {
498 tPath = PR_GetEnv("TEMP");
499 if (!tPath || !*tPath) {
500 tPath = "/tmp/";
505 return NS_NewNativeLocalFile(nsDependentCString(tPath), true, aFile);
507 #else
508 break;
509 #endif
510 #if defined(MOZ_WIDGET_COCOA)
511 case Mac_SystemDirectory: {
512 return GetOSXFolderType(kClassicDomain, kSystemFolderType, aFile);
514 case Mac_UserLibDirectory: {
515 return GetOSXFolderType(kUserDomain, kDomainLibraryFolderType, aFile);
517 case Mac_HomeDirectory: {
518 return GetOSXFolderType(kUserDomain, kDomainTopLevelFolderType, aFile);
520 case Mac_DefaultDownloadDirectory: {
521 nsresult rv = GetOSXFolderType(kUserDomain, kDownloadsFolderType, aFile);
522 if (NS_FAILED(rv)) {
523 return GetOSXFolderType(kUserDomain, kDesktopFolderType, aFile);
525 return NS_OK;
527 case Mac_UserDesktopDirectory: {
528 return GetOSXFolderType(kUserDomain, kDesktopFolderType, aFile);
530 case Mac_LocalApplicationsDirectory: {
531 return GetOSXFolderType(kLocalDomain, kApplicationsFolderType, aFile);
533 case Mac_UserPreferencesDirectory: {
534 return GetOSXFolderType(kUserDomain, kPreferencesFolderType, aFile);
536 case Mac_PictureDocumentsDirectory: {
537 return GetOSXFolderType(kUserDomain, kPictureDocumentsFolderType, aFile);
539 case Mac_DefaultScreenshotDirectory: {
540 auto prefValue = CFTypeRefPtr<CFPropertyListRef>::WrapUnderCreateRule(
541 CFPreferencesCopyAppValue(CFSTR("location"),
542 CFSTR("com.apple.screencapture")));
544 if (!prefValue || CFGetTypeID(prefValue.get()) != CFStringGetTypeID()) {
545 return GetOSXFolderType(kUserDomain, kPictureDocumentsFolderType,
546 aFile);
549 nsAutoString path;
550 mozilla::Span<char16_t> data =
551 path.GetMutableData(CFStringGetLength((CFStringRef)prefValue.get()));
552 CFStringGetCharacters((CFStringRef)prefValue.get(),
553 CFRangeMake(0, data.Length()),
554 reinterpret_cast<UniChar*>(data.Elements()));
556 return NS_NewLocalFile(path, true, aFile);
558 #elif defined(XP_WIN)
559 case Win_SystemDirectory: {
560 int32_t len = ::GetSystemDirectoryW(path, MAX_PATH);
562 // Need enough space to add the trailing backslash
563 if (!len || len > MAX_PATH - 2) {
564 break;
566 path[len] = L'\\';
567 path[++len] = L'\0';
569 return NS_NewLocalFile(nsDependentString(path, len), true, aFile);
572 case Win_WindowsDirectory: {
573 int32_t len = ::GetWindowsDirectoryW(path, MAX_PATH);
575 // Need enough space to add the trailing backslash
576 if (!len || len > MAX_PATH - 2) {
577 break;
580 path[len] = L'\\';
581 path[++len] = L'\0';
583 return NS_NewLocalFile(nsDependentString(path, len), true, aFile);
586 case Win_ProgramFiles: {
587 return GetWindowsFolder(CSIDL_PROGRAM_FILES, aFile);
590 case Win_HomeDirectory: {
591 nsresult rv = GetWindowsFolder(CSIDL_PROFILE, aFile);
592 if (NS_SUCCEEDED(rv)) {
593 return rv;
596 int32_t len;
597 if ((len = ::GetEnvironmentVariableW(L"HOME", path, MAX_PATH)) > 0) {
598 // Need enough space to add the trailing backslash
599 if (len > MAX_PATH - 2) {
600 break;
603 path[len] = L'\\';
604 path[++len] = L'\0';
606 rv = NS_NewLocalFile(nsDependentString(path, len), true, aFile);
607 if (NS_SUCCEEDED(rv)) {
608 return rv;
612 len = ::GetEnvironmentVariableW(L"HOMEDRIVE", path, MAX_PATH);
613 if (0 < len && len < MAX_PATH) {
614 WCHAR temp[MAX_PATH];
615 DWORD len2 = ::GetEnvironmentVariableW(L"HOMEPATH", temp, MAX_PATH);
616 if (0 < len2 && len + len2 < MAX_PATH) {
617 wcsncat(path, temp, len2);
620 len = wcslen(path);
622 // Need enough space to add the trailing backslash
623 if (len > MAX_PATH - 2) {
624 break;
627 path[len] = L'\\';
628 path[++len] = L'\0';
630 return NS_NewLocalFile(nsDependentString(path, len), true, aFile);
632 break;
634 case Win_Programs: {
635 return GetWindowsFolder(CSIDL_PROGRAMS, aFile);
638 case Win_Downloads: {
639 // Defined in KnownFolders.h.
640 GUID folderid_downloads = {
641 0x374de290,
642 0x123f,
643 0x4565,
644 {0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b}};
645 nsresult rv = GetKnownFolder(&folderid_downloads, aFile);
646 // On WinXP, there is no downloads folder, default
647 // to 'Desktop'.
648 if (NS_ERROR_FAILURE == rv) {
649 rv = GetWindowsFolder(CSIDL_DESKTOP, aFile);
651 return rv;
654 case Win_Favorites: {
655 return GetWindowsFolder(CSIDL_FAVORITES, aFile);
657 case Win_Desktopdirectory: {
658 return GetWindowsFolder(CSIDL_DESKTOPDIRECTORY, aFile);
660 case Win_Cookies: {
661 return GetWindowsFolder(CSIDL_COOKIES, aFile);
663 case Win_Appdata: {
664 nsresult rv = GetWindowsFolder(CSIDL_APPDATA, aFile);
665 if (NS_FAILED(rv)) {
666 rv = GetRegWindowsAppDataFolder(false, aFile);
668 return rv;
670 case Win_LocalAppdata: {
671 nsresult rv = GetWindowsFolder(CSIDL_LOCAL_APPDATA, aFile);
672 if (NS_FAILED(rv)) {
673 rv = GetRegWindowsAppDataFolder(true, aFile);
675 return rv;
677 # if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
678 case Win_Documents: {
679 return GetLibrarySaveToPath(CSIDL_MYDOCUMENTS, FOLDERID_DocumentsLibrary,
680 aFile);
682 # endif
683 #endif // XP_WIN
685 #if defined(XP_UNIX)
686 case Unix_HomeDirectory:
687 return GetUnixHomeDir(aFile);
689 case Unix_XDG_Desktop:
690 case Unix_XDG_Download:
691 return GetUnixXDGUserDirectory(aSystemSystemDirectory, aFile);
693 case Unix_SystemConfigDirectory:
694 return GetUnixSystemConfigDir(aFile);
695 #endif
697 default:
698 break;
700 return NS_ERROR_NOT_AVAILABLE;
703 #if defined(MOZ_WIDGET_COCOA)
704 nsresult GetOSXFolderType(short aDomain, OSType aFolderType,
705 nsIFile** aLocalFile) {
706 nsresult rv = NS_ERROR_FAILURE;
708 if (aFolderType == kTemporaryFolderType) {
709 NS_NewLocalFile(u""_ns, true, aLocalFile);
710 nsCOMPtr<nsILocalFileMac> localMacFile(do_QueryInterface(*aLocalFile));
711 if (localMacFile) {
712 rv = localMacFile->InitWithCFURL(
713 CocoaFileUtils::GetTemporaryFolder().get());
715 return rv;
718 OSErr err;
719 FSRef fsRef;
720 err = ::FSFindFolder(aDomain, aFolderType, kCreateFolder, &fsRef);
721 if (err == noErr) {
722 NS_NewLocalFile(u""_ns, true, aLocalFile);
723 nsCOMPtr<nsILocalFileMac> localMacFile(do_QueryInterface(*aLocalFile));
724 if (localMacFile) {
725 rv = localMacFile->InitWithFSRef(&fsRef);
728 return rv;
730 #endif