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