No Bug, mozilla-central repo-update HSTS HPKP remote-settings tld-suffixes ct-logs...
[gecko.git] / xpcom / io / nsLocalFileWin.cpp
blob44ee6aac74b43a39cfe9277417ccb37b3d5cdf81
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"
15 #include "nsCOMPtr.h"
17 #include "nsLocalFile.h"
18 #include "nsLocalFileCommon.h"
19 #include "nsIDirectoryEnumerator.h"
20 #include "nsNativeCharsetUtils.h"
22 #include "nsSimpleEnumerator.h"
23 #include "prio.h"
24 #include "private/pprio.h" // To get PR_ImportFile
25 #include "nsHashKeys.h"
27 #include "nsString.h"
28 #include "nsReadableUtils.h"
30 #include <direct.h>
31 #include <fileapi.h>
32 #include <windows.h>
33 #include <shlwapi.h>
34 #include <aclapi.h>
36 #include "shellapi.h"
37 #include "shlguid.h"
39 #include <io.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <mbstring.h>
44 #include "prproces.h"
45 #include "prlink.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"
62 #include "WinUtils.h"
64 using namespace mozilla;
65 using mozilla::FilePreferences::kDevicePathSpecifier;
66 using mozilla::FilePreferences::kPathSeparator;
68 #define CHECK_mWorkingPath() \
69 do { \
70 if (mWorkingPath.IsEmpty()) return NS_ERROR_NOT_INITIALIZED; \
71 } while (0)
73 #ifndef FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
74 # define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000
75 #endif
77 #ifndef DRIVE_REMOTE
78 # define DRIVE_REMOTE 4
79 #endif
81 namespace {
83 nsresult NewLocalFile(const nsAString& aPath, bool aUseDOSDevicePathSyntax,
84 nsIFile** aResult) {
85 RefPtr<nsLocalFile> file = new nsLocalFile();
87 file->SetUseDOSDevicePathSyntax(aUseDOSDevicePathSyntax);
89 if (!aPath.IsEmpty()) {
90 nsresult rv = file->InitWithPath(aPath);
91 if (NS_FAILED(rv)) {
92 return rv;
96 file.forget(aResult);
97 return NS_OK;
100 } // anonymous namespace
102 static HWND GetMostRecentNavigatorHWND() {
103 nsresult rv;
104 nsCOMPtr<nsIWindowMediator> winMediator(
105 do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv));
106 if (NS_FAILED(rv)) {
107 return nullptr;
110 nsCOMPtr<mozIDOMWindowProxy> navWin;
111 rv = winMediator->GetMostRecentBrowserWindow(getter_AddRefs(navWin));
112 if (NS_FAILED(rv) || !navWin) {
113 return nullptr;
116 nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin);
117 nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(win);
118 if (!widget) {
119 return nullptr;
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;
133 HRESULT hr;
134 if (attributes & FILE_ATTRIBUTE_DIRECTORY) {
135 // We have a directory so we should open the directory itself.
136 LPITEMIDLIST dir = ILCreateFromPathW(aResolvedPath.get());
137 if (!dir) {
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);
146 CoTaskMemFree(dir);
147 } else {
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);
160 if (!dir) {
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());
166 if (!item) {
167 CoTaskMemFree(dir);
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);
177 CoTaskMemFree(dir);
178 CoTaskMemFree(item);
181 return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
184 // static
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('.')) {
198 return true;
203 return false;
206 class nsDriveEnumerator : public nsSimpleEnumerator,
207 public nsIDirectoryEnumerator {
208 public:
209 explicit nsDriveEnumerator(bool aUseDOSDevicePathSyntax);
210 NS_DECL_ISUPPORTS_INHERITED
211 NS_DECL_NSISIMPLEENUMERATOR
212 NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::)
213 nsresult Init();
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) {
221 return rv;
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);
229 return NS_OK;
232 NS_IMETHOD Close() override { return NS_OK; }
234 private:
235 virtual ~nsDriveEnumerator();
237 /* mDrives stores the null-separated drive names.
238 * Init sets them.
239 * HasMoreElements checks mStartOfCurrentDrive.
240 * GetNext advances mStartOfCurrentDrive.
242 nsString mDrives;
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
261 * codes.
263 static nsresult ConvertWinError(DWORD aWinErr) {
264 nsresult rv;
266 switch (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;
273 break;
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;
282 break;
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;
287 break;
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;
292 break;
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;
297 break;
298 case ERROR_WRITE_PROTECT:
299 rv = NS_ERROR_FILE_READ_ONLY;
300 break;
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;
305 break;
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;
310 break;
311 case ERROR_FILENAME_EXCED_RANGE:
312 rv = NS_ERROR_FILE_NAME_TOO_LONG;
313 break;
314 case ERROR_DIRECTORY:
315 rv = NS_ERROR_FILE_NOT_DIRECTORY;
316 break;
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;
321 break;
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;
332 break;
333 case ERROR_NOT_READY:
334 rv = NS_ERROR_FILE_DEVICE_TEMPORARY_FAILURE;
335 break;
336 case ERROR_INVALID_NAME:
337 rv = NS_ERROR_FILE_INVALID_PATH;
338 break;
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;
345 break;
346 case 0:
347 rv = NS_OK;
348 break;
349 default:
350 printf_stderr(
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);
355 break;
357 return rv;
360 // Check whether a path is a volume root. Expects paths to be \-terminated.
361 static bool IsRootPath(const nsAString& aPath) {
362 // Easy cases first:
363 if (aPath.Last() != L'\\') {
364 return false;
366 if (StringEndsWith(aPath, u":\\"_ns)) {
367 return true;
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:
374 end--;
375 // Find the next last slash:
376 if (RFindInReadable(u"\\"_ns, begin, end)) {
377 // Reset iterator:
378 aPath.EndReading(end);
379 end--;
380 auto lastSegment = Substring(++begin, end);
381 if (lastSegment.IsEmpty()) {
382 return false;
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())) {
388 return true;
390 // Volume GUID paths:
391 if (StringBeginsWith(lastSegment, u"Volume{"_ns) &&
392 lastSegment.Last() == L'}') {
393 return true;
396 return false;
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);
407 auto iter = begin;
408 // Early exit if there's no '$' (common case)
409 if (!FindCharInReadable(L'$', iter, end)) {
410 return false;
413 iter = begin;
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)) {
417 return true;
420 auto normalized = mozilla::MakeUniqueFallible<wchar_t[]>(MAX_PATH);
421 if (!normalized) {
422 return true;
424 auto flatPath = PromiseFlatString(aFilePath);
425 auto fullPathRV =
426 GetFullPathNameW(flatPath.get(), MAX_PATH - 1, normalized.get(), nullptr);
427 if (fullPathRV == 0 || fullPathRV > MAX_PATH - 1) {
428 return false;
431 nsString normalizedPath(normalized.get());
432 normalizedPath.BeginReading(begin);
433 normalizedPath.EndReading(end);
434 iter = begin;
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
446 // our check:
447 if (iterCopy == end || kDelimiters.Contains(*iterCopy)) {
448 iterCopy = iter;
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));
457 iter++;
460 return false;
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 //-----------------------------------------------------------------------------
476 typedef enum {
477 _PR_TRI_TRUE = 1,
478 _PR_TRI_FALSE = 0,
479 _PR_TRI_UNKNOWN = -1
480 } _PRTriStateBool;
482 struct _MDFileDesc {
483 PROsfd osfd;
486 struct PRFilePrivate {
487 int32_t state;
488 bool nonblocking;
489 _PRTriStateBool inheritable;
490 PRFileDesc* next;
491 int lockCount; /* 0: not locked
492 * -1: a native lockfile call is in progress
493 * > 0: # times the file is locked */
494 bool appendMode;
495 _MDFileDesc md;
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) {
510 int32_t access = 0;
512 int32_t shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
513 int32_t disposition = 0;
514 int32_t attributes = 0;
516 if (aShareDelete) {
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;
535 } else {
536 disposition = OPEN_ALWAYS;
538 } else {
539 if (aOsflags & PR_TRUNCATE) {
540 disposition = TRUNCATE_EXISTING;
541 } else {
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) {
566 *aFd = nullptr;
567 return ConvertWinError(GetLastError());
570 *aFd = PR_ImportFile((PROsfd)file);
571 if (*aFd) {
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;
575 return NS_OK;
578 nsresult rv = NS_ErrorAccordingToNSPR();
580 CloseHandle(file);
582 return rv;
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) {
588 #ifdef __GNUC__
589 const PRTime _pr_filetime_offset = 116444736000000000LL;
590 #else
591 const PRTime _pr_filetime_offset = 116444736000000000i64;
592 #endif
594 MOZ_ASSERT(sizeof(FILETIME) == sizeof(PRTime));
595 ::CopyMemory(aPrtm, aFiletime, sizeof(PRTime));
596 #ifdef __GNUC__
597 *aPrtm = (*aPrtm - _pr_filetime_offset) / 10LL;
598 #else
599 *aPrtm = (*aPrtm - _pr_filetime_offset) / 10i64;
600 #endif
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()
615 : 0;
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;
630 } else {
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;
643 } else {
644 FileTimeToPRTime(&fileData.ftCreationTime, &aInfo->creationTime);
647 FileTimeToPRTime(&fileData.ftLastAccessTime, &aInfo->accessTime);
649 return NS_OK;
652 struct nsDir {
653 HANDLE handle;
654 WIN32_FIND_DATAW data;
655 bool firstEntry;
658 static nsresult OpenDir(const nsString& aName, nsDir** aDir) {
659 if (NS_WARN_IF(!aDir)) {
660 return NS_ERROR_INVALID_ARG;
663 *aDir = nullptr;
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('*');
671 } else {
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) {
685 delete d;
686 return ConvertWinError(GetLastError());
688 d->firstEntry = true;
690 *aDir = d;
691 return NS_OK;
694 static nsresult ReadDir(nsDir* aDir, PRDirFlags aFlags, nsString& aName) {
695 aName.Truncate();
696 if (NS_WARN_IF(!aDir)) {
697 return NS_ERROR_INVALID_ARG;
700 while (1) {
701 BOOL rv;
702 if (aDir->firstEntry) {
703 aDir->firstEntry = false;
704 rv = 1;
705 } else {
706 rv = ::FindNextFileW(aDir->handle, &(aDir->data));
709 if (rv == 0) {
710 break;
713 const wchar_t* fileName;
714 fileName = (aDir)->data.cFileName;
716 if ((aFlags & PR_SKIP_DOT) && (fileName[0] == L'.') &&
717 (fileName[1] == L'\0')) {
718 continue;
720 if ((aFlags & PR_SKIP_DOT_DOT) && (fileName[0] == L'.') &&
721 (fileName[1] == L'.') && (fileName[2] == L'\0')) {
722 continue;
725 DWORD attrib = aDir->data.dwFileAttributes;
726 if ((aFlags & PR_SKIP_HIDDEN) && (attrib & FILE_ATTRIBUTE_HIDDEN)) {
727 continue;
730 aName = fileName;
731 return NS_OK;
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);
744 delete aDir;
745 aDir = nullptr;
746 return isOk ? NS_OK : ConvertWinError(GetLastError());
749 //-----------------------------------------------------------------------------
750 // nsDirEnumerator
751 //-----------------------------------------------------------------------------
753 class nsDirEnumerator final : public nsSimpleEnumerator,
754 public nsIDirectoryEnumerator {
755 private:
756 ~nsDirEnumerator() { Close(); }
758 public:
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);
782 if (NS_FAILED(rv)) {
783 return rv;
786 mParent = aParent;
787 return NS_OK;
790 NS_IMETHOD HasMoreElements(bool* aResult) override {
791 nsresult rv;
792 if (!mNext && mDir) {
793 nsString name;
794 rv = ReadDir(mDir, PR_SKIP_BOTH, name);
795 if (NS_FAILED(rv)) {
796 return rv;
798 if (name.IsEmpty()) {
799 // end of dir entries
800 rv = CloseDir(mDir);
801 if (NS_FAILED(rv)) {
802 return rv;
805 *aResult = false;
806 return NS_OK;
809 nsCOMPtr<nsIFile> file;
810 rv = mParent->Clone(getter_AddRefs(file));
811 if (NS_FAILED(rv)) {
812 return rv;
815 rv = file->Append(name);
816 if (NS_FAILED(rv)) {
817 return rv;
820 mNext = file.forget();
822 *aResult = mNext != nullptr;
823 if (!*aResult) {
824 Close();
826 return NS_OK;
829 NS_IMETHOD GetNext(nsISupports** aResult) override {
830 nsresult rv;
831 bool hasMore;
832 rv = HasMoreElements(&hasMore);
833 if (NS_FAILED(rv)) {
834 return rv;
836 if (!hasMore) {
837 return NS_ERROR_FAILURE;
840 mNext.forget(aResult);
841 return NS_OK;
844 NS_IMETHOD GetNextFile(nsIFile** aResult) override {
845 *aResult = nullptr;
846 bool hasMore = false;
847 nsresult rv = HasMoreElements(&hasMore);
848 if (NS_FAILED(rv) || !hasMore) {
849 return rv;
851 mNext.forget(aResult);
852 return NS_OK;
855 NS_IMETHOD Close() override {
856 if (mDir) {
857 nsresult rv = CloseDir(mDir);
858 NS_ASSERTION(NS_SUCCEEDED(rv), "close failed");
859 if (NS_FAILED(rv)) {
860 return NS_ERROR_FAILURE;
863 return NS_OK;
866 protected:
867 nsDir* mDir;
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);
895 if (NS_FAILED(rv)) {
896 delete inst;
897 return rv;
899 return NS_OK;
902 //-----------------------------------------------------------------------------
903 // nsLocalFile::nsISupports
904 //-----------------------------------------------------------------------------
906 NS_IMPL_ISUPPORTS(nsLocalFile, nsIFile, nsILocalFileWin)
908 //-----------------------------------------------------------------------------
909 // nsLocalFile <private>
910 //-----------------------------------------------------------------------------
912 nsLocalFile::nsLocalFile(const nsLocalFile& aOther)
913 : mDirty(true),
914 mResolveDirty(true),
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());
924 return NS_OK;
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
931 if (!mDirty) {
932 return NS_OK;
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);
953 if (NS_FAILED(rv)) {
954 return rv;
957 if (mFileInfo.type != PR_FILE_OTHER) {
958 mResolveDirty = false;
959 mDirty = false;
960 return NS_OK;
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.
967 if (NS_FAILED(rv)) {
968 mResolvedPath.Assign(mWorkingPath);
969 return rv;
972 mResolveDirty = false;
973 // get the details of the resolved path
974 rv = GetFileInfo(mResolvedPath, &mFileInfo);
975 if (NS_FAILED(rv)) {
976 return rv;
979 mDirty = false;
980 return NS_OK;
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) {
993 return NS_OK;
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;
1007 return NS_OK;
1010 //-----------------------------------------------------------------------------
1011 // nsLocalFile::nsIFile
1012 //-----------------------------------------------------------------------------
1014 NS_IMETHODIMP
1015 nsLocalFile::Clone(nsIFile** aFile) {
1016 // Just copy-construct ourselves
1017 RefPtr<nsLocalFile> file = new nsLocalFile(*this);
1018 file.forget(aFile);
1020 return NS_OK;
1023 NS_IMETHODIMP
1024 nsLocalFile::InitWithFile(nsIFile* aFile) {
1025 if (NS_WARN_IF(!aFile)) {
1026 return NS_ERROR_INVALID_ARG;
1029 nsAutoString path;
1030 aFile->GetPath(path);
1031 if (path.IsEmpty()) {
1032 return NS_ERROR_INVALID_ARG;
1034 return InitWithPath(path);
1037 NS_IMETHODIMP
1038 nsLocalFile::InitWithPath(const nsAString& aFilePath) {
1039 MakeDirty();
1041 nsAString::const_iterator begin, end;
1042 aFilePath.BeginReading(begin);
1043 aFilePath.EndReading(end);
1045 // input string must not be empty
1046 if (begin == end) {
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;
1094 return NS_OK;
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);
1109 aPath.Append(' ');
1111 // case insensitive
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;
1132 // case insensitive
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.
1151 /* static */
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
1173 return false;
1175 auto destination = mozilla::MakeUniqueFallible<wchar_t[]>(bufLength);
1176 if (!destination) return false;
1177 if (!::ExpandEnvironmentStringsW(handlerCommand.get(), destination.get(),
1178 bufLength))
1179 return false;
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
1187 // handler.
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);
1195 return true;
1198 NS_IMETHODIMP
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);
1207 NS_IMETHODIMP
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)) {
1212 return rv;
1215 return NS_OK;
1218 NS_IMETHODIMP
1219 nsLocalFile::OpenANSIFileDesc(const char* aMode, FILE** aResult) {
1220 *aResult = _wfopen(mWorkingPath.get(), NS_ConvertASCIItoUTF16(aMode).get());
1221 if (*aResult) {
1222 return NS_OK;
1225 return NS_ERROR_FAILURE;
1228 static nsresult do_create(nsIFile* aFile, const nsString& aPath,
1229 uint32_t aAttributes) {
1230 PRFileDesc* file;
1231 nsresult rv =
1232 OpenFile(aPath, PR_RDONLY | PR_CREATE_FILE | PR_APPEND | PR_EXCL,
1233 aAttributes, false, &file);
1234 if (file) {
1235 PR_Close(file);
1238 if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
1239 // need to return already-exists for directories (bug 452217)
1240 bool isdir;
1241 if (NS_SUCCEEDED(aFile->IsDirectory(&isdir)) && isdir) {
1242 rv = NS_ERROR_FILE_ALREADY_EXISTS;
1245 return rv;
1248 static nsresult do_mkdir(nsIFile*, const nsString& aPath, uint32_t) {
1249 if (!::CreateDirectoryW(aPath.get(), nullptr)) {
1250 return ConvertWinError(GetLastError());
1252 return NS_OK;
1255 NS_IMETHODIMP
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 ||
1266 aSkipAncestors) {
1267 return 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
1275 // ^--- start here
1277 // - UNC path: \\machine\volume\some\path\on\this\drive
1278 // ^--- start here
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'\\');
1288 if (!path) {
1289 return NS_ERROR_FILE_INVALID_PATH;
1291 ++path;
1294 // search for first slash after the drive (or volume) name
1295 wchar_t* slash = wcschr(path, L'\\');
1297 nsresult directoryCreateError = NS_OK;
1298 if (slash) {
1299 // skip the first '\\'
1300 ++slash;
1301 slash = wcschr(slash, L'\\');
1303 while (slash) {
1304 *slash = L'\0';
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) {
1318 return rv;
1321 directoryCreateError = rv;
1323 *slash = L'\\';
1324 ++slash;
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);
1337 NS_IMETHODIMP
1338 nsLocalFile::Append(const nsAString& aNode) {
1339 // append this path, multiple components are not permitted
1340 return AppendInternal(PromiseFlatString(aNode), false);
1343 NS_IMETHODIMP
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()) {
1352 return NS_OK;
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);
1371 offset = end;
1372 while (FindInReadable(doubleDot, start, offset)) {
1373 if (offset == end || *offset == L'\\') {
1374 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1376 start = offset;
1377 offset = end;
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;
1390 MakeDirty();
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;
1401 return NS_OK;
1404 nsresult nsLocalFile::OpenNSPRFileDescMaybeShareDelete(int32_t aFlags,
1405 int32_t aMode,
1406 bool aShareDelete,
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))
1413 NS_IMETHODIMP
1414 nsLocalFile::Normalize() {
1415 // XXX See bug 187957 comment 18 for possible problems with this
1416 // implementation.
1418 if (mWorkingPath.IsEmpty()) {
1419 return NS_OK;
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];
1445 WCHAR* pcwd = cwd;
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);
1469 if (!pcwd) {
1470 return NS_ERROR_OUT_OF_MEMORY;
1472 nsAutoString currentDir(pcwd);
1473 if (pcwd != cwd) {
1474 free(pcwd);
1477 if (currentDir.Last() == '\\') {
1478 path.Replace(0, 2, currentDir);
1479 } else {
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()) {
1490 return NS_OK;
1493 // assign the root
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
1515 begin = end + 1;
1516 end = path.FindChar('\\', begin);
1517 if (end == kNotFound) {
1518 end = path.Length();
1520 len = end - begin;
1522 // ignore double backslashes
1523 if (len == 0) {
1524 continue;
1527 // len != 0, and interesting paths always begin with a dot
1528 if (pathBuffer[begin] == '.') {
1529 // ignore single dots
1530 if (len == 1) {
1531 continue;
1534 // handle multiple dots
1535 if (len >= 2 && pathBuffer[begin + 1] == L'.') {
1536 // back up a path component on double dot
1537 if (len == 2) {
1538 int32_t prev = mWorkingPath.RFindChar('\\');
1539 if (prev >= rootIdx) {
1540 mWorkingPath.Truncate(prev);
1542 continue;
1545 // length is > 2 and the first two characters are dots.
1546 // if the rest of the string is dots, then ignore it.
1547 int idx = len - 1;
1548 for (; idx >= 2; --idx) {
1549 if (pathBuffer[begin + idx] != L'.') {
1550 break;
1554 // this is true if the loop above didn't break
1555 // and all characters in this segment are dots.
1556 if (idx < 2) {
1557 continue;
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--);
1573 MakeDirty();
1574 return NS_OK;
1577 NS_IMETHODIMP
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;
1590 } else {
1591 aLeafName = Substring(mWorkingPath, offset + 1);
1594 return NS_OK;
1597 NS_IMETHODIMP
1598 nsLocalFile::SetLeafName(const nsAString& aLeafName) {
1599 MakeDirty();
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'\\');
1607 nsString newDir;
1608 if (offset) {
1609 newDir = Substring(mWorkingPath, 0, offset + 1) + aLeafName;
1610 } else {
1611 newDir = mWorkingPath + aLeafName;
1613 if (IsSpecialNTFSPath(newDir)) {
1614 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1617 mWorkingPath.Assign(newDir);
1619 return NS_OK;
1622 NS_IMETHODIMP
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),
1631 SHGFI_DISPLAYNAME);
1632 // If we found a display name, return that:
1633 if (result) {
1634 aLeafName.Assign(sfi.szDisplayName);
1635 return NS_OK;
1637 // Nope - fall back to the regular leaf name.
1638 return GetLeafName(aLeafName);
1641 NS_IMETHODIMP
1642 nsLocalFile::GetPath(nsAString& aResult) {
1643 MOZ_ASSERT_IF(
1644 mUseDOSDevicePathSyntax,
1645 !FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath));
1646 aResult = mWorkingPath;
1647 return NS_OK;
1650 NS_IMETHODIMP
1651 nsLocalFile::GetCanonicalPath(nsAString& aResult) {
1652 EnsureShortPath();
1653 aResult.Assign(mShortWorkingPath);
1654 return NS_OK;
1657 typedef struct {
1658 WORD wLanguage;
1659 WORD wCodePage;
1660 } LANGANDCODEPAGE;
1662 NS_IMETHODIMP
1663 nsLocalFile::GetVersionInfoField(const char* aField, nsAString& aResult) {
1664 nsresult rv = NS_ERROR_FAILURE;
1666 const WCHAR* path = mWorkingPath.get();
1668 DWORD dummy;
1669 DWORD size = ::GetFileVersionInfoSizeW(path, &dummy);
1670 if (!size) {
1671 return rv;
1674 void* ver = moz_xcalloc(size, 1);
1675 if (::GetFileVersionInfoW(path, 0, size, ver)) {
1676 LANGANDCODEPAGE* translate = nullptr;
1677 UINT pageCount;
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;
1688 UINT size;
1689 queryResult = ::VerQueryValueW(ver, subBlock, &value, &size);
1690 if (queryResult && value) {
1691 aResult.Assign(static_cast<char16_t*>(value));
1692 if (!aResult.IsEmpty()) {
1693 rv = NS_OK;
1694 break;
1700 free(ver);
1702 return rv;
1705 NS_IMETHODIMP
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)) {
1710 return rv;
1713 return NS_OK;
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)) {
1731 return false;
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
1736 // a backslash.
1737 if (len >= MAX_PATH) {
1738 return false;
1741 dirPath[len] = L'\\';
1742 dirPath[len + 1] = L'\0';
1743 UINT driveType = GetDriveTypeW(dirPath);
1744 aRemote = driveType == DRIVE_REMOTE;
1745 return true;
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
1759 // target.
1760 nsAutoString destPath;
1761 rv = aDestParent->GetTarget(destPath);
1762 if (NS_FAILED(rv)) {
1763 return rv;
1766 destPath.Append('\\');
1768 if (aNewName.IsEmpty()) {
1769 nsAutoString aFileName;
1770 aSourceFile->GetLeafName(aFileName);
1771 destPath.Append(aFileName);
1772 } else {
1773 destPath.Append(aNewName);
1776 if (aOptions & FollowSymlinks) {
1777 rv = aSourceFile->GetTarget(filePath);
1778 if (filePath.IsEmpty()) {
1779 rv = aSourceFile->GetPath(filePath);
1781 } else {
1782 rv = aSourceFile->GetPath(filePath);
1785 if (NS_FAILED(rv)) {
1786 return rv;
1789 #ifdef DEBUG
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");
1805 #endif
1807 if (FilePreferences::IsBlockedUNCPath(destPath)) {
1808 return NS_ERROR_FILE_ACCESS_DENIED;
1811 int copyOK = 0;
1812 if (move) {
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 ||
1836 path2Remote) {
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);
1867 if (pOldDACL) {
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) {
1876 inherited = true;
1877 break;
1881 if (!inherited) {
1882 ::SetNamedSecurityInfoW(
1883 (LPWSTR)destPath.get(), SE_FILE_OBJECT,
1884 DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION,
1885 nullptr, nullptr, pOldDACL, nullptr);
1890 return rv;
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)) {
1909 return 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)) {
1920 return 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)) {
1933 return rv;
1936 if (!exists) {
1937 rv = newParentDir->Create(DIRECTORY_TYPE,
1938 0644); // TODO, what permissions should we use
1939 if (NS_FAILED(rv)) {
1940 return rv;
1942 } else {
1943 bool isDir = false;
1944 rv = newParentDir->IsDirectory(&isDir);
1945 if (NS_FAILED(rv)) {
1946 return rv;
1949 if (!isDir) {
1950 if (followSymlinks) {
1951 bool isLink = false;
1952 rv = newParentDir->IsSymlink(&isLink);
1953 if (NS_FAILED(rv)) {
1954 return rv;
1957 if (isLink) {
1958 nsAutoString target;
1959 rv = newParentDir->GetTarget(target);
1960 if (NS_FAILED(rv)) {
1961 return rv;
1964 nsCOMPtr<nsIFile> realDest = new nsLocalFile();
1965 rv = realDest->InitWithPath(target);
1966 if (NS_FAILED(rv)) {
1967 return rv;
1970 return CopyMove(realDest, aNewName, aOptions);
1972 } else {
1973 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
1979 // Try different ways to move/copy files/directories
1980 bool done = false;
1982 bool isDir = false;
1983 rv = IsDirectory(&isDir);
1984 if (NS_FAILED(rv)) {
1985 return rv;
1988 bool isSymlink = false;
1989 rv = IsSymlink(&isSymlink);
1990 if (NS_FAILED(rv)) {
1991 return 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
1998 if (!aParentDir) {
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)) {
2006 return rv;
2010 // Not able to copy or move directly, so enumerate it
2011 if (!done) {
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)) {
2016 return rv;
2019 nsAutoString allocatedNewName;
2020 if (aNewName.IsEmpty()) {
2021 bool isLink = false;
2022 rv = IsSymlink(&isLink);
2023 if (NS_FAILED(rv)) {
2024 return rv;
2027 if (isLink) {
2028 nsAutoString temp;
2029 rv = GetTarget(temp);
2030 if (NS_FAILED(rv)) {
2031 return rv;
2034 int32_t offset = temp.RFindChar(L'\\');
2035 if (offset == kNotFound) {
2036 allocatedNewName = temp;
2037 } else {
2038 allocatedNewName = Substring(temp, offset + 1);
2040 } else {
2041 GetLeafName(allocatedNewName); // this should be the leaf name of the
2043 } else {
2044 allocatedNewName = aNewName;
2047 rv = target->Append(allocatedNewName);
2048 if (NS_FAILED(rv)) {
2049 return 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)) {
2058 return rv;
2061 if (!exists) {
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)) {
2066 return rv;
2068 } else {
2069 // check if the destination directory is writable and empty
2070 bool isWritable = false;
2071 rv = target->IsWritable(&isWritable);
2072 if (NS_FAILED(rv)) {
2073 return rv;
2076 if (!isWritable) {
2077 return NS_ERROR_FILE_ACCESS_DENIED;
2080 nsCOMPtr<nsIDirectoryEnumerator> targetIterator;
2081 rv = target->GetDirectoryEntries(getter_AddRefs(targetIterator));
2082 if (NS_FAILED(rv)) {
2083 return rv;
2086 bool more;
2087 targetIterator->HasMoreElements(&more);
2088 // return error if target directory is not empty
2089 if (more) {
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");
2099 return rv;
2102 nsCOMPtr<nsIFile> file;
2103 while (NS_SUCCEEDED(dirEnum->GetNextFile(getter_AddRefs(file))) && file) {
2104 bool isDir = false;
2105 rv = file->IsDirectory(&isDir);
2106 if (NS_FAILED(rv)) {
2107 return rv;
2110 bool isLink = false;
2111 rv = file->IsSymlink(&isLink);
2112 if (NS_FAILED(rv)) {
2113 return rv;
2116 if (move) {
2117 if (followSymlinks) {
2118 return NS_ERROR_FAILURE;
2121 rv = file->MoveTo(target, u""_ns);
2122 if (NS_FAILED(rv)) {
2123 return rv;
2125 } else {
2126 if (followSymlinks) {
2127 rv = file->CopyToFollowingLinks(target, u""_ns);
2128 } else {
2129 rv = file->CopyTo(target, u""_ns);
2131 if (NS_FAILED(rv)) {
2132 return 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.
2142 if (move) {
2143 rv = Remove(false /* recursive */);
2144 if (NS_FAILED(rv)) {
2145 return rv;
2150 // If we moved, we want to adjust this.
2151 if (move) {
2152 MakeDirty();
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);
2166 Append(aFileName);
2167 } else {
2168 InitWithPath(newParentPath);
2169 Append(aNewName);
2173 return NS_OK;
2176 NS_IMETHODIMP
2177 nsLocalFile::CopyTo(nsIFile* aNewParentDir, const nsAString& aNewName) {
2178 return CopyMove(aNewParentDir, aNewName, 0);
2181 NS_IMETHODIMP
2182 nsLocalFile::CopyToFollowingLinks(nsIFile* aNewParentDir,
2183 const nsAString& aNewName) {
2184 return CopyMove(aNewParentDir, aNewName, FollowSymlinks);
2187 NS_IMETHODIMP
2188 nsLocalFile::MoveTo(nsIFile* aNewParentDir, const nsAString& aNewName) {
2189 return CopyMove(aNewParentDir, aNewName, Move);
2192 NS_IMETHODIMP
2193 nsLocalFile::MoveToFollowingLinks(nsIFile* aNewParentDir,
2194 const nsAString& aNewName) {
2195 return CopyMove(aNewParentDir, aNewName, Move | FollowSymlinks);
2198 NS_IMETHODIMP
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)) {
2212 return 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)) {
2222 return 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)) {
2235 return rv;
2238 if (!exists) {
2239 rv = targetParentDir->Create(DIRECTORY_TYPE, 0644);
2240 if (NS_FAILED(rv)) {
2241 return rv;
2243 } else {
2244 bool isDir = false;
2245 rv = targetParentDir->IsDirectory(&isDir);
2246 if (NS_FAILED(rv)) {
2247 return rv;
2249 if (!isDir) {
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);
2263 NS_IMETHODIMP
2264 nsLocalFile::RenameToNative(nsIFile* aNewParentDir,
2265 const nsACString& aNewName) {
2266 nsAutoString tmp;
2267 nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
2268 if (NS_SUCCEEDED(rv)) {
2269 return RenameTo(aNewParentDir, tmp);
2272 return rv;
2275 NS_IMETHODIMP
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);
2285 #endif
2287 PRLibSpec libSpec;
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);
2294 #endif
2296 if (*aResult) {
2297 return NS_OK;
2299 return NS_ERROR_NULL_POINTER;
2302 NS_IMETHODIMP
2303 nsLocalFile::Remove(bool aRecursive, uint32_t* aRemoveCount) {
2304 // NOTE:
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)) {
2330 return rv;
2333 // only check to see if we have a directory if it isn't a link
2334 bool isDir = false;
2335 if (!isLink) {
2336 rv = IsDirectory(&isDir);
2337 if (NS_FAILED(rv)) {
2338 return rv;
2342 if (isDir) {
2343 if (aRecursive) {
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)) {
2353 return 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());
2366 } else {
2367 if (DeleteFileW(mWorkingPath.get()) == 0) {
2368 return ConvertWinError(GetLastError());
2372 if (aRemoveCount) {
2373 *aRemoveCount += 1;
2376 MakeDirty();
2377 return rv;
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{};
2391 FileInfo* pInfo;
2393 if (aFollowLinks) {
2394 MOZ_TRY(ResolveAndStat());
2395 pInfo = &mFileInfo;
2396 } else {
2397 MOZ_TRY(GetFileInfo(mWorkingPath, &symlinkInfo));
2398 pInfo = &symlinkInfo;
2401 switch (aTimeField) {
2402 case TimeField::AccessedTime:
2403 *aTime = pInfo->accessTime / PR_USEC_PER_MSEC;
2404 break;
2406 case TimeField::ModifiedTime:
2407 *aTime = pInfo->modifyTime / PR_USEC_PER_MSEC;
2408 break;
2410 default:
2411 MOZ_CRASH("Unknown time field");
2414 return NS_OK;
2417 NS_IMETHODIMP
2418 nsLocalFile::GetLastAccessedTime(PRTime* aLastAccessedTime) {
2419 return GetDateImpl(aLastAccessedTime, TimeField::AccessedTime,
2420 /* aFollowSymlinks = */ true);
2423 NS_IMETHODIMP
2424 nsLocalFile::GetLastAccessedTimeOfLink(PRTime* aLastAccessedTime) {
2425 return GetDateImpl(aLastAccessedTime, TimeField::AccessedTime,
2426 /* aFollowSymlinks = */ false);
2429 NS_IMETHODIMP
2430 nsLocalFile::SetLastAccessedTime(PRTime aLastAccessedTime) {
2431 return SetDateImpl(aLastAccessedTime, TimeField::AccessedTime);
2434 NS_IMETHODIMP
2435 nsLocalFile::SetLastAccessedTimeOfLink(PRTime aLastAccessedTime) {
2436 return SetLastAccessedTime(aLastAccessedTime);
2439 NS_IMETHODIMP
2440 nsLocalFile::GetLastModifiedTime(PRTime* aLastModifiedTime) {
2441 return GetDateImpl(aLastModifiedTime, TimeField::ModifiedTime,
2442 /* aFollowSymlinks = */ true);
2445 NS_IMETHODIMP
2446 nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModifiedTime) {
2447 return GetDateImpl(aLastModifiedTime, TimeField::ModifiedTime,
2448 /* aFollowSymlinks = */ false);
2451 NS_IMETHODIMP
2452 nsLocalFile::SetLastModifiedTime(PRTime aLastModifiedTime) {
2453 return SetDateImpl(aLastModifiedTime, TimeField::ModifiedTime);
2456 NS_IMETHODIMP
2457 nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModifiedTime) {
2458 return SetLastModifiedTime(aLastModifiedTime);
2461 NS_IMETHODIMP
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;
2474 return NS_OK;
2477 NS_IMETHODIMP
2478 nsLocalFile::GetCreationTimeOfLink(PRTime* aCreationTime) {
2479 CHECK_mWorkingPath();
2481 if (NS_WARN_IF(!aCreationTime)) {
2482 return NS_ERROR_INVALID_ARG;
2485 FileInfo info;
2486 nsresult rv = GetFileInfo(mWorkingPath, &info);
2487 NS_ENSURE_SUCCESS(rv, rv);
2489 *aCreationTime = info.creationTime / PR_USEC_PER_MSEC;
2491 return NS_OK;
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.
2501 HANDLE file =
2502 ::CreateFileW(mWorkingPath.get(), // pointer to name of the file
2503 GENERIC_WRITE, // access (write) mode
2504 0, // share mode
2505 nullptr, // pointer to security attributes
2506 OPEN_EXISTING, // how to create
2507 FILE_FLAG_BACKUP_SEMANTICS, // file attributes
2508 nullptr);
2510 if (file == INVALID_HANDLE_VALUE) {
2511 return ConvertWinError(GetLastError());
2514 FILETIME ft;
2515 SYSTEMTIME st;
2516 PRExplodedTime pret;
2518 if (aTime == 0) {
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;
2525 st.wMonth =
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) {
2538 accessTime = &ft;
2539 } else {
2540 modifiedTime = &ft;
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());
2551 CloseHandle(file);
2553 if (NS_SUCCEEDED(rv)) {
2554 MakeDirty();
2557 return rv;
2560 NS_IMETHODIMP
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)) {
2572 return rv;
2575 bool isWritable = false;
2576 rv = IsWritable(&isWritable);
2577 if (NS_FAILED(rv)) {
2578 return rv;
2581 bool isExecutable = false;
2582 rv = IsExecutable(&isExecutable);
2583 if (NS_FAILED(rv)) {
2584 return rv;
2587 *aPermissions = PR_IRUSR | PR_IRGRP | PR_IROTH; // all read
2588 if (isWritable) {
2589 *aPermissions |= PR_IWUSR | PR_IWGRP | PR_IWOTH; // all write
2591 if (isExecutable) {
2592 *aPermissions |= PR_IXUSR | PR_IXGRP | PR_IXOTH; // all execute
2595 return NS_OK;
2598 NS_IMETHODIMP
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
2618 if (isWritable) {
2619 *aPermissions |= PR_IWUSR | PR_IWGRP | PR_IWOTH; // all write
2622 return NS_OK;
2625 NS_IMETHODIMP
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)) {
2636 return rv;
2639 // windows only knows about the following permissions
2640 int mode = 0;
2641 if (aPermissions & (PR_IRUSR | PR_IRGRP | PR_IROTH)) { // any read
2642 mode |= _S_IREAD;
2644 if (aPermissions & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) { // any write
2645 mode |= _S_IWRITE;
2648 if (_wchmod(mResolvedPath.get(), mode) == -1) {
2649 return NS_ERROR_FAILURE;
2652 return NS_OK;
2655 NS_IMETHODIMP
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
2661 int mode = 0;
2662 if (aPermissions & (PR_IRUSR | PR_IRGRP | PR_IROTH)) { // any read
2663 mode |= _S_IREAD;
2665 if (aPermissions & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) { // any write
2666 mode |= _S_IWRITE;
2669 if (_wchmod(mWorkingPath.get(), mode) == -1) {
2670 return NS_ERROR_FAILURE;
2673 return NS_OK;
2676 NS_IMETHODIMP
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)) {
2684 return rv;
2687 *aFileSize = mFileInfo.size;
2688 return NS_OK;
2691 NS_IMETHODIMP
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.
2703 FileInfo info{};
2704 if (NS_FAILED(GetFileInfo(mWorkingPath, &info))) {
2705 return NS_ERROR_FILE_INVALID_PATH;
2708 *aFileSize = info.size;
2709 return NS_OK;
2712 NS_IMETHODIMP
2713 nsLocalFile::SetFileSize(int64_t aFileSize) {
2714 // Check we are correctly initialized.
2715 CHECK_mWorkingPath();
2717 HANDLE hFile =
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
2724 nullptr);
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)) {
2736 MakeDirty();
2737 rv = NS_OK;
2740 CloseHandle(hFile);
2741 return rv;
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;
2754 return NS_OK;
2757 return ConvertWinError(::GetLastError());
2760 NS_IMETHODIMP
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)) {
2773 return 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);
2785 int64_t dummy = 0;
2786 return GetDiskSpaceAttributes(mResolvedPath, aDiskSpaceAvailable, &dummy);
2789 NS_IMETHODIMP
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)) {
2800 return 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);
2811 int64_t dummy = 0;
2812 return GetDiskSpaceAttributes(mResolvedPath, &dummy, aDiskCapacity);
2815 NS_IMETHODIMP
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) {
2826 *aParent = nullptr;
2827 return NS_OK;
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'\\') {
2841 *aParent = nullptr;
2842 return NS_OK;
2845 nsAutoString parentPath(mWorkingPath);
2847 if (offset > 0) {
2848 parentPath.Truncate(offset);
2849 } else {
2850 parentPath.AssignLiteral("\\\\.");
2853 nsCOMPtr<nsIFile> localFile;
2854 nsresult rv = NewLocalFile(parentPath, mUseDOSDevicePathSyntax,
2855 getter_AddRefs(localFile));
2856 if (NS_FAILED(rv)) {
2857 return rv;
2860 localFile.forget(aParent);
2861 return NS_OK;
2864 NS_IMETHODIMP
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;
2872 *aResult = false;
2874 MakeDirty();
2875 nsresult rv = ResolveAndStat();
2876 *aResult = NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_IS_LOCKED;
2878 return NS_OK;
2881 NS_IMETHODIMP
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;
2891 return NS_OK;
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)) {
2896 return rv;
2898 if (*aIsWritable) {
2899 return NS_OK;
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;
2906 return NS_OK;
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)) {
2911 return 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.
2917 if (*aIsWritable) {
2918 PRFileDesc* file;
2919 rv = OpenFile(mResolvedPath, PR_WRONLY, 0, false, &file);
2920 if (NS_SUCCEEDED(rv)) {
2921 PR_Close(file);
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;
2928 } else {
2929 return rv;
2932 return NS_OK;
2935 NS_IMETHODIMP
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;
2943 *aResult = false;
2945 nsresult rv = ResolveAndStat();
2946 if (NS_FAILED(rv)) {
2947 return rv;
2950 *aResult = true;
2951 return NS_OK;
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;
2962 *aResult = false;
2964 nsresult rv;
2966 // only files can be executables
2967 bool isFile;
2968 rv = IsFile(&isFile);
2969 if (NS_FAILED(rv)) {
2970 return rv;
2972 if (!isFile) {
2973 return NS_OK;
2976 // TODO: shouldn't we be checking mFollowSymlinks here?
2977 bool symLink = false;
2978 rv = IsSymlink(&symLink);
2979 if (NS_FAILED(rv)) {
2980 return rv;
2983 nsAutoString path;
2984 if (symLink) {
2985 GetTarget(path);
2986 } else {
2987 GetPath(path);
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--);
2997 // Get extension.
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.
3010 *aResult = true;
3011 break;
3016 return NS_OK;
3019 NS_IMETHODIMP
3020 nsLocalFile::IsExecutable(bool* aResult) {
3021 return LookupExtensionIn(sExecutableExts, std::size(sExecutableExts),
3022 aResult);
3025 NS_IMETHODIMP
3026 nsLocalFile::IsDirectory(bool* aResult) {
3027 return HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, aResult);
3030 NS_IMETHODIMP
3031 nsLocalFile::IsFile(bool* aResult) {
3032 nsresult rv = HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, aResult);
3033 if (NS_SUCCEEDED(rv)) {
3034 *aResult = !*aResult;
3036 return rv;
3039 NS_IMETHODIMP
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)) {
3051 return rv;
3054 DWORD attributes = GetFileAttributesW(mResolvedPath.get());
3055 if (INVALID_FILE_ATTRIBUTES == attributes) {
3056 return ConvertWinError(GetLastError());
3059 *aResult = ((attributes & aFileAttrib) != 0);
3060 return NS_OK;
3063 NS_IMETHODIMP
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
3073 *aResult = false;
3074 return NS_OK;
3077 NS_IMETHODIMP
3078 nsLocalFile::IsSpecial(bool* aResult) {
3079 return HasFileAttribute(FILE_ATTRIBUTE_SYSTEM, aResult);
3082 NS_IMETHODIMP
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));
3092 if (!lf) {
3093 *aResult = false;
3094 return NS_OK;
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;
3107 } else {
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)) {
3135 *aResult = true;
3136 return NS_OK;
3139 EnsureShortPath();
3140 lf->GetCanonicalPath(inFilePath);
3141 workingPath = mShortWorkingPath;
3142 *aResult =
3143 equalPath(workingPath, inFilePath, removePathPrefix, removeInPathPrefix);
3145 return NS_OK;
3148 NS_IMETHODIMP
3149 nsLocalFile::Contains(nsIFile* aInFile, bool* aResult) {
3150 // Check we are correctly initialized.
3151 CHECK_mWorkingPath();
3153 *aResult = false;
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) {
3171 *aResult = true;
3175 return NS_OK;
3178 NS_IMETHODIMP
3179 nsLocalFile::GetTarget(nsAString& aResult) {
3180 aResult.Truncate();
3181 Resolve();
3183 MOZ_ASSERT_IF(
3184 mUseDOSDevicePathSyntax,
3185 !FilePreferences::StartsWithDiskDesignatorAndBackslash(mResolvedPath));
3187 aResult = mResolvedPath;
3188 return NS_OK;
3191 NS_IMETHODIMP
3192 nsLocalFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator** aEntries) {
3193 nsresult rv;
3195 *aEntries = nullptr;
3196 if (mWorkingPath.EqualsLiteral("\\\\.")) {
3197 RefPtr<nsDriveEnumerator> drives =
3198 new nsDriveEnumerator(mUseDOSDevicePathSyntax);
3199 rv = drives->Init();
3200 if (NS_FAILED(rv)) {
3201 return rv;
3203 drives.forget(aEntries);
3204 return NS_OK;
3207 RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator();
3208 rv = dirEnum->Init(this);
3209 if (NS_FAILED(rv)) {
3210 return rv;
3213 dirEnum.forget(aEntries);
3215 return NS_OK;
3218 NS_IMETHODIMP
3219 nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor) {
3220 CopyUTF16toUTF8(mWorkingPath, aPersistentDescriptor);
3221 return NS_OK;
3224 NS_IMETHODIMP
3225 nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor) {
3226 if (IsUtf8(aPersistentDescriptor)) {
3227 return InitWithPath(NS_ConvertUTF8toUTF16(aPersistentDescriptor));
3228 } else {
3229 return InitWithNativePath(aPersistentDescriptor);
3233 NS_IMETHODIMP
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;
3244 return NS_OK;
3247 NS_IMETHODIMP
3248 nsLocalFile::SetReadOnly(bool aReadOnly) {
3249 DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get());
3250 if (dwAttrs == INVALID_FILE_ATTRIBUTES) {
3251 return NS_ERROR_FILE_INVALID_PATH;
3254 if (aReadOnly) {
3255 dwAttrs |= FILE_ATTRIBUTE_READONLY;
3256 } else {
3257 dwAttrs &= ~FILE_ATTRIBUTE_READONLY;
3260 if (SetFileAttributesW(mWorkingPath.get(), dwAttrs) == 0) {
3261 return NS_ERROR_FAILURE;
3264 return NS_OK;
3267 NS_IMETHODIMP
3268 nsLocalFile::GetUseDOSDevicePathSyntax(bool* aUseDOSDevicePathSyntax) {
3269 MOZ_ASSERT(aUseDOSDevicePathSyntax);
3271 *aUseDOSDevicePathSyntax = mUseDOSDevicePathSyntax;
3272 return NS_OK;
3275 NS_IMETHODIMP
3276 nsLocalFile::SetUseDOSDevicePathSyntax(bool aUseDOSDevicePathSyntax) {
3277 if (mUseDOSDevicePathSyntax == aUseDOSDevicePathSyntax) {
3278 return NS_OK;
3281 if (mUseDOSDevicePathSyntax) {
3282 if (StringBeginsWith(mWorkingPath, kDevicePathSpecifier)) {
3283 MakeDirty();
3284 // Remove the prefix
3285 mWorkingPath = Substring(mWorkingPath, kDevicePathSpecifier.Length());
3287 } else {
3288 if (FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath)) {
3289 MakeDirty();
3290 // Prepend the prefix
3291 mWorkingPath = kDevicePathSpecifier + mWorkingPath;
3295 mUseDOSDevicePathSyntax = aUseDOSDevicePathSyntax;
3296 return NS_OK;
3299 NS_IMETHODIMP
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) {
3307 return rv;
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));
3316 RevealFile(path);
3317 if (doCoUninitialize) {
3318 CoUninitialize();
3322 return NS_DispatchBackgroundTask(task,
3323 nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK);
3326 NS_IMETHODIMP
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());
3335 *aAttrs = dwAttrs;
3337 return NS_OK;
3340 NS_IMETHODIMP
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());
3353 return NS_OK;
3356 NS_IMETHODIMP
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());
3364 _variant_t args;
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;
3378 } else {
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"};
3416 bool isExecutable;
3417 nsresult rv =
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()) {
3431 return NS_OK;
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;
3448 return NS_OK;
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)) {
3457 return rv;
3461 file.forget(aResult);
3462 return NS_OK;
3465 //-----------------------------------------------------------------------------
3466 // Native (lossy) interface
3467 //-----------------------------------------------------------------------------
3469 NS_IMETHODIMP
3470 nsLocalFile::InitWithNativePath(const nsACString& aFilePath) {
3471 nsAutoString tmp;
3472 nsresult rv = NS_CopyNativeToUnicode(aFilePath, tmp);
3473 if (NS_SUCCEEDED(rv)) {
3474 return InitWithPath(tmp);
3477 return rv;
3480 NS_IMETHODIMP
3481 nsLocalFile::AppendNative(const nsACString& aNode) {
3482 nsAutoString tmp;
3483 nsresult rv = NS_CopyNativeToUnicode(aNode, tmp);
3484 if (NS_SUCCEEDED(rv)) {
3485 return Append(tmp);
3488 return rv;
3491 NS_IMETHODIMP
3492 nsLocalFile::AppendRelativeNativePath(const nsACString& aNode) {
3493 nsAutoString tmp;
3494 nsresult rv = NS_CopyNativeToUnicode(aNode, tmp);
3495 if (NS_SUCCEEDED(rv)) {
3496 return AppendRelativePath(tmp);
3498 return rv;
3501 NS_IMETHODIMP
3502 nsLocalFile::GetNativeLeafName(nsACString& aLeafName) {
3503 // NS_WARNING("This API is lossy. Use GetLeafName !");
3504 nsAutoString tmp;
3505 nsresult rv = GetLeafName(tmp);
3506 if (NS_SUCCEEDED(rv)) {
3507 rv = NS_CopyUnicodeToNative(tmp, aLeafName);
3510 return rv;
3513 NS_IMETHODIMP
3514 nsLocalFile::SetNativeLeafName(const nsACString& aLeafName) {
3515 nsAutoString tmp;
3516 nsresult rv = NS_CopyNativeToUnicode(aLeafName, tmp);
3517 if (NS_SUCCEEDED(rv)) {
3518 return SetLeafName(tmp);
3521 return rv;
3524 NS_IMETHODIMP
3525 nsLocalFile::HostPath(JSContext* aCx, dom::Promise** aPromise) {
3526 return NS_ERROR_NOT_IMPLEMENTED;
3529 nsString nsLocalFile::NativePath() { return mWorkingPath; }
3531 nsCString nsIFile::HumanReadablePath() {
3532 nsString path;
3533 DebugOnly<nsresult> rv = GetPath(path);
3534 MOZ_ASSERT(NS_SUCCEEDED(rv));
3535 return NS_ConvertUTF16toUTF8(path);
3538 NS_IMETHODIMP
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);
3547 nsAutoString tmp;
3548 nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
3549 if (NS_SUCCEEDED(rv)) {
3550 return CopyTo(aNewParentDir, tmp);
3553 return rv;
3556 NS_IMETHODIMP
3557 nsLocalFile::CopyToFollowingLinksNative(nsIFile* aNewParentDir,
3558 const nsACString& aNewName) {
3559 if (aNewName.IsEmpty()) {
3560 return CopyToFollowingLinks(aNewParentDir, u""_ns);
3563 nsAutoString tmp;
3564 nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
3565 if (NS_SUCCEEDED(rv)) {
3566 return CopyToFollowingLinks(aNewParentDir, tmp);
3569 return rv;
3572 NS_IMETHODIMP
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);
3581 nsAutoString tmp;
3582 nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
3583 if (NS_SUCCEEDED(rv)) {
3584 return MoveTo(aNewParentDir, tmp);
3587 return rv;
3590 NS_IMETHODIMP
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);
3600 nsAutoString tmp;
3601 nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
3602 if (NS_SUCCEEDED(rv)) {
3603 return MoveToFollowingLinks(aNewParentDir, tmp);
3606 return rv;
3609 NS_IMETHODIMP
3610 nsLocalFile::GetNativeTarget(nsACString& aResult) {
3611 // Check we are correctly initialized.
3612 CHECK_mWorkingPath();
3614 NS_WARNING("This API is lossy. Use GetTarget !");
3615 nsAutoString tmp;
3616 nsresult rv = GetTarget(tmp);
3617 if (NS_SUCCEEDED(rv)) {
3618 rv = NS_CopyUnicodeToNative(tmp, aResult);
3621 return rv;
3624 nsresult NS_NewNativeLocalFile(const nsACString& aPath, nsIFile** aResult) {
3625 nsAutoString buf;
3626 nsresult rv = NS_CopyNativeToUnicode(aPath, buf);
3627 if (NS_FAILED(rv)) {
3628 *aResult = nullptr;
3629 return rv;
3631 return NS_NewLocalFile(buf, aResult);
3634 void nsLocalFile::EnsureShortPath() {
3635 if (!mShortWorkingPath.IsEmpty()) {
3636 return;
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);
3647 } else {
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);
3674 return NS_OK;
3677 NS_IMETHODIMP
3678 nsDriveEnumerator::HasMoreElements(bool* aHasMore) {
3679 *aHasMore = *mStartOfCurrentDrive != L'\0';
3680 return NS_OK;
3683 NS_IMETHODIMP
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') {
3690 *aNext = nullptr;
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;
3699 nsIFile* file;
3700 nsresult rv = NewLocalFile(drive, mUseDOSDevicePathSyntax, &file);
3702 *aNext = file;
3703 return rv;