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