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"
13 #include "mozilla/WinHeaderOnlyUtils.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"
47 #include "mozilla/FilePreferences.h"
48 #include "mozilla/Mutex.h"
49 #include "SpecialSystemDirectory.h"
51 #include "nsTraceRefcnt.h"
52 #include "nsXPCOMCIDInternal.h"
53 #include "nsThreadUtils.h"
54 #include "nsXULAppAPI.h"
55 #include "nsIWindowMediator.h"
57 #include "mozIDOMWindow.h"
58 #include "nsPIDOMWindow.h"
59 #include "nsIWidget.h"
60 #include "mozilla/ShellHeaderOnlyUtils.h"
61 #include "mozilla/WidgetUtils.h"
64 using namespace mozilla
;
65 using mozilla::FilePreferences::kDevicePathSpecifier
;
66 using mozilla::FilePreferences::kPathSeparator
;
68 #define CHECK_mWorkingPath() \
70 if (mWorkingPath.IsEmpty()) return NS_ERROR_NOT_INITIALIZED; \
73 #ifndef FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
74 # define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000
78 # define DRIVE_REMOTE 4
83 nsresult
NewLocalFile(const nsAString
& aPath
, bool aUseDOSDevicePathSyntax
,
85 RefPtr
<nsLocalFile
> file
= new nsLocalFile();
87 file
->SetUseDOSDevicePathSyntax(aUseDOSDevicePathSyntax
);
89 if (!aPath
.IsEmpty()) {
90 nsresult rv
= file
->InitWithPath(aPath
);
100 } // anonymous namespace
102 static HWND
GetMostRecentNavigatorHWND() {
104 nsCOMPtr
<nsIWindowMediator
> winMediator(
105 do_GetService(NS_WINDOWMEDIATOR_CONTRACTID
, &rv
));
110 nsCOMPtr
<mozIDOMWindowProxy
> navWin
;
111 rv
= winMediator
->GetMostRecentBrowserWindow(getter_AddRefs(navWin
));
112 if (NS_FAILED(rv
) || !navWin
) {
116 nsPIDOMWindowOuter
* win
= nsPIDOMWindowOuter::From(navWin
);
117 nsCOMPtr
<nsIWidget
> widget
= widget::WidgetUtils::DOMWindowToWidget(win
);
122 return reinterpret_cast<HWND
>(widget
->GetNativeData(NS_NATIVE_WINDOW
));
125 nsresult
nsLocalFile::RevealFile(const nsString
& aResolvedPath
) {
126 MOZ_ASSERT(!NS_IsMainThread(), "Don't run on the main thread");
128 DWORD attributes
= GetFileAttributesW(aResolvedPath
.get());
129 if (INVALID_FILE_ATTRIBUTES
== attributes
) {
130 return NS_ERROR_FILE_INVALID_PATH
;
134 if (attributes
& FILE_ATTRIBUTE_DIRECTORY
) {
135 // We have a directory so we should open the directory itself.
136 LPITEMIDLIST dir
= ILCreateFromPathW(aResolvedPath
.get());
138 return NS_ERROR_FAILURE
;
141 LPCITEMIDLIST selection
[] = {dir
};
142 UINT count
= std::size(selection
);
144 // Perform the open of the directory.
145 hr
= SHOpenFolderAndSelectItems(dir
, count
, selection
, 0);
148 int32_t len
= aResolvedPath
.Length();
149 // We don't currently handle UNC long paths of the form \\?\ anywhere so
150 // this should be fine.
151 if (len
> MAX_PATH
) {
152 return NS_ERROR_FILE_INVALID_PATH
;
154 WCHAR parentDirectoryPath
[MAX_PATH
+ 1] = {0};
155 wcsncpy(parentDirectoryPath
, aResolvedPath
.get(), MAX_PATH
);
156 PathRemoveFileSpecW(parentDirectoryPath
);
158 // We have a file so we should open the parent directory.
159 LPITEMIDLIST dir
= ILCreateFromPathW(parentDirectoryPath
);
161 return NS_ERROR_FAILURE
;
164 // Set the item in the directory to select to the file we want to reveal.
165 LPITEMIDLIST item
= ILCreateFromPathW(aResolvedPath
.get());
168 return NS_ERROR_FAILURE
;
171 LPCITEMIDLIST selection
[] = {item
};
172 UINT count
= std::size(selection
);
174 // Perform the selection of the file.
175 hr
= SHOpenFolderAndSelectItems(dir
, count
, selection
, 0);
181 return SUCCEEDED(hr
) ? NS_OK
: NS_ERROR_FAILURE
;
185 bool nsLocalFile::CheckForReservedFileName(const nsString
& aFileName
) {
186 static const nsLiteralString forbiddenNames
[] = {
187 u
"COM1"_ns
, u
"COM2"_ns
, u
"COM3"_ns
, u
"COM4"_ns
, u
"COM5"_ns
, u
"COM6"_ns
,
188 u
"COM7"_ns
, u
"COM8"_ns
, u
"COM9"_ns
, u
"LPT1"_ns
, u
"LPT2"_ns
, u
"LPT3"_ns
,
189 u
"LPT4"_ns
, u
"LPT5"_ns
, u
"LPT6"_ns
, u
"LPT7"_ns
, u
"LPT8"_ns
, u
"LPT9"_ns
,
190 u
"CON"_ns
, u
"PRN"_ns
, u
"AUX"_ns
, u
"NUL"_ns
, u
"CLOCK$"_ns
};
192 for (const nsLiteralString
& forbiddenName
: forbiddenNames
) {
193 if (StringBeginsWith(aFileName
, forbiddenName
,
194 nsASCIICaseInsensitiveStringComparator
)) {
195 // invalid name is either the entire string, or a prefix with a period
196 if (aFileName
.Length() == forbiddenName
.Length() ||
197 aFileName
.CharAt(forbiddenName
.Length()) == char16_t('.')) {
206 class nsDriveEnumerator
: public nsSimpleEnumerator
,
207 public nsIDirectoryEnumerator
{
209 explicit nsDriveEnumerator(bool aUseDOSDevicePathSyntax
);
210 NS_DECL_ISUPPORTS_INHERITED
211 NS_DECL_NSISIMPLEENUMERATOR
212 NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::)
215 const nsID
& DefaultInterface() override
{ return NS_GET_IID(nsIFile
); }
217 NS_IMETHOD
GetNextFile(nsIFile
** aResult
) override
{
218 bool hasMore
= false;
219 nsresult rv
= HasMoreElements(&hasMore
);
220 if (NS_FAILED(rv
) || !hasMore
) {
223 nsCOMPtr
<nsISupports
> next
;
224 rv
= GetNext(getter_AddRefs(next
));
225 NS_ENSURE_SUCCESS(rv
, rv
);
227 nsCOMPtr
<nsIFile
> result
= do_QueryInterface(next
);
228 result
.forget(aResult
);
232 NS_IMETHOD
Close() override
{ return NS_OK
; }
235 virtual ~nsDriveEnumerator();
237 /* mDrives stores the null-separated drive names.
239 * HasMoreElements checks mStartOfCurrentDrive.
240 * GetNext advances mStartOfCurrentDrive.
243 nsAString::const_iterator mStartOfCurrentDrive
;
244 nsAString::const_iterator mEndOfDrivesString
;
245 const bool mUseDOSDevicePathSyntax
;
248 //-----------------------------------------------------------------------------
249 // static helper functions
250 //-----------------------------------------------------------------------------
253 * While not comprehensive, this will map many common Windows error codes to a
254 * corresponding nsresult. If an unmapped error is encountered, the hex error
255 * code will be logged to stderr. Error codes, names, and descriptions can be
256 * found at the following MSDN page:
257 * https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes
259 * \note When adding more mappings here, it must be checked if there's code that
260 * depends on the current generic NS_ERROR_MODULE_WIN32 mapping for such error
263 static nsresult
ConvertWinError(DWORD aWinErr
) {
267 case ERROR_FILE_NOT_FOUND
:
268 [[fallthrough
]]; // to NS_ERROR_FILE_NOT_FOUND
269 case ERROR_PATH_NOT_FOUND
:
270 [[fallthrough
]]; // to NS_ERROR_FILE_NOT_FOUND
271 case ERROR_INVALID_DRIVE
:
272 rv
= NS_ERROR_FILE_NOT_FOUND
;
274 case ERROR_ACCESS_DENIED
:
275 [[fallthrough
]]; // to NS_ERROR_FILE_ACCESS_DENIED
276 case ERROR_NOT_SAME_DEVICE
:
277 [[fallthrough
]]; // to NS_ERROR_FILE_ACCESS_DENIED
278 case ERROR_CANNOT_MAKE
:
279 [[fallthrough
]]; // to NS_ERROR_FILE_ACCESS_DENIED
280 case ERROR_CONTENT_BLOCKED
:
281 rv
= NS_ERROR_FILE_ACCESS_DENIED
;
283 case ERROR_SHARING_VIOLATION
: // CreateFile without sharing flags
284 [[fallthrough
]]; // to NS_ERROR_FILE_IS_LOCKED
285 case ERROR_LOCK_VIOLATION
: // LockFile, LockFileEx
286 rv
= NS_ERROR_FILE_IS_LOCKED
;
288 case ERROR_NOT_ENOUGH_MEMORY
:
289 [[fallthrough
]]; // to NS_ERROR_OUT_OF_MEMORY
290 case ERROR_NO_SYSTEM_RESOURCES
:
291 rv
= NS_ERROR_OUT_OF_MEMORY
;
293 case ERROR_DIR_NOT_EMPTY
:
294 [[fallthrough
]]; // to NS_ERROR_FILE_DIR_NOT_EMPTY
295 case ERROR_CURRENT_DIRECTORY
:
296 rv
= NS_ERROR_FILE_DIR_NOT_EMPTY
;
298 case ERROR_WRITE_PROTECT
:
299 rv
= NS_ERROR_FILE_READ_ONLY
;
301 case ERROR_HANDLE_DISK_FULL
:
302 [[fallthrough
]]; // to NS_ERROR_FILE_NO_DEVICE_SPACE
303 case ERROR_DISK_FULL
:
304 rv
= NS_ERROR_FILE_NO_DEVICE_SPACE
;
306 case ERROR_FILE_EXISTS
:
307 [[fallthrough
]]; // to NS_ERROR_FILE_ALREADY_EXISTS
308 case ERROR_ALREADY_EXISTS
:
309 rv
= NS_ERROR_FILE_ALREADY_EXISTS
;
311 case ERROR_FILENAME_EXCED_RANGE
:
312 rv
= NS_ERROR_FILE_NAME_TOO_LONG
;
314 case ERROR_DIRECTORY
:
315 rv
= NS_ERROR_FILE_NOT_DIRECTORY
;
317 case ERROR_FILE_CORRUPT
:
318 [[fallthrough
]]; // to NS_ERROR_FILE_FS_CORRUPTED
319 case ERROR_DISK_CORRUPT
:
320 rv
= NS_ERROR_FILE_FS_CORRUPTED
;
322 case ERROR_DEVICE_HARDWARE_ERROR
:
323 [[fallthrough
]]; // to NS_ERROR_FILE_DEVICE_FAILURE
324 case ERROR_DEVICE_NOT_CONNECTED
:
325 [[fallthrough
]]; // to NS_ERROR_FILE_DEVICE_FAILURE
326 case ERROR_DEV_NOT_EXIST
:
327 [[fallthrough
]]; // to NS_ERROR_FILE_DEVICE_FAILURE
328 case ERROR_INVALID_FUNCTION
:
329 [[fallthrough
]]; // to NS_ERROR_FILE_DEVICE_FAILURE
330 case ERROR_IO_DEVICE
:
331 rv
= NS_ERROR_FILE_DEVICE_FAILURE
;
333 case ERROR_NOT_READY
:
334 rv
= NS_ERROR_FILE_DEVICE_TEMPORARY_FAILURE
;
336 case ERROR_INVALID_NAME
:
337 rv
= NS_ERROR_FILE_INVALID_PATH
;
339 case ERROR_INVALID_BLOCK
:
340 [[fallthrough
]]; // to NS_ERROR_FILE_INVALID_HANDLE
341 case ERROR_INVALID_HANDLE
:
342 [[fallthrough
]]; // to NS_ERROR_FILE_INVALID_HANDLE
343 case ERROR_ARENA_TRASHED
:
344 rv
= NS_ERROR_FILE_INVALID_HANDLE
;
351 "ConvertWinError received an unrecognized WinError: 0x%" PRIx32
"\n",
352 static_cast<uint32_t>(aWinErr
));
353 MOZ_ASSERT((aWinErr
& 0xFFFF) == aWinErr
);
354 rv
= NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_WIN32
, aWinErr
& 0xFFFF);
360 // Check whether a path is a volume root. Expects paths to be \-terminated.
361 static bool IsRootPath(const nsAString
& aPath
) {
363 if (aPath
.Last() != L
'\\') {
366 if (StringEndsWith(aPath
, u
":\\"_ns
)) {
370 nsAString::const_iterator begin
, end
;
371 aPath
.BeginReading(begin
);
372 aPath
.EndReading(end
);
373 // We know we've got a trailing slash, skip that:
375 // Find the next last slash:
376 if (RFindInReadable(u
"\\"_ns
, begin
, end
)) {
378 aPath
.EndReading(end
);
380 auto lastSegment
= Substring(++begin
, end
);
381 if (lastSegment
.IsEmpty()) {
385 // Check if we end with e.g. "c$", a drive letter in UNC or network shares
386 if (lastSegment
.Last() == L
'$' && lastSegment
.Length() == 2 &&
387 IsAsciiAlpha(lastSegment
.First())) {
390 // Volume GUID paths:
391 if (StringBeginsWith(lastSegment
, u
"Volume{"_ns
) &&
392 lastSegment
.Last() == L
'}') {
399 static auto kSpecialNTFSFilesInRoot
= {
400 u
"$MFT"_ns
, u
"$MFTMirr"_ns
, u
"$LogFile"_ns
, u
"$Volume"_ns
,
401 u
"$AttrDef"_ns
, u
"$Bitmap"_ns
, u
"$Boot"_ns
, u
"$BadClus"_ns
,
402 u
"$Secure"_ns
, u
"$UpCase"_ns
, u
"$Extend"_ns
};
403 static bool IsSpecialNTFSPath(const nsAString
& aFilePath
) {
404 nsAString::const_iterator begin
, end
;
405 aFilePath
.BeginReading(begin
);
406 aFilePath
.EndReading(end
);
408 // Early exit if there's no '$' (common case)
409 if (!FindCharInReadable(L
'$', iter
, end
)) {
414 // Any use of ':$' is illegal in filenames anyway; while we support some
415 // ADS stuff (ie ":Zone.Identifier"), none of them use the ':$' syntax:
416 if (FindInReadable(u
":$"_ns
, iter
, end
)) {
420 auto normalized
= mozilla::MakeUniqueFallible
<wchar_t[]>(MAX_PATH
);
424 auto flatPath
= PromiseFlatString(aFilePath
);
426 GetFullPathNameW(flatPath
.get(), MAX_PATH
- 1, normalized
.get(), nullptr);
427 if (fullPathRV
== 0 || fullPathRV
> MAX_PATH
- 1) {
431 nsString
normalizedPath(normalized
.get());
432 normalizedPath
.BeginReading(begin
);
433 normalizedPath
.EndReading(end
);
435 auto kDelimiters
= u
"\\:"_ns
;
436 while (iter
!= end
&& FindCharInReadable(L
'$', iter
, end
)) {
437 for (auto str
: kSpecialNTFSFilesInRoot
) {
438 if (StringBeginsWith(Substring(iter
, end
), str
,
439 nsCaseInsensitiveStringComparator
)) {
440 // If we're enclosed by separators or the beginning/end of the string,
441 // this is one of the special files. Check if we're on a volume root.
442 auto iterCopy
= iter
;
443 iterCopy
.advance(str
.Length());
444 // We check for both \ and : here because the filename could be
445 // followd by a colon and a stream name/type, which shouldn't affect
447 if (iterCopy
== end
|| kDelimiters
.Contains(*iterCopy
)) {
449 // At the start of this path component, we don't need to care about
450 // colons: we would have caught those in the check for `:$` above.
451 if (iterCopy
== begin
|| *(--iterCopy
) == L
'\\') {
452 return IsRootPath(Substring(begin
, iter
));
463 //-----------------------------------------------------------------------------
464 // We need the following three definitions to make |OpenFile| convert a file
465 // handle to an NSPR file descriptor correctly when |O_APPEND| flag is
466 // specified. It is defined in a private header of NSPR (primpl.h) we can't
467 // include. As a temporary workaround until we decide how to extend
468 // |PR_ImportFile|, we define it here. Currently, |_PR_HAVE_PEEK_BUFFER|
469 // and |PR_STRICT_ADDR_LEN| are not defined for the 'w95'-dependent portion
470 // of NSPR so that fields of |PRFilePrivate| #ifdef'd by them are not copied.
471 // Similarly, |_MDFileDesc| is taken from nsprpub/pr/include/md/_win95.h.
472 // In an unlikely case we switch to 'NT'-dependent NSPR AND this temporary
473 // workaround last beyond the switch, |PRFilePrivate| and |_MDFileDesc|
474 // need to be changed to match the definitions for WinNT.
475 //-----------------------------------------------------------------------------
486 struct PRFilePrivate
{
489 _PRTriStateBool inheritable
;
491 int lockCount
; /* 0: not locked
492 * -1: a native lockfile call is in progress
493 * > 0: # times the file is locked */
498 //-----------------------------------------------------------------------------
499 // Six static methods defined below (OpenFile, FileTimeToPRTime, GetFileInfo,
500 // OpenDir, CloseDir, ReadDir) should go away once the corresponding
501 // UTF-16 APIs are implemented on all the supported platforms (or at least
502 // Windows 9x/ME) in NSPR. Currently, they're only implemented on
503 // Windows NT4 or later. (bug 330665)
504 //-----------------------------------------------------------------------------
506 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} :
507 // PR_Open and _PR_MD_OPEN
508 nsresult
OpenFile(const nsString
& aName
, int aOsflags
, int aMode
,
509 bool aShareDelete
, PRFileDesc
** aFd
) {
512 int32_t shareMode
= FILE_SHARE_READ
| FILE_SHARE_WRITE
;
513 int32_t disposition
= 0;
514 int32_t attributes
= 0;
517 shareMode
|= FILE_SHARE_DELETE
;
520 if (aOsflags
& PR_SYNC
) {
521 attributes
= FILE_FLAG_WRITE_THROUGH
;
523 if (aOsflags
& PR_RDONLY
|| aOsflags
& PR_RDWR
) {
524 access
|= GENERIC_READ
;
526 if (aOsflags
& PR_WRONLY
|| aOsflags
& PR_RDWR
) {
527 access
|= GENERIC_WRITE
;
530 if (aOsflags
& PR_CREATE_FILE
&& aOsflags
& PR_EXCL
) {
531 disposition
= CREATE_NEW
;
532 } else if (aOsflags
& PR_CREATE_FILE
) {
533 if (aOsflags
& PR_TRUNCATE
) {
534 disposition
= CREATE_ALWAYS
;
536 disposition
= OPEN_ALWAYS
;
539 if (aOsflags
& PR_TRUNCATE
) {
540 disposition
= TRUNCATE_EXISTING
;
542 disposition
= OPEN_EXISTING
;
546 if (aOsflags
& nsIFile::DELETE_ON_CLOSE
) {
547 attributes
|= FILE_FLAG_DELETE_ON_CLOSE
;
550 if (aOsflags
& nsIFile::OS_READAHEAD
) {
551 attributes
|= FILE_FLAG_SEQUENTIAL_SCAN
;
554 // If no write permissions are requested, and if we are possibly creating
555 // the file, then set the new file as read only.
556 // The flag has no effect if we happen to open the file.
557 if (!(aMode
& (PR_IWUSR
| PR_IWGRP
| PR_IWOTH
)) &&
558 disposition
!= OPEN_EXISTING
) {
559 attributes
|= FILE_ATTRIBUTE_READONLY
;
562 HANDLE file
= ::CreateFileW(aName
.get(), access
, shareMode
, nullptr,
563 disposition
, attributes
, nullptr);
565 if (file
== INVALID_HANDLE_VALUE
) {
567 return ConvertWinError(GetLastError());
570 *aFd
= PR_ImportFile((PROsfd
)file
);
572 // On Windows, _PR_HAVE_O_APPEND is not defined so that we have to
573 // add it manually. (see |PR_Open| in nsprpub/pr/src/io/prfile.c)
574 (*aFd
)->secret
->appendMode
= (PR_APPEND
& aOsflags
) ? true : false;
578 nsresult rv
= NS_ErrorAccordingToNSPR();
585 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} :
586 // PR_FileTimeToPRTime and _PR_FileTimeToPRTime
587 static void FileTimeToPRTime(const FILETIME
* aFiletime
, PRTime
* aPrtm
) {
589 const PRTime _pr_filetime_offset
= 116444736000000000LL;
591 const PRTime _pr_filetime_offset
= 116444736000000000i64
;
594 MOZ_ASSERT(sizeof(FILETIME
) == sizeof(PRTime
));
595 ::CopyMemory(aPrtm
, aFiletime
, sizeof(PRTime
));
597 *aPrtm
= (*aPrtm
- _pr_filetime_offset
) / 10LL;
599 *aPrtm
= (*aPrtm
- _pr_filetime_offset
) / 10i64
;
603 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} with some
604 // changes : PR_GetFileInfo64, _PR_MD_GETFILEINFO64
605 static nsresult
GetFileInfo(const nsString
& aName
,
606 nsLocalFile::FileInfo
* aInfo
) {
607 if (aName
.IsEmpty()) {
608 return NS_ERROR_INVALID_ARG
;
611 // Checking u"?*" for the file path excluding the kDevicePathSpecifier.
612 // ToDo: Check if checking "?" for the file path is still needed.
613 const int32_t offset
= StringBeginsWith(aName
, kDevicePathSpecifier
)
614 ? kDevicePathSpecifier
.Length()
617 if (aName
.FindCharInSet(u
"?*", offset
) != kNotFound
) {
618 return NS_ERROR_INVALID_ARG
;
621 WIN32_FILE_ATTRIBUTE_DATA fileData
;
622 if (!::GetFileAttributesExW(aName
.get(), GetFileExInfoStandard
, &fileData
)) {
623 return ConvertWinError(GetLastError());
626 if (fileData
.dwFileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
) {
627 aInfo
->type
= PR_FILE_OTHER
;
628 } else if (fileData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
629 aInfo
->type
= PR_FILE_DIRECTORY
;
631 aInfo
->type
= PR_FILE_FILE
;
634 aInfo
->size
= fileData
.nFileSizeHigh
;
635 aInfo
->size
= (aInfo
->size
<< 32) + fileData
.nFileSizeLow
;
637 // modifyTime must be initialized before creationTime refers it.
638 FileTimeToPRTime(&fileData
.ftLastWriteTime
, &aInfo
->modifyTime
);
640 if (0 == fileData
.ftCreationTime
.dwLowDateTime
&&
641 0 == fileData
.ftCreationTime
.dwHighDateTime
) {
642 aInfo
->creationTime
= aInfo
->modifyTime
;
644 FileTimeToPRTime(&fileData
.ftCreationTime
, &aInfo
->creationTime
);
647 FileTimeToPRTime(&fileData
.ftLastAccessTime
, &aInfo
->accessTime
);
654 WIN32_FIND_DATAW data
;
658 static nsresult
OpenDir(const nsString
& aName
, nsDir
** aDir
) {
659 if (NS_WARN_IF(!aDir
)) {
660 return NS_ERROR_INVALID_ARG
;
665 nsDir
* d
= new nsDir();
666 nsAutoString
filename(aName
);
668 // If |aName| ends in a slash or backslash, do not append another backslash.
669 if (filename
.Last() == L
'/' || filename
.Last() == L
'\\') {
670 filename
.Append('*');
672 filename
.AppendLiteral("\\*");
675 filename
.ReplaceChar(L
'/', L
'\\');
677 // FindFirstFileExW Will have a last error of ERROR_DIRECTORY if
678 // <file_path>\* is passed in. If <unknown_path>\* is passed in then
679 // ERROR_PATH_NOT_FOUND will be the last error.
680 d
->handle
= ::FindFirstFileExW(filename
.get(), FindExInfoBasic
, &(d
->data
),
681 FindExSearchNameMatch
, nullptr,
682 FIND_FIRST_EX_LARGE_FETCH
);
684 if (d
->handle
== INVALID_HANDLE_VALUE
) {
686 return ConvertWinError(GetLastError());
688 d
->firstEntry
= true;
694 static nsresult
ReadDir(nsDir
* aDir
, PRDirFlags aFlags
, nsString
& aName
) {
696 if (NS_WARN_IF(!aDir
)) {
697 return NS_ERROR_INVALID_ARG
;
702 if (aDir
->firstEntry
) {
703 aDir
->firstEntry
= false;
706 rv
= ::FindNextFileW(aDir
->handle
, &(aDir
->data
));
713 const wchar_t* fileName
;
714 fileName
= (aDir
)->data
.cFileName
;
716 if ((aFlags
& PR_SKIP_DOT
) && (fileName
[0] == L
'.') &&
717 (fileName
[1] == L
'\0')) {
720 if ((aFlags
& PR_SKIP_DOT_DOT
) && (fileName
[0] == L
'.') &&
721 (fileName
[1] == L
'.') && (fileName
[2] == L
'\0')) {
725 DWORD attrib
= aDir
->data
.dwFileAttributes
;
726 if ((aFlags
& PR_SKIP_HIDDEN
) && (attrib
& FILE_ATTRIBUTE_HIDDEN
)) {
734 DWORD err
= GetLastError();
735 return err
== ERROR_NO_MORE_FILES
? NS_OK
: ConvertWinError(err
);
738 static nsresult
CloseDir(nsDir
*& aDir
) {
739 if (NS_WARN_IF(!aDir
)) {
740 return NS_ERROR_INVALID_ARG
;
743 BOOL isOk
= FindClose(aDir
->handle
);
746 return isOk
? NS_OK
: ConvertWinError(GetLastError());
749 //-----------------------------------------------------------------------------
751 //-----------------------------------------------------------------------------
753 class nsDirEnumerator final
: public nsSimpleEnumerator
,
754 public nsIDirectoryEnumerator
{
756 ~nsDirEnumerator() { Close(); }
759 NS_DECL_ISUPPORTS_INHERITED
761 NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::)
763 nsDirEnumerator() : mDir(nullptr) {}
765 const nsID
& DefaultInterface() override
{ return NS_GET_IID(nsIFile
); }
767 nsresult
Init(nsIFile
* aParent
) {
768 nsAutoString filepath
;
769 aParent
->GetTarget(filepath
);
771 if (filepath
.IsEmpty()) {
772 aParent
->GetPath(filepath
);
775 if (filepath
.IsEmpty()) {
776 return NS_ERROR_UNEXPECTED
;
779 // IsDirectory is not needed here because OpenDir will return
780 // NS_ERROR_FILE_NOT_DIRECTORY if the passed in path is a file.
781 nsresult rv
= OpenDir(filepath
, &mDir
);
790 NS_IMETHOD
HasMoreElements(bool* aResult
) override
{
792 if (!mNext
&& mDir
) {
794 rv
= ReadDir(mDir
, PR_SKIP_BOTH
, name
);
798 if (name
.IsEmpty()) {
799 // end of dir entries
809 nsCOMPtr
<nsIFile
> file
;
810 rv
= mParent
->Clone(getter_AddRefs(file
));
815 rv
= file
->Append(name
);
820 mNext
= file
.forget();
822 *aResult
= mNext
!= nullptr;
829 NS_IMETHOD
GetNext(nsISupports
** aResult
) override
{
832 rv
= HasMoreElements(&hasMore
);
837 return NS_ERROR_FAILURE
;
840 mNext
.forget(aResult
);
844 NS_IMETHOD
GetNextFile(nsIFile
** aResult
) override
{
846 bool hasMore
= false;
847 nsresult rv
= HasMoreElements(&hasMore
);
848 if (NS_FAILED(rv
) || !hasMore
) {
851 mNext
.forget(aResult
);
855 NS_IMETHOD
Close() override
{
857 nsresult rv
= CloseDir(mDir
);
858 NS_ASSERTION(NS_SUCCEEDED(rv
), "close failed");
860 return NS_ERROR_FAILURE
;
868 nsCOMPtr
<nsIFile
> mParent
;
869 nsCOMPtr
<nsIFile
> mNext
;
872 NS_IMPL_ISUPPORTS_INHERITED(nsDirEnumerator
, nsSimpleEnumerator
,
873 nsIDirectoryEnumerator
)
875 //-----------------------------------------------------------------------------
876 // nsLocalFile <public>
877 //-----------------------------------------------------------------------------
879 nsLocalFile::nsLocalFile()
880 : mDirty(true), mResolveDirty(true), mUseDOSDevicePathSyntax(false) {}
882 nsLocalFile::nsLocalFile(const nsAString
& aFilePath
)
883 : mUseDOSDevicePathSyntax(false) {
884 InitWithPath(aFilePath
);
887 nsresult
nsLocalFile::nsLocalFileConstructor(const nsIID
& aIID
,
888 void** aInstancePtr
) {
889 if (NS_WARN_IF(!aInstancePtr
)) {
890 return NS_ERROR_INVALID_ARG
;
893 nsLocalFile
* inst
= new nsLocalFile();
894 nsresult rv
= inst
->QueryInterface(aIID
, aInstancePtr
);
902 //-----------------------------------------------------------------------------
903 // nsLocalFile::nsISupports
904 //-----------------------------------------------------------------------------
906 NS_IMPL_ISUPPORTS(nsLocalFile
, nsIFile
, nsILocalFileWin
)
908 //-----------------------------------------------------------------------------
909 // nsLocalFile <private>
910 //-----------------------------------------------------------------------------
912 nsLocalFile::nsLocalFile(const nsLocalFile
& aOther
)
915 mUseDOSDevicePathSyntax(aOther
.mUseDOSDevicePathSyntax
),
916 mWorkingPath(aOther
.mWorkingPath
) {}
918 nsresult
nsLocalFile::ResolveSymlink() {
919 std::wstring
workingPath(mWorkingPath
.Data());
920 if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(workingPath
)) {
921 return NS_ERROR_FAILURE
;
923 mResolvedPath
.Assign(workingPath
.c_str(), workingPath
.length());
927 // Resolve any shortcuts and stat the resolved path. After a successful return
928 // the path is guaranteed valid and the members of mFileInfo can be used.
929 nsresult
nsLocalFile::ResolveAndStat() {
930 // if we aren't dirty then we are already done
935 AUTO_PROFILER_LABEL("nsLocalFile::ResolveAndStat", OTHER
);
936 // we can't resolve/stat anything that isn't a valid NSPR addressable path
937 if (mWorkingPath
.IsEmpty()) {
938 return NS_ERROR_FILE_INVALID_PATH
;
941 // this is usually correct
942 mResolvedPath
.Assign(mWorkingPath
);
944 // Make sure root paths have a trailing slash.
945 nsAutoString
nsprPath(mWorkingPath
);
946 if (mWorkingPath
.Length() == 2 && mWorkingPath
.CharAt(1) == u
':') {
947 nsprPath
.Append('\\');
950 // first we will see if the working path exists. If it doesn't then
951 // there is nothing more that can be done
952 nsresult rv
= GetFileInfo(nsprPath
, &mFileInfo
);
957 if (mFileInfo
.type
!= PR_FILE_OTHER
) {
958 mResolveDirty
= false;
963 // OTHER from GetFileInfo currently means a symlink
964 rv
= ResolveSymlink();
965 // Even if it fails we need to have the resolved path equal to working path
966 // for those functions that always use the resolved path.
968 mResolvedPath
.Assign(mWorkingPath
);
972 mResolveDirty
= false;
973 // get the details of the resolved path
974 rv
= GetFileInfo(mResolvedPath
, &mFileInfo
);
984 * Fills the mResolvedPath member variable with the file or symlink target
985 * if follow symlinks is on. This is a copy of the Resolve parts from
986 * ResolveAndStat. ResolveAndStat is much slower though because of the stat.
988 * @return NS_OK on success.
990 nsresult
nsLocalFile::Resolve() {
991 // if we aren't dirty then we are already done
992 if (!mResolveDirty
) {
996 // we can't resolve/stat anything that isn't a valid NSPR addressable path
997 if (mWorkingPath
.IsEmpty()) {
998 return NS_ERROR_FILE_INVALID_PATH
;
1001 // this is usually correct
1002 mResolvedPath
.Assign(mWorkingPath
);
1004 // TODO: Implement symlink support
1006 mResolveDirty
= false;
1010 //-----------------------------------------------------------------------------
1011 // nsLocalFile::nsIFile
1012 //-----------------------------------------------------------------------------
1015 nsLocalFile::Clone(nsIFile
** aFile
) {
1016 // Just copy-construct ourselves
1017 RefPtr
<nsLocalFile
> file
= new nsLocalFile(*this);
1024 nsLocalFile::InitWithFile(nsIFile
* aFile
) {
1025 if (NS_WARN_IF(!aFile
)) {
1026 return NS_ERROR_INVALID_ARG
;
1030 aFile
->GetPath(path
);
1031 if (path
.IsEmpty()) {
1032 return NS_ERROR_INVALID_ARG
;
1034 return InitWithPath(path
);
1038 nsLocalFile::InitWithPath(const nsAString
& aFilePath
) {
1041 nsAString::const_iterator begin
, end
;
1042 aFilePath
.BeginReading(begin
);
1043 aFilePath
.EndReading(end
);
1045 // input string must not be empty
1047 return NS_ERROR_FAILURE
;
1050 char16_t firstChar
= *begin
;
1051 char16_t secondChar
= *(++begin
);
1053 // just do a sanity check. if it has any forward slashes, it is not a Native
1054 // path on windows. Also, it must have a colon at after the first char.
1055 if (FindCharInReadable(L
'/', begin
, end
)) {
1056 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1059 if (FilePreferences::IsBlockedUNCPath(aFilePath
)) {
1060 return NS_ERROR_FILE_ACCESS_DENIED
;
1063 if (secondChar
!= L
':' && (secondChar
!= L
'\\' || firstChar
!= L
'\\')) {
1064 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1067 if (secondChar
== L
':') {
1068 // Make sure we have a valid drive, later code assumes the drive letter
1069 // is a single char a-z or A-Z.
1070 if (MozPathGetDriveNumber
<wchar_t>(aFilePath
.Data()) == -1) {
1071 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1075 if (IsSpecialNTFSPath(aFilePath
)) {
1076 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1079 mWorkingPath
= aFilePath
;
1080 // kill any trailing '\'
1081 if (mWorkingPath
.Last() == L
'\\') {
1082 mWorkingPath
.Truncate(mWorkingPath
.Length() - 1);
1085 // Bug 1626514: make sure that we don't end up with multiple prefixes.
1087 // Prepend the "\\?\" prefix if the useDOSDevicePathSyntax is set and the path
1088 // starts with a disk designator and backslash.
1089 if (mUseDOSDevicePathSyntax
&&
1090 FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath
)) {
1091 mWorkingPath
= kDevicePathSpecifier
+ mWorkingPath
;
1097 // Strip a handler command string of its quotes and parameters.
1098 static void CleanupHandlerPath(nsString
& aPath
) {
1099 // Example command strings passed into this routine:
1101 // 1) C:\Program Files\Company\some.exe -foo -bar
1102 // 2) C:\Program Files\Company\some.dll
1103 // 3) C:\Windows\some.dll,-foo -bar
1104 // 4) C:\Windows\some.cpl,-foo -bar
1106 int32_t lastCommaPos
= aPath
.RFindChar(',');
1107 if (lastCommaPos
!= kNotFound
) aPath
.Truncate(lastCommaPos
);
1112 int32_t index
= aPath
.LowerCaseFindASCII(".exe ");
1113 if (index
== kNotFound
) index
= aPath
.LowerCaseFindASCII(".dll ");
1114 if (index
== kNotFound
) index
= aPath
.LowerCaseFindASCII(".cpl ");
1116 if (index
!= kNotFound
) aPath
.Truncate(index
+ 4);
1117 aPath
.Trim(" ", true, true);
1120 // Strip the windows host process bootstrap executable rundll32.exe
1121 // from a handler's command string if it exists.
1122 static void StripRundll32(nsString
& aCommandString
) {
1123 // Example rundll formats:
1124 // C:\Windows\System32\rundll32.exe "path to dll"
1125 // rundll32.exe "path to dll"
1126 // C:\Windows\System32\rundll32.exe "path to dll", var var
1127 // rundll32.exe "path to dll", var var
1129 constexpr auto rundllSegment
= "rundll32.exe "_ns
;
1130 constexpr auto rundllSegmentShort
= "rundll32 "_ns
;
1133 int32_t strLen
= rundllSegment
.Length();
1134 int32_t index
= aCommandString
.LowerCaseFindASCII(rundllSegment
);
1135 if (index
== kNotFound
) {
1136 strLen
= rundllSegmentShort
.Length();
1137 index
= aCommandString
.LowerCaseFindASCII(rundllSegmentShort
);
1140 if (index
!= kNotFound
) {
1141 uint32_t rundllSegmentLength
= index
+ strLen
;
1142 aCommandString
.Cut(0, rundllSegmentLength
);
1146 // Returns the fully qualified path to an application handler based on
1147 // a parameterized command string. Note this routine should not be used
1148 // to launch the associated application as it strips parameters and
1149 // rundll.exe from the string. Designed for retrieving display information
1150 // on a particular handler.
1152 bool nsLocalFile::CleanupCmdHandlerPath(nsAString
& aCommandHandler
) {
1153 nsAutoString
handlerCommand(aCommandHandler
);
1155 // Straight command path:
1157 // %SystemRoot%\system32\NOTEPAD.EXE var
1158 // "C:\Program Files\iTunes\iTunes.exe" var var
1159 // C:\Program Files\iTunes\iTunes.exe var var
1161 // Example rundll handlers:
1163 // rundll32.exe "%ProgramFiles%\Win...ery\PhotoViewer.dll", var var
1164 // rundll32.exe "%ProgramFiles%\Windows Photo Gallery\PhotoViewer.dll"
1165 // C:\Windows\System32\rundll32.exe "path to dll", var var
1166 // %SystemRoot%\System32\rundll32.exe "%ProgramFiles%\Win...ery\Photo
1167 // Viewer.dll", var var
1169 // Expand environment variables so we have full path strings.
1170 uint32_t bufLength
=
1171 ::ExpandEnvironmentStringsW(handlerCommand
.get(), nullptr, 0);
1172 if (bufLength
== 0) // Error
1175 auto destination
= mozilla::MakeUniqueFallible
<wchar_t[]>(bufLength
);
1176 if (!destination
) return false;
1177 if (!::ExpandEnvironmentStringsW(handlerCommand
.get(), destination
.get(),
1181 handlerCommand
.Assign(destination
.get());
1183 // Remove quotes around paths
1184 handlerCommand
.StripChars(u
"\"");
1186 // Strip windows host process bootstrap so we can get to the actual
1188 StripRundll32(handlerCommand
);
1190 // Trim any command parameters so that we have a native path we can
1191 // initialize a local file with.
1192 CleanupHandlerPath(handlerCommand
);
1194 aCommandHandler
.Assign(handlerCommand
);
1199 nsLocalFile::InitWithCommandLine(const nsAString
& aCommandLine
) {
1200 nsAutoString
commandLine(aCommandLine
);
1201 if (!CleanupCmdHandlerPath(commandLine
)) {
1202 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1204 return InitWithPath(commandLine
);
1208 nsLocalFile::OpenNSPRFileDesc(int32_t aFlags
, int32_t aMode
,
1209 PRFileDesc
** aResult
) {
1210 nsresult rv
= OpenNSPRFileDescMaybeShareDelete(aFlags
, aMode
, false, aResult
);
1211 if (NS_FAILED(rv
)) {
1219 nsLocalFile::OpenANSIFileDesc(const char* aMode
, FILE** aResult
) {
1220 *aResult
= _wfopen(mWorkingPath
.get(), NS_ConvertASCIItoUTF16(aMode
).get());
1225 return NS_ERROR_FAILURE
;
1228 static nsresult
do_create(nsIFile
* aFile
, const nsString
& aPath
,
1229 uint32_t aAttributes
) {
1232 OpenFile(aPath
, PR_RDONLY
| PR_CREATE_FILE
| PR_APPEND
| PR_EXCL
,
1233 aAttributes
, false, &file
);
1238 if (rv
== NS_ERROR_FILE_ACCESS_DENIED
) {
1239 // need to return already-exists for directories (bug 452217)
1241 if (NS_SUCCEEDED(aFile
->IsDirectory(&isdir
)) && isdir
) {
1242 rv
= NS_ERROR_FILE_ALREADY_EXISTS
;
1248 static nsresult
do_mkdir(nsIFile
*, const nsString
& aPath
, uint32_t) {
1249 if (!::CreateDirectoryW(aPath
.get(), nullptr)) {
1250 return ConvertWinError(GetLastError());
1256 nsLocalFile::Create(uint32_t aType
, uint32_t aAttributes
, bool aSkipAncestors
) {
1257 if (aType
!= NORMAL_FILE_TYPE
&& aType
!= DIRECTORY_TYPE
) {
1258 return NS_ERROR_FILE_UNKNOWN_TYPE
;
1261 auto* createFunc
= (aType
== NORMAL_FILE_TYPE
? do_create
: do_mkdir
);
1263 nsresult rv
= createFunc(this, mWorkingPath
, aAttributes
);
1265 if (NS_SUCCEEDED(rv
) || NS_ERROR_FILE_ALREADY_EXISTS
== rv
||
1270 // create directories to target
1272 // A given local file can be either one of these forms:
1274 // - normal: X:\some\path\on\this\drive
1277 // - UNC path: \\machine\volume\some\path\on\this\drive
1280 // Skip the first 'X:\' for the first form, and skip the first full
1281 // '\\machine\volume\' segment for the second form.
1283 wchar_t* path
= char16ptr_t(mWorkingPath
.BeginWriting());
1285 if (path
[0] == L
'\\' && path
[1] == L
'\\') {
1286 // dealing with a UNC path here; skip past '\\machine\'
1287 path
= wcschr(path
+ 2, L
'\\');
1289 return NS_ERROR_FILE_INVALID_PATH
;
1294 // search for first slash after the drive (or volume) name
1295 wchar_t* slash
= wcschr(path
, L
'\\');
1297 nsresult directoryCreateError
= NS_OK
;
1299 // skip the first '\\'
1301 slash
= wcschr(slash
, L
'\\');
1306 if (!::CreateDirectoryW(mWorkingPath
.get(), nullptr)) {
1307 rv
= ConvertWinError(GetLastError());
1308 if (NS_ERROR_FILE_NOT_FOUND
== rv
&&
1309 NS_ERROR_FILE_ACCESS_DENIED
== directoryCreateError
) {
1310 // If a previous CreateDirectory failed due to access, return that.
1311 return NS_ERROR_FILE_ACCESS_DENIED
;
1313 // perhaps the base path already exists, or perhaps we don't have
1314 // permissions to create the directory. NOTE: access denied could
1315 // occur on a parent directory even though it exists.
1316 else if (rv
!= NS_ERROR_FILE_ALREADY_EXISTS
&&
1317 rv
!= NS_ERROR_FILE_ACCESS_DENIED
) {
1321 directoryCreateError
= rv
;
1325 slash
= wcschr(slash
, L
'\\');
1329 // If our last CreateDirectory failed due to access, return that.
1330 if (NS_ERROR_FILE_ACCESS_DENIED
== directoryCreateError
) {
1331 return directoryCreateError
;
1334 return createFunc(this, mWorkingPath
, aAttributes
);
1338 nsLocalFile::Append(const nsAString
& aNode
) {
1339 // append this path, multiple components are not permitted
1340 return AppendInternal(PromiseFlatString(aNode
), false);
1344 nsLocalFile::AppendRelativePath(const nsAString
& aNode
) {
1345 // append this path, multiple components are permitted
1346 return AppendInternal(PromiseFlatString(aNode
), true);
1349 nsresult
nsLocalFile::AppendInternal(const nsString
& aNode
,
1350 bool aMultipleComponents
) {
1351 if (aNode
.IsEmpty()) {
1355 // check the relative path for validity
1356 if (aNode
.First() == L
'\\' || // can't start with an '\'
1357 aNode
.Contains(L
'/') || // can't contain /
1358 aNode
.EqualsASCII("..")) { // can't be ..
1359 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1362 if (aMultipleComponents
) {
1363 // can't contain .. as a path component. Ensure that the valid components
1364 // "foo..foo", "..foo", and "foo.." are not falsely detected,
1365 // but the invalid paths "..\", "foo\..", "foo\..\foo",
1366 // "..\foo", etc are.
1367 constexpr auto doubleDot
= u
"\\.."_ns
;
1368 nsAString::const_iterator start
, end
, offset
;
1369 aNode
.BeginReading(start
);
1370 aNode
.EndReading(end
);
1372 while (FindInReadable(doubleDot
, start
, offset
)) {
1373 if (offset
== end
|| *offset
== L
'\\') {
1374 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1380 // catches the remaining cases of prefixes
1381 if (StringBeginsWith(aNode
, u
"..\\"_ns
)) {
1382 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1385 // single components can't contain '\'
1386 else if (aNode
.Contains(L
'\\')) {
1387 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1392 mWorkingPath
.Append('\\');
1393 mWorkingPath
.Append(aNode
);
1395 if (IsSpecialNTFSPath(mWorkingPath
)) {
1396 // Revert changes to mWorkingPath:
1397 mWorkingPath
.SetLength(mWorkingPath
.Length() - aNode
.Length() - 1);
1398 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1404 nsresult
nsLocalFile::OpenNSPRFileDescMaybeShareDelete(int32_t aFlags
,
1407 PRFileDesc
** aResult
) {
1408 return OpenFile(mWorkingPath
, aFlags
, aMode
, aShareDelete
, aResult
);
1411 #define TOUPPER(u) (((u) >= L'a' && (u) <= L'z') ? (u) - (L'a' - L'A') : (u))
1414 nsLocalFile::Normalize() {
1415 // XXX See bug 187957 comment 18 for possible problems with this
1418 if (mWorkingPath
.IsEmpty()) {
1422 nsAutoString
path(mWorkingPath
);
1424 // find the index of the root backslash for the path. Everything before
1425 // this is considered fully normalized and cannot be ascended beyond
1426 // using ".." For a local drive this is the first slash (e.g. "c:\").
1427 // For a UNC path it is the slash following the share name
1428 // (e.g. "\\server\share\").
1429 int32_t rootIdx
= 2; // default to local drive
1430 if (path
.First() == L
'\\') { // if a share then calculate the rootIdx
1431 rootIdx
= path
.FindChar(L
'\\', 2); // skip \\ in front of the server
1432 if (rootIdx
== kNotFound
) {
1433 return NS_OK
; // already normalized
1435 rootIdx
= path
.FindChar(L
'\\', rootIdx
+ 1);
1436 if (rootIdx
== kNotFound
) {
1437 return NS_OK
; // already normalized
1439 } else if (path
.CharAt(rootIdx
) != L
'\\') {
1440 // The path has been specified relative to the current working directory
1441 // for that drive. To normalize it, the current working directory for
1442 // that drive needs to be inserted before the supplied relative path
1443 // which will provide an absolute path (and the rootIdx will still be 2).
1444 WCHAR cwd
[MAX_PATH
];
1446 int drive
= TOUPPER(path
.First()) - 'A' + 1;
1447 /* We need to worry about IPH, for details read bug 419326.
1448 * _getdrives - http://msdn2.microsoft.com/en-us/library/xdhk0xd2.aspx
1449 * uses a bitmask, bit 0 is 'a:'
1450 * _chdrive - http://msdn2.microsoft.com/en-us/library/0d1409hb.aspx
1451 * _getdcwd - http://msdn2.microsoft.com/en-us/library/7t2zk3s4.aspx
1452 * take an int, 1 is 'a:'.
1454 * Because of this, we need to do some math. Subtract 1 to convert from
1455 * _chdrive/_getdcwd format to _getdrives drive numbering.
1456 * Shift left x bits to convert from integer indexing to bitfield indexing.
1457 * And of course, we need to find out if the drive is in the bitmask.
1459 * If we're really unlucky, we can still lose, but only if the user
1460 * manages to eject the drive between our call to _getdrives() and
1461 * our *calls* to _wgetdcwd.
1463 if (!((1 << (drive
- 1)) & _getdrives())) {
1464 return NS_ERROR_FILE_INVALID_PATH
;
1466 if (!_wgetdcwd(drive
, pcwd
, MAX_PATH
)) {
1467 pcwd
= _wgetdcwd(drive
, 0, 0);
1470 return NS_ERROR_OUT_OF_MEMORY
;
1472 nsAutoString
currentDir(pcwd
);
1477 if (currentDir
.Last() == '\\') {
1478 path
.Replace(0, 2, currentDir
);
1480 path
.Replace(0, 2, currentDir
+ u
"\\"_ns
);
1484 MOZ_ASSERT(0 < rootIdx
&& rootIdx
< (int32_t)path
.Length(),
1485 "rootIdx is invalid");
1486 MOZ_ASSERT(path
.CharAt(rootIdx
) == '\\', "rootIdx is invalid");
1488 // if there is nothing following the root path then it is already normalized
1489 if (rootIdx
+ 1 == (int32_t)path
.Length()) {
1494 const char16_t
* pathBuffer
= path
.get(); // simplify access to the buffer
1495 mWorkingPath
.SetCapacity(path
.Length()); // it won't ever grow longer
1496 mWorkingPath
.Assign(pathBuffer
, rootIdx
);
1498 // Normalize the path components. The actions taken are:
1500 // "\\" condense to single backslash
1501 // "." remove from path
1502 // ".." up a directory
1503 // "..." remove from path (any number of dots > 2)
1505 // The last form is something that Windows 95 and 98 supported and
1506 // is a shortcut for changing up multiple directories. Windows XP
1507 // and ilk ignore it in a path, as is done here.
1508 int32_t len
, begin
, end
= rootIdx
;
1509 while (end
< (int32_t)path
.Length()) {
1510 // find the current segment (text between the backslashes) to
1511 // be examined, this will set the following variables:
1512 // begin == index of first char in segment
1513 // end == index 1 char after last char in segment
1514 // len == length of segment
1516 end
= path
.FindChar('\\', begin
);
1517 if (end
== kNotFound
) {
1518 end
= path
.Length();
1522 // ignore double backslashes
1527 // len != 0, and interesting paths always begin with a dot
1528 if (pathBuffer
[begin
] == '.') {
1529 // ignore single dots
1534 // handle multiple dots
1535 if (len
>= 2 && pathBuffer
[begin
+ 1] == L
'.') {
1536 // back up a path component on double dot
1538 int32_t prev
= mWorkingPath
.RFindChar('\\');
1539 if (prev
>= rootIdx
) {
1540 mWorkingPath
.Truncate(prev
);
1545 // length is > 2 and the first two characters are dots.
1546 // if the rest of the string is dots, then ignore it.
1548 for (; idx
>= 2; --idx
) {
1549 if (pathBuffer
[begin
+ idx
] != L
'.') {
1554 // this is true if the loop above didn't break
1555 // and all characters in this segment are dots.
1562 // add the current component to the path, including the preceding backslash
1563 mWorkingPath
.Append(pathBuffer
+ begin
- 1, len
+ 1);
1566 // kill trailing dots and spaces.
1567 int32_t filePathLen
= mWorkingPath
.Length() - 1;
1568 while (filePathLen
> 0 && (mWorkingPath
[filePathLen
] == L
' ' ||
1569 mWorkingPath
[filePathLen
] == L
'.')) {
1570 mWorkingPath
.Truncate(filePathLen
--);
1578 nsLocalFile::GetLeafName(nsAString
& aLeafName
) {
1579 aLeafName
.Truncate();
1581 if (mWorkingPath
.IsEmpty()) {
1582 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1585 int32_t offset
= mWorkingPath
.RFindChar(L
'\\');
1587 // if the working path is just a node without any lashes.
1588 if (offset
== kNotFound
) {
1589 aLeafName
= mWorkingPath
;
1591 aLeafName
= Substring(mWorkingPath
, offset
+ 1);
1598 nsLocalFile::SetLeafName(const nsAString
& aLeafName
) {
1601 if (mWorkingPath
.IsEmpty()) {
1602 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1605 // cannot use nsCString::RFindChar() due to 0x5c problem
1606 int32_t offset
= mWorkingPath
.RFindChar(L
'\\');
1609 newDir
= Substring(mWorkingPath
, 0, offset
+ 1) + aLeafName
;
1611 newDir
= mWorkingPath
+ aLeafName
;
1613 if (IsSpecialNTFSPath(newDir
)) {
1614 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1617 mWorkingPath
.Assign(newDir
);
1623 nsLocalFile::GetDisplayName(nsAString
& aLeafName
) {
1624 aLeafName
.Truncate();
1626 if (mWorkingPath
.IsEmpty()) {
1627 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
1629 SHFILEINFOW sfi
= {};
1630 DWORD_PTR result
= ::SHGetFileInfoW(mWorkingPath
.get(), 0, &sfi
, sizeof(sfi
),
1632 // If we found a display name, return that:
1634 aLeafName
.Assign(sfi
.szDisplayName
);
1637 // Nope - fall back to the regular leaf name.
1638 return GetLeafName(aLeafName
);
1642 nsLocalFile::GetPath(nsAString
& aResult
) {
1644 mUseDOSDevicePathSyntax
,
1645 !FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath
));
1646 aResult
= mWorkingPath
;
1651 nsLocalFile::GetCanonicalPath(nsAString
& aResult
) {
1653 aResult
.Assign(mShortWorkingPath
);
1663 nsLocalFile::GetVersionInfoField(const char* aField
, nsAString
& aResult
) {
1664 nsresult rv
= NS_ERROR_FAILURE
;
1666 const WCHAR
* path
= mWorkingPath
.get();
1669 DWORD size
= ::GetFileVersionInfoSizeW(path
, &dummy
);
1674 void* ver
= moz_xcalloc(size
, 1);
1675 if (::GetFileVersionInfoW(path
, 0, size
, ver
)) {
1676 LANGANDCODEPAGE
* translate
= nullptr;
1678 BOOL queryResult
= ::VerQueryValueW(ver
, L
"\\VarFileInfo\\Translation",
1679 (void**)&translate
, &pageCount
);
1680 if (queryResult
&& translate
) {
1681 for (int32_t i
= 0; i
< 2; ++i
) {
1682 wchar_t subBlock
[MAX_PATH
];
1683 _snwprintf(subBlock
, MAX_PATH
, L
"\\StringFileInfo\\%04x%04x\\%S",
1684 (i
== 0 ? translate
[0].wLanguage
: ::GetUserDefaultLangID()),
1685 translate
[0].wCodePage
, aField
);
1686 subBlock
[MAX_PATH
- 1] = 0;
1687 LPVOID value
= nullptr;
1689 queryResult
= ::VerQueryValueW(ver
, subBlock
, &value
, &size
);
1690 if (queryResult
&& value
) {
1691 aResult
.Assign(static_cast<char16_t
*>(value
));
1692 if (!aResult
.IsEmpty()) {
1706 nsLocalFile::OpenNSPRFileDescShareDelete(int32_t aFlags
, int32_t aMode
,
1707 PRFileDesc
** aResult
) {
1708 nsresult rv
= OpenNSPRFileDescMaybeShareDelete(aFlags
, aMode
, true, aResult
);
1709 if (NS_FAILED(rv
)) {
1717 * Determines if the drive type for the specified file is rmeote or local.
1719 * @param path The path of the file to check
1720 * @param remote Out parameter, on function success holds true if the specified
1721 * file path is remote, or false if the file path is local.
1722 * @return true on success. The return value implies absolutely nothing about
1723 * wether the file is local or remote.
1725 static bool IsRemoteFilePath(LPCWSTR aPath
, bool& aRemote
) {
1726 // Obtain the parent directory path and make sure it ends with
1727 // a trailing backslash.
1728 WCHAR dirPath
[MAX_PATH
+ 1] = {0};
1729 wcsncpy(dirPath
, aPath
, MAX_PATH
);
1730 if (!PathRemoveFileSpecW(dirPath
)) {
1733 size_t len
= wcslen(dirPath
);
1734 // In case the dirPath holds exaclty MAX_PATH and remains unchanged, we
1735 // recheck the required length here since we need to terminate it with
1737 if (len
>= MAX_PATH
) {
1741 dirPath
[len
] = L
'\\';
1742 dirPath
[len
+ 1] = L
'\0';
1743 UINT driveType
= GetDriveTypeW(dirPath
);
1744 aRemote
= driveType
== DRIVE_REMOTE
;
1748 nsresult
nsLocalFile::CopySingleFile(nsIFile
* aSourceFile
, nsIFile
* aDestParent
,
1749 const nsAString
& aNewName
,
1750 uint32_t aOptions
) {
1751 nsresult rv
= NS_OK
;
1752 nsAutoString filePath
;
1754 bool move
= aOptions
& (Move
| Rename
);
1756 // get the path that we are going to copy to.
1757 // Since windows does not know how to auto
1758 // resolve shortcuts, we must work with the
1760 nsAutoString destPath
;
1761 rv
= aDestParent
->GetTarget(destPath
);
1762 if (NS_FAILED(rv
)) {
1766 destPath
.Append('\\');
1768 if (aNewName
.IsEmpty()) {
1769 nsAutoString aFileName
;
1770 aSourceFile
->GetLeafName(aFileName
);
1771 destPath
.Append(aFileName
);
1773 destPath
.Append(aNewName
);
1776 if (aOptions
& FollowSymlinks
) {
1777 rv
= aSourceFile
->GetTarget(filePath
);
1778 if (filePath
.IsEmpty()) {
1779 rv
= aSourceFile
->GetPath(filePath
);
1782 rv
= aSourceFile
->GetPath(filePath
);
1785 if (NS_FAILED(rv
)) {
1790 nsCOMPtr
<nsILocalFileWin
> srcWinFile
= do_QueryInterface(aSourceFile
);
1791 MOZ_ASSERT(srcWinFile
);
1793 bool srcUseDOSDevicePathSyntax
;
1794 srcWinFile
->GetUseDOSDevicePathSyntax(&srcUseDOSDevicePathSyntax
);
1796 nsCOMPtr
<nsILocalFileWin
> destWinFile
= do_QueryInterface(aDestParent
);
1797 MOZ_ASSERT(destWinFile
);
1799 bool destUseDOSDevicePathSyntax
;
1800 destWinFile
->GetUseDOSDevicePathSyntax(&destUseDOSDevicePathSyntax
);
1802 MOZ_ASSERT(srcUseDOSDevicePathSyntax
== destUseDOSDevicePathSyntax
,
1803 "Copy or Move files with different values for "
1804 "useDOSDevicePathSyntax would fail");
1807 if (FilePreferences::IsBlockedUNCPath(destPath
)) {
1808 return NS_ERROR_FILE_ACCESS_DENIED
;
1813 copyOK
= ::MoveFileExW(filePath
.get(), destPath
.get(),
1814 MOVEFILE_REPLACE_EXISTING
);
1817 // If we either failed to move the file, or this is a copy, try copying:
1818 if (!copyOK
&& (!move
|| GetLastError() == ERROR_NOT_SAME_DEVICE
)) {
1819 // Failed renames here should just return access denied.
1820 if (move
&& (aOptions
& Rename
)) {
1821 return NS_ERROR_FILE_ACCESS_DENIED
;
1824 // Pass the flag COPY_FILE_NO_BUFFERING to CopyFileEx as we may be copying
1825 // to a SMBV2 remote drive. Without this parameter subsequent append mode
1826 // file writes can cause the resultant file to become corrupt. We only need
1827 // to do this if the major version of Windows is > 5(Only Windows Vista and
1828 // above can support SMBV2). With a 7200RPM hard drive: Copying a 1KB file
1829 // with COPY_FILE_NO_BUFFERING takes about 30-60ms. Copying a 1KB file
1830 // without COPY_FILE_NO_BUFFERING takes < 1ms. So we only use
1831 // COPY_FILE_NO_BUFFERING when we have a remote drive.
1832 DWORD dwCopyFlags
= COPY_FILE_ALLOW_DECRYPTED_DESTINATION
;
1833 bool path1Remote
, path2Remote
;
1834 if (!IsRemoteFilePath(filePath
.get(), path1Remote
) ||
1835 !IsRemoteFilePath(destPath
.get(), path2Remote
) || path1Remote
||
1837 dwCopyFlags
|= COPY_FILE_NO_BUFFERING
;
1840 copyOK
= ::CopyFileExW(filePath
.get(), destPath
.get(), nullptr, nullptr,
1841 nullptr, dwCopyFlags
);
1842 // On Windows 10, copying without buffering has started failing, so try
1843 // with buffering...
1844 if (!copyOK
&& (dwCopyFlags
& COPY_FILE_NO_BUFFERING
) &&
1845 GetLastError() == ERROR_INVALID_PARAMETER
) {
1846 dwCopyFlags
&= ~COPY_FILE_NO_BUFFERING
;
1847 copyOK
= ::CopyFileExW(filePath
.get(), destPath
.get(), nullptr, nullptr,
1848 nullptr, dwCopyFlags
);
1851 if (move
&& copyOK
) {
1852 DeleteFileW(filePath
.get());
1856 if (!copyOK
) { // CopyFileEx and MoveFileEx return zero at failure.
1857 rv
= ConvertWinError(GetLastError());
1858 } else if (move
&& !(aOptions
& SkipNtfsAclReset
)) {
1859 // Set security permissions to inherit from parent.
1860 // Note: propagates to all children: slow for big file trees
1861 PACL pOldDACL
= nullptr;
1862 PSECURITY_DESCRIPTOR pSD
= nullptr;
1863 ::GetNamedSecurityInfoW((LPWSTR
)destPath
.get(), SE_FILE_OBJECT
,
1864 DACL_SECURITY_INFORMATION
, nullptr, nullptr,
1865 &pOldDACL
, nullptr, &pSD
);
1866 UniquePtr
<VOID
, LocalFreeDeleter
> autoFreeSecDesc(pSD
);
1868 // Test the current DACL, if we find one that is inherited then we can
1869 // skip the reset. This avoids a request for SeTcbPrivilege, which can
1870 // cause a lot of audit events if enabled (Bug 1816694).
1871 bool inherited
= false;
1872 for (DWORD i
= 0; i
< pOldDACL
->AceCount
; ++i
) {
1873 VOID
* pAce
= nullptr;
1874 if (::GetAce(pOldDACL
, i
, &pAce
) &&
1875 static_cast<PACE_HEADER
>(pAce
)->AceFlags
& INHERITED_ACE
) {
1882 ::SetNamedSecurityInfoW(
1883 (LPWSTR
)destPath
.get(), SE_FILE_OBJECT
,
1884 DACL_SECURITY_INFORMATION
| UNPROTECTED_DACL_SECURITY_INFORMATION
,
1885 nullptr, nullptr, pOldDACL
, nullptr);
1893 nsresult
nsLocalFile::CopyMove(nsIFile
* aParentDir
, const nsAString
& aNewName
,
1894 uint32_t aOptions
) {
1895 bool move
= aOptions
& (Move
| Rename
);
1896 bool followSymlinks
= aOptions
& FollowSymlinks
;
1897 // If we're not provided with a new parent, we're copying or moving to
1898 // another file in the same directory and can safely skip checking if the
1899 // destination directory exists:
1900 bool targetInSameDirectory
= !aParentDir
;
1902 nsCOMPtr
<nsIFile
> newParentDir
= aParentDir
;
1903 // check to see if this exists, otherwise return an error.
1904 // we will check this by resolving. If the user wants us
1905 // to follow links, then we are talking about the target,
1906 // hence we can use the |FollowSymlinks| option.
1907 nsresult rv
= ResolveAndStat();
1908 if (NS_FAILED(rv
)) {
1912 if (!newParentDir
) {
1913 // no parent was specified. We must rename.
1914 if (aNewName
.IsEmpty()) {
1915 return NS_ERROR_INVALID_ARG
;
1918 rv
= GetParent(getter_AddRefs(newParentDir
));
1919 if (NS_FAILED(rv
)) {
1924 if (!newParentDir
) {
1925 return NS_ERROR_FILE_DESTINATION_NOT_DIR
;
1928 if (!targetInSameDirectory
) {
1929 // make sure it exists and is a directory. Create it if not there.
1930 bool exists
= false;
1931 rv
= newParentDir
->Exists(&exists
);
1932 if (NS_FAILED(rv
)) {
1937 rv
= newParentDir
->Create(DIRECTORY_TYPE
,
1938 0644); // TODO, what permissions should we use
1939 if (NS_FAILED(rv
)) {
1944 rv
= newParentDir
->IsDirectory(&isDir
);
1945 if (NS_FAILED(rv
)) {
1950 if (followSymlinks
) {
1951 bool isLink
= false;
1952 rv
= newParentDir
->IsSymlink(&isLink
);
1953 if (NS_FAILED(rv
)) {
1958 nsAutoString target
;
1959 rv
= newParentDir
->GetTarget(target
);
1960 if (NS_FAILED(rv
)) {
1964 nsCOMPtr
<nsIFile
> realDest
= new nsLocalFile();
1965 rv
= realDest
->InitWithPath(target
);
1966 if (NS_FAILED(rv
)) {
1970 return CopyMove(realDest
, aNewName
, aOptions
);
1973 return NS_ERROR_FILE_DESTINATION_NOT_DIR
;
1979 // Try different ways to move/copy files/directories
1983 rv
= IsDirectory(&isDir
);
1984 if (NS_FAILED(rv
)) {
1988 bool isSymlink
= false;
1989 rv
= IsSymlink(&isSymlink
);
1990 if (NS_FAILED(rv
)) {
1994 // Try to move the file or directory, or try to copy a single file (or
1995 // non-followed symlink)
1996 if (move
|| !isDir
|| (isSymlink
&& !followSymlinks
)) {
1997 // Copy/Move single file, or move a directory
1999 aOptions
|= SkipNtfsAclReset
;
2001 rv
= CopySingleFile(this, newParentDir
, aNewName
, aOptions
);
2002 done
= NS_SUCCEEDED(rv
);
2003 // If we are moving a directory and that fails, fallback on directory
2004 // enumeration. See bug 231300 for details.
2005 if (!done
&& !(move
&& isDir
)) {
2010 // Not able to copy or move directly, so enumerate it
2012 // create a new target destination in the new parentDir;
2013 nsCOMPtr
<nsIFile
> target
;
2014 rv
= newParentDir
->Clone(getter_AddRefs(target
));
2015 if (NS_FAILED(rv
)) {
2019 nsAutoString allocatedNewName
;
2020 if (aNewName
.IsEmpty()) {
2021 bool isLink
= false;
2022 rv
= IsSymlink(&isLink
);
2023 if (NS_FAILED(rv
)) {
2029 rv
= GetTarget(temp
);
2030 if (NS_FAILED(rv
)) {
2034 int32_t offset
= temp
.RFindChar(L
'\\');
2035 if (offset
== kNotFound
) {
2036 allocatedNewName
= temp
;
2038 allocatedNewName
= Substring(temp
, offset
+ 1);
2041 GetLeafName(allocatedNewName
); // this should be the leaf name of the
2044 allocatedNewName
= aNewName
;
2047 rv
= target
->Append(allocatedNewName
);
2048 if (NS_FAILED(rv
)) {
2052 allocatedNewName
.Truncate();
2054 bool exists
= false;
2055 // check if the destination directory already exists
2056 rv
= target
->Exists(&exists
);
2057 if (NS_FAILED(rv
)) {
2062 // if the destination directory cannot be created, return an error
2063 rv
= target
->Create(DIRECTORY_TYPE
,
2064 0644); // TODO, what permissions should we use
2065 if (NS_FAILED(rv
)) {
2069 // check if the destination directory is writable and empty
2070 bool isWritable
= false;
2071 rv
= target
->IsWritable(&isWritable
);
2072 if (NS_FAILED(rv
)) {
2077 return NS_ERROR_FILE_ACCESS_DENIED
;
2080 nsCOMPtr
<nsIDirectoryEnumerator
> targetIterator
;
2081 rv
= target
->GetDirectoryEntries(getter_AddRefs(targetIterator
));
2082 if (NS_FAILED(rv
)) {
2087 targetIterator
->HasMoreElements(&more
);
2088 // return error if target directory is not empty
2090 return NS_ERROR_FILE_DIR_NOT_EMPTY
;
2094 RefPtr
<nsDirEnumerator
> dirEnum
= new nsDirEnumerator();
2096 rv
= dirEnum
->Init(this);
2097 if (NS_FAILED(rv
)) {
2098 NS_WARNING("dirEnum initialization failed");
2102 nsCOMPtr
<nsIFile
> file
;
2103 while (NS_SUCCEEDED(dirEnum
->GetNextFile(getter_AddRefs(file
))) && file
) {
2105 rv
= file
->IsDirectory(&isDir
);
2106 if (NS_FAILED(rv
)) {
2110 bool isLink
= false;
2111 rv
= file
->IsSymlink(&isLink
);
2112 if (NS_FAILED(rv
)) {
2117 if (followSymlinks
) {
2118 return NS_ERROR_FAILURE
;
2121 rv
= file
->MoveTo(target
, u
""_ns
);
2122 if (NS_FAILED(rv
)) {
2126 if (followSymlinks
) {
2127 rv
= file
->CopyToFollowingLinks(target
, u
""_ns
);
2129 rv
= file
->CopyTo(target
, u
""_ns
);
2131 if (NS_FAILED(rv
)) {
2136 // we've finished moving all the children of this directory
2137 // in the new directory. so now delete the directory
2138 // note, we don't need to do a recursive delete.
2139 // MoveTo() is recursive. At this point,
2140 // we've already moved the children of the current folder
2141 // to the new location. nothing should be left in the folder.
2143 rv
= Remove(false /* recursive */);
2144 if (NS_FAILED(rv
)) {
2150 // If we moved, we want to adjust this.
2154 nsAutoString newParentPath
;
2155 newParentDir
->GetPath(newParentPath
);
2157 if (newParentPath
.IsEmpty()) {
2158 return NS_ERROR_FAILURE
;
2161 if (aNewName
.IsEmpty()) {
2162 nsAutoString aFileName
;
2163 GetLeafName(aFileName
);
2165 InitWithPath(newParentPath
);
2168 InitWithPath(newParentPath
);
2177 nsLocalFile::CopyTo(nsIFile
* aNewParentDir
, const nsAString
& aNewName
) {
2178 return CopyMove(aNewParentDir
, aNewName
, 0);
2182 nsLocalFile::CopyToFollowingLinks(nsIFile
* aNewParentDir
,
2183 const nsAString
& aNewName
) {
2184 return CopyMove(aNewParentDir
, aNewName
, FollowSymlinks
);
2188 nsLocalFile::MoveTo(nsIFile
* aNewParentDir
, const nsAString
& aNewName
) {
2189 return CopyMove(aNewParentDir
, aNewName
, Move
);
2193 nsLocalFile::MoveToFollowingLinks(nsIFile
* aNewParentDir
,
2194 const nsAString
& aNewName
) {
2195 return CopyMove(aNewParentDir
, aNewName
, Move
| FollowSymlinks
);
2199 nsLocalFile::RenameTo(nsIFile
* aNewParentDir
, const nsAString
& aNewName
) {
2200 // If we're not provided with a new parent, we're renaming inside one and
2201 // the same directory and can safely skip checking if the destination
2202 // directory exists:
2203 bool targetInSameDirectory
= !aNewParentDir
;
2205 nsCOMPtr
<nsIFile
> targetParentDir
= aNewParentDir
;
2206 // check to see if this exists, otherwise return an error.
2207 // we will check this by resolving. If the user wants us
2208 // to follow links, then we are talking about the target,
2209 // hence we can use the |followSymlinks| parameter.
2210 nsresult rv
= ResolveAndStat();
2211 if (NS_FAILED(rv
)) {
2215 if (!targetParentDir
) {
2216 // no parent was specified. We must rename.
2217 if (aNewName
.IsEmpty()) {
2218 return NS_ERROR_INVALID_ARG
;
2220 rv
= GetParent(getter_AddRefs(targetParentDir
));
2221 if (NS_FAILED(rv
)) {
2226 if (!targetParentDir
) {
2227 return NS_ERROR_FILE_DESTINATION_NOT_DIR
;
2230 if (!targetInSameDirectory
) {
2231 // make sure it exists and is a directory. Create it if not there.
2232 bool exists
= false;
2233 rv
= targetParentDir
->Exists(&exists
);
2234 if (NS_FAILED(rv
)) {
2239 rv
= targetParentDir
->Create(DIRECTORY_TYPE
, 0644);
2240 if (NS_FAILED(rv
)) {
2245 rv
= targetParentDir
->IsDirectory(&isDir
);
2246 if (NS_FAILED(rv
)) {
2250 return NS_ERROR_FILE_DESTINATION_NOT_DIR
;
2255 uint32_t options
= Rename
;
2256 if (!aNewParentDir
) {
2257 options
|= SkipNtfsAclReset
;
2259 // Move single file, or move a directory
2260 return CopySingleFile(this, targetParentDir
, aNewName
, options
);
2264 nsLocalFile::RenameToNative(nsIFile
* aNewParentDir
,
2265 const nsACString
& aNewName
) {
2267 nsresult rv
= NS_CopyNativeToUnicode(aNewName
, tmp
);
2268 if (NS_SUCCEEDED(rv
)) {
2269 return RenameTo(aNewParentDir
, tmp
);
2276 nsLocalFile::Load(PRLibrary
** aResult
) {
2277 // Check we are correctly initialized.
2278 CHECK_mWorkingPath();
2279 if (NS_WARN_IF(!aResult
)) {
2280 return NS_ERROR_INVALID_ARG
;
2283 #ifdef NS_BUILD_REFCNT_LOGGING
2284 nsTraceRefcnt::SetActivityIsLegal(false);
2288 libSpec
.value
.pathname_u
= mWorkingPath
.get();
2289 libSpec
.type
= PR_LibSpec_PathnameU
;
2290 *aResult
= PR_LoadLibraryWithFlags(libSpec
, 0);
2292 #ifdef NS_BUILD_REFCNT_LOGGING
2293 nsTraceRefcnt::SetActivityIsLegal(true);
2299 return NS_ERROR_NULL_POINTER
;
2303 nsLocalFile::Remove(bool aRecursive
, uint32_t* aRemoveCount
) {
2306 // if the working path points to a shortcut, then we will only
2307 // delete the shortcut itself. even if the shortcut points to
2308 // a directory, we will not recurse into that directory or
2309 // delete that directory itself. likewise, if the shortcut
2310 // points to a normal file, we will not delete the real file.
2311 // this is done to be consistent with the other platforms that
2312 // behave this way. we do this even if the followLinks attribute
2313 // is set to true. this helps protect against misuse that could
2314 // lead to security bugs (e.g., bug 210588).
2316 // Since shortcut files are no longer permitted to be used as unix-like
2317 // symlinks interspersed in the path (e.g. "c:/file.lnk/foo/bar.txt")
2318 // this processing is a lot simpler. Even if the shortcut file is
2319 // pointing to a directory, only the mWorkingPath value is used and so
2320 // only the shortcut file will be deleted.
2322 // Check we are correctly initialized.
2323 CHECK_mWorkingPath();
2325 nsresult rv
= NS_OK
;
2327 bool isLink
= false;
2328 rv
= IsSymlink(&isLink
);
2329 if (NS_FAILED(rv
)) {
2333 // only check to see if we have a directory if it isn't a link
2336 rv
= IsDirectory(&isDir
);
2337 if (NS_FAILED(rv
)) {
2344 // WARNING: neither the `SHFileOperation` nor `IFileOperation` APIs are
2345 // appropriate here as neither handle long path names, i.e. paths prefixed
2346 // with `\\?\` or longer than 260 characters on Windows 10+ system with
2347 // long paths enabled.
2349 RefPtr
<nsDirEnumerator
> dirEnum
= new nsDirEnumerator();
2351 rv
= dirEnum
->Init(this);
2352 if (NS_FAILED(rv
)) {
2356 // XXX: We are ignoring the result of the removal here while
2357 // nsLocalFileUnix does not. We should align the behavior. (bug 1779696)
2358 nsCOMPtr
<nsIFile
> file
;
2359 while (NS_SUCCEEDED(dirEnum
->GetNextFile(getter_AddRefs(file
))) && file
) {
2360 file
->Remove(aRecursive
, aRemoveCount
);
2363 if (RemoveDirectoryW(mWorkingPath
.get()) == 0) {
2364 return ConvertWinError(GetLastError());
2367 if (DeleteFileW(mWorkingPath
.get()) == 0) {
2368 return ConvertWinError(GetLastError());
2380 nsresult
nsLocalFile::GetDateImpl(PRTime
* aTime
,
2381 nsLocalFile::TimeField aTimeField
,
2382 bool aFollowLinks
) {
2383 // Check we are correctly initialized.
2384 CHECK_mWorkingPath();
2386 if (NS_WARN_IF(!aTime
)) {
2387 return NS_ERROR_INVALID_ARG
;
2390 FileInfo symlinkInfo
{};
2394 MOZ_TRY(ResolveAndStat());
2397 MOZ_TRY(GetFileInfo(mWorkingPath
, &symlinkInfo
));
2398 pInfo
= &symlinkInfo
;
2401 switch (aTimeField
) {
2402 case TimeField::AccessedTime
:
2403 *aTime
= pInfo
->accessTime
/ PR_USEC_PER_MSEC
;
2406 case TimeField::ModifiedTime
:
2407 *aTime
= pInfo
->modifyTime
/ PR_USEC_PER_MSEC
;
2411 MOZ_CRASH("Unknown time field");
2418 nsLocalFile::GetLastAccessedTime(PRTime
* aLastAccessedTime
) {
2419 return GetDateImpl(aLastAccessedTime
, TimeField::AccessedTime
,
2420 /* aFollowSymlinks = */ true);
2424 nsLocalFile::GetLastAccessedTimeOfLink(PRTime
* aLastAccessedTime
) {
2425 return GetDateImpl(aLastAccessedTime
, TimeField::AccessedTime
,
2426 /* aFollowSymlinks = */ false);
2430 nsLocalFile::SetLastAccessedTime(PRTime aLastAccessedTime
) {
2431 return SetDateImpl(aLastAccessedTime
, TimeField::AccessedTime
);
2435 nsLocalFile::SetLastAccessedTimeOfLink(PRTime aLastAccessedTime
) {
2436 return SetLastAccessedTime(aLastAccessedTime
);
2440 nsLocalFile::GetLastModifiedTime(PRTime
* aLastModifiedTime
) {
2441 return GetDateImpl(aLastModifiedTime
, TimeField::ModifiedTime
,
2442 /* aFollowSymlinks = */ true);
2446 nsLocalFile::GetLastModifiedTimeOfLink(PRTime
* aLastModifiedTime
) {
2447 return GetDateImpl(aLastModifiedTime
, TimeField::ModifiedTime
,
2448 /* aFollowSymlinks = */ false);
2452 nsLocalFile::SetLastModifiedTime(PRTime aLastModifiedTime
) {
2453 return SetDateImpl(aLastModifiedTime
, TimeField::ModifiedTime
);
2457 nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModifiedTime
) {
2458 return SetLastModifiedTime(aLastModifiedTime
);
2462 nsLocalFile::GetCreationTime(PRTime
* aCreationTime
) {
2463 CHECK_mWorkingPath();
2465 if (NS_WARN_IF(!aCreationTime
)) {
2466 return NS_ERROR_INVALID_ARG
;
2469 nsresult rv
= ResolveAndStat();
2470 NS_ENSURE_SUCCESS(rv
, rv
);
2472 *aCreationTime
= mFileInfo
.creationTime
/ PR_USEC_PER_MSEC
;
2478 nsLocalFile::GetCreationTimeOfLink(PRTime
* aCreationTime
) {
2479 CHECK_mWorkingPath();
2481 if (NS_WARN_IF(!aCreationTime
)) {
2482 return NS_ERROR_INVALID_ARG
;
2486 nsresult rv
= GetFileInfo(mWorkingPath
, &info
);
2487 NS_ENSURE_SUCCESS(rv
, rv
);
2489 *aCreationTime
= info
.creationTime
/ PR_USEC_PER_MSEC
;
2494 nsresult
nsLocalFile::SetDateImpl(PRTime aTime
,
2495 nsLocalFile::TimeField aTimeField
) {
2496 // Check we are correctly initialized.
2497 CHECK_mWorkingPath();
2499 // The FILE_FLAG_BACKUP_SEMANTICS is required in order to change the
2500 // modification time for directories.
2502 ::CreateFileW(mWorkingPath
.get(), // pointer to name of the file
2503 GENERIC_WRITE
, // access (write) mode
2505 nullptr, // pointer to security attributes
2506 OPEN_EXISTING
, // how to create
2507 FILE_FLAG_BACKUP_SEMANTICS
, // file attributes
2510 if (file
== INVALID_HANDLE_VALUE
) {
2511 return ConvertWinError(GetLastError());
2516 PRExplodedTime pret
;
2519 aTime
= PR_Now() / PR_USEC_PER_MSEC
;
2522 // PR_ExplodeTime expects usecs...
2523 PR_ExplodeTime(aTime
* PR_USEC_PER_MSEC
, PR_GMTParameters
, &pret
);
2524 st
.wYear
= pret
.tm_year
;
2526 pret
.tm_month
+ 1; // Convert start offset -- Win32: Jan=1; NSPR: Jan=0
2527 st
.wDayOfWeek
= pret
.tm_wday
;
2528 st
.wDay
= pret
.tm_mday
;
2529 st
.wHour
= pret
.tm_hour
;
2530 st
.wMinute
= pret
.tm_min
;
2531 st
.wSecond
= pret
.tm_sec
;
2532 st
.wMilliseconds
= pret
.tm_usec
/ 1000;
2534 const FILETIME
* accessTime
= nullptr;
2535 const FILETIME
* modifiedTime
= nullptr;
2537 if (aTimeField
== TimeField::AccessedTime
) {
2543 nsresult rv
= NS_OK
;
2545 // if at least one of these fails...
2546 if (!(SystemTimeToFileTime(&st
, &ft
) != 0 &&
2547 SetFileTime(file
, nullptr, accessTime
, modifiedTime
) != 0)) {
2548 rv
= ConvertWinError(GetLastError());
2553 if (NS_SUCCEEDED(rv
)) {
2561 nsLocalFile::GetPermissions(uint32_t* aPermissions
) {
2562 if (NS_WARN_IF(!aPermissions
)) {
2563 return NS_ERROR_INVALID_ARG
;
2566 // get the permissions of the target as determined by mFollowSymlinks
2567 // If true, then this will be for the target of the shortcut file,
2568 // otherwise it will be for the shortcut file itself (i.e. the same
2569 // results as GetPermissionsOfLink)
2570 nsresult rv
= ResolveAndStat();
2571 if (NS_FAILED(rv
)) {
2575 bool isWritable
= false;
2576 rv
= IsWritable(&isWritable
);
2577 if (NS_FAILED(rv
)) {
2581 bool isExecutable
= false;
2582 rv
= IsExecutable(&isExecutable
);
2583 if (NS_FAILED(rv
)) {
2587 *aPermissions
= PR_IRUSR
| PR_IRGRP
| PR_IROTH
; // all read
2589 *aPermissions
|= PR_IWUSR
| PR_IWGRP
| PR_IWOTH
; // all write
2592 *aPermissions
|= PR_IXUSR
| PR_IXGRP
| PR_IXOTH
; // all execute
2599 nsLocalFile::GetPermissionsOfLink(uint32_t* aPermissions
) {
2600 // Check we are correctly initialized.
2601 CHECK_mWorkingPath();
2603 if (NS_WARN_IF(!aPermissions
)) {
2604 return NS_ERROR_INVALID_ARG
;
2607 // The caller is assumed to have already called IsSymlink
2608 // and to have found that this file is a link. It is not
2609 // possible for a link file to be executable.
2611 DWORD word
= ::GetFileAttributesW(mWorkingPath
.get());
2612 if (word
== INVALID_FILE_ATTRIBUTES
) {
2613 return NS_ERROR_FILE_INVALID_PATH
;
2616 bool isWritable
= !(word
& FILE_ATTRIBUTE_READONLY
);
2617 *aPermissions
= PR_IRUSR
| PR_IRGRP
| PR_IROTH
; // all read
2619 *aPermissions
|= PR_IWUSR
| PR_IWGRP
| PR_IWOTH
; // all write
2626 nsLocalFile::SetPermissions(uint32_t aPermissions
) {
2627 // Check we are correctly initialized.
2628 CHECK_mWorkingPath();
2630 // set the permissions of the target as determined by mFollowSymlinks
2631 // If true, then this will be for the target of the shortcut file,
2632 // otherwise it will be for the shortcut file itself (i.e. the same
2633 // results as SetPermissionsOfLink)
2634 nsresult rv
= Resolve();
2635 if (NS_FAILED(rv
)) {
2639 // windows only knows about the following permissions
2641 if (aPermissions
& (PR_IRUSR
| PR_IRGRP
| PR_IROTH
)) { // any read
2644 if (aPermissions
& (PR_IWUSR
| PR_IWGRP
| PR_IWOTH
)) { // any write
2648 if (_wchmod(mResolvedPath
.get(), mode
) == -1) {
2649 return NS_ERROR_FAILURE
;
2656 nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions
) {
2657 // The caller is assumed to have already called IsSymlink
2658 // and to have found that this file is a link.
2660 // windows only knows about the following permissions
2662 if (aPermissions
& (PR_IRUSR
| PR_IRGRP
| PR_IROTH
)) { // any read
2665 if (aPermissions
& (PR_IWUSR
| PR_IWGRP
| PR_IWOTH
)) { // any write
2669 if (_wchmod(mWorkingPath
.get(), mode
) == -1) {
2670 return NS_ERROR_FAILURE
;
2677 nsLocalFile::GetFileSize(int64_t* aFileSize
) {
2678 if (NS_WARN_IF(!aFileSize
)) {
2679 return NS_ERROR_INVALID_ARG
;
2682 nsresult rv
= ResolveAndStat();
2683 if (NS_FAILED(rv
)) {
2687 *aFileSize
= mFileInfo
.size
;
2692 nsLocalFile::GetFileSizeOfLink(int64_t* aFileSize
) {
2693 // Check we are correctly initialized.
2694 CHECK_mWorkingPath();
2696 if (NS_WARN_IF(!aFileSize
)) {
2697 return NS_ERROR_INVALID_ARG
;
2700 // The caller is assumed to have already called IsSymlink
2701 // and to have found that this file is a link.
2704 if (NS_FAILED(GetFileInfo(mWorkingPath
, &info
))) {
2705 return NS_ERROR_FILE_INVALID_PATH
;
2708 *aFileSize
= info
.size
;
2713 nsLocalFile::SetFileSize(int64_t aFileSize
) {
2714 // Check we are correctly initialized.
2715 CHECK_mWorkingPath();
2718 ::CreateFileW(mWorkingPath
.get(), // pointer to name of the file
2719 GENERIC_WRITE
, // access (write) mode
2720 FILE_SHARE_READ
, // share mode
2721 nullptr, // pointer to security attributes
2722 OPEN_EXISTING
, // how to create
2723 FILE_ATTRIBUTE_NORMAL
, // file attributes
2725 if (hFile
== INVALID_HANDLE_VALUE
) {
2726 return ConvertWinError(GetLastError());
2729 // seek the file pointer to the new, desired end of file
2730 // and then truncate the file at that position
2731 nsresult rv
= NS_ERROR_FAILURE
;
2732 LARGE_INTEGER distance
;
2733 distance
.QuadPart
= aFileSize
;
2734 if (SetFilePointerEx(hFile
, distance
, nullptr, FILE_BEGIN
) &&
2735 SetEndOfFile(hFile
)) {
2744 static nsresult
GetDiskSpaceAttributes(const nsString
& aResolvedPath
,
2745 int64_t* aFreeBytesAvailable
,
2746 int64_t* aTotalBytes
) {
2747 ULARGE_INTEGER liFreeBytesAvailableToCaller
;
2748 ULARGE_INTEGER liTotalNumberOfBytes
;
2749 if (::GetDiskFreeSpaceExW(aResolvedPath
.get(), &liFreeBytesAvailableToCaller
,
2750 &liTotalNumberOfBytes
, nullptr)) {
2751 *aFreeBytesAvailable
= liFreeBytesAvailableToCaller
.QuadPart
;
2752 *aTotalBytes
= liTotalNumberOfBytes
.QuadPart
;
2757 return ConvertWinError(::GetLastError());
2761 nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable
) {
2762 // Check we are correctly initialized.
2763 CHECK_mWorkingPath();
2765 if (NS_WARN_IF(!aDiskSpaceAvailable
)) {
2766 return NS_ERROR_INVALID_ARG
;
2769 *aDiskSpaceAvailable
= 0;
2771 nsresult rv
= ResolveAndStat();
2772 if (NS_FAILED(rv
)) {
2776 if (mFileInfo
.type
== PR_FILE_FILE
) {
2777 // Since GetDiskFreeSpaceExW works only on directories, use the parent
2778 // which must exist if we are a file.
2779 nsCOMPtr
<nsIFile
> parent
;
2780 rv
= GetParent(getter_AddRefs(parent
));
2781 NS_ENSURE_SUCCESS(rv
, rv
);
2782 return parent
->GetDiskSpaceAvailable(aDiskSpaceAvailable
);
2786 return GetDiskSpaceAttributes(mResolvedPath
, aDiskSpaceAvailable
, &dummy
);
2790 nsLocalFile::GetDiskCapacity(int64_t* aDiskCapacity
) {
2791 // Check we are correctly initialized.
2792 CHECK_mWorkingPath();
2794 if (NS_WARN_IF(!aDiskCapacity
)) {
2795 return NS_ERROR_INVALID_ARG
;
2798 nsresult rv
= ResolveAndStat();
2799 if (NS_FAILED(rv
)) {
2803 if (mFileInfo
.type
== PR_FILE_FILE
) {
2804 // Since GetDiskFreeSpaceExW works only on directories, use the parent.
2805 nsCOMPtr
<nsIFile
> parent
;
2806 if (NS_SUCCEEDED(GetParent(getter_AddRefs(parent
))) && parent
) {
2807 return parent
->GetDiskCapacity(aDiskCapacity
);
2812 return GetDiskSpaceAttributes(mResolvedPath
, &dummy
, aDiskCapacity
);
2816 nsLocalFile::GetParent(nsIFile
** aParent
) {
2817 // Check we are correctly initialized.
2818 CHECK_mWorkingPath();
2820 if (NS_WARN_IF(!aParent
)) {
2821 return NS_ERROR_INVALID_ARG
;
2824 // A two-character path must be a drive such as C:, so it has no parent
2825 if (mWorkingPath
.Length() == 2) {
2830 int32_t offset
= mWorkingPath
.RFindChar(char16_t('\\'));
2831 // adding this offset check that was removed in bug 241708 fixes mail
2832 // directories that aren't relative to/underneath the profile dir.
2833 // e.g., on a different drive. Before you remove them, please make
2834 // sure local mail directories that aren't underneath the profile dir work.
2835 if (offset
== kNotFound
) {
2836 return NS_ERROR_FILE_UNRECOGNIZED_PATH
;
2839 // A path of the form \\NAME is a top-level path and has no parent
2840 if (offset
== 1 && mWorkingPath
[0] == L
'\\') {
2845 nsAutoString
parentPath(mWorkingPath
);
2848 parentPath
.Truncate(offset
);
2850 parentPath
.AssignLiteral("\\\\.");
2853 nsCOMPtr
<nsIFile
> localFile
;
2854 nsresult rv
= NewLocalFile(parentPath
, mUseDOSDevicePathSyntax
,
2855 getter_AddRefs(localFile
));
2856 if (NS_FAILED(rv
)) {
2860 localFile
.forget(aParent
);
2865 nsLocalFile::Exists(bool* aResult
) {
2866 // Check we are correctly initialized.
2867 CHECK_mWorkingPath();
2869 if (NS_WARN_IF(!aResult
)) {
2870 return NS_ERROR_INVALID_ARG
;
2875 nsresult rv
= ResolveAndStat();
2876 *aResult
= NS_SUCCEEDED(rv
) || rv
== NS_ERROR_FILE_IS_LOCKED
;
2882 nsLocalFile::IsWritable(bool* aIsWritable
) {
2883 // Check we are correctly initialized.
2884 CHECK_mWorkingPath();
2886 // The read-only attribute on a FAT directory only means that it can't
2887 // be deleted. It is still possible to modify the contents of the directory.
2888 nsresult rv
= IsDirectory(aIsWritable
);
2889 if (rv
== NS_ERROR_FILE_ACCESS_DENIED
) {
2890 *aIsWritable
= true;
2892 } else if (rv
== NS_ERROR_FILE_IS_LOCKED
) {
2893 // If the file is normally allowed write access
2894 // we should still return that the file is writable.
2895 } else if (NS_FAILED(rv
)) {
2902 // writable if the file doesn't have the readonly attribute
2903 rv
= HasFileAttribute(FILE_ATTRIBUTE_READONLY
, aIsWritable
);
2904 if (rv
== NS_ERROR_FILE_ACCESS_DENIED
) {
2905 *aIsWritable
= false;
2907 } else if (rv
== NS_ERROR_FILE_IS_LOCKED
) {
2908 // If the file is normally allowed write access
2909 // we should still return that the file is writable.
2910 } else if (NS_FAILED(rv
)) {
2913 *aIsWritable
= !*aIsWritable
;
2915 // If the read only attribute is not set, check to make sure
2916 // we can open the file with write access.
2919 rv
= OpenFile(mResolvedPath
, PR_WRONLY
, 0, false, &file
);
2920 if (NS_SUCCEEDED(rv
)) {
2922 } else if (rv
== NS_ERROR_FILE_ACCESS_DENIED
) {
2923 *aIsWritable
= false;
2924 } else if (rv
== NS_ERROR_FILE_IS_LOCKED
) {
2925 // If it is locked and read only we would have
2926 // gotten access denied
2927 *aIsWritable
= true;
2936 nsLocalFile::IsReadable(bool* aResult
) {
2937 // Check we are correctly initialized.
2938 CHECK_mWorkingPath();
2940 if (NS_WARN_IF(!aResult
)) {
2941 return NS_ERROR_INVALID_ARG
;
2945 nsresult rv
= ResolveAndStat();
2946 if (NS_FAILED(rv
)) {
2954 nsresult
nsLocalFile::LookupExtensionIn(const char* const* aExtensionsArray
,
2955 size_t aArrayLength
, bool* aResult
) {
2956 // Check we are correctly initialized.
2957 CHECK_mWorkingPath();
2959 if (NS_WARN_IF(!aResult
)) {
2960 return NS_ERROR_INVALID_ARG
;
2966 // only files can be executables
2968 rv
= IsFile(&isFile
);
2969 if (NS_FAILED(rv
)) {
2976 // TODO: shouldn't we be checking mFollowSymlinks here?
2977 bool symLink
= false;
2978 rv
= IsSymlink(&symLink
);
2979 if (NS_FAILED(rv
)) {
2990 // kill trailing dots and spaces.
2991 int32_t filePathLen
= path
.Length() - 1;
2992 while (filePathLen
> 0 &&
2993 (path
[filePathLen
] == L
' ' || path
[filePathLen
] == L
'.')) {
2994 path
.Truncate(filePathLen
--);
2998 int32_t dotIdx
= path
.RFindChar(char16_t('.'));
2999 if (dotIdx
!= kNotFound
) {
3000 // Convert extension to lower case.
3001 char16_t
* p
= path
.BeginWriting();
3002 for (p
+= dotIdx
+ 1; *p
; ++p
) {
3003 *p
+= (*p
>= L
'A' && *p
<= L
'Z') ? 'a' - 'A' : 0;
3006 nsDependentSubstring ext
= Substring(path
, dotIdx
);
3007 for (size_t i
= 0; i
< aArrayLength
; ++i
) {
3008 if (ext
.EqualsASCII(aExtensionsArray
[i
])) {
3009 // Found a match. Set result and quit.
3020 nsLocalFile::IsExecutable(bool* aResult
) {
3021 return LookupExtensionIn(sExecutableExts
, std::size(sExecutableExts
),
3026 nsLocalFile::IsDirectory(bool* aResult
) {
3027 return HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY
, aResult
);
3031 nsLocalFile::IsFile(bool* aResult
) {
3032 nsresult rv
= HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY
, aResult
);
3033 if (NS_SUCCEEDED(rv
)) {
3034 *aResult
= !*aResult
;
3040 nsLocalFile::IsHidden(bool* aResult
) {
3041 return HasFileAttribute(FILE_ATTRIBUTE_HIDDEN
, aResult
);
3044 nsresult
nsLocalFile::HasFileAttribute(DWORD aFileAttrib
, bool* aResult
) {
3045 if (NS_WARN_IF(!aResult
)) {
3046 return NS_ERROR_INVALID_ARG
;
3049 nsresult rv
= Resolve();
3050 if (NS_FAILED(rv
)) {
3054 DWORD attributes
= GetFileAttributesW(mResolvedPath
.get());
3055 if (INVALID_FILE_ATTRIBUTES
== attributes
) {
3056 return ConvertWinError(GetLastError());
3059 *aResult
= ((attributes
& aFileAttrib
) != 0);
3064 nsLocalFile::IsSymlink(bool* aResult
) {
3065 // Check we are correctly initialized.
3066 CHECK_mWorkingPath();
3068 if (NS_WARN_IF(!aResult
)) {
3069 return NS_ERROR_INVALID_ARG
;
3072 // TODO: Implement symlink support
3078 nsLocalFile::IsSpecial(bool* aResult
) {
3079 return HasFileAttribute(FILE_ATTRIBUTE_SYSTEM
, aResult
);
3083 nsLocalFile::Equals(nsIFile
* aInFile
, bool* aResult
) {
3084 if (NS_WARN_IF(!aInFile
)) {
3085 return NS_ERROR_INVALID_ARG
;
3087 if (NS_WARN_IF(!aResult
)) {
3088 return NS_ERROR_INVALID_ARG
;
3091 nsCOMPtr
<nsILocalFileWin
> lf(do_QueryInterface(aInFile
));
3097 bool inUseDOSDevicePathSyntax
;
3098 lf
->GetUseDOSDevicePathSyntax(&inUseDOSDevicePathSyntax
);
3100 // If useDOSDevicePathSyntax are different remove the prefix from the one that
3101 // might have it. This is added because of Omnijar. It compares files from
3102 // different modules with itself.
3103 bool removePathPrefix
, removeInPathPrefix
;
3104 if (inUseDOSDevicePathSyntax
!= mUseDOSDevicePathSyntax
) {
3105 removeInPathPrefix
= inUseDOSDevicePathSyntax
;
3106 removePathPrefix
= mUseDOSDevicePathSyntax
;
3108 removePathPrefix
= removeInPathPrefix
= false;
3111 nsAutoString inFilePath
, workingPath
;
3112 aInFile
->GetPath(inFilePath
);
3113 workingPath
= mWorkingPath
;
3115 constexpr static auto equalPath
=
3116 [](nsAutoString
& workingPath
, nsAutoString
& inFilePath
,
3117 bool removePathPrefix
, bool removeInPathPrefix
) {
3118 if (removeInPathPrefix
&&
3119 StringBeginsWith(inFilePath
, kDevicePathSpecifier
)) {
3120 MOZ_ASSERT(!StringBeginsWith(workingPath
, kDevicePathSpecifier
));
3122 inFilePath
= Substring(inFilePath
, kDevicePathSpecifier
.Length());
3123 } else if (removePathPrefix
&&
3124 StringBeginsWith(workingPath
, kDevicePathSpecifier
)) {
3125 MOZ_ASSERT(!StringBeginsWith(inFilePath
, kDevicePathSpecifier
));
3127 workingPath
= Substring(workingPath
, kDevicePathSpecifier
.Length());
3130 return _wcsicmp(workingPath
.get(), inFilePath
.get()) == 0;
3133 if (equalPath(workingPath
, inFilePath
, removePathPrefix
,
3134 removeInPathPrefix
)) {
3140 lf
->GetCanonicalPath(inFilePath
);
3141 workingPath
= mShortWorkingPath
;
3143 equalPath(workingPath
, inFilePath
, removePathPrefix
, removeInPathPrefix
);
3149 nsLocalFile::Contains(nsIFile
* aInFile
, bool* aResult
) {
3150 // Check we are correctly initialized.
3151 CHECK_mWorkingPath();
3155 nsAutoString myFilePath
;
3156 if (NS_FAILED(GetTarget(myFilePath
))) {
3157 GetPath(myFilePath
);
3160 uint32_t myFilePathLen
= myFilePath
.Length();
3162 nsAutoString inFilePath
;
3163 if (NS_FAILED(aInFile
->GetTarget(inFilePath
))) {
3164 aInFile
->GetPath(inFilePath
);
3167 // Make sure that the |aInFile|'s path has a trailing separator.
3168 if (inFilePath
.Length() > myFilePathLen
&&
3169 inFilePath
[myFilePathLen
] == L
'\\') {
3170 if (_wcsnicmp(myFilePath
.get(), inFilePath
.get(), myFilePathLen
) == 0) {
3179 nsLocalFile::GetTarget(nsAString
& aResult
) {
3184 mUseDOSDevicePathSyntax
,
3185 !FilePreferences::StartsWithDiskDesignatorAndBackslash(mResolvedPath
));
3187 aResult
= mResolvedPath
;
3192 nsLocalFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator
** aEntries
) {
3195 *aEntries
= nullptr;
3196 if (mWorkingPath
.EqualsLiteral("\\\\.")) {
3197 RefPtr
<nsDriveEnumerator
> drives
=
3198 new nsDriveEnumerator(mUseDOSDevicePathSyntax
);
3199 rv
= drives
->Init();
3200 if (NS_FAILED(rv
)) {
3203 drives
.forget(aEntries
);
3207 RefPtr
<nsDirEnumerator
> dirEnum
= new nsDirEnumerator();
3208 rv
= dirEnum
->Init(this);
3209 if (NS_FAILED(rv
)) {
3213 dirEnum
.forget(aEntries
);
3219 nsLocalFile::GetPersistentDescriptor(nsACString
& aPersistentDescriptor
) {
3220 CopyUTF16toUTF8(mWorkingPath
, aPersistentDescriptor
);
3225 nsLocalFile::SetPersistentDescriptor(const nsACString
& aPersistentDescriptor
) {
3226 if (IsUtf8(aPersistentDescriptor
)) {
3227 return InitWithPath(NS_ConvertUTF8toUTF16(aPersistentDescriptor
));
3229 return InitWithNativePath(aPersistentDescriptor
);
3234 nsLocalFile::GetReadOnly(bool* aReadOnly
) {
3235 NS_ENSURE_ARG_POINTER(aReadOnly
);
3237 DWORD dwAttrs
= GetFileAttributesW(mWorkingPath
.get());
3238 if (dwAttrs
== INVALID_FILE_ATTRIBUTES
) {
3239 return NS_ERROR_FILE_INVALID_PATH
;
3242 *aReadOnly
= dwAttrs
& FILE_ATTRIBUTE_READONLY
;
3248 nsLocalFile::SetReadOnly(bool aReadOnly
) {
3249 DWORD dwAttrs
= GetFileAttributesW(mWorkingPath
.get());
3250 if (dwAttrs
== INVALID_FILE_ATTRIBUTES
) {
3251 return NS_ERROR_FILE_INVALID_PATH
;
3255 dwAttrs
|= FILE_ATTRIBUTE_READONLY
;
3257 dwAttrs
&= ~FILE_ATTRIBUTE_READONLY
;
3260 if (SetFileAttributesW(mWorkingPath
.get(), dwAttrs
) == 0) {
3261 return NS_ERROR_FAILURE
;
3268 nsLocalFile::GetUseDOSDevicePathSyntax(bool* aUseDOSDevicePathSyntax
) {
3269 MOZ_ASSERT(aUseDOSDevicePathSyntax
);
3271 *aUseDOSDevicePathSyntax
= mUseDOSDevicePathSyntax
;
3276 nsLocalFile::SetUseDOSDevicePathSyntax(bool aUseDOSDevicePathSyntax
) {
3277 if (mUseDOSDevicePathSyntax
== aUseDOSDevicePathSyntax
) {
3281 if (mUseDOSDevicePathSyntax
) {
3282 if (StringBeginsWith(mWorkingPath
, kDevicePathSpecifier
)) {
3284 // Remove the prefix
3285 mWorkingPath
= Substring(mWorkingPath
, kDevicePathSpecifier
.Length());
3288 if (FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath
)) {
3290 // Prepend the prefix
3291 mWorkingPath
= kDevicePathSpecifier
+ mWorkingPath
;
3295 mUseDOSDevicePathSyntax
= aUseDOSDevicePathSyntax
;
3300 nsLocalFile::Reveal() {
3301 // This API should be main thread only
3302 MOZ_ASSERT(NS_IsMainThread());
3304 // make sure mResolvedPath is set
3305 nsresult rv
= Resolve();
3306 if (NS_FAILED(rv
) && rv
!= NS_ERROR_FILE_NOT_FOUND
) {
3310 nsCOMPtr
<nsIRunnable
> task
=
3311 NS_NewRunnableFunction("nsLocalFile::Reveal", [path
= mResolvedPath
]() {
3312 MOZ_ASSERT(!NS_IsMainThread(), "Don't run on the main thread");
3314 bool doCoUninitialize
= SUCCEEDED(CoInitializeEx(
3315 nullptr, COINIT_APARTMENTTHREADED
| COINIT_DISABLE_OLE1DDE
));
3317 if (doCoUninitialize
) {
3322 return NS_DispatchBackgroundTask(task
,
3323 nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK
);
3327 nsLocalFile::GetWindowsFileAttributes(uint32_t* aAttrs
) {
3328 NS_ENSURE_ARG_POINTER(aAttrs
);
3330 DWORD dwAttrs
= ::GetFileAttributesW(mWorkingPath
.get());
3331 if (dwAttrs
== INVALID_FILE_ATTRIBUTES
) {
3332 return ConvertWinError(GetLastError());
3341 nsLocalFile::SetWindowsFileAttributes(uint32_t aSetAttrs
,
3342 uint32_t aClearAttrs
) {
3343 DWORD dwAttrs
= ::GetFileAttributesW(mWorkingPath
.get());
3344 if (dwAttrs
== INVALID_FILE_ATTRIBUTES
) {
3345 return ConvertWinError(GetLastError());
3348 dwAttrs
= (dwAttrs
& ~aClearAttrs
) | aSetAttrs
;
3350 if (::SetFileAttributesW(mWorkingPath
.get(), dwAttrs
) == 0) {
3351 return ConvertWinError(GetLastError());
3357 nsLocalFile::Launch() {
3358 // This API should be main thread only
3359 MOZ_ASSERT(NS_IsMainThread());
3361 // use the app registry name to launch a shell execute....
3362 _bstr_t
execPath(mWorkingPath
.get());
3365 // Pass VT_ERROR/DISP_E_PARAMNOTFOUND to omit an optional RPC parameter
3366 // to execute a file with the default verb.
3367 _variant_t
verbDefault(DISP_E_PARAMNOTFOUND
, VT_ERROR
);
3368 _variant_t
showCmd(SW_SHOWNORMAL
);
3370 // Use the directory of the file we're launching as the working
3371 // directory. That way if we have a self extracting EXE it won't
3372 // suggest to extract to the install directory.
3373 wchar_t* workingDirectoryPtr
= nullptr;
3374 WCHAR workingDirectory
[MAX_PATH
+ 1] = {L
'\0'};
3375 wcsncpy(workingDirectory
, mWorkingPath
.get(), MAX_PATH
);
3376 if (PathRemoveFileSpecW(workingDirectory
)) {
3377 workingDirectoryPtr
= workingDirectory
;
3379 NS_WARNING("Could not set working directory for launched file.");
3382 // We have two methods to launch a file: ShellExecuteExW and
3383 // ShellExecuteByExplorer. ShellExecuteExW starts a new process as a child
3384 // of the current process, while ShellExecuteByExplorer starts a new process
3385 // as a child of explorer.exe.
3387 // We prefer launching a process via ShellExecuteByExplorer because
3388 // applications may not support the mitigation policies inherited from our
3389 // process. For example, Skype for Business does not start correctly with
3390 // the PreferSystem32Images policy which is one of the policies we use.
3392 // If ShellExecuteByExplorer fails for some reason e.g. a system without
3393 // running explorer.exe or VDI environment like Citrix, we fall back to
3394 // ShellExecuteExW which still works in those special environments.
3396 // There is an exception where we go straight to ShellExecuteExW without
3397 // trying ShellExecuteByExplorer. When the extension of a downloaded file is
3398 // "exe", we prefer security rather than compatibility.
3400 // When a user launches a downloaded executable, the directory containing
3401 // the downloaded file may contain a malicious DLL with a common name, which
3402 // may have been downloaded before. If the downloaded executable is launched
3403 // without the PreferSystem32Images policy, the process can be tricked into
3404 // loading the malicious DLL in the same directory if its name is in the
3405 // executable's dependent modules. Therefore, we always launch ".exe"
3406 // executables via ShellExecuteExW so they inherit our process's mitigation
3407 // policies including PreferSystem32Images.
3409 // If the extension is not "exe", then we assume that we are launching an
3410 // installed application, and therefore the security risk described above
3411 // is lessened, as a malicious DLL is less likely to be installed in the
3412 // application's directory. In that case, we attempt to preserve
3413 // compatibility and try ShellExecuteByExplorer first.
3415 static const char* const onlyExeExt
[] = {".exe"};
3418 LookupExtensionIn(onlyExeExt
, std::size(onlyExeExt
), &isExecutable
);
3419 if (NS_FAILED(rv
)) {
3420 isExecutable
= false;
3423 // If the file is an executable, go straight to ShellExecuteExW.
3424 // Otherwise try ShellExecuteByExplorer first, and if it fails,
3425 // run ShellExecuteExW.
3426 if (!isExecutable
) {
3427 mozilla::LauncherVoidResult shellExecuteOk
=
3428 mozilla::ShellExecuteByExplorer(execPath
, args
, verbDefault
,
3429 workingDirectoryPtr
, showCmd
);
3430 if (shellExecuteOk
.isOk()) {
3435 SHELLEXECUTEINFOW seinfo
= {sizeof(SHELLEXECUTEINFOW
)};
3436 seinfo
.fMask
= SEE_MASK_ASYNCOK
;
3437 seinfo
.hwnd
= GetMostRecentNavigatorHWND();
3438 seinfo
.lpVerb
= nullptr;
3439 seinfo
.lpFile
= mWorkingPath
.get();
3440 seinfo
.lpParameters
= nullptr;
3441 seinfo
.lpDirectory
= workingDirectoryPtr
;
3442 seinfo
.nShow
= SW_SHOWNORMAL
;
3444 if (!ShellExecuteExW(&seinfo
)) {
3445 return NS_ERROR_FILE_EXECUTION_FAILED
;
3451 nsresult
NS_NewLocalFile(const nsAString
& aPath
, nsIFile
** aResult
) {
3452 RefPtr
<nsLocalFile
> file
= new nsLocalFile();
3454 if (!aPath
.IsEmpty()) {
3455 nsresult rv
= file
->InitWithPath(aPath
);
3456 if (NS_FAILED(rv
)) {
3461 file
.forget(aResult
);
3465 //-----------------------------------------------------------------------------
3466 // Native (lossy) interface
3467 //-----------------------------------------------------------------------------
3470 nsLocalFile::InitWithNativePath(const nsACString
& aFilePath
) {
3472 nsresult rv
= NS_CopyNativeToUnicode(aFilePath
, tmp
);
3473 if (NS_SUCCEEDED(rv
)) {
3474 return InitWithPath(tmp
);
3481 nsLocalFile::AppendNative(const nsACString
& aNode
) {
3483 nsresult rv
= NS_CopyNativeToUnicode(aNode
, tmp
);
3484 if (NS_SUCCEEDED(rv
)) {
3492 nsLocalFile::AppendRelativeNativePath(const nsACString
& aNode
) {
3494 nsresult rv
= NS_CopyNativeToUnicode(aNode
, tmp
);
3495 if (NS_SUCCEEDED(rv
)) {
3496 return AppendRelativePath(tmp
);
3502 nsLocalFile::GetNativeLeafName(nsACString
& aLeafName
) {
3503 // NS_WARNING("This API is lossy. Use GetLeafName !");
3505 nsresult rv
= GetLeafName(tmp
);
3506 if (NS_SUCCEEDED(rv
)) {
3507 rv
= NS_CopyUnicodeToNative(tmp
, aLeafName
);
3514 nsLocalFile::SetNativeLeafName(const nsACString
& aLeafName
) {
3516 nsresult rv
= NS_CopyNativeToUnicode(aLeafName
, tmp
);
3517 if (NS_SUCCEEDED(rv
)) {
3518 return SetLeafName(tmp
);
3525 nsLocalFile::HostPath(JSContext
* aCx
, dom::Promise
** aPromise
) {
3526 return NS_ERROR_NOT_IMPLEMENTED
;
3529 nsString
nsLocalFile::NativePath() { return mWorkingPath
; }
3531 nsCString
nsIFile::HumanReadablePath() {
3533 DebugOnly
<nsresult
> rv
= GetPath(path
);
3534 MOZ_ASSERT(NS_SUCCEEDED(rv
));
3535 return NS_ConvertUTF16toUTF8(path
);
3539 nsLocalFile::CopyToNative(nsIFile
* aNewParentDir
, const nsACString
& aNewName
) {
3540 // Check we are correctly initialized.
3541 CHECK_mWorkingPath();
3543 if (aNewName
.IsEmpty()) {
3544 return CopyTo(aNewParentDir
, u
""_ns
);
3548 nsresult rv
= NS_CopyNativeToUnicode(aNewName
, tmp
);
3549 if (NS_SUCCEEDED(rv
)) {
3550 return CopyTo(aNewParentDir
, tmp
);
3557 nsLocalFile::CopyToFollowingLinksNative(nsIFile
* aNewParentDir
,
3558 const nsACString
& aNewName
) {
3559 if (aNewName
.IsEmpty()) {
3560 return CopyToFollowingLinks(aNewParentDir
, u
""_ns
);
3564 nsresult rv
= NS_CopyNativeToUnicode(aNewName
, tmp
);
3565 if (NS_SUCCEEDED(rv
)) {
3566 return CopyToFollowingLinks(aNewParentDir
, tmp
);
3573 nsLocalFile::MoveToNative(nsIFile
* aNewParentDir
, const nsACString
& aNewName
) {
3574 // Check we are correctly initialized.
3575 CHECK_mWorkingPath();
3577 if (aNewName
.IsEmpty()) {
3578 return MoveTo(aNewParentDir
, u
""_ns
);
3582 nsresult rv
= NS_CopyNativeToUnicode(aNewName
, tmp
);
3583 if (NS_SUCCEEDED(rv
)) {
3584 return MoveTo(aNewParentDir
, tmp
);
3591 nsLocalFile::MoveToFollowingLinksNative(nsIFile
* aNewParentDir
,
3592 const nsACString
& aNewName
) {
3593 // Check we are correctly initialized.
3594 CHECK_mWorkingPath();
3596 if (aNewName
.IsEmpty()) {
3597 return MoveToFollowingLinks(aNewParentDir
, u
""_ns
);
3601 nsresult rv
= NS_CopyNativeToUnicode(aNewName
, tmp
);
3602 if (NS_SUCCEEDED(rv
)) {
3603 return MoveToFollowingLinks(aNewParentDir
, tmp
);
3610 nsLocalFile::GetNativeTarget(nsACString
& aResult
) {
3611 // Check we are correctly initialized.
3612 CHECK_mWorkingPath();
3614 NS_WARNING("This API is lossy. Use GetTarget !");
3616 nsresult rv
= GetTarget(tmp
);
3617 if (NS_SUCCEEDED(rv
)) {
3618 rv
= NS_CopyUnicodeToNative(tmp
, aResult
);
3624 nsresult
NS_NewNativeLocalFile(const nsACString
& aPath
, nsIFile
** aResult
) {
3626 nsresult rv
= NS_CopyNativeToUnicode(aPath
, buf
);
3627 if (NS_FAILED(rv
)) {
3631 return NS_NewLocalFile(buf
, aResult
);
3634 void nsLocalFile::EnsureShortPath() {
3635 if (!mShortWorkingPath
.IsEmpty()) {
3639 WCHAR shortPath
[MAX_PATH
+ 1];
3640 DWORD lengthNeeded
=
3641 ::GetShortPathNameW(mWorkingPath
.get(), shortPath
, std::size(shortPath
));
3642 // If an error occurred then lengthNeeded is set to 0 or the length of the
3643 // needed buffer including null termination. If it succeeds the number of
3644 // wide characters not including null termination is returned.
3645 if (lengthNeeded
!= 0 && lengthNeeded
< std::size(shortPath
)) {
3646 mShortWorkingPath
.Assign(shortPath
);
3648 mShortWorkingPath
.Assign(mWorkingPath
);
3652 NS_IMPL_ISUPPORTS_INHERITED(nsDriveEnumerator
, nsSimpleEnumerator
,
3653 nsIDirectoryEnumerator
)
3655 nsDriveEnumerator::nsDriveEnumerator(bool aUseDOSDevicePathSyntax
)
3656 : mUseDOSDevicePathSyntax(aUseDOSDevicePathSyntax
) {}
3658 nsDriveEnumerator::~nsDriveEnumerator() {}
3660 nsresult
nsDriveEnumerator::Init() {
3661 /* If the length passed to GetLogicalDriveStrings is smaller
3662 * than the length of the string it would return, it returns
3663 * the length required for the string. */
3664 DWORD length
= GetLogicalDriveStringsW(0, 0);
3665 /* The string is null terminated */
3666 if (!mDrives
.SetLength(length
+ 1, fallible
)) {
3667 return NS_ERROR_OUT_OF_MEMORY
;
3669 if (!GetLogicalDriveStringsW(length
, mDrives
.get())) {
3670 return NS_ERROR_FAILURE
;
3672 mDrives
.BeginReading(mStartOfCurrentDrive
);
3673 mDrives
.EndReading(mEndOfDrivesString
);
3678 nsDriveEnumerator::HasMoreElements(bool* aHasMore
) {
3679 *aHasMore
= *mStartOfCurrentDrive
!= L
'\0';
3684 nsDriveEnumerator::GetNext(nsISupports
** aNext
) {
3685 /* GetLogicalDrives stored in mDrives is a concatenation
3686 * of null terminated strings, followed by a null terminator.
3687 * mStartOfCurrentDrive is an iterator pointing at the first
3688 * character of the current drive. */
3689 if (*mStartOfCurrentDrive
== L
'\0') {
3691 return NS_ERROR_FAILURE
;
3694 nsAString::const_iterator driveEnd
= mStartOfCurrentDrive
;
3695 FindCharInReadable(L
'\0', driveEnd
, mEndOfDrivesString
);
3696 nsString
drive(Substring(mStartOfCurrentDrive
, driveEnd
));
3697 mStartOfCurrentDrive
= ++driveEnd
;
3700 nsresult rv
= NewLocalFile(drive
, mUseDOSDevicePathSyntax
, &file
);