Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / xpcom / io / nsLocalFileWin.cpp
blob13fea1d2ca899b4a865c54d8c0a34127a112c760
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->GetMostRecentWindow(u"navigator:browser",
112 getter_AddRefs(navWin));
113 if (NS_FAILED(rv) || !navWin) {
114 return nullptr;
117 nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin);
118 nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(win);
119 if (!widget) {
120 return nullptr;
123 return reinterpret_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW));
126 nsresult nsLocalFile::RevealFile(const nsString& aResolvedPath) {
127 MOZ_ASSERT(!NS_IsMainThread(), "Don't run on the main thread");
129 DWORD attributes = GetFileAttributesW(aResolvedPath.get());
130 if (INVALID_FILE_ATTRIBUTES == attributes) {
131 return NS_ERROR_FILE_INVALID_PATH;
134 HRESULT hr;
135 if (attributes & FILE_ATTRIBUTE_DIRECTORY) {
136 // We have a directory so we should open the directory itself.
137 LPITEMIDLIST dir = ILCreateFromPathW(aResolvedPath.get());
138 if (!dir) {
139 return NS_ERROR_FAILURE;
142 LPCITEMIDLIST selection[] = {dir};
143 UINT count = ArrayLength(selection);
145 // Perform the open of the directory.
146 hr = SHOpenFolderAndSelectItems(dir, count, selection, 0);
147 CoTaskMemFree(dir);
148 } else {
149 int32_t len = aResolvedPath.Length();
150 // We don't currently handle UNC long paths of the form \\?\ anywhere so
151 // this should be fine.
152 if (len > MAX_PATH) {
153 return NS_ERROR_FILE_INVALID_PATH;
155 WCHAR parentDirectoryPath[MAX_PATH + 1] = {0};
156 wcsncpy(parentDirectoryPath, aResolvedPath.get(), MAX_PATH);
157 PathRemoveFileSpecW(parentDirectoryPath);
159 // We have a file so we should open the parent directory.
160 LPITEMIDLIST dir = ILCreateFromPathW(parentDirectoryPath);
161 if (!dir) {
162 return NS_ERROR_FAILURE;
165 // Set the item in the directory to select to the file we want to reveal.
166 LPITEMIDLIST item = ILCreateFromPathW(aResolvedPath.get());
167 if (!item) {
168 CoTaskMemFree(dir);
169 return NS_ERROR_FAILURE;
172 LPCITEMIDLIST selection[] = {item};
173 UINT count = ArrayLength(selection);
175 // Perform the selection of the file.
176 hr = SHOpenFolderAndSelectItems(dir, count, selection, 0);
178 CoTaskMemFree(dir);
179 CoTaskMemFree(item);
182 return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
185 // static
186 bool nsLocalFile::CheckForReservedFileName(const nsString& aFileName) {
187 static const nsLiteralString forbiddenNames[] = {
188 u"COM1"_ns, u"COM2"_ns, u"COM3"_ns, u"COM4"_ns, u"COM5"_ns, u"COM6"_ns,
189 u"COM7"_ns, u"COM8"_ns, u"COM9"_ns, u"LPT1"_ns, u"LPT2"_ns, u"LPT3"_ns,
190 u"LPT4"_ns, u"LPT5"_ns, u"LPT6"_ns, u"LPT7"_ns, u"LPT8"_ns, u"LPT9"_ns,
191 u"CON"_ns, u"PRN"_ns, u"AUX"_ns, u"NUL"_ns, u"CLOCK$"_ns};
193 for (const nsLiteralString& forbiddenName : forbiddenNames) {
194 if (StringBeginsWith(aFileName, forbiddenName,
195 nsASCIICaseInsensitiveStringComparator)) {
196 // invalid name is either the entire string, or a prefix with a period
197 if (aFileName.Length() == forbiddenName.Length() ||
198 aFileName.CharAt(forbiddenName.Length()) == char16_t('.')) {
199 return true;
204 return false;
207 class nsDriveEnumerator : public nsSimpleEnumerator,
208 public nsIDirectoryEnumerator {
209 public:
210 explicit nsDriveEnumerator(bool aUseDOSDevicePathSyntax);
211 NS_DECL_ISUPPORTS_INHERITED
212 NS_DECL_NSISIMPLEENUMERATOR
213 NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::)
214 nsresult Init();
216 const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); }
218 NS_IMETHOD GetNextFile(nsIFile** aResult) override {
219 bool hasMore = false;
220 nsresult rv = HasMoreElements(&hasMore);
221 if (NS_FAILED(rv) || !hasMore) {
222 return rv;
224 nsCOMPtr<nsISupports> next;
225 rv = GetNext(getter_AddRefs(next));
226 NS_ENSURE_SUCCESS(rv, rv);
228 nsCOMPtr<nsIFile> result = do_QueryInterface(next);
229 result.forget(aResult);
230 return NS_OK;
233 NS_IMETHOD Close() override { return NS_OK; }
235 private:
236 virtual ~nsDriveEnumerator();
238 /* mDrives stores the null-separated drive names.
239 * Init sets them.
240 * HasMoreElements checks mStartOfCurrentDrive.
241 * GetNext advances mStartOfCurrentDrive.
243 nsString mDrives;
244 nsAString::const_iterator mStartOfCurrentDrive;
245 nsAString::const_iterator mEndOfDrivesString;
246 const bool mUseDOSDevicePathSyntax;
249 //-----------------------------------------------------------------------------
250 // static helper functions
251 //-----------------------------------------------------------------------------
254 * While not comprehensive, this will map many common Windows error codes to a
255 * corresponding nsresult. If an unmapped error is encountered, the hex error
256 * code will be logged to stderr. Error codes, names, and descriptions can be
257 * found at the following MSDN page:
258 * https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes
260 * \note When adding more mappings here, it must be checked if there's code that
261 * depends on the current generic NS_ERROR_MODULE_WIN32 mapping for such error
262 * codes.
264 static nsresult ConvertWinError(DWORD aWinErr) {
265 nsresult rv;
267 switch (aWinErr) {
268 case ERROR_FILE_NOT_FOUND:
269 [[fallthrough]]; // to NS_ERROR_FILE_NOT_FOUND
270 case ERROR_PATH_NOT_FOUND:
271 [[fallthrough]]; // to NS_ERROR_FILE_NOT_FOUND
272 case ERROR_INVALID_DRIVE:
273 rv = NS_ERROR_FILE_NOT_FOUND;
274 break;
275 case ERROR_ACCESS_DENIED:
276 [[fallthrough]]; // to NS_ERROR_FILE_ACCESS_DENIED
277 case ERROR_NOT_SAME_DEVICE:
278 [[fallthrough]]; // to NS_ERROR_FILE_ACCESS_DENIED
279 case ERROR_CANNOT_MAKE:
280 [[fallthrough]]; // to NS_ERROR_FILE_ACCESS_DENIED
281 case ERROR_CONTENT_BLOCKED:
282 rv = NS_ERROR_FILE_ACCESS_DENIED;
283 break;
284 case ERROR_SHARING_VIOLATION: // CreateFile without sharing flags
285 [[fallthrough]]; // to NS_ERROR_FILE_IS_LOCKED
286 case ERROR_LOCK_VIOLATION: // LockFile, LockFileEx
287 rv = NS_ERROR_FILE_IS_LOCKED;
288 break;
289 case ERROR_NOT_ENOUGH_MEMORY:
290 [[fallthrough]]; // to NS_ERROR_OUT_OF_MEMORY
291 case ERROR_NO_SYSTEM_RESOURCES:
292 rv = NS_ERROR_OUT_OF_MEMORY;
293 break;
294 case ERROR_DIR_NOT_EMPTY:
295 [[fallthrough]]; // to NS_ERROR_FILE_DIR_NOT_EMPTY
296 case ERROR_CURRENT_DIRECTORY:
297 rv = NS_ERROR_FILE_DIR_NOT_EMPTY;
298 break;
299 case ERROR_WRITE_PROTECT:
300 rv = NS_ERROR_FILE_READ_ONLY;
301 break;
302 case ERROR_HANDLE_DISK_FULL:
303 [[fallthrough]]; // to NS_ERROR_FILE_NO_DEVICE_SPACE
304 case ERROR_DISK_FULL:
305 rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
306 break;
307 case ERROR_FILE_EXISTS:
308 [[fallthrough]]; // to NS_ERROR_FILE_ALREADY_EXISTS
309 case ERROR_ALREADY_EXISTS:
310 rv = NS_ERROR_FILE_ALREADY_EXISTS;
311 break;
312 case ERROR_FILENAME_EXCED_RANGE:
313 rv = NS_ERROR_FILE_NAME_TOO_LONG;
314 break;
315 case ERROR_DIRECTORY:
316 rv = NS_ERROR_FILE_NOT_DIRECTORY;
317 break;
318 case ERROR_FILE_CORRUPT:
319 [[fallthrough]]; // to NS_ERROR_FILE_FS_CORRUPTED
320 case ERROR_DISK_CORRUPT:
321 rv = NS_ERROR_FILE_FS_CORRUPTED;
322 break;
323 case ERROR_DEVICE_HARDWARE_ERROR:
324 [[fallthrough]]; // to NS_ERROR_FILE_DEVICE_FAILURE
325 case ERROR_DEVICE_NOT_CONNECTED:
326 [[fallthrough]]; // to NS_ERROR_FILE_DEVICE_FAILURE
327 case ERROR_DEV_NOT_EXIST:
328 [[fallthrough]]; // to NS_ERROR_FILE_DEVICE_FAILURE
329 case ERROR_IO_DEVICE:
330 rv = NS_ERROR_FILE_DEVICE_FAILURE;
331 break;
332 case ERROR_NOT_READY:
333 rv = NS_ERROR_FILE_DEVICE_TEMPORARY_FAILURE;
334 break;
335 case ERROR_INVALID_NAME:
336 rv = NS_ERROR_FILE_INVALID_PATH;
337 break;
338 case ERROR_INVALID_BLOCK:
339 [[fallthrough]]; // to NS_ERROR_FILE_INVALID_HANDLE
340 case ERROR_INVALID_HANDLE:
341 [[fallthrough]]; // to NS_ERROR_FILE_INVALID_HANDLE
342 case ERROR_ARENA_TRASHED:
343 rv = NS_ERROR_FILE_INVALID_HANDLE;
344 break;
345 case 0:
346 rv = NS_OK;
347 break;
348 default:
349 printf_stderr(
350 "ConvertWinError received an unrecognized WinError: 0x%" PRIx32 "\n",
351 static_cast<uint32_t>(aWinErr));
352 MOZ_ASSERT((aWinErr & 0xFFFF) == aWinErr);
353 rv = NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_WIN32, aWinErr & 0xFFFF);
354 break;
356 return rv;
359 // Check whether a path is a volume root. Expects paths to be \-terminated.
360 static bool IsRootPath(const nsAString& aPath) {
361 // Easy cases first:
362 if (aPath.Last() != L'\\') {
363 return false;
365 if (StringEndsWith(aPath, u":\\"_ns)) {
366 return true;
369 nsAString::const_iterator begin, end;
370 aPath.BeginReading(begin);
371 aPath.EndReading(end);
372 // We know we've got a trailing slash, skip that:
373 end--;
374 // Find the next last slash:
375 if (RFindInReadable(u"\\"_ns, begin, end)) {
376 // Reset iterator:
377 aPath.EndReading(end);
378 end--;
379 auto lastSegment = Substring(++begin, end);
380 if (lastSegment.IsEmpty()) {
381 return false;
384 // Check if we end with e.g. "c$", a drive letter in UNC or network shares
385 if (lastSegment.Last() == L'$' && lastSegment.Length() == 2 &&
386 IsAsciiAlpha(lastSegment.First())) {
387 return true;
389 // Volume GUID paths:
390 if (StringBeginsWith(lastSegment, u"Volume{"_ns) &&
391 lastSegment.Last() == L'}') {
392 return true;
395 return false;
398 static auto kSpecialNTFSFilesInRoot = {
399 u"$MFT"_ns, u"$MFTMirr"_ns, u"$LogFile"_ns, u"$Volume"_ns,
400 u"$AttrDef"_ns, u"$Bitmap"_ns, u"$Boot"_ns, u"$BadClus"_ns,
401 u"$Secure"_ns, u"$UpCase"_ns, u"$Extend"_ns};
402 static bool IsSpecialNTFSPath(const nsAString& aFilePath) {
403 nsAString::const_iterator begin, end;
404 aFilePath.BeginReading(begin);
405 aFilePath.EndReading(end);
406 auto iter = begin;
407 // Early exit if there's no '$' (common case)
408 if (!FindCharInReadable(L'$', iter, end)) {
409 return false;
412 iter = begin;
413 // Any use of ':$' is illegal in filenames anyway; while we support some
414 // ADS stuff (ie ":Zone.Identifier"), none of them use the ':$' syntax:
415 if (FindInReadable(u":$"_ns, iter, end)) {
416 return true;
419 auto normalized = mozilla::MakeUniqueFallible<wchar_t[]>(MAX_PATH);
420 if (!normalized) {
421 return true;
423 auto flatPath = PromiseFlatString(aFilePath);
424 auto fullPathRV =
425 GetFullPathNameW(flatPath.get(), MAX_PATH - 1, normalized.get(), nullptr);
426 if (fullPathRV == 0 || fullPathRV > MAX_PATH - 1) {
427 return false;
430 nsString normalizedPath(normalized.get());
431 normalizedPath.BeginReading(begin);
432 normalizedPath.EndReading(end);
433 iter = begin;
434 auto kDelimiters = u"\\:"_ns;
435 while (iter != end && FindCharInReadable(L'$', iter, end)) {
436 for (auto str : kSpecialNTFSFilesInRoot) {
437 if (StringBeginsWith(Substring(iter, end), str,
438 nsCaseInsensitiveStringComparator)) {
439 // If we're enclosed by separators or the beginning/end of the string,
440 // this is one of the special files. Check if we're on a volume root.
441 auto iterCopy = iter;
442 iterCopy.advance(str.Length());
443 // We check for both \ and : here because the filename could be
444 // followd by a colon and a stream name/type, which shouldn't affect
445 // our check:
446 if (iterCopy == end || kDelimiters.Contains(*iterCopy)) {
447 iterCopy = iter;
448 // At the start of this path component, we don't need to care about
449 // colons: we would have caught those in the check for `:$` above.
450 if (iterCopy == begin || *(--iterCopy) == L'\\') {
451 return IsRootPath(Substring(begin, iter));
456 iter++;
459 return false;
462 //-----------------------------------------------------------------------------
463 // We need the following three definitions to make |OpenFile| convert a file
464 // handle to an NSPR file descriptor correctly when |O_APPEND| flag is
465 // specified. It is defined in a private header of NSPR (primpl.h) we can't
466 // include. As a temporary workaround until we decide how to extend
467 // |PR_ImportFile|, we define it here. Currently, |_PR_HAVE_PEEK_BUFFER|
468 // and |PR_STRICT_ADDR_LEN| are not defined for the 'w95'-dependent portion
469 // of NSPR so that fields of |PRFilePrivate| #ifdef'd by them are not copied.
470 // Similarly, |_MDFileDesc| is taken from nsprpub/pr/include/md/_win95.h.
471 // In an unlikely case we switch to 'NT'-dependent NSPR AND this temporary
472 // workaround last beyond the switch, |PRFilePrivate| and |_MDFileDesc|
473 // need to be changed to match the definitions for WinNT.
474 //-----------------------------------------------------------------------------
475 typedef enum {
476 _PR_TRI_TRUE = 1,
477 _PR_TRI_FALSE = 0,
478 _PR_TRI_UNKNOWN = -1
479 } _PRTriStateBool;
481 struct _MDFileDesc {
482 PROsfd osfd;
485 struct PRFilePrivate {
486 int32_t state;
487 bool nonblocking;
488 _PRTriStateBool inheritable;
489 PRFileDesc* next;
490 int lockCount; /* 0: not locked
491 * -1: a native lockfile call is in progress
492 * > 0: # times the file is locked */
493 bool appendMode;
494 _MDFileDesc md;
497 //-----------------------------------------------------------------------------
498 // Six static methods defined below (OpenFile, FileTimeToPRTime, GetFileInfo,
499 // OpenDir, CloseDir, ReadDir) should go away once the corresponding
500 // UTF-16 APIs are implemented on all the supported platforms (or at least
501 // Windows 9x/ME) in NSPR. Currently, they're only implemented on
502 // Windows NT4 or later. (bug 330665)
503 //-----------------------------------------------------------------------------
505 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} :
506 // PR_Open and _PR_MD_OPEN
507 nsresult OpenFile(const nsString& aName, int aOsflags, int aMode,
508 bool aShareDelete, PRFileDesc** aFd) {
509 int32_t access = 0;
511 int32_t shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
512 int32_t disposition = 0;
513 int32_t attributes = 0;
515 if (aShareDelete) {
516 shareMode |= FILE_SHARE_DELETE;
519 if (aOsflags & PR_SYNC) {
520 attributes = FILE_FLAG_WRITE_THROUGH;
522 if (aOsflags & PR_RDONLY || aOsflags & PR_RDWR) {
523 access |= GENERIC_READ;
525 if (aOsflags & PR_WRONLY || aOsflags & PR_RDWR) {
526 access |= GENERIC_WRITE;
529 if (aOsflags & PR_CREATE_FILE && aOsflags & PR_EXCL) {
530 disposition = CREATE_NEW;
531 } else if (aOsflags & PR_CREATE_FILE) {
532 if (aOsflags & PR_TRUNCATE) {
533 disposition = CREATE_ALWAYS;
534 } else {
535 disposition = OPEN_ALWAYS;
537 } else {
538 if (aOsflags & PR_TRUNCATE) {
539 disposition = TRUNCATE_EXISTING;
540 } else {
541 disposition = OPEN_EXISTING;
545 if (aOsflags & nsIFile::DELETE_ON_CLOSE) {
546 attributes |= FILE_FLAG_DELETE_ON_CLOSE;
549 if (aOsflags & nsIFile::OS_READAHEAD) {
550 attributes |= FILE_FLAG_SEQUENTIAL_SCAN;
553 // If no write permissions are requested, and if we are possibly creating
554 // the file, then set the new file as read only.
555 // The flag has no effect if we happen to open the file.
556 if (!(aMode & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) &&
557 disposition != OPEN_EXISTING) {
558 attributes |= FILE_ATTRIBUTE_READONLY;
561 HANDLE file = ::CreateFileW(aName.get(), access, shareMode, nullptr,
562 disposition, attributes, nullptr);
564 if (file == INVALID_HANDLE_VALUE) {
565 *aFd = nullptr;
566 return ConvertWinError(GetLastError());
569 *aFd = PR_ImportFile((PROsfd)file);
570 if (*aFd) {
571 // On Windows, _PR_HAVE_O_APPEND is not defined so that we have to
572 // add it manually. (see |PR_Open| in nsprpub/pr/src/io/prfile.c)
573 (*aFd)->secret->appendMode = (PR_APPEND & aOsflags) ? true : false;
574 return NS_OK;
577 nsresult rv = NS_ErrorAccordingToNSPR();
579 CloseHandle(file);
581 return rv;
584 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} :
585 // PR_FileTimeToPRTime and _PR_FileTimeToPRTime
586 static void FileTimeToPRTime(const FILETIME* aFiletime, PRTime* aPrtm) {
587 #ifdef __GNUC__
588 const PRTime _pr_filetime_offset = 116444736000000000LL;
589 #else
590 const PRTime _pr_filetime_offset = 116444736000000000i64;
591 #endif
593 MOZ_ASSERT(sizeof(FILETIME) == sizeof(PRTime));
594 ::CopyMemory(aPrtm, aFiletime, sizeof(PRTime));
595 #ifdef __GNUC__
596 *aPrtm = (*aPrtm - _pr_filetime_offset) / 10LL;
597 #else
598 *aPrtm = (*aPrtm - _pr_filetime_offset) / 10i64;
599 #endif
602 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} with some
603 // changes : PR_GetFileInfo64, _PR_MD_GETFILEINFO64
604 static nsresult GetFileInfo(const nsString& aName,
605 nsLocalFile::FileInfo* aInfo) {
606 if (aName.IsEmpty()) {
607 return NS_ERROR_INVALID_ARG;
610 // Checking u"?*" for the file path excluding the kDevicePathSpecifier.
611 // ToDo: Check if checking "?" for the file path is still needed.
612 const int32_t offset = StringBeginsWith(aName, kDevicePathSpecifier)
613 ? kDevicePathSpecifier.Length()
614 : 0;
616 if (aName.FindCharInSet(u"?*", offset) != kNotFound) {
617 return NS_ERROR_INVALID_ARG;
620 WIN32_FILE_ATTRIBUTE_DATA fileData;
621 if (!::GetFileAttributesExW(aName.get(), GetFileExInfoStandard, &fileData)) {
622 return ConvertWinError(GetLastError());
625 if (fileData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
626 aInfo->type = PR_FILE_OTHER;
627 } else if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
628 aInfo->type = PR_FILE_DIRECTORY;
629 } else {
630 aInfo->type = PR_FILE_FILE;
633 aInfo->size = fileData.nFileSizeHigh;
634 aInfo->size = (aInfo->size << 32) + fileData.nFileSizeLow;
636 if (0 == fileData.ftCreationTime.dwLowDateTime &&
637 0 == fileData.ftCreationTime.dwHighDateTime) {
638 aInfo->creationTime = aInfo->modifyTime;
639 } else {
640 FileTimeToPRTime(&fileData.ftCreationTime, &aInfo->creationTime);
643 FileTimeToPRTime(&fileData.ftLastAccessTime, &aInfo->accessTime);
644 FileTimeToPRTime(&fileData.ftLastWriteTime, &aInfo->modifyTime);
646 return NS_OK;
649 struct nsDir {
650 HANDLE handle;
651 WIN32_FIND_DATAW data;
652 bool firstEntry;
655 static nsresult OpenDir(const nsString& aName, nsDir** aDir) {
656 if (NS_WARN_IF(!aDir)) {
657 return NS_ERROR_INVALID_ARG;
660 *aDir = nullptr;
662 nsDir* d = new nsDir();
663 nsAutoString filename(aName);
665 // If |aName| ends in a slash or backslash, do not append another backslash.
666 if (filename.Last() == L'/' || filename.Last() == L'\\') {
667 filename.Append('*');
668 } else {
669 filename.AppendLiteral("\\*");
672 filename.ReplaceChar(L'/', L'\\');
674 // FindFirstFileW Will have a last error of ERROR_DIRECTORY if
675 // <file_path>\* is passed in. If <unknown_path>\* is passed in then
676 // ERROR_PATH_NOT_FOUND will be the last error.
677 d->handle = ::FindFirstFileW(filename.get(), &(d->data));
679 if (d->handle == INVALID_HANDLE_VALUE) {
680 delete d;
681 return ConvertWinError(GetLastError());
683 d->firstEntry = true;
685 *aDir = d;
686 return NS_OK;
689 static nsresult ReadDir(nsDir* aDir, PRDirFlags aFlags, nsString& aName) {
690 aName.Truncate();
691 if (NS_WARN_IF(!aDir)) {
692 return NS_ERROR_INVALID_ARG;
695 while (1) {
696 BOOL rv;
697 if (aDir->firstEntry) {
698 aDir->firstEntry = false;
699 rv = 1;
700 } else {
701 rv = ::FindNextFileW(aDir->handle, &(aDir->data));
704 if (rv == 0) {
705 break;
708 const wchar_t* fileName;
709 fileName = (aDir)->data.cFileName;
711 if ((aFlags & PR_SKIP_DOT) && (fileName[0] == L'.') &&
712 (fileName[1] == L'\0')) {
713 continue;
715 if ((aFlags & PR_SKIP_DOT_DOT) && (fileName[0] == L'.') &&
716 (fileName[1] == L'.') && (fileName[2] == L'\0')) {
717 continue;
720 DWORD attrib = aDir->data.dwFileAttributes;
721 if ((aFlags & PR_SKIP_HIDDEN) && (attrib & FILE_ATTRIBUTE_HIDDEN)) {
722 continue;
725 aName = fileName;
726 return NS_OK;
729 DWORD err = GetLastError();
730 return err == ERROR_NO_MORE_FILES ? NS_OK : ConvertWinError(err);
733 static nsresult CloseDir(nsDir*& aDir) {
734 if (NS_WARN_IF(!aDir)) {
735 return NS_ERROR_INVALID_ARG;
738 BOOL isOk = FindClose(aDir->handle);
739 delete aDir;
740 aDir = nullptr;
741 return isOk ? NS_OK : ConvertWinError(GetLastError());
744 //-----------------------------------------------------------------------------
745 // nsDirEnumerator
746 //-----------------------------------------------------------------------------
748 class nsDirEnumerator final : public nsSimpleEnumerator,
749 public nsIDirectoryEnumerator {
750 private:
751 ~nsDirEnumerator() { Close(); }
753 public:
754 NS_DECL_ISUPPORTS_INHERITED
756 NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::)
758 nsDirEnumerator() : mDir(nullptr) {}
760 const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); }
762 nsresult Init(nsIFile* aParent) {
763 nsAutoString filepath;
764 aParent->GetTarget(filepath);
766 if (filepath.IsEmpty()) {
767 aParent->GetPath(filepath);
770 if (filepath.IsEmpty()) {
771 return NS_ERROR_UNEXPECTED;
774 // IsDirectory is not needed here because OpenDir will return
775 // NS_ERROR_FILE_NOT_DIRECTORY if the passed in path is a file.
776 nsresult rv = OpenDir(filepath, &mDir);
777 if (NS_FAILED(rv)) {
778 return rv;
781 mParent = aParent;
782 return NS_OK;
785 NS_IMETHOD HasMoreElements(bool* aResult) override {
786 nsresult rv;
787 if (!mNext && mDir) {
788 nsString name;
789 rv = ReadDir(mDir, PR_SKIP_BOTH, name);
790 if (NS_FAILED(rv)) {
791 return rv;
793 if (name.IsEmpty()) {
794 // end of dir entries
795 rv = CloseDir(mDir);
796 if (NS_FAILED(rv)) {
797 return rv;
800 *aResult = false;
801 return NS_OK;
804 nsCOMPtr<nsIFile> file;
805 rv = mParent->Clone(getter_AddRefs(file));
806 if (NS_FAILED(rv)) {
807 return rv;
810 rv = file->Append(name);
811 if (NS_FAILED(rv)) {
812 return rv;
815 mNext = file.forget();
817 *aResult = mNext != nullptr;
818 if (!*aResult) {
819 Close();
821 return NS_OK;
824 NS_IMETHOD GetNext(nsISupports** aResult) override {
825 nsresult rv;
826 bool hasMore;
827 rv = HasMoreElements(&hasMore);
828 if (NS_FAILED(rv)) {
829 return rv;
831 if (!hasMore) {
832 return NS_ERROR_FAILURE;
835 mNext.forget(aResult);
836 return NS_OK;
839 NS_IMETHOD GetNextFile(nsIFile** aResult) override {
840 *aResult = nullptr;
841 bool hasMore = false;
842 nsresult rv = HasMoreElements(&hasMore);
843 if (NS_FAILED(rv) || !hasMore) {
844 return rv;
846 mNext.forget(aResult);
847 return NS_OK;
850 NS_IMETHOD Close() override {
851 if (mDir) {
852 nsresult rv = CloseDir(mDir);
853 NS_ASSERTION(NS_SUCCEEDED(rv), "close failed");
854 if (NS_FAILED(rv)) {
855 return NS_ERROR_FAILURE;
858 return NS_OK;
861 protected:
862 nsDir* mDir;
863 nsCOMPtr<nsIFile> mParent;
864 nsCOMPtr<nsIFile> mNext;
867 NS_IMPL_ISUPPORTS_INHERITED(nsDirEnumerator, nsSimpleEnumerator,
868 nsIDirectoryEnumerator)
870 //-----------------------------------------------------------------------------
871 // nsLocalFile <public>
872 //-----------------------------------------------------------------------------
874 nsLocalFile::nsLocalFile()
875 : mDirty(true), mResolveDirty(true), mUseDOSDevicePathSyntax(false) {}
877 nsLocalFile::nsLocalFile(const nsAString& aFilePath)
878 : mUseDOSDevicePathSyntax(false) {
879 InitWithPath(aFilePath);
882 nsresult nsLocalFile::nsLocalFileConstructor(const nsIID& aIID,
883 void** aInstancePtr) {
884 if (NS_WARN_IF(!aInstancePtr)) {
885 return NS_ERROR_INVALID_ARG;
888 nsLocalFile* inst = new nsLocalFile();
889 nsresult rv = inst->QueryInterface(aIID, aInstancePtr);
890 if (NS_FAILED(rv)) {
891 delete inst;
892 return rv;
894 return NS_OK;
897 //-----------------------------------------------------------------------------
898 // nsLocalFile::nsISupports
899 //-----------------------------------------------------------------------------
901 NS_IMPL_ISUPPORTS(nsLocalFile, nsIFile, nsILocalFileWin)
903 //-----------------------------------------------------------------------------
904 // nsLocalFile <private>
905 //-----------------------------------------------------------------------------
907 nsLocalFile::nsLocalFile(const nsLocalFile& aOther)
908 : mDirty(true),
909 mResolveDirty(true),
910 mUseDOSDevicePathSyntax(aOther.mUseDOSDevicePathSyntax),
911 mWorkingPath(aOther.mWorkingPath) {}
913 nsresult nsLocalFile::ResolveSymlink() {
914 std::wstring workingPath(mWorkingPath.Data());
915 if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(workingPath)) {
916 return NS_ERROR_FAILURE;
918 mResolvedPath.Assign(workingPath.c_str(), workingPath.length());
919 return NS_OK;
922 // Resolve any shortcuts and stat the resolved path. After a successful return
923 // the path is guaranteed valid and the members of mFileInfo can be used.
924 nsresult nsLocalFile::ResolveAndStat() {
925 // if we aren't dirty then we are already done
926 if (!mDirty) {
927 return NS_OK;
930 AUTO_PROFILER_LABEL("nsLocalFile::ResolveAndStat", OTHER);
931 // we can't resolve/stat anything that isn't a valid NSPR addressable path
932 if (mWorkingPath.IsEmpty()) {
933 return NS_ERROR_FILE_INVALID_PATH;
936 // this is usually correct
937 mResolvedPath.Assign(mWorkingPath);
939 // Make sure root paths have a trailing slash.
940 nsAutoString nsprPath(mWorkingPath);
941 if (mWorkingPath.Length() == 2 && mWorkingPath.CharAt(1) == u':') {
942 nsprPath.Append('\\');
945 // first we will see if the working path exists. If it doesn't then
946 // there is nothing more that can be done
947 nsresult rv = GetFileInfo(nsprPath, &mFileInfo);
948 if (NS_FAILED(rv)) {
949 return rv;
952 if (mFileInfo.type != PR_FILE_OTHER) {
953 mResolveDirty = false;
954 mDirty = false;
955 return NS_OK;
958 // OTHER from GetFileInfo currently means a symlink
959 rv = ResolveSymlink();
960 // Even if it fails we need to have the resolved path equal to working path
961 // for those functions that always use the resolved path.
962 if (NS_FAILED(rv)) {
963 mResolvedPath.Assign(mWorkingPath);
964 return rv;
967 mResolveDirty = false;
968 // get the details of the resolved path
969 rv = GetFileInfo(mResolvedPath, &mFileInfo);
970 if (NS_FAILED(rv)) {
971 return rv;
974 mDirty = false;
975 return NS_OK;
979 * Fills the mResolvedPath member variable with the file or symlink target
980 * if follow symlinks is on. This is a copy of the Resolve parts from
981 * ResolveAndStat. ResolveAndStat is much slower though because of the stat.
983 * @return NS_OK on success.
985 nsresult nsLocalFile::Resolve() {
986 // if we aren't dirty then we are already done
987 if (!mResolveDirty) {
988 return NS_OK;
991 // we can't resolve/stat anything that isn't a valid NSPR addressable path
992 if (mWorkingPath.IsEmpty()) {
993 return NS_ERROR_FILE_INVALID_PATH;
996 // this is usually correct
997 mResolvedPath.Assign(mWorkingPath);
999 // TODO: Implement symlink support
1001 mResolveDirty = false;
1002 return NS_OK;
1005 //-----------------------------------------------------------------------------
1006 // nsLocalFile::nsIFile
1007 //-----------------------------------------------------------------------------
1009 NS_IMETHODIMP
1010 nsLocalFile::Clone(nsIFile** aFile) {
1011 // Just copy-construct ourselves
1012 RefPtr<nsLocalFile> file = new nsLocalFile(*this);
1013 file.forget(aFile);
1015 return NS_OK;
1018 NS_IMETHODIMP
1019 nsLocalFile::InitWithFile(nsIFile* aFile) {
1020 if (NS_WARN_IF(!aFile)) {
1021 return NS_ERROR_INVALID_ARG;
1024 nsAutoString path;
1025 aFile->GetPath(path);
1026 if (path.IsEmpty()) {
1027 return NS_ERROR_INVALID_ARG;
1029 return InitWithPath(path);
1032 NS_IMETHODIMP
1033 nsLocalFile::InitWithPath(const nsAString& aFilePath) {
1034 MakeDirty();
1036 nsAString::const_iterator begin, end;
1037 aFilePath.BeginReading(begin);
1038 aFilePath.EndReading(end);
1040 // input string must not be empty
1041 if (begin == end) {
1042 return NS_ERROR_FAILURE;
1045 char16_t firstChar = *begin;
1046 char16_t secondChar = *(++begin);
1048 // just do a sanity check. if it has any forward slashes, it is not a Native
1049 // path on windows. Also, it must have a colon at after the first char.
1050 if (FindCharInReadable(L'/', begin, end)) {
1051 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1054 if (FilePreferences::IsBlockedUNCPath(aFilePath)) {
1055 return NS_ERROR_FILE_ACCESS_DENIED;
1058 if (secondChar != L':' && (secondChar != L'\\' || firstChar != L'\\')) {
1059 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1062 if (secondChar == L':') {
1063 // Make sure we have a valid drive, later code assumes the drive letter
1064 // is a single char a-z or A-Z.
1065 if (MozPathGetDriveNumber<wchar_t>(aFilePath.Data()) == -1) {
1066 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1070 if (IsSpecialNTFSPath(aFilePath)) {
1071 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1074 mWorkingPath = aFilePath;
1075 // kill any trailing '\'
1076 if (mWorkingPath.Last() == L'\\') {
1077 mWorkingPath.Truncate(mWorkingPath.Length() - 1);
1080 // Bug 1626514: make sure that we don't end up with multiple prefixes.
1082 // Prepend the "\\?\" prefix if the useDOSDevicePathSyntax is set and the path
1083 // starts with a disk designator and backslash.
1084 if (mUseDOSDevicePathSyntax &&
1085 FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath)) {
1086 mWorkingPath = kDevicePathSpecifier + mWorkingPath;
1089 return NS_OK;
1092 // Strip a handler command string of its quotes and parameters.
1093 static void CleanupHandlerPath(nsString& aPath) {
1094 // Example command strings passed into this routine:
1096 // 1) C:\Program Files\Company\some.exe -foo -bar
1097 // 2) C:\Program Files\Company\some.dll
1098 // 3) C:\Windows\some.dll,-foo -bar
1099 // 4) C:\Windows\some.cpl,-foo -bar
1101 int32_t lastCommaPos = aPath.RFindChar(',');
1102 if (lastCommaPos != kNotFound) aPath.Truncate(lastCommaPos);
1104 aPath.Append(' ');
1106 // case insensitive
1107 int32_t index = aPath.LowerCaseFindASCII(".exe ");
1108 if (index == kNotFound) index = aPath.LowerCaseFindASCII(".dll ");
1109 if (index == kNotFound) index = aPath.LowerCaseFindASCII(".cpl ");
1111 if (index != kNotFound) aPath.Truncate(index + 4);
1112 aPath.Trim(" ", true, true);
1115 // Strip the windows host process bootstrap executable rundll32.exe
1116 // from a handler's command string if it exists.
1117 static void StripRundll32(nsString& aCommandString) {
1118 // Example rundll formats:
1119 // C:\Windows\System32\rundll32.exe "path to dll"
1120 // rundll32.exe "path to dll"
1121 // C:\Windows\System32\rundll32.exe "path to dll", var var
1122 // rundll32.exe "path to dll", var var
1124 constexpr auto rundllSegment = "rundll32.exe "_ns;
1125 constexpr auto rundllSegmentShort = "rundll32 "_ns;
1127 // case insensitive
1128 int32_t strLen = rundllSegment.Length();
1129 int32_t index = aCommandString.LowerCaseFindASCII(rundllSegment);
1130 if (index == kNotFound) {
1131 strLen = rundllSegmentShort.Length();
1132 index = aCommandString.LowerCaseFindASCII(rundllSegmentShort);
1135 if (index != kNotFound) {
1136 uint32_t rundllSegmentLength = index + strLen;
1137 aCommandString.Cut(0, rundllSegmentLength);
1141 // Returns the fully qualified path to an application handler based on
1142 // a parameterized command string. Note this routine should not be used
1143 // to launch the associated application as it strips parameters and
1144 // rundll.exe from the string. Designed for retrieving display information
1145 // on a particular handler.
1146 /* static */
1147 bool nsLocalFile::CleanupCmdHandlerPath(nsAString& aCommandHandler) {
1148 nsAutoString handlerCommand(aCommandHandler);
1150 // Straight command path:
1152 // %SystemRoot%\system32\NOTEPAD.EXE var
1153 // "C:\Program Files\iTunes\iTunes.exe" var var
1154 // C:\Program Files\iTunes\iTunes.exe var var
1156 // Example rundll handlers:
1158 // rundll32.exe "%ProgramFiles%\Win...ery\PhotoViewer.dll", var var
1159 // rundll32.exe "%ProgramFiles%\Windows Photo Gallery\PhotoViewer.dll"
1160 // C:\Windows\System32\rundll32.exe "path to dll", var var
1161 // %SystemRoot%\System32\rundll32.exe "%ProgramFiles%\Win...ery\Photo
1162 // Viewer.dll", var var
1164 // Expand environment variables so we have full path strings.
1165 uint32_t bufLength =
1166 ::ExpandEnvironmentStringsW(handlerCommand.get(), nullptr, 0);
1167 if (bufLength == 0) // Error
1168 return false;
1170 auto destination = mozilla::MakeUniqueFallible<wchar_t[]>(bufLength);
1171 if (!destination) return false;
1172 if (!::ExpandEnvironmentStringsW(handlerCommand.get(), destination.get(),
1173 bufLength))
1174 return false;
1176 handlerCommand.Assign(destination.get());
1178 // Remove quotes around paths
1179 handlerCommand.StripChars(u"\"");
1181 // Strip windows host process bootstrap so we can get to the actual
1182 // handler.
1183 StripRundll32(handlerCommand);
1185 // Trim any command parameters so that we have a native path we can
1186 // initialize a local file with.
1187 CleanupHandlerPath(handlerCommand);
1189 aCommandHandler.Assign(handlerCommand);
1190 return true;
1193 NS_IMETHODIMP
1194 nsLocalFile::InitWithCommandLine(const nsAString& aCommandLine) {
1195 nsAutoString commandLine(aCommandLine);
1196 if (!CleanupCmdHandlerPath(commandLine)) {
1197 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1199 return InitWithPath(commandLine);
1202 NS_IMETHODIMP
1203 nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode,
1204 PRFileDesc** aResult) {
1205 nsresult rv = OpenNSPRFileDescMaybeShareDelete(aFlags, aMode, false, aResult);
1206 if (NS_FAILED(rv)) {
1207 return rv;
1210 return NS_OK;
1213 NS_IMETHODIMP
1214 nsLocalFile::OpenANSIFileDesc(const char* aMode, FILE** aResult) {
1215 *aResult = _wfopen(mWorkingPath.get(), NS_ConvertASCIItoUTF16(aMode).get());
1216 if (*aResult) {
1217 return NS_OK;
1220 return NS_ERROR_FAILURE;
1223 static nsresult do_create(nsIFile* aFile, const nsString& aPath,
1224 uint32_t aAttributes) {
1225 PRFileDesc* file;
1226 nsresult rv =
1227 OpenFile(aPath, PR_RDONLY | PR_CREATE_FILE | PR_APPEND | PR_EXCL,
1228 aAttributes, false, &file);
1229 if (file) {
1230 PR_Close(file);
1233 if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
1234 // need to return already-exists for directories (bug 452217)
1235 bool isdir;
1236 if (NS_SUCCEEDED(aFile->IsDirectory(&isdir)) && isdir) {
1237 rv = NS_ERROR_FILE_ALREADY_EXISTS;
1240 return rv;
1243 static nsresult do_mkdir(nsIFile*, const nsString& aPath, uint32_t) {
1244 if (!::CreateDirectoryW(aPath.get(), nullptr)) {
1245 return ConvertWinError(GetLastError());
1247 return NS_OK;
1250 NS_IMETHODIMP
1251 nsLocalFile::Create(uint32_t aType, uint32_t aAttributes, bool aSkipAncestors) {
1252 if (aType != NORMAL_FILE_TYPE && aType != DIRECTORY_TYPE) {
1253 return NS_ERROR_FILE_UNKNOWN_TYPE;
1256 auto* createFunc = (aType == NORMAL_FILE_TYPE ? do_create : do_mkdir);
1258 nsresult rv = createFunc(this, mWorkingPath, aAttributes);
1260 if (NS_SUCCEEDED(rv) || NS_ERROR_FILE_ALREADY_EXISTS == rv ||
1261 aSkipAncestors) {
1262 return rv;
1265 // create directories to target
1267 // A given local file can be either one of these forms:
1269 // - normal: X:\some\path\on\this\drive
1270 // ^--- start here
1272 // - UNC path: \\machine\volume\some\path\on\this\drive
1273 // ^--- start here
1275 // Skip the first 'X:\' for the first form, and skip the first full
1276 // '\\machine\volume\' segment for the second form.
1278 wchar_t* path = char16ptr_t(mWorkingPath.BeginWriting());
1280 if (path[0] == L'\\' && path[1] == L'\\') {
1281 // dealing with a UNC path here; skip past '\\machine\'
1282 path = wcschr(path + 2, L'\\');
1283 if (!path) {
1284 return NS_ERROR_FILE_INVALID_PATH;
1286 ++path;
1289 // search for first slash after the drive (or volume) name
1290 wchar_t* slash = wcschr(path, L'\\');
1292 nsresult directoryCreateError = NS_OK;
1293 if (slash) {
1294 // skip the first '\\'
1295 ++slash;
1296 slash = wcschr(slash, L'\\');
1298 while (slash) {
1299 *slash = L'\0';
1301 if (!::CreateDirectoryW(mWorkingPath.get(), nullptr)) {
1302 rv = ConvertWinError(GetLastError());
1303 if (NS_ERROR_FILE_NOT_FOUND == rv &&
1304 NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) {
1305 // If a previous CreateDirectory failed due to access, return that.
1306 return NS_ERROR_FILE_ACCESS_DENIED;
1308 // perhaps the base path already exists, or perhaps we don't have
1309 // permissions to create the directory. NOTE: access denied could
1310 // occur on a parent directory even though it exists.
1311 else if (rv != NS_ERROR_FILE_ALREADY_EXISTS &&
1312 rv != NS_ERROR_FILE_ACCESS_DENIED) {
1313 return rv;
1316 directoryCreateError = rv;
1318 *slash = L'\\';
1319 ++slash;
1320 slash = wcschr(slash, L'\\');
1324 // If our last CreateDirectory failed due to access, return that.
1325 if (NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) {
1326 return directoryCreateError;
1329 return createFunc(this, mWorkingPath, aAttributes);
1332 NS_IMETHODIMP
1333 nsLocalFile::Append(const nsAString& aNode) {
1334 // append this path, multiple components are not permitted
1335 return AppendInternal(PromiseFlatString(aNode), false);
1338 NS_IMETHODIMP
1339 nsLocalFile::AppendRelativePath(const nsAString& aNode) {
1340 // append this path, multiple components are permitted
1341 return AppendInternal(PromiseFlatString(aNode), true);
1344 nsresult nsLocalFile::AppendInternal(const nsString& aNode,
1345 bool aMultipleComponents) {
1346 if (aNode.IsEmpty()) {
1347 return NS_OK;
1350 // check the relative path for validity
1351 if (aNode.First() == L'\\' || // can't start with an '\'
1352 aNode.Contains(L'/') || // can't contain /
1353 aNode.EqualsASCII("..")) { // can't be ..
1354 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1357 if (aMultipleComponents) {
1358 // can't contain .. as a path component. Ensure that the valid components
1359 // "foo..foo", "..foo", and "foo.." are not falsely detected,
1360 // but the invalid paths "..\", "foo\..", "foo\..\foo",
1361 // "..\foo", etc are.
1362 constexpr auto doubleDot = u"\\.."_ns;
1363 nsAString::const_iterator start, end, offset;
1364 aNode.BeginReading(start);
1365 aNode.EndReading(end);
1366 offset = end;
1367 while (FindInReadable(doubleDot, start, offset)) {
1368 if (offset == end || *offset == L'\\') {
1369 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1371 start = offset;
1372 offset = end;
1375 // catches the remaining cases of prefixes
1376 if (StringBeginsWith(aNode, u"..\\"_ns)) {
1377 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1380 // single components can't contain '\'
1381 else if (aNode.Contains(L'\\')) {
1382 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1385 MakeDirty();
1387 mWorkingPath.Append('\\');
1388 mWorkingPath.Append(aNode);
1390 if (IsSpecialNTFSPath(mWorkingPath)) {
1391 // Revert changes to mWorkingPath:
1392 mWorkingPath.SetLength(mWorkingPath.Length() - aNode.Length() - 1);
1393 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1396 return NS_OK;
1399 nsresult nsLocalFile::OpenNSPRFileDescMaybeShareDelete(int32_t aFlags,
1400 int32_t aMode,
1401 bool aShareDelete,
1402 PRFileDesc** aResult) {
1403 return OpenFile(mWorkingPath, aFlags, aMode, aShareDelete, aResult);
1406 #define TOUPPER(u) (((u) >= L'a' && (u) <= L'z') ? (u) - (L'a' - L'A') : (u))
1408 NS_IMETHODIMP
1409 nsLocalFile::Normalize() {
1410 // XXX See bug 187957 comment 18 for possible problems with this
1411 // implementation.
1413 if (mWorkingPath.IsEmpty()) {
1414 return NS_OK;
1417 nsAutoString path(mWorkingPath);
1419 // find the index of the root backslash for the path. Everything before
1420 // this is considered fully normalized and cannot be ascended beyond
1421 // using ".." For a local drive this is the first slash (e.g. "c:\").
1422 // For a UNC path it is the slash following the share name
1423 // (e.g. "\\server\share\").
1424 int32_t rootIdx = 2; // default to local drive
1425 if (path.First() == L'\\') { // if a share then calculate the rootIdx
1426 rootIdx = path.FindChar(L'\\', 2); // skip \\ in front of the server
1427 if (rootIdx == kNotFound) {
1428 return NS_OK; // already normalized
1430 rootIdx = path.FindChar(L'\\', rootIdx + 1);
1431 if (rootIdx == kNotFound) {
1432 return NS_OK; // already normalized
1434 } else if (path.CharAt(rootIdx) != L'\\') {
1435 // The path has been specified relative to the current working directory
1436 // for that drive. To normalize it, the current working directory for
1437 // that drive needs to be inserted before the supplied relative path
1438 // which will provide an absolute path (and the rootIdx will still be 2).
1439 WCHAR cwd[MAX_PATH];
1440 WCHAR* pcwd = cwd;
1441 int drive = TOUPPER(path.First()) - 'A' + 1;
1442 /* We need to worry about IPH, for details read bug 419326.
1443 * _getdrives - http://msdn2.microsoft.com/en-us/library/xdhk0xd2.aspx
1444 * uses a bitmask, bit 0 is 'a:'
1445 * _chdrive - http://msdn2.microsoft.com/en-us/library/0d1409hb.aspx
1446 * _getdcwd - http://msdn2.microsoft.com/en-us/library/7t2zk3s4.aspx
1447 * take an int, 1 is 'a:'.
1449 * Because of this, we need to do some math. Subtract 1 to convert from
1450 * _chdrive/_getdcwd format to _getdrives drive numbering.
1451 * Shift left x bits to convert from integer indexing to bitfield indexing.
1452 * And of course, we need to find out if the drive is in the bitmask.
1454 * If we're really unlucky, we can still lose, but only if the user
1455 * manages to eject the drive between our call to _getdrives() and
1456 * our *calls* to _wgetdcwd.
1458 if (!((1 << (drive - 1)) & _getdrives())) {
1459 return NS_ERROR_FILE_INVALID_PATH;
1461 if (!_wgetdcwd(drive, pcwd, MAX_PATH)) {
1462 pcwd = _wgetdcwd(drive, 0, 0);
1464 if (!pcwd) {
1465 return NS_ERROR_OUT_OF_MEMORY;
1467 nsAutoString currentDir(pcwd);
1468 if (pcwd != cwd) {
1469 free(pcwd);
1472 if (currentDir.Last() == '\\') {
1473 path.Replace(0, 2, currentDir);
1474 } else {
1475 path.Replace(0, 2, currentDir + u"\\"_ns);
1479 MOZ_ASSERT(0 < rootIdx && rootIdx < (int32_t)path.Length(),
1480 "rootIdx is invalid");
1481 MOZ_ASSERT(path.CharAt(rootIdx) == '\\', "rootIdx is invalid");
1483 // if there is nothing following the root path then it is already normalized
1484 if (rootIdx + 1 == (int32_t)path.Length()) {
1485 return NS_OK;
1488 // assign the root
1489 const char16_t* pathBuffer = path.get(); // simplify access to the buffer
1490 mWorkingPath.SetCapacity(path.Length()); // it won't ever grow longer
1491 mWorkingPath.Assign(pathBuffer, rootIdx);
1493 // Normalize the path components. The actions taken are:
1495 // "\\" condense to single backslash
1496 // "." remove from path
1497 // ".." up a directory
1498 // "..." remove from path (any number of dots > 2)
1500 // The last form is something that Windows 95 and 98 supported and
1501 // is a shortcut for changing up multiple directories. Windows XP
1502 // and ilk ignore it in a path, as is done here.
1503 int32_t len, begin, end = rootIdx;
1504 while (end < (int32_t)path.Length()) {
1505 // find the current segment (text between the backslashes) to
1506 // be examined, this will set the following variables:
1507 // begin == index of first char in segment
1508 // end == index 1 char after last char in segment
1509 // len == length of segment
1510 begin = end + 1;
1511 end = path.FindChar('\\', begin);
1512 if (end == kNotFound) {
1513 end = path.Length();
1515 len = end - begin;
1517 // ignore double backslashes
1518 if (len == 0) {
1519 continue;
1522 // len != 0, and interesting paths always begin with a dot
1523 if (pathBuffer[begin] == '.') {
1524 // ignore single dots
1525 if (len == 1) {
1526 continue;
1529 // handle multiple dots
1530 if (len >= 2 && pathBuffer[begin + 1] == L'.') {
1531 // back up a path component on double dot
1532 if (len == 2) {
1533 int32_t prev = mWorkingPath.RFindChar('\\');
1534 if (prev >= rootIdx) {
1535 mWorkingPath.Truncate(prev);
1537 continue;
1540 // length is > 2 and the first two characters are dots.
1541 // if the rest of the string is dots, then ignore it.
1542 int idx = len - 1;
1543 for (; idx >= 2; --idx) {
1544 if (pathBuffer[begin + idx] != L'.') {
1545 break;
1549 // this is true if the loop above didn't break
1550 // and all characters in this segment are dots.
1551 if (idx < 2) {
1552 continue;
1557 // add the current component to the path, including the preceding backslash
1558 mWorkingPath.Append(pathBuffer + begin - 1, len + 1);
1561 // kill trailing dots and spaces.
1562 int32_t filePathLen = mWorkingPath.Length() - 1;
1563 while (filePathLen > 0 && (mWorkingPath[filePathLen] == L' ' ||
1564 mWorkingPath[filePathLen] == L'.')) {
1565 mWorkingPath.Truncate(filePathLen--);
1568 MakeDirty();
1569 return NS_OK;
1572 NS_IMETHODIMP
1573 nsLocalFile::GetLeafName(nsAString& aLeafName) {
1574 aLeafName.Truncate();
1576 if (mWorkingPath.IsEmpty()) {
1577 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1580 int32_t offset = mWorkingPath.RFindChar(L'\\');
1582 // if the working path is just a node without any lashes.
1583 if (offset == kNotFound) {
1584 aLeafName = mWorkingPath;
1585 } else {
1586 aLeafName = Substring(mWorkingPath, offset + 1);
1589 return NS_OK;
1592 NS_IMETHODIMP
1593 nsLocalFile::SetLeafName(const nsAString& aLeafName) {
1594 MakeDirty();
1596 if (mWorkingPath.IsEmpty()) {
1597 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1600 // cannot use nsCString::RFindChar() due to 0x5c problem
1601 int32_t offset = mWorkingPath.RFindChar(L'\\');
1602 nsString newDir;
1603 if (offset) {
1604 newDir = Substring(mWorkingPath, 0, offset + 1) + aLeafName;
1605 } else {
1606 newDir = mWorkingPath + aLeafName;
1608 if (IsSpecialNTFSPath(newDir)) {
1609 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1612 mWorkingPath.Assign(newDir);
1614 return NS_OK;
1617 NS_IMETHODIMP
1618 nsLocalFile::GetDisplayName(nsAString& aLeafName) {
1619 aLeafName.Truncate();
1621 if (mWorkingPath.IsEmpty()) {
1622 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1624 SHFILEINFOW sfi = {};
1625 DWORD_PTR result = ::SHGetFileInfoW(mWorkingPath.get(), 0, &sfi, sizeof(sfi),
1626 SHGFI_DISPLAYNAME);
1627 // If we found a display name, return that:
1628 if (result) {
1629 aLeafName.Assign(sfi.szDisplayName);
1630 return NS_OK;
1632 // Nope - fall back to the regular leaf name.
1633 return GetLeafName(aLeafName);
1636 NS_IMETHODIMP
1637 nsLocalFile::GetPath(nsAString& aResult) {
1638 MOZ_ASSERT_IF(
1639 mUseDOSDevicePathSyntax,
1640 !FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath));
1641 aResult = mWorkingPath;
1642 return NS_OK;
1645 NS_IMETHODIMP
1646 nsLocalFile::GetCanonicalPath(nsAString& aResult) {
1647 EnsureShortPath();
1648 aResult.Assign(mShortWorkingPath);
1649 return NS_OK;
1652 typedef struct {
1653 WORD wLanguage;
1654 WORD wCodePage;
1655 } LANGANDCODEPAGE;
1657 NS_IMETHODIMP
1658 nsLocalFile::GetVersionInfoField(const char* aField, nsAString& aResult) {
1659 nsresult rv = NS_ERROR_FAILURE;
1661 const WCHAR* path = mWorkingPath.get();
1663 DWORD dummy;
1664 DWORD size = ::GetFileVersionInfoSizeW(path, &dummy);
1665 if (!size) {
1666 return rv;
1669 void* ver = moz_xcalloc(size, 1);
1670 if (::GetFileVersionInfoW(path, 0, size, ver)) {
1671 LANGANDCODEPAGE* translate = nullptr;
1672 UINT pageCount;
1673 BOOL queryResult = ::VerQueryValueW(ver, L"\\VarFileInfo\\Translation",
1674 (void**)&translate, &pageCount);
1675 if (queryResult && translate) {
1676 for (int32_t i = 0; i < 2; ++i) {
1677 wchar_t subBlock[MAX_PATH];
1678 _snwprintf(subBlock, MAX_PATH, L"\\StringFileInfo\\%04x%04x\\%S",
1679 (i == 0 ? translate[0].wLanguage : ::GetUserDefaultLangID()),
1680 translate[0].wCodePage, aField);
1681 subBlock[MAX_PATH - 1] = 0;
1682 LPVOID value = nullptr;
1683 UINT size;
1684 queryResult = ::VerQueryValueW(ver, subBlock, &value, &size);
1685 if (queryResult && value) {
1686 aResult.Assign(static_cast<char16_t*>(value));
1687 if (!aResult.IsEmpty()) {
1688 rv = NS_OK;
1689 break;
1695 free(ver);
1697 return rv;
1700 NS_IMETHODIMP
1701 nsLocalFile::OpenNSPRFileDescShareDelete(int32_t aFlags, int32_t aMode,
1702 PRFileDesc** aResult) {
1703 nsresult rv = OpenNSPRFileDescMaybeShareDelete(aFlags, aMode, true, aResult);
1704 if (NS_FAILED(rv)) {
1705 return rv;
1708 return NS_OK;
1712 * Determines if the drive type for the specified file is rmeote or local.
1714 * @param path The path of the file to check
1715 * @param remote Out parameter, on function success holds true if the specified
1716 * file path is remote, or false if the file path is local.
1717 * @return true on success. The return value implies absolutely nothing about
1718 * wether the file is local or remote.
1720 static bool IsRemoteFilePath(LPCWSTR aPath, bool& aRemote) {
1721 // Obtain the parent directory path and make sure it ends with
1722 // a trailing backslash.
1723 WCHAR dirPath[MAX_PATH + 1] = {0};
1724 wcsncpy(dirPath, aPath, MAX_PATH);
1725 if (!PathRemoveFileSpecW(dirPath)) {
1726 return false;
1728 size_t len = wcslen(dirPath);
1729 // In case the dirPath holds exaclty MAX_PATH and remains unchanged, we
1730 // recheck the required length here since we need to terminate it with
1731 // a backslash.
1732 if (len >= MAX_PATH) {
1733 return false;
1736 dirPath[len] = L'\\';
1737 dirPath[len + 1] = L'\0';
1738 UINT driveType = GetDriveTypeW(dirPath);
1739 aRemote = driveType == DRIVE_REMOTE;
1740 return true;
1743 nsresult nsLocalFile::CopySingleFile(nsIFile* aSourceFile, nsIFile* aDestParent,
1744 const nsAString& aNewName,
1745 uint32_t aOptions) {
1746 nsresult rv = NS_OK;
1747 nsAutoString filePath;
1749 bool move = aOptions & (Move | Rename);
1751 // get the path that we are going to copy to.
1752 // Since windows does not know how to auto
1753 // resolve shortcuts, we must work with the
1754 // target.
1755 nsAutoString destPath;
1756 rv = aDestParent->GetTarget(destPath);
1757 if (NS_FAILED(rv)) {
1758 return rv;
1761 destPath.Append('\\');
1763 if (aNewName.IsEmpty()) {
1764 nsAutoString aFileName;
1765 aSourceFile->GetLeafName(aFileName);
1766 destPath.Append(aFileName);
1767 } else {
1768 destPath.Append(aNewName);
1771 if (aOptions & FollowSymlinks) {
1772 rv = aSourceFile->GetTarget(filePath);
1773 if (filePath.IsEmpty()) {
1774 rv = aSourceFile->GetPath(filePath);
1776 } else {
1777 rv = aSourceFile->GetPath(filePath);
1780 if (NS_FAILED(rv)) {
1781 return rv;
1784 #ifdef DEBUG
1785 nsCOMPtr<nsILocalFileWin> srcWinFile = do_QueryInterface(aSourceFile);
1786 MOZ_ASSERT(srcWinFile);
1788 bool srcUseDOSDevicePathSyntax;
1789 srcWinFile->GetUseDOSDevicePathSyntax(&srcUseDOSDevicePathSyntax);
1791 nsCOMPtr<nsILocalFileWin> destWinFile = do_QueryInterface(aDestParent);
1792 MOZ_ASSERT(destWinFile);
1794 bool destUseDOSDevicePathSyntax;
1795 destWinFile->GetUseDOSDevicePathSyntax(&destUseDOSDevicePathSyntax);
1797 MOZ_ASSERT(srcUseDOSDevicePathSyntax == destUseDOSDevicePathSyntax,
1798 "Copy or Move files with different values for "
1799 "useDOSDevicePathSyntax would fail");
1800 #endif
1802 if (FilePreferences::IsBlockedUNCPath(destPath)) {
1803 return NS_ERROR_FILE_ACCESS_DENIED;
1806 int copyOK = 0;
1807 if (move) {
1808 copyOK = ::MoveFileExW(filePath.get(), destPath.get(),
1809 MOVEFILE_REPLACE_EXISTING);
1812 // If we either failed to move the file, or this is a copy, try copying:
1813 if (!copyOK && (!move || GetLastError() == ERROR_NOT_SAME_DEVICE)) {
1814 // Failed renames here should just return access denied.
1815 if (move && (aOptions & Rename)) {
1816 return NS_ERROR_FILE_ACCESS_DENIED;
1819 // Pass the flag COPY_FILE_NO_BUFFERING to CopyFileEx as we may be copying
1820 // to a SMBV2 remote drive. Without this parameter subsequent append mode
1821 // file writes can cause the resultant file to become corrupt. We only need
1822 // to do this if the major version of Windows is > 5(Only Windows Vista and
1823 // above can support SMBV2). With a 7200RPM hard drive: Copying a 1KB file
1824 // with COPY_FILE_NO_BUFFERING takes about 30-60ms. Copying a 1KB file
1825 // without COPY_FILE_NO_BUFFERING takes < 1ms. So we only use
1826 // COPY_FILE_NO_BUFFERING when we have a remote drive.
1827 DWORD dwCopyFlags = COPY_FILE_ALLOW_DECRYPTED_DESTINATION;
1828 bool path1Remote, path2Remote;
1829 if (!IsRemoteFilePath(filePath.get(), path1Remote) ||
1830 !IsRemoteFilePath(destPath.get(), path2Remote) || path1Remote ||
1831 path2Remote) {
1832 dwCopyFlags |= COPY_FILE_NO_BUFFERING;
1835 copyOK = ::CopyFileExW(filePath.get(), destPath.get(), nullptr, nullptr,
1836 nullptr, dwCopyFlags);
1837 // On Windows 10, copying without buffering has started failing, so try
1838 // with buffering...
1839 if (!copyOK && (dwCopyFlags & COPY_FILE_NO_BUFFERING) &&
1840 GetLastError() == ERROR_INVALID_PARAMETER) {
1841 dwCopyFlags &= ~COPY_FILE_NO_BUFFERING;
1842 copyOK = ::CopyFileExW(filePath.get(), destPath.get(), nullptr, nullptr,
1843 nullptr, dwCopyFlags);
1846 if (move && copyOK) {
1847 DeleteFileW(filePath.get());
1851 if (!copyOK) { // CopyFileEx and MoveFileEx return zero at failure.
1852 rv = ConvertWinError(GetLastError());
1853 } else if (move && !(aOptions & SkipNtfsAclReset)) {
1854 // Set security permissions to inherit from parent.
1855 // Note: propagates to all children: slow for big file trees
1856 PACL pOldDACL = nullptr;
1857 PSECURITY_DESCRIPTOR pSD = nullptr;
1858 ::GetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT,
1859 DACL_SECURITY_INFORMATION, nullptr, nullptr,
1860 &pOldDACL, nullptr, &pSD);
1861 UniquePtr<VOID, LocalFreeDeleter> autoFreeSecDesc(pSD);
1862 if (pOldDACL) {
1863 // Test the current DACL, if we find one that is inherited then we can
1864 // skip the reset. This avoids a request for SeTcbPrivilege, which can
1865 // cause a lot of audit events if enabled (Bug 1816694).
1866 bool inherited = false;
1867 for (DWORD i = 0; i < pOldDACL->AceCount; ++i) {
1868 VOID* pAce = nullptr;
1869 if (::GetAce(pOldDACL, i, &pAce) &&
1870 static_cast<PACE_HEADER>(pAce)->AceFlags & INHERITED_ACE) {
1871 inherited = true;
1872 break;
1876 if (!inherited) {
1877 ::SetNamedSecurityInfoW(
1878 (LPWSTR)destPath.get(), SE_FILE_OBJECT,
1879 DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION,
1880 nullptr, nullptr, pOldDACL, nullptr);
1885 return rv;
1888 nsresult nsLocalFile::CopyMove(nsIFile* aParentDir, const nsAString& aNewName,
1889 uint32_t aOptions) {
1890 bool move = aOptions & (Move | Rename);
1891 bool followSymlinks = aOptions & FollowSymlinks;
1892 // If we're not provided with a new parent, we're copying or moving to
1893 // another file in the same directory and can safely skip checking if the
1894 // destination directory exists:
1895 bool targetInSameDirectory = !aParentDir;
1897 nsCOMPtr<nsIFile> newParentDir = aParentDir;
1898 // check to see if this exists, otherwise return an error.
1899 // we will check this by resolving. If the user wants us
1900 // to follow links, then we are talking about the target,
1901 // hence we can use the |FollowSymlinks| option.
1902 nsresult rv = ResolveAndStat();
1903 if (NS_FAILED(rv)) {
1904 return rv;
1907 if (!newParentDir) {
1908 // no parent was specified. We must rename.
1909 if (aNewName.IsEmpty()) {
1910 return NS_ERROR_INVALID_ARG;
1913 rv = GetParent(getter_AddRefs(newParentDir));
1914 if (NS_FAILED(rv)) {
1915 return rv;
1919 if (!newParentDir) {
1920 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
1923 if (!targetInSameDirectory) {
1924 // make sure it exists and is a directory. Create it if not there.
1925 bool exists = false;
1926 rv = newParentDir->Exists(&exists);
1927 if (NS_FAILED(rv)) {
1928 return rv;
1931 if (!exists) {
1932 rv = newParentDir->Create(DIRECTORY_TYPE,
1933 0644); // TODO, what permissions should we use
1934 if (NS_FAILED(rv)) {
1935 return rv;
1937 } else {
1938 bool isDir = false;
1939 rv = newParentDir->IsDirectory(&isDir);
1940 if (NS_FAILED(rv)) {
1941 return rv;
1944 if (!isDir) {
1945 if (followSymlinks) {
1946 bool isLink = false;
1947 rv = newParentDir->IsSymlink(&isLink);
1948 if (NS_FAILED(rv)) {
1949 return rv;
1952 if (isLink) {
1953 nsAutoString target;
1954 rv = newParentDir->GetTarget(target);
1955 if (NS_FAILED(rv)) {
1956 return rv;
1959 nsCOMPtr<nsIFile> realDest = new nsLocalFile();
1960 rv = realDest->InitWithPath(target);
1961 if (NS_FAILED(rv)) {
1962 return rv;
1965 return CopyMove(realDest, aNewName, aOptions);
1967 } else {
1968 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
1974 // Try different ways to move/copy files/directories
1975 bool done = false;
1977 bool isDir = false;
1978 rv = IsDirectory(&isDir);
1979 if (NS_FAILED(rv)) {
1980 return rv;
1983 bool isSymlink = false;
1984 rv = IsSymlink(&isSymlink);
1985 if (NS_FAILED(rv)) {
1986 return rv;
1989 // Try to move the file or directory, or try to copy a single file (or
1990 // non-followed symlink)
1991 if (move || !isDir || (isSymlink && !followSymlinks)) {
1992 // Copy/Move single file, or move a directory
1993 if (!aParentDir) {
1994 aOptions |= SkipNtfsAclReset;
1996 rv = CopySingleFile(this, newParentDir, aNewName, aOptions);
1997 done = NS_SUCCEEDED(rv);
1998 // If we are moving a directory and that fails, fallback on directory
1999 // enumeration. See bug 231300 for details.
2000 if (!done && !(move && isDir)) {
2001 return rv;
2005 // Not able to copy or move directly, so enumerate it
2006 if (!done) {
2007 // create a new target destination in the new parentDir;
2008 nsCOMPtr<nsIFile> target;
2009 rv = newParentDir->Clone(getter_AddRefs(target));
2010 if (NS_FAILED(rv)) {
2011 return rv;
2014 nsAutoString allocatedNewName;
2015 if (aNewName.IsEmpty()) {
2016 bool isLink = false;
2017 rv = IsSymlink(&isLink);
2018 if (NS_FAILED(rv)) {
2019 return rv;
2022 if (isLink) {
2023 nsAutoString temp;
2024 rv = GetTarget(temp);
2025 if (NS_FAILED(rv)) {
2026 return rv;
2029 int32_t offset = temp.RFindChar(L'\\');
2030 if (offset == kNotFound) {
2031 allocatedNewName = temp;
2032 } else {
2033 allocatedNewName = Substring(temp, offset + 1);
2035 } else {
2036 GetLeafName(allocatedNewName); // this should be the leaf name of the
2038 } else {
2039 allocatedNewName = aNewName;
2042 rv = target->Append(allocatedNewName);
2043 if (NS_FAILED(rv)) {
2044 return rv;
2047 allocatedNewName.Truncate();
2049 bool exists = false;
2050 // check if the destination directory already exists
2051 rv = target->Exists(&exists);
2052 if (NS_FAILED(rv)) {
2053 return rv;
2056 if (!exists) {
2057 // if the destination directory cannot be created, return an error
2058 rv = target->Create(DIRECTORY_TYPE,
2059 0644); // TODO, what permissions should we use
2060 if (NS_FAILED(rv)) {
2061 return rv;
2063 } else {
2064 // check if the destination directory is writable and empty
2065 bool isWritable = false;
2066 rv = target->IsWritable(&isWritable);
2067 if (NS_FAILED(rv)) {
2068 return rv;
2071 if (!isWritable) {
2072 return NS_ERROR_FILE_ACCESS_DENIED;
2075 nsCOMPtr<nsIDirectoryEnumerator> targetIterator;
2076 rv = target->GetDirectoryEntries(getter_AddRefs(targetIterator));
2077 if (NS_FAILED(rv)) {
2078 return rv;
2081 bool more;
2082 targetIterator->HasMoreElements(&more);
2083 // return error if target directory is not empty
2084 if (more) {
2085 return NS_ERROR_FILE_DIR_NOT_EMPTY;
2089 RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator();
2091 rv = dirEnum->Init(this);
2092 if (NS_FAILED(rv)) {
2093 NS_WARNING("dirEnum initialization failed");
2094 return rv;
2097 nsCOMPtr<nsIFile> file;
2098 while (NS_SUCCEEDED(dirEnum->GetNextFile(getter_AddRefs(file))) && file) {
2099 bool isDir = false;
2100 rv = file->IsDirectory(&isDir);
2101 if (NS_FAILED(rv)) {
2102 return rv;
2105 bool isLink = false;
2106 rv = file->IsSymlink(&isLink);
2107 if (NS_FAILED(rv)) {
2108 return rv;
2111 if (move) {
2112 if (followSymlinks) {
2113 return NS_ERROR_FAILURE;
2116 rv = file->MoveTo(target, u""_ns);
2117 if (NS_FAILED(rv)) {
2118 return rv;
2120 } else {
2121 if (followSymlinks) {
2122 rv = file->CopyToFollowingLinks(target, u""_ns);
2123 } else {
2124 rv = file->CopyTo(target, u""_ns);
2126 if (NS_FAILED(rv)) {
2127 return rv;
2131 // we've finished moving all the children of this directory
2132 // in the new directory. so now delete the directory
2133 // note, we don't need to do a recursive delete.
2134 // MoveTo() is recursive. At this point,
2135 // we've already moved the children of the current folder
2136 // to the new location. nothing should be left in the folder.
2137 if (move) {
2138 rv = Remove(false /* recursive */);
2139 if (NS_FAILED(rv)) {
2140 return rv;
2145 // If we moved, we want to adjust this.
2146 if (move) {
2147 MakeDirty();
2149 nsAutoString newParentPath;
2150 newParentDir->GetPath(newParentPath);
2152 if (newParentPath.IsEmpty()) {
2153 return NS_ERROR_FAILURE;
2156 if (aNewName.IsEmpty()) {
2157 nsAutoString aFileName;
2158 GetLeafName(aFileName);
2160 InitWithPath(newParentPath);
2161 Append(aFileName);
2162 } else {
2163 InitWithPath(newParentPath);
2164 Append(aNewName);
2168 return NS_OK;
2171 NS_IMETHODIMP
2172 nsLocalFile::CopyTo(nsIFile* aNewParentDir, const nsAString& aNewName) {
2173 return CopyMove(aNewParentDir, aNewName, 0);
2176 NS_IMETHODIMP
2177 nsLocalFile::CopyToFollowingLinks(nsIFile* aNewParentDir,
2178 const nsAString& aNewName) {
2179 return CopyMove(aNewParentDir, aNewName, FollowSymlinks);
2182 NS_IMETHODIMP
2183 nsLocalFile::MoveTo(nsIFile* aNewParentDir, const nsAString& aNewName) {
2184 return CopyMove(aNewParentDir, aNewName, Move);
2187 NS_IMETHODIMP
2188 nsLocalFile::MoveToFollowingLinks(nsIFile* aNewParentDir,
2189 const nsAString& aNewName) {
2190 return CopyMove(aNewParentDir, aNewName, Move | FollowSymlinks);
2193 NS_IMETHODIMP
2194 nsLocalFile::RenameTo(nsIFile* aNewParentDir, const nsAString& aNewName) {
2195 // If we're not provided with a new parent, we're renaming inside one and
2196 // the same directory and can safely skip checking if the destination
2197 // directory exists:
2198 bool targetInSameDirectory = !aNewParentDir;
2200 nsCOMPtr<nsIFile> targetParentDir = aNewParentDir;
2201 // check to see if this exists, otherwise return an error.
2202 // we will check this by resolving. If the user wants us
2203 // to follow links, then we are talking about the target,
2204 // hence we can use the |followSymlinks| parameter.
2205 nsresult rv = ResolveAndStat();
2206 if (NS_FAILED(rv)) {
2207 return rv;
2210 if (!targetParentDir) {
2211 // no parent was specified. We must rename.
2212 if (aNewName.IsEmpty()) {
2213 return NS_ERROR_INVALID_ARG;
2215 rv = GetParent(getter_AddRefs(targetParentDir));
2216 if (NS_FAILED(rv)) {
2217 return rv;
2221 if (!targetParentDir) {
2222 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
2225 if (!targetInSameDirectory) {
2226 // make sure it exists and is a directory. Create it if not there.
2227 bool exists = false;
2228 rv = targetParentDir->Exists(&exists);
2229 if (NS_FAILED(rv)) {
2230 return rv;
2233 if (!exists) {
2234 rv = targetParentDir->Create(DIRECTORY_TYPE, 0644);
2235 if (NS_FAILED(rv)) {
2236 return rv;
2238 } else {
2239 bool isDir = false;
2240 rv = targetParentDir->IsDirectory(&isDir);
2241 if (NS_FAILED(rv)) {
2242 return rv;
2244 if (!isDir) {
2245 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
2250 uint32_t options = Rename;
2251 if (!aNewParentDir) {
2252 options |= SkipNtfsAclReset;
2254 // Move single file, or move a directory
2255 return CopySingleFile(this, targetParentDir, aNewName, options);
2258 NS_IMETHODIMP
2259 nsLocalFile::RenameToNative(nsIFile* aNewParentDir,
2260 const nsACString& aNewName) {
2261 nsAutoString tmp;
2262 nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
2263 if (NS_SUCCEEDED(rv)) {
2264 return RenameTo(aNewParentDir, tmp);
2267 return rv;
2270 NS_IMETHODIMP
2271 nsLocalFile::Load(PRLibrary** aResult) {
2272 // Check we are correctly initialized.
2273 CHECK_mWorkingPath();
2274 if (NS_WARN_IF(!aResult)) {
2275 return NS_ERROR_INVALID_ARG;
2278 #ifdef NS_BUILD_REFCNT_LOGGING
2279 nsTraceRefcnt::SetActivityIsLegal(false);
2280 #endif
2282 PRLibSpec libSpec;
2283 libSpec.value.pathname_u = mWorkingPath.get();
2284 libSpec.type = PR_LibSpec_PathnameU;
2285 *aResult = PR_LoadLibraryWithFlags(libSpec, 0);
2287 #ifdef NS_BUILD_REFCNT_LOGGING
2288 nsTraceRefcnt::SetActivityIsLegal(true);
2289 #endif
2291 if (*aResult) {
2292 return NS_OK;
2294 return NS_ERROR_NULL_POINTER;
2297 NS_IMETHODIMP
2298 nsLocalFile::Remove(bool aRecursive, uint32_t* aRemoveCount) {
2299 // NOTE:
2301 // if the working path points to a shortcut, then we will only
2302 // delete the shortcut itself. even if the shortcut points to
2303 // a directory, we will not recurse into that directory or
2304 // delete that directory itself. likewise, if the shortcut
2305 // points to a normal file, we will not delete the real file.
2306 // this is done to be consistent with the other platforms that
2307 // behave this way. we do this even if the followLinks attribute
2308 // is set to true. this helps protect against misuse that could
2309 // lead to security bugs (e.g., bug 210588).
2311 // Since shortcut files are no longer permitted to be used as unix-like
2312 // symlinks interspersed in the path (e.g. "c:/file.lnk/foo/bar.txt")
2313 // this processing is a lot simpler. Even if the shortcut file is
2314 // pointing to a directory, only the mWorkingPath value is used and so
2315 // only the shortcut file will be deleted.
2317 // Check we are correctly initialized.
2318 CHECK_mWorkingPath();
2320 nsresult rv = NS_OK;
2322 bool isLink = false;
2323 rv = IsSymlink(&isLink);
2324 if (NS_FAILED(rv)) {
2325 return rv;
2328 // only check to see if we have a directory if it isn't a link
2329 bool isDir = false;
2330 if (!isLink) {
2331 rv = IsDirectory(&isDir);
2332 if (NS_FAILED(rv)) {
2333 return rv;
2337 if (isDir) {
2338 if (aRecursive) {
2339 // WARNING: neither the `SHFileOperation` nor `IFileOperation` APIs are
2340 // appropriate here as neither handle long path names, i.e. paths prefixed
2341 // with `\\?\` or longer than 260 characters on Windows 10+ system with
2342 // long paths enabled.
2344 RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator();
2346 rv = dirEnum->Init(this);
2347 if (NS_FAILED(rv)) {
2348 return rv;
2351 // XXX: We are ignoring the result of the removal here while
2352 // nsLocalFileUnix does not. We should align the behavior. (bug 1779696)
2353 nsCOMPtr<nsIFile> file;
2354 while (NS_SUCCEEDED(dirEnum->GetNextFile(getter_AddRefs(file))) && file) {
2355 file->Remove(aRecursive, aRemoveCount);
2358 if (RemoveDirectoryW(mWorkingPath.get()) == 0) {
2359 return ConvertWinError(GetLastError());
2361 } else {
2362 if (DeleteFileW(mWorkingPath.get()) == 0) {
2363 return ConvertWinError(GetLastError());
2367 if (aRemoveCount) {
2368 *aRemoveCount += 1;
2371 MakeDirty();
2372 return rv;
2375 nsresult nsLocalFile::GetDateImpl(PRTime* aTime,
2376 nsLocalFile::TimeField aTimeField,
2377 bool aFollowLinks) {
2378 // Check we are correctly initialized.
2379 CHECK_mWorkingPath();
2381 if (NS_WARN_IF(!aTime)) {
2382 return NS_ERROR_INVALID_ARG;
2385 FileInfo symlinkInfo{};
2386 FileInfo* pInfo;
2388 if (aFollowLinks) {
2389 if (nsresult rv = GetFileInfo(mWorkingPath, &symlinkInfo); NS_FAILED(rv)) {
2390 return rv;
2393 pInfo = &symlinkInfo;
2394 } else {
2395 if (nsresult rv = ResolveAndStat(); NS_FAILED(rv)) {
2396 return rv;
2399 pInfo = &mFileInfo;
2402 switch (aTimeField) {
2403 case TimeField::AccessedTime:
2404 *aTime = pInfo->accessTime / PR_USEC_PER_MSEC;
2405 break;
2407 case TimeField::ModifiedTime:
2408 *aTime = pInfo->modifyTime / PR_USEC_PER_MSEC;
2409 break;
2411 default:
2412 MOZ_CRASH("Unknown time field");
2415 return NS_OK;
2418 NS_IMETHODIMP
2419 nsLocalFile::GetLastAccessedTime(PRTime* aLastAccessedTime) {
2420 return GetDateImpl(aLastAccessedTime, TimeField::AccessedTime,
2421 /* aFollowSymlinks = */ true);
2424 NS_IMETHODIMP
2425 nsLocalFile::GetLastAccessedTimeOfLink(PRTime* aLastAccessedTime) {
2426 return GetDateImpl(aLastAccessedTime, TimeField::AccessedTime,
2427 /* aFollowSymlinks = */ false);
2430 NS_IMETHODIMP
2431 nsLocalFile::SetLastAccessedTime(PRTime aLastAccessedTime) {
2432 return SetDateImpl(aLastAccessedTime, TimeField::AccessedTime);
2435 NS_IMETHODIMP
2436 nsLocalFile::SetLastAccessedTimeOfLink(PRTime aLastAccessedTime) {
2437 return SetLastAccessedTime(aLastAccessedTime);
2440 NS_IMETHODIMP
2441 nsLocalFile::GetLastModifiedTime(PRTime* aLastModifiedTime) {
2442 return GetDateImpl(aLastModifiedTime, TimeField::ModifiedTime,
2443 /* aFollowSymlinks = */ true);
2446 NS_IMETHODIMP
2447 nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModifiedTime) {
2448 return GetDateImpl(aLastModifiedTime, TimeField::ModifiedTime,
2449 /* aFollowSymlinks = */ false);
2452 NS_IMETHODIMP
2453 nsLocalFile::SetLastModifiedTime(PRTime aLastModifiedTime) {
2454 return SetDateImpl(aLastModifiedTime, TimeField::ModifiedTime);
2457 NS_IMETHODIMP
2458 nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModifiedTime) {
2459 return SetLastModifiedTime(aLastModifiedTime);
2462 NS_IMETHODIMP
2463 nsLocalFile::GetCreationTime(PRTime* aCreationTime) {
2464 CHECK_mWorkingPath();
2466 if (NS_WARN_IF(!aCreationTime)) {
2467 return NS_ERROR_INVALID_ARG;
2470 nsresult rv = ResolveAndStat();
2471 NS_ENSURE_SUCCESS(rv, rv);
2473 *aCreationTime = mFileInfo.creationTime / PR_USEC_PER_MSEC;
2475 return NS_OK;
2478 NS_IMETHODIMP
2479 nsLocalFile::GetCreationTimeOfLink(PRTime* aCreationTime) {
2480 CHECK_mWorkingPath();
2482 if (NS_WARN_IF(!aCreationTime)) {
2483 return NS_ERROR_INVALID_ARG;
2486 FileInfo info;
2487 nsresult rv = GetFileInfo(mWorkingPath, &info);
2488 NS_ENSURE_SUCCESS(rv, rv);
2490 *aCreationTime = info.creationTime / PR_USEC_PER_MSEC;
2492 return NS_OK;
2495 nsresult nsLocalFile::SetDateImpl(PRTime aTime,
2496 nsLocalFile::TimeField aTimeField) {
2497 // Check we are correctly initialized.
2498 CHECK_mWorkingPath();
2500 // The FILE_FLAG_BACKUP_SEMANTICS is required in order to change the
2501 // modification time for directories.
2502 HANDLE file =
2503 ::CreateFileW(mWorkingPath.get(), // pointer to name of the file
2504 GENERIC_WRITE, // access (write) mode
2505 0, // share mode
2506 nullptr, // pointer to security attributes
2507 OPEN_EXISTING, // how to create
2508 FILE_FLAG_BACKUP_SEMANTICS, // file attributes
2509 nullptr);
2511 if (file == INVALID_HANDLE_VALUE) {
2512 return ConvertWinError(GetLastError());
2515 FILETIME ft;
2516 SYSTEMTIME st;
2517 PRExplodedTime pret;
2519 if (aTime == 0) {
2520 aTime = PR_Now() / PR_USEC_PER_MSEC;
2523 // PR_ExplodeTime expects usecs...
2524 PR_ExplodeTime(aTime * PR_USEC_PER_MSEC, PR_GMTParameters, &pret);
2525 st.wYear = pret.tm_year;
2526 st.wMonth =
2527 pret.tm_month + 1; // Convert start offset -- Win32: Jan=1; NSPR: Jan=0
2528 st.wDayOfWeek = pret.tm_wday;
2529 st.wDay = pret.tm_mday;
2530 st.wHour = pret.tm_hour;
2531 st.wMinute = pret.tm_min;
2532 st.wSecond = pret.tm_sec;
2533 st.wMilliseconds = pret.tm_usec / 1000;
2535 const FILETIME* accessTime = nullptr;
2536 const FILETIME* modifiedTime = nullptr;
2538 if (aTimeField == TimeField::AccessedTime) {
2539 accessTime = &ft;
2540 } else {
2541 modifiedTime = &ft;
2544 nsresult rv = NS_OK;
2546 // if at least one of these fails...
2547 if (!(SystemTimeToFileTime(&st, &ft) != 0 &&
2548 SetFileTime(file, nullptr, accessTime, modifiedTime) != 0)) {
2549 rv = ConvertWinError(GetLastError());
2552 CloseHandle(file);
2554 return rv;
2557 NS_IMETHODIMP
2558 nsLocalFile::GetPermissions(uint32_t* aPermissions) {
2559 if (NS_WARN_IF(!aPermissions)) {
2560 return NS_ERROR_INVALID_ARG;
2563 // get the permissions of the target as determined by mFollowSymlinks
2564 // If true, then this will be for the target of the shortcut file,
2565 // otherwise it will be for the shortcut file itself (i.e. the same
2566 // results as GetPermissionsOfLink)
2567 nsresult rv = ResolveAndStat();
2568 if (NS_FAILED(rv)) {
2569 return rv;
2572 bool isWritable = false;
2573 rv = IsWritable(&isWritable);
2574 if (NS_FAILED(rv)) {
2575 return rv;
2578 bool isExecutable = false;
2579 rv = IsExecutable(&isExecutable);
2580 if (NS_FAILED(rv)) {
2581 return rv;
2584 *aPermissions = PR_IRUSR | PR_IRGRP | PR_IROTH; // all read
2585 if (isWritable) {
2586 *aPermissions |= PR_IWUSR | PR_IWGRP | PR_IWOTH; // all write
2588 if (isExecutable) {
2589 *aPermissions |= PR_IXUSR | PR_IXGRP | PR_IXOTH; // all execute
2592 return NS_OK;
2595 NS_IMETHODIMP
2596 nsLocalFile::GetPermissionsOfLink(uint32_t* aPermissions) {
2597 // Check we are correctly initialized.
2598 CHECK_mWorkingPath();
2600 if (NS_WARN_IF(!aPermissions)) {
2601 return NS_ERROR_INVALID_ARG;
2604 // The caller is assumed to have already called IsSymlink
2605 // and to have found that this file is a link. It is not
2606 // possible for a link file to be executable.
2608 DWORD word = ::GetFileAttributesW(mWorkingPath.get());
2609 if (word == INVALID_FILE_ATTRIBUTES) {
2610 return NS_ERROR_FILE_INVALID_PATH;
2613 bool isWritable = !(word & FILE_ATTRIBUTE_READONLY);
2614 *aPermissions = PR_IRUSR | PR_IRGRP | PR_IROTH; // all read
2615 if (isWritable) {
2616 *aPermissions |= PR_IWUSR | PR_IWGRP | PR_IWOTH; // all write
2619 return NS_OK;
2622 NS_IMETHODIMP
2623 nsLocalFile::SetPermissions(uint32_t aPermissions) {
2624 // Check we are correctly initialized.
2625 CHECK_mWorkingPath();
2627 // set the permissions of the target as determined by mFollowSymlinks
2628 // If true, then this will be for the target of the shortcut file,
2629 // otherwise it will be for the shortcut file itself (i.e. the same
2630 // results as SetPermissionsOfLink)
2631 nsresult rv = Resolve();
2632 if (NS_FAILED(rv)) {
2633 return rv;
2636 // windows only knows about the following permissions
2637 int mode = 0;
2638 if (aPermissions & (PR_IRUSR | PR_IRGRP | PR_IROTH)) { // any read
2639 mode |= _S_IREAD;
2641 if (aPermissions & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) { // any write
2642 mode |= _S_IWRITE;
2645 if (_wchmod(mResolvedPath.get(), mode) == -1) {
2646 return NS_ERROR_FAILURE;
2649 return NS_OK;
2652 NS_IMETHODIMP
2653 nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions) {
2654 // The caller is assumed to have already called IsSymlink
2655 // and to have found that this file is a link.
2657 // windows only knows about the following permissions
2658 int mode = 0;
2659 if (aPermissions & (PR_IRUSR | PR_IRGRP | PR_IROTH)) { // any read
2660 mode |= _S_IREAD;
2662 if (aPermissions & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) { // any write
2663 mode |= _S_IWRITE;
2666 if (_wchmod(mWorkingPath.get(), mode) == -1) {
2667 return NS_ERROR_FAILURE;
2670 return NS_OK;
2673 NS_IMETHODIMP
2674 nsLocalFile::GetFileSize(int64_t* aFileSize) {
2675 if (NS_WARN_IF(!aFileSize)) {
2676 return NS_ERROR_INVALID_ARG;
2679 nsresult rv = ResolveAndStat();
2680 if (NS_FAILED(rv)) {
2681 return rv;
2684 *aFileSize = mFileInfo.size;
2685 return NS_OK;
2688 NS_IMETHODIMP
2689 nsLocalFile::GetFileSizeOfLink(int64_t* aFileSize) {
2690 // Check we are correctly initialized.
2691 CHECK_mWorkingPath();
2693 if (NS_WARN_IF(!aFileSize)) {
2694 return NS_ERROR_INVALID_ARG;
2697 // The caller is assumed to have already called IsSymlink
2698 // and to have found that this file is a link.
2700 FileInfo info{};
2701 if (NS_FAILED(GetFileInfo(mWorkingPath, &info))) {
2702 return NS_ERROR_FILE_INVALID_PATH;
2705 *aFileSize = info.size;
2706 return NS_OK;
2709 NS_IMETHODIMP
2710 nsLocalFile::SetFileSize(int64_t aFileSize) {
2711 // Check we are correctly initialized.
2712 CHECK_mWorkingPath();
2714 HANDLE hFile =
2715 ::CreateFileW(mWorkingPath.get(), // pointer to name of the file
2716 GENERIC_WRITE, // access (write) mode
2717 FILE_SHARE_READ, // share mode
2718 nullptr, // pointer to security attributes
2719 OPEN_EXISTING, // how to create
2720 FILE_ATTRIBUTE_NORMAL, // file attributes
2721 nullptr);
2722 if (hFile == INVALID_HANDLE_VALUE) {
2723 return ConvertWinError(GetLastError());
2726 // seek the file pointer to the new, desired end of file
2727 // and then truncate the file at that position
2728 nsresult rv = NS_ERROR_FAILURE;
2729 LARGE_INTEGER distance;
2730 distance.QuadPart = aFileSize;
2731 if (SetFilePointerEx(hFile, distance, nullptr, FILE_BEGIN) &&
2732 SetEndOfFile(hFile)) {
2733 MakeDirty();
2734 rv = NS_OK;
2737 CloseHandle(hFile);
2738 return rv;
2741 static nsresult GetDiskSpaceAttributes(const nsString& aResolvedPath,
2742 int64_t* aFreeBytesAvailable,
2743 int64_t* aTotalBytes) {
2744 ULARGE_INTEGER liFreeBytesAvailableToCaller;
2745 ULARGE_INTEGER liTotalNumberOfBytes;
2746 if (::GetDiskFreeSpaceExW(aResolvedPath.get(), &liFreeBytesAvailableToCaller,
2747 &liTotalNumberOfBytes, nullptr)) {
2748 *aFreeBytesAvailable = liFreeBytesAvailableToCaller.QuadPart;
2749 *aTotalBytes = liTotalNumberOfBytes.QuadPart;
2751 return NS_OK;
2754 return ConvertWinError(::GetLastError());
2757 NS_IMETHODIMP
2758 nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable) {
2759 // Check we are correctly initialized.
2760 CHECK_mWorkingPath();
2762 if (NS_WARN_IF(!aDiskSpaceAvailable)) {
2763 return NS_ERROR_INVALID_ARG;
2766 *aDiskSpaceAvailable = 0;
2768 nsresult rv = ResolveAndStat();
2769 if (NS_FAILED(rv)) {
2770 return rv;
2773 if (mFileInfo.type == PR_FILE_FILE) {
2774 // Since GetDiskFreeSpaceExW works only on directories, use the parent.
2775 nsCOMPtr<nsIFile> parent;
2776 if (NS_SUCCEEDED(GetParent(getter_AddRefs(parent))) && parent) {
2777 return parent->GetDiskSpaceAvailable(aDiskSpaceAvailable);
2781 int64_t dummy = 0;
2782 return GetDiskSpaceAttributes(mResolvedPath, aDiskSpaceAvailable, &dummy);
2785 NS_IMETHODIMP
2786 nsLocalFile::GetDiskCapacity(int64_t* aDiskCapacity) {
2787 // Check we are correctly initialized.
2788 CHECK_mWorkingPath();
2790 if (NS_WARN_IF(!aDiskCapacity)) {
2791 return NS_ERROR_INVALID_ARG;
2794 nsresult rv = ResolveAndStat();
2795 if (NS_FAILED(rv)) {
2796 return rv;
2799 if (mFileInfo.type == PR_FILE_FILE) {
2800 // Since GetDiskFreeSpaceExW works only on directories, use the parent.
2801 nsCOMPtr<nsIFile> parent;
2802 if (NS_SUCCEEDED(GetParent(getter_AddRefs(parent))) && parent) {
2803 return parent->GetDiskCapacity(aDiskCapacity);
2807 int64_t dummy = 0;
2808 return GetDiskSpaceAttributes(mResolvedPath, &dummy, aDiskCapacity);
2811 NS_IMETHODIMP
2812 nsLocalFile::GetParent(nsIFile** aParent) {
2813 // Check we are correctly initialized.
2814 CHECK_mWorkingPath();
2816 if (NS_WARN_IF(!aParent)) {
2817 return NS_ERROR_INVALID_ARG;
2820 // A two-character path must be a drive such as C:, so it has no parent
2821 if (mWorkingPath.Length() == 2) {
2822 *aParent = nullptr;
2823 return NS_OK;
2826 int32_t offset = mWorkingPath.RFindChar(char16_t('\\'));
2827 // adding this offset check that was removed in bug 241708 fixes mail
2828 // directories that aren't relative to/underneath the profile dir.
2829 // e.g., on a different drive. Before you remove them, please make
2830 // sure local mail directories that aren't underneath the profile dir work.
2831 if (offset == kNotFound) {
2832 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
2835 // A path of the form \\NAME is a top-level path and has no parent
2836 if (offset == 1 && mWorkingPath[0] == L'\\') {
2837 *aParent = nullptr;
2838 return NS_OK;
2841 nsAutoString parentPath(mWorkingPath);
2843 if (offset > 0) {
2844 parentPath.Truncate(offset);
2845 } else {
2846 parentPath.AssignLiteral("\\\\.");
2849 nsCOMPtr<nsIFile> localFile;
2850 nsresult rv = NewLocalFile(parentPath, mUseDOSDevicePathSyntax,
2851 getter_AddRefs(localFile));
2852 if (NS_FAILED(rv)) {
2853 return rv;
2856 localFile.forget(aParent);
2857 return NS_OK;
2860 NS_IMETHODIMP
2861 nsLocalFile::Exists(bool* aResult) {
2862 // Check we are correctly initialized.
2863 CHECK_mWorkingPath();
2865 if (NS_WARN_IF(!aResult)) {
2866 return NS_ERROR_INVALID_ARG;
2868 *aResult = false;
2870 MakeDirty();
2871 nsresult rv = ResolveAndStat();
2872 *aResult = NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_IS_LOCKED;
2874 return NS_OK;
2877 NS_IMETHODIMP
2878 nsLocalFile::IsWritable(bool* aIsWritable) {
2879 // Check we are correctly initialized.
2880 CHECK_mWorkingPath();
2882 // The read-only attribute on a FAT directory only means that it can't
2883 // be deleted. It is still possible to modify the contents of the directory.
2884 nsresult rv = IsDirectory(aIsWritable);
2885 if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
2886 *aIsWritable = true;
2887 return NS_OK;
2888 } else if (rv == NS_ERROR_FILE_IS_LOCKED) {
2889 // If the file is normally allowed write access
2890 // we should still return that the file is writable.
2891 } else if (NS_FAILED(rv)) {
2892 return rv;
2894 if (*aIsWritable) {
2895 return NS_OK;
2898 // writable if the file doesn't have the readonly attribute
2899 rv = HasFileAttribute(FILE_ATTRIBUTE_READONLY, aIsWritable);
2900 if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
2901 *aIsWritable = false;
2902 return NS_OK;
2903 } else if (rv == NS_ERROR_FILE_IS_LOCKED) {
2904 // If the file is normally allowed write access
2905 // we should still return that the file is writable.
2906 } else if (NS_FAILED(rv)) {
2907 return rv;
2909 *aIsWritable = !*aIsWritable;
2911 // If the read only attribute is not set, check to make sure
2912 // we can open the file with write access.
2913 if (*aIsWritable) {
2914 PRFileDesc* file;
2915 rv = OpenFile(mResolvedPath, PR_WRONLY, 0, false, &file);
2916 if (NS_SUCCEEDED(rv)) {
2917 PR_Close(file);
2918 } else if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
2919 *aIsWritable = false;
2920 } else if (rv == NS_ERROR_FILE_IS_LOCKED) {
2921 // If it is locked and read only we would have
2922 // gotten access denied
2923 *aIsWritable = true;
2924 } else {
2925 return rv;
2928 return NS_OK;
2931 NS_IMETHODIMP
2932 nsLocalFile::IsReadable(bool* aResult) {
2933 // Check we are correctly initialized.
2934 CHECK_mWorkingPath();
2936 if (NS_WARN_IF(!aResult)) {
2937 return NS_ERROR_INVALID_ARG;
2939 *aResult = false;
2941 nsresult rv = ResolveAndStat();
2942 if (NS_FAILED(rv)) {
2943 return rv;
2946 *aResult = true;
2947 return NS_OK;
2950 nsresult nsLocalFile::LookupExtensionIn(const char* const* aExtensionsArray,
2951 size_t aArrayLength, bool* aResult) {
2952 // Check we are correctly initialized.
2953 CHECK_mWorkingPath();
2955 if (NS_WARN_IF(!aResult)) {
2956 return NS_ERROR_INVALID_ARG;
2958 *aResult = false;
2960 nsresult rv;
2962 // only files can be executables
2963 bool isFile;
2964 rv = IsFile(&isFile);
2965 if (NS_FAILED(rv)) {
2966 return rv;
2968 if (!isFile) {
2969 return NS_OK;
2972 // TODO: shouldn't we be checking mFollowSymlinks here?
2973 bool symLink = false;
2974 rv = IsSymlink(&symLink);
2975 if (NS_FAILED(rv)) {
2976 return rv;
2979 nsAutoString path;
2980 if (symLink) {
2981 GetTarget(path);
2982 } else {
2983 GetPath(path);
2986 // kill trailing dots and spaces.
2987 int32_t filePathLen = path.Length() - 1;
2988 while (filePathLen > 0 &&
2989 (path[filePathLen] == L' ' || path[filePathLen] == L'.')) {
2990 path.Truncate(filePathLen--);
2993 // Get extension.
2994 int32_t dotIdx = path.RFindChar(char16_t('.'));
2995 if (dotIdx != kNotFound) {
2996 // Convert extension to lower case.
2997 char16_t* p = path.BeginWriting();
2998 for (p += dotIdx + 1; *p; ++p) {
2999 *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0;
3002 nsDependentSubstring ext = Substring(path, dotIdx);
3003 for (size_t i = 0; i < aArrayLength; ++i) {
3004 if (ext.EqualsASCII(aExtensionsArray[i])) {
3005 // Found a match. Set result and quit.
3006 *aResult = true;
3007 break;
3012 return NS_OK;
3015 NS_IMETHODIMP
3016 nsLocalFile::IsExecutable(bool* aResult) {
3017 return LookupExtensionIn(sExecutableExts, ArrayLength(sExecutableExts),
3018 aResult);
3021 NS_IMETHODIMP
3022 nsLocalFile::IsDirectory(bool* aResult) {
3023 return HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, aResult);
3026 NS_IMETHODIMP
3027 nsLocalFile::IsFile(bool* aResult) {
3028 nsresult rv = HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, aResult);
3029 if (NS_SUCCEEDED(rv)) {
3030 *aResult = !*aResult;
3032 return rv;
3035 NS_IMETHODIMP
3036 nsLocalFile::IsHidden(bool* aResult) {
3037 return HasFileAttribute(FILE_ATTRIBUTE_HIDDEN, aResult);
3040 nsresult nsLocalFile::HasFileAttribute(DWORD aFileAttrib, bool* aResult) {
3041 if (NS_WARN_IF(!aResult)) {
3042 return NS_ERROR_INVALID_ARG;
3045 nsresult rv = Resolve();
3046 if (NS_FAILED(rv)) {
3047 return rv;
3050 DWORD attributes = GetFileAttributesW(mResolvedPath.get());
3051 if (INVALID_FILE_ATTRIBUTES == attributes) {
3052 return ConvertWinError(GetLastError());
3055 *aResult = ((attributes & aFileAttrib) != 0);
3056 return NS_OK;
3059 NS_IMETHODIMP
3060 nsLocalFile::IsSymlink(bool* aResult) {
3061 // Check we are correctly initialized.
3062 CHECK_mWorkingPath();
3064 if (NS_WARN_IF(!aResult)) {
3065 return NS_ERROR_INVALID_ARG;
3068 // TODO: Implement symlink support
3069 *aResult = false;
3070 return NS_OK;
3073 NS_IMETHODIMP
3074 nsLocalFile::IsSpecial(bool* aResult) {
3075 return HasFileAttribute(FILE_ATTRIBUTE_SYSTEM, aResult);
3078 NS_IMETHODIMP
3079 nsLocalFile::Equals(nsIFile* aInFile, bool* aResult) {
3080 if (NS_WARN_IF(!aInFile)) {
3081 return NS_ERROR_INVALID_ARG;
3083 if (NS_WARN_IF(!aResult)) {
3084 return NS_ERROR_INVALID_ARG;
3087 nsCOMPtr<nsILocalFileWin> lf(do_QueryInterface(aInFile));
3088 if (!lf) {
3089 *aResult = false;
3090 return NS_OK;
3093 bool inUseDOSDevicePathSyntax;
3094 lf->GetUseDOSDevicePathSyntax(&inUseDOSDevicePathSyntax);
3096 // If useDOSDevicePathSyntax are different remove the prefix from the one that
3097 // might have it. This is added because of Omnijar. It compares files from
3098 // different modules with itself.
3099 bool removePathPrefix, removeInPathPrefix;
3100 if (inUseDOSDevicePathSyntax != mUseDOSDevicePathSyntax) {
3101 removeInPathPrefix = inUseDOSDevicePathSyntax;
3102 removePathPrefix = mUseDOSDevicePathSyntax;
3103 } else {
3104 removePathPrefix = removeInPathPrefix = false;
3107 nsAutoString inFilePath, workingPath;
3108 aInFile->GetPath(inFilePath);
3109 workingPath = mWorkingPath;
3111 constexpr static auto equalPath =
3112 [](nsAutoString& workingPath, nsAutoString& inFilePath,
3113 bool removePathPrefix, bool removeInPathPrefix) {
3114 if (removeInPathPrefix &&
3115 StringBeginsWith(inFilePath, kDevicePathSpecifier)) {
3116 MOZ_ASSERT(!StringBeginsWith(workingPath, kDevicePathSpecifier));
3118 inFilePath = Substring(inFilePath, kDevicePathSpecifier.Length());
3119 } else if (removePathPrefix &&
3120 StringBeginsWith(workingPath, kDevicePathSpecifier)) {
3121 MOZ_ASSERT(!StringBeginsWith(inFilePath, kDevicePathSpecifier));
3123 workingPath = Substring(workingPath, kDevicePathSpecifier.Length());
3126 return _wcsicmp(workingPath.get(), inFilePath.get()) == 0;
3129 if (equalPath(workingPath, inFilePath, removePathPrefix,
3130 removeInPathPrefix)) {
3131 *aResult = true;
3132 return NS_OK;
3135 EnsureShortPath();
3136 lf->GetCanonicalPath(inFilePath);
3137 workingPath = mShortWorkingPath;
3138 *aResult =
3139 equalPath(workingPath, inFilePath, removePathPrefix, removeInPathPrefix);
3141 return NS_OK;
3144 NS_IMETHODIMP
3145 nsLocalFile::Contains(nsIFile* aInFile, bool* aResult) {
3146 // Check we are correctly initialized.
3147 CHECK_mWorkingPath();
3149 *aResult = false;
3151 nsAutoString myFilePath;
3152 if (NS_FAILED(GetTarget(myFilePath))) {
3153 GetPath(myFilePath);
3156 uint32_t myFilePathLen = myFilePath.Length();
3158 nsAutoString inFilePath;
3159 if (NS_FAILED(aInFile->GetTarget(inFilePath))) {
3160 aInFile->GetPath(inFilePath);
3163 // Make sure that the |aInFile|'s path has a trailing separator.
3164 if (inFilePath.Length() > myFilePathLen &&
3165 inFilePath[myFilePathLen] == L'\\') {
3166 if (_wcsnicmp(myFilePath.get(), inFilePath.get(), myFilePathLen) == 0) {
3167 *aResult = true;
3171 return NS_OK;
3174 NS_IMETHODIMP
3175 nsLocalFile::GetTarget(nsAString& aResult) {
3176 aResult.Truncate();
3177 Resolve();
3179 MOZ_ASSERT_IF(
3180 mUseDOSDevicePathSyntax,
3181 !FilePreferences::StartsWithDiskDesignatorAndBackslash(mResolvedPath));
3183 aResult = mResolvedPath;
3184 return NS_OK;
3187 NS_IMETHODIMP
3188 nsLocalFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator** aEntries) {
3189 nsresult rv;
3191 *aEntries = nullptr;
3192 if (mWorkingPath.EqualsLiteral("\\\\.")) {
3193 RefPtr<nsDriveEnumerator> drives =
3194 new nsDriveEnumerator(mUseDOSDevicePathSyntax);
3195 rv = drives->Init();
3196 if (NS_FAILED(rv)) {
3197 return rv;
3199 drives.forget(aEntries);
3200 return NS_OK;
3203 RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator();
3204 rv = dirEnum->Init(this);
3205 if (NS_FAILED(rv)) {
3206 return rv;
3209 dirEnum.forget(aEntries);
3211 return NS_OK;
3214 NS_IMETHODIMP
3215 nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor) {
3216 CopyUTF16toUTF8(mWorkingPath, aPersistentDescriptor);
3217 return NS_OK;
3220 NS_IMETHODIMP
3221 nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor) {
3222 if (IsUtf8(aPersistentDescriptor)) {
3223 return InitWithPath(NS_ConvertUTF8toUTF16(aPersistentDescriptor));
3224 } else {
3225 return InitWithNativePath(aPersistentDescriptor);
3229 NS_IMETHODIMP
3230 nsLocalFile::GetReadOnly(bool* aReadOnly) {
3231 NS_ENSURE_ARG_POINTER(aReadOnly);
3233 DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get());
3234 if (dwAttrs == INVALID_FILE_ATTRIBUTES) {
3235 return NS_ERROR_FILE_INVALID_PATH;
3238 *aReadOnly = dwAttrs & FILE_ATTRIBUTE_READONLY;
3240 return NS_OK;
3243 NS_IMETHODIMP
3244 nsLocalFile::SetReadOnly(bool aReadOnly) {
3245 DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get());
3246 if (dwAttrs == INVALID_FILE_ATTRIBUTES) {
3247 return NS_ERROR_FILE_INVALID_PATH;
3250 if (aReadOnly) {
3251 dwAttrs |= FILE_ATTRIBUTE_READONLY;
3252 } else {
3253 dwAttrs &= ~FILE_ATTRIBUTE_READONLY;
3256 if (SetFileAttributesW(mWorkingPath.get(), dwAttrs) == 0) {
3257 return NS_ERROR_FAILURE;
3260 return NS_OK;
3263 NS_IMETHODIMP
3264 nsLocalFile::GetUseDOSDevicePathSyntax(bool* aUseDOSDevicePathSyntax) {
3265 MOZ_ASSERT(aUseDOSDevicePathSyntax);
3267 *aUseDOSDevicePathSyntax = mUseDOSDevicePathSyntax;
3268 return NS_OK;
3271 NS_IMETHODIMP
3272 nsLocalFile::SetUseDOSDevicePathSyntax(bool aUseDOSDevicePathSyntax) {
3273 if (mUseDOSDevicePathSyntax == aUseDOSDevicePathSyntax) {
3274 return NS_OK;
3277 if (mUseDOSDevicePathSyntax) {
3278 if (StringBeginsWith(mWorkingPath, kDevicePathSpecifier)) {
3279 MakeDirty();
3280 // Remove the prefix
3281 mWorkingPath = Substring(mWorkingPath, kDevicePathSpecifier.Length());
3283 } else {
3284 if (FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath)) {
3285 MakeDirty();
3286 // Prepend the prefix
3287 mWorkingPath = kDevicePathSpecifier + mWorkingPath;
3291 mUseDOSDevicePathSyntax = aUseDOSDevicePathSyntax;
3292 return NS_OK;
3295 NS_IMETHODIMP
3296 nsLocalFile::Reveal() {
3297 // This API should be main thread only
3298 MOZ_ASSERT(NS_IsMainThread());
3300 // make sure mResolvedPath is set
3301 nsresult rv = Resolve();
3302 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
3303 return rv;
3306 nsCOMPtr<nsIRunnable> task =
3307 NS_NewRunnableFunction("nsLocalFile::Reveal", [path = mResolvedPath]() {
3308 MOZ_ASSERT(!NS_IsMainThread(), "Don't run on the main thread");
3310 bool doCoUninitialize = SUCCEEDED(CoInitializeEx(
3311 nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));
3312 RevealFile(path);
3313 if (doCoUninitialize) {
3314 CoUninitialize();
3318 return NS_DispatchBackgroundTask(task,
3319 nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK);
3322 NS_IMETHODIMP
3323 nsLocalFile::GetWindowsFileAttributes(uint32_t* aAttrs) {
3324 NS_ENSURE_ARG_POINTER(aAttrs);
3326 DWORD dwAttrs = ::GetFileAttributesW(mWorkingPath.get());
3327 if (dwAttrs == INVALID_FILE_ATTRIBUTES) {
3328 return ConvertWinError(GetLastError());
3331 *aAttrs = dwAttrs;
3333 return NS_OK;
3336 NS_IMETHODIMP
3337 nsLocalFile::SetWindowsFileAttributes(uint32_t aSetAttrs,
3338 uint32_t aClearAttrs) {
3339 DWORD dwAttrs = ::GetFileAttributesW(mWorkingPath.get());
3340 if (dwAttrs == INVALID_FILE_ATTRIBUTES) {
3341 return ConvertWinError(GetLastError());
3344 dwAttrs = (dwAttrs & ~aClearAttrs) | aSetAttrs;
3346 if (::SetFileAttributesW(mWorkingPath.get(), dwAttrs) == 0) {
3347 return ConvertWinError(GetLastError());
3349 return NS_OK;
3352 NS_IMETHODIMP
3353 nsLocalFile::Launch() {
3354 // This API should be main thread only
3355 MOZ_ASSERT(NS_IsMainThread());
3357 // use the app registry name to launch a shell execute....
3358 _bstr_t execPath(mWorkingPath.get());
3360 _variant_t args;
3361 // Pass VT_ERROR/DISP_E_PARAMNOTFOUND to omit an optional RPC parameter
3362 // to execute a file with the default verb.
3363 _variant_t verbDefault(DISP_E_PARAMNOTFOUND, VT_ERROR);
3364 _variant_t showCmd(SW_SHOWNORMAL);
3366 // Use the directory of the file we're launching as the working
3367 // directory. That way if we have a self extracting EXE it won't
3368 // suggest to extract to the install directory.
3369 wchar_t* workingDirectoryPtr = nullptr;
3370 WCHAR workingDirectory[MAX_PATH + 1] = {L'\0'};
3371 wcsncpy(workingDirectory, mWorkingPath.get(), MAX_PATH);
3372 if (PathRemoveFileSpecW(workingDirectory)) {
3373 workingDirectoryPtr = workingDirectory;
3374 } else {
3375 NS_WARNING("Could not set working directory for launched file.");
3378 // We have two methods to launch a file: ShellExecuteExW and
3379 // ShellExecuteByExplorer. ShellExecuteExW starts a new process as a child
3380 // of the current process, while ShellExecuteByExplorer starts a new process
3381 // as a child of explorer.exe.
3383 // We prefer launching a process via ShellExecuteByExplorer because
3384 // applications may not support the mitigation policies inherited from our
3385 // process. For example, Skype for Business does not start correctly with
3386 // the PreferSystem32Images policy which is one of the policies we use.
3388 // If ShellExecuteByExplorer fails for some reason e.g. a system without
3389 // running explorer.exe or VDI environment like Citrix, we fall back to
3390 // ShellExecuteExW which still works in those special environments.
3392 // There is an exception where we go straight to ShellExecuteExW without
3393 // trying ShellExecuteByExplorer. When the extension of a downloaded file is
3394 // "exe", we prefer security rather than compatibility.
3396 // When a user launches a downloaded executable, the directory containing
3397 // the downloaded file may contain a malicious DLL with a common name, which
3398 // may have been downloaded before. If the downloaded executable is launched
3399 // without the PreferSystem32Images policy, the process can be tricked into
3400 // loading the malicious DLL in the same directory if its name is in the
3401 // executable's dependent modules. Therefore, we always launch ".exe"
3402 // executables via ShellExecuteExW so they inherit our process's mitigation
3403 // policies including PreferSystem32Images.
3405 // If the extension is not "exe", then we assume that we are launching an
3406 // installed application, and therefore the security risk described above
3407 // is lessened, as a malicious DLL is less likely to be installed in the
3408 // application's directory. In that case, we attempt to preserve
3409 // compatibility and try ShellExecuteByExplorer first.
3411 static const char* const onlyExeExt[] = {".exe"};
3412 bool isExecutable;
3413 nsresult rv =
3414 LookupExtensionIn(onlyExeExt, ArrayLength(onlyExeExt), &isExecutable);
3415 if (NS_FAILED(rv)) {
3416 isExecutable = false;
3419 // If the file is an executable, go straight to ShellExecuteExW.
3420 // Otherwise try ShellExecuteByExplorer first, and if it fails,
3421 // run ShellExecuteExW.
3422 if (!isExecutable) {
3423 mozilla::LauncherVoidResult shellExecuteOk =
3424 mozilla::ShellExecuteByExplorer(execPath, args, verbDefault,
3425 workingDirectoryPtr, showCmd);
3426 if (shellExecuteOk.isOk()) {
3427 return NS_OK;
3431 SHELLEXECUTEINFOW seinfo = {sizeof(SHELLEXECUTEINFOW)};
3432 seinfo.fMask = SEE_MASK_ASYNCOK;
3433 seinfo.hwnd = GetMostRecentNavigatorHWND();
3434 seinfo.lpVerb = nullptr;
3435 seinfo.lpFile = mWorkingPath.get();
3436 seinfo.lpParameters = nullptr;
3437 seinfo.lpDirectory = workingDirectoryPtr;
3438 seinfo.nShow = SW_SHOWNORMAL;
3440 if (!ShellExecuteExW(&seinfo)) {
3441 return NS_ERROR_FILE_EXECUTION_FAILED;
3444 return NS_OK;
3447 nsresult NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks,
3448 nsIFile** aResult) {
3449 RefPtr<nsLocalFile> file = new nsLocalFile();
3451 if (!aPath.IsEmpty()) {
3452 nsresult rv = file->InitWithPath(aPath);
3453 if (NS_FAILED(rv)) {
3454 return rv;
3458 file.forget(aResult);
3459 return NS_OK;
3462 //-----------------------------------------------------------------------------
3463 // Native (lossy) interface
3464 //-----------------------------------------------------------------------------
3466 NS_IMETHODIMP
3467 nsLocalFile::InitWithNativePath(const nsACString& aFilePath) {
3468 nsAutoString tmp;
3469 nsresult rv = NS_CopyNativeToUnicode(aFilePath, tmp);
3470 if (NS_SUCCEEDED(rv)) {
3471 return InitWithPath(tmp);
3474 return rv;
3477 NS_IMETHODIMP
3478 nsLocalFile::AppendNative(const nsACString& aNode) {
3479 nsAutoString tmp;
3480 nsresult rv = NS_CopyNativeToUnicode(aNode, tmp);
3481 if (NS_SUCCEEDED(rv)) {
3482 return Append(tmp);
3485 return rv;
3488 NS_IMETHODIMP
3489 nsLocalFile::AppendRelativeNativePath(const nsACString& aNode) {
3490 nsAutoString tmp;
3491 nsresult rv = NS_CopyNativeToUnicode(aNode, tmp);
3492 if (NS_SUCCEEDED(rv)) {
3493 return AppendRelativePath(tmp);
3495 return rv;
3498 NS_IMETHODIMP
3499 nsLocalFile::GetNativeLeafName(nsACString& aLeafName) {
3500 // NS_WARNING("This API is lossy. Use GetLeafName !");
3501 nsAutoString tmp;
3502 nsresult rv = GetLeafName(tmp);
3503 if (NS_SUCCEEDED(rv)) {
3504 rv = NS_CopyUnicodeToNative(tmp, aLeafName);
3507 return rv;
3510 NS_IMETHODIMP
3511 nsLocalFile::SetNativeLeafName(const nsACString& aLeafName) {
3512 nsAutoString tmp;
3513 nsresult rv = NS_CopyNativeToUnicode(aLeafName, tmp);
3514 if (NS_SUCCEEDED(rv)) {
3515 return SetLeafName(tmp);
3518 return rv;
3521 nsString nsLocalFile::NativePath() { return mWorkingPath; }
3523 nsCString nsIFile::HumanReadablePath() {
3524 nsString path;
3525 DebugOnly<nsresult> rv = GetPath(path);
3526 MOZ_ASSERT(NS_SUCCEEDED(rv));
3527 return NS_ConvertUTF16toUTF8(path);
3530 NS_IMETHODIMP
3531 nsLocalFile::CopyToNative(nsIFile* aNewParentDir, const nsACString& aNewName) {
3532 // Check we are correctly initialized.
3533 CHECK_mWorkingPath();
3535 if (aNewName.IsEmpty()) {
3536 return CopyTo(aNewParentDir, u""_ns);
3539 nsAutoString tmp;
3540 nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
3541 if (NS_SUCCEEDED(rv)) {
3542 return CopyTo(aNewParentDir, tmp);
3545 return rv;
3548 NS_IMETHODIMP
3549 nsLocalFile::CopyToFollowingLinksNative(nsIFile* aNewParentDir,
3550 const nsACString& aNewName) {
3551 if (aNewName.IsEmpty()) {
3552 return CopyToFollowingLinks(aNewParentDir, u""_ns);
3555 nsAutoString tmp;
3556 nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
3557 if (NS_SUCCEEDED(rv)) {
3558 return CopyToFollowingLinks(aNewParentDir, tmp);
3561 return rv;
3564 NS_IMETHODIMP
3565 nsLocalFile::MoveToNative(nsIFile* aNewParentDir, const nsACString& aNewName) {
3566 // Check we are correctly initialized.
3567 CHECK_mWorkingPath();
3569 if (aNewName.IsEmpty()) {
3570 return MoveTo(aNewParentDir, u""_ns);
3573 nsAutoString tmp;
3574 nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
3575 if (NS_SUCCEEDED(rv)) {
3576 return MoveTo(aNewParentDir, tmp);
3579 return rv;
3582 NS_IMETHODIMP
3583 nsLocalFile::MoveToFollowingLinksNative(nsIFile* aNewParentDir,
3584 const nsACString& aNewName) {
3585 // Check we are correctly initialized.
3586 CHECK_mWorkingPath();
3588 if (aNewName.IsEmpty()) {
3589 return MoveToFollowingLinks(aNewParentDir, u""_ns);
3592 nsAutoString tmp;
3593 nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
3594 if (NS_SUCCEEDED(rv)) {
3595 return MoveToFollowingLinks(aNewParentDir, tmp);
3598 return rv;
3601 NS_IMETHODIMP
3602 nsLocalFile::GetNativeTarget(nsACString& aResult) {
3603 // Check we are correctly initialized.
3604 CHECK_mWorkingPath();
3606 NS_WARNING("This API is lossy. Use GetTarget !");
3607 nsAutoString tmp;
3608 nsresult rv = GetTarget(tmp);
3609 if (NS_SUCCEEDED(rv)) {
3610 rv = NS_CopyUnicodeToNative(tmp, aResult);
3613 return rv;
3616 nsresult NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowLinks,
3617 nsIFile** aResult) {
3618 nsAutoString buf;
3619 nsresult rv = NS_CopyNativeToUnicode(aPath, buf);
3620 if (NS_FAILED(rv)) {
3621 *aResult = nullptr;
3622 return rv;
3624 return NS_NewLocalFile(buf, aFollowLinks, aResult);
3627 void nsLocalFile::EnsureShortPath() {
3628 if (!mShortWorkingPath.IsEmpty()) {
3629 return;
3632 WCHAR shortPath[MAX_PATH + 1];
3633 DWORD lengthNeeded = ::GetShortPathNameW(mWorkingPath.get(), shortPath,
3634 ArrayLength(shortPath));
3635 // If an error occurred then lengthNeeded is set to 0 or the length of the
3636 // needed buffer including null termination. If it succeeds the number of
3637 // wide characters not including null termination is returned.
3638 if (lengthNeeded != 0 && lengthNeeded < ArrayLength(shortPath)) {
3639 mShortWorkingPath.Assign(shortPath);
3640 } else {
3641 mShortWorkingPath.Assign(mWorkingPath);
3645 NS_IMPL_ISUPPORTS_INHERITED(nsDriveEnumerator, nsSimpleEnumerator,
3646 nsIDirectoryEnumerator)
3648 nsDriveEnumerator::nsDriveEnumerator(bool aUseDOSDevicePathSyntax)
3649 : mUseDOSDevicePathSyntax(aUseDOSDevicePathSyntax) {}
3651 nsDriveEnumerator::~nsDriveEnumerator() {}
3653 nsresult nsDriveEnumerator::Init() {
3654 /* If the length passed to GetLogicalDriveStrings is smaller
3655 * than the length of the string it would return, it returns
3656 * the length required for the string. */
3657 DWORD length = GetLogicalDriveStringsW(0, 0);
3658 /* The string is null terminated */
3659 if (!mDrives.SetLength(length + 1, fallible)) {
3660 return NS_ERROR_OUT_OF_MEMORY;
3662 if (!GetLogicalDriveStringsW(length, mDrives.get())) {
3663 return NS_ERROR_FAILURE;
3665 mDrives.BeginReading(mStartOfCurrentDrive);
3666 mDrives.EndReading(mEndOfDrivesString);
3667 return NS_OK;
3670 NS_IMETHODIMP
3671 nsDriveEnumerator::HasMoreElements(bool* aHasMore) {
3672 *aHasMore = *mStartOfCurrentDrive != L'\0';
3673 return NS_OK;
3676 NS_IMETHODIMP
3677 nsDriveEnumerator::GetNext(nsISupports** aNext) {
3678 /* GetLogicalDrives stored in mDrives is a concatenation
3679 * of null terminated strings, followed by a null terminator.
3680 * mStartOfCurrentDrive is an iterator pointing at the first
3681 * character of the current drive. */
3682 if (*mStartOfCurrentDrive == L'\0') {
3683 *aNext = nullptr;
3684 return NS_ERROR_FAILURE;
3687 nsAString::const_iterator driveEnd = mStartOfCurrentDrive;
3688 FindCharInReadable(L'\0', driveEnd, mEndOfDrivesString);
3689 nsString drive(Substring(mStartOfCurrentDrive, driveEnd));
3690 mStartOfCurrentDrive = ++driveEnd;
3692 nsIFile* file;
3693 nsresult rv = NewLocalFile(drive, mUseDOSDevicePathSyntax, &file);
3695 *aNext = file;
3696 return rv;