diff options
Diffstat (limited to 'xpcom/io/SpecialSystemDirectory.cpp')
-rw-r--r-- | xpcom/io/SpecialSystemDirectory.cpp | 830 |
1 files changed, 830 insertions, 0 deletions
diff --git a/xpcom/io/SpecialSystemDirectory.cpp b/xpcom/io/SpecialSystemDirectory.cpp new file mode 100644 index 000000000..9ce8eb85b --- /dev/null +++ b/xpcom/io/SpecialSystemDirectory.cpp @@ -0,0 +1,830 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SpecialSystemDirectory.h" +#include "nsString.h" +#include "nsDependentString.h" +#include "nsAutoPtr.h" + +#if defined(XP_WIN) + +#include <windows.h> +#include <shlobj.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <direct.h> +#include <shlobj.h> +#include <knownfolders.h> +#include <guiddef.h> +#include "mozilla/WindowsVersion.h" + +using mozilla::IsWin7OrLater; + +#elif defined(XP_UNIX) + +#include <limits.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/param.h> +#include "prenv.h" +#if defined(MOZ_WIDGET_COCOA) +#include "CocoaFileUtils.h" +#endif + +#endif + +#ifndef MAXPATHLEN +#ifdef PATH_MAX +#define MAXPATHLEN PATH_MAX +#elif defined(MAX_PATH) +#define MAXPATHLEN MAX_PATH +#elif defined(_MAX_PATH) +#define MAXPATHLEN _MAX_PATH +#elif defined(CCHMAXPATH) +#define MAXPATHLEN CCHMAXPATH +#else +#define MAXPATHLEN 1024 +#endif +#endif + +#ifdef XP_WIN +typedef HRESULT (WINAPI* nsGetKnownFolderPath)(GUID& rfid, + DWORD dwFlags, + HANDLE hToken, + PWSTR* ppszPath); + +static nsGetKnownFolderPath gGetKnownFolderPath = nullptr; +#endif + +void +StartupSpecialSystemDirectory() +{ +#if defined (XP_WIN) + // SHGetKnownFolderPath is only available on Windows Vista + // so that we need to use GetProcAddress to get the pointer. + HMODULE hShell32DLLInst = GetModuleHandleW(L"shell32.dll"); + if (hShell32DLLInst) { + gGetKnownFolderPath = (nsGetKnownFolderPath) + GetProcAddress(hShell32DLLInst, "SHGetKnownFolderPath"); + } +#endif +} + +#if defined (XP_WIN) + +static nsresult +GetKnownFolder(GUID* aGuid, nsIFile** aFile) +{ + if (!aGuid || !gGetKnownFolderPath) { + return NS_ERROR_FAILURE; + } + + PWSTR path = nullptr; + gGetKnownFolderPath(*aGuid, 0, nullptr, &path); + + if (!path) { + return NS_ERROR_FAILURE; + } + + nsresult rv = NS_NewLocalFile(nsDependentString(path), + true, + aFile); + + CoTaskMemFree(path); + return rv; +} + +static nsresult +GetWindowsFolder(int aFolder, nsIFile** aFile) +{ + WCHAR path_orig[MAX_PATH + 3]; + WCHAR* path = path_orig + 1; + HRESULT result = SHGetSpecialFolderPathW(nullptr, path, aFolder, true); + + if (!SUCCEEDED(result)) { + return NS_ERROR_FAILURE; + } + + // Append the trailing slash + int len = wcslen(path); + if (len > 1 && path[len - 1] != L'\\') { + path[len] = L'\\'; + path[++len] = L'\0'; + } + + return NS_NewLocalFile(nsDependentString(path, len), true, aFile); +} + +__inline HRESULT +SHLoadLibraryFromKnownFolder(REFKNOWNFOLDERID aFolderId, DWORD aMode, + REFIID riid, void** ppv) +{ + *ppv = nullptr; + IShellLibrary* plib; + HRESULT hr = CoCreateInstance(CLSID_ShellLibrary, nullptr, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&plib)); + if (SUCCEEDED(hr)) { + hr = plib->LoadLibraryFromKnownFolder(aFolderId, aMode); + if (SUCCEEDED(hr)) { + hr = plib->QueryInterface(riid, ppv); + } + plib->Release(); + } + return hr; +} + +/* + * Check to see if we're on Win7 and up, and if so, returns the default + * save-to location for the Windows Library passed in through aFolderId. + * Otherwise falls back on pre-win7 GetWindowsFolder. + */ +static nsresult +GetLibrarySaveToPath(int aFallbackFolderId, REFKNOWNFOLDERID aFolderId, + nsIFile** aFile) +{ + // Skip off checking for library support if the os is Vista or lower. + if (!IsWin7OrLater()) { + return GetWindowsFolder(aFallbackFolderId, aFile); + } + + RefPtr<IShellLibrary> shellLib; + RefPtr<IShellItem> savePath; + HRESULT hr = + SHLoadLibraryFromKnownFolder(aFolderId, STGM_READ, + IID_IShellLibrary, getter_AddRefs(shellLib)); + + if (shellLib && + SUCCEEDED(shellLib->GetDefaultSaveFolder(DSFT_DETECT, IID_IShellItem, + getter_AddRefs(savePath)))) { + wchar_t* str = nullptr; + if (SUCCEEDED(savePath->GetDisplayName(SIGDN_FILESYSPATH, &str))) { + nsAutoString path; + path.Assign(str); + path.Append('\\'); + nsresult rv = + NS_NewLocalFile(path, false, aFile); + CoTaskMemFree(str); + return rv; + } + } + + return GetWindowsFolder(aFallbackFolderId, aFile); +} + +/** + * Provides a fallback for getting the path to APPDATA or LOCALAPPDATA by + * querying the registry when the call to SHGetSpecialFolderPathW is unable to + * provide these paths (Bug 513958). + */ +static nsresult +GetRegWindowsAppDataFolder(bool aLocal, nsIFile** aFile) +{ + HKEY key; + NS_NAMED_LITERAL_STRING(keyName, + "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"); + DWORD res = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName.get(), 0, KEY_READ, + &key); + if (res != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + WCHAR path[MAX_PATH + 2]; + DWORD type, size; + res = RegQueryValueExW(key, (aLocal ? L"Local AppData" : L"AppData"), + nullptr, &type, (LPBYTE)&path, &size); + ::RegCloseKey(key); + // The call to RegQueryValueExW must succeed, the type must be REG_SZ, the + // buffer size must not equal 0, and the buffer size be a multiple of 2. + if (res != ERROR_SUCCESS || type != REG_SZ || size == 0 || size % 2 != 0) { + return NS_ERROR_FAILURE; + } + + // Append the trailing slash + int len = wcslen(path); + if (len > 1 && path[len - 1] != L'\\') { + path[len] = L'\\'; + path[++len] = L'\0'; + } + + return NS_NewLocalFile(nsDependentString(path, len), true, aFile); +} + +#endif // XP_WIN + +#if defined(XP_UNIX) +static nsresult +GetUnixHomeDir(nsIFile** aFile) +{ +#if defined(ANDROID) + // XXX no home dir on android; maybe we should return the sdcard if present? + return NS_ERROR_FAILURE; +#else + return NS_NewNativeLocalFile(nsDependentCString(PR_GetEnv("HOME")), + true, aFile); +#endif +} + +/* + The following license applies to the xdg_user_dir_lookup function: + + Copyright (c) 2007 Red Hat, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +static char* +xdg_user_dir_lookup(const char* aType) +{ + FILE* file; + char* home_dir; + char* config_home; + char* config_file; + char buffer[512]; + char* user_dir; + char* p; + char* d; + int len; + int relative; + + home_dir = getenv("HOME"); + + if (!home_dir) { + goto error; + } + + config_home = getenv("XDG_CONFIG_HOME"); + if (!config_home || config_home[0] == 0) { + config_file = (char*)malloc(strlen(home_dir) + + strlen("/.config/user-dirs.dirs") + 1); + if (!config_file) { + goto error; + } + + strcpy(config_file, home_dir); + strcat(config_file, "/.config/user-dirs.dirs"); + } else { + config_file = (char*)malloc(strlen(config_home) + + strlen("/user-dirs.dirs") + 1); + if (!config_file) { + goto error; + } + + strcpy(config_file, config_home); + strcat(config_file, "/user-dirs.dirs"); + } + + file = fopen(config_file, "r"); + free(config_file); + if (!file) { + goto error; + } + + user_dir = nullptr; + while (fgets(buffer, sizeof(buffer), file)) { + /* Remove newline at end */ + len = strlen(buffer); + if (len > 0 && buffer[len - 1] == '\n') { + buffer[len - 1] = 0; + } + + p = buffer; + while (*p == ' ' || *p == '\t') { + p++; + } + + if (strncmp(p, "XDG_", 4) != 0) { + continue; + } + p += 4; + if (strncmp(p, aType, strlen(aType)) != 0) { + continue; + } + p += strlen(aType); + if (strncmp(p, "_DIR", 4) != 0) { + continue; + } + p += 4; + + while (*p == ' ' || *p == '\t') { + p++; + } + + if (*p != '=') { + continue; + } + p++; + + while (*p == ' ' || *p == '\t') { + p++; + } + + if (*p != '"') { + continue; + } + p++; + + relative = 0; + if (strncmp(p, "$HOME/", 6) == 0) { + p += 6; + relative = 1; + } else if (*p != '/') { + continue; + } + + if (relative) { + user_dir = (char*)malloc(strlen(home_dir) + 1 + strlen(p) + 1); + if (!user_dir) { + goto error2; + } + + strcpy(user_dir, home_dir); + strcat(user_dir, "/"); + } else { + user_dir = (char*)malloc(strlen(p) + 1); + if (!user_dir) { + goto error2; + } + + *user_dir = 0; + } + + d = user_dir + strlen(user_dir); + while (*p && *p != '"') { + if ((*p == '\\') && (*(p + 1) != 0)) { + p++; + } + *d++ = *p++; + } + *d = 0; + } +error2: + fclose(file); + + if (user_dir) { + return user_dir; + } + +error: + return nullptr; +} + +static const char xdg_user_dirs[] = + "DESKTOP\0" + "DOCUMENTS\0" + "DOWNLOAD\0" + "MUSIC\0" + "PICTURES\0" + "PUBLICSHARE\0" + "TEMPLATES\0" + "VIDEOS"; + +static const uint8_t xdg_user_dir_offsets[] = { + 0, + 8, + 18, + 27, + 33, + 42, + 54, + 64 +}; + +static nsresult +GetUnixXDGUserDirectory(SystemDirectories aSystemDirectory, + nsIFile** aFile) +{ + char* dir = xdg_user_dir_lookup( + xdg_user_dirs + xdg_user_dir_offsets[aSystemDirectory - Unix_XDG_Desktop]); + + nsresult rv; + nsCOMPtr<nsIFile> file; + if (dir) { + rv = NS_NewNativeLocalFile(nsDependentCString(dir), true, + getter_AddRefs(file)); + free(dir); + } else if (Unix_XDG_Desktop == aSystemDirectory) { + // for the XDG desktop dir, fall back to HOME/Desktop + // (for historical compatibility) + rv = GetUnixHomeDir(getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = file->AppendNative(NS_LITERAL_CSTRING("Desktop")); + } else { + // no fallback for the other XDG dirs + rv = NS_ERROR_FAILURE; + } + + if (NS_FAILED(rv)) { + return rv; + } + + bool exists; + rv = file->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + if (!exists) { + rv = file->Create(nsIFile::DIRECTORY_TYPE, 0755); + if (NS_FAILED(rv)) { + return rv; + } + } + + *aFile = nullptr; + file.swap(*aFile); + + return NS_OK; +} +#endif + +nsresult +GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory, + nsIFile** aFile) +{ +#if defined(XP_WIN) + WCHAR path[MAX_PATH]; +#else + char path[MAXPATHLEN]; +#endif + + switch (aSystemSystemDirectory) { + case OS_CurrentWorkingDirectory: +#if defined(XP_WIN) + if (!_wgetcwd(path, MAX_PATH)) { + return NS_ERROR_FAILURE; + } + return NS_NewLocalFile(nsDependentString(path), + true, + aFile); +#else + if (!getcwd(path, MAXPATHLEN)) { + return NS_ERROR_FAILURE; + } +#endif + +#if !defined(XP_WIN) + return NS_NewNativeLocalFile(nsDependentCString(path), + true, + aFile); +#endif + + case OS_DriveDirectory: +#if defined (XP_WIN) + { + int32_t len = ::GetWindowsDirectoryW(path, MAX_PATH); + if (len == 0) { + break; + } + if (path[1] == char16_t(':') && path[2] == char16_t('\\')) { + path[3] = 0; + } + + return NS_NewLocalFile(nsDependentString(path), + true, + aFile); + } +#else + return NS_NewNativeLocalFile(nsDependentCString("/"), + true, + aFile); + +#endif + + case OS_TemporaryDirectory: +#if defined (XP_WIN) + { + DWORD len = ::GetTempPathW(MAX_PATH, path); + if (len == 0) { + break; + } + return NS_NewLocalFile(nsDependentString(path, len), + true, + aFile); + } +#elif defined(MOZ_WIDGET_COCOA) + { + return GetOSXFolderType(kUserDomain, kTemporaryFolderType, aFile); + } + +#elif defined(XP_UNIX) + { + static const char* tPath = nullptr; + if (!tPath) { + tPath = PR_GetEnv("TMPDIR"); + if (!tPath || !*tPath) { + tPath = PR_GetEnv("TMP"); + if (!tPath || !*tPath) { + tPath = PR_GetEnv("TEMP"); + if (!tPath || !*tPath) { + tPath = "/tmp/"; + } + } + } + } + return NS_NewNativeLocalFile(nsDependentCString(tPath), + true, + aFile); + } +#else + break; +#endif +#if defined (XP_WIN) + case Win_SystemDirectory: { + int32_t len = ::GetSystemDirectoryW(path, MAX_PATH); + + // Need enough space to add the trailing backslash + if (!len || len > MAX_PATH - 2) { + break; + } + path[len] = L'\\'; + path[++len] = L'\0'; + + return NS_NewLocalFile(nsDependentString(path, len), + true, + aFile); + } + + case Win_WindowsDirectory: { + int32_t len = ::GetWindowsDirectoryW(path, MAX_PATH); + + // Need enough space to add the trailing backslash + if (!len || len > MAX_PATH - 2) { + break; + } + + path[len] = L'\\'; + path[++len] = L'\0'; + + return NS_NewLocalFile(nsDependentString(path, len), + true, + aFile); + } + + case Win_ProgramFiles: { + return GetWindowsFolder(CSIDL_PROGRAM_FILES, aFile); + } + + case Win_HomeDirectory: { + nsresult rv = GetWindowsFolder(CSIDL_PROFILE, aFile); + if (NS_SUCCEEDED(rv)) { + return rv; + } + + int32_t len; + if ((len = ::GetEnvironmentVariableW(L"HOME", path, MAX_PATH)) > 0) { + // Need enough space to add the trailing backslash + if (len > MAX_PATH - 2) { + break; + } + + path[len] = L'\\'; + path[++len] = L'\0'; + + rv = NS_NewLocalFile(nsDependentString(path, len), + true, + aFile); + if (NS_SUCCEEDED(rv)) { + return rv; + } + } + + len = ::GetEnvironmentVariableW(L"HOMEDRIVE", path, MAX_PATH); + if (0 < len && len < MAX_PATH) { + WCHAR temp[MAX_PATH]; + DWORD len2 = ::GetEnvironmentVariableW(L"HOMEPATH", temp, MAX_PATH); + if (0 < len2 && len + len2 < MAX_PATH) { + wcsncat(path, temp, len2); + } + + len = wcslen(path); + + // Need enough space to add the trailing backslash + if (len > MAX_PATH - 2) { + break; + } + + path[len] = L'\\'; + path[++len] = L'\0'; + + return NS_NewLocalFile(nsDependentString(path, len), + true, + aFile); + } + } + case Win_Desktop: { + return GetWindowsFolder(CSIDL_DESKTOP, aFile); + } + case Win_Programs: { + return GetWindowsFolder(CSIDL_PROGRAMS, aFile); + } + + case Win_Downloads: { + // Defined in KnownFolders.h. + GUID folderid_downloads = { + 0x374de290, 0x123f, 0x4565, + { 0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b } + }; + nsresult rv = GetKnownFolder(&folderid_downloads, aFile); + // On WinXP, there is no downloads folder, default + // to 'Desktop'. + if (NS_ERROR_FAILURE == rv) { + rv = GetWindowsFolder(CSIDL_DESKTOP, aFile); + } + return rv; + } + + case Win_Controls: { + return GetWindowsFolder(CSIDL_CONTROLS, aFile); + } + case Win_Printers: { + return GetWindowsFolder(CSIDL_PRINTERS, aFile); + } + case Win_Personal: { + return GetWindowsFolder(CSIDL_PERSONAL, aFile); + } + case Win_Favorites: { + return GetWindowsFolder(CSIDL_FAVORITES, aFile); + } + case Win_Startup: { + return GetWindowsFolder(CSIDL_STARTUP, aFile); + } + case Win_Recent: { + return GetWindowsFolder(CSIDL_RECENT, aFile); + } + case Win_Sendto: { + return GetWindowsFolder(CSIDL_SENDTO, aFile); + } + case Win_Bitbucket: { + return GetWindowsFolder(CSIDL_BITBUCKET, aFile); + } + case Win_Startmenu: { + return GetWindowsFolder(CSIDL_STARTMENU, aFile); + } + case Win_Desktopdirectory: { + return GetWindowsFolder(CSIDL_DESKTOPDIRECTORY, aFile); + } + case Win_Drives: { + return GetWindowsFolder(CSIDL_DRIVES, aFile); + } + case Win_Network: { + return GetWindowsFolder(CSIDL_NETWORK, aFile); + } + case Win_Nethood: { + return GetWindowsFolder(CSIDL_NETHOOD, aFile); + } + case Win_Fonts: { + return GetWindowsFolder(CSIDL_FONTS, aFile); + } + case Win_Templates: { + return GetWindowsFolder(CSIDL_TEMPLATES, aFile); + } + case Win_Common_Startmenu: { + return GetWindowsFolder(CSIDL_COMMON_STARTMENU, aFile); + } + case Win_Common_Programs: { + return GetWindowsFolder(CSIDL_COMMON_PROGRAMS, aFile); + } + case Win_Common_Startup: { + return GetWindowsFolder(CSIDL_COMMON_STARTUP, aFile); + } + case Win_Common_Desktopdirectory: { + return GetWindowsFolder(CSIDL_COMMON_DESKTOPDIRECTORY, aFile); + } + case Win_Common_AppData: { + return GetWindowsFolder(CSIDL_COMMON_APPDATA, aFile); + } + case Win_Printhood: { + return GetWindowsFolder(CSIDL_PRINTHOOD, aFile); + } + case Win_Cookies: { + return GetWindowsFolder(CSIDL_COOKIES, aFile); + } + case Win_Appdata: { + nsresult rv = GetWindowsFolder(CSIDL_APPDATA, aFile); + if (NS_FAILED(rv)) { + rv = GetRegWindowsAppDataFolder(false, aFile); + } + return rv; + } + case Win_LocalAppdata: { + nsresult rv = GetWindowsFolder(CSIDL_LOCAL_APPDATA, aFile); + if (NS_FAILED(rv)) { + rv = GetRegWindowsAppDataFolder(true, aFile); + } + return rv; + } +#if defined(MOZ_CONTENT_SANDBOX) + case Win_LocalAppdataLow: { + // This should only really fail on versions pre-Vista, in which case this + // shouldn't have been used in the first place. + GUID localAppDataLowGuid = FOLDERID_LocalAppDataLow; + return GetKnownFolder(&localAppDataLowGuid, aFile); + } +#endif + case Win_Documents: { + return GetLibrarySaveToPath(CSIDL_MYDOCUMENTS, + FOLDERID_DocumentsLibrary, + aFile); + } + case Win_Pictures: { + return GetLibrarySaveToPath(CSIDL_MYPICTURES, + FOLDERID_PicturesLibrary, + aFile); + } + case Win_Music: { + return GetLibrarySaveToPath(CSIDL_MYMUSIC, + FOLDERID_MusicLibrary, + aFile); + } + case Win_Videos: { + return GetLibrarySaveToPath(CSIDL_MYVIDEO, + FOLDERID_VideosLibrary, + aFile); + } +#endif // XP_WIN + +#if defined(XP_UNIX) + case Unix_LocalDirectory: + return NS_NewNativeLocalFile(nsDependentCString("/usr/local/netscape/"), + true, + aFile); + case Unix_LibDirectory: + return NS_NewNativeLocalFile(nsDependentCString("/usr/local/lib/netscape/"), + true, + aFile); + + case Unix_HomeDirectory: + return GetUnixHomeDir(aFile); + + case Unix_XDG_Desktop: + case Unix_XDG_Documents: + case Unix_XDG_Download: + case Unix_XDG_Music: + case Unix_XDG_Pictures: + case Unix_XDG_PublicShare: + case Unix_XDG_Templates: + case Unix_XDG_Videos: + return GetUnixXDGUserDirectory(aSystemSystemDirectory, aFile); +#endif + + default: + break; + } + return NS_ERROR_NOT_AVAILABLE; +} + +#if defined (MOZ_WIDGET_COCOA) +nsresult +GetOSXFolderType(short aDomain, OSType aFolderType, nsIFile** aLocalFile) +{ + nsresult rv = NS_ERROR_FAILURE; + + if (aFolderType == kTemporaryFolderType) { + NS_NewLocalFile(EmptyString(), true, aLocalFile); + nsCOMPtr<nsILocalFileMac> localMacFile(do_QueryInterface(*aLocalFile)); + if (localMacFile) { + rv = localMacFile->InitWithCFURL( + CocoaFileUtils::GetTemporaryFolderCFURLRef()); + } + return rv; + } + + OSErr err; + FSRef fsRef; + err = ::FSFindFolder(aDomain, aFolderType, kCreateFolder, &fsRef); + if (err == noErr) { + NS_NewLocalFile(EmptyString(), true, aLocalFile); + nsCOMPtr<nsILocalFileMac> localMacFile(do_QueryInterface(*aLocalFile)); + if (localMacFile) { + rv = localMacFile->InitWithFSRef(&fsRef); + } + } + return rv; +} +#endif + |