1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/ArrayUtils.h"
8 #include "mozilla/DebugOnly.h"
9 #include "mozilla/ProfilerLabels.h"
10 #include "mozilla/TextUtils.h"
11 #include "mozilla/UniquePtrExtensions.h"
12 #include "mozilla/Utf8.h"
17 #include "nsLocalFile.h"
18 #include "nsLocalFileCommon.h"
19 #include "nsIDirectoryEnumerator.h"
20 #include "nsNativeCharsetUtils.h"
22 #include "nsSimpleEnumerator.h"
24 #include "private/pprio.h" // To get PR_ImportFile
25 #include "nsHashKeys.h"
28 #include "nsReadableUtils.h"
46 #include "mozilla/FilePreferences.h"
47 #include "mozilla/Mutex.h"
48 #include "SpecialSystemDirectory.h"
50 #include "nsTraceRefcnt.h"
51 #include "nsXPCOMCIDInternal.h"
52 #include "nsThreadUtils.h"
53 #include "nsXULAppAPI.h"
54 #include "nsIWindowMediator.h"
56 #include "mozIDOMWindow.h"
57 #include "nsPIDOMWindow.h"
58 #include "nsIWidget.h"
59 #include "mozilla/ShellHeaderOnlyUtils.h"
60 #include "mozilla/WidgetUtils.h"
63 using namespace mozilla
;
64 using mozilla::FilePreferences::kDevicePathSpecifier
;
65 using mozilla::FilePreferences::kPathSeparator
;
67 #define CHECK_mWorkingPath() \
69 if (mWorkingPath.IsEmpty()) return NS_ERROR_NOT_INITIALIZED; \
72 // CopyFileEx only supports unbuffered I/O in Windows Vista and above
73 #ifndef COPY_FILE_NO_BUFFERING
74 # define COPY_FILE_NO_BUFFERING 0x00001000
77 #ifndef FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
78 # define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000
82 # define DRIVE_REMOTE 4
87 nsresult
NewLocalFile(const nsAString
& aPath
, bool aUseDOSDevicePathSyntax
,
89 RefPtr
<nsLocalFile
> file
= new nsLocalFile();
91 file
->SetUseDOSDevicePathSyntax(aUseDOSDevicePathSyntax
);
93 if (!aPath
.IsEmpty()) {
94 nsresult rv
= file
->InitWithPath(aPath
);
100 file
.forget(aResult
);
104 } // anonymous namespace
106 static HWND
GetMostRecentNavigatorHWND() {
108 nsCOMPtr
<nsIWindowMediator
> winMediator(
109 do_GetService(NS_WINDOWMEDIATOR_CONTRACTID
, &rv
));
114 nsCOMPtr
<mozIDOMWindowProxy
> navWin
;
115 rv
= winMediator
->GetMostRecentWindow(u
"navigator:browser",
116 getter_AddRefs(navWin
));
117 if (NS_FAILED(rv
) || !navWin
) {
121 nsPIDOMWindowOuter
* win
= nsPIDOMWindowOuter::From(navWin
);
122 nsCOMPtr
<nsIWidget
> widget
= widget::WidgetUtils::DOMWindowToWidget(win
);
127 return reinterpret_cast<HWND
>(widget
->GetNativeData(NS_NATIVE_WINDOW
));
130 nsresult
nsLocalFile::RevealFile(const nsString
& aResolvedPath
) {
131 MOZ_ASSERT(!NS_IsMainThread(), "Don't run on the main thread");
133 DWORD attributes
= GetFileAttributesW(aResolvedPath
.get());
134 if (INVALID_FILE_ATTRIBUTES
== attributes
) {
135 return NS_ERROR_FILE_INVALID_PATH
;
139 if (attributes
& FILE_ATTRIBUTE_DIRECTORY
) {
140 // We have a directory so we should open the directory itself.
141 LPITEMIDLIST dir
= ILCreateFromPathW(aResolvedPath
.get());
143 return NS_ERROR_FAILURE
;
146 LPCITEMIDLIST selection
[] = {dir
};
147 UINT count
= ArrayLength(selection
);
149 // Perform the open of the directory.
150 hr
= SHOpenFolderAndSelectItems(dir
, count
, selection
, 0);
153 int32_t len
= aResolvedPath
.Length();
154 // We don't currently handle UNC long paths of the form \\?\ anywhere so
155 // this should be fine.
156 if (len
> MAX_PATH
) {
157 return NS_ERROR_FILE_INVALID_PATH
;
159 WCHAR parentDirectoryPath
[MAX_PATH
+ 1] = {0};
160 wcsncpy(parentDirectoryPath
, aResolvedPath
.get(), MAX_PATH
);
161 PathRemoveFileSpecW(parentDirectoryPath
);
163 // We have a file so we should open the parent directory.
164 LPITEMIDLIST dir
= ILCreateFromPathW(parentDirectoryPath
);
166 return NS_ERROR_FAILURE
;
169 // Set the item in the directory to select to the file we want to reveal.
170 LPITEMIDLIST item
= ILCreateFromPathW(aResolvedPath
.get());
173 return NS_ERROR_FAILURE
;
176 LPCITEMIDLIST selection
[] = {item
};
177 UINT count
= ArrayLength(selection
);
179 // Perform the selection of the file.
180 hr
= SHOpenFolderAndSelectItems(dir
, count
, selection
, 0);
186 return SUCCEEDED(hr
) ? NS_OK
: NS_ERROR_FAILURE
;
189 class nsDriveEnumerator
: public nsSimpleEnumerator
,
190 public nsIDirectoryEnumerator
{
192 explicit nsDriveEnumerator(bool aUseDOSDevicePathSyntax
);
193 NS_DECL_ISUPPORTS_INHERITED
194 NS_DECL_NSISIMPLEENUMERATOR
195 NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::)
198 const nsID
& DefaultInterface() override
{ return NS_GET_IID(nsIFile
); }
200 NS_IMETHOD
GetNextFile(nsIFile
** aResult
) override
{
201 bool hasMore
= false;
202 nsresult rv
= HasMoreElements(&hasMore
);
203 if (NS_FAILED(rv
) || !hasMore
) {
206 nsCOMPtr
<nsISupports
> next
;
207 rv
= GetNext(getter_AddRefs(next
));
208 NS_ENSURE_SUCCESS(rv
, rv
);
210 nsCOMPtr
<nsIFile
> result
= do_QueryInterface(next
);
211 result
.forget(aResult
);
215 NS_IMETHOD
Close() override
{ return NS_OK
; }
218 virtual ~nsDriveEnumerator();
220 /* mDrives stores the null-separated drive names.
222 * HasMoreElements checks mStartOfCurrentDrive.
223 * GetNext advances mStartOfCurrentDrive.
226 nsAString::const_iterator mStartOfCurrentDrive
;
227 nsAString::const_iterator mEndOfDrivesString
;
228 const bool mUseDOSDevicePathSyntax
;
231 //-----------------------------------------------------------------------------
232 // static helper functions
233 //-----------------------------------------------------------------------------
236 * While not comprehensive, this will map many common Windows error codes to a
237 * corresponding nsresult. If an unmapped error is encountered, the hex error
238 * code will be logged to stderr. Error codes, names, and descriptions can be
239 * found at the following MSDN page:
240 * https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes
242 * \note When adding more mappings here, it must be checked if there's code that
243 * depends on the current generic NS_ERROR_MODULE_WIN32 mapping for such error
246 static nsresult
ConvertWinError(DWORD aWinErr
) {
250 case ERROR_FILE_NOT_FOUND
:
251 case ERROR_PATH_NOT_FOUND
:
252 case ERROR_INVALID_DRIVE
:
253 case ERROR_NOT_READY
:
254 rv
= NS_ERROR_FILE_NOT_FOUND
;
256 case ERROR_ACCESS_DENIED
:
257 case ERROR_NOT_SAME_DEVICE
:
258 rv
= NS_ERROR_FILE_ACCESS_DENIED
;
260 case ERROR_SHARING_VIOLATION
: // CreateFile without sharing flags
261 case ERROR_LOCK_VIOLATION
: // LockFile, LockFileEx
262 rv
= NS_ERROR_FILE_IS_LOCKED
;
264 case ERROR_NOT_ENOUGH_MEMORY
:
265 case ERROR_INVALID_BLOCK
:
266 case ERROR_INVALID_HANDLE
:
267 case ERROR_ARENA_TRASHED
:
268 rv
= NS_ERROR_OUT_OF_MEMORY
;
270 case ERROR_DIR_NOT_EMPTY
:
271 case ERROR_CURRENT_DIRECTORY
:
272 rv
= NS_ERROR_FILE_DIR_NOT_EMPTY
;
274 case ERROR_WRITE_PROTECT
:
275 rv
= NS_ERROR_FILE_READ_ONLY
;
277 case ERROR_HANDLE_DISK_FULL
:
278 rv
= NS_ERROR_FILE_TOO_BIG
;
280 case ERROR_FILE_EXISTS
:
281 case ERROR_ALREADY_EXISTS
:
282 case ERROR_CANNOT_MAKE
:
283 rv
= NS_ERROR_FILE_ALREADY_EXISTS
;
285 case ERROR_FILENAME_EXCED_RANGE
:
286 rv
= NS_ERROR_FILE_NAME_TOO_LONG
;
288 case ERROR_DIRECTORY
:
289 rv
= NS_ERROR_FILE_NOT_DIRECTORY
;
296 "ConvertWinError received an unrecognized WinError: 0x%" PRIx32
"\n",
297 static_cast<uint32_t>(aWinErr
));
298 MOZ_ASSERT((aWinErr
& 0xFFFF) == aWinErr
);
299 rv
= NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_WIN32
, aWinErr
& 0xFFFF);
305 // as suggested in the MSDN documentation on SetFilePointer
306 static __int64
MyFileSeek64(HANDLE aHandle
, __int64 aDistance
,
310 li
.QuadPart
= aDistance
;
311 li
.LowPart
= SetFilePointer(aHandle
, li
.LowPart
, &li
.HighPart
, aMoveMethod
);
312 if (li
.LowPart
== INVALID_SET_FILE_POINTER
&& GetLastError() != NO_ERROR
) {
319 // Check whether a path is a volume root. Expects paths to be \-terminated.
320 static bool IsRootPath(const nsAString
& aPath
) {
322 if (aPath
.Last() != L
'\\') {
325 if (StringEndsWith(aPath
, u
":\\"_ns
)) {
329 nsAString::const_iterator begin
, end
;
330 aPath
.BeginReading(begin
);
331 aPath
.EndReading(end
);
332 // We know we've got a trailing slash, skip that:
334 // Find the next last slash:
335 if (RFindInReadable(u
"\\"_ns
, begin
, end
)) {
337 aPath
.EndReading(end
);
339 auto lastSegment
= Substring(++begin
, end
);
340 if (lastSegment
.IsEmpty()) {
344 // Check if we end with e.g. "c$", a drive letter in UNC or network shares
345 if (lastSegment
.Last() == L
'$' && lastSegment
.Length() == 2 &&
346 IsAsciiAlpha(lastSegment
.First())) {
349 // Volume GUID paths:
350 if (StringBeginsWith(lastSegment
, u
"Volume{"_ns
) &&
351 lastSegment
.Last() == L
'}') {
358 static auto kSpecialNTFSFilesInRoot
= {
359 u
"$MFT"_ns
, u
"$MFTMirr"_ns
, u
"$LogFile"_ns
, u
"$Volume"_ns
,
360 u
"$AttrDef"_ns
, u
"$Bitmap"_ns
, u
"$Boot"_ns
, u
"$BadClus"_ns
,
361 u
"$Secure"_ns
, u
"$UpCase"_ns
, u
"$Extend"_ns
};
362 static bool IsSpecialNTFSPath(const nsAString
& aFilePath
) {
363 nsAString::const_iterator begin
, end
;
364 aFilePath
.BeginReading(begin
);
365 aFilePath
.EndReading(end
);
367 // Early exit if there's no '$' (common case)
368 if (!FindCharInReadable(L
'$', iter
, end
)) {
373 // Any use of ':$' is illegal in filenames anyway; while we support some
374 // ADS stuff (ie ":Zone.Identifier"), none of them use the ':$' syntax:
375 if (FindInReadable(u
":$"_ns
, iter
, end
)) {
379 auto normalized
= mozilla::MakeUniqueFallible
<wchar_t[]>(MAX_PATH
);
383 auto flatPath
= PromiseFlatString(aFilePath
);
385 GetFullPathNameW(flatPath
.get(), MAX_PATH
- 1, normalized
.get(), nullptr);
386 if (fullPathRV
== 0 || fullPathRV
> MAX_PATH
- 1) {
390 nsString
normalizedPath(normalized
.get());
391 normalizedPath
.BeginReading(begin
);
392 normalizedPath
.EndReading(end
);
394 auto kDelimiters
= u
"\\:"_ns
;
395 while (iter
!= end
&& FindCharInReadable(L
'$', iter
, end
)) {
396 for (auto str
: kSpecialNTFSFilesInRoot
) {
397 if (StringBeginsWith(Substring(iter
, end
), str
,
398 nsCaseInsensitiveStringComparator
)) {
399 // If we're enclosed by separators or the beginning/end of the string,
400 // this is one of the special files. Check if we're on a volume root.
401 auto iterCopy
= iter
;
402 iterCopy
.advance(str
.Length());
403 // We check for both \ and : here because the filename could be
404 // followd by a colon and a stream name/type, which shouldn't affect
406 if (iterCopy
== end
|| kDelimiters
.Contains(*iterCopy
)) {
408 // At the start of this path component, we don't need to care about
409 // colons: we would have caught those in the check for `:$` above.
410 if (iterCopy
== begin
|| *(--iterCopy
) == L
'\\') {
411 return IsRootPath(Substring(begin
, iter
));
422 //-----------------------------------------------------------------------------
423 // We need the following three definitions to make |OpenFile| convert a file
424 // handle to an NSPR file descriptor correctly when |O_APPEND| flag is
425 // specified. It is defined in a private header of NSPR (primpl.h) we can't
426 // include. As a temporary workaround until we decide how to extend
427 // |PR_ImportFile|, we define it here. Currently, |_PR_HAVE_PEEK_BUFFER|
428 // and |PR_STRICT_ADDR_LEN| are not defined for the 'w95'-dependent portion
429 // of NSPR so that fields of |PRFilePrivate| #ifdef'd by them are not copied.
430 // Similarly, |_MDFileDesc| is taken from nsprpub/pr/include/md/_win95.h.
431 // In an unlikely case we switch to 'NT'-dependent NSPR AND this temporary
432 // workaround last beyond the switch, |PRFilePrivate| and |_MDFileDesc|
433 // need to be changed to match the definitions for WinNT.
434 //-----------------------------------------------------------------------------
445 struct PRFilePrivate
{
448 _PRTriStateBool inheritable
;
450 int lockCount
; /* 0: not locked
451 * -1: a native lockfile call is in progress
452 * > 0: # times the file is locked */
457 //-----------------------------------------------------------------------------
458 // Six static methods defined below (OpenFile, FileTimeToPRTime, GetFileInfo,
459 // OpenDir, CloseDir, ReadDir) should go away once the corresponding
460 // UTF-16 APIs are implemented on all the supported platforms (or at least
461 // Windows 9x/ME) in NSPR. Currently, they're only implemented on
462 // Windows NT4 or later. (bug 330665)
463 //-----------------------------------------------------------------------------
465 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} :
466 // PR_Open and _PR_MD_OPEN
467 nsresult
OpenFile(const nsString
& aName
, int aOsflags
, int aMode
,
468 bool aShareDelete
, PRFileDesc
** aFd
) {
471 int32_t shareMode
= FILE_SHARE_READ
| FILE_SHARE_WRITE
;
472 int32_t disposition
= 0;
473 int32_t attributes
= 0;
476 shareMode
|= FILE_SHARE_DELETE
;
479 if (aOsflags
& PR_SYNC
) {
480 attributes
= FILE_FLAG_WRITE_THROUGH
;
482 if (aOsflags
& PR_RDONLY
|| aOsflags
& PR_RDWR
) {
483 access
|= GENERIC_READ
;
485 if (aOsflags
& PR_WRONLY
|| aOsflags
& PR_RDWR
) {
486 access
|= GENERIC_WRITE
;
489 if (aOsflags
& PR_CREATE_FILE
&& aOsflags
& PR_EXCL
) {
490 disposition
= CREATE_NEW
;
491 } else if (aOsflags
& PR_CREATE_FILE
) {
492 if (aOsflags
& PR_TRUNCATE
) {
493 disposition
= CREATE_ALWAYS
;
495 disposition
= OPEN_ALWAYS
;
498 if (aOsflags
& PR_TRUNCATE
) {
499 disposition
= TRUNCATE_EXISTING
;
501 disposition
= OPEN_EXISTING
;
505 if (aOsflags
& nsIFile::DELETE_ON_CLOSE
) {
506 attributes
|= FILE_FLAG_DELETE_ON_CLOSE
;
509 if (aOsflags
& nsIFile::OS_READAHEAD
) {
510 attributes
|= FILE_FLAG_SEQUENTIAL_SCAN
;
513 // If no write permissions are requested, and if we are possibly creating
514 // the file, then set the new file as read only.
515 // The flag has no effect if we happen to open the file.
516 if (!(aMode
& (PR_IWUSR
| PR_IWGRP
| PR_IWOTH
)) &&
517 disposition
!= OPEN_EXISTING
) {
518 attributes
|= FILE_ATTRIBUTE_READONLY
;
521 HANDLE file
= ::CreateFileW(aName
.get(), access
, shareMode
, nullptr,
522 disposition
, attributes
, nullptr);
524 if (file
== INVALID_HANDLE_VALUE
) {
526 return ConvertWinError(GetLastError());
529 *aFd
= PR_ImportFile((PROsfd
)file
);
531 // On Windows, _PR_HAVE_O_APPEND is not defined so that we have to
532 // add it manually. (see |PR_Open| in nsprpub/pr/src/io/prfile.c)
533 (*aFd
)->secret
->appendMode
= (PR_APPEND
& aOsflags
) ? true : false;
537 nsresult rv
= NS_ErrorAccordingToNSPR();
544 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} :
545 // PR_FileTimeToPRTime and _PR_FileTimeToPRTime
546 static void FileTimeToPRTime(const FILETIME
* aFiletime
, PRTime
* aPrtm
) {
548 const PRTime _pr_filetime_offset
= 116444736000000000LL;
550 const PRTime _pr_filetime_offset
= 116444736000000000i64
;
553 MOZ_ASSERT(sizeof(FILETIME
) == sizeof(PRTime
));
554 ::CopyMemory(aPrtm
, aFiletime
, sizeof(PRTime
));
556 *aPrtm
= (*aPrtm
- _pr_filetime_offset
) / 10LL;
558 *aPrtm
= (*aPrtm
- _pr_filetime_offset
) / 10i64
;
562 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} with some
563 // changes : PR_GetFileInfo64, _PR_MD_GETFILEINFO64
564 static nsresult
GetFileInfo(const nsString
& aName
, PRFileInfo64
* aInfo
) {
565 if (aName
.IsEmpty()) {
566 return NS_ERROR_INVALID_ARG
;
569 // Checking u"?*" for the file path excluding the kDevicePathSpecifier.
570 // ToDo: Check if checking "?" for the file path is still needed.
571 const int32_t offset
= StringBeginsWith(aName
, kDevicePathSpecifier
)
572 ? kDevicePathSpecifier
.Length()
575 if (aName
.FindCharInSet(u
"?*", offset
) != kNotFound
) {
576 return NS_ERROR_INVALID_ARG
;
579 WIN32_FILE_ATTRIBUTE_DATA fileData
;
580 if (!::GetFileAttributesExW(aName
.get(), GetFileExInfoStandard
, &fileData
)) {
581 return ConvertWinError(GetLastError());
584 if (fileData
.dwFileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
) {
585 aInfo
->type
= PR_FILE_OTHER
;
586 } else if (fileData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
587 aInfo
->type
= PR_FILE_DIRECTORY
;
589 aInfo
->type
= PR_FILE_FILE
;
592 aInfo
->size
= fileData
.nFileSizeHigh
;
593 aInfo
->size
= (aInfo
->size
<< 32) + fileData
.nFileSizeLow
;
595 FileTimeToPRTime(&fileData
.ftLastWriteTime
, &aInfo
->modifyTime
);
597 if (0 == fileData
.ftCreationTime
.dwLowDateTime
&&
598 0 == fileData
.ftCreationTime
.dwHighDateTime
) {
599 aInfo
->creationTime
= aInfo
->modifyTime
;
601 FileTimeToPRTime(&fileData
.ftCreationTime
, &aInfo
->creationTime
);
609 WIN32_FIND_DATAW data
;
613 static nsresult
OpenDir(const nsString
& aName
, nsDir
** aDir
) {
614 if (NS_WARN_IF(!aDir
)) {
615 return NS_ERROR_INVALID_ARG
;
620 nsDir
* d
= new nsDir();
621 nsAutoString
filename(aName
);
623 // If |aName| ends in a slash or backslash, do not append another backslash.
624 if (filename
.Last() == L
'/' || filename
.Last() == L
'\\') {
625 filename
.Append('*');
627 filename
.AppendLiteral("\\*");
630 filename
.ReplaceChar(L
'/', L
'\\');
632 // FindFirstFileW Will have a last error of ERROR_DIRECTORY if
633 // <file_path>\* is passed in. If <unknown_path>\* is passed in then
634 // ERROR_PATH_NOT_FOUND will be the last error.
635 d
->handle
= ::FindFirstFileW(filename
.get(), &(d
->data
));
637 if (d
->handle
== INVALID_HANDLE_VALUE
) {
639 return ConvertWinError(GetLastError());
641 d
->firstEntry
= true;
647 static nsresult
ReadDir(nsDir
* aDir
, PRDirFlags aFlags
, nsString
& aName
) {
649 if (NS_WARN_IF(!aDir
)) {
650 return NS_ERROR_INVALID_ARG
;
655 if (aDir
->firstEntry
) {
656 aDir
->firstEntry
= false;
659 rv
= ::FindNextFileW(aDir
->handle
, &(aDir
->data
));
666 const wchar_t* fileName
;
667 fileName
= (aDir
)->data
.cFileName
;
669 if ((aFlags
& PR_SKIP_DOT
) && (fileName
[0] == L
'.') &&
670 (fileName
[1] == L
'\0')) {
673 if ((aFlags
& PR_SKIP_DOT_DOT
) && (fileName
[0] == L
'.') &&
674 (fileName
[1] == L
'.') && (fileName
[2] == L
'\0')) {
678 DWORD attrib
= aDir
->data
.dwFileAttributes
;
679 if ((aFlags
& PR_SKIP_HIDDEN
) && (attrib
& FILE_ATTRIBUTE_HIDDEN
)) {
687 DWORD err
= GetLastError();
688 return err
== ERROR_NO_MORE_FILES
? NS_OK
: ConvertWinError(err
);
691 static nsresult
CloseDir(nsDir
*& aDir
) {
692 if (NS_WARN_IF(!aDir
)) {
693 return NS_ERROR_INVALID_ARG
;
696 BOOL isOk
= FindClose(aDir
->handle
);
699 return isOk
? NS_OK
: ConvertWinError(GetLastError());
702 //-----------------------------------------------------------------------------
704 //-----------------------------------------------------------------------------
706 class nsDirEnumerator final
: public nsSimpleEnumerator
,
707 public nsIDirectoryEnumerator
{
709 ~nsDirEnumerator() { Close(); }
712 NS_DECL_ISUPPORTS_INHERITED
714 NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::)
716 nsDirEnumerator() : mDir(nullptr) {}
718 const nsID
& DefaultInterface() override
{ return NS_GET_IID(nsIFile
); }
720 nsresult
Init(nsIFile
* aParent
) {
721 nsAutoString filepath
;
722 aParent
->GetTarget(filepath
);
724 if (filepath
.IsEmpty()) {
725 aParent
->GetPath(filepath
);
728 if (filepath
.IsEmpty()) {
729 return NS_ERROR_UNEXPECTED
;
732 // IsDirectory is not needed here because OpenDir will return
733 // NS_ERROR_FILE_NOT_DIRECTORY if the passed in path is a file.
734 nsresult rv
= OpenDir(filepath
, &mDir
);
743 NS_IMETHOD
HasMoreElements(bool* aResult
) override
{
745 if (!mNext
&& mDir
) {
747 rv
= ReadDir(mDir
, PR_SKIP_BOTH
, name
);
751 if (name
.IsEmpty()) {
752 // end of dir entries
762 nsCOMPtr
<nsIFile
> file
;
763 rv
= mParent
->Clone(getter_AddRefs(file
));
768 rv
= file
->Append(name
);
773 mNext
= file
.forget();
775 *aResult
= mNext
!= nullptr;
782 NS_IMETHOD
GetNext(nsISupports
** aResult
) override
{
785 rv
= HasMoreElements(&hasMore
);
790 return NS_ERROR_FAILURE
;
793 mNext
.forget(aResult
);
797 NS_IMETHOD
GetNextFile(nsIFile
** aResult
) override
{
799 bool hasMore
= false;
800 nsresult rv
= HasMoreElements(&hasMore
);
801 if (NS_FAILED(rv
) || !hasMore
) {
804 mNext
.forget(aResult
);
808 NS_IMETHOD
Close() override
{
810 nsresult rv
= CloseDir(mDir
);
811 NS_ASSERTION(NS_SUCCEEDED(rv
), "close failed");
813 return NS_ERROR_FAILURE
;
821 nsCOMPtr
<nsIFile
> mParent
;
822 nsCOMPtr
<nsIFile
> mNext
;
825 NS_IMPL_ISUPPORTS_INHERITED(nsDirEnumerator
, nsSimpleEnumerator
,
826 nsIDirectoryEnumerator
)
828 //-----------------------------------------------------------------------------
829 // nsLocalFile <public>
830 //-----------------------------------------------------------------------------
832 nsLocalFile::nsLocalFile()
833 : mDirty(true), mResolveDirty(true), mUseDOSDevicePathSyntax(false) {}
835 nsLocalFile::nsLocalFile(const nsAString
& aFilePath
)
836 : mUseDOSDevicePathSyntax(false) {
837 InitWithPath(aFilePath
);
840 nsresult
nsLocalFile::nsLocalFileConstructor(nsISupports
* aOuter
,
842 void** aInstancePtr
) {
843 if (NS_WARN_IF(!aInstancePtr
)) {
844 return NS_ERROR_INVALID_ARG
;
846 if (NS_WARN_IF(aOuter
)) {
847 return NS_ERROR_NO_AGGREGATION
;
850 nsLocalFile
* inst
= new nsLocalFile();
851 nsresult rv
= inst
->QueryInterface(aIID
, aInstancePtr
);
859 //-----------------------------------------------------------------------------
860 // nsLocalFile::nsISupports
861 //-----------------------------------------------------------------------------
863 NS_IMPL_ISUPPORTS(nsLocalFile
, nsIFile
, nsILocalFileWin
)
865 //-----------------------------------------------------------------------------
866 // nsLocalFile <private>
867 //-----------------------------------------------------------------------------
869 nsLocalFile::nsLocalFile(const nsLocalFile
& aOther
)
872 mUseDOSDevicePathSyntax(aOther
.mUseDOSDevicePathSyntax
),
873 mWorkingPath(aOther
.mWorkingPath
) {}
875 nsresult
nsLocalFile::ResolveSymlink() {
876 std::wstring
workingPath(mWorkingPath
.Data());
877 if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(workingPath
)) {
878 return NS_ERROR_FAILURE
;
880 mResolvedPath
.Assign(workingPath
.c_str(), workingPath
.length());
884 // Resolve any shortcuts and stat the resolved path. After a successful return
885 // the path is guaranteed valid and the members of mFileInfo64 can be used.
886 nsresult
nsLocalFile::ResolveAndStat() {
887 // if we aren't dirty then we are already done
892 AUTO_PROFILER_LABEL("nsLocalFile::ResolveAndStat", OTHER
);
893 // we can't resolve/stat anything that isn't a valid NSPR addressable path
894 if (mWorkingPath
.IsEmpty()) {
895 return NS_ERROR_FILE_INVALID_PATH
;
898 // this is usually correct
899 mResolvedPath
.Assign(mWorkingPath
);
901 // Make sure root paths have a trailing slash.
902 nsAutoString
nsprPath(mWorkingPath
);
903 if (mWorkingPath
.Length() == 2 && mWorkingPath
.CharAt(1) == u
':') {
904 nsprPath
.Append('\\');
907 // first we will see if the working path exists. If it doesn't then
908 // there is nothing more that can be done
909 nsresult rv
= GetFileInfo(nsprPath
, &mFileInfo64
);
914 if (mFileInfo64
.type
!= PR_FILE_OTHER
) {
915 mResolveDirty
= false;
920 // OTHER from GetFileInfo currently means a symlink
921 rv
= ResolveSymlink();
922 // Even if it fails we need to have the resolved path equal to working path
923 // for those functions that always use the resolved path.
925 mResolvedPath
.Assign(mWorkingPath
);
929 mResolveDirty
= false;
930 // get the details of the resolved path
931 rv
= GetFileInfo(mResolvedPath
, &mFileInfo64
);
941 * Fills the mResolvedPath member variable with the file or symlink target
942 * if follow symlinks is on. This is a copy of the Resolve parts from
943 * ResolveAndStat. ResolveAndStat is much slower though because of the stat.
945 * @return NS_OK on success.
947 nsresult
nsLocalFile::Resolve() {
948 // if we aren't dirty then we are already done
949 if (!mResolveDirty
) {
953 // we can't resolve/stat anything that isn't a valid NSPR addressable path
954 if (mWorkingPath
.IsEmpty()) {
955 return NS_ERROR_FILE_INVALID_PATH
;
958 // this is usually correct
959 mResolvedPath
.Assign(mWorkingPath
);
961 // TODO: Implement symlink support
963 mResolveDirty
= false;
967 //-----------------------------------------------------------------------------
968 // nsLocalFile::nsIFile
969 //-----------------------------------------------------------------------------
972 nsLocalFile::Clone(nsIFile
** aFile
) {
973 // Just copy-construct ourselves
974 RefPtr
<nsLocalFile
> file
= new nsLocalFile(*this);
981 nsLocalFile::InitWithFile(nsIFile
* aFile
) {
982 if (NS_WARN_IF(!aFile
)) {
983 return NS_ERROR_INVALID_ARG
;
987 aFile
->GetPath(path
);
988 if (path
.IsEmpty()) {
989 return NS_ERROR_INVALID_ARG
;
991 return InitWithPath(path
);
995 nsLocalFile::InitWithPath(const nsAString
& aFilePath
) {
998 nsAString::const_iterator begin
, end
;
999 aFilePath
.BeginReading(begin
);
1000 aFilePath
.EndReading(end
);
1002 // input string must not be empty
1004 return NS_ERROR_FAILURE
;
1007 char16_t firstChar
= *begin
;
1008 char16_t secondChar
= *(++begin
);
1010 // just do a sanity check. if it has any forward slashes, it is not a Native
1011 // path on windows. Also, it must have a colon at after the first char.
1012 if (FindCharInReadable(L
'/', begin
, end
)) {
1013 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1016 if (FilePreferences::IsBlockedUNCPath(aFilePath
)) {
1017 return NS_ERROR_FILE_ACCESS_DENIED
;
1020 if (secondChar
!= L
':' && (secondChar
!= L
'\\' || firstChar
!= L
'\\')) {
1021 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1024 if (secondChar
== L
':') {
1025 // Make sure we have a valid drive, later code assumes the drive letter
1026 // is a single char a-z or A-Z.
1027 if (PathGetDriveNumberW(aFilePath
.Data()) == -1) {
1028 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1032 if (IsSpecialNTFSPath(aFilePath
)) {
1033 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1036 mWorkingPath
= aFilePath
;
1037 // kill any trailing '\'
1038 if (mWorkingPath
.Last() == L
'\\') {
1039 mWorkingPath
.Truncate(mWorkingPath
.Length() - 1);
1042 // Bug 1626514: make sure that we don't end up with multiple prefixes.
1044 // Prepend the "\\?\" prefix if the useDOSDevicePathSyntax is set and the path
1045 // starts with a disk designator and backslash.
1046 if (mUseDOSDevicePathSyntax
&&
1047 FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath
)) {
1048 mWorkingPath
= kDevicePathSpecifier
+ mWorkingPath
;
1054 // Strip a handler command string of its quotes and parameters.
1055 static void CleanupHandlerPath(nsString
& aPath
) {
1056 // Example command strings passed into this routine:
1058 // 1) C:\Program Files\Company\some.exe -foo -bar
1059 // 2) C:\Program Files\Company\some.dll
1060 // 3) C:\Windows\some.dll,-foo -bar
1061 // 4) C:\Windows\some.cpl,-foo -bar
1063 int32_t lastCommaPos
= aPath
.RFindChar(',');
1064 if (lastCommaPos
!= kNotFound
) aPath
.Truncate(lastCommaPos
);
1069 int32_t index
= aPath
.Find(".exe ", true);
1070 if (index
== kNotFound
) index
= aPath
.Find(".dll ", true);
1071 if (index
== kNotFound
) index
= aPath
.Find(".cpl ", true);
1073 if (index
!= kNotFound
) aPath
.Truncate(index
+ 4);
1074 aPath
.Trim(" ", true, true);
1077 // Strip the windows host process bootstrap executable rundll32.exe
1078 // from a handler's command string if it exists.
1079 static void StripRundll32(nsString
& aCommandString
) {
1080 // Example rundll formats:
1081 // C:\Windows\System32\rundll32.exe "path to dll"
1082 // rundll32.exe "path to dll"
1083 // C:\Windows\System32\rundll32.exe "path to dll", var var
1084 // rundll32.exe "path to dll", var var
1086 constexpr auto rundllSegment
= u
"rundll32.exe "_ns
;
1087 constexpr auto rundllSegmentShort
= u
"rundll32 "_ns
;
1090 int32_t strLen
= rundllSegment
.Length();
1091 int32_t index
= aCommandString
.Find(rundllSegment
, true);
1092 if (index
== kNotFound
) {
1093 strLen
= rundllSegmentShort
.Length();
1094 index
= aCommandString
.Find(rundllSegmentShort
, true);
1097 if (index
!= kNotFound
) {
1098 uint32_t rundllSegmentLength
= index
+ strLen
;
1099 aCommandString
.Cut(0, rundllSegmentLength
);
1103 // Returns the fully qualified path to an application handler based on
1104 // a parameterized command string. Note this routine should not be used
1105 // to launch the associated application as it strips parameters and
1106 // rundll.exe from the string. Designed for retrieving display information
1107 // on a particular handler.
1109 bool nsLocalFile::CleanupCmdHandlerPath(nsAString
& aCommandHandler
) {
1110 nsAutoString
handlerCommand(aCommandHandler
);
1112 // Straight command path:
1114 // %SystemRoot%\system32\NOTEPAD.EXE var
1115 // "C:\Program Files\iTunes\iTunes.exe" var var
1116 // C:\Program Files\iTunes\iTunes.exe var var
1118 // Example rundll handlers:
1120 // rundll32.exe "%ProgramFiles%\Win...ery\PhotoViewer.dll", var var
1121 // rundll32.exe "%ProgramFiles%\Windows Photo Gallery\PhotoViewer.dll"
1122 // C:\Windows\System32\rundll32.exe "path to dll", var var
1123 // %SystemRoot%\System32\rundll32.exe "%ProgramFiles%\Win...ery\Photo
1124 // Viewer.dll", var var
1126 // Expand environment variables so we have full path strings.
1127 uint32_t bufLength
=
1128 ::ExpandEnvironmentStringsW(handlerCommand
.get(), nullptr, 0);
1129 if (bufLength
== 0) // Error
1132 auto destination
= mozilla::MakeUniqueFallible
<wchar_t[]>(bufLength
);
1133 if (!destination
) return false;
1134 if (!::ExpandEnvironmentStringsW(handlerCommand
.get(), destination
.get(),
1138 handlerCommand
.Assign(destination
.get());
1140 // Remove quotes around paths
1141 handlerCommand
.StripChars("\"");
1143 // Strip windows host process bootstrap so we can get to the actual
1145 StripRundll32(handlerCommand
);
1147 // Trim any command parameters so that we have a native path we can
1148 // initialize a local file with.
1149 CleanupHandlerPath(handlerCommand
);
1151 aCommandHandler
.Assign(handlerCommand
);
1156 nsLocalFile::InitWithCommandLine(const nsAString
& aCommandLine
) {
1157 nsAutoString
commandLine(aCommandLine
);
1158 if (!CleanupCmdHandlerPath(commandLine
)) {
1159 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1161 return InitWithPath(commandLine
);
1165 nsLocalFile::OpenNSPRFileDesc(int32_t aFlags
, int32_t aMode
,
1166 PRFileDesc
** aResult
) {
1167 nsresult rv
= OpenNSPRFileDescMaybeShareDelete(aFlags
, aMode
, false, aResult
);
1168 if (NS_FAILED(rv
)) {
1176 nsLocalFile::OpenANSIFileDesc(const char* aMode
, FILE** aResult
) {
1177 *aResult
= _wfopen(mWorkingPath
.get(), NS_ConvertASCIItoUTF16(aMode
).get());
1182 return NS_ERROR_FAILURE
;
1185 static nsresult
do_create(nsIFile
* aFile
, const nsString
& aPath
,
1186 uint32_t aAttributes
) {
1189 OpenFile(aPath
, PR_RDONLY
| PR_CREATE_FILE
| PR_APPEND
| PR_EXCL
,
1190 aAttributes
, false, &file
);
1195 if (rv
== NS_ERROR_FILE_ACCESS_DENIED
) {
1196 // need to return already-exists for directories (bug 452217)
1198 if (NS_SUCCEEDED(aFile
->IsDirectory(&isdir
)) && isdir
) {
1199 rv
= NS_ERROR_FILE_ALREADY_EXISTS
;
1205 static nsresult
do_mkdir(nsIFile
*, const nsString
& aPath
, uint32_t) {
1206 if (!::CreateDirectoryW(aPath
.get(), nullptr)) {
1207 return ConvertWinError(GetLastError());
1213 nsLocalFile::Create(uint32_t aType
, uint32_t aAttributes
) {
1214 if (aType
!= NORMAL_FILE_TYPE
&& aType
!= DIRECTORY_TYPE
) {
1215 return NS_ERROR_FILE_UNKNOWN_TYPE
;
1218 auto* createFunc
= (aType
== NORMAL_FILE_TYPE
? do_create
: do_mkdir
);
1220 nsresult rv
= createFunc(this, mWorkingPath
, aAttributes
);
1222 if (NS_SUCCEEDED(rv
) || NS_ERROR_FILE_ALREADY_EXISTS
== rv
) {
1226 // create directories to target
1228 // A given local file can be either one of these forms:
1230 // - normal: X:\some\path\on\this\drive
1233 // - UNC path: \\machine\volume\some\path\on\this\drive
1236 // Skip the first 'X:\' for the first form, and skip the first full
1237 // '\\machine\volume\' segment for the second form.
1239 wchar_t* path
= char16ptr_t(mWorkingPath
.BeginWriting());
1241 if (path
[0] == L
'\\' && path
[1] == L
'\\') {
1242 // dealing with a UNC path here; skip past '\\machine\'
1243 path
= wcschr(path
+ 2, L
'\\');
1245 return NS_ERROR_FILE_INVALID_PATH
;
1250 // search for first slash after the drive (or volume) name
1251 wchar_t* slash
= wcschr(path
, L
'\\');
1253 nsresult directoryCreateError
= NS_OK
;
1255 // skip the first '\\'
1257 slash
= wcschr(slash
, L
'\\');
1262 if (!::CreateDirectoryW(mWorkingPath
.get(), nullptr)) {
1263 rv
= ConvertWinError(GetLastError());
1264 if (NS_ERROR_FILE_NOT_FOUND
== rv
&&
1265 NS_ERROR_FILE_ACCESS_DENIED
== directoryCreateError
) {
1266 // If a previous CreateDirectory failed due to access, return that.
1267 return NS_ERROR_FILE_ACCESS_DENIED
;
1269 // perhaps the base path already exists, or perhaps we don't have
1270 // permissions to create the directory. NOTE: access denied could
1271 // occur on a parent directory even though it exists.
1272 else if (rv
!= NS_ERROR_FILE_ALREADY_EXISTS
&&
1273 rv
!= NS_ERROR_FILE_ACCESS_DENIED
) {
1277 directoryCreateError
= rv
;
1281 slash
= wcschr(slash
, L
'\\');
1285 // If our last CreateDirectory failed due to access, return that.
1286 if (NS_ERROR_FILE_ACCESS_DENIED
== directoryCreateError
) {
1287 return directoryCreateError
;
1290 return createFunc(this, mWorkingPath
, aAttributes
);
1294 nsLocalFile::Append(const nsAString
& aNode
) {
1295 // append this path, multiple components are not permitted
1296 return AppendInternal(PromiseFlatString(aNode
), false);
1300 nsLocalFile::AppendRelativePath(const nsAString
& aNode
) {
1301 // append this path, multiple components are permitted
1302 return AppendInternal(PromiseFlatString(aNode
), true);
1305 nsresult
nsLocalFile::AppendInternal(const nsString
& aNode
,
1306 bool aMultipleComponents
) {
1307 if (aNode
.IsEmpty()) {
1311 // check the relative path for validity
1312 if (aNode
.First() == L
'\\' || // can't start with an '\'
1313 aNode
.Contains(L
'/') || // can't contain /
1314 aNode
.EqualsASCII("..")) { // can't be ..
1315 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1318 if (aMultipleComponents
) {
1319 // can't contain .. as a path component. Ensure that the valid components
1320 // "foo..foo", "..foo", and "foo.." are not falsely detected,
1321 // but the invalid paths "..\", "foo\..", "foo\..\foo",
1322 // "..\foo", etc are.
1323 constexpr auto doubleDot
= u
"\\.."_ns
;
1324 nsAString::const_iterator start
, end
, offset
;
1325 aNode
.BeginReading(start
);
1326 aNode
.EndReading(end
);
1328 while (FindInReadable(doubleDot
, start
, offset
)) {
1329 if (offset
== end
|| *offset
== L
'\\') {
1330 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1336 // catches the remaining cases of prefixes
1337 if (StringBeginsWith(aNode
, u
"..\\"_ns
)) {
1338 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1341 // single components can't contain '\'
1342 else if (aNode
.Contains(L
'\\')) {
1343 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1348 mWorkingPath
.Append('\\');
1349 mWorkingPath
.Append(aNode
);
1351 if (IsSpecialNTFSPath(mWorkingPath
)) {
1352 // Revert changes to mWorkingPath:
1353 mWorkingPath
.SetLength(mWorkingPath
.Length() - aNode
.Length() - 1);
1354 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1360 nsresult
nsLocalFile::OpenNSPRFileDescMaybeShareDelete(int32_t aFlags
,
1363 PRFileDesc
** aResult
) {
1364 return OpenFile(mWorkingPath
, aFlags
, aMode
, aShareDelete
, aResult
);
1367 #define TOUPPER(u) (((u) >= L'a' && (u) <= L'z') ? (u) - (L'a' - L'A') : (u))
1370 nsLocalFile::Normalize() {
1371 // XXX See bug 187957 comment 18 for possible problems with this
1374 if (mWorkingPath
.IsEmpty()) {
1378 nsAutoString
path(mWorkingPath
);
1380 // find the index of the root backslash for the path. Everything before
1381 // this is considered fully normalized and cannot be ascended beyond
1382 // using ".." For a local drive this is the first slash (e.g. "c:\").
1383 // For a UNC path it is the slash following the share name
1384 // (e.g. "\\server\share\").
1385 int32_t rootIdx
= 2; // default to local drive
1386 if (path
.First() == L
'\\') { // if a share then calculate the rootIdx
1387 rootIdx
= path
.FindChar(L
'\\', 2); // skip \\ in front of the server
1388 if (rootIdx
== kNotFound
) {
1389 return NS_OK
; // already normalized
1391 rootIdx
= path
.FindChar(L
'\\', rootIdx
+ 1);
1392 if (rootIdx
== kNotFound
) {
1393 return NS_OK
; // already normalized
1395 } else if (path
.CharAt(rootIdx
) != L
'\\') {
1396 // The path has been specified relative to the current working directory
1397 // for that drive. To normalize it, the current working directory for
1398 // that drive needs to be inserted before the supplied relative path
1399 // which will provide an absolute path (and the rootIdx will still be 2).
1400 WCHAR cwd
[MAX_PATH
];
1402 int drive
= TOUPPER(path
.First()) - 'A' + 1;
1403 /* We need to worry about IPH, for details read bug 419326.
1404 * _getdrives - http://msdn2.microsoft.com/en-us/library/xdhk0xd2.aspx
1405 * uses a bitmask, bit 0 is 'a:'
1406 * _chdrive - http://msdn2.microsoft.com/en-us/library/0d1409hb.aspx
1407 * _getdcwd - http://msdn2.microsoft.com/en-us/library/7t2zk3s4.aspx
1408 * take an int, 1 is 'a:'.
1410 * Because of this, we need to do some math. Subtract 1 to convert from
1411 * _chdrive/_getdcwd format to _getdrives drive numbering.
1412 * Shift left x bits to convert from integer indexing to bitfield indexing.
1413 * And of course, we need to find out if the drive is in the bitmask.
1415 * If we're really unlucky, we can still lose, but only if the user
1416 * manages to eject the drive between our call to _getdrives() and
1417 * our *calls* to _wgetdcwd.
1419 if (!((1 << (drive
- 1)) & _getdrives())) {
1420 return NS_ERROR_FILE_INVALID_PATH
;
1422 if (!_wgetdcwd(drive
, pcwd
, MAX_PATH
)) {
1423 pcwd
= _wgetdcwd(drive
, 0, 0);
1426 return NS_ERROR_OUT_OF_MEMORY
;
1428 nsAutoString
currentDir(pcwd
);
1433 if (currentDir
.Last() == '\\') {
1434 path
.Replace(0, 2, currentDir
);
1436 path
.Replace(0, 2, currentDir
+ u
"\\"_ns
);
1440 MOZ_ASSERT(0 < rootIdx
&& rootIdx
< (int32_t)path
.Length(),
1441 "rootIdx is invalid");
1442 MOZ_ASSERT(path
.CharAt(rootIdx
) == '\\', "rootIdx is invalid");
1444 // if there is nothing following the root path then it is already normalized
1445 if (rootIdx
+ 1 == (int32_t)path
.Length()) {
1450 const char16_t
* pathBuffer
= path
.get(); // simplify access to the buffer
1451 mWorkingPath
.SetCapacity(path
.Length()); // it won't ever grow longer
1452 mWorkingPath
.Assign(pathBuffer
, rootIdx
);
1454 // Normalize the path components. The actions taken are:
1456 // "\\" condense to single backslash
1457 // "." remove from path
1458 // ".." up a directory
1459 // "..." remove from path (any number of dots > 2)
1461 // The last form is something that Windows 95 and 98 supported and
1462 // is a shortcut for changing up multiple directories. Windows XP
1463 // and ilk ignore it in a path, as is done here.
1464 int32_t len
, begin
, end
= rootIdx
;
1465 while (end
< (int32_t)path
.Length()) {
1466 // find the current segment (text between the backslashes) to
1467 // be examined, this will set the following variables:
1468 // begin == index of first char in segment
1469 // end == index 1 char after last char in segment
1470 // len == length of segment
1472 end
= path
.FindChar('\\', begin
);
1473 if (end
== kNotFound
) {
1474 end
= path
.Length();
1478 // ignore double backslashes
1483 // len != 0, and interesting paths always begin with a dot
1484 if (pathBuffer
[begin
] == '.') {
1485 // ignore single dots
1490 // handle multiple dots
1491 if (len
>= 2 && pathBuffer
[begin
+ 1] == L
'.') {
1492 // back up a path component on double dot
1494 int32_t prev
= mWorkingPath
.RFindChar('\\');
1495 if (prev
>= rootIdx
) {
1496 mWorkingPath
.Truncate(prev
);
1501 // length is > 2 and the first two characters are dots.
1502 // if the rest of the string is dots, then ignore it.
1504 for (; idx
>= 2; --idx
) {
1505 if (pathBuffer
[begin
+ idx
] != L
'.') {
1510 // this is true if the loop above didn't break
1511 // and all characters in this segment are dots.
1518 // add the current component to the path, including the preceding backslash
1519 mWorkingPath
.Append(pathBuffer
+ begin
- 1, len
+ 1);
1522 // kill trailing dots and spaces.
1523 int32_t filePathLen
= mWorkingPath
.Length() - 1;
1524 while (filePathLen
> 0 && (mWorkingPath
[filePathLen
] == L
' ' ||
1525 mWorkingPath
[filePathLen
] == L
'.')) {
1526 mWorkingPath
.Truncate(filePathLen
--);
1534 nsLocalFile::GetLeafName(nsAString
& aLeafName
) {
1535 aLeafName
.Truncate();
1537 if (mWorkingPath
.IsEmpty()) {
1538 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1541 int32_t offset
= mWorkingPath
.RFindChar(L
'\\');
1543 // if the working path is just a node without any lashes.
1544 if (offset
== kNotFound
) {
1545 aLeafName
= mWorkingPath
;
1547 aLeafName
= Substring(mWorkingPath
, offset
+ 1);
1554 nsLocalFile::SetLeafName(const nsAString
& aLeafName
) {
1557 if (mWorkingPath
.IsEmpty()) {
1558 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1561 // cannot use nsCString::RFindChar() due to 0x5c problem
1562 int32_t offset
= mWorkingPath
.RFindChar(L
'\\');
1565 newDir
= Substring(mWorkingPath
, 0, offset
+ 1) + aLeafName
;
1567 newDir
= mWorkingPath
+ aLeafName
;
1569 if (IsSpecialNTFSPath(newDir
)) {
1570 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1573 mWorkingPath
.Assign(newDir
);
1579 nsLocalFile::GetPath(nsAString
& aResult
) {
1581 mUseDOSDevicePathSyntax
,
1582 !FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath
));
1583 aResult
= mWorkingPath
;
1588 nsLocalFile::GetCanonicalPath(nsAString
& aResult
) {
1590 aResult
.Assign(mShortWorkingPath
);
1600 nsLocalFile::GetVersionInfoField(const char* aField
, nsAString
& aResult
) {
1601 nsresult rv
= NS_ERROR_FAILURE
;
1603 const WCHAR
* path
= mWorkingPath
.get();
1606 DWORD size
= ::GetFileVersionInfoSizeW(path
, &dummy
);
1611 void* ver
= moz_xcalloc(size
, 1);
1612 if (::GetFileVersionInfoW(path
, 0, size
, ver
)) {
1613 LANGANDCODEPAGE
* translate
= nullptr;
1615 BOOL queryResult
= ::VerQueryValueW(ver
, L
"\\VarFileInfo\\Translation",
1616 (void**)&translate
, &pageCount
);
1617 if (queryResult
&& translate
) {
1618 for (int32_t i
= 0; i
< 2; ++i
) {
1619 wchar_t subBlock
[MAX_PATH
];
1620 _snwprintf(subBlock
, MAX_PATH
, L
"\\StringFileInfo\\%04x%04x\\%s",
1621 (i
== 0 ? translate
[0].wLanguage
: ::GetUserDefaultLangID()),
1622 translate
[0].wCodePage
,
1623 NS_ConvertASCIItoUTF16(nsDependentCString(aField
)).get());
1624 subBlock
[MAX_PATH
- 1] = 0;
1625 LPVOID value
= nullptr;
1627 queryResult
= ::VerQueryValueW(ver
, subBlock
, &value
, &size
);
1628 if (queryResult
&& value
) {
1629 aResult
.Assign(static_cast<char16_t
*>(value
));
1630 if (!aResult
.IsEmpty()) {
1644 nsLocalFile::OpenNSPRFileDescShareDelete(int32_t aFlags
, int32_t aMode
,
1645 PRFileDesc
** aResult
) {
1646 nsresult rv
= OpenNSPRFileDescMaybeShareDelete(aFlags
, aMode
, true, aResult
);
1647 if (NS_FAILED(rv
)) {
1655 * Determines if the drive type for the specified file is rmeote or local.
1657 * @param path The path of the file to check
1658 * @param remote Out parameter, on function success holds true if the specified
1659 * file path is remote, or false if the file path is local.
1660 * @return true on success. The return value implies absolutely nothing about
1661 * wether the file is local or remote.
1663 static bool IsRemoteFilePath(LPCWSTR aPath
, bool& aRemote
) {
1664 // Obtain the parent directory path and make sure it ends with
1665 // a trailing backslash.
1666 WCHAR dirPath
[MAX_PATH
+ 1] = {0};
1667 wcsncpy(dirPath
, aPath
, MAX_PATH
);
1668 if (!PathRemoveFileSpecW(dirPath
)) {
1671 size_t len
= wcslen(dirPath
);
1672 // In case the dirPath holds exaclty MAX_PATH and remains unchanged, we
1673 // recheck the required length here since we need to terminate it with
1675 if (len
>= MAX_PATH
) {
1679 dirPath
[len
] = L
'\\';
1680 dirPath
[len
+ 1] = L
'\0';
1681 UINT driveType
= GetDriveTypeW(dirPath
);
1682 aRemote
= driveType
== DRIVE_REMOTE
;
1686 nsresult
nsLocalFile::CopySingleFile(nsIFile
* aSourceFile
, nsIFile
* aDestParent
,
1687 const nsAString
& aNewName
,
1688 uint32_t aOptions
) {
1689 nsresult rv
= NS_OK
;
1690 nsAutoString filePath
;
1692 bool move
= aOptions
& (Move
| Rename
);
1694 // get the path that we are going to copy to.
1695 // Since windows does not know how to auto
1696 // resolve shortcuts, we must work with the
1698 nsAutoString destPath
;
1699 rv
= aDestParent
->GetTarget(destPath
);
1700 if (NS_FAILED(rv
)) {
1704 destPath
.Append('\\');
1706 if (aNewName
.IsEmpty()) {
1707 nsAutoString aFileName
;
1708 aSourceFile
->GetLeafName(aFileName
);
1709 destPath
.Append(aFileName
);
1711 destPath
.Append(aNewName
);
1714 if (aOptions
& FollowSymlinks
) {
1715 rv
= aSourceFile
->GetTarget(filePath
);
1716 if (filePath
.IsEmpty()) {
1717 rv
= aSourceFile
->GetPath(filePath
);
1720 rv
= aSourceFile
->GetPath(filePath
);
1723 if (NS_FAILED(rv
)) {
1728 nsCOMPtr
<nsILocalFileWin
> srcWinFile
= do_QueryInterface(aSourceFile
);
1729 MOZ_ASSERT(srcWinFile
);
1731 bool srcUseDOSDevicePathSyntax
;
1732 srcWinFile
->GetUseDOSDevicePathSyntax(&srcUseDOSDevicePathSyntax
);
1734 nsCOMPtr
<nsILocalFileWin
> destWinFile
= do_QueryInterface(aDestParent
);
1735 MOZ_ASSERT(destWinFile
);
1737 bool destUseDOSDevicePathSyntax
;
1738 destWinFile
->GetUseDOSDevicePathSyntax(&destUseDOSDevicePathSyntax
);
1740 MOZ_ASSERT(srcUseDOSDevicePathSyntax
== destUseDOSDevicePathSyntax
,
1741 "Copy or Move files with different values for "
1742 "useDOSDevicePathSyntax would fail");
1745 if (FilePreferences::IsBlockedUNCPath(destPath
)) {
1746 return NS_ERROR_FILE_ACCESS_DENIED
;
1751 copyOK
= ::MoveFileExW(filePath
.get(), destPath
.get(),
1752 MOVEFILE_REPLACE_EXISTING
);
1755 // If we either failed to move the file, or this is a copy, try copying:
1756 if (!copyOK
&& (!move
|| GetLastError() == ERROR_NOT_SAME_DEVICE
)) {
1757 // Failed renames here should just return access denied.
1758 if (move
&& (aOptions
& Rename
)) {
1759 return NS_ERROR_FILE_ACCESS_DENIED
;
1762 // Pass the flag COPY_FILE_NO_BUFFERING to CopyFileEx as we may be copying
1763 // to a SMBV2 remote drive. Without this parameter subsequent append mode
1764 // file writes can cause the resultant file to become corrupt. We only need
1765 // to do this if the major version of Windows is > 5(Only Windows Vista and
1766 // above can support SMBV2). With a 7200RPM hard drive: Copying a 1KB file
1767 // with COPY_FILE_NO_BUFFERING takes about 30-60ms. Copying a 1KB file
1768 // without COPY_FILE_NO_BUFFERING takes < 1ms. So we only use
1769 // COPY_FILE_NO_BUFFERING when we have a remote drive.
1770 DWORD dwCopyFlags
= COPY_FILE_ALLOW_DECRYPTED_DESTINATION
;
1771 bool path1Remote
, path2Remote
;
1772 if (!IsRemoteFilePath(filePath
.get(), path1Remote
) ||
1773 !IsRemoteFilePath(destPath
.get(), path2Remote
) || path1Remote
||
1775 dwCopyFlags
|= COPY_FILE_NO_BUFFERING
;
1778 copyOK
= ::CopyFileExW(filePath
.get(), destPath
.get(), nullptr, nullptr,
1779 nullptr, dwCopyFlags
);
1781 if (move
&& copyOK
) {
1782 DeleteFileW(filePath
.get());
1786 if (!copyOK
) { // CopyFileEx and MoveFileEx return zero at failure.
1787 rv
= ConvertWinError(GetLastError());
1788 } else if (move
&& !(aOptions
& SkipNtfsAclReset
)) {
1789 // Set security permissions to inherit from parent.
1790 // Note: propagates to all children: slow for big file trees
1791 PACL pOldDACL
= nullptr;
1792 PSECURITY_DESCRIPTOR pSD
= nullptr;
1793 ::GetNamedSecurityInfoW((LPWSTR
)destPath
.get(), SE_FILE_OBJECT
,
1794 DACL_SECURITY_INFORMATION
, nullptr, nullptr,
1795 &pOldDACL
, nullptr, &pSD
);
1797 ::SetNamedSecurityInfoW(
1798 (LPWSTR
)destPath
.get(), SE_FILE_OBJECT
,
1799 DACL_SECURITY_INFORMATION
| UNPROTECTED_DACL_SECURITY_INFORMATION
,
1800 nullptr, nullptr, pOldDACL
, nullptr);
1802 LocalFree((HLOCAL
)pSD
);
1809 nsresult
nsLocalFile::CopyMove(nsIFile
* aParentDir
, const nsAString
& aNewName
,
1810 uint32_t aOptions
) {
1811 bool move
= aOptions
& (Move
| Rename
);
1812 bool followSymlinks
= aOptions
& FollowSymlinks
;
1813 // If we're not provided with a new parent, we're copying or moving to
1814 // another file in the same directory and can safely skip checking if the
1815 // destination directory exists:
1816 bool targetInSameDirectory
= !aParentDir
;
1818 nsCOMPtr
<nsIFile
> newParentDir
= aParentDir
;
1819 // check to see if this exists, otherwise return an error.
1820 // we will check this by resolving. If the user wants us
1821 // to follow links, then we are talking about the target,
1822 // hence we can use the |FollowSymlinks| option.
1823 nsresult rv
= ResolveAndStat();
1824 if (NS_FAILED(rv
)) {
1828 if (!newParentDir
) {
1829 // no parent was specified. We must rename.
1830 if (aNewName
.IsEmpty()) {
1831 return NS_ERROR_INVALID_ARG
;
1834 rv
= GetParent(getter_AddRefs(newParentDir
));
1835 if (NS_FAILED(rv
)) {
1840 if (!newParentDir
) {
1841 return NS_ERROR_FILE_DESTINATION_NOT_DIR
;
1844 if (!targetInSameDirectory
) {
1845 // make sure it exists and is a directory. Create it if not there.
1846 bool exists
= false;
1847 rv
= newParentDir
->Exists(&exists
);
1848 if (NS_FAILED(rv
)) {
1853 rv
= newParentDir
->Create(DIRECTORY_TYPE
,
1854 0644); // TODO, what permissions should we use
1855 if (NS_FAILED(rv
)) {
1860 rv
= newParentDir
->IsDirectory(&isDir
);
1861 if (NS_FAILED(rv
)) {
1866 if (followSymlinks
) {
1867 bool isLink
= false;
1868 rv
= newParentDir
->IsSymlink(&isLink
);
1869 if (NS_FAILED(rv
)) {
1874 nsAutoString target
;
1875 rv
= newParentDir
->GetTarget(target
);
1876 if (NS_FAILED(rv
)) {
1880 nsCOMPtr
<nsIFile
> realDest
= new nsLocalFile();
1881 rv
= realDest
->InitWithPath(target
);
1882 if (NS_FAILED(rv
)) {
1886 return CopyMove(realDest
, aNewName
, aOptions
);
1889 return NS_ERROR_FILE_DESTINATION_NOT_DIR
;
1895 // Try different ways to move/copy files/directories
1899 rv
= IsDirectory(&isDir
);
1900 if (NS_FAILED(rv
)) {
1904 bool isSymlink
= false;
1905 rv
= IsSymlink(&isSymlink
);
1906 if (NS_FAILED(rv
)) {
1910 // Try to move the file or directory, or try to copy a single file (or
1911 // non-followed symlink)
1912 if (move
|| !isDir
|| (isSymlink
&& !followSymlinks
)) {
1913 // Copy/Move single file, or move a directory
1915 aOptions
|= SkipNtfsAclReset
;
1917 rv
= CopySingleFile(this, newParentDir
, aNewName
, aOptions
);
1918 done
= NS_SUCCEEDED(rv
);
1919 // If we are moving a directory and that fails, fallback on directory
1920 // enumeration. See bug 231300 for details.
1921 if (!done
&& !(move
&& isDir
)) {
1926 // Not able to copy or move directly, so enumerate it
1928 // create a new target destination in the new parentDir;
1929 nsCOMPtr
<nsIFile
> target
;
1930 rv
= newParentDir
->Clone(getter_AddRefs(target
));
1931 if (NS_FAILED(rv
)) {
1935 nsAutoString allocatedNewName
;
1936 if (aNewName
.IsEmpty()) {
1937 bool isLink
= false;
1938 rv
= IsSymlink(&isLink
);
1939 if (NS_FAILED(rv
)) {
1945 rv
= GetTarget(temp
);
1946 if (NS_FAILED(rv
)) {
1950 int32_t offset
= temp
.RFindChar(L
'\\');
1951 if (offset
== kNotFound
) {
1952 allocatedNewName
= temp
;
1954 allocatedNewName
= Substring(temp
, offset
+ 1);
1957 GetLeafName(allocatedNewName
); // this should be the leaf name of the
1960 allocatedNewName
= aNewName
;
1963 rv
= target
->Append(allocatedNewName
);
1964 if (NS_FAILED(rv
)) {
1968 allocatedNewName
.Truncate();
1970 bool exists
= false;
1971 // check if the destination directory already exists
1972 rv
= target
->Exists(&exists
);
1973 if (NS_FAILED(rv
)) {
1978 // if the destination directory cannot be created, return an error
1979 rv
= target
->Create(DIRECTORY_TYPE
,
1980 0644); // TODO, what permissions should we use
1981 if (NS_FAILED(rv
)) {
1985 // check if the destination directory is writable and empty
1986 bool isWritable
= false;
1987 rv
= target
->IsWritable(&isWritable
);
1988 if (NS_FAILED(rv
)) {
1993 return NS_ERROR_FILE_ACCESS_DENIED
;
1996 nsCOMPtr
<nsIDirectoryEnumerator
> targetIterator
;
1997 rv
= target
->GetDirectoryEntries(getter_AddRefs(targetIterator
));
1998 if (NS_FAILED(rv
)) {
2003 targetIterator
->HasMoreElements(&more
);
2004 // return error if target directory is not empty
2006 return NS_ERROR_FILE_DIR_NOT_EMPTY
;
2010 RefPtr
<nsDirEnumerator
> dirEnum
= new nsDirEnumerator();
2012 rv
= dirEnum
->Init(this);
2013 if (NS_FAILED(rv
)) {
2014 NS_WARNING("dirEnum initialization failed");
2018 nsCOMPtr
<nsIFile
> file
;
2019 while (NS_SUCCEEDED(dirEnum
->GetNextFile(getter_AddRefs(file
))) && file
) {
2021 rv
= file
->IsDirectory(&isDir
);
2022 if (NS_FAILED(rv
)) {
2026 bool isLink
= false;
2027 rv
= file
->IsSymlink(&isLink
);
2028 if (NS_FAILED(rv
)) {
2033 if (followSymlinks
) {
2034 return NS_ERROR_FAILURE
;
2037 rv
= file
->MoveTo(target
, u
""_ns
);
2038 if (NS_FAILED(rv
)) {
2042 if (followSymlinks
) {
2043 rv
= file
->CopyToFollowingLinks(target
, u
""_ns
);
2045 rv
= file
->CopyTo(target
, u
""_ns
);
2047 if (NS_FAILED(rv
)) {
2052 // we've finished moving all the children of this directory
2053 // in the new directory. so now delete the directory
2054 // note, we don't need to do a recursive delete.
2055 // MoveTo() is recursive. At this point,
2056 // we've already moved the children of the current folder
2057 // to the new location. nothing should be left in the folder.
2059 rv
= Remove(false /* recursive */);
2060 if (NS_FAILED(rv
)) {
2066 // If we moved, we want to adjust this.
2070 nsAutoString newParentPath
;
2071 newParentDir
->GetPath(newParentPath
);
2073 if (newParentPath
.IsEmpty()) {
2074 return NS_ERROR_FAILURE
;
2077 if (aNewName
.IsEmpty()) {
2078 nsAutoString aFileName
;
2079 GetLeafName(aFileName
);
2081 InitWithPath(newParentPath
);
2084 InitWithPath(newParentPath
);
2093 nsLocalFile::CopyTo(nsIFile
* aNewParentDir
, const nsAString
& aNewName
) {
2094 return CopyMove(aNewParentDir
, aNewName
, 0);
2098 nsLocalFile::CopyToFollowingLinks(nsIFile
* aNewParentDir
,
2099 const nsAString
& aNewName
) {
2100 return CopyMove(aNewParentDir
, aNewName
, FollowSymlinks
);
2104 nsLocalFile::MoveTo(nsIFile
* aNewParentDir
, const nsAString
& aNewName
) {
2105 return CopyMove(aNewParentDir
, aNewName
, Move
);
2109 nsLocalFile::MoveToFollowingLinks(nsIFile
* aNewParentDir
,
2110 const nsAString
& aNewName
) {
2111 return CopyMove(aNewParentDir
, aNewName
, Move
| FollowSymlinks
);
2115 nsLocalFile::RenameTo(nsIFile
* aNewParentDir
, const nsAString
& aNewName
) {
2116 // If we're not provided with a new parent, we're renaming inside one and
2117 // the same directory and can safely skip checking if the destination
2118 // directory exists:
2119 bool targetInSameDirectory
= !aNewParentDir
;
2121 nsCOMPtr
<nsIFile
> targetParentDir
= aNewParentDir
;
2122 // check to see if this exists, otherwise return an error.
2123 // we will check this by resolving. If the user wants us
2124 // to follow links, then we are talking about the target,
2125 // hence we can use the |followSymlinks| parameter.
2126 nsresult rv
= ResolveAndStat();
2127 if (NS_FAILED(rv
)) {
2131 if (!targetParentDir
) {
2132 // no parent was specified. We must rename.
2133 if (aNewName
.IsEmpty()) {
2134 return NS_ERROR_INVALID_ARG
;
2136 rv
= GetParent(getter_AddRefs(targetParentDir
));
2137 if (NS_FAILED(rv
)) {
2142 if (!targetParentDir
) {
2143 return NS_ERROR_FILE_DESTINATION_NOT_DIR
;
2146 if (!targetInSameDirectory
) {
2147 // make sure it exists and is a directory. Create it if not there.
2148 bool exists
= false;
2149 rv
= targetParentDir
->Exists(&exists
);
2150 if (NS_FAILED(rv
)) {
2155 rv
= targetParentDir
->Create(DIRECTORY_TYPE
, 0644);
2156 if (NS_FAILED(rv
)) {
2161 rv
= targetParentDir
->IsDirectory(&isDir
);
2162 if (NS_FAILED(rv
)) {
2166 return NS_ERROR_FILE_DESTINATION_NOT_DIR
;
2171 uint32_t options
= Rename
;
2172 if (!aNewParentDir
) {
2173 options
|= SkipNtfsAclReset
;
2175 // Move single file, or move a directory
2176 return CopySingleFile(this, targetParentDir
, aNewName
, options
);
2180 nsLocalFile::RenameToNative(nsIFile
* aNewParentDir
,
2181 const nsACString
& aNewName
) {
2183 nsresult rv
= NS_CopyNativeToUnicode(aNewName
, tmp
);
2184 if (NS_SUCCEEDED(rv
)) {
2185 return RenameTo(aNewParentDir
, tmp
);
2192 nsLocalFile::Load(PRLibrary
** aResult
) {
2193 // Check we are correctly initialized.
2194 CHECK_mWorkingPath();
2195 if (NS_WARN_IF(!aResult
)) {
2196 return NS_ERROR_INVALID_ARG
;
2199 #ifdef NS_BUILD_REFCNT_LOGGING
2200 nsTraceRefcnt::SetActivityIsLegal(false);
2204 libSpec
.value
.pathname_u
= mWorkingPath
.get();
2205 libSpec
.type
= PR_LibSpec_PathnameU
;
2206 *aResult
= PR_LoadLibraryWithFlags(libSpec
, 0);
2208 #ifdef NS_BUILD_REFCNT_LOGGING
2209 nsTraceRefcnt::SetActivityIsLegal(true);
2215 return NS_ERROR_NULL_POINTER
;
2219 nsLocalFile::Remove(bool aRecursive
) {
2222 // if the working path points to a shortcut, then we will only
2223 // delete the shortcut itself. even if the shortcut points to
2224 // a directory, we will not recurse into that directory or
2225 // delete that directory itself. likewise, if the shortcut
2226 // points to a normal file, we will not delete the real file.
2227 // this is done to be consistent with the other platforms that
2228 // behave this way. we do this even if the followLinks attribute
2229 // is set to true. this helps protect against misuse that could
2230 // lead to security bugs (e.g., bug 210588).
2232 // Since shortcut files are no longer permitted to be used as unix-like
2233 // symlinks interspersed in the path (e.g. "c:/file.lnk/foo/bar.txt")
2234 // this processing is a lot simpler. Even if the shortcut file is
2235 // pointing to a directory, only the mWorkingPath value is used and so
2236 // only the shortcut file will be deleted.
2238 // Check we are correctly initialized.
2239 CHECK_mWorkingPath();
2241 nsresult rv
= NS_OK
;
2243 bool isLink
= false;
2244 rv
= IsSymlink(&isLink
);
2245 if (NS_FAILED(rv
)) {
2249 // only check to see if we have a directory if it isn't a link
2252 rv
= IsDirectory(&isDir
);
2253 if (NS_FAILED(rv
)) {
2260 RefPtr
<nsDirEnumerator
> dirEnum
= new nsDirEnumerator();
2262 rv
= dirEnum
->Init(this);
2263 if (NS_FAILED(rv
)) {
2268 while (NS_SUCCEEDED(dirEnum
->HasMoreElements(&more
)) && more
) {
2269 nsCOMPtr
<nsISupports
> item
;
2270 dirEnum
->GetNext(getter_AddRefs(item
));
2271 nsCOMPtr
<nsIFile
> file
= do_QueryInterface(item
);
2273 file
->Remove(aRecursive
);
2277 if (RemoveDirectoryW(mWorkingPath
.get()) == 0) {
2278 return ConvertWinError(GetLastError());
2281 if (DeleteFileW(mWorkingPath
.get()) == 0) {
2282 return ConvertWinError(GetLastError());
2291 nsLocalFile::GetLastModifiedTime(PRTime
* aLastModifiedTime
) {
2292 // Check we are correctly initialized.
2293 CHECK_mWorkingPath();
2295 if (NS_WARN_IF(!aLastModifiedTime
)) {
2296 return NS_ERROR_INVALID_ARG
;
2299 // get the modified time of the target as determined by mFollowSymlinks
2300 // If true, then this will be for the target of the shortcut file,
2301 // otherwise it will be for the shortcut file itself (i.e. the same
2302 // results as GetLastModifiedTimeOfLink)
2304 nsresult rv
= ResolveAndStat();
2305 if (NS_FAILED(rv
)) {
2309 // microseconds -> milliseconds
2310 *aLastModifiedTime
= mFileInfo64
.modifyTime
/ PR_USEC_PER_MSEC
;
2315 nsLocalFile::GetLastModifiedTimeOfLink(PRTime
* aLastModifiedTime
) {
2316 // Check we are correctly initialized.
2317 CHECK_mWorkingPath();
2319 if (NS_WARN_IF(!aLastModifiedTime
)) {
2320 return NS_ERROR_INVALID_ARG
;
2323 // The caller is assumed to have already called IsSymlink
2324 // and to have found that this file is a link.
2327 nsresult rv
= GetFileInfo(mWorkingPath
, &info
);
2328 if (NS_FAILED(rv
)) {
2332 // microseconds -> milliseconds
2333 *aLastModifiedTime
= info
.modifyTime
/ PR_USEC_PER_MSEC
;
2338 nsLocalFile::SetLastModifiedTime(PRTime aLastModifiedTime
) {
2339 // Check we are correctly initialized.
2340 CHECK_mWorkingPath();
2342 nsresult rv
= SetModDate(aLastModifiedTime
, mWorkingPath
.get());
2343 if (NS_SUCCEEDED(rv
)) {
2351 nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModifiedTime
) {
2352 return SetLastModifiedTime(aLastModifiedTime
);
2356 nsLocalFile::GetCreationTime(PRTime
* aCreationTime
) {
2357 CHECK_mWorkingPath();
2359 if (NS_WARN_IF(!aCreationTime
)) {
2360 return NS_ERROR_INVALID_ARG
;
2363 nsresult rv
= ResolveAndStat();
2364 NS_ENSURE_SUCCESS(rv
, rv
);
2366 *aCreationTime
= mFileInfo64
.creationTime
/ PR_USEC_PER_MSEC
;
2372 nsLocalFile::GetCreationTimeOfLink(PRTime
* aCreationTime
) {
2373 CHECK_mWorkingPath();
2375 if (NS_WARN_IF(!aCreationTime
)) {
2376 return NS_ERROR_INVALID_ARG
;
2380 nsresult rv
= GetFileInfo(mWorkingPath
, &info
);
2381 NS_ENSURE_SUCCESS(rv
, rv
);
2383 *aCreationTime
= info
.creationTime
/ PR_USEC_PER_MSEC
;
2388 nsresult
nsLocalFile::SetModDate(PRTime aLastModifiedTime
,
2389 const wchar_t* aFilePath
) {
2390 // The FILE_FLAG_BACKUP_SEMANTICS is required in order to change the
2391 // modification time for directories.
2392 HANDLE file
= ::CreateFileW(aFilePath
, // pointer to name of the file
2393 GENERIC_WRITE
, // access (write) mode
2395 nullptr, // pointer to security attributes
2396 OPEN_EXISTING
, // how to create
2397 FILE_FLAG_BACKUP_SEMANTICS
, // file attributes
2400 if (file
== INVALID_HANDLE_VALUE
) {
2401 return ConvertWinError(GetLastError());
2406 PRExplodedTime pret
;
2408 // PR_ExplodeTime expects usecs...
2409 PR_ExplodeTime(aLastModifiedTime
* PR_USEC_PER_MSEC
, PR_GMTParameters
, &pret
);
2410 st
.wYear
= pret
.tm_year
;
2412 pret
.tm_month
+ 1; // Convert start offset -- Win32: Jan=1; NSPR: Jan=0
2413 st
.wDayOfWeek
= pret
.tm_wday
;
2414 st
.wDay
= pret
.tm_mday
;
2415 st
.wHour
= pret
.tm_hour
;
2416 st
.wMinute
= pret
.tm_min
;
2417 st
.wSecond
= pret
.tm_sec
;
2418 st
.wMilliseconds
= pret
.tm_usec
/ 1000;
2420 nsresult rv
= NS_OK
;
2421 // if at least one of these fails...
2422 if (!(SystemTimeToFileTime(&st
, &ft
) != 0 &&
2423 SetFileTime(file
, nullptr, &ft
, &ft
) != 0)) {
2424 rv
= ConvertWinError(GetLastError());
2432 nsLocalFile::GetPermissions(uint32_t* aPermissions
) {
2433 if (NS_WARN_IF(!aPermissions
)) {
2434 return NS_ERROR_INVALID_ARG
;
2437 // get the permissions of the target as determined by mFollowSymlinks
2438 // If true, then this will be for the target of the shortcut file,
2439 // otherwise it will be for the shortcut file itself (i.e. the same
2440 // results as GetPermissionsOfLink)
2441 nsresult rv
= ResolveAndStat();
2442 if (NS_FAILED(rv
)) {
2446 bool isWritable
= false;
2447 rv
= IsWritable(&isWritable
);
2448 if (NS_FAILED(rv
)) {
2452 bool isExecutable
= false;
2453 rv
= IsExecutable(&isExecutable
);
2454 if (NS_FAILED(rv
)) {
2458 *aPermissions
= PR_IRUSR
| PR_IRGRP
| PR_IROTH
; // all read
2460 *aPermissions
|= PR_IWUSR
| PR_IWGRP
| PR_IWOTH
; // all write
2463 *aPermissions
|= PR_IXUSR
| PR_IXGRP
| PR_IXOTH
; // all execute
2470 nsLocalFile::GetPermissionsOfLink(uint32_t* aPermissions
) {
2471 // Check we are correctly initialized.
2472 CHECK_mWorkingPath();
2474 if (NS_WARN_IF(!aPermissions
)) {
2475 return NS_ERROR_INVALID_ARG
;
2478 // The caller is assumed to have already called IsSymlink
2479 // and to have found that this file is a link. It is not
2480 // possible for a link file to be executable.
2482 DWORD word
= ::GetFileAttributesW(mWorkingPath
.get());
2483 if (word
== INVALID_FILE_ATTRIBUTES
) {
2484 return NS_ERROR_FILE_INVALID_PATH
;
2487 bool isWritable
= !(word
& FILE_ATTRIBUTE_READONLY
);
2488 *aPermissions
= PR_IRUSR
| PR_IRGRP
| PR_IROTH
; // all read
2490 *aPermissions
|= PR_IWUSR
| PR_IWGRP
| PR_IWOTH
; // all write
2497 nsLocalFile::SetPermissions(uint32_t aPermissions
) {
2498 // Check we are correctly initialized.
2499 CHECK_mWorkingPath();
2501 // set the permissions of the target as determined by mFollowSymlinks
2502 // If true, then this will be for the target of the shortcut file,
2503 // otherwise it will be for the shortcut file itself (i.e. the same
2504 // results as SetPermissionsOfLink)
2505 nsresult rv
= Resolve();
2506 if (NS_FAILED(rv
)) {
2510 // windows only knows about the following permissions
2512 if (aPermissions
& (PR_IRUSR
| PR_IRGRP
| PR_IROTH
)) { // any read
2515 if (aPermissions
& (PR_IWUSR
| PR_IWGRP
| PR_IWOTH
)) { // any write
2519 if (_wchmod(mResolvedPath
.get(), mode
) == -1) {
2520 return NS_ERROR_FAILURE
;
2527 nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions
) {
2528 // The caller is assumed to have already called IsSymlink
2529 // and to have found that this file is a link.
2531 // windows only knows about the following permissions
2533 if (aPermissions
& (PR_IRUSR
| PR_IRGRP
| PR_IROTH
)) { // any read
2536 if (aPermissions
& (PR_IWUSR
| PR_IWGRP
| PR_IWOTH
)) { // any write
2540 if (_wchmod(mWorkingPath
.get(), mode
) == -1) {
2541 return NS_ERROR_FAILURE
;
2548 nsLocalFile::GetFileSize(int64_t* aFileSize
) {
2549 if (NS_WARN_IF(!aFileSize
)) {
2550 return NS_ERROR_INVALID_ARG
;
2553 nsresult rv
= ResolveAndStat();
2554 if (NS_FAILED(rv
)) {
2558 *aFileSize
= mFileInfo64
.size
;
2563 nsLocalFile::GetFileSizeOfLink(int64_t* aFileSize
) {
2564 // Check we are correctly initialized.
2565 CHECK_mWorkingPath();
2567 if (NS_WARN_IF(!aFileSize
)) {
2568 return NS_ERROR_INVALID_ARG
;
2571 // The caller is assumed to have already called IsSymlink
2572 // and to have found that this file is a link.
2575 if (NS_FAILED(GetFileInfo(mWorkingPath
, &info
))) {
2576 return NS_ERROR_FILE_INVALID_PATH
;
2579 *aFileSize
= info
.size
;
2584 nsLocalFile::SetFileSize(int64_t aFileSize
) {
2585 // Check we are correctly initialized.
2586 CHECK_mWorkingPath();
2589 ::CreateFileW(mWorkingPath
.get(), // pointer to name of the file
2590 GENERIC_WRITE
, // access (write) mode
2591 FILE_SHARE_READ
, // share mode
2592 nullptr, // pointer to security attributes
2593 OPEN_EXISTING
, // how to create
2594 FILE_ATTRIBUTE_NORMAL
, // file attributes
2596 if (hFile
== INVALID_HANDLE_VALUE
) {
2597 return ConvertWinError(GetLastError());
2600 // seek the file pointer to the new, desired end of file
2601 // and then truncate the file at that position
2602 nsresult rv
= NS_ERROR_FAILURE
;
2603 aFileSize
= MyFileSeek64(hFile
, aFileSize
, FILE_BEGIN
);
2604 if (aFileSize
!= -1 && SetEndOfFile(hFile
)) {
2614 nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable
) {
2615 // Check we are correctly initialized.
2616 CHECK_mWorkingPath();
2618 if (NS_WARN_IF(!aDiskSpaceAvailable
)) {
2619 return NS_ERROR_INVALID_ARG
;
2624 if (mFileInfo64
.type
== PR_FILE_FILE
) {
2625 // Since GetDiskFreeSpaceExW works only on directories, use the parent.
2626 nsCOMPtr
<nsIFile
> parent
;
2627 if (NS_SUCCEEDED(GetParent(getter_AddRefs(parent
))) && parent
) {
2628 return parent
->GetDiskSpaceAvailable(aDiskSpaceAvailable
);
2632 ULARGE_INTEGER liFreeBytesAvailableToCaller
, liTotalNumberOfBytes
;
2633 if (::GetDiskFreeSpaceExW(mResolvedPath
.get(), &liFreeBytesAvailableToCaller
,
2634 &liTotalNumberOfBytes
, nullptr)) {
2635 *aDiskSpaceAvailable
= liFreeBytesAvailableToCaller
.QuadPart
;
2638 *aDiskSpaceAvailable
= 0;
2643 nsLocalFile::GetParent(nsIFile
** aParent
) {
2644 // Check we are correctly initialized.
2645 CHECK_mWorkingPath();
2647 if (NS_WARN_IF(!aParent
)) {
2648 return NS_ERROR_INVALID_ARG
;
2651 // A two-character path must be a drive such as C:, so it has no parent
2652 if (mWorkingPath
.Length() == 2) {
2657 int32_t offset
= mWorkingPath
.RFindChar(char16_t('\\'));
2658 // adding this offset check that was removed in bug 241708 fixes mail
2659 // directories that aren't relative to/underneath the profile dir.
2660 // e.g., on a different drive. Before you remove them, please make
2661 // sure local mail directories that aren't underneath the profile dir work.
2662 if (offset
== kNotFound
) {
2663 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
2666 // A path of the form \\NAME is a top-level path and has no parent
2667 if (offset
== 1 && mWorkingPath
[0] == L
'\\') {
2672 nsAutoString
parentPath(mWorkingPath
);
2675 parentPath
.Truncate(offset
);
2677 parentPath
.AssignLiteral("\\\\.");
2680 nsCOMPtr
<nsIFile
> localFile
;
2681 nsresult rv
= NewLocalFile(parentPath
, mUseDOSDevicePathSyntax
,
2682 getter_AddRefs(localFile
));
2683 if (NS_FAILED(rv
)) {
2687 localFile
.forget(aParent
);
2692 nsLocalFile::Exists(bool* aResult
) {
2693 // Check we are correctly initialized.
2694 CHECK_mWorkingPath();
2696 if (NS_WARN_IF(!aResult
)) {
2697 return NS_ERROR_INVALID_ARG
;
2702 nsresult rv
= ResolveAndStat();
2703 *aResult
= NS_SUCCEEDED(rv
) || rv
== NS_ERROR_FILE_IS_LOCKED
;
2709 nsLocalFile::IsWritable(bool* aIsWritable
) {
2710 // Check we are correctly initialized.
2711 CHECK_mWorkingPath();
2713 // The read-only attribute on a FAT directory only means that it can't
2714 // be deleted. It is still possible to modify the contents of the directory.
2715 nsresult rv
= IsDirectory(aIsWritable
);
2716 if (rv
== NS_ERROR_FILE_ACCESS_DENIED
) {
2717 *aIsWritable
= true;
2719 } else if (rv
== NS_ERROR_FILE_IS_LOCKED
) {
2720 // If the file is normally allowed write access
2721 // we should still return that the file is writable.
2722 } else if (NS_FAILED(rv
)) {
2729 // writable if the file doesn't have the readonly attribute
2730 rv
= HasFileAttribute(FILE_ATTRIBUTE_READONLY
, aIsWritable
);
2731 if (rv
== NS_ERROR_FILE_ACCESS_DENIED
) {
2732 *aIsWritable
= false;
2734 } else if (rv
== NS_ERROR_FILE_IS_LOCKED
) {
2735 // If the file is normally allowed write access
2736 // we should still return that the file is writable.
2737 } else if (NS_FAILED(rv
)) {
2740 *aIsWritable
= !*aIsWritable
;
2742 // If the read only attribute is not set, check to make sure
2743 // we can open the file with write access.
2746 rv
= OpenFile(mResolvedPath
, PR_WRONLY
, 0, false, &file
);
2747 if (NS_SUCCEEDED(rv
)) {
2749 } else if (rv
== NS_ERROR_FILE_ACCESS_DENIED
) {
2750 *aIsWritable
= false;
2751 } else if (rv
== NS_ERROR_FILE_IS_LOCKED
) {
2752 // If it is locked and read only we would have
2753 // gotten access denied
2754 *aIsWritable
= true;
2763 nsLocalFile::IsReadable(bool* aResult
) {
2764 // Check we are correctly initialized.
2765 CHECK_mWorkingPath();
2767 if (NS_WARN_IF(!aResult
)) {
2768 return NS_ERROR_INVALID_ARG
;
2772 nsresult rv
= ResolveAndStat();
2773 if (NS_FAILED(rv
)) {
2781 nsresult
nsLocalFile::LookupExtensionIn(const char* const* aExtensionsArray
,
2782 size_t aArrayLength
, bool* aResult
) {
2783 // Check we are correctly initialized.
2784 CHECK_mWorkingPath();
2786 if (NS_WARN_IF(!aResult
)) {
2787 return NS_ERROR_INVALID_ARG
;
2793 // only files can be executables
2795 rv
= IsFile(&isFile
);
2796 if (NS_FAILED(rv
)) {
2803 // TODO: shouldn't we be checking mFollowSymlinks here?
2804 bool symLink
= false;
2805 rv
= IsSymlink(&symLink
);
2806 if (NS_FAILED(rv
)) {
2817 // kill trailing dots and spaces.
2818 int32_t filePathLen
= path
.Length() - 1;
2819 while (filePathLen
> 0 &&
2820 (path
[filePathLen
] == L
' ' || path
[filePathLen
] == L
'.')) {
2821 path
.Truncate(filePathLen
--);
2825 int32_t dotIdx
= path
.RFindChar(char16_t('.'));
2826 if (dotIdx
!= kNotFound
) {
2827 // Convert extension to lower case.
2828 char16_t
* p
= path
.BeginWriting();
2829 for (p
+= dotIdx
+ 1; *p
; ++p
) {
2830 *p
+= (*p
>= L
'A' && *p
<= L
'Z') ? 'a' - 'A' : 0;
2833 nsDependentSubstring ext
= Substring(path
, dotIdx
);
2834 for (size_t i
= 0; i
< aArrayLength
; ++i
) {
2835 if (ext
.EqualsASCII(aExtensionsArray
[i
])) {
2836 // Found a match. Set result and quit.
2847 nsLocalFile::IsExecutable(bool* aResult
) {
2848 return LookupExtensionIn(sExecutableExts
, ArrayLength(sExecutableExts
),
2853 nsLocalFile::IsDirectory(bool* aResult
) {
2854 return HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY
, aResult
);
2858 nsLocalFile::IsFile(bool* aResult
) {
2859 nsresult rv
= HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY
, aResult
);
2860 if (NS_SUCCEEDED(rv
)) {
2861 *aResult
= !*aResult
;
2867 nsLocalFile::IsHidden(bool* aResult
) {
2868 return HasFileAttribute(FILE_ATTRIBUTE_HIDDEN
, aResult
);
2871 nsresult
nsLocalFile::HasFileAttribute(DWORD aFileAttrib
, bool* aResult
) {
2872 if (NS_WARN_IF(!aResult
)) {
2873 return NS_ERROR_INVALID_ARG
;
2876 nsresult rv
= Resolve();
2877 if (NS_FAILED(rv
)) {
2881 DWORD attributes
= GetFileAttributesW(mResolvedPath
.get());
2882 if (INVALID_FILE_ATTRIBUTES
== attributes
) {
2883 return ConvertWinError(GetLastError());
2886 *aResult
= ((attributes
& aFileAttrib
) != 0);
2891 nsLocalFile::IsSymlink(bool* aResult
) {
2892 // Check we are correctly initialized.
2893 CHECK_mWorkingPath();
2895 if (NS_WARN_IF(!aResult
)) {
2896 return NS_ERROR_INVALID_ARG
;
2899 // TODO: Implement symlink support
2905 nsLocalFile::IsSpecial(bool* aResult
) {
2906 return HasFileAttribute(FILE_ATTRIBUTE_SYSTEM
, aResult
);
2910 nsLocalFile::Equals(nsIFile
* aInFile
, bool* aResult
) {
2911 if (NS_WARN_IF(!aInFile
)) {
2912 return NS_ERROR_INVALID_ARG
;
2914 if (NS_WARN_IF(!aResult
)) {
2915 return NS_ERROR_INVALID_ARG
;
2920 nsCOMPtr
<nsILocalFileWin
> lf(do_QueryInterface(aInFile
));
2926 nsAutoString inFilePath
;
2927 lf
->GetCanonicalPath(inFilePath
);
2929 bool inUseDOSDevicePathSyntax
;
2930 lf
->GetUseDOSDevicePathSyntax(&inUseDOSDevicePathSyntax
);
2932 // Remove the prefix for both inFilePath and mShortWorkingPath if the
2933 // useDOSDevicePathSyntax from them are not the same.
2934 // This is added because of Omnijar. It compare files from different moduals
2936 nsAutoString shortWorkingPath
;
2937 if (inUseDOSDevicePathSyntax
== mUseDOSDevicePathSyntax
) {
2938 shortWorkingPath
= mShortWorkingPath
;
2939 } else if (inUseDOSDevicePathSyntax
&&
2940 StringBeginsWith(inFilePath
, kDevicePathSpecifier
)) {
2941 MOZ_ASSERT(!StringBeginsWith(mShortWorkingPath
, kDevicePathSpecifier
));
2943 shortWorkingPath
= mShortWorkingPath
;
2944 inFilePath
= Substring(inFilePath
, kDevicePathSpecifier
.Length());
2945 } else if (mUseDOSDevicePathSyntax
&&
2946 StringBeginsWith(mShortWorkingPath
, kDevicePathSpecifier
)) {
2947 MOZ_ASSERT(!StringBeginsWith(inFilePath
, kDevicePathSpecifier
));
2950 Substring(mShortWorkingPath
, kDevicePathSpecifier
.Length());
2954 *aResult
= _wcsicmp(shortWorkingPath
.get(), inFilePath
.get()) == 0;
2960 nsLocalFile::Contains(nsIFile
* aInFile
, bool* aResult
) {
2961 // Check we are correctly initialized.
2962 CHECK_mWorkingPath();
2966 nsAutoString myFilePath
;
2967 if (NS_FAILED(GetTarget(myFilePath
))) {
2968 GetPath(myFilePath
);
2971 uint32_t myFilePathLen
= myFilePath
.Length();
2973 nsAutoString inFilePath
;
2974 if (NS_FAILED(aInFile
->GetTarget(inFilePath
))) {
2975 aInFile
->GetPath(inFilePath
);
2978 // make sure that the |aInFile|'s path has a trailing separator.
2979 if (inFilePath
.Length() >= myFilePathLen
&&
2980 inFilePath
[myFilePathLen
] == L
'\\') {
2981 if (_wcsnicmp(myFilePath
.get(), inFilePath
.get(), myFilePathLen
) == 0) {
2990 nsLocalFile::GetTarget(nsAString
& aResult
) {
2995 mUseDOSDevicePathSyntax
,
2996 !FilePreferences::StartsWithDiskDesignatorAndBackslash(mResolvedPath
));
2998 aResult
= mResolvedPath
;
3003 nsLocalFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator
** aEntries
) {
3006 *aEntries
= nullptr;
3007 if (mWorkingPath
.EqualsLiteral("\\\\.")) {
3008 RefPtr
<nsDriveEnumerator
> drives
=
3009 new nsDriveEnumerator(mUseDOSDevicePathSyntax
);
3010 rv
= drives
->Init();
3011 if (NS_FAILED(rv
)) {
3014 drives
.forget(aEntries
);
3018 RefPtr
<nsDirEnumerator
> dirEnum
= new nsDirEnumerator();
3019 rv
= dirEnum
->Init(this);
3020 if (NS_FAILED(rv
)) {
3024 dirEnum
.forget(aEntries
);
3030 nsLocalFile::GetPersistentDescriptor(nsACString
& aPersistentDescriptor
) {
3031 CopyUTF16toUTF8(mWorkingPath
, aPersistentDescriptor
);
3036 nsLocalFile::SetPersistentDescriptor(const nsACString
& aPersistentDescriptor
) {
3037 if (IsUtf8(aPersistentDescriptor
)) {
3038 return InitWithPath(NS_ConvertUTF8toUTF16(aPersistentDescriptor
));
3040 return InitWithNativePath(aPersistentDescriptor
);
3045 nsLocalFile::GetFileAttributesWin(uint32_t* aAttribs
) {
3047 DWORD dwAttrs
= GetFileAttributesW(mWorkingPath
.get());
3048 if (dwAttrs
== INVALID_FILE_ATTRIBUTES
) {
3049 return NS_ERROR_FILE_INVALID_PATH
;
3052 if (!(dwAttrs
& FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
)) {
3053 *aAttribs
|= WFA_SEARCH_INDEXED
;
3060 nsLocalFile::SetFileAttributesWin(uint32_t aAttribs
) {
3061 DWORD dwAttrs
= GetFileAttributesW(mWorkingPath
.get());
3062 if (dwAttrs
== INVALID_FILE_ATTRIBUTES
) {
3063 return NS_ERROR_FILE_INVALID_PATH
;
3066 if (aAttribs
& WFA_SEARCH_INDEXED
) {
3067 dwAttrs
&= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
;
3069 dwAttrs
|= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
;
3072 if (aAttribs
& WFA_READONLY
) {
3073 dwAttrs
|= FILE_ATTRIBUTE_READONLY
;
3074 } else if ((aAttribs
& WFA_READWRITE
) &&
3075 (dwAttrs
& FILE_ATTRIBUTE_READONLY
)) {
3076 dwAttrs
&= ~FILE_ATTRIBUTE_READONLY
;
3079 if (SetFileAttributesW(mWorkingPath
.get(), dwAttrs
) == 0) {
3080 return NS_ERROR_FAILURE
;
3086 nsLocalFile::GetUseDOSDevicePathSyntax(bool* aUseDOSDevicePathSyntax
) {
3087 MOZ_ASSERT(aUseDOSDevicePathSyntax
);
3089 *aUseDOSDevicePathSyntax
= mUseDOSDevicePathSyntax
;
3094 nsLocalFile::SetUseDOSDevicePathSyntax(bool aUseDOSDevicePathSyntax
) {
3095 if (mUseDOSDevicePathSyntax
== aUseDOSDevicePathSyntax
) {
3099 if (mUseDOSDevicePathSyntax
) {
3100 if (StringBeginsWith(mWorkingPath
, kDevicePathSpecifier
)) {
3102 // Remove the prefix
3103 mWorkingPath
= Substring(mWorkingPath
, kDevicePathSpecifier
.Length());
3106 if (FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath
)) {
3108 // Prepend the prefix
3109 mWorkingPath
= kDevicePathSpecifier
+ mWorkingPath
;
3113 mUseDOSDevicePathSyntax
= aUseDOSDevicePathSyntax
;
3118 nsLocalFile::Reveal() {
3119 // This API should be main thread only
3120 MOZ_ASSERT(NS_IsMainThread());
3122 // make sure mResolvedPath is set
3123 nsresult rv
= Resolve();
3124 if (NS_FAILED(rv
) && rv
!= NS_ERROR_FILE_NOT_FOUND
) {
3128 nsCOMPtr
<nsIRunnable
> task
=
3129 NS_NewRunnableFunction("nsLocalFile::Reveal", [path
= mResolvedPath
]() {
3130 MOZ_ASSERT(!NS_IsMainThread(), "Don't run on the main thread");
3132 bool doCoUninitialize
= SUCCEEDED(CoInitializeEx(
3133 nullptr, COINIT_APARTMENTTHREADED
| COINIT_DISABLE_OLE1DDE
));
3135 if (doCoUninitialize
) {
3140 return NS_DispatchBackgroundTask(task
,
3141 nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK
);
3145 nsLocalFile::Launch() {
3146 // This API should be main thread only
3147 MOZ_ASSERT(NS_IsMainThread());
3149 // use the app registry name to launch a shell execute....
3150 _bstr_t
execPath(mWorkingPath
.get());
3153 // Pass VT_ERROR/DISP_E_PARAMNOTFOUND to omit an optional RPC parameter
3154 // to execute a file with the default verb.
3155 _variant_t
verbDefault(DISP_E_PARAMNOTFOUND
, VT_ERROR
);
3156 _variant_t
showCmd(SW_SHOWNORMAL
);
3158 // Use the directory of the file we're launching as the working
3159 // directory. That way if we have a self extracting EXE it won't
3160 // suggest to extract to the install directory.
3161 wchar_t* workingDirectoryPtr
= nullptr;
3162 WCHAR workingDirectory
[MAX_PATH
+ 1] = {L
'\0'};
3163 wcsncpy(workingDirectory
, mWorkingPath
.get(), MAX_PATH
);
3164 if (PathRemoveFileSpecW(workingDirectory
)) {
3165 workingDirectoryPtr
= workingDirectory
;
3167 NS_WARNING("Could not set working directory for launched file.");
3170 // We have two methods to launch a file: ShellExecuteExW and
3171 // ShellExecuteByExplorer. ShellExecuteExW starts a new process as a child
3172 // of the current process, while ShellExecuteByExplorer starts a new process
3173 // as a child of explorer.exe.
3175 // We prefer launching a process via ShellExecuteByExplorer because
3176 // applications may not support the mitigation policies inherited from our
3177 // process. For example, Skype for Business does not start correctly with
3178 // the PreferSystem32Images policy which is one of the policies we use.
3180 // If ShellExecuteByExplorer fails for some reason e.g. a system without
3181 // running explorer.exe or VDI environment like Citrix, we fall back to
3182 // ShellExecuteExW which still works in those special environments.
3184 // There is an exception where we go straight to ShellExecuteExW without
3185 // trying ShellExecuteByExplorer. When the extension of a downloaded file is
3186 // "exe", we prefer security rather than compatibility.
3188 // When a user launches a downloaded executable, the directory containing
3189 // the downloaded file may contain a malicious DLL with a common name, which
3190 // may have been downloaded before. If the downloaded executable is launched
3191 // without the PreferSystem32Images policy, the process can be tricked into
3192 // loading the malicious DLL in the same directory if its name is in the
3193 // executable's dependent modules. Therefore, we always launch ".exe"
3194 // executables via ShellExecuteExW so they inherit our process's mitigation
3195 // policies including PreferSystem32Images.
3197 // If the extension is not "exe", then we assume that we are launching an
3198 // installed application, and therefore the security risk described above
3199 // is lessened, as a malicious DLL is less likely to be installed in the
3200 // application's directory. In that case, we attempt to preserve
3201 // compatibility and try ShellExecuteByExplorer first.
3203 static const char* const onlyExeExt
[] = {".exe"};
3206 LookupExtensionIn(onlyExeExt
, ArrayLength(onlyExeExt
), &isExecutable
);
3207 if (NS_FAILED(rv
)) {
3208 isExecutable
= false;
3211 // If the file is an executable, go straight to ShellExecuteExW.
3212 // Otherwise try ShellExecuteByExplorer first, and if it fails,
3213 // run ShellExecuteExW.
3214 if (!isExecutable
) {
3215 mozilla::LauncherVoidResult shellExecuteOk
=
3216 mozilla::ShellExecuteByExplorer(execPath
, args
, verbDefault
,
3217 workingDirectoryPtr
, showCmd
);
3218 if (shellExecuteOk
.isOk()) {
3223 SHELLEXECUTEINFOW seinfo
= {sizeof(SHELLEXECUTEINFOW
)};
3224 seinfo
.fMask
= SEE_MASK_ASYNCOK
;
3225 seinfo
.hwnd
= GetMostRecentNavigatorHWND();
3226 seinfo
.lpVerb
= nullptr;
3227 seinfo
.lpFile
= mWorkingPath
.get();
3228 seinfo
.lpParameters
= nullptr;
3229 seinfo
.lpDirectory
= workingDirectoryPtr
;
3230 seinfo
.nShow
= SW_SHOWNORMAL
;
3232 if (!ShellExecuteExW(&seinfo
)) {
3233 return NS_ERROR_FILE_EXECUTION_FAILED
;
3239 nsresult
NS_NewLocalFile(const nsAString
& aPath
, bool aFollowLinks
,
3240 nsIFile
** aResult
) {
3241 RefPtr
<nsLocalFile
> file
= new nsLocalFile();
3243 if (!aPath
.IsEmpty()) {
3244 nsresult rv
= file
->InitWithPath(aPath
);
3245 if (NS_FAILED(rv
)) {
3250 file
.forget(aResult
);
3254 //-----------------------------------------------------------------------------
3255 // Native (lossy) interface
3256 //-----------------------------------------------------------------------------
3259 nsLocalFile::InitWithNativePath(const nsACString
& aFilePath
) {
3261 nsresult rv
= NS_CopyNativeToUnicode(aFilePath
, tmp
);
3262 if (NS_SUCCEEDED(rv
)) {
3263 return InitWithPath(tmp
);
3270 nsLocalFile::AppendNative(const nsACString
& aNode
) {
3272 nsresult rv
= NS_CopyNativeToUnicode(aNode
, tmp
);
3273 if (NS_SUCCEEDED(rv
)) {
3281 nsLocalFile::AppendRelativeNativePath(const nsACString
& aNode
) {
3283 nsresult rv
= NS_CopyNativeToUnicode(aNode
, tmp
);
3284 if (NS_SUCCEEDED(rv
)) {
3285 return AppendRelativePath(tmp
);
3291 nsLocalFile::GetNativeLeafName(nsACString
& aLeafName
) {
3292 // NS_WARNING("This API is lossy. Use GetLeafName !");
3294 nsresult rv
= GetLeafName(tmp
);
3295 if (NS_SUCCEEDED(rv
)) {
3296 rv
= NS_CopyUnicodeToNative(tmp
, aLeafName
);
3303 nsLocalFile::SetNativeLeafName(const nsACString
& aLeafName
) {
3305 nsresult rv
= NS_CopyNativeToUnicode(aLeafName
, tmp
);
3306 if (NS_SUCCEEDED(rv
)) {
3307 return SetLeafName(tmp
);
3313 nsString
nsLocalFile::NativePath() { return mWorkingPath
; }
3315 nsCString
nsIFile::HumanReadablePath() {
3317 DebugOnly
<nsresult
> rv
= GetPath(path
);
3318 MOZ_ASSERT(NS_SUCCEEDED(rv
));
3319 return NS_ConvertUTF16toUTF8(path
);
3323 nsLocalFile::GetNativeCanonicalPath(nsACString
& aResult
) {
3324 NS_WARNING("This method is lossy. Use GetCanonicalPath !");
3326 NS_CopyUnicodeToNative(mShortWorkingPath
, aResult
);
3331 nsLocalFile::CopyToNative(nsIFile
* aNewParentDir
, const nsACString
& aNewName
) {
3332 // Check we are correctly initialized.
3333 CHECK_mWorkingPath();
3335 if (aNewName
.IsEmpty()) {
3336 return CopyTo(aNewParentDir
, u
""_ns
);
3340 nsresult rv
= NS_CopyNativeToUnicode(aNewName
, tmp
);
3341 if (NS_SUCCEEDED(rv
)) {
3342 return CopyTo(aNewParentDir
, tmp
);
3349 nsLocalFile::CopyToFollowingLinksNative(nsIFile
* aNewParentDir
,
3350 const nsACString
& aNewName
) {
3351 if (aNewName
.IsEmpty()) {
3352 return CopyToFollowingLinks(aNewParentDir
, u
""_ns
);
3356 nsresult rv
= NS_CopyNativeToUnicode(aNewName
, tmp
);
3357 if (NS_SUCCEEDED(rv
)) {
3358 return CopyToFollowingLinks(aNewParentDir
, tmp
);
3365 nsLocalFile::MoveToNative(nsIFile
* aNewParentDir
, const nsACString
& aNewName
) {
3366 // Check we are correctly initialized.
3367 CHECK_mWorkingPath();
3369 if (aNewName
.IsEmpty()) {
3370 return MoveTo(aNewParentDir
, u
""_ns
);
3374 nsresult rv
= NS_CopyNativeToUnicode(aNewName
, tmp
);
3375 if (NS_SUCCEEDED(rv
)) {
3376 return MoveTo(aNewParentDir
, tmp
);
3383 nsLocalFile::MoveToFollowingLinksNative(nsIFile
* aNewParentDir
,
3384 const nsACString
& aNewName
) {
3385 // Check we are correctly initialized.
3386 CHECK_mWorkingPath();
3388 if (aNewName
.IsEmpty()) {
3389 return MoveToFollowingLinks(aNewParentDir
, u
""_ns
);
3393 nsresult rv
= NS_CopyNativeToUnicode(aNewName
, tmp
);
3394 if (NS_SUCCEEDED(rv
)) {
3395 return MoveToFollowingLinks(aNewParentDir
, tmp
);
3402 nsLocalFile::GetNativeTarget(nsACString
& aResult
) {
3403 // Check we are correctly initialized.
3404 CHECK_mWorkingPath();
3406 NS_WARNING("This API is lossy. Use GetTarget !");
3408 nsresult rv
= GetTarget(tmp
);
3409 if (NS_SUCCEEDED(rv
)) {
3410 rv
= NS_CopyUnicodeToNative(tmp
, aResult
);
3416 nsresult
NS_NewNativeLocalFile(const nsACString
& aPath
, bool aFollowLinks
,
3417 nsIFile
** aResult
) {
3419 nsresult rv
= NS_CopyNativeToUnicode(aPath
, buf
);
3420 if (NS_FAILED(rv
)) {
3424 return NS_NewLocalFile(buf
, aFollowLinks
, aResult
);
3427 void nsLocalFile::EnsureShortPath() {
3428 if (!mShortWorkingPath
.IsEmpty()) {
3432 WCHAR shortPath
[MAX_PATH
+ 1];
3433 DWORD lengthNeeded
= ::GetShortPathNameW(mWorkingPath
.get(), shortPath
,
3434 ArrayLength(shortPath
));
3435 // If an error occurred then lengthNeeded is set to 0 or the length of the
3436 // needed buffer including null termination. If it succeeds the number of
3437 // wide characters not including null termination is returned.
3438 if (lengthNeeded
!= 0 && lengthNeeded
< ArrayLength(shortPath
)) {
3439 mShortWorkingPath
.Assign(shortPath
);
3441 mShortWorkingPath
.Assign(mWorkingPath
);
3445 NS_IMPL_ISUPPORTS_INHERITED(nsDriveEnumerator
, nsSimpleEnumerator
,
3446 nsIDirectoryEnumerator
)
3448 nsDriveEnumerator::nsDriveEnumerator(bool aUseDOSDevicePathSyntax
)
3449 : mUseDOSDevicePathSyntax(aUseDOSDevicePathSyntax
) {}
3451 nsDriveEnumerator::~nsDriveEnumerator() {}
3453 nsresult
nsDriveEnumerator::Init() {
3454 /* If the length passed to GetLogicalDriveStrings is smaller
3455 * than the length of the string it would return, it returns
3456 * the length required for the string. */
3457 DWORD length
= GetLogicalDriveStringsW(0, 0);
3458 /* The string is null terminated */
3459 if (!mDrives
.SetLength(length
+ 1, fallible
)) {
3460 return NS_ERROR_OUT_OF_MEMORY
;
3462 if (!GetLogicalDriveStringsW(length
, mDrives
.get())) {
3463 return NS_ERROR_FAILURE
;
3465 mDrives
.BeginReading(mStartOfCurrentDrive
);
3466 mDrives
.EndReading(mEndOfDrivesString
);
3471 nsDriveEnumerator::HasMoreElements(bool* aHasMore
) {
3472 *aHasMore
= *mStartOfCurrentDrive
!= L
'\0';
3477 nsDriveEnumerator::GetNext(nsISupports
** aNext
) {
3478 /* GetLogicalDrives stored in mDrives is a concatenation
3479 * of null terminated strings, followed by a null terminator.
3480 * mStartOfCurrentDrive is an iterator pointing at the first
3481 * character of the current drive. */
3482 if (*mStartOfCurrentDrive
== L
'\0') {
3484 return NS_ERROR_FAILURE
;
3487 nsAString::const_iterator driveEnd
= mStartOfCurrentDrive
;
3488 FindCharInReadable(L
'\0', driveEnd
, mEndOfDrivesString
);
3489 nsString
drive(Substring(mStartOfCurrentDrive
, driveEnd
));
3490 mStartOfCurrentDrive
= ++driveEnd
;
3493 nsresult rv
= NewLocalFile(drive
, mUseDOSDevicePathSyntax
, &file
);