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/. */
6
7 #include "SpecialSystemDirectory.h"
8 #include "nsString.h"
9 #include "nsDependentString.h"
10
11 #if defined(XP_WIN)
12
13 # include <windows.h>
14 # include <stdlib.h>
15 # include <stdio.h>
16 # include <string.h>
17 # include <direct.h>
18 # include <shlobj.h>
19 # include <knownfolders.h>
20 # include <guiddef.h>
21
22 #elif defined(XP_UNIX)
23
24 # include <limits.h>
25 # include <unistd.h>
26 # include <stdlib.h>
27 # include <sys/param.h>
28 # include "prenv.h"
29 # if defined(MOZ_WIDGET_COCOA)
30 # include "CocoaFileUtils.h"
31 # endif
32
33 #endif
34
35 #ifndef MAXPATHLEN
36 # ifdef PATH_MAX
37 # define MAXPATHLEN PATH_MAX
38 # elif defined(MAX_PATH)
39 # define MAXPATHLEN MAX_PATH
40 # elif defined(_MAX_PATH)
41 # define MAXPATHLEN _MAX_PATH
42 # elif defined(CCHMAXPATH)
43 # define MAXPATHLEN CCHMAXPATH
44 # else
45 # define MAXPATHLEN 1024
46 # endif
47 #endif
48
49 #if defined(XP_WIN)
50
GetKnownFolder(GUID * aGuid,nsIFile ** aFile)51 static nsresult GetKnownFolder(GUID* aGuid, nsIFile** aFile) {
52 if (!aGuid) {
53 return NS_ERROR_FAILURE;
54 }
55
56 PWSTR path = nullptr;
57 SHGetKnownFolderPath(*aGuid, 0, nullptr, &path);
58
59 if (!path) {
60 return NS_ERROR_FAILURE;
61 }
62
63 nsresult rv = NS_NewLocalFile(nsDependentString(path), true, aFile);
64
65 CoTaskMemFree(path);
66 return rv;
67 }
68
GetWindowsFolder(int aFolder,nsIFile ** aFile)69 static nsresult GetWindowsFolder(int aFolder, nsIFile** aFile) {
70 WCHAR path_orig[MAX_PATH + 3];
71 WCHAR* path = path_orig + 1;
72 BOOL result = SHGetSpecialFolderPathW(nullptr, path, aFolder, true);
73
74 if (!result) {
75 return NS_ERROR_FAILURE;
76 }
77
78 // Append the trailing slash
79 int len = wcslen(path);
80 if (len == 0) {
81 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
82 }
83 if (len > 1 && path[len - 1] != L'\\') {
84 path[len] = L'\\';
85 path[++len] = L'\0';
86 }
87
88 return NS_NewLocalFile(nsDependentString(path, len), true, aFile);
89 }
90
91 # if WINVER < 0x0601
SHLoadLibraryFromKnownFolder(REFKNOWNFOLDERID aFolderId,DWORD aMode,REFIID riid,void ** ppv)92 __inline HRESULT SHLoadLibraryFromKnownFolder(REFKNOWNFOLDERID aFolderId,
93 DWORD aMode, REFIID riid,
94 void** ppv) {
95 *ppv = nullptr;
96 IShellLibrary* plib;
97 HRESULT hr = CoCreateInstance(CLSID_ShellLibrary, nullptr,
98 CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&plib));
99 if (SUCCEEDED(hr)) {
100 hr = plib->LoadLibraryFromKnownFolder(aFolderId, aMode);
101 if (SUCCEEDED(hr)) {
102 hr = plib->QueryInterface(riid, ppv);
103 }
104 plib->Release();
105 }
106 return hr;
107 }
108 # endif
109
110 # if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
111 /*
112 * Return the default save-to location for the Windows Library passed in
113 * through aFolderId.
114 */
GetLibrarySaveToPath(int aFallbackFolderId,REFKNOWNFOLDERID aFolderId,nsIFile ** aFile)115 static nsresult GetLibrarySaveToPath(int aFallbackFolderId,
116 REFKNOWNFOLDERID aFolderId,
117 nsIFile** aFile) {
118 RefPtr<IShellLibrary> shellLib;
119 RefPtr<IShellItem> savePath;
120 SHLoadLibraryFromKnownFolder(aFolderId, STGM_READ, IID_IShellLibrary,
121 getter_AddRefs(shellLib));
122
123 if (shellLib && SUCCEEDED(shellLib->GetDefaultSaveFolder(
124 DSFT_DETECT, IID_IShellItem, getter_AddRefs(savePath)))) {
125 wchar_t* str = nullptr;
126 if (SUCCEEDED(savePath->GetDisplayName(SIGDN_FILESYSPATH, &str))) {
127 nsAutoString path;
128 path.Assign(str);
129 path.Append('\\');
130 nsresult rv = NS_NewLocalFile(path, false, aFile);
131 CoTaskMemFree(str);
132 return rv;
133 }
134 }
135
136 return GetWindowsFolder(aFallbackFolderId, aFile);
137 }
138 # endif
139
140 /**
141 * Provides a fallback for getting the path to APPDATA or LOCALAPPDATA by
142 * querying the registry when the call to SHGetSpecialFolderPathW is unable to
143 * provide these paths (Bug 513958).
144 */
GetRegWindowsAppDataFolder(bool aLocal,nsIFile ** aFile)145 static nsresult GetRegWindowsAppDataFolder(bool aLocal, nsIFile** aFile) {
146 HKEY key;
147 LPCWSTR keyName =
148 L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
149 DWORD res = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &key);
150 if (res != ERROR_SUCCESS) {
151 return NS_ERROR_FAILURE;
152 }
153
154 WCHAR path[MAX_PATH + 2];
155 DWORD type, size;
156 res = RegQueryValueExW(key, (aLocal ? L"Local AppData" : L"AppData"), nullptr,
157 &type, (LPBYTE)&path, &size);
158 ::RegCloseKey(key);
159 // The call to RegQueryValueExW must succeed, the type must be REG_SZ, the
160 // buffer size must not equal 0, and the buffer size be a multiple of 2.
161 if (res != ERROR_SUCCESS || type != REG_SZ || size == 0 || size % 2 != 0) {
162 return NS_ERROR_FAILURE;
163 }
164
165 // Append the trailing slash
166 int len = wcslen(path);
167 if (len > 1 && path[len - 1] != L'\\') {
168 path[len] = L'\\';
169 path[++len] = L'\0';
170 }
171
172 return NS_NewLocalFile(nsDependentString(path, len), true, aFile);
173 }
174
175 #endif // XP_WIN
176
177 #if defined(XP_UNIX)
GetUnixHomeDir(nsIFile ** aFile)178 static nsresult GetUnixHomeDir(nsIFile** aFile) {
179 # if defined(ANDROID)
180 // XXX no home dir on android; maybe we should return the sdcard if present?
181 return NS_ERROR_FAILURE;
182 # else
183 return NS_NewNativeLocalFile(nsDependentCString(PR_GetEnv("HOME")), true,
184 aFile);
185 # endif
186 }
187
188 /*
189 The following license applies to the xdg_user_dir_lookup function:
190
191 Copyright (c) 2007 Red Hat, Inc.
192
193 Permission is hereby granted, free of charge, to any person
194 obtaining a copy of this software and associated documentation files
195 (the "Software"), to deal in the Software without restriction,
196 including without limitation the rights to use, copy, modify, merge,
197 publish, distribute, sublicense, and/or sell copies of the Software,
198 and to permit persons to whom the Software is furnished to do so,
199 subject to the following conditions:
200
201 The above copyright notice and this permission notice shall be
202 included in all copies or substantial portions of the Software.
203
204 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
205 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
206 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
207 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
208 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
209 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
210 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
211 SOFTWARE.
212 */
213
xdg_user_dir_lookup(const char * aType)214 static char* xdg_user_dir_lookup(const char* aType) {
215 FILE* file;
216 char* home_dir;
217 char* config_home;
218 char* config_file;
219 char buffer[512];
220 char* user_dir;
221 char* p;
222 char* d;
223 int len;
224 int relative;
225
226 home_dir = getenv("HOME");
227
228 if (!home_dir) {
229 goto error;
230 }
231
232 config_home = getenv("XDG_CONFIG_HOME");
233 if (!config_home || config_home[0] == 0) {
234 config_file =
235 (char*)malloc(strlen(home_dir) + strlen("/.config/user-dirs.dirs") + 1);
236 if (!config_file) {
237 goto error;
238 }
239
240 strcpy(config_file, home_dir);
241 strcat(config_file, "/.config/user-dirs.dirs");
242 } else {
243 config_file =
244 (char*)malloc(strlen(config_home) + strlen("/user-dirs.dirs") + 1);
245 if (!config_file) {
246 goto error;
247 }
248
249 strcpy(config_file, config_home);
250 strcat(config_file, "/user-dirs.dirs");
251 }
252
253 file = fopen(config_file, "r");
254 free(config_file);
255 if (!file) {
256 goto error;
257 }
258
259 user_dir = nullptr;
260 while (fgets(buffer, sizeof(buffer), file)) {
261 /* Remove newline at end */
262 len = strlen(buffer);
263 if (len > 0 && buffer[len - 1] == '\n') {
264 buffer[len - 1] = 0;
265 }
266
267 p = buffer;
268 while (*p == ' ' || *p == '\t') {
269 p++;
270 }
271
272 if (strncmp(p, "XDG_", 4) != 0) {
273 continue;
274 }
275 p += 4;
276 if (strncmp(p, aType, strlen(aType)) != 0) {
277 continue;
278 }
279 p += strlen(aType);
280 if (strncmp(p, "_DIR", 4) != 0) {
281 continue;
282 }
283 p += 4;
284
285 while (*p == ' ' || *p == '\t') {
286 p++;
287 }
288
289 if (*p != '=') {
290 continue;
291 }
292 p++;
293
294 while (*p == ' ' || *p == '\t') {
295 p++;
296 }
297
298 if (*p != '"') {
299 continue;
300 }
301 p++;
302
303 relative = 0;
304 if (strncmp(p, "$HOME/", 6) == 0) {
305 p += 6;
306 relative = 1;
307 } else if (*p != '/') {
308 continue;
309 }
310
311 if (relative) {
312 user_dir = (char*)malloc(strlen(home_dir) + 1 + strlen(p) + 1);
313 if (!user_dir) {
314 goto error2;
315 }
316
317 strcpy(user_dir, home_dir);
318 strcat(user_dir, "/");
319 } else {
320 user_dir = (char*)malloc(strlen(p) + 1);
321 if (!user_dir) {
322 goto error2;
323 }
324
325 *user_dir = 0;
326 }
327
328 d = user_dir + strlen(user_dir);
329 while (*p && *p != '"') {
330 if ((*p == '\\') && (*(p + 1) != 0)) {
331 p++;
332 }
333 *d++ = *p++;
334 }
335 *d = 0;
336 }
337 error2:
338 fclose(file);
339
340 if (user_dir) {
341 return user_dir;
342 }
343
344 error:
345 return nullptr;
346 }
347
348 static const char xdg_user_dirs[] =
349 "DESKTOP\0"
350 "DOCUMENTS\0"
351 "DOWNLOAD\0"
352 "MUSIC\0"
353 "PICTURES\0"
354 "PUBLICSHARE\0"
355 "TEMPLATES\0"
356 "VIDEOS";
357
358 static const uint8_t xdg_user_dir_offsets[] = {0, 8, 18, 27, 33, 42, 54, 64};
359
GetUnixXDGUserDirectory(SystemDirectories aSystemDirectory,nsIFile ** aFile)360 static nsresult GetUnixXDGUserDirectory(SystemDirectories aSystemDirectory,
361 nsIFile** aFile) {
362 char* dir = xdg_user_dir_lookup(
363 xdg_user_dirs +
364 xdg_user_dir_offsets[aSystemDirectory - Unix_XDG_Desktop]);
365
366 nsresult rv;
367 nsCOMPtr<nsIFile> file;
368 if (dir) {
369 rv = NS_NewNativeLocalFile(nsDependentCString(dir), true,
370 getter_AddRefs(file));
371 free(dir);
372 } else if (Unix_XDG_Desktop == aSystemDirectory) {
373 // for the XDG desktop dir, fall back to HOME/Desktop
374 // (for historical compatibility)
375 rv = GetUnixHomeDir(getter_AddRefs(file));
376 if (NS_FAILED(rv)) {
377 return rv;
378 }
379
380 rv = file->AppendNative("Desktop"_ns);
381 } else {
382 // no fallback for the other XDG dirs
383 rv = NS_ERROR_FAILURE;
384 }
385
386 if (NS_FAILED(rv)) {
387 return rv;
388 }
389
390 bool exists;
391 rv = file->Exists(&exists);
392 if (NS_FAILED(rv)) {
393 return rv;
394 }
395 if (!exists) {
396 rv = file->Create(nsIFile::DIRECTORY_TYPE, 0755);
397 if (NS_FAILED(rv)) {
398 return rv;
399 }
400 }
401
402 *aFile = nullptr;
403 file.swap(*aFile);
404
405 return NS_OK;
406 }
407 #endif
408
GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory,nsIFile ** aFile)409 nsresult GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory,
410 nsIFile** aFile) {
411 #if defined(XP_WIN)
412 WCHAR path[MAX_PATH];
413 #else
414 char path[MAXPATHLEN];
415 #endif
416
417 switch (aSystemSystemDirectory) {
418 case OS_CurrentWorkingDirectory:
419 #if defined(XP_WIN)
420 if (!_wgetcwd(path, MAX_PATH)) {
421 return NS_ERROR_FAILURE;
422 }
423 return NS_NewLocalFile(nsDependentString(path), true, aFile);
424 #else
425 if (!getcwd(path, MAXPATHLEN)) {
426 return NS_ERROR_FAILURE;
427 }
428 #endif
429
430 #if !defined(XP_WIN)
431 return NS_NewNativeLocalFile(nsDependentCString(path), true, aFile);
432 #endif
433
434 case OS_TemporaryDirectory:
435 #if defined(XP_WIN)
436 {
437 DWORD len = ::GetTempPathW(MAX_PATH, path);
438 if (len == 0) {
439 break;
440 }
441 return NS_NewLocalFile(nsDependentString(path, len), true, aFile);
442 }
443 #elif defined(MOZ_WIDGET_COCOA)
444 {
445 return GetOSXFolderType(kUserDomain, kTemporaryFolderType, aFile);
446 }
447
448 #elif defined(XP_UNIX)
449 {
450 static const char* tPath = nullptr;
451 if (!tPath) {
452 tPath = PR_GetEnv("TMPDIR");
453 if (!tPath || !*tPath) {
454 tPath = PR_GetEnv("TMP");
455 if (!tPath || !*tPath) {
456 tPath = PR_GetEnv("TEMP");
457 if (!tPath || !*tPath) {
458 tPath = "/tmp/";
459 }
460 }
461 }
462 }
463 return NS_NewNativeLocalFile(nsDependentCString(tPath), true, aFile);
464 }
465 #else
466 break;
467 #endif
468 #if defined(XP_WIN)
469 case Win_SystemDirectory: {
470 int32_t len = ::GetSystemDirectoryW(path, MAX_PATH);
471
472 // Need enough space to add the trailing backslash
473 if (!len || len > MAX_PATH - 2) {
474 break;
475 }
476 path[len] = L'\\';
477 path[++len] = L'\0';
478
479 return NS_NewLocalFile(nsDependentString(path, len), true, aFile);
480 }
481
482 case Win_WindowsDirectory: {
483 int32_t len = ::GetWindowsDirectoryW(path, MAX_PATH);
484
485 // Need enough space to add the trailing backslash
486 if (!len || len > MAX_PATH - 2) {
487 break;
488 }
489
490 path[len] = L'\\';
491 path[++len] = L'\0';
492
493 return NS_NewLocalFile(nsDependentString(path, len), true, aFile);
494 }
495
496 case Win_ProgramFiles: {
497 return GetWindowsFolder(CSIDL_PROGRAM_FILES, aFile);
498 }
499
500 case Win_HomeDirectory: {
501 nsresult rv = GetWindowsFolder(CSIDL_PROFILE, aFile);
502 if (NS_SUCCEEDED(rv)) {
503 return rv;
504 }
505
506 int32_t len;
507 if ((len = ::GetEnvironmentVariableW(L"HOME", path, MAX_PATH)) > 0) {
508 // Need enough space to add the trailing backslash
509 if (len > MAX_PATH - 2) {
510 break;
511 }
512
513 path[len] = L'\\';
514 path[++len] = L'\0';
515
516 rv = NS_NewLocalFile(nsDependentString(path, len), true, aFile);
517 if (NS_SUCCEEDED(rv)) {
518 return rv;
519 }
520 }
521
522 len = ::GetEnvironmentVariableW(L"HOMEDRIVE", path, MAX_PATH);
523 if (0 < len && len < MAX_PATH) {
524 WCHAR temp[MAX_PATH];
525 DWORD len2 = ::GetEnvironmentVariableW(L"HOMEPATH", temp, MAX_PATH);
526 if (0 < len2 && len + len2 < MAX_PATH) {
527 wcsncat(path, temp, len2);
528 }
529
530 len = wcslen(path);
531
532 // Need enough space to add the trailing backslash
533 if (len > MAX_PATH - 2) {
534 break;
535 }
536
537 path[len] = L'\\';
538 path[++len] = L'\0';
539
540 return NS_NewLocalFile(nsDependentString(path, len), true, aFile);
541 }
542 }
543 case Win_Programs: {
544 return GetWindowsFolder(CSIDL_PROGRAMS, aFile);
545 }
546
547 case Win_Downloads: {
548 // Defined in KnownFolders.h.
549 GUID folderid_downloads = {
550 0x374de290,
551 0x123f,
552 0x4565,
553 {0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b}};
554 nsresult rv = GetKnownFolder(&folderid_downloads, aFile);
555 // On WinXP, there is no downloads folder, default
556 // to 'Desktop'.
557 if (NS_ERROR_FAILURE == rv) {
558 rv = GetWindowsFolder(CSIDL_DESKTOP, aFile);
559 }
560 return rv;
561 }
562
563 case Win_Favorites: {
564 return GetWindowsFolder(CSIDL_FAVORITES, aFile);
565 }
566 case Win_Desktopdirectory: {
567 return GetWindowsFolder(CSIDL_DESKTOPDIRECTORY, aFile);
568 }
569 case Win_Cookies: {
570 return GetWindowsFolder(CSIDL_COOKIES, aFile);
571 }
572 case Win_Appdata: {
573 nsresult rv = GetWindowsFolder(CSIDL_APPDATA, aFile);
574 if (NS_FAILED(rv)) {
575 rv = GetRegWindowsAppDataFolder(false, aFile);
576 }
577 return rv;
578 }
579 case Win_LocalAppdata: {
580 nsresult rv = GetWindowsFolder(CSIDL_LOCAL_APPDATA, aFile);
581 if (NS_FAILED(rv)) {
582 rv = GetRegWindowsAppDataFolder(true, aFile);
583 }
584 return rv;
585 }
586 # if defined(MOZ_SANDBOX)
587 case Win_LocalAppdataLow: {
588 GUID localAppDataLowGuid = FOLDERID_LocalAppDataLow;
589 return GetKnownFolder(&localAppDataLowGuid, aFile);
590 }
591 # endif
592 # if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
593 case Win_Documents: {
594 return GetLibrarySaveToPath(CSIDL_MYDOCUMENTS, FOLDERID_DocumentsLibrary,
595 aFile);
596 }
597 # endif
598 #endif // XP_WIN
599
600 #if defined(XP_UNIX)
601 case Unix_HomeDirectory:
602 return GetUnixHomeDir(aFile);
603
604 case Unix_XDG_Desktop:
605 case Unix_XDG_Download:
606 return GetUnixXDGUserDirectory(aSystemSystemDirectory, aFile);
607 #endif
608
609 default:
610 break;
611 }
612 return NS_ERROR_NOT_AVAILABLE;
613 }
614
615 #if defined(MOZ_WIDGET_COCOA)
GetOSXFolderType(short aDomain,OSType aFolderType,nsIFile ** aLocalFile)616 nsresult GetOSXFolderType(short aDomain, OSType aFolderType,
617 nsIFile** aLocalFile) {
618 nsresult rv = NS_ERROR_FAILURE;
619
620 if (aFolderType == kTemporaryFolderType) {
621 NS_NewLocalFile(u""_ns, true, aLocalFile);
622 nsCOMPtr<nsILocalFileMac> localMacFile(do_QueryInterface(*aLocalFile));
623 if (localMacFile) {
624 rv = localMacFile->InitWithCFURL(
625 CocoaFileUtils::GetTemporaryFolderCFURLRef());
626 }
627 return rv;
628 }
629
630 OSErr err;
631 FSRef fsRef;
632 err = ::FSFindFolder(aDomain, aFolderType, kCreateFolder, &fsRef);
633 if (err == noErr) {
634 NS_NewLocalFile(u""_ns, true, aLocalFile);
635 nsCOMPtr<nsILocalFileMac> localMacFile(do_QueryInterface(*aLocalFile));
636 if (localMacFile) {
637 rv = localMacFile->InitWithFSRef(&fsRef);
638 }
639 }
640 return rv;
641 }
642 #endif
643