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"
10 #include "nsDependentString.h"
11 #include "nsIXULAppInfo.h"
21 # include <knownfolders.h>
24 #elif defined(XP_UNIX)
29 # include <sys/param.h>
31 # if defined(MOZ_WIDGET_COCOA)
32 # include "CFTypeRefPtr.h"
33 # include "CocoaFileUtils.h"
35 # if defined(MOZ_WIDGET_GTK)
36 # include "mozilla/WidgetUtilsGtk.h"
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
51 # define MAXPATHLEN 1024
57 static nsresult
GetKnownFolder(GUID
* aGuid
, nsIFile
** aFile
) {
59 return NS_ERROR_FAILURE
;
63 SHGetKnownFolderPath(*aGuid
, 0, nullptr, &path
);
66 return NS_ERROR_FAILURE
;
69 nsresult rv
= NS_NewLocalFile(nsDependentString(path
), true, aFile
);
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);
81 return NS_ERROR_FAILURE
;
84 // Append the trailing slash
85 int len
= wcslen(path
);
87 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
89 if (len
> 1 && path
[len
- 1] != L
'\\') {
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
102 static nsresult
GetLibrarySaveToPath(int aFallbackFolderId
,
103 REFKNOWNFOLDERID aFolderId
,
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
))) {
117 nsresult rv
= NS_NewLocalFile(path
, false, aFile
);
123 return GetWindowsFolder(aFallbackFolderId
, aFile
);
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
) {
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];
143 res
= RegQueryValueExW(key
, (aLocal
? L
"Local AppData" : L
"AppData"), nullptr,
144 &type
, (LPBYTE
)&path
, &size
);
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
'\\') {
159 return NS_NewLocalFile(nsDependentString(path
, len
), true, aFile
);
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
;
170 return NS_NewNativeLocalFile(nsDependentCString(PR_GetEnv("HOME")), true,
175 static nsresult
GetUnixSystemConfigDir(nsIFile
** aFile
) {
176 # if defined(ANDROID)
177 return NS_ERROR_FAILURE
;
179 nsAutoCString appName
;
180 if (nsCOMPtr
<nsIXULAppInfo
> appInfo
=
181 do_GetService("@mozilla.org/xre/app-info;1")) {
182 MOZ_TRY(appInfo
->GetName(appName
));
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"));
201 if (sysConfigDir
.IsEmpty()) {
202 sysConfigDir
.Assign(nsLiteralCString("/etc"));
204 MOZ_TRY(NS_NewNativeLocalFile(sysConfigDir
, true, aFile
));
205 MOZ_TRY((*aFile
)->AppendNative(appName
));
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
236 static char* xdg_user_dir_lookup(const char* aType
) {
248 home_dir
= getenv("HOME");
254 config_home
= getenv("XDG_CONFIG_HOME");
255 if (!config_home
|| config_home
[0] == 0) {
257 (char*)malloc(strlen(home_dir
) + strlen("/.config/user-dirs.dirs") + 1);
262 strcpy(config_file
, home_dir
);
263 strcat(config_file
, "/.config/user-dirs.dirs");
266 (char*)malloc(strlen(config_home
) + strlen("/user-dirs.dirs") + 1);
271 strcpy(config_file
, config_home
);
272 strcat(config_file
, "/user-dirs.dirs");
275 file
= fopen(config_file
, "r");
282 while (fgets(buffer
, sizeof(buffer
), file
)) {
283 /* Remove newline at end */
284 len
= strlen(buffer
);
285 if (len
> 0 && buffer
[len
- 1] == '\n') {
290 while (*p
== ' ' || *p
== '\t') {
294 if (strncmp(p
, "XDG_", 4) != 0) {
298 if (strncmp(p
, aType
, strlen(aType
)) != 0) {
302 if (strncmp(p
, "_DIR", 4) != 0) {
307 while (*p
== ' ' || *p
== '\t') {
316 while (*p
== ' ' || *p
== '\t') {
326 if (strncmp(p
, "$HOME/", 6) == 0) {
329 } else if (*p
!= '/') {
334 user_dir
= (char*)malloc(strlen(home_dir
) + 1 + strlen(p
) + 1);
339 strcpy(user_dir
, home_dir
);
340 strcat(user_dir
, "/");
342 user_dir
= (char*)malloc(strlen(p
) + 1);
350 d
= user_dir
+ strlen(user_dir
);
351 while (*p
&& *p
!= '"') {
352 if ((*p
== '\\') && (*(p
+ 1) != 0)) {
370 static const char xdg_user_dirs
[] =
380 static const uint8_t xdg_user_dir_offsets
[] = {0, 8, 18, 27, 33, 42, 54, 64};
382 static nsresult
GetUnixXDGUserDirectory(SystemDirectories aSystemDirectory
,
384 char* dir
= xdg_user_dir_lookup(
386 xdg_user_dir_offsets
[aSystemDirectory
- Unix_XDG_Desktop
]);
389 nsCOMPtr
<nsIFile
> file
;
392 rv
= NS_NewNativeLocalFile(nsDependentCString(dir
), true,
393 getter_AddRefs(file
));
400 rv
= file
->Exists(&exists
);
406 rv
= file
->Create(nsIFile::DIRECTORY_TYPE
, 0755);
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
));
420 rv
= home
->Clone(getter_AddRefs(file
));
425 rv
= file
->AppendNative("Desktop"_ns
);
430 rv
= file
->Exists(&exists
);
435 // fallback to HOME only if HOME/Desktop doesn't exist
440 // no fallback for the other XDG dirs
441 return NS_ERROR_FAILURE
;
451 nsresult
GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory
,
454 WCHAR path
[MAX_PATH
];
456 char path
[MAXPATHLEN
];
459 switch (aSystemSystemDirectory
) {
460 case OS_CurrentWorkingDirectory
:
462 if (!_wgetcwd(path
, MAX_PATH
)) {
463 return NS_ERROR_FAILURE
;
465 return NS_NewLocalFile(nsDependentString(path
), true, aFile
);
467 if (!getcwd(path
, MAXPATHLEN
)) {
468 return NS_ERROR_FAILURE
;
473 return NS_NewNativeLocalFile(nsDependentCString(path
), true, aFile
);
476 case OS_TemporaryDirectory
:
479 DWORD len
= ::GetTempPathW(MAX_PATH
, path
);
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;
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
) {
505 return NS_NewNativeLocalFile(nsDependentCString(tPath
), true, aFile
);
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
);
523 return GetOSXFolderType(kUserDomain
, kDesktopFolderType
, aFile
);
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
,
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) {
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) {
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
)) {
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) {
606 rv
= NS_NewLocalFile(nsDependentString(path
, len
), true, aFile
);
607 if (NS_SUCCEEDED(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
);
622 // Need enough space to add the trailing backslash
623 if (len
> MAX_PATH
- 2) {
630 return NS_NewLocalFile(nsDependentString(path
, len
), true, aFile
);
635 return GetWindowsFolder(CSIDL_PROGRAMS
, aFile
);
638 case Win_Downloads
: {
639 // Defined in KnownFolders.h.
640 GUID folderid_downloads
= {
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
648 if (NS_ERROR_FAILURE
== rv
) {
649 rv
= GetWindowsFolder(CSIDL_DESKTOP
, aFile
);
654 case Win_Favorites
: {
655 return GetWindowsFolder(CSIDL_FAVORITES
, aFile
);
657 case Win_Desktopdirectory
: {
658 return GetWindowsFolder(CSIDL_DESKTOPDIRECTORY
, aFile
);
661 return GetWindowsFolder(CSIDL_COOKIES
, aFile
);
664 nsresult rv
= GetWindowsFolder(CSIDL_APPDATA
, aFile
);
666 rv
= GetRegWindowsAppDataFolder(false, aFile
);
670 case Win_LocalAppdata
: {
671 nsresult rv
= GetWindowsFolder(CSIDL_LOCAL_APPDATA
, aFile
);
673 rv
= GetRegWindowsAppDataFolder(true, aFile
);
677 # if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
678 case Win_Documents
: {
679 return GetLibrarySaveToPath(CSIDL_MYDOCUMENTS
, FOLDERID_DocumentsLibrary
,
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
);
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
));
712 rv
= localMacFile
->InitWithCFURL(
713 CocoaFileUtils::GetTemporaryFolder().get());
720 err
= ::FSFindFolder(aDomain
, aFolderType
, kCreateFolder
, &fsRef
);
722 NS_NewLocalFile(u
""_ns
, true, aLocalFile
);
723 nsCOMPtr
<nsILocalFileMac
> localMacFile(do_QueryInterface(*aLocalFile
));
725 rv
= localMacFile
->InitWithFSRef(&fsRef
);