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 /**
8  * Implementation of nsIFile for "unixy" systems.
9  */
10 
11 #include "mozilla/ArrayUtils.h"
12 #include "mozilla/Attributes.h"
13 #include "mozilla/DebugOnly.h"
14 #include "mozilla/Sprintf.h"
15 #include "mozilla/FilePreferences.h"
16 #include "prtime.h"
17 
18 #include <sys/fcntl.h>
19 #include <sys/select.h>
20 #include <sys/stat.h>
21 #include <sys/time.h>
22 #include <sys/types.h>
23 #include <unistd.h>
24 #include <fcntl.h>
25 #include <errno.h>
26 #include <utime.h>
27 #include <dirent.h>
28 #include <ctype.h>
29 #include <locale.h>
30 
31 #if defined(XP_MACOSX)
32 #  include <sys/xattr.h>
33 #endif
34 
35 #if defined(HAVE_SYS_QUOTA_H) && defined(HAVE_LINUX_QUOTA_H)
36 #  define USE_LINUX_QUOTACTL
37 #  include <sys/mount.h>
38 #  include <sys/quota.h>
39 #  include <sys/sysmacros.h>
40 #  ifndef BLOCK_SIZE
41 #    define BLOCK_SIZE 1024 /* kernel block size */
42 #  endif
43 #endif
44 
45 #include "nsDirectoryServiceDefs.h"
46 #include "nsCRT.h"
47 #include "nsCOMPtr.h"
48 #include "nsMemory.h"
49 #include "nsIFile.h"
50 #include "nsString.h"
51 #include "nsReadableUtils.h"
52 #include "nsLocalFile.h"
53 #include "prproces.h"
54 #include "nsIDirectoryEnumerator.h"
55 #include "nsSimpleEnumerator.h"
56 #include "private/pprio.h"
57 #include "prlink.h"
58 
59 #ifdef MOZ_WIDGET_GTK
60 #  include "nsIGIOService.h"
61 #endif
62 
63 #ifdef MOZ_WIDGET_COCOA
64 #  include <Carbon/Carbon.h>
65 #  include "CocoaFileUtils.h"
66 #  include "prmem.h"
67 #  include "plbase64.h"
68 
69 static nsresult MacErrorMapper(OSErr inErr);
70 #endif
71 
72 #ifdef MOZ_WIDGET_ANDROID
73 #  include "mozilla/java/GeckoAppShellWrappers.h"
74 #  include "nsIMIMEService.h"
75 #  include <linux/magic.h>
76 #endif
77 
78 #include "nsNativeCharsetUtils.h"
79 #include "nsTraceRefcnt.h"
80 #include "nsHashKeys.h"
81 
82 /**
83  *  we need these for statfs()
84  */
85 #ifdef HAVE_SYS_STATVFS_H
86 #  if defined(__osf__) && defined(__DECCXX)
87 extern "C" int statvfs(const char*, struct statvfs*);
88 #  endif
89 #  include <sys/statvfs.h>
90 #endif
91 
92 #ifdef HAVE_SYS_STATFS_H
93 #  include <sys/statfs.h>
94 #endif
95 
96 #ifdef HAVE_SYS_VFS_H
97 #  include <sys/vfs.h>
98 #endif
99 
100 #ifdef HAVE_SYS_MOUNT_H
101 #  include <sys/param.h>
102 #  include <sys/mount.h>
103 #endif
104 
105 #if defined(HAVE_STATVFS64) && (!defined(LINUX) && !defined(__osf__))
106 #  define STATFS statvfs64
107 #  define F_BSIZE f_frsize
108 #elif defined(HAVE_STATVFS) && (!defined(LINUX) && !defined(__osf__))
109 #  define STATFS statvfs
110 #  define F_BSIZE f_frsize
111 #elif defined(HAVE_STATFS64)
112 #  define STATFS statfs64
113 #  define F_BSIZE f_bsize
114 #elif defined(HAVE_STATFS)
115 #  define STATFS statfs
116 #  define F_BSIZE f_bsize
117 #endif
118 
119 using namespace mozilla;
120 
121 #define ENSURE_STAT_CACHE()                            \
122   do {                                                 \
123     if (!FillStatCache()) return NSRESULT_FOR_ERRNO(); \
124   } while (0)
125 
126 #define CHECK_mPath()                                     \
127   do {                                                    \
128     if (mPath.IsEmpty()) return NS_ERROR_NOT_INITIALIZED; \
129     if (!FilePreferences::IsAllowedPath(mPath))           \
130       return NS_ERROR_FILE_ACCESS_DENIED;                 \
131   } while (0)
132 
TimespecToMillis(const struct timespec & aTimeSpec)133 static PRTime TimespecToMillis(const struct timespec& aTimeSpec) {
134   return PRTime(aTimeSpec.tv_sec) * PR_MSEC_PER_SEC +
135          PRTime(aTimeSpec.tv_nsec) / PR_NSEC_PER_MSEC;
136 }
137 
138 /* directory enumerator */
139 class nsDirEnumeratorUnix final : public nsSimpleEnumerator,
140                                   public nsIDirectoryEnumerator {
141  public:
142   nsDirEnumeratorUnix();
143 
144   // nsISupports interface
145   NS_DECL_ISUPPORTS_INHERITED
146 
147   // nsISimpleEnumerator interface
148   NS_DECL_NSISIMPLEENUMERATOR
149 
150   // nsIDirectoryEnumerator interface
151   NS_DECL_NSIDIRECTORYENUMERATOR
152 
153   NS_IMETHOD Init(nsLocalFile* aParent, bool aIgnored);
154 
NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::) const155   NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::)
156 
157   const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); }
158 
159  private:
160   ~nsDirEnumeratorUnix() override;
161 
162  protected:
163   NS_IMETHOD GetNextEntry();
164 
165   DIR* mDir;
166   struct dirent* mEntry;
167   nsCString mParentPath;
168 };
169 
nsDirEnumeratorUnix()170 nsDirEnumeratorUnix::nsDirEnumeratorUnix() : mDir(nullptr), mEntry(nullptr) {}
171 
~nsDirEnumeratorUnix()172 nsDirEnumeratorUnix::~nsDirEnumeratorUnix() { Close(); }
173 
NS_IMPL_ISUPPORTS_INHERITED(nsDirEnumeratorUnix,nsSimpleEnumerator,nsIDirectoryEnumerator)174 NS_IMPL_ISUPPORTS_INHERITED(nsDirEnumeratorUnix, nsSimpleEnumerator,
175                             nsIDirectoryEnumerator)
176 
177 NS_IMETHODIMP
178 nsDirEnumeratorUnix::Init(nsLocalFile* aParent,
179                           bool aResolveSymlinks /*ignored*/) {
180   nsAutoCString dirPath;
181   if (NS_FAILED(aParent->GetNativePath(dirPath)) || dirPath.IsEmpty()) {
182     return NS_ERROR_FILE_INVALID_PATH;
183   }
184 
185   // When enumerating the directory, the paths must have a slash at the end.
186   nsAutoCString dirPathWithSlash(dirPath);
187   dirPathWithSlash.Append('/');
188   if (!FilePreferences::IsAllowedPath(dirPathWithSlash)) {
189     return NS_ERROR_FILE_ACCESS_DENIED;
190   }
191 
192   if (NS_FAILED(aParent->GetNativePath(mParentPath))) {
193     return NS_ERROR_FAILURE;
194   }
195 
196   mDir = opendir(dirPath.get());
197   if (!mDir) {
198     return NSRESULT_FOR_ERRNO();
199   }
200   return GetNextEntry();
201 }
202 
203 NS_IMETHODIMP
HasMoreElements(bool * aResult)204 nsDirEnumeratorUnix::HasMoreElements(bool* aResult) {
205   *aResult = mDir && mEntry;
206   if (!*aResult) {
207     Close();
208   }
209   return NS_OK;
210 }
211 
212 NS_IMETHODIMP
GetNext(nsISupports ** aResult)213 nsDirEnumeratorUnix::GetNext(nsISupports** aResult) {
214   nsCOMPtr<nsIFile> file;
215   nsresult rv = GetNextFile(getter_AddRefs(file));
216   if (NS_FAILED(rv)) {
217     return rv;
218   }
219   if (!file) {
220     return NS_ERROR_FAILURE;
221   }
222   file.forget(aResult);
223   return NS_OK;
224 }
225 
226 NS_IMETHODIMP
GetNextEntry()227 nsDirEnumeratorUnix::GetNextEntry() {
228   do {
229     errno = 0;
230     mEntry = readdir(mDir);
231 
232     // end of dir or error
233     if (!mEntry) {
234       return NSRESULT_FOR_ERRNO();
235     }
236 
237     // keep going past "." and ".."
238   } while (mEntry->d_name[0] == '.' &&
239            (mEntry->d_name[1] == '\0' ||                                // .\0
240             (mEntry->d_name[1] == '.' && mEntry->d_name[2] == '\0')));  // ..\0
241   return NS_OK;
242 }
243 
244 NS_IMETHODIMP
GetNextFile(nsIFile ** aResult)245 nsDirEnumeratorUnix::GetNextFile(nsIFile** aResult) {
246   nsresult rv;
247   if (!mDir || !mEntry) {
248     *aResult = nullptr;
249     return NS_OK;
250   }
251 
252   nsCOMPtr<nsIFile> file = new nsLocalFile();
253 
254   if (NS_FAILED(rv = file->InitWithNativePath(mParentPath)) ||
255       NS_FAILED(rv = file->AppendNative(nsDependentCString(mEntry->d_name)))) {
256     return rv;
257   }
258 
259   file.forget(aResult);
260   return GetNextEntry();
261 }
262 
263 NS_IMETHODIMP
Close()264 nsDirEnumeratorUnix::Close() {
265   if (mDir) {
266     closedir(mDir);
267     mDir = nullptr;
268   }
269   return NS_OK;
270 }
271 
nsLocalFile()272 nsLocalFile::nsLocalFile() : mCachedStat() {}
273 
nsLocalFile(const nsACString & aFilePath)274 nsLocalFile::nsLocalFile(const nsACString& aFilePath) : mCachedStat() {
275   InitWithNativePath(aFilePath);
276 }
277 
nsLocalFile(const nsLocalFile & aOther)278 nsLocalFile::nsLocalFile(const nsLocalFile& aOther) : mPath(aOther.mPath) {}
279 
280 #ifdef MOZ_WIDGET_COCOA
NS_IMPL_ISUPPORTS(nsLocalFile,nsILocalFileMac,nsIFile)281 NS_IMPL_ISUPPORTS(nsLocalFile, nsILocalFileMac, nsIFile)
282 #else
283 NS_IMPL_ISUPPORTS(nsLocalFile, nsIFile)
284 #endif
285 
286 nsresult nsLocalFile::nsLocalFileConstructor(nsISupports* aOuter,
287                                              const nsIID& aIID,
288                                              void** aInstancePtr) {
289   if (NS_WARN_IF(!aInstancePtr)) {
290     return NS_ERROR_INVALID_ARG;
291   }
292   if (NS_WARN_IF(aOuter)) {
293     return NS_ERROR_NO_AGGREGATION;
294   }
295 
296   *aInstancePtr = nullptr;
297 
298   nsCOMPtr<nsIFile> inst = new nsLocalFile();
299   return inst->QueryInterface(aIID, aInstancePtr);
300 }
301 
FillStatCache()302 bool nsLocalFile::FillStatCache() {
303   if (!FilePreferences::IsAllowedPath(mPath)) {
304     errno = EACCES;
305     return false;
306   }
307 
308   if (STAT(mPath.get(), &mCachedStat) == -1) {
309     // try lstat it may be a symlink
310     if (LSTAT(mPath.get(), &mCachedStat) == -1) {
311       return false;
312     }
313   }
314   return true;
315 }
316 
317 NS_IMETHODIMP
Clone(nsIFile ** aFile)318 nsLocalFile::Clone(nsIFile** aFile) {
319   // Just copy-construct ourselves
320   RefPtr<nsLocalFile> copy = new nsLocalFile(*this);
321   copy.forget(aFile);
322   return NS_OK;
323 }
324 
325 NS_IMETHODIMP
InitWithNativePath(const nsACString & aFilePath)326 nsLocalFile::InitWithNativePath(const nsACString& aFilePath) {
327   if (aFilePath.EqualsLiteral("~") ||
328       Substring(aFilePath, 0, 2).EqualsLiteral("~/")) {
329     nsCOMPtr<nsIFile> homeDir;
330     nsAutoCString homePath;
331     if (NS_FAILED(
332             NS_GetSpecialDirectory(NS_OS_HOME_DIR, getter_AddRefs(homeDir))) ||
333         NS_FAILED(homeDir->GetNativePath(homePath))) {
334       return NS_ERROR_FAILURE;
335     }
336 
337     mPath = homePath;
338     if (aFilePath.Length() > 2) {
339       mPath.Append(Substring(aFilePath, 1, aFilePath.Length() - 1));
340     }
341   } else {
342     if (aFilePath.IsEmpty() || aFilePath.First() != '/') {
343       return NS_ERROR_FILE_UNRECOGNIZED_PATH;
344     }
345     mPath = aFilePath;
346   }
347 
348   if (!FilePreferences::IsAllowedPath(mPath)) {
349     mPath.Truncate();
350     return NS_ERROR_FILE_ACCESS_DENIED;
351   }
352 
353   // trim off trailing slashes
354   ssize_t len = mPath.Length();
355   while ((len > 1) && (mPath[len - 1] == '/')) {
356     --len;
357   }
358   mPath.SetLength(len);
359 
360   return NS_OK;
361 }
362 
363 NS_IMETHODIMP
CreateAllAncestors(uint32_t aPermissions)364 nsLocalFile::CreateAllAncestors(uint32_t aPermissions) {
365   if (!FilePreferences::IsAllowedPath(mPath)) {
366     return NS_ERROR_FILE_ACCESS_DENIED;
367   }
368 
369   // <jband> I promise to play nice
370   char* buffer = mPath.BeginWriting();
371   char* slashp = buffer;
372   int mkdir_result = 0;
373   int mkdir_errno;
374 
375 #ifdef DEBUG_NSIFILE
376   fprintf(stderr, "nsIFile: before: %s\n", buffer);
377 #endif
378 
379   while ((slashp = strchr(slashp + 1, '/'))) {
380     /*
381      * Sequences of '/' are equivalent to a single '/'.
382      */
383     if (slashp[1] == '/') {
384       continue;
385     }
386 
387     /*
388      * If the path has a trailing slash, don't make the last component,
389      * because we'll get EEXIST in Create when we try to build the final
390      * component again, and it's easier to condition the logic here than
391      * there.
392      */
393     if (slashp[1] == '\0') {
394       break;
395     }
396 
397     /* Temporarily NUL-terminate here */
398     *slashp = '\0';
399 #ifdef DEBUG_NSIFILE
400     fprintf(stderr, "nsIFile: mkdir(\"%s\")\n", buffer);
401 #endif
402     mkdir_result = mkdir(buffer, aPermissions);
403     if (mkdir_result == -1) {
404       mkdir_errno = errno;
405       /*
406        * Always set |errno| to EEXIST if the dir already exists
407        * (we have to do this here since the errno value is not consistent
408        * in all cases - various reasons like different platform,
409        * automounter-controlled dir, etc. can affect it (see bug 125489
410        * for details)).
411        */
412       if (mkdir_errno != EEXIST && access(buffer, F_OK) == 0) {
413         mkdir_errno = EEXIST;
414       }
415 #ifdef DEBUG_NSIFILE
416       fprintf(stderr, "nsIFile: errno: %d\n", mkdir_errno);
417 #endif
418     }
419 
420     /* Put the / back */
421     *slashp = '/';
422   }
423 
424   /*
425    * We could get EEXIST for an existing file -- not directory --
426    * but that's OK: we'll get ENOTDIR when we try to make the final
427    * component of the path back in Create and error out appropriately.
428    */
429   if (mkdir_result == -1 && mkdir_errno != EEXIST) {
430     return NS_ERROR_FAILURE;
431   }
432 
433   return NS_OK;
434 }
435 
436 NS_IMETHODIMP
OpenNSPRFileDesc(int32_t aFlags,int32_t aMode,PRFileDesc ** aResult)437 nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode,
438                               PRFileDesc** aResult) {
439   if (!FilePreferences::IsAllowedPath(mPath)) {
440     return NS_ERROR_FILE_ACCESS_DENIED;
441   }
442   *aResult = PR_Open(mPath.get(), aFlags, aMode);
443   if (!*aResult) {
444     return NS_ErrorAccordingToNSPR();
445   }
446 
447   if (aFlags & DELETE_ON_CLOSE) {
448     PR_Delete(mPath.get());
449   }
450 
451 #if defined(HAVE_POSIX_FADVISE)
452   if (aFlags & OS_READAHEAD) {
453     posix_fadvise(PR_FileDesc2NativeHandle(*aResult), 0, 0,
454                   POSIX_FADV_SEQUENTIAL);
455   }
456 #endif
457   return NS_OK;
458 }
459 
460 NS_IMETHODIMP
OpenANSIFileDesc(const char * aMode,FILE ** aResult)461 nsLocalFile::OpenANSIFileDesc(const char* aMode, FILE** aResult) {
462   if (!FilePreferences::IsAllowedPath(mPath)) {
463     return NS_ERROR_FILE_ACCESS_DENIED;
464   }
465   *aResult = fopen(mPath.get(), aMode);
466   if (!*aResult) {
467     return NS_ERROR_FAILURE;
468   }
469 
470   return NS_OK;
471 }
472 
do_create(const char * aPath,int aFlags,mode_t aMode,PRFileDesc ** aResult)473 static int do_create(const char* aPath, int aFlags, mode_t aMode,
474                      PRFileDesc** aResult) {
475   *aResult = PR_Open(aPath, aFlags, aMode);
476   return *aResult ? 0 : -1;
477 }
478 
do_mkdir(const char * aPath,int aFlags,mode_t aMode,PRFileDesc ** aResult)479 static int do_mkdir(const char* aPath, int aFlags, mode_t aMode,
480                     PRFileDesc** aResult) {
481   *aResult = nullptr;
482   return mkdir(aPath, aMode);
483 }
484 
CreateAndKeepOpen(uint32_t aType,int aFlags,uint32_t aPermissions,bool aSkipAncestors,PRFileDesc ** aResult)485 nsresult nsLocalFile::CreateAndKeepOpen(uint32_t aType, int aFlags,
486                                         uint32_t aPermissions,
487                                         bool aSkipAncestors,
488                                         PRFileDesc** aResult) {
489   if (!FilePreferences::IsAllowedPath(mPath)) {
490     return NS_ERROR_FILE_ACCESS_DENIED;
491   }
492 
493   if (aType != NORMAL_FILE_TYPE && aType != DIRECTORY_TYPE) {
494     return NS_ERROR_FILE_UNKNOWN_TYPE;
495   }
496 
497   int (*createFunc)(const char*, int, mode_t, PRFileDesc**) =
498       (aType == NORMAL_FILE_TYPE) ? do_create : do_mkdir;
499 
500   int result = createFunc(mPath.get(), aFlags, aPermissions, aResult);
501   if (result == -1 && errno == ENOENT && !aSkipAncestors) {
502     /*
503      * If we failed because of missing ancestor components, try to create
504      * them and then retry the original creation.
505      *
506      * Ancestor directories get the same permissions as the file we're
507      * creating, with the X bit set for each of (user,group,other) with
508      * an R bit in the original permissions.    If you want to do anything
509      * fancy like setgid or sticky bits, do it by hand.
510      */
511     int dirperm = aPermissions;
512     if (aPermissions & S_IRUSR) {
513       dirperm |= S_IXUSR;
514     }
515     if (aPermissions & S_IRGRP) {
516       dirperm |= S_IXGRP;
517     }
518     if (aPermissions & S_IROTH) {
519       dirperm |= S_IXOTH;
520     }
521 
522 #ifdef DEBUG_NSIFILE
523     fprintf(stderr, "nsIFile: perm = %o, dirperm = %o\n", aPermissions,
524             dirperm);
525 #endif
526 
527     if (NS_FAILED(CreateAllAncestors(dirperm))) {
528       return NS_ERROR_FAILURE;
529     }
530 
531 #ifdef DEBUG_NSIFILE
532     fprintf(stderr, "nsIFile: Create(\"%s\") again\n", mPath.get());
533 #endif
534     result = createFunc(mPath.get(), aFlags, aPermissions, aResult);
535   }
536   return NSRESULT_FOR_RETURN(result);
537 }
538 
539 NS_IMETHODIMP
Create(uint32_t aType,uint32_t aPermissions,bool aSkipAncestors)540 nsLocalFile::Create(uint32_t aType, uint32_t aPermissions,
541                     bool aSkipAncestors) {
542   if (!FilePreferences::IsAllowedPath(mPath)) {
543     return NS_ERROR_FILE_ACCESS_DENIED;
544   }
545 
546   PRFileDesc* junk = nullptr;
547   nsresult rv = CreateAndKeepOpen(
548       aType, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE | PR_EXCL, aPermissions,
549       aSkipAncestors, &junk);
550   if (junk) {
551     PR_Close(junk);
552   }
553   return rv;
554 }
555 
556 NS_IMETHODIMP
AppendNative(const nsACString & aFragment)557 nsLocalFile::AppendNative(const nsACString& aFragment) {
558   if (aFragment.IsEmpty()) {
559     return NS_OK;
560   }
561 
562   // only one component of path can be appended
563   nsACString::const_iterator begin, end;
564   if (FindCharInReadable('/', aFragment.BeginReading(begin),
565                          aFragment.EndReading(end))) {
566     return NS_ERROR_FILE_UNRECOGNIZED_PATH;
567   }
568 
569   return AppendRelativeNativePath(aFragment);
570 }
571 
572 NS_IMETHODIMP
AppendRelativeNativePath(const nsACString & aFragment)573 nsLocalFile::AppendRelativeNativePath(const nsACString& aFragment) {
574   if (aFragment.IsEmpty()) {
575     return NS_OK;
576   }
577 
578   // No leading '/'
579   if (aFragment.First() == '/') {
580     return NS_ERROR_FILE_UNRECOGNIZED_PATH;
581   }
582 
583   if (!mPath.EqualsLiteral("/")) {
584     mPath.Append('/');
585   }
586   mPath.Append(aFragment);
587 
588   return NS_OK;
589 }
590 
591 NS_IMETHODIMP
Normalize()592 nsLocalFile::Normalize() {
593   char resolved_path[PATH_MAX] = "";
594   char* resolved_path_ptr = nullptr;
595 
596   if (!FilePreferences::IsAllowedPath(mPath)) {
597     return NS_ERROR_FILE_ACCESS_DENIED;
598   }
599 
600   resolved_path_ptr = realpath(mPath.get(), resolved_path);
601 
602   // if there is an error, the return is null.
603   if (!resolved_path_ptr) {
604     return NSRESULT_FOR_ERRNO();
605   }
606 
607   mPath = resolved_path;
608   return NS_OK;
609 }
610 
LocateNativeLeafName(nsACString::const_iterator & aBegin,nsACString::const_iterator & aEnd)611 void nsLocalFile::LocateNativeLeafName(nsACString::const_iterator& aBegin,
612                                        nsACString::const_iterator& aEnd) {
613   // XXX perhaps we should cache this??
614 
615   mPath.BeginReading(aBegin);
616   mPath.EndReading(aEnd);
617 
618   nsACString::const_iterator it = aEnd;
619   nsACString::const_iterator stop = aBegin;
620   --stop;
621   while (--it != stop) {
622     if (*it == '/') {
623       aBegin = ++it;
624       return;
625     }
626   }
627   // else, the entire path is the leaf name (which means this
628   // isn't an absolute path... unexpected??)
629 }
630 
631 NS_IMETHODIMP
GetNativeLeafName(nsACString & aLeafName)632 nsLocalFile::GetNativeLeafName(nsACString& aLeafName) {
633   nsACString::const_iterator begin, end;
634   LocateNativeLeafName(begin, end);
635   aLeafName = Substring(begin, end);
636   return NS_OK;
637 }
638 
639 NS_IMETHODIMP
SetNativeLeafName(const nsACString & aLeafName)640 nsLocalFile::SetNativeLeafName(const nsACString& aLeafName) {
641   nsACString::const_iterator begin, end;
642   LocateNativeLeafName(begin, end);
643   mPath.Replace(begin.get() - mPath.get(), Distance(begin, end), aLeafName);
644   return NS_OK;
645 }
646 
NativePath()647 nsCString nsLocalFile::NativePath() { return mPath; }
648 
GetNativePath(nsACString & aResult)649 nsresult nsIFile::GetNativePath(nsACString& aResult) {
650   aResult = NativePath();
651   return NS_OK;
652 }
653 
HumanReadablePath()654 nsCString nsIFile::HumanReadablePath() {
655   nsCString path;
656   DebugOnly<nsresult> rv = GetNativePath(path);
657   MOZ_ASSERT(NS_SUCCEEDED(rv));
658   return path;
659 }
660 
GetNativeTargetPathName(nsIFile * aNewParent,const nsACString & aNewName,nsACString & aResult)661 nsresult nsLocalFile::GetNativeTargetPathName(nsIFile* aNewParent,
662                                               const nsACString& aNewName,
663                                               nsACString& aResult) {
664   nsresult rv;
665   nsCOMPtr<nsIFile> oldParent;
666 
667   if (!aNewParent) {
668     if (NS_FAILED(rv = GetParent(getter_AddRefs(oldParent)))) {
669       return rv;
670     }
671     aNewParent = oldParent.get();
672   } else {
673     // check to see if our target directory exists
674     bool targetExists;
675     if (NS_FAILED(rv = aNewParent->Exists(&targetExists))) {
676       return rv;
677     }
678 
679     if (!targetExists) {
680       // XXX create the new directory with some permissions
681       rv = aNewParent->Create(DIRECTORY_TYPE, 0755);
682       if (NS_FAILED(rv)) {
683         return rv;
684       }
685     } else {
686       // make sure that the target is actually a directory
687       bool targetIsDirectory;
688       if (NS_FAILED(rv = aNewParent->IsDirectory(&targetIsDirectory))) {
689         return rv;
690       }
691       if (!targetIsDirectory) {
692         return NS_ERROR_FILE_DESTINATION_NOT_DIR;
693       }
694     }
695   }
696 
697   nsACString::const_iterator nameBegin, nameEnd;
698   if (!aNewName.IsEmpty()) {
699     aNewName.BeginReading(nameBegin);
700     aNewName.EndReading(nameEnd);
701   } else {
702     LocateNativeLeafName(nameBegin, nameEnd);
703   }
704 
705   nsAutoCString dirName;
706   if (NS_FAILED(rv = aNewParent->GetNativePath(dirName))) {
707     return rv;
708   }
709 
710   aResult = dirName + "/"_ns + Substring(nameBegin, nameEnd);
711   return NS_OK;
712 }
713 
CopyDirectoryTo(nsIFile * aNewParent)714 nsresult nsLocalFile::CopyDirectoryTo(nsIFile* aNewParent) {
715   nsresult rv;
716   /*
717    * dirCheck is used for various boolean test results such as from Equals,
718    * Exists, isDir, etc.
719    */
720   bool dirCheck, isSymlink;
721   uint32_t oldPerms;
722 
723   if (NS_FAILED(rv = IsDirectory(&dirCheck))) {
724     return rv;
725   }
726   if (!dirCheck) {
727     return CopyToNative(aNewParent, ""_ns);
728   }
729 
730   if (NS_FAILED(rv = Equals(aNewParent, &dirCheck))) {
731     return rv;
732   }
733   if (dirCheck) {
734     // can't copy dir to itself
735     return NS_ERROR_INVALID_ARG;
736   }
737 
738   if (NS_FAILED(rv = aNewParent->Exists(&dirCheck))) {
739     return rv;
740   }
741   // get the dirs old permissions
742   if (NS_FAILED(rv = GetPermissions(&oldPerms))) {
743     return rv;
744   }
745   if (!dirCheck) {
746     if (NS_FAILED(rv = aNewParent->Create(DIRECTORY_TYPE, oldPerms))) {
747       return rv;
748     }
749   } else {  // dir exists lets try to use leaf
750     nsAutoCString leafName;
751     if (NS_FAILED(rv = GetNativeLeafName(leafName))) {
752       return rv;
753     }
754     if (NS_FAILED(rv = aNewParent->AppendNative(leafName))) {
755       return rv;
756     }
757     if (NS_FAILED(rv = aNewParent->Exists(&dirCheck))) {
758       return rv;
759     }
760     if (dirCheck) {
761       return NS_ERROR_FILE_ALREADY_EXISTS;  // dest exists
762     }
763     if (NS_FAILED(rv = aNewParent->Create(DIRECTORY_TYPE, oldPerms))) {
764       return rv;
765     }
766   }
767 
768   nsCOMPtr<nsIDirectoryEnumerator> dirIterator;
769   if (NS_FAILED(rv = GetDirectoryEntries(getter_AddRefs(dirIterator)))) {
770     return rv;
771   }
772 
773   nsCOMPtr<nsIFile> entry;
774   while (NS_SUCCEEDED(dirIterator->GetNextFile(getter_AddRefs(entry))) &&
775          entry) {
776     if (NS_FAILED(rv = entry->IsSymlink(&isSymlink))) {
777       return rv;
778     }
779     if (NS_FAILED(rv = entry->IsDirectory(&dirCheck))) {
780       return rv;
781     }
782     if (dirCheck && !isSymlink) {
783       nsCOMPtr<nsIFile> destClone;
784       rv = aNewParent->Clone(getter_AddRefs(destClone));
785       if (NS_SUCCEEDED(rv)) {
786         if (NS_FAILED(rv = entry->CopyToNative(destClone, ""_ns))) {
787 #ifdef DEBUG
788           nsresult rv2;
789           nsAutoCString pathName;
790           if (NS_FAILED(rv2 = entry->GetNativePath(pathName))) {
791             return rv2;
792           }
793           printf("Operation not supported: %s\n", pathName.get());
794 #endif
795           if (rv == NS_ERROR_OUT_OF_MEMORY) {
796             return rv;
797           }
798           continue;
799         }
800       }
801     } else {
802       if (NS_FAILED(rv = entry->CopyToNative(aNewParent, ""_ns))) {
803 #ifdef DEBUG
804         nsresult rv2;
805         nsAutoCString pathName;
806         if (NS_FAILED(rv2 = entry->GetNativePath(pathName))) {
807           return rv2;
808         }
809         printf("Operation not supported: %s\n", pathName.get());
810 #endif
811         if (rv == NS_ERROR_OUT_OF_MEMORY) {
812           return rv;
813         }
814         continue;
815       }
816     }
817   }
818   return NS_OK;
819 }
820 
821 NS_IMETHODIMP
CopyToNative(nsIFile * aNewParent,const nsACString & aNewName)822 nsLocalFile::CopyToNative(nsIFile* aNewParent, const nsACString& aNewName) {
823   nsresult rv;
824   // check to make sure that this has been initialized properly
825   CHECK_mPath();
826 
827   // we copy the parent here so 'aNewParent' remains immutable
828   nsCOMPtr<nsIFile> workParent;
829   if (aNewParent) {
830     if (NS_FAILED(rv = aNewParent->Clone(getter_AddRefs(workParent)))) {
831       return rv;
832     }
833   } else {
834     if (NS_FAILED(rv = GetParent(getter_AddRefs(workParent)))) {
835       return rv;
836     }
837   }
838 
839   // check to see if we are a directory or if we are a file
840   bool isDirectory;
841   if (NS_FAILED(rv = IsDirectory(&isDirectory))) {
842     return rv;
843   }
844 
845   nsAutoCString newPathName;
846   if (isDirectory) {
847     if (!aNewName.IsEmpty()) {
848       if (NS_FAILED(rv = workParent->AppendNative(aNewName))) {
849         return rv;
850       }
851     } else {
852       if (NS_FAILED(rv = GetNativeLeafName(newPathName))) {
853         return rv;
854       }
855       if (NS_FAILED(rv = workParent->AppendNative(newPathName))) {
856         return rv;
857       }
858     }
859     if (NS_FAILED(rv = CopyDirectoryTo(workParent))) {
860       return rv;
861     }
862   } else {
863     rv = GetNativeTargetPathName(workParent, aNewName, newPathName);
864     if (NS_FAILED(rv)) {
865       return rv;
866     }
867 
868 #ifdef DEBUG_blizzard
869     printf("nsLocalFile::CopyTo() %s -> %s\n", mPath.get(), newPathName.get());
870 #endif
871 
872     // actually create the file.
873     auto* newFile = new nsLocalFile();
874     nsCOMPtr<nsIFile> fileRef(newFile);  // release on exit
875 
876     rv = newFile->InitWithNativePath(newPathName);
877     if (NS_FAILED(rv)) {
878       return rv;
879     }
880 
881     // get the old permissions
882     uint32_t myPerms = 0;
883     rv = GetPermissions(&myPerms);
884     if (NS_FAILED(rv)) {
885       return rv;
886     }
887 
888     // Create the new file with the old file's permissions, even if write
889     // permission is missing.  We can't create with write permission and
890     // then change back to myPerm on all filesystems (FAT on Linux, e.g.).
891     // But we can write to a read-only file on all Unix filesystems if we
892     // open it successfully for writing.
893 
894     PRFileDesc* newFD;
895     rv = newFile->CreateAndKeepOpen(
896         NORMAL_FILE_TYPE, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, myPerms,
897         /* aSkipAncestors = */ false, &newFD);
898     if (NS_FAILED(rv)) {
899       return rv;
900     }
901 
902     // open the old file, too
903     bool specialFile;
904     if (NS_FAILED(rv = IsSpecial(&specialFile))) {
905       PR_Close(newFD);
906       return rv;
907     }
908     if (specialFile) {
909 #ifdef DEBUG
910       printf("Operation not supported: %s\n", mPath.get());
911 #endif
912       // make sure to clean up properly
913       PR_Close(newFD);
914       return NS_OK;
915     }
916 
917 #if defined(XP_MACOSX)
918     bool quarantined = true;
919     if (getxattr(mPath.get(), "com.apple.quarantine", nullptr, 0, 0, 0) == -1) {
920       if (errno == ENOATTR) {
921         quarantined = false;
922       }
923     }
924 #endif
925 
926     PRFileDesc* oldFD;
927     rv = OpenNSPRFileDesc(PR_RDONLY, myPerms, &oldFD);
928     if (NS_FAILED(rv)) {
929       // make sure to clean up properly
930       PR_Close(newFD);
931       return rv;
932     }
933 
934 #ifdef DEBUG_blizzard
935     int32_t totalRead = 0;
936     int32_t totalWritten = 0;
937 #endif
938     char buf[BUFSIZ];
939     int32_t bytesRead;
940 
941     // record PR_Write() error for better error message later.
942     nsresult saved_write_error = NS_OK;
943     nsresult saved_read_error = NS_OK;
944     nsresult saved_read_close_error = NS_OK;
945     nsresult saved_write_close_error = NS_OK;
946 
947     // DONE: Does PR_Read() return bytesRead < 0 for error?
948     // Yes., The errors from PR_Read are not so common and
949     // the value may not have correspondence in NS_ERROR_*, but
950     // we do catch it still, immediately after while() loop.
951     // We can differentiate errors pf PR_Read and PR_Write by
952     // looking at saved_write_error value. If PR_Write error occurs (and not
953     // PR_Read() error), save_write_error is not NS_OK.
954 
955     while ((bytesRead = PR_Read(oldFD, buf, BUFSIZ)) > 0) {
956 #ifdef DEBUG_blizzard
957       totalRead += bytesRead;
958 #endif
959 
960       // PR_Write promises never to do a short write
961       int32_t bytesWritten = PR_Write(newFD, buf, bytesRead);
962       if (bytesWritten < 0) {
963         saved_write_error = NSRESULT_FOR_ERRNO();
964         bytesRead = -1;
965         break;
966       }
967       NS_ASSERTION(bytesWritten == bytesRead, "short PR_Write?");
968 
969 #ifdef DEBUG_blizzard
970       totalWritten += bytesWritten;
971 #endif
972     }
973 
974     // TODO/FIXME: If CIFS (and NFS?) may force read/write to return EINTR,
975     // we are better off to prepare for retrying. But we need confirmation if
976     // EINTR is returned.
977 
978     // Record error if PR_Read() failed.
979     // Must be done before any other I/O which may reset errno.
980     if (bytesRead < 0 && saved_write_error == NS_OK) {
981       saved_read_error = NSRESULT_FOR_ERRNO();
982     }
983 
984 #ifdef DEBUG_blizzard
985     printf("read %d bytes, wrote %d bytes\n", totalRead, totalWritten);
986 #endif
987 
988     // DONE: Errors of close can occur.  Read man page of
989     // close(2);
990     // This is likely to happen if the file system is remote file
991     // system (NFS, CIFS, etc.) and network outage occurs.
992     // At least, we should tell the user that filesystem/disk is
993     // hosed (possibly due to network error, hard disk failure,
994     // etc.) so that users can take remedial action.
995 
996     // close the files
997     if (PR_Close(newFD) < 0) {
998       saved_write_close_error = NSRESULT_FOR_ERRNO();
999 #if DEBUG
1000       // This error merits printing.
1001       fprintf(stderr, "ERROR: PR_Close(newFD) returned error. errno = %d\n",
1002               errno);
1003 #endif
1004     }
1005 #if defined(XP_MACOSX)
1006     else if (!quarantined) {
1007       // If the original file was not in quarantine, lift the quarantine that
1008       // file creation added because of LSFileQuarantineEnabled.
1009       removexattr(newPathName.get(), "com.apple.quarantine", 0);
1010     }
1011 #endif  // defined(XP_MACOSX)
1012 
1013     if (PR_Close(oldFD) < 0) {
1014       saved_read_close_error = NSRESULT_FOR_ERRNO();
1015 #if DEBUG
1016       fprintf(stderr, "ERROR: PR_Close(oldFD) returned error. errno = %d\n",
1017               errno);
1018 #endif
1019     }
1020 
1021     // Let us report the failure to write and read.
1022     // check for write/read error after cleaning up
1023     if (bytesRead < 0) {
1024       if (saved_write_error != NS_OK) {
1025         return saved_write_error;
1026       }
1027       if (saved_read_error != NS_OK) {
1028         return saved_read_error;
1029       }
1030 #if DEBUG
1031       MOZ_ASSERT(0);
1032 #endif
1033     }
1034 
1035     if (saved_write_close_error != NS_OK) {
1036       return saved_write_close_error;
1037     }
1038     if (saved_read_close_error != NS_OK) {
1039       return saved_read_close_error;
1040     }
1041   }
1042   return rv;
1043 }
1044 
1045 NS_IMETHODIMP
CopyToFollowingLinksNative(nsIFile * aNewParent,const nsACString & aNewName)1046 nsLocalFile::CopyToFollowingLinksNative(nsIFile* aNewParent,
1047                                         const nsACString& aNewName) {
1048   return CopyToNative(aNewParent, aNewName);
1049 }
1050 
1051 NS_IMETHODIMP
MoveToNative(nsIFile * aNewParent,const nsACString & aNewName)1052 nsLocalFile::MoveToNative(nsIFile* aNewParent, const nsACString& aNewName) {
1053   nsresult rv;
1054 
1055   // check to make sure that this has been initialized properly
1056   CHECK_mPath();
1057 
1058   // check to make sure that we have a new parent
1059   nsAutoCString newPathName;
1060   rv = GetNativeTargetPathName(aNewParent, aNewName, newPathName);
1061   if (NS_FAILED(rv)) {
1062     return rv;
1063   }
1064 
1065   if (!FilePreferences::IsAllowedPath(newPathName)) {
1066     return NS_ERROR_FILE_ACCESS_DENIED;
1067   }
1068 
1069   // try for atomic rename, falling back to copy/delete
1070   if (rename(mPath.get(), newPathName.get()) < 0) {
1071     if (errno == EXDEV) {
1072       rv = CopyToNative(aNewParent, aNewName);
1073       if (NS_SUCCEEDED(rv)) {
1074         rv = Remove(true);
1075       }
1076     } else {
1077       rv = NSRESULT_FOR_ERRNO();
1078     }
1079   }
1080 
1081   if (NS_SUCCEEDED(rv)) {
1082     // Adjust this
1083     mPath = newPathName;
1084   }
1085   return rv;
1086 }
1087 
1088 NS_IMETHODIMP
MoveToFollowingLinksNative(nsIFile * aNewParent,const nsACString & aNewName)1089 nsLocalFile::MoveToFollowingLinksNative(nsIFile* aNewParent,
1090                                         const nsACString& aNewName) {
1091   return MoveToNative(aNewParent, aNewName);
1092 }
1093 
1094 NS_IMETHODIMP
Remove(bool aRecursive)1095 nsLocalFile::Remove(bool aRecursive) {
1096   CHECK_mPath();
1097   ENSURE_STAT_CACHE();
1098 
1099   bool isSymLink;
1100 
1101   nsresult rv = IsSymlink(&isSymLink);
1102   if (NS_FAILED(rv)) {
1103     return rv;
1104   }
1105 
1106   if (isSymLink || !S_ISDIR(mCachedStat.st_mode)) {
1107     return NSRESULT_FOR_RETURN(unlink(mPath.get()));
1108   }
1109 
1110   if (aRecursive) {
1111     auto* dir = new nsDirEnumeratorUnix();
1112 
1113     RefPtr<nsSimpleEnumerator> dirRef(dir);  // release on exit
1114 
1115     rv = dir->Init(this, false);
1116     if (NS_FAILED(rv)) {
1117       return rv;
1118     }
1119 
1120     bool more;
1121     while (NS_SUCCEEDED(dir->HasMoreElements(&more)) && more) {
1122       nsCOMPtr<nsISupports> item;
1123       rv = dir->GetNext(getter_AddRefs(item));
1124       if (NS_FAILED(rv)) {
1125         return NS_ERROR_FAILURE;
1126       }
1127 
1128       nsCOMPtr<nsIFile> file = do_QueryInterface(item, &rv);
1129       if (NS_FAILED(rv)) {
1130         return NS_ERROR_FAILURE;
1131       }
1132       rv = file->Remove(aRecursive);
1133 
1134 #ifdef ANDROID
1135       // See bug 580434 - Bionic gives us just deleted files
1136       if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
1137         continue;
1138       }
1139 #endif
1140       if (NS_FAILED(rv)) {
1141         return rv;
1142       }
1143     }
1144   }
1145 
1146   return NSRESULT_FOR_RETURN(rmdir(mPath.get()));
1147 }
1148 
GetLastModifiedTimeImpl(PRTime * aLastModTime,bool aFollowLinks)1149 nsresult nsLocalFile::GetLastModifiedTimeImpl(PRTime* aLastModTime,
1150                                               bool aFollowLinks) {
1151   CHECK_mPath();
1152   if (NS_WARN_IF(!aLastModTime)) {
1153     return NS_ERROR_INVALID_ARG;
1154   }
1155 
1156   using StatFn = int (*)(const char*, struct STAT*);
1157   StatFn statFn = aFollowLinks ? &STAT : &LSTAT;
1158 
1159   struct STAT fileStats {};
1160   if (statFn(mPath.get(), &fileStats) < 0) {
1161     return NSRESULT_FOR_ERRNO();
1162   }
1163 
1164 #if (defined(__APPLE__) && defined(__MACH__))
1165   *aLastModTime = TimespecToMillis(fileStats.st_mtimespec);
1166 #else
1167   *aLastModTime = TimespecToMillis(fileStats.st_mtim);
1168 #endif
1169 
1170   return NS_OK;
1171 }
1172 
SetLastModifiedTimeImpl(PRTime aLastModTime,bool aFollowLinks)1173 nsresult nsLocalFile::SetLastModifiedTimeImpl(PRTime aLastModTime,
1174                                               bool aFollowLinks) {
1175   CHECK_mPath();
1176 
1177   using UtimesFn = int (*)(const char*, const timeval*);
1178   UtimesFn utimesFn = &utimes;
1179 
1180 #if HAVE_LUTIMES
1181   if (!aFollowLinks) {
1182     utimesFn = &lutimes;
1183   }
1184 #endif
1185 
1186   int result;
1187   if (aLastModTime != 0) {
1188     ENSURE_STAT_CACHE();
1189     timeval access{};
1190 #if (defined(__APPLE__) && defined(__MACH__))
1191     access.tv_sec = mCachedStat.st_atimespec.tv_sec;
1192     access.tv_usec = mCachedStat.st_atimespec.tv_nsec / 1000;
1193 #else
1194     access.tv_sec = mCachedStat.st_atim.tv_sec;
1195     access.tv_usec = mCachedStat.st_atim.tv_nsec / 1000;
1196 #endif
1197     timeval modification{};
1198     modification.tv_sec = aLastModTime / PR_MSEC_PER_SEC;
1199     modification.tv_usec = (aLastModTime % PR_MSEC_PER_SEC) * PR_USEC_PER_MSEC;
1200 
1201     timeval times[2];
1202     times[0] = access;
1203     times[1] = modification;
1204     result = utimesFn(mPath.get(), times);
1205   } else {
1206     result = utimesFn(mPath.get(), nullptr);
1207   }
1208   return NSRESULT_FOR_RETURN(result);
1209 }
1210 
1211 NS_IMETHODIMP
GetLastModifiedTime(PRTime * aLastModTime)1212 nsLocalFile::GetLastModifiedTime(PRTime* aLastModTime) {
1213   return GetLastModifiedTimeImpl(aLastModTime, /* follow links? */ true);
1214 }
1215 
1216 NS_IMETHODIMP
SetLastModifiedTime(PRTime aLastModTime)1217 nsLocalFile::SetLastModifiedTime(PRTime aLastModTime) {
1218   return SetLastModifiedTimeImpl(aLastModTime, /* follow links ? */ true);
1219 }
1220 
1221 NS_IMETHODIMP
GetLastModifiedTimeOfLink(PRTime * aLastModTimeOfLink)1222 nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModTimeOfLink) {
1223   return GetLastModifiedTimeImpl(aLastModTimeOfLink, /* follow link? */ false);
1224 }
1225 
1226 NS_IMETHODIMP
SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink)1227 nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink) {
1228   return SetLastModifiedTimeImpl(aLastModTimeOfLink, /* follow links? */ false);
1229 }
1230 
1231 NS_IMETHODIMP
GetCreationTime(PRTime * aCreationTime)1232 nsLocalFile::GetCreationTime(PRTime* aCreationTime) {
1233   return GetCreationTimeImpl(aCreationTime, false);
1234 }
1235 
1236 NS_IMETHODIMP
GetCreationTimeOfLink(PRTime * aCreationTimeOfLink)1237 nsLocalFile::GetCreationTimeOfLink(PRTime* aCreationTimeOfLink) {
1238   return GetCreationTimeImpl(aCreationTimeOfLink, /* aFollowLinks = */ true);
1239 }
1240 
GetCreationTimeImpl(PRTime * aCreationTime,bool aFollowLinks)1241 nsresult nsLocalFile::GetCreationTimeImpl(PRTime* aCreationTime,
1242                                           bool aFollowLinks) {
1243   CHECK_mPath();
1244   if (NS_WARN_IF(!aCreationTime)) {
1245     return NS_ERROR_INVALID_ARG;
1246   }
1247 
1248 #if defined(_DARWIN_FEATURE_64_BIT_INODE)
1249   using StatFn = int (*)(const char*, struct STAT*);
1250   StatFn statFn = aFollowLinks ? &STAT : &LSTAT;
1251 
1252   struct STAT fileStats {};
1253   if (statFn(mPath.get(), &fileStats) < 0) {
1254     return NSRESULT_FOR_ERRNO();
1255   }
1256 
1257   *aCreationTime = TimespecToMillis(fileStats.st_birthtimespec);
1258   return NS_OK;
1259 #else
1260   return NS_ERROR_NOT_IMPLEMENTED;
1261 #endif
1262 }
1263 
1264 /*
1265  * Only send back permissions bits: maybe we want to send back the whole
1266  * mode_t to permit checks against other file types?
1267  */
1268 
1269 #define NORMALIZE_PERMS(mode) ((mode) & (S_IRWXU | S_IRWXG | S_IRWXO))
1270 
1271 NS_IMETHODIMP
GetPermissions(uint32_t * aPermissions)1272 nsLocalFile::GetPermissions(uint32_t* aPermissions) {
1273   if (NS_WARN_IF(!aPermissions)) {
1274     return NS_ERROR_INVALID_ARG;
1275   }
1276   ENSURE_STAT_CACHE();
1277   *aPermissions = NORMALIZE_PERMS(mCachedStat.st_mode);
1278   return NS_OK;
1279 }
1280 
1281 NS_IMETHODIMP
GetPermissionsOfLink(uint32_t * aPermissionsOfLink)1282 nsLocalFile::GetPermissionsOfLink(uint32_t* aPermissionsOfLink) {
1283   CHECK_mPath();
1284   if (NS_WARN_IF(!aPermissionsOfLink)) {
1285     return NS_ERROR_INVALID_ARG;
1286   }
1287 
1288   struct STAT sbuf;
1289   if (LSTAT(mPath.get(), &sbuf) == -1) {
1290     return NSRESULT_FOR_ERRNO();
1291   }
1292   *aPermissionsOfLink = NORMALIZE_PERMS(sbuf.st_mode);
1293   return NS_OK;
1294 }
1295 
1296 NS_IMETHODIMP
SetPermissions(uint32_t aPermissions)1297 nsLocalFile::SetPermissions(uint32_t aPermissions) {
1298   CHECK_mPath();
1299 
1300   /*
1301    * Race condition here: we should use fchmod instead, there's no way to
1302    * guarantee the name still refers to the same file.
1303    */
1304   if (chmod(mPath.get(), aPermissions) >= 0) {
1305     return NS_OK;
1306   }
1307 #if defined(ANDROID) && defined(STATFS)
1308   // For the time being, this is restricted for use by Android, but we
1309   // will figure out what to do for all platforms in bug 638503
1310   struct STATFS sfs;
1311   if (STATFS(mPath.get(), &sfs) < 0) {
1312     return NSRESULT_FOR_ERRNO();
1313   }
1314 
1315   // if this is a FAT file system we can't set file permissions
1316   if (sfs.f_type == MSDOS_SUPER_MAGIC) {
1317     return NS_OK;
1318   }
1319 #endif
1320   return NSRESULT_FOR_ERRNO();
1321 }
1322 
1323 NS_IMETHODIMP
SetPermissionsOfLink(uint32_t aPermissions)1324 nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions) {
1325   // There isn't a consistent mechanism for doing this on UNIX platforms. We
1326   // might want to carefully implement this in the future though.
1327   return NS_ERROR_NOT_IMPLEMENTED;
1328 }
1329 
1330 NS_IMETHODIMP
GetFileSize(int64_t * aFileSize)1331 nsLocalFile::GetFileSize(int64_t* aFileSize) {
1332   if (NS_WARN_IF(!aFileSize)) {
1333     return NS_ERROR_INVALID_ARG;
1334   }
1335   *aFileSize = 0;
1336   ENSURE_STAT_CACHE();
1337 
1338   if (!S_ISDIR(mCachedStat.st_mode)) {
1339     *aFileSize = (int64_t)mCachedStat.st_size;
1340   }
1341   return NS_OK;
1342 }
1343 
1344 NS_IMETHODIMP
SetFileSize(int64_t aFileSize)1345 nsLocalFile::SetFileSize(int64_t aFileSize) {
1346   CHECK_mPath();
1347 
1348 #if defined(ANDROID)
1349   /* no truncate on bionic */
1350   int fd = open(mPath.get(), O_WRONLY);
1351   if (fd == -1) {
1352     return NSRESULT_FOR_ERRNO();
1353   }
1354 
1355   int ret = ftruncate(fd, (off_t)aFileSize);
1356   close(fd);
1357 
1358   if (ret == -1) {
1359     return NSRESULT_FOR_ERRNO();
1360   }
1361 #elif defined(HAVE_TRUNCATE64)
1362   if (truncate64(mPath.get(), (off64_t)aFileSize) == -1) {
1363     return NSRESULT_FOR_ERRNO();
1364   }
1365 #else
1366   off_t size = (off_t)aFileSize;
1367   if (truncate(mPath.get(), size) == -1) {
1368     return NSRESULT_FOR_ERRNO();
1369   }
1370 #endif
1371   return NS_OK;
1372 }
1373 
1374 NS_IMETHODIMP
GetFileSizeOfLink(int64_t * aFileSize)1375 nsLocalFile::GetFileSizeOfLink(int64_t* aFileSize) {
1376   CHECK_mPath();
1377   if (NS_WARN_IF(!aFileSize)) {
1378     return NS_ERROR_INVALID_ARG;
1379   }
1380 
1381   struct STAT sbuf;
1382   if (LSTAT(mPath.get(), &sbuf) == -1) {
1383     return NSRESULT_FOR_ERRNO();
1384   }
1385 
1386   *aFileSize = (int64_t)sbuf.st_size;
1387   return NS_OK;
1388 }
1389 
1390 #if defined(USE_LINUX_QUOTACTL)
1391 /*
1392  * Searches /proc/self/mountinfo for given device (Major:Minor),
1393  * returns exported name from /dev
1394  *
1395  * Fails when /proc/self/mountinfo or diven device don't exist.
1396  */
GetDeviceName(unsigned int aDeviceMajor,unsigned int aDeviceMinor,nsACString & aDeviceName)1397 static bool GetDeviceName(unsigned int aDeviceMajor, unsigned int aDeviceMinor,
1398                           nsACString& aDeviceName) {
1399   bool ret = false;
1400 
1401   const int kMountInfoLineLength = 200;
1402   const int kMountInfoDevPosition = 6;
1403 
1404   char mountinfoLine[kMountInfoLineLength];
1405   char deviceNum[kMountInfoLineLength];
1406 
1407   SprintfLiteral(deviceNum, "%u:%u", aDeviceMajor, aDeviceMinor);
1408 
1409   FILE* f = fopen("/proc/self/mountinfo", "rt");
1410   if (!f) {
1411     return ret;
1412   }
1413 
1414   // Expects /proc/self/mountinfo in format:
1415   // 'ID ID major:minor root mountpoint flags - type devicename flags'
1416   while (fgets(mountinfoLine, kMountInfoLineLength, f)) {
1417     char* p_dev = strstr(mountinfoLine, deviceNum);
1418 
1419     for (int i = 0; i < kMountInfoDevPosition && p_dev; ++i) {
1420       p_dev = strchr(p_dev, ' ');
1421       if (p_dev) {
1422         p_dev++;
1423       }
1424     }
1425 
1426     if (p_dev) {
1427       char* p_dev_end = strchr(p_dev, ' ');
1428       if (p_dev_end) {
1429         *p_dev_end = '\0';
1430         aDeviceName.Assign(p_dev);
1431         ret = true;
1432         break;
1433       }
1434     }
1435   }
1436 
1437   fclose(f);
1438   return ret;
1439 }
1440 #endif
1441 
1442 NS_IMETHODIMP
GetDiskSpaceAvailable(int64_t * aDiskSpaceAvailable)1443 nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable) {
1444   if (NS_WARN_IF(!aDiskSpaceAvailable)) {
1445     return NS_ERROR_INVALID_ARG;
1446   }
1447 
1448   // These systems have the operations necessary to check disk space.
1449 
1450 #ifdef STATFS
1451 
1452   // check to make sure that mPath is properly initialized
1453   CHECK_mPath();
1454 
1455   struct STATFS fs_buf;
1456 
1457   /*
1458    * Members of the STATFS struct that you should know about:
1459    * F_BSIZE = block size on disk.
1460    * f_bavail = number of free blocks available to a non-superuser.
1461    * f_bfree = number of total free blocks in file system.
1462    */
1463 
1464   if (STATFS(mPath.get(), &fs_buf) < 0) {
1465     // The call to STATFS failed.
1466 #  ifdef DEBUG
1467     printf("ERROR: GetDiskSpaceAvailable: STATFS call FAILED. \n");
1468 #  endif
1469     return NS_ERROR_FAILURE;
1470   }
1471 
1472   *aDiskSpaceAvailable = (int64_t)fs_buf.F_BSIZE * fs_buf.f_bavail;
1473 
1474 #  ifdef DEBUG_DISK_SPACE
1475   printf("DiskSpaceAvailable: %lu bytes\n", *aDiskSpaceAvailable);
1476 #  endif
1477 
1478 #  if defined(USE_LINUX_QUOTACTL)
1479 
1480   if (!FillStatCache()) {
1481     // Return available size from statfs
1482     return NS_OK;
1483   }
1484 
1485   nsCString deviceName;
1486   if (!GetDeviceName(major(mCachedStat.st_dev), minor(mCachedStat.st_dev),
1487                      deviceName)) {
1488     return NS_OK;
1489   }
1490 
1491   struct dqblk dq;
1492   if (!quotactl(QCMD(Q_GETQUOTA, USRQUOTA), deviceName.get(), getuid(),
1493                 (caddr_t)&dq)
1494 #    ifdef QIF_BLIMITS
1495       && dq.dqb_valid & QIF_BLIMITS
1496 #    endif
1497       && dq.dqb_bhardlimit) {
1498     int64_t QuotaSpaceAvailable = 0;
1499     // dqb_bhardlimit is count of BLOCK_SIZE blocks, dqb_curspace is bytes
1500     if ((BLOCK_SIZE * dq.dqb_bhardlimit) > dq.dqb_curspace)
1501       QuotaSpaceAvailable =
1502           int64_t(BLOCK_SIZE * dq.dqb_bhardlimit - dq.dqb_curspace);
1503     if (QuotaSpaceAvailable < *aDiskSpaceAvailable) {
1504       *aDiskSpaceAvailable = QuotaSpaceAvailable;
1505     }
1506   }
1507 #  endif
1508 
1509   return NS_OK;
1510 
1511 #else
1512   /*
1513    * This platform doesn't have statfs or statvfs.  I'm sure that there's
1514    * a way to check for free disk space on platforms that don't have statfs
1515    * (I'm SURE they have df, for example).
1516    *
1517    * Until we figure out how to do that, lets be honest and say that this
1518    * command isn't implemented properly for these platforms yet.
1519    */
1520 #  ifdef DEBUG
1521   printf(
1522       "ERROR: GetDiskSpaceAvailable: Not implemented for plaforms without "
1523       "statfs.\n");
1524 #  endif
1525   return NS_ERROR_NOT_IMPLEMENTED;
1526 
1527 #endif /* STATFS */
1528 }
1529 
1530 NS_IMETHODIMP
GetParent(nsIFile ** aParent)1531 nsLocalFile::GetParent(nsIFile** aParent) {
1532   CHECK_mPath();
1533   if (NS_WARN_IF(!aParent)) {
1534     return NS_ERROR_INVALID_ARG;
1535   }
1536   *aParent = nullptr;
1537 
1538   // if '/' we are at the top of the volume, return null
1539   if (mPath.EqualsLiteral("/")) {
1540     return NS_OK;
1541   }
1542 
1543   // <brendan, after jband> I promise to play nice
1544   char* buffer = mPath.BeginWriting();
1545   // find the last significant slash in buffer
1546   char* slashp = strrchr(buffer, '/');
1547   NS_ASSERTION(slashp, "non-canonical path?");
1548   if (!slashp) {
1549     return NS_ERROR_FILE_INVALID_PATH;
1550   }
1551 
1552   // for the case where we are at '/'
1553   if (slashp == buffer) {
1554     slashp++;
1555   }
1556 
1557   // temporarily terminate buffer at the last significant slash
1558   char c = *slashp;
1559   *slashp = '\0';
1560 
1561   nsCOMPtr<nsIFile> localFile;
1562   nsresult rv = NS_NewNativeLocalFile(nsDependentCString(buffer), true,
1563                                       getter_AddRefs(localFile));
1564 
1565   // make buffer whole again
1566   *slashp = c;
1567 
1568   if (NS_FAILED(rv)) {
1569     return rv;
1570   }
1571 
1572   localFile.forget(aParent);
1573   return NS_OK;
1574 }
1575 
1576 /*
1577  * The results of Exists, isWritable and isReadable are not cached.
1578  */
1579 
1580 NS_IMETHODIMP
Exists(bool * aResult)1581 nsLocalFile::Exists(bool* aResult) {
1582   CHECK_mPath();
1583   if (NS_WARN_IF(!aResult)) {
1584     return NS_ERROR_INVALID_ARG;
1585   }
1586 
1587   *aResult = (access(mPath.get(), F_OK) == 0);
1588   return NS_OK;
1589 }
1590 
1591 NS_IMETHODIMP
IsWritable(bool * aResult)1592 nsLocalFile::IsWritable(bool* aResult) {
1593   CHECK_mPath();
1594   if (NS_WARN_IF(!aResult)) {
1595     return NS_ERROR_INVALID_ARG;
1596   }
1597 
1598   *aResult = (access(mPath.get(), W_OK) == 0);
1599   if (*aResult || errno == EACCES) {
1600     return NS_OK;
1601   }
1602   return NSRESULT_FOR_ERRNO();
1603 }
1604 
1605 NS_IMETHODIMP
IsReadable(bool * aResult)1606 nsLocalFile::IsReadable(bool* aResult) {
1607   CHECK_mPath();
1608   if (NS_WARN_IF(!aResult)) {
1609     return NS_ERROR_INVALID_ARG;
1610   }
1611 
1612   *aResult = (access(mPath.get(), R_OK) == 0);
1613   if (*aResult || errno == EACCES) {
1614     return NS_OK;
1615   }
1616   return NSRESULT_FOR_ERRNO();
1617 }
1618 
1619 NS_IMETHODIMP
IsExecutable(bool * aResult)1620 nsLocalFile::IsExecutable(bool* aResult) {
1621   CHECK_mPath();
1622   if (NS_WARN_IF(!aResult)) {
1623     return NS_ERROR_INVALID_ARG;
1624   }
1625 
1626   // Check extension (bug 663899). On certain platforms, the file
1627   // extension may cause the OS to treat it as executable regardless of
1628   // the execute bit, such as .jar on Mac OS X. We borrow the code from
1629   // nsLocalFileWin, slightly modified.
1630 
1631   // Don't be fooled by symlinks.
1632   bool symLink;
1633   nsresult rv = IsSymlink(&symLink);
1634   if (NS_FAILED(rv)) {
1635     return rv;
1636   }
1637 
1638   nsAutoString path;
1639   if (symLink) {
1640     GetTarget(path);
1641   } else {
1642     GetPath(path);
1643   }
1644 
1645   int32_t dotIdx = path.RFindChar(char16_t('.'));
1646   if (dotIdx != kNotFound) {
1647     // Convert extension to lower case.
1648     char16_t* p = path.BeginWriting();
1649     for (p += dotIdx + 1; *p; ++p) {
1650       *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0;
1651     }
1652 
1653     // Search for any of the set of executable extensions.
1654     static const char* const executableExts[] = {
1655         "air",  // Adobe AIR installer
1656 #ifdef MOZ_WIDGET_COCOA
1657         "fileloc",  // File location files can be used to point to other
1658                     // files.
1659         "inetloc", // Shouldn't be able to do the same, but can, due to
1660                     // macOS vulnerabilities.
1661 #endif
1662         "jar"  // java application bundle
1663     };
1664     nsDependentSubstring ext = Substring(path, dotIdx + 1);
1665     for (auto executableExt : executableExts) {
1666       if (ext.EqualsASCII(executableExt)) {
1667         // Found a match.  Set result and quit.
1668         *aResult = true;
1669         return NS_OK;
1670       }
1671     }
1672   }
1673 
1674   // On OS X, then query Launch Services.
1675 #ifdef MOZ_WIDGET_COCOA
1676   // Certain Mac applications, such as Classic applications, which
1677   // run under Rosetta, might not have the +x mode bit but are still
1678   // considered to be executable by Launch Services (bug 646748).
1679   CFURLRef url;
1680   if (NS_FAILED(GetCFURL(&url))) {
1681     return NS_ERROR_FAILURE;
1682   }
1683 
1684   LSRequestedInfo theInfoRequest = kLSRequestAllInfo;
1685   LSItemInfoRecord theInfo;
1686   OSStatus result = ::LSCopyItemInfoForURL(url, theInfoRequest, &theInfo);
1687   ::CFRelease(url);
1688   if (result == noErr) {
1689     if ((theInfo.flags & kLSItemInfoIsApplication) != 0) {
1690       *aResult = true;
1691       return NS_OK;
1692     }
1693   }
1694 #endif
1695 
1696   // Then check the execute bit.
1697   *aResult = (access(mPath.get(), X_OK) == 0);
1698 #ifdef SOLARIS
1699   // On Solaris, access will always return 0 for root user, however
1700   // the file is only executable if S_IXUSR | S_IXGRP | S_IXOTH is set.
1701   // See bug 351950, https://bugzilla.mozilla.org/show_bug.cgi?id=351950
1702   if (*aResult) {
1703     struct STAT buf;
1704 
1705     *aResult = (STAT(mPath.get(), &buf) == 0);
1706     if (*aResult || errno == EACCES) {
1707       *aResult = *aResult && (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH));
1708       return NS_OK;
1709     }
1710 
1711     return NSRESULT_FOR_ERRNO();
1712   }
1713 #endif
1714   if (*aResult || errno == EACCES) {
1715     return NS_OK;
1716   }
1717   return NSRESULT_FOR_ERRNO();
1718 }
1719 
1720 NS_IMETHODIMP
IsDirectory(bool * aResult)1721 nsLocalFile::IsDirectory(bool* aResult) {
1722   if (NS_WARN_IF(!aResult)) {
1723     return NS_ERROR_INVALID_ARG;
1724   }
1725   *aResult = false;
1726   ENSURE_STAT_CACHE();
1727   *aResult = S_ISDIR(mCachedStat.st_mode);
1728   return NS_OK;
1729 }
1730 
1731 NS_IMETHODIMP
IsFile(bool * aResult)1732 nsLocalFile::IsFile(bool* aResult) {
1733   if (NS_WARN_IF(!aResult)) {
1734     return NS_ERROR_INVALID_ARG;
1735   }
1736   *aResult = false;
1737   ENSURE_STAT_CACHE();
1738   *aResult = S_ISREG(mCachedStat.st_mode);
1739   return NS_OK;
1740 }
1741 
1742 NS_IMETHODIMP
IsHidden(bool * aResult)1743 nsLocalFile::IsHidden(bool* aResult) {
1744   if (NS_WARN_IF(!aResult)) {
1745     return NS_ERROR_INVALID_ARG;
1746   }
1747   nsACString::const_iterator begin, end;
1748   LocateNativeLeafName(begin, end);
1749   *aResult = (*begin == '.');
1750   return NS_OK;
1751 }
1752 
1753 NS_IMETHODIMP
IsSymlink(bool * aResult)1754 nsLocalFile::IsSymlink(bool* aResult) {
1755   if (NS_WARN_IF(!aResult)) {
1756     return NS_ERROR_INVALID_ARG;
1757   }
1758   CHECK_mPath();
1759 
1760   struct STAT symStat;
1761   if (LSTAT(mPath.get(), &symStat) == -1) {
1762     return NSRESULT_FOR_ERRNO();
1763   }
1764   *aResult = S_ISLNK(symStat.st_mode);
1765   return NS_OK;
1766 }
1767 
1768 NS_IMETHODIMP
IsSpecial(bool * aResult)1769 nsLocalFile::IsSpecial(bool* aResult) {
1770   if (NS_WARN_IF(!aResult)) {
1771     return NS_ERROR_INVALID_ARG;
1772   }
1773   ENSURE_STAT_CACHE();
1774   *aResult = S_ISCHR(mCachedStat.st_mode) || S_ISBLK(mCachedStat.st_mode) ||
1775 #ifdef S_ISSOCK
1776              S_ISSOCK(mCachedStat.st_mode) ||
1777 #endif
1778              S_ISFIFO(mCachedStat.st_mode);
1779 
1780   return NS_OK;
1781 }
1782 
1783 NS_IMETHODIMP
Equals(nsIFile * aInFile,bool * aResult)1784 nsLocalFile::Equals(nsIFile* aInFile, bool* aResult) {
1785   if (NS_WARN_IF(!aInFile)) {
1786     return NS_ERROR_INVALID_ARG;
1787   }
1788   if (NS_WARN_IF(!aResult)) {
1789     return NS_ERROR_INVALID_ARG;
1790   }
1791   *aResult = false;
1792 
1793   nsAutoCString inPath;
1794   nsresult rv = aInFile->GetNativePath(inPath);
1795   if (NS_FAILED(rv)) {
1796     return rv;
1797   }
1798 
1799   // We don't need to worry about "/foo/" vs. "/foo" here
1800   // because trailing slashes are stripped on init.
1801   *aResult = !strcmp(inPath.get(), mPath.get());
1802   return NS_OK;
1803 }
1804 
1805 NS_IMETHODIMP
Contains(nsIFile * aInFile,bool * aResult)1806 nsLocalFile::Contains(nsIFile* aInFile, bool* aResult) {
1807   CHECK_mPath();
1808   if (NS_WARN_IF(!aInFile)) {
1809     return NS_ERROR_INVALID_ARG;
1810   }
1811   if (NS_WARN_IF(!aResult)) {
1812     return NS_ERROR_INVALID_ARG;
1813   }
1814 
1815   nsAutoCString inPath;
1816   nsresult rv;
1817 
1818   if (NS_FAILED(rv = aInFile->GetNativePath(inPath))) {
1819     return rv;
1820   }
1821 
1822   *aResult = false;
1823 
1824   ssize_t len = mPath.Length();
1825   if (strncmp(mPath.get(), inPath.get(), len) == 0) {
1826     // Now make sure that the |aInFile|'s path has a separator at len,
1827     // which implies that it has more components after len.
1828     if (inPath[len] == '/') {
1829       *aResult = true;
1830     }
1831   }
1832 
1833   return NS_OK;
1834 }
1835 
1836 NS_IMETHODIMP
GetNativeTarget(nsACString & aResult)1837 nsLocalFile::GetNativeTarget(nsACString& aResult) {
1838   CHECK_mPath();
1839   aResult.Truncate();
1840 
1841   struct STAT symStat;
1842   if (LSTAT(mPath.get(), &symStat) == -1) {
1843     return NSRESULT_FOR_ERRNO();
1844   }
1845 
1846   if (!S_ISLNK(symStat.st_mode)) {
1847     return NS_ERROR_FILE_INVALID_PATH;
1848   }
1849 
1850   int32_t size = (int32_t)symStat.st_size;
1851   nsAutoCString target;
1852   if (!target.SetLength(size, mozilla::fallible)) {
1853     return NS_ERROR_OUT_OF_MEMORY;
1854   }
1855 
1856   if (readlink(mPath.get(), target.BeginWriting(), (size_t)size) < 0) {
1857     return NSRESULT_FOR_ERRNO();
1858   }
1859 
1860   nsresult rv = NS_OK;
1861   nsCOMPtr<nsIFile> self(this);
1862   int32_t maxLinks = 40;
1863   while (true) {
1864     if (maxLinks-- == 0) {
1865       rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
1866       break;
1867     }
1868 
1869     if (target[0] != '/') {
1870       nsCOMPtr<nsIFile> parent;
1871       if (NS_FAILED(rv = self->GetParent(getter_AddRefs(parent)))) {
1872         break;
1873       }
1874       if (NS_FAILED(rv = parent->AppendRelativeNativePath(target))) {
1875         break;
1876       }
1877       if (NS_FAILED(rv = parent->GetNativePath(aResult))) {
1878         break;
1879       }
1880       self = parent;
1881     } else {
1882       aResult = target;
1883     }
1884 
1885     const nsPromiseFlatCString& flatRetval = PromiseFlatCString(aResult);
1886 
1887     // Any failure in testing the current target we'll just interpret
1888     // as having reached our destiny.
1889     if (LSTAT(flatRetval.get(), &symStat) == -1) {
1890       break;
1891     }
1892 
1893     // And of course we're done if it isn't a symlink.
1894     if (!S_ISLNK(symStat.st_mode)) {
1895       break;
1896     }
1897 
1898     int32_t newSize = (int32_t)symStat.st_size;
1899     size = newSize;
1900     nsAutoCString newTarget;
1901     if (!newTarget.SetLength(size, mozilla::fallible)) {
1902       rv = NS_ERROR_OUT_OF_MEMORY;
1903       break;
1904     }
1905 
1906     int32_t linkLen =
1907         readlink(flatRetval.get(), newTarget.BeginWriting(), size);
1908     if (linkLen == -1) {
1909       rv = NSRESULT_FOR_ERRNO();
1910       break;
1911     }
1912     target = newTarget;
1913   }
1914 
1915   if (NS_FAILED(rv)) {
1916     aResult.Truncate();
1917   }
1918   return rv;
1919 }
1920 
1921 NS_IMETHODIMP
GetDirectoryEntriesImpl(nsIDirectoryEnumerator ** aEntries)1922 nsLocalFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator** aEntries) {
1923   RefPtr<nsDirEnumeratorUnix> dir = new nsDirEnumeratorUnix();
1924 
1925   nsresult rv = dir->Init(this, false);
1926   if (NS_FAILED(rv)) {
1927     *aEntries = nullptr;
1928   } else {
1929     dir.forget(aEntries);
1930   }
1931 
1932   return rv;
1933 }
1934 
1935 NS_IMETHODIMP
Load(PRLibrary ** aResult)1936 nsLocalFile::Load(PRLibrary** aResult) {
1937   CHECK_mPath();
1938   if (NS_WARN_IF(!aResult)) {
1939     return NS_ERROR_INVALID_ARG;
1940   }
1941 
1942 #ifdef NS_BUILD_REFCNT_LOGGING
1943   nsTraceRefcnt::SetActivityIsLegal(false);
1944 #endif
1945 
1946   *aResult = PR_LoadLibrary(mPath.get());
1947 
1948 #ifdef NS_BUILD_REFCNT_LOGGING
1949   nsTraceRefcnt::SetActivityIsLegal(true);
1950 #endif
1951 
1952   if (!*aResult) {
1953     return NS_ERROR_FAILURE;
1954   }
1955   return NS_OK;
1956 }
1957 
1958 NS_IMETHODIMP
GetPersistentDescriptor(nsACString & aPersistentDescriptor)1959 nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor) {
1960   return GetNativePath(aPersistentDescriptor);
1961 }
1962 
1963 NS_IMETHODIMP
SetPersistentDescriptor(const nsACString & aPersistentDescriptor)1964 nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor) {
1965 #ifdef MOZ_WIDGET_COCOA
1966   if (aPersistentDescriptor.IsEmpty()) {
1967     return NS_ERROR_INVALID_ARG;
1968   }
1969 
1970   // Support pathnames as user-supplied descriptors if they begin with '/'
1971   // or '~'.  These characters do not collide with the base64 set used for
1972   // encoding alias records.
1973   char first = aPersistentDescriptor.First();
1974   if (first == '/' || first == '~') {
1975     return InitWithNativePath(aPersistentDescriptor);
1976   }
1977 
1978   uint32_t dataSize = aPersistentDescriptor.Length();
1979   char* decodedData = PL_Base64Decode(
1980       PromiseFlatCString(aPersistentDescriptor).get(), dataSize, nullptr);
1981   if (!decodedData) {
1982     NS_ERROR("SetPersistentDescriptor was given bad data");
1983     return NS_ERROR_FAILURE;
1984   }
1985 
1986   // Cast to an alias record and resolve.
1987   AliasRecord aliasHeader = *(AliasPtr)decodedData;
1988   int32_t aliasSize = ::GetAliasSizeFromPtr(&aliasHeader);
1989   if (aliasSize >
1990       ((int32_t)dataSize * 3) / 4) {  // be paranoid about having too few data
1991     PR_Free(decodedData);             // PL_Base64Decode() uses PR_Malloc().
1992     return NS_ERROR_FAILURE;
1993   }
1994 
1995   nsresult rv = NS_OK;
1996 
1997   // Move the now-decoded data into the Handle.
1998   // The size of the decoded data is 3/4 the size of the encoded data. See
1999   // plbase64.h
2000   Handle newHandle = nullptr;
2001   if (::PtrToHand(decodedData, &newHandle, aliasSize) != noErr) {
2002     rv = NS_ERROR_OUT_OF_MEMORY;
2003   }
2004   PR_Free(decodedData);  // PL_Base64Decode() uses PR_Malloc().
2005   if (NS_FAILED(rv)) {
2006     return rv;
2007   }
2008 
2009   Boolean changed;
2010   FSRef resolvedFSRef;
2011   OSErr err = ::FSResolveAlias(nullptr, (AliasHandle)newHandle, &resolvedFSRef,
2012                                &changed);
2013 
2014   rv = MacErrorMapper(err);
2015   DisposeHandle(newHandle);
2016   if (NS_FAILED(rv)) {
2017     return rv;
2018   }
2019 
2020   return InitWithFSRef(&resolvedFSRef);
2021 #else
2022   return InitWithNativePath(aPersistentDescriptor);
2023 #endif
2024 }
2025 
2026 NS_IMETHODIMP
Reveal()2027 nsLocalFile::Reveal() {
2028   if (!FilePreferences::IsAllowedPath(mPath)) {
2029     return NS_ERROR_FILE_ACCESS_DENIED;
2030   }
2031 
2032 #ifdef MOZ_WIDGET_GTK
2033   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
2034   if (!giovfs) {
2035     return NS_ERROR_FAILURE;
2036   }
2037 
2038   bool isDirectory;
2039   if (NS_FAILED(IsDirectory(&isDirectory))) {
2040     return NS_ERROR_FAILURE;
2041   }
2042 
2043   if (isDirectory) {
2044     return giovfs->ShowURIForInput(mPath);
2045   }
2046   if (NS_SUCCEEDED(giovfs->OrgFreedesktopFileManager1ShowItems(mPath))) {
2047     return NS_OK;
2048   }
2049   nsCOMPtr<nsIFile> parentDir;
2050   nsAutoCString dirPath;
2051   if (NS_FAILED(GetParent(getter_AddRefs(parentDir)))) {
2052     return NS_ERROR_FAILURE;
2053   }
2054   if (NS_FAILED(parentDir->GetNativePath(dirPath))) {
2055     return NS_ERROR_FAILURE;
2056   }
2057 
2058   return giovfs->ShowURIForInput(dirPath);
2059 #elif defined(MOZ_WIDGET_COCOA)
2060   CFURLRef url;
2061   if (NS_SUCCEEDED(GetCFURL(&url))) {
2062     nsresult rv = CocoaFileUtils::RevealFileInFinder(url);
2063     ::CFRelease(url);
2064     return rv;
2065   }
2066   return NS_ERROR_FAILURE;
2067 #else
2068   return NS_ERROR_FAILURE;
2069 #endif
2070 }
2071 
2072 NS_IMETHODIMP
Launch()2073 nsLocalFile::Launch() {
2074   if (!FilePreferences::IsAllowedPath(mPath)) {
2075     return NS_ERROR_FILE_ACCESS_DENIED;
2076   }
2077 
2078 #ifdef MOZ_WIDGET_GTK
2079   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
2080   if (!giovfs) {
2081     return NS_ERROR_FAILURE;
2082   }
2083 
2084   return giovfs->ShowURIForInput(mPath);
2085 #elif defined(MOZ_WIDGET_ANDROID)
2086   // Not supported on GeckoView
2087   return NS_ERROR_NOT_IMPLEMENTED;
2088 #elif defined(MOZ_WIDGET_COCOA)
2089   CFURLRef url;
2090   if (NS_SUCCEEDED(GetCFURL(&url))) {
2091     nsresult rv = CocoaFileUtils::OpenURL(url);
2092     ::CFRelease(url);
2093     return rv;
2094   }
2095   return NS_ERROR_FAILURE;
2096 #else
2097   return NS_ERROR_FAILURE;
2098 #endif
2099 }
2100 
NS_NewNativeLocalFile(const nsACString & aPath,bool aFollowSymlinks,nsIFile ** aResult)2101 nsresult NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowSymlinks,
2102                                nsIFile** aResult) {
2103   RefPtr<nsLocalFile> file = new nsLocalFile();
2104 
2105   if (!aPath.IsEmpty()) {
2106     nsresult rv = file->InitWithNativePath(aPath);
2107     if (NS_FAILED(rv)) {
2108       return rv;
2109     }
2110   }
2111   file.forget(aResult);
2112   return NS_OK;
2113 }
2114 
2115 //-----------------------------------------------------------------------------
2116 // unicode support
2117 //-----------------------------------------------------------------------------
2118 
2119 #define SET_UCS(func, ucsArg)                          \
2120   {                                                    \
2121     nsAutoCString buf;                                 \
2122     nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \
2123     if (NS_FAILED(rv)) return rv;                      \
2124     return (func)(buf);                                \
2125   }
2126 
2127 #define GET_UCS(func, ucsArg)                   \
2128   {                                             \
2129     nsAutoCString buf;                          \
2130     nsresult rv = (func)(buf);                  \
2131     if (NS_FAILED(rv)) return rv;               \
2132     return NS_CopyNativeToUnicode(buf, ucsArg); \
2133   }
2134 
2135 #define SET_UCS_2ARGS_2(func, opaqueArg, ucsArg)       \
2136   {                                                    \
2137     nsAutoCString buf;                                 \
2138     nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \
2139     if (NS_FAILED(rv)) return rv;                      \
2140     return (func)(opaqueArg, buf);                     \
2141   }
2142 
2143 // Unicode interface Wrapper
InitWithPath(const nsAString & aFilePath)2144 nsresult nsLocalFile::InitWithPath(const nsAString& aFilePath) {
2145   SET_UCS(InitWithNativePath, aFilePath);
2146 }
Append(const nsAString & aNode)2147 nsresult nsLocalFile::Append(const nsAString& aNode) {
2148   SET_UCS(AppendNative, aNode);
2149 }
AppendRelativePath(const nsAString & aNode)2150 nsresult nsLocalFile::AppendRelativePath(const nsAString& aNode) {
2151   SET_UCS(AppendRelativeNativePath, aNode);
2152 }
GetLeafName(nsAString & aLeafName)2153 nsresult nsLocalFile::GetLeafName(nsAString& aLeafName) {
2154   GET_UCS(GetNativeLeafName, aLeafName);
2155 }
SetLeafName(const nsAString & aLeafName)2156 nsresult nsLocalFile::SetLeafName(const nsAString& aLeafName) {
2157   SET_UCS(SetNativeLeafName, aLeafName);
2158 }
GetPath(nsAString & aResult)2159 nsresult nsLocalFile::GetPath(nsAString& aResult) {
2160   return NS_CopyNativeToUnicode(mPath, aResult);
2161 }
CopyTo(nsIFile * aNewParentDir,const nsAString & aNewName)2162 nsresult nsLocalFile::CopyTo(nsIFile* aNewParentDir,
2163                              const nsAString& aNewName) {
2164   SET_UCS_2ARGS_2(CopyToNative, aNewParentDir, aNewName);
2165 }
CopyToFollowingLinks(nsIFile * aNewParentDir,const nsAString & aNewName)2166 nsresult nsLocalFile::CopyToFollowingLinks(nsIFile* aNewParentDir,
2167                                            const nsAString& aNewName) {
2168   SET_UCS_2ARGS_2(CopyToFollowingLinksNative, aNewParentDir, aNewName);
2169 }
MoveTo(nsIFile * aNewParentDir,const nsAString & aNewName)2170 nsresult nsLocalFile::MoveTo(nsIFile* aNewParentDir,
2171                              const nsAString& aNewName) {
2172   SET_UCS_2ARGS_2(MoveToNative, aNewParentDir, aNewName);
2173 }
2174 NS_IMETHODIMP
MoveToFollowingLinks(nsIFile * aNewParentDir,const nsAString & aNewName)2175 nsLocalFile::MoveToFollowingLinks(nsIFile* aNewParentDir,
2176                                   const nsAString& aNewName) {
2177   SET_UCS_2ARGS_2(MoveToFollowingLinksNative, aNewParentDir, aNewName);
2178 }
2179 
2180 NS_IMETHODIMP
RenameTo(nsIFile * aNewParentDir,const nsAString & aNewName)2181 nsLocalFile::RenameTo(nsIFile* aNewParentDir, const nsAString& aNewName) {
2182   SET_UCS_2ARGS_2(RenameToNative, aNewParentDir, aNewName);
2183 }
2184 
2185 NS_IMETHODIMP
RenameToNative(nsIFile * aNewParentDir,const nsACString & aNewName)2186 nsLocalFile::RenameToNative(nsIFile* aNewParentDir,
2187                             const nsACString& aNewName) {
2188   nsresult rv;
2189 
2190   // check to make sure that this has been initialized properly
2191   CHECK_mPath();
2192 
2193   // check to make sure that we have a new parent
2194   nsAutoCString newPathName;
2195   rv = GetNativeTargetPathName(aNewParentDir, aNewName, newPathName);
2196   if (NS_FAILED(rv)) {
2197     return rv;
2198   }
2199 
2200   if (!FilePreferences::IsAllowedPath(newPathName)) {
2201     return NS_ERROR_FILE_ACCESS_DENIED;
2202   }
2203 
2204   // try for atomic rename
2205   if (rename(mPath.get(), newPathName.get()) < 0) {
2206     if (errno == EXDEV) {
2207       rv = NS_ERROR_FILE_ACCESS_DENIED;
2208     } else {
2209       rv = NSRESULT_FOR_ERRNO();
2210     }
2211   }
2212 
2213   return rv;
2214 }
2215 
GetTarget(nsAString & aResult)2216 nsresult nsLocalFile::GetTarget(nsAString& aResult) {
2217   GET_UCS(GetNativeTarget, aResult);
2218 }
2219 
NS_NewLocalFile(const nsAString & aPath,bool aFollowLinks,nsIFile ** aResult)2220 nsresult NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks,
2221                          nsIFile** aResult) {
2222   nsAutoCString buf;
2223   nsresult rv = NS_CopyUnicodeToNative(aPath, buf);
2224   if (NS_FAILED(rv)) {
2225     return rv;
2226   }
2227   return NS_NewNativeLocalFile(buf, aFollowLinks, aResult);
2228 }
2229 
2230 // nsILocalFileMac
2231 
2232 #ifdef MOZ_WIDGET_COCOA
2233 
MacErrorMapper(OSErr inErr)2234 static nsresult MacErrorMapper(OSErr inErr) {
2235   nsresult outErr;
2236 
2237   switch (inErr) {
2238     case noErr:
2239       outErr = NS_OK;
2240       break;
2241 
2242     case fnfErr:
2243     case afpObjectNotFound:
2244     case afpDirNotFound:
2245       outErr = NS_ERROR_FILE_NOT_FOUND;
2246       break;
2247 
2248     case dupFNErr:
2249     case afpObjectExists:
2250       outErr = NS_ERROR_FILE_ALREADY_EXISTS;
2251       break;
2252 
2253     case dskFulErr:
2254     case afpDiskFull:
2255       outErr = NS_ERROR_FILE_NO_DEVICE_SPACE;
2256       break;
2257 
2258     case fLckdErr:
2259     case afpVolLocked:
2260       outErr = NS_ERROR_FILE_IS_LOCKED;
2261       break;
2262 
2263     case afpAccessDenied:
2264       outErr = NS_ERROR_FILE_ACCESS_DENIED;
2265       break;
2266 
2267     case afpDirNotEmpty:
2268       outErr = NS_ERROR_FILE_DIR_NOT_EMPTY;
2269       break;
2270 
2271     // Can't find good map for some
2272     case bdNamErr:
2273       outErr = NS_ERROR_FAILURE;
2274       break;
2275 
2276     default:
2277       outErr = NS_ERROR_FAILURE;
2278       break;
2279   }
2280 
2281   return outErr;
2282 }
2283 
CFStringReftoUTF8(CFStringRef aInStrRef,nsACString & aOutStr)2284 static nsresult CFStringReftoUTF8(CFStringRef aInStrRef, nsACString& aOutStr) {
2285   // first see if the conversion would succeed and find the length of the
2286   // result
2287   CFIndex usedBufLen, inStrLen = ::CFStringGetLength(aInStrRef);
2288   CFIndex charsConverted = ::CFStringGetBytes(
2289       aInStrRef, CFRangeMake(0, inStrLen), kCFStringEncodingUTF8, 0, false,
2290       nullptr, 0, &usedBufLen);
2291   if (charsConverted == inStrLen) {
2292     // all characters converted, do the actual conversion
2293     aOutStr.SetLength(usedBufLen);
2294     if (aOutStr.Length() != (unsigned int)usedBufLen) {
2295       return NS_ERROR_OUT_OF_MEMORY;
2296     }
2297     UInt8* buffer = (UInt8*)aOutStr.BeginWriting();
2298     ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen),
2299                        kCFStringEncodingUTF8, 0, false, buffer, usedBufLen,
2300                        &usedBufLen);
2301     return NS_OK;
2302   }
2303 
2304   return NS_ERROR_FAILURE;
2305 }
2306 
2307 NS_IMETHODIMP
InitWithCFURL(CFURLRef aCFURL)2308 nsLocalFile::InitWithCFURL(CFURLRef aCFURL) {
2309   UInt8 path[PATH_MAX];
2310   if (::CFURLGetFileSystemRepresentation(aCFURL, true, path, PATH_MAX)) {
2311     nsDependentCString nativePath((char*)path);
2312     return InitWithNativePath(nativePath);
2313   }
2314 
2315   return NS_ERROR_FAILURE;
2316 }
2317 
2318 NS_IMETHODIMP
InitWithFSRef(const FSRef * aFSRef)2319 nsLocalFile::InitWithFSRef(const FSRef* aFSRef) {
2320   if (NS_WARN_IF(!aFSRef)) {
2321     return NS_ERROR_INVALID_ARG;
2322   }
2323 
2324   CFURLRef newURLRef = ::CFURLCreateFromFSRef(kCFAllocatorDefault, aFSRef);
2325   if (newURLRef) {
2326     nsresult rv = InitWithCFURL(newURLRef);
2327     ::CFRelease(newURLRef);
2328     return rv;
2329   }
2330 
2331   return NS_ERROR_FAILURE;
2332 }
2333 
2334 NS_IMETHODIMP
GetCFURL(CFURLRef * aResult)2335 nsLocalFile::GetCFURL(CFURLRef* aResult) {
2336   CHECK_mPath();
2337 
2338   bool isDir;
2339   IsDirectory(&isDir);
2340   *aResult = ::CFURLCreateFromFileSystemRepresentation(
2341       kCFAllocatorDefault, (UInt8*)mPath.get(), mPath.Length(), isDir);
2342 
2343   return (*aResult ? NS_OK : NS_ERROR_FAILURE);
2344 }
2345 
2346 NS_IMETHODIMP
GetFSRef(FSRef * aResult)2347 nsLocalFile::GetFSRef(FSRef* aResult) {
2348   if (NS_WARN_IF(!aResult)) {
2349     return NS_ERROR_INVALID_ARG;
2350   }
2351 
2352   nsresult rv = NS_ERROR_FAILURE;
2353 
2354   CFURLRef url = nullptr;
2355   if (NS_SUCCEEDED(GetCFURL(&url))) {
2356     if (::CFURLGetFSRef(url, aResult)) {
2357       rv = NS_OK;
2358     }
2359     ::CFRelease(url);
2360   }
2361 
2362   return rv;
2363 }
2364 
2365 NS_IMETHODIMP
GetFSSpec(FSSpec * aResult)2366 nsLocalFile::GetFSSpec(FSSpec* aResult) {
2367   if (NS_WARN_IF(!aResult)) {
2368     return NS_ERROR_INVALID_ARG;
2369   }
2370 
2371   FSRef fsRef;
2372   nsresult rv = GetFSRef(&fsRef);
2373   if (NS_SUCCEEDED(rv)) {
2374     OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNone, nullptr, nullptr,
2375                                    aResult, nullptr);
2376     return MacErrorMapper(err);
2377   }
2378 
2379   return rv;
2380 }
2381 
2382 NS_IMETHODIMP
GetFileSizeWithResFork(int64_t * aFileSizeWithResFork)2383 nsLocalFile::GetFileSizeWithResFork(int64_t* aFileSizeWithResFork) {
2384   if (NS_WARN_IF(!aFileSizeWithResFork)) {
2385     return NS_ERROR_INVALID_ARG;
2386   }
2387 
2388   FSRef fsRef;
2389   nsresult rv = GetFSRef(&fsRef);
2390   if (NS_FAILED(rv)) {
2391     return rv;
2392   }
2393 
2394   FSCatalogInfo catalogInfo;
2395   OSErr err =
2396       ::FSGetCatalogInfo(&fsRef, kFSCatInfoDataSizes + kFSCatInfoRsrcSizes,
2397                          &catalogInfo, nullptr, nullptr, nullptr);
2398   if (err != noErr) {
2399     return MacErrorMapper(err);
2400   }
2401 
2402   *aFileSizeWithResFork =
2403       catalogInfo.dataLogicalSize + catalogInfo.rsrcLogicalSize;
2404   return NS_OK;
2405 }
2406 
2407 NS_IMETHODIMP
GetFileType(OSType * aFileType)2408 nsLocalFile::GetFileType(OSType* aFileType) {
2409   CFURLRef url;
2410   if (NS_SUCCEEDED(GetCFURL(&url))) {
2411     nsresult rv = CocoaFileUtils::GetFileTypeCode(url, aFileType);
2412     ::CFRelease(url);
2413     return rv;
2414   }
2415   return NS_ERROR_FAILURE;
2416 }
2417 
2418 NS_IMETHODIMP
SetFileType(OSType aFileType)2419 nsLocalFile::SetFileType(OSType aFileType) {
2420   CFURLRef url;
2421   if (NS_SUCCEEDED(GetCFURL(&url))) {
2422     nsresult rv = CocoaFileUtils::SetFileTypeCode(url, aFileType);
2423     ::CFRelease(url);
2424     return rv;
2425   }
2426   return NS_ERROR_FAILURE;
2427 }
2428 
2429 NS_IMETHODIMP
GetFileCreator(OSType * aFileCreator)2430 nsLocalFile::GetFileCreator(OSType* aFileCreator) {
2431   CFURLRef url;
2432   if (NS_SUCCEEDED(GetCFURL(&url))) {
2433     nsresult rv = CocoaFileUtils::GetFileCreatorCode(url, aFileCreator);
2434     ::CFRelease(url);
2435     return rv;
2436   }
2437   return NS_ERROR_FAILURE;
2438 }
2439 
2440 NS_IMETHODIMP
SetFileCreator(OSType aFileCreator)2441 nsLocalFile::SetFileCreator(OSType aFileCreator) {
2442   CFURLRef url;
2443   if (NS_SUCCEEDED(GetCFURL(&url))) {
2444     nsresult rv = CocoaFileUtils::SetFileCreatorCode(url, aFileCreator);
2445     ::CFRelease(url);
2446     return rv;
2447   }
2448   return NS_ERROR_FAILURE;
2449 }
2450 
2451 NS_IMETHODIMP
LaunchWithDoc(nsIFile * aDocToLoad,bool aLaunchInBackground)2452 nsLocalFile::LaunchWithDoc(nsIFile* aDocToLoad, bool aLaunchInBackground) {
2453   bool isExecutable;
2454   nsresult rv = IsExecutable(&isExecutable);
2455   if (NS_FAILED(rv)) {
2456     return rv;
2457   }
2458   if (!isExecutable) {
2459     return NS_ERROR_FILE_EXECUTION_FAILED;
2460   }
2461 
2462   FSRef appFSRef, docFSRef;
2463   rv = GetFSRef(&appFSRef);
2464   if (NS_FAILED(rv)) {
2465     return rv;
2466   }
2467 
2468   if (aDocToLoad) {
2469     nsCOMPtr<nsILocalFileMac> macDoc = do_QueryInterface(aDocToLoad);
2470     rv = macDoc->GetFSRef(&docFSRef);
2471     if (NS_FAILED(rv)) {
2472       return rv;
2473     }
2474   }
2475 
2476   LSLaunchFlags theLaunchFlags = kLSLaunchDefaults;
2477   LSLaunchFSRefSpec thelaunchSpec;
2478 
2479   if (aLaunchInBackground) {
2480     theLaunchFlags |= kLSLaunchDontSwitch;
2481   }
2482   memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec));
2483 
2484   thelaunchSpec.appRef = &appFSRef;
2485   if (aDocToLoad) {
2486     thelaunchSpec.numDocs = 1;
2487     thelaunchSpec.itemRefs = &docFSRef;
2488   }
2489   thelaunchSpec.launchFlags = theLaunchFlags;
2490 
2491   OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, nullptr);
2492   if (err != noErr) {
2493     return MacErrorMapper(err);
2494   }
2495 
2496   return NS_OK;
2497 }
2498 
2499 NS_IMETHODIMP
OpenDocWithApp(nsIFile * aAppToOpenWith,bool aLaunchInBackground)2500 nsLocalFile::OpenDocWithApp(nsIFile* aAppToOpenWith, bool aLaunchInBackground) {
2501   FSRef docFSRef;
2502   nsresult rv = GetFSRef(&docFSRef);
2503   if (NS_FAILED(rv)) {
2504     return rv;
2505   }
2506 
2507   if (!aAppToOpenWith) {
2508     OSErr err = ::LSOpenFSRef(&docFSRef, nullptr);
2509     return MacErrorMapper(err);
2510   }
2511 
2512   nsCOMPtr<nsILocalFileMac> appFileMac = do_QueryInterface(aAppToOpenWith, &rv);
2513   if (!appFileMac) {
2514     return rv;
2515   }
2516 
2517   bool isExecutable;
2518   rv = appFileMac->IsExecutable(&isExecutable);
2519   if (NS_FAILED(rv)) {
2520     return rv;
2521   }
2522   if (!isExecutable) {
2523     return NS_ERROR_FILE_EXECUTION_FAILED;
2524   }
2525 
2526   FSRef appFSRef;
2527   rv = appFileMac->GetFSRef(&appFSRef);
2528   if (NS_FAILED(rv)) {
2529     return rv;
2530   }
2531 
2532   LSLaunchFlags theLaunchFlags = kLSLaunchDefaults;
2533   LSLaunchFSRefSpec thelaunchSpec;
2534 
2535   if (aLaunchInBackground) {
2536     theLaunchFlags |= kLSLaunchDontSwitch;
2537   }
2538   memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec));
2539 
2540   thelaunchSpec.appRef = &appFSRef;
2541   thelaunchSpec.numDocs = 1;
2542   thelaunchSpec.itemRefs = &docFSRef;
2543   thelaunchSpec.launchFlags = theLaunchFlags;
2544 
2545   OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, nullptr);
2546   if (err != noErr) {
2547     return MacErrorMapper(err);
2548   }
2549 
2550   return NS_OK;
2551 }
2552 
2553 NS_IMETHODIMP
IsPackage(bool * aResult)2554 nsLocalFile::IsPackage(bool* aResult) {
2555   if (NS_WARN_IF(!aResult)) {
2556     return NS_ERROR_INVALID_ARG;
2557   }
2558   *aResult = false;
2559 
2560   CFURLRef url;
2561   nsresult rv = GetCFURL(&url);
2562   if (NS_FAILED(rv)) {
2563     return rv;
2564   }
2565 
2566   LSItemInfoRecord info;
2567   OSStatus status =
2568       ::LSCopyItemInfoForURL(url, kLSRequestBasicFlagsOnly, &info);
2569 
2570   ::CFRelease(url);
2571 
2572   if (status != noErr) {
2573     return NS_ERROR_FAILURE;
2574   }
2575 
2576   *aResult = !!(info.flags & kLSItemInfoIsPackage);
2577 
2578   return NS_OK;
2579 }
2580 
2581 NS_IMETHODIMP
GetBundleDisplayName(nsAString & aOutBundleName)2582 nsLocalFile::GetBundleDisplayName(nsAString& aOutBundleName) {
2583   bool isPackage = false;
2584   nsresult rv = IsPackage(&isPackage);
2585   if (NS_FAILED(rv) || !isPackage) {
2586     return NS_ERROR_FAILURE;
2587   }
2588 
2589   nsAutoString name;
2590   rv = GetLeafName(name);
2591   if (NS_FAILED(rv)) {
2592     return rv;
2593   }
2594 
2595   int32_t length = name.Length();
2596   if (Substring(name, length - 4, length).EqualsLiteral(".app")) {
2597     // 4 characters in ".app"
2598     aOutBundleName = Substring(name, 0, length - 4);
2599   } else {
2600     aOutBundleName = name;
2601   }
2602 
2603   return NS_OK;
2604 }
2605 
2606 NS_IMETHODIMP
GetBundleIdentifier(nsACString & aOutBundleIdentifier)2607 nsLocalFile::GetBundleIdentifier(nsACString& aOutBundleIdentifier) {
2608   nsresult rv = NS_ERROR_FAILURE;
2609 
2610   CFURLRef urlRef;
2611   if (NS_SUCCEEDED(GetCFURL(&urlRef))) {
2612     CFBundleRef bundle = ::CFBundleCreate(nullptr, urlRef);
2613     if (bundle) {
2614       CFStringRef bundleIdentifier = ::CFBundleGetIdentifier(bundle);
2615       if (bundleIdentifier) {
2616         rv = CFStringReftoUTF8(bundleIdentifier, aOutBundleIdentifier);
2617       }
2618       ::CFRelease(bundle);
2619     }
2620     ::CFRelease(urlRef);
2621   }
2622 
2623   return rv;
2624 }
2625 
2626 NS_IMETHODIMP
GetBundleContentsLastModifiedTime(int64_t * aLastModTime)2627 nsLocalFile::GetBundleContentsLastModifiedTime(int64_t* aLastModTime) {
2628   CHECK_mPath();
2629   if (NS_WARN_IF(!aLastModTime)) {
2630     return NS_ERROR_INVALID_ARG;
2631   }
2632 
2633   bool isPackage = false;
2634   nsresult rv = IsPackage(&isPackage);
2635   if (NS_FAILED(rv) || !isPackage) {
2636     return GetLastModifiedTime(aLastModTime);
2637   }
2638 
2639   nsAutoCString infoPlistPath(mPath);
2640   infoPlistPath.AppendLiteral("/Contents/Info.plist");
2641   PRFileInfo64 info;
2642   if (PR_GetFileInfo64(infoPlistPath.get(), &info) != PR_SUCCESS) {
2643     return GetLastModifiedTime(aLastModTime);
2644   }
2645   int64_t modTime = int64_t(info.modifyTime);
2646   if (modTime == 0) {
2647     *aLastModTime = 0;
2648   } else {
2649     *aLastModTime = modTime / int64_t(PR_USEC_PER_MSEC);
2650   }
2651 
2652   return NS_OK;
2653 }
2654 
InitWithFile(nsIFile * aFile)2655 NS_IMETHODIMP nsLocalFile::InitWithFile(nsIFile* aFile) {
2656   if (NS_WARN_IF(!aFile)) {
2657     return NS_ERROR_INVALID_ARG;
2658   }
2659 
2660   nsAutoCString nativePath;
2661   nsresult rv = aFile->GetNativePath(nativePath);
2662   if (NS_FAILED(rv)) {
2663     return rv;
2664   }
2665 
2666   return InitWithNativePath(nativePath);
2667 }
2668 
NS_NewLocalFileWithFSRef(const FSRef * aFSRef,bool aFollowLinks,nsILocalFileMac ** aResult)2669 nsresult NS_NewLocalFileWithFSRef(const FSRef* aFSRef, bool aFollowLinks,
2670                                   nsILocalFileMac** aResult) {
2671   RefPtr<nsLocalFile> file = new nsLocalFile();
2672 
2673   nsresult rv = file->InitWithFSRef(aFSRef);
2674   if (NS_FAILED(rv)) {
2675     return rv;
2676   }
2677   file.forget(aResult);
2678   return NS_OK;
2679 }
2680 
NS_NewLocalFileWithCFURL(const CFURLRef aURL,bool aFollowLinks,nsILocalFileMac ** aResult)2681 nsresult NS_NewLocalFileWithCFURL(const CFURLRef aURL, bool aFollowLinks,
2682                                   nsILocalFileMac** aResult) {
2683   RefPtr<nsLocalFile> file = new nsLocalFile();
2684 
2685   nsresult rv = file->InitWithCFURL(aURL);
2686   if (NS_FAILED(rv)) {
2687     return rv;
2688   }
2689   file.forget(aResult);
2690   return NS_OK;
2691 }
2692 
2693 #endif
2694