Bumping manifests a=b2g-bump
[gecko.git] / xpcom / io / nsLocalFileWin.cpp
blob777b4fc4ed721be1e89e6a2c0cf2fc0a0b488e49
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/WindowsVersion.h"
11 #include "nsCOMPtr.h"
12 #include "nsAutoPtr.h"
13 #include "nsMemory.h"
15 #include "nsLocalFile.h"
16 #include "nsIDirectoryEnumerator.h"
17 #include "nsNativeCharsetUtils.h"
19 #include "nsISimpleEnumerator.h"
20 #include "nsIComponentManager.h"
21 #include "prio.h"
22 #include "private/pprio.h" // To get PR_ImportFile
23 #include "prprf.h"
24 #include "prmem.h"
25 #include "nsHashKeys.h"
27 #include "nsXPIDLString.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 "nsXPIDLString.h"
44 #include "prproces.h"
45 #include "prlink.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"
55 using namespace mozilla;
57 #define CHECK_mWorkingPath() \
58 PR_BEGIN_MACRO \
59 if (mWorkingPath.IsEmpty()) \
60 return NS_ERROR_NOT_INITIALIZED; \
61 PR_END_MACRO
63 // CopyFileEx only supports unbuffered I/O in Windows Vista and above
64 #ifndef COPY_FILE_NO_BUFFERING
65 #define COPY_FILE_NO_BUFFERING 0x00001000
66 #endif
68 #ifndef FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
69 #define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000
70 #endif
72 #ifndef DRIVE_REMOTE
73 #define DRIVE_REMOTE 4
74 #endif
76 /**
77 * A runnable to dispatch back to the main thread when
78 * AsyncLocalFileWinOperation completes.
80 class AsyncLocalFileWinDone : public nsRunnable
82 public:
83 AsyncLocalFileWinDone() :
84 mWorkerThread(do_GetCurrentThread())
86 // Objects of this type must only be created on worker threads
87 MOZ_ASSERT(!NS_IsMainThread());
90 NS_IMETHOD Run()
92 // This event shuts down the worker thread and so must be main thread.
93 MOZ_ASSERT(NS_IsMainThread());
95 // If we don't destroy the thread when we're done with it, it will hang
96 // around forever... and that is bad!
97 mWorkerThread->Shutdown();
98 return NS_OK;
101 private:
102 nsCOMPtr<nsIThread> mWorkerThread;
106 * A runnable to dispatch from the main thread when an async operation should
107 * be performed.
109 class AsyncLocalFileWinOperation : public nsRunnable
111 public:
112 enum FileOp { RevealOp, LaunchOp };
114 AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::FileOp aOperation,
115 const nsAString& aResolvedPath) :
116 mOperation(aOperation),
117 mResolvedPath(aResolvedPath)
121 NS_IMETHOD Run()
123 MOZ_ASSERT(!NS_IsMainThread(),
124 "AsyncLocalFileWinOperation should not be run on the main thread!");
126 CoInitialize(nullptr);
127 switch (mOperation) {
128 case RevealOp: {
129 Reveal();
131 break;
132 case LaunchOp: {
133 Launch();
135 break;
137 CoUninitialize();
139 // Send the result back to the main thread so that it can shutdown
140 nsCOMPtr<nsIRunnable> resultrunnable = new AsyncLocalFileWinDone();
141 NS_DispatchToMainThread(resultrunnable);
142 return NS_OK;
145 private:
146 // Reveals the path in explorer.
147 nsresult Reveal()
149 DWORD attributes = GetFileAttributesW(mResolvedPath.get());
150 if (INVALID_FILE_ATTRIBUTES == attributes) {
151 return NS_ERROR_FILE_INVALID_PATH;
154 HRESULT hr;
155 if (attributes & FILE_ATTRIBUTE_DIRECTORY) {
156 // We have a directory so we should open the directory itself.
157 ITEMIDLIST* dir = ILCreateFromPathW(mResolvedPath.get());
158 if (!dir) {
159 return NS_ERROR_FAILURE;
162 const ITEMIDLIST* selection[] = { dir };
163 UINT count = ArrayLength(selection);
165 //Perform the open of the directory.
166 hr = SHOpenFolderAndSelectItems(dir, count, selection, 0);
167 CoTaskMemFree(dir);
168 } else {
169 int32_t len = mResolvedPath.Length();
170 // We don't currently handle UNC long paths of the form \\?\ anywhere so
171 // this should be fine.
172 if (len > MAX_PATH) {
173 return NS_ERROR_FILE_INVALID_PATH;
175 WCHAR parentDirectoryPath[MAX_PATH + 1] = { 0 };
176 wcsncpy(parentDirectoryPath, mResolvedPath.get(), MAX_PATH);
177 PathRemoveFileSpecW(parentDirectoryPath);
179 // We have a file so we should open the parent directory.
180 ITEMIDLIST* dir = ILCreateFromPathW(parentDirectoryPath);
181 if (!dir) {
182 return NS_ERROR_FAILURE;
185 // Set the item in the directory to select to the file we want to reveal.
186 ITEMIDLIST* item = ILCreateFromPathW(mResolvedPath.get());
187 if (!item) {
188 CoTaskMemFree(dir);
189 return NS_ERROR_FAILURE;
192 const ITEMIDLIST* selection[] = { item };
193 UINT count = ArrayLength(selection);
195 //Perform the selection of the file.
196 hr = SHOpenFolderAndSelectItems(dir, count, selection, 0);
198 CoTaskMemFree(dir);
199 CoTaskMemFree(item);
202 return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
205 // Launches the default shell operation for the file path
206 nsresult Launch()
208 // use the app registry name to launch a shell execute....
209 SHELLEXECUTEINFOW seinfo;
210 memset(&seinfo, 0, sizeof(seinfo));
211 seinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
212 if (XRE_GetWindowsEnvironment() == WindowsEnvironmentType_Metro) {
213 seinfo.fMask = SEE_MASK_FLAG_LOG_USAGE;
215 seinfo.hwnd = nullptr;
216 seinfo.lpVerb = nullptr;
217 seinfo.lpFile = mResolvedPath.get();
218 seinfo.lpParameters = nullptr;
219 seinfo.lpDirectory = nullptr;
220 seinfo.nShow = SW_SHOWNORMAL;
222 // Use the directory of the file we're launching as the working
223 // directory. That way if we have a self extracting EXE it won't
224 // suggest to extract to the install directory.
225 WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' };
226 wcsncpy(workingDirectory, mResolvedPath.get(), MAX_PATH);
227 if (PathRemoveFileSpecW(workingDirectory)) {
228 seinfo.lpDirectory = workingDirectory;
229 } else {
230 NS_WARNING("Could not set working directory for launched file.");
233 if (ShellExecuteExW(&seinfo)) {
234 return NS_OK;
236 DWORD r = GetLastError();
237 // if the file has no association, we launch windows'
238 // "what do you want to do" dialog
239 if (r == SE_ERR_NOASSOC) {
240 nsAutoString shellArg;
241 shellArg.AssignLiteral(MOZ_UTF16("shell32.dll,OpenAs_RunDLL "));
242 shellArg.Append(mResolvedPath);
243 seinfo.lpFile = L"RUNDLL32.EXE";
244 seinfo.lpParameters = shellArg.get();
245 if (ShellExecuteExW(&seinfo)) {
246 return NS_OK;
248 r = GetLastError();
250 if (r < 32) {
251 switch (r) {
252 case 0:
253 case SE_ERR_OOM:
254 return NS_ERROR_OUT_OF_MEMORY;
255 case ERROR_FILE_NOT_FOUND:
256 return NS_ERROR_FILE_NOT_FOUND;
257 case ERROR_PATH_NOT_FOUND:
258 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
259 case ERROR_BAD_FORMAT:
260 return NS_ERROR_FILE_CORRUPTED;
261 case SE_ERR_ACCESSDENIED:
262 return NS_ERROR_FILE_ACCESS_DENIED;
263 case SE_ERR_ASSOCINCOMPLETE:
264 case SE_ERR_NOASSOC:
265 return NS_ERROR_UNEXPECTED;
266 case SE_ERR_DDEBUSY:
267 case SE_ERR_DDEFAIL:
268 case SE_ERR_DDETIMEOUT:
269 return NS_ERROR_NOT_AVAILABLE;
270 case SE_ERR_DLLNOTFOUND:
271 return NS_ERROR_FAILURE;
272 case SE_ERR_SHARE:
273 return NS_ERROR_FILE_IS_LOCKED;
274 default:
275 return NS_ERROR_FILE_EXECUTION_FAILED;
278 return NS_OK;
281 // Stores the operation that will be performed on the thread
282 AsyncLocalFileWinOperation::FileOp mOperation;
284 // Stores the path to perform the operation on
285 nsString mResolvedPath;
288 class nsDriveEnumerator : public nsISimpleEnumerator
290 public:
291 nsDriveEnumerator();
292 NS_DECL_ISUPPORTS
293 NS_DECL_NSISIMPLEENUMERATOR
294 nsresult Init();
295 private:
296 virtual ~nsDriveEnumerator();
298 /* mDrives stores the null-separated drive names.
299 * Init sets them.
300 * HasMoreElements checks mStartOfCurrentDrive.
301 * GetNext advances mStartOfCurrentDrive.
303 nsString mDrives;
304 nsAString::const_iterator mStartOfCurrentDrive;
305 nsAString::const_iterator mEndOfDrivesString;
308 //----------------------------------------------------------------------------
309 // short cut resolver
310 //----------------------------------------------------------------------------
311 class ShortcutResolver
313 public:
314 ShortcutResolver();
315 // nonvirtual since we're not subclassed
316 ~ShortcutResolver();
318 nsresult Init();
319 nsresult Resolve(const WCHAR* aIn, WCHAR* aOut);
320 nsresult SetShortcut(bool aUpdateExisting,
321 const WCHAR* aShortcutPath,
322 const WCHAR* aTargetPath,
323 const WCHAR* aWorkingDir,
324 const WCHAR* aArgs,
325 const WCHAR* aDescription,
326 const WCHAR* aIconFile,
327 int32_t aIconIndex);
329 private:
330 Mutex mLock;
331 nsRefPtr<IPersistFile> mPersistFile;
332 nsRefPtr<IShellLinkW> mShellLink;
335 ShortcutResolver::ShortcutResolver() :
336 mLock("ShortcutResolver.mLock")
338 CoInitialize(nullptr);
341 ShortcutResolver::~ShortcutResolver()
343 CoUninitialize();
346 nsresult
347 ShortcutResolver::Init()
349 // Get a pointer to the IPersistFile interface.
350 if (FAILED(CoCreateInstance(CLSID_ShellLink,
351 nullptr,
352 CLSCTX_INPROC_SERVER,
353 IID_IShellLinkW,
354 getter_AddRefs(mShellLink))) ||
355 FAILED(mShellLink->QueryInterface(IID_IPersistFile,
356 getter_AddRefs(mPersistFile)))) {
357 mShellLink = nullptr;
358 return NS_ERROR_FAILURE;
360 return NS_OK;
363 // |out| must be an allocated buffer of size MAX_PATH
364 nsresult
365 ShortcutResolver::Resolve(const WCHAR* aIn, WCHAR* aOut)
367 if (!mShellLink) {
368 return NS_ERROR_FAILURE;
371 MutexAutoLock lock(mLock);
373 if (FAILED(mPersistFile->Load(aIn, STGM_READ)) ||
374 FAILED(mShellLink->Resolve(nullptr, SLR_NO_UI)) ||
375 FAILED(mShellLink->GetPath(aOut, MAX_PATH, nullptr, SLGP_UNCPRIORITY))) {
376 return NS_ERROR_FAILURE;
378 return NS_OK;
381 nsresult
382 ShortcutResolver::SetShortcut(bool aUpdateExisting,
383 const WCHAR* aShortcutPath,
384 const WCHAR* aTargetPath,
385 const WCHAR* aWorkingDir,
386 const WCHAR* aArgs,
387 const WCHAR* aDescription,
388 const WCHAR* aIconPath,
389 int32_t aIconIndex)
391 if (!mShellLink) {
392 return NS_ERROR_FAILURE;
395 if (!aShortcutPath) {
396 return NS_ERROR_FAILURE;
399 MutexAutoLock lock(mLock);
401 if (aUpdateExisting) {
402 if (FAILED(mPersistFile->Load(aShortcutPath, STGM_READWRITE))) {
403 return NS_ERROR_FAILURE;
405 } else {
406 if (!aTargetPath) {
407 return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
410 // Since we reuse our IPersistFile, we have to clear out any values that
411 // may be left over from previous calls to SetShortcut.
412 if (FAILED(mShellLink->SetWorkingDirectory(L"")) ||
413 FAILED(mShellLink->SetArguments(L"")) ||
414 FAILED(mShellLink->SetDescription(L"")) ||
415 FAILED(mShellLink->SetIconLocation(L"", 0))) {
416 return NS_ERROR_FAILURE;
420 if (aTargetPath && FAILED(mShellLink->SetPath(aTargetPath))) {
421 return NS_ERROR_FAILURE;
424 if (aWorkingDir && FAILED(mShellLink->SetWorkingDirectory(aWorkingDir))) {
425 return NS_ERROR_FAILURE;
428 if (aArgs && FAILED(mShellLink->SetArguments(aArgs))) {
429 return NS_ERROR_FAILURE;
432 if (aDescription && FAILED(mShellLink->SetDescription(aDescription))) {
433 return NS_ERROR_FAILURE;
436 if (aIconPath && FAILED(mShellLink->SetIconLocation(aIconPath, aIconIndex))) {
437 return NS_ERROR_FAILURE;
440 if (FAILED(mPersistFile->Save(aShortcutPath,
441 TRUE))) {
442 // Second argument indicates whether the file path specified in the
443 // first argument should become the "current working file" for this
444 // IPersistFile
445 return NS_ERROR_FAILURE;
448 return NS_OK;
451 static ShortcutResolver* gResolver = nullptr;
453 static nsresult
454 NS_CreateShortcutResolver()
456 gResolver = new ShortcutResolver();
457 if (!gResolver) {
458 return NS_ERROR_OUT_OF_MEMORY;
461 return gResolver->Init();
464 static void
465 NS_DestroyShortcutResolver()
467 delete gResolver;
468 gResolver = nullptr;
472 //-----------------------------------------------------------------------------
473 // static helper functions
474 //-----------------------------------------------------------------------------
476 // certainly not all the error that can be
477 // encountered, but many of them common ones
478 static nsresult
479 ConvertWinError(DWORD aWinErr)
481 nsresult rv;
483 switch (aWinErr) {
484 case ERROR_FILE_NOT_FOUND:
485 case ERROR_PATH_NOT_FOUND:
486 case ERROR_INVALID_DRIVE:
487 rv = NS_ERROR_FILE_NOT_FOUND;
488 break;
489 case ERROR_ACCESS_DENIED:
490 case ERROR_NOT_SAME_DEVICE:
491 rv = NS_ERROR_FILE_ACCESS_DENIED;
492 break;
493 case ERROR_SHARING_VIOLATION: // CreateFile without sharing flags
494 case ERROR_LOCK_VIOLATION: // LockFile, LockFileEx
495 rv = NS_ERROR_FILE_IS_LOCKED;
496 break;
497 case ERROR_NOT_ENOUGH_MEMORY:
498 case ERROR_INVALID_BLOCK:
499 case ERROR_INVALID_HANDLE:
500 case ERROR_ARENA_TRASHED:
501 rv = NS_ERROR_OUT_OF_MEMORY;
502 break;
503 case ERROR_CURRENT_DIRECTORY:
504 rv = NS_ERROR_FILE_DIR_NOT_EMPTY;
505 break;
506 case ERROR_WRITE_PROTECT:
507 rv = NS_ERROR_FILE_READ_ONLY;
508 break;
509 case ERROR_HANDLE_DISK_FULL:
510 rv = NS_ERROR_FILE_TOO_BIG;
511 break;
512 case ERROR_FILE_EXISTS:
513 case ERROR_ALREADY_EXISTS:
514 case ERROR_CANNOT_MAKE:
515 rv = NS_ERROR_FILE_ALREADY_EXISTS;
516 break;
517 case ERROR_FILENAME_EXCED_RANGE:
518 rv = NS_ERROR_FILE_NAME_TOO_LONG;
519 break;
520 case ERROR_DIRECTORY:
521 rv = NS_ERROR_FILE_NOT_DIRECTORY;
522 break;
523 case 0:
524 rv = NS_OK;
525 break;
526 default:
527 rv = NS_ERROR_FAILURE;
528 break;
530 return rv;
533 // as suggested in the MSDN documentation on SetFilePointer
534 static __int64
535 MyFileSeek64(HANDLE aHandle, __int64 aDistance, DWORD aMoveMethod)
537 LARGE_INTEGER li;
539 li.QuadPart = aDistance;
540 li.LowPart = SetFilePointer(aHandle, li.LowPart, &li.HighPart, aMoveMethod);
541 if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) {
542 li.QuadPart = -1;
545 return li.QuadPart;
548 static bool
549 IsShortcutPath(const nsAString& aPath)
551 // Under Windows, the shortcuts are just files with a ".lnk" extension.
552 // Note also that we don't resolve links in the middle of paths.
553 // i.e. "c:\foo.lnk\bar.txt" is invalid.
554 NS_ABORT_IF_FALSE(!aPath.IsEmpty(), "don't pass an empty string");
555 int32_t len = aPath.Length();
556 return len >= 4 && (StringTail(aPath, 4).LowerCaseEqualsASCII(".lnk"));
559 //-----------------------------------------------------------------------------
560 // We need the following three definitions to make |OpenFile| convert a file
561 // handle to an NSPR file descriptor correctly when |O_APPEND| flag is
562 // specified. It is defined in a private header of NSPR (primpl.h) we can't
563 // include. As a temporary workaround until we decide how to extend
564 // |PR_ImportFile|, we define it here. Currently, |_PR_HAVE_PEEK_BUFFER|
565 // and |PR_STRICT_ADDR_LEN| are not defined for the 'w95'-dependent portion
566 // of NSPR so that fields of |PRFilePrivate| #ifdef'd by them are not copied.
567 // Similarly, |_MDFileDesc| is taken from nsprpub/pr/include/md/_win95.h.
568 // In an unlikely case we switch to 'NT'-dependent NSPR AND this temporary
569 // workaround last beyond the switch, |PRFilePrivate| and |_MDFileDesc|
570 // need to be changed to match the definitions for WinNT.
571 //-----------------------------------------------------------------------------
572 typedef enum
574 _PR_TRI_TRUE = 1,
575 _PR_TRI_FALSE = 0,
576 _PR_TRI_UNKNOWN = -1
577 } _PRTriStateBool;
579 struct _MDFileDesc
581 PROsfd osfd;
584 struct PRFilePrivate
586 int32_t state;
587 bool nonblocking;
588 _PRTriStateBool inheritable;
589 PRFileDesc* next;
590 int lockCount; /* 0: not locked
591 * -1: a native lockfile call is in progress
592 * > 0: # times the file is locked */
593 bool appendMode;
594 _MDFileDesc md;
597 //-----------------------------------------------------------------------------
598 // Six static methods defined below (OpenFile, FileTimeToPRTime, GetFileInfo,
599 // OpenDir, CloseDir, ReadDir) should go away once the corresponding
600 // UTF-16 APIs are implemented on all the supported platforms (or at least
601 // Windows 9x/ME) in NSPR. Currently, they're only implemented on
602 // Windows NT4 or later. (bug 330665)
603 //-----------------------------------------------------------------------------
605 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} :
606 // PR_Open and _PR_MD_OPEN
607 nsresult
608 OpenFile(const nsAFlatString& aName, int aOsflags, int aMode, PRFileDesc** aFd)
610 int32_t access = 0;
612 int32_t disposition = 0;
613 int32_t attributes = 0;
615 if (aOsflags & PR_SYNC) {
616 attributes = FILE_FLAG_WRITE_THROUGH;
618 if (aOsflags & PR_RDONLY || aOsflags & PR_RDWR) {
619 access |= GENERIC_READ;
621 if (aOsflags & PR_WRONLY || aOsflags & PR_RDWR) {
622 access |= GENERIC_WRITE;
625 if (aOsflags & PR_CREATE_FILE && aOsflags & PR_EXCL) {
626 disposition = CREATE_NEW;
627 } else if (aOsflags & PR_CREATE_FILE) {
628 if (aOsflags & PR_TRUNCATE) {
629 disposition = CREATE_ALWAYS;
630 } else {
631 disposition = OPEN_ALWAYS;
633 } else {
634 if (aOsflags & PR_TRUNCATE) {
635 disposition = TRUNCATE_EXISTING;
636 } else {
637 disposition = OPEN_EXISTING;
641 if (aOsflags & nsIFile::DELETE_ON_CLOSE) {
642 attributes |= FILE_FLAG_DELETE_ON_CLOSE;
645 if (aOsflags & nsIFile::OS_READAHEAD) {
646 attributes |= FILE_FLAG_SEQUENTIAL_SCAN;
649 // If no write permissions are requested, and if we are possibly creating
650 // the file, then set the new file as read only.
651 // The flag has no effect if we happen to open the file.
652 if (!(aMode & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) &&
653 disposition != OPEN_EXISTING) {
654 attributes |= FILE_ATTRIBUTE_READONLY;
657 HANDLE file = ::CreateFileW(aName.get(), access,
658 FILE_SHARE_READ | FILE_SHARE_WRITE,
659 nullptr, disposition, attributes, nullptr);
661 if (file == INVALID_HANDLE_VALUE) {
662 *aFd = nullptr;
663 return ConvertWinError(GetLastError());
666 *aFd = PR_ImportFile((PROsfd) file);
667 if (*aFd) {
668 // On Windows, _PR_HAVE_O_APPEND is not defined so that we have to
669 // add it manually. (see |PR_Open| in nsprpub/pr/src/io/prfile.c)
670 (*aFd)->secret->appendMode = (PR_APPEND & aOsflags) ? true : false;
671 return NS_OK;
674 nsresult rv = NS_ErrorAccordingToNSPR();
676 CloseHandle(file);
678 return rv;
681 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} :
682 // PR_FileTimeToPRTime and _PR_FileTimeToPRTime
683 static void
684 FileTimeToPRTime(const FILETIME* aFiletime, PRTime* aPrtm)
686 #ifdef __GNUC__
687 const PRTime _pr_filetime_offset = 116444736000000000LL;
688 #else
689 const PRTime _pr_filetime_offset = 116444736000000000i64;
690 #endif
692 PR_ASSERT(sizeof(FILETIME) == sizeof(PRTime));
693 ::CopyMemory(aPrtm, aFiletime, sizeof(PRTime));
694 #ifdef __GNUC__
695 *aPrtm = (*aPrtm - _pr_filetime_offset) / 10LL;
696 #else
697 *aPrtm = (*aPrtm - _pr_filetime_offset) / 10i64;
698 #endif
701 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} with some
702 // changes : PR_GetFileInfo64, _PR_MD_GETFILEINFO64
703 static nsresult
704 GetFileInfo(const nsAFlatString& aName, PRFileInfo64* aInfo)
706 WIN32_FILE_ATTRIBUTE_DATA fileData;
708 if (aName.IsEmpty() || aName.FindCharInSet(MOZ_UTF16("?*")) != kNotFound) {
709 return NS_ERROR_INVALID_ARG;
712 if (!::GetFileAttributesExW(aName.get(), GetFileExInfoStandard, &fileData)) {
713 return ConvertWinError(GetLastError());
716 if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
717 aInfo->type = PR_FILE_DIRECTORY;
718 } else {
719 aInfo->type = PR_FILE_FILE;
722 aInfo->size = fileData.nFileSizeHigh;
723 aInfo->size = (aInfo->size << 32) + fileData.nFileSizeLow;
725 FileTimeToPRTime(&fileData.ftLastWriteTime, &aInfo->modifyTime);
727 if (0 == fileData.ftCreationTime.dwLowDateTime &&
728 0 == fileData.ftCreationTime.dwHighDateTime) {
729 aInfo->creationTime = aInfo->modifyTime;
730 } else {
731 FileTimeToPRTime(&fileData.ftCreationTime, &aInfo->creationTime);
734 return NS_OK;
737 struct nsDir
739 HANDLE handle;
740 WIN32_FIND_DATAW data;
741 bool firstEntry;
744 static nsresult
745 OpenDir(const nsAFlatString& aName, nsDir** aDir)
747 if (NS_WARN_IF(!aDir)) {
748 return NS_ERROR_INVALID_ARG;
751 *aDir = nullptr;
752 if (aName.Length() + 3 >= MAX_PATH) {
753 return NS_ERROR_FILE_NAME_TOO_LONG;
756 nsDir* d = PR_NEW(nsDir);
757 if (!d) {
758 return NS_ERROR_OUT_OF_MEMORY;
761 nsAutoString filename(aName);
763 // If |aName| ends in a slash or backslash, do not append another backslash.
764 if (filename.Last() == L'/' || filename.Last() == L'\\') {
765 filename.Append('*');
766 } else {
767 filename.AppendLiteral("\\*");
770 filename.ReplaceChar(L'/', L'\\');
772 // FindFirstFileW Will have a last error of ERROR_DIRECTORY if
773 // <file_path>\* is passed in. If <unknown_path>\* is passed in then
774 // ERROR_PATH_NOT_FOUND will be the last error.
775 d->handle = ::FindFirstFileW(filename.get(), &(d->data));
777 if (d->handle == INVALID_HANDLE_VALUE) {
778 PR_Free(d);
779 return ConvertWinError(GetLastError());
781 d->firstEntry = true;
783 *aDir = d;
784 return NS_OK;
787 static nsresult
788 ReadDir(nsDir* aDir, PRDirFlags aFlags, nsString& aName)
790 aName.Truncate();
791 if (NS_WARN_IF(!aDir)) {
792 return NS_ERROR_INVALID_ARG;
795 while (1) {
796 BOOL rv;
797 if (aDir->firstEntry) {
798 aDir->firstEntry = false;
799 rv = 1;
800 } else {
801 rv = ::FindNextFileW(aDir->handle, &(aDir->data));
804 if (rv == 0) {
805 break;
808 const wchar_t* fileName;
809 nsString tmp;
810 fileName = (aDir)->data.cFileName;
812 if ((aFlags & PR_SKIP_DOT) &&
813 (fileName[0] == L'.') && (fileName[1] == L'\0')) {
814 continue;
816 if ((aFlags & PR_SKIP_DOT_DOT) &&
817 (fileName[0] == L'.') && (fileName[1] == L'.') &&
818 (fileName[2] == L'\0')) {
819 continue;
822 DWORD attrib = aDir->data.dwFileAttributes;
823 if ((aFlags & PR_SKIP_HIDDEN) && (attrib & FILE_ATTRIBUTE_HIDDEN)) {
824 continue;
827 if (fileName == tmp.get()) {
828 aName = tmp;
829 } else {
830 aName = fileName;
832 return NS_OK;
835 DWORD err = GetLastError();
836 return err == ERROR_NO_MORE_FILES ? NS_OK : ConvertWinError(err);
839 static nsresult
840 CloseDir(nsDir*& aDir)
842 if (NS_WARN_IF(!aDir)) {
843 return NS_ERROR_INVALID_ARG;
846 BOOL isOk = FindClose(aDir->handle);
847 // PR_DELETE also nulls out the passed in pointer.
848 PR_DELETE(aDir);
849 return isOk ? NS_OK : ConvertWinError(GetLastError());
852 //-----------------------------------------------------------------------------
853 // nsDirEnumerator
854 //-----------------------------------------------------------------------------
856 class nsDirEnumerator MOZ_FINAL
857 : public nsISimpleEnumerator
858 , public nsIDirectoryEnumerator
860 private:
861 ~nsDirEnumerator()
863 Close();
866 public:
867 NS_DECL_ISUPPORTS
869 nsDirEnumerator() : mDir(nullptr)
873 nsresult Init(nsIFile* aParent)
875 nsAutoString filepath;
876 aParent->GetTarget(filepath);
878 if (filepath.IsEmpty()) {
879 aParent->GetPath(filepath);
882 if (filepath.IsEmpty()) {
883 return NS_ERROR_UNEXPECTED;
886 // IsDirectory is not needed here because OpenDir will return
887 // NS_ERROR_FILE_NOT_DIRECTORY if the passed in path is a file.
888 nsresult rv = OpenDir(filepath, &mDir);
889 if (NS_FAILED(rv)) {
890 return rv;
893 mParent = aParent;
894 return NS_OK;
897 NS_IMETHOD HasMoreElements(bool* aResult)
899 nsresult rv;
900 if (!mNext && mDir) {
901 nsString name;
902 rv = ReadDir(mDir, PR_SKIP_BOTH, name);
903 if (NS_FAILED(rv)) {
904 return rv;
906 if (name.IsEmpty()) {
907 // end of dir entries
908 if (NS_FAILED(CloseDir(mDir))) {
909 return NS_ERROR_FAILURE;
912 *aResult = false;
913 return NS_OK;
916 nsCOMPtr<nsIFile> file;
917 rv = mParent->Clone(getter_AddRefs(file));
918 if (NS_FAILED(rv)) {
919 return rv;
922 rv = file->Append(name);
923 if (NS_FAILED(rv)) {
924 return rv;
927 mNext = do_QueryInterface(file);
929 *aResult = mNext != nullptr;
930 if (!*aResult) {
931 Close();
933 return NS_OK;
936 NS_IMETHOD GetNext(nsISupports** aResult)
938 nsresult rv;
939 bool hasMore;
940 rv = HasMoreElements(&hasMore);
941 if (NS_FAILED(rv)) {
942 return rv;
945 *aResult = mNext; // might return nullptr
946 NS_IF_ADDREF(*aResult);
948 mNext = nullptr;
949 return NS_OK;
952 NS_IMETHOD GetNextFile(nsIFile** aResult)
954 *aResult = nullptr;
955 bool hasMore = false;
956 nsresult rv = HasMoreElements(&hasMore);
957 if (NS_FAILED(rv) || !hasMore) {
958 return rv;
960 *aResult = mNext;
961 NS_IF_ADDREF(*aResult);
962 mNext = nullptr;
963 return NS_OK;
966 NS_IMETHOD Close()
968 if (mDir) {
969 nsresult rv = CloseDir(mDir);
970 NS_ASSERTION(NS_SUCCEEDED(rv), "close failed");
971 if (NS_FAILED(rv)) {
972 return NS_ERROR_FAILURE;
975 return NS_OK;
978 protected:
979 nsDir* mDir;
980 nsCOMPtr<nsIFile> mParent;
981 nsCOMPtr<nsIFile> mNext;
984 NS_IMPL_ISUPPORTS(nsDirEnumerator, nsISimpleEnumerator, nsIDirectoryEnumerator)
987 //-----------------------------------------------------------------------------
988 // nsLocalFile <public>
989 //-----------------------------------------------------------------------------
991 nsLocalFile::nsLocalFile()
992 : mDirty(true)
993 , mResolveDirty(true)
994 , mFollowSymlinks(false)
998 nsresult
999 nsLocalFile::nsLocalFileConstructor(nsISupports* aOuter, const nsIID& aIID,
1000 void** aInstancePtr)
1002 if (NS_WARN_IF(!aInstancePtr)) {
1003 return NS_ERROR_INVALID_ARG;
1005 if (NS_WARN_IF(aOuter)) {
1006 return NS_ERROR_NO_AGGREGATION;
1009 nsLocalFile* inst = new nsLocalFile();
1010 if (!inst) {
1011 return NS_ERROR_OUT_OF_MEMORY;
1014 nsresult rv = inst->QueryInterface(aIID, aInstancePtr);
1015 if (NS_FAILED(rv)) {
1016 delete inst;
1017 return rv;
1019 return NS_OK;
1023 //-----------------------------------------------------------------------------
1024 // nsLocalFile::nsISupports
1025 //-----------------------------------------------------------------------------
1027 NS_IMPL_ISUPPORTS(nsLocalFile,
1028 nsILocalFile,
1029 nsIFile,
1030 nsILocalFileWin,
1031 nsIHashable)
1034 //-----------------------------------------------------------------------------
1035 // nsLocalFile <private>
1036 //-----------------------------------------------------------------------------
1038 nsLocalFile::nsLocalFile(const nsLocalFile& aOther)
1039 : mDirty(true)
1040 , mResolveDirty(true)
1041 , mFollowSymlinks(aOther.mFollowSymlinks)
1042 , mWorkingPath(aOther.mWorkingPath)
1046 // Resolve the shortcut file from mWorkingPath and write the path
1047 // it points to into mResolvedPath.
1048 nsresult
1049 nsLocalFile::ResolveShortcut()
1051 // we can't do anything without the resolver
1052 if (!gResolver) {
1053 return NS_ERROR_FAILURE;
1056 mResolvedPath.SetLength(MAX_PATH);
1057 if (mResolvedPath.Length() != MAX_PATH) {
1058 return NS_ERROR_OUT_OF_MEMORY;
1061 wchar_t* resolvedPath = wwc(mResolvedPath.BeginWriting());
1063 // resolve this shortcut
1064 nsresult rv = gResolver->Resolve(mWorkingPath.get(), resolvedPath);
1066 size_t len = NS_FAILED(rv) ? 0 : wcslen(resolvedPath);
1067 mResolvedPath.SetLength(len);
1069 return rv;
1072 // Resolve any shortcuts and stat the resolved path. After a successful return
1073 // the path is guaranteed valid and the members of mFileInfo64 can be used.
1074 nsresult
1075 nsLocalFile::ResolveAndStat()
1077 // if we aren't dirty then we are already done
1078 if (!mDirty) {
1079 return NS_OK;
1082 // we can't resolve/stat anything that isn't a valid NSPR addressable path
1083 if (mWorkingPath.IsEmpty()) {
1084 return NS_ERROR_FILE_INVALID_PATH;
1087 // this is usually correct
1088 mResolvedPath.Assign(mWorkingPath);
1090 // slutty hack designed to work around bug 134796 until it is fixed
1091 nsAutoString nsprPath(mWorkingPath.get());
1092 if (mWorkingPath.Length() == 2 && mWorkingPath.CharAt(1) == L':') {
1093 nsprPath.Append('\\');
1096 // first we will see if the working path exists. If it doesn't then
1097 // there is nothing more that can be done
1098 nsresult rv = GetFileInfo(nsprPath, &mFileInfo64);
1099 if (NS_FAILED(rv)) {
1100 return rv;
1103 // if this isn't a shortcut file or we aren't following symlinks then we're done
1104 if (!mFollowSymlinks ||
1105 mFileInfo64.type != PR_FILE_FILE ||
1106 !IsShortcutPath(mWorkingPath)) {
1107 mDirty = false;
1108 mResolveDirty = false;
1109 return NS_OK;
1112 // we need to resolve this shortcut to what it points to, this will
1113 // set mResolvedPath. Even if it fails we need to have the resolved
1114 // path equal to working path for those functions that always use
1115 // the resolved path.
1116 rv = ResolveShortcut();
1117 if (NS_FAILED(rv)) {
1118 mResolvedPath.Assign(mWorkingPath);
1119 return rv;
1121 mResolveDirty = false;
1123 // get the details of the resolved path
1124 rv = GetFileInfo(mResolvedPath, &mFileInfo64);
1125 if (NS_FAILED(rv)) {
1126 return rv;
1129 mDirty = false;
1130 return NS_OK;
1134 * Fills the mResolvedPath member variable with the file or symlink target
1135 * if follow symlinks is on. This is a copy of the Resolve parts from
1136 * ResolveAndStat. ResolveAndStat is much slower though because of the stat.
1138 * @return NS_OK on success.
1140 nsresult
1141 nsLocalFile::Resolve()
1143 // if we aren't dirty then we are already done
1144 if (!mResolveDirty) {
1145 return NS_OK;
1148 // we can't resolve/stat anything that isn't a valid NSPR addressable path
1149 if (mWorkingPath.IsEmpty()) {
1150 return NS_ERROR_FILE_INVALID_PATH;
1153 // this is usually correct
1154 mResolvedPath.Assign(mWorkingPath);
1156 // if this isn't a shortcut file or we aren't following symlinks then
1157 // we're done.
1158 if (!mFollowSymlinks ||
1159 !IsShortcutPath(mWorkingPath)) {
1160 mResolveDirty = false;
1161 return NS_OK;
1164 // we need to resolve this shortcut to what it points to, this will
1165 // set mResolvedPath. Even if it fails we need to have the resolved
1166 // path equal to working path for those functions that always use
1167 // the resolved path.
1168 nsresult rv = ResolveShortcut();
1169 if (NS_FAILED(rv)) {
1170 mResolvedPath.Assign(mWorkingPath);
1171 return rv;
1174 mResolveDirty = false;
1175 return NS_OK;
1178 //-----------------------------------------------------------------------------
1179 // nsLocalFile::nsIFile,nsILocalFile
1180 //-----------------------------------------------------------------------------
1182 NS_IMETHODIMP
1183 nsLocalFile::Clone(nsIFile** aFile)
1185 // Just copy-construct ourselves
1186 *aFile = new nsLocalFile(*this);
1187 if (!*aFile) {
1188 return NS_ERROR_OUT_OF_MEMORY;
1191 NS_ADDREF(*aFile);
1193 return NS_OK;
1196 NS_IMETHODIMP
1197 nsLocalFile::InitWithFile(nsIFile* aFile)
1199 if (NS_WARN_IF(!aFile)) {
1200 return NS_ERROR_INVALID_ARG;
1203 nsAutoString path;
1204 aFile->GetPath(path);
1205 if (path.IsEmpty()) {
1206 return NS_ERROR_INVALID_ARG;
1208 return InitWithPath(path);
1211 NS_IMETHODIMP
1212 nsLocalFile::InitWithPath(const nsAString& aFilePath)
1214 MakeDirty();
1216 nsAString::const_iterator begin, end;
1217 aFilePath.BeginReading(begin);
1218 aFilePath.EndReading(end);
1220 // input string must not be empty
1221 if (begin == end) {
1222 return NS_ERROR_FAILURE;
1225 char16_t firstChar = *begin;
1226 char16_t secondChar = *(++begin);
1228 // just do a sanity check. if it has any forward slashes, it is not a Native path
1229 // on windows. Also, it must have a colon at after the first char.
1230 if (FindCharInReadable(L'/', begin, end)) {
1231 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1234 if (secondChar != L':' && (secondChar != L'\\' || firstChar != L'\\')) {
1235 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1238 if (secondChar == L':') {
1239 // Make sure we have a valid drive, later code assumes the drive letter
1240 // is a single char a-z or A-Z.
1241 if (PathGetDriveNumberW(aFilePath.Data()) == -1) {
1242 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1246 mWorkingPath = aFilePath;
1247 // kill any trailing '\'
1248 if (mWorkingPath.Last() == L'\\') {
1249 mWorkingPath.Truncate(mWorkingPath.Length() - 1);
1252 return NS_OK;
1256 NS_IMETHODIMP
1257 nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode,
1258 PRFileDesc** aResult)
1260 nsresult rv = Resolve();
1261 if (NS_FAILED(rv)) {
1262 return rv;
1265 return OpenFile(mResolvedPath, aFlags, aMode, aResult);
1269 NS_IMETHODIMP
1270 nsLocalFile::OpenANSIFileDesc(const char* aMode, FILE** aResult)
1272 nsresult rv = ResolveAndStat();
1273 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
1274 return rv;
1277 *aResult = _wfopen(mResolvedPath.get(), NS_ConvertASCIItoUTF16(aMode).get());
1278 if (*aResult) {
1279 return NS_OK;
1282 return NS_ERROR_FAILURE;
1287 NS_IMETHODIMP
1288 nsLocalFile::Create(uint32_t aType, uint32_t aAttributes)
1290 if (aType != NORMAL_FILE_TYPE && aType != DIRECTORY_TYPE) {
1291 return NS_ERROR_FILE_UNKNOWN_TYPE;
1294 nsresult rv = ResolveAndStat();
1295 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
1296 return rv;
1299 // create directories to target
1301 // A given local file can be either one of these forms:
1303 // - normal: X:\some\path\on\this\drive
1304 // ^--- start here
1306 // - UNC path: \\machine\volume\some\path\on\this\drive
1307 // ^--- start here
1309 // Skip the first 'X:\' for the first form, and skip the first full
1310 // '\\machine\volume\' segment for the second form.
1312 wchar_t* path = wwc(mResolvedPath.BeginWriting());
1314 if (path[0] == L'\\' && path[1] == L'\\') {
1315 // dealing with a UNC path here; skip past '\\machine\'
1316 path = wcschr(path + 2, L'\\');
1317 if (!path) {
1318 return NS_ERROR_FILE_INVALID_PATH;
1320 ++path;
1323 // search for first slash after the drive (or volume) name
1324 wchar_t* slash = wcschr(path, L'\\');
1326 nsresult directoryCreateError = NS_OK;
1327 if (slash) {
1328 // skip the first '\\'
1329 ++slash;
1330 slash = wcschr(slash, L'\\');
1332 while (slash) {
1333 *slash = L'\0';
1335 if (!::CreateDirectoryW(mResolvedPath.get(), nullptr)) {
1336 rv = ConvertWinError(GetLastError());
1337 if (NS_ERROR_FILE_NOT_FOUND == rv &&
1338 NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) {
1339 // If a previous CreateDirectory failed due to access, return that.
1340 return NS_ERROR_FILE_ACCESS_DENIED;
1342 // perhaps the base path already exists, or perhaps we don't have
1343 // permissions to create the directory. NOTE: access denied could
1344 // occur on a parent directory even though it exists.
1345 else if (rv != NS_ERROR_FILE_ALREADY_EXISTS &&
1346 rv != NS_ERROR_FILE_ACCESS_DENIED) {
1347 return rv;
1350 directoryCreateError = rv;
1352 *slash = L'\\';
1353 ++slash;
1354 slash = wcschr(slash, L'\\');
1358 if (aType == NORMAL_FILE_TYPE) {
1359 PRFileDesc* file;
1360 rv = OpenFile(mResolvedPath,
1361 PR_RDONLY | PR_CREATE_FILE | PR_APPEND | PR_EXCL,
1362 aAttributes, &file);
1363 if (file) {
1364 PR_Close(file);
1367 if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
1368 // need to return already-exists for directories (bug 452217)
1369 bool isdir;
1370 if (NS_SUCCEEDED(IsDirectory(&isdir)) && isdir) {
1371 rv = NS_ERROR_FILE_ALREADY_EXISTS;
1373 } else if (NS_ERROR_FILE_NOT_FOUND == rv &&
1374 NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) {
1375 // If a previous CreateDirectory failed due to access, return that.
1376 return NS_ERROR_FILE_ACCESS_DENIED;
1378 return rv;
1381 if (aType == DIRECTORY_TYPE) {
1382 if (!::CreateDirectoryW(mResolvedPath.get(), nullptr)) {
1383 rv = ConvertWinError(GetLastError());
1384 if (NS_ERROR_FILE_NOT_FOUND == rv &&
1385 NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) {
1386 // If a previous CreateDirectory failed due to access, return that.
1387 return NS_ERROR_FILE_ACCESS_DENIED;
1389 return rv;
1391 return NS_OK;
1394 return NS_ERROR_FILE_UNKNOWN_TYPE;
1398 NS_IMETHODIMP
1399 nsLocalFile::Append(const nsAString& aNode)
1401 // append this path, multiple components are not permitted
1402 return AppendInternal(PromiseFlatString(aNode), false);
1405 NS_IMETHODIMP
1406 nsLocalFile::AppendRelativePath(const nsAString& aNode)
1408 // append this path, multiple components are permitted
1409 return AppendInternal(PromiseFlatString(aNode), true);
1413 nsresult
1414 nsLocalFile::AppendInternal(const nsAFlatString& aNode,
1415 bool aMultipleComponents)
1417 if (aNode.IsEmpty()) {
1418 return NS_OK;
1421 // check the relative path for validity
1422 if (aNode.First() == L'\\' || // can't start with an '\'
1423 aNode.FindChar(L'/') != kNotFound || // can't contain /
1424 aNode.EqualsASCII("..")) { // can't be ..
1425 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1428 if (aMultipleComponents) {
1429 // can't contain .. as a path component. Ensure that the valid components
1430 // "foo..foo", "..foo", and "foo.." are not falsely detected,
1431 // but the invalid paths "..\", "foo\..", "foo\..\foo",
1432 // "..\foo", etc are.
1433 NS_NAMED_LITERAL_STRING(doubleDot, "\\..");
1434 nsAString::const_iterator start, end, offset;
1435 aNode.BeginReading(start);
1436 aNode.EndReading(end);
1437 offset = end;
1438 while (FindInReadable(doubleDot, start, offset)) {
1439 if (offset == end || *offset == L'\\') {
1440 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1442 start = offset;
1443 offset = end;
1446 // catches the remaining cases of prefixes
1447 if (StringBeginsWith(aNode, NS_LITERAL_STRING("..\\"))) {
1448 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1451 // single components can't contain '\'
1452 else if (aNode.FindChar(L'\\') != kNotFound) {
1453 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1456 MakeDirty();
1458 mWorkingPath.Append('\\');
1459 mWorkingPath.Append(aNode);
1461 return NS_OK;
1464 #define TOUPPER(u) (((u) >= L'a' && (u) <= L'z') ? \
1465 (u) - (L'a' - L'A') : (u))
1467 NS_IMETHODIMP
1468 nsLocalFile::Normalize()
1470 // XXX See bug 187957 comment 18 for possible problems with this implementation.
1472 if (mWorkingPath.IsEmpty()) {
1473 return NS_OK;
1476 nsAutoString path(mWorkingPath);
1478 // find the index of the root backslash for the path. Everything before
1479 // this is considered fully normalized and cannot be ascended beyond
1480 // using ".." For a local drive this is the first slash (e.g. "c:\").
1481 // For a UNC path it is the slash following the share name
1482 // (e.g. "\\server\share\").
1483 int32_t rootIdx = 2; // default to local drive
1484 if (path.First() == L'\\') { // if a share then calculate the rootIdx
1485 rootIdx = path.FindChar(L'\\', 2); // skip \\ in front of the server
1486 if (rootIdx == kNotFound) {
1487 return NS_OK; // already normalized
1489 rootIdx = path.FindChar(L'\\', rootIdx + 1);
1490 if (rootIdx == kNotFound) {
1491 return NS_OK; // already normalized
1493 } else if (path.CharAt(rootIdx) != L'\\') {
1494 // The path has been specified relative to the current working directory
1495 // for that drive. To normalize it, the current working directory for
1496 // that drive needs to be inserted before the supplied relative path
1497 // which will provide an absolute path (and the rootIdx will still be 2).
1498 WCHAR cwd[MAX_PATH];
1499 WCHAR* pcwd = cwd;
1500 int drive = TOUPPER(path.First()) - 'A' + 1;
1501 /* We need to worry about IPH, for details read bug 419326.
1502 * _getdrives - http://msdn2.microsoft.com/en-us/library/xdhk0xd2.aspx
1503 * uses a bitmask, bit 0 is 'a:'
1504 * _chdrive - http://msdn2.microsoft.com/en-us/library/0d1409hb.aspx
1505 * _getdcwd - http://msdn2.microsoft.com/en-us/library/7t2zk3s4.aspx
1506 * take an int, 1 is 'a:'.
1508 * Because of this, we need to do some math. Subtract 1 to convert from
1509 * _chdrive/_getdcwd format to _getdrives drive numbering.
1510 * Shift left x bits to convert from integer indexing to bitfield indexing.
1511 * And of course, we need to find out if the drive is in the bitmask.
1513 * If we're really unlucky, we can still lose, but only if the user
1514 * manages to eject the drive between our call to _getdrives() and
1515 * our *calls* to _wgetdcwd.
1517 if (!((1 << (drive - 1)) & _getdrives())) {
1518 return NS_ERROR_FILE_INVALID_PATH;
1520 if (!_wgetdcwd(drive, pcwd, MAX_PATH)) {
1521 pcwd = _wgetdcwd(drive, 0, 0);
1523 if (!pcwd) {
1524 return NS_ERROR_OUT_OF_MEMORY;
1526 nsAutoString currentDir(pcwd);
1527 if (pcwd != cwd) {
1528 free(pcwd);
1531 if (currentDir.Last() == '\\') {
1532 path.Replace(0, 2, currentDir);
1533 } else {
1534 path.Replace(0, 2, currentDir + NS_LITERAL_STRING("\\"));
1537 NS_POSTCONDITION(0 < rootIdx && rootIdx < (int32_t)path.Length(), "rootIdx is invalid");
1538 NS_POSTCONDITION(path.CharAt(rootIdx) == '\\', "rootIdx is invalid");
1540 // if there is nothing following the root path then it is already normalized
1541 if (rootIdx + 1 == (int32_t)path.Length()) {
1542 return NS_OK;
1545 // assign the root
1546 const char16_t* pathBuffer = path.get(); // simplify access to the buffer
1547 mWorkingPath.SetCapacity(path.Length()); // it won't ever grow longer
1548 mWorkingPath.Assign(pathBuffer, rootIdx);
1550 // Normalize the path components. The actions taken are:
1552 // "\\" condense to single backslash
1553 // "." remove from path
1554 // ".." up a directory
1555 // "..." remove from path (any number of dots > 2)
1557 // The last form is something that Windows 95 and 98 supported and
1558 // is a shortcut for changing up multiple directories. Windows XP
1559 // and ilk ignore it in a path, as is done here.
1560 int32_t len, begin, end = rootIdx;
1561 while (end < (int32_t)path.Length()) {
1562 // find the current segment (text between the backslashes) to
1563 // be examined, this will set the following variables:
1564 // begin == index of first char in segment
1565 // end == index 1 char after last char in segment
1566 // len == length of segment
1567 begin = end + 1;
1568 end = path.FindChar('\\', begin);
1569 if (end == kNotFound) {
1570 end = path.Length();
1572 len = end - begin;
1574 // ignore double backslashes
1575 if (len == 0) {
1576 continue;
1579 // len != 0, and interesting paths always begin with a dot
1580 if (pathBuffer[begin] == '.') {
1581 // ignore single dots
1582 if (len == 1) {
1583 continue;
1586 // handle multiple dots
1587 if (len >= 2 && pathBuffer[begin + 1] == L'.') {
1588 // back up a path component on double dot
1589 if (len == 2) {
1590 int32_t prev = mWorkingPath.RFindChar('\\');
1591 if (prev >= rootIdx) {
1592 mWorkingPath.Truncate(prev);
1594 continue;
1597 // length is > 2 and the first two characters are dots.
1598 // if the rest of the string is dots, then ignore it.
1599 int idx = len - 1;
1600 for (; idx >= 2; --idx) {
1601 if (pathBuffer[begin + idx] != L'.') {
1602 break;
1606 // this is true if the loop above didn't break
1607 // and all characters in this segment are dots.
1608 if (idx < 2) {
1609 continue;
1614 // add the current component to the path, including the preceding backslash
1615 mWorkingPath.Append(pathBuffer + begin - 1, len + 1);
1618 // kill trailing dots and spaces.
1619 int32_t filePathLen = mWorkingPath.Length() - 1;
1620 while (filePathLen > 0 && (mWorkingPath[filePathLen] == L' ' ||
1621 mWorkingPath[filePathLen] == L'.')) {
1622 mWorkingPath.Truncate(filePathLen--);
1625 MakeDirty();
1626 return NS_OK;
1629 NS_IMETHODIMP
1630 nsLocalFile::GetLeafName(nsAString& aLeafName)
1632 aLeafName.Truncate();
1634 if (mWorkingPath.IsEmpty()) {
1635 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1638 int32_t offset = mWorkingPath.RFindChar(L'\\');
1640 // if the working path is just a node without any lashes.
1641 if (offset == kNotFound) {
1642 aLeafName = mWorkingPath;
1643 } else {
1644 aLeafName = Substring(mWorkingPath, offset + 1);
1647 return NS_OK;
1650 NS_IMETHODIMP
1651 nsLocalFile::SetLeafName(const nsAString& aLeafName)
1653 MakeDirty();
1655 if (mWorkingPath.IsEmpty()) {
1656 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1659 // cannot use nsCString::RFindChar() due to 0x5c problem
1660 int32_t offset = mWorkingPath.RFindChar(L'\\');
1661 if (offset) {
1662 mWorkingPath.Truncate(offset + 1);
1664 mWorkingPath.Append(aLeafName);
1666 return NS_OK;
1670 NS_IMETHODIMP
1671 nsLocalFile::GetPath(nsAString& aResult)
1673 aResult = mWorkingPath;
1674 return NS_OK;
1677 NS_IMETHODIMP
1678 nsLocalFile::GetCanonicalPath(nsAString& aResult)
1680 EnsureShortPath();
1681 aResult.Assign(mShortWorkingPath);
1682 return NS_OK;
1685 typedef struct
1687 WORD wLanguage;
1688 WORD wCodePage;
1689 } LANGANDCODEPAGE;
1691 NS_IMETHODIMP
1692 nsLocalFile::GetVersionInfoField(const char* aField, nsAString& aResult)
1694 nsresult rv = ResolveAndStat();
1695 if (NS_FAILED(rv)) {
1696 return rv;
1699 rv = NS_ERROR_FAILURE;
1701 const WCHAR* path =
1702 mFollowSymlinks ? mResolvedPath.get() : mWorkingPath.get();
1704 DWORD dummy;
1705 DWORD size = ::GetFileVersionInfoSizeW(path, &dummy);
1706 if (!size) {
1707 return rv;
1710 void* ver = calloc(size, 1);
1711 if (!ver) {
1712 return NS_ERROR_OUT_OF_MEMORY;
1715 if (::GetFileVersionInfoW(path, 0, size, ver)) {
1716 LANGANDCODEPAGE* translate = nullptr;
1717 UINT pageCount;
1718 BOOL queryResult = ::VerQueryValueW(ver, L"\\VarFileInfo\\Translation",
1719 (void**)&translate, &pageCount);
1720 if (queryResult && translate) {
1721 for (int32_t i = 0; i < 2; ++i) {
1722 wchar_t subBlock[MAX_PATH];
1723 _snwprintf(subBlock, MAX_PATH,
1724 L"\\StringFileInfo\\%04x%04x\\%s",
1725 (i == 0 ? translate[0].wLanguage : ::GetUserDefaultLangID()),
1726 translate[0].wCodePage,
1727 NS_ConvertASCIItoUTF16(
1728 nsDependentCString(aField)).get());
1729 subBlock[MAX_PATH - 1] = 0;
1730 LPVOID value = nullptr;
1731 UINT size;
1732 queryResult = ::VerQueryValueW(ver, subBlock, &value, &size);
1733 if (queryResult && value) {
1734 aResult.Assign(static_cast<char16_t*>(value));
1735 if (!aResult.IsEmpty()) {
1736 rv = NS_OK;
1737 break;
1743 free(ver);
1745 return rv;
1748 NS_IMETHODIMP
1749 nsLocalFile::SetShortcut(nsIFile* aTargetFile,
1750 nsIFile* aWorkingDir,
1751 const char16_t* aArgs,
1752 const char16_t* aDescription,
1753 nsIFile* aIconFile,
1754 int32_t aIconIndex)
1756 bool exists;
1757 nsresult rv = this->Exists(&exists);
1758 if (NS_FAILED(rv)) {
1759 return rv;
1762 const WCHAR* targetFilePath = nullptr;
1763 const WCHAR* workingDirPath = nullptr;
1764 const WCHAR* iconFilePath = nullptr;
1766 nsAutoString targetFilePathAuto;
1767 if (aTargetFile) {
1768 rv = aTargetFile->GetPath(targetFilePathAuto);
1769 if (NS_FAILED(rv)) {
1770 return rv;
1772 targetFilePath = targetFilePathAuto.get();
1775 nsAutoString workingDirPathAuto;
1776 if (aWorkingDir) {
1777 rv = aWorkingDir->GetPath(workingDirPathAuto);
1778 if (NS_FAILED(rv)) {
1779 return rv;
1781 workingDirPath = workingDirPathAuto.get();
1784 nsAutoString iconPathAuto;
1785 if (aIconFile) {
1786 rv = aIconFile->GetPath(iconPathAuto);
1787 if (NS_FAILED(rv)) {
1788 return rv;
1790 iconFilePath = iconPathAuto.get();
1793 rv = gResolver->SetShortcut(exists,
1794 mWorkingPath.get(),
1795 targetFilePath,
1796 workingDirPath,
1797 char16ptr_t(aArgs),
1798 char16ptr_t(aDescription),
1799 iconFilePath,
1800 iconFilePath ? aIconIndex : 0);
1801 if (targetFilePath && NS_SUCCEEDED(rv)) {
1802 MakeDirty();
1805 return rv;
1809 * Determines if the drive type for the specified file is rmeote or local.
1811 * @param path The path of the file to check
1812 * @param remote Out parameter, on function success holds true if the specified
1813 * file path is remote, or false if the file path is local.
1814 * @return true on success. The return value implies absolutely nothing about
1815 * wether the file is local or remote.
1817 static bool
1818 IsRemoteFilePath(LPCWSTR aPath, bool& aRemote)
1820 // Obtain the parent directory path and make sure it ends with
1821 // a trailing backslash.
1822 WCHAR dirPath[MAX_PATH + 1] = { 0 };
1823 wcsncpy(dirPath, aPath, MAX_PATH);
1824 if (!PathRemoveFileSpecW(dirPath)) {
1825 return false;
1827 size_t len = wcslen(dirPath);
1828 // In case the dirPath holds exaclty MAX_PATH and remains unchanged, we
1829 // recheck the required length here since we need to terminate it with
1830 // a backslash.
1831 if (len >= MAX_PATH) {
1832 return false;
1835 dirPath[len] = L'\\';
1836 dirPath[len + 1] = L'\0';
1837 UINT driveType = GetDriveTypeW(dirPath);
1838 aRemote = driveType == DRIVE_REMOTE;
1839 return true;
1842 nsresult
1843 nsLocalFile::CopySingleFile(nsIFile* aSourceFile, nsIFile* aDestParent,
1844 const nsAString& aNewName, uint32_t aOptions)
1846 nsresult rv = NS_OK;
1847 nsAutoString filePath;
1849 bool move = aOptions & (Move | Rename);
1851 // get the path that we are going to copy to.
1852 // Since windows does not know how to auto
1853 // resolve shortcuts, we must work with the
1854 // target.
1855 nsAutoString destPath;
1856 aDestParent->GetTarget(destPath);
1858 destPath.Append('\\');
1860 if (aNewName.IsEmpty()) {
1861 nsAutoString aFileName;
1862 aSourceFile->GetLeafName(aFileName);
1863 destPath.Append(aFileName);
1864 } else {
1865 destPath.Append(aNewName);
1869 if (aOptions & FollowSymlinks) {
1870 rv = aSourceFile->GetTarget(filePath);
1871 if (filePath.IsEmpty()) {
1872 rv = aSourceFile->GetPath(filePath);
1874 } else {
1875 rv = aSourceFile->GetPath(filePath);
1878 if (NS_FAILED(rv)) {
1879 return rv;
1882 // Pass the flag COPY_FILE_NO_BUFFERING to CopyFileEx as we may be copying
1883 // to a SMBV2 remote drive. Without this parameter subsequent append mode
1884 // file writes can cause the resultant file to become corrupt. We only need to do
1885 // this if the major version of Windows is > 5(Only Windows Vista and above
1886 // can support SMBV2). With a 7200RPM hard drive:
1887 // Copying a 1KB file with COPY_FILE_NO_BUFFERING takes about 30-60ms.
1888 // Copying a 1KB file without COPY_FILE_NO_BUFFERING takes < 1ms.
1889 // So we only use COPY_FILE_NO_BUFFERING when we have a remote drive.
1890 int copyOK;
1891 DWORD dwCopyFlags = COPY_FILE_ALLOW_DECRYPTED_DESTINATION;
1892 if (IsVistaOrLater()) {
1893 bool path1Remote, path2Remote;
1894 if (!IsRemoteFilePath(filePath.get(), path1Remote) ||
1895 !IsRemoteFilePath(destPath.get(), path2Remote) ||
1896 path1Remote || path2Remote) {
1897 dwCopyFlags |= COPY_FILE_NO_BUFFERING;
1901 if (!move) {
1902 copyOK = ::CopyFileExW(filePath.get(), destPath.get(), nullptr,
1903 nullptr, nullptr, dwCopyFlags);
1904 } else {
1905 copyOK = ::MoveFileExW(filePath.get(), destPath.get(),
1906 MOVEFILE_REPLACE_EXISTING);
1908 // Check if copying the source file to a different volume,
1909 // as this could be an SMBV2 mapped drive.
1910 if (!copyOK && GetLastError() == ERROR_NOT_SAME_DEVICE) {
1911 if (aOptions & Rename) {
1912 return NS_ERROR_FILE_ACCESS_DENIED;
1914 copyOK = CopyFileExW(filePath.get(), destPath.get(), nullptr,
1915 nullptr, nullptr, dwCopyFlags);
1917 if (copyOK) {
1918 DeleteFileW(filePath.get());
1923 if (!copyOK) { // CopyFileEx and MoveFileEx return zero at failure.
1924 rv = ConvertWinError(GetLastError());
1925 } else if (move && !(aOptions & SkipNtfsAclReset)) {
1926 // Set security permissions to inherit from parent.
1927 // Note: propagates to all children: slow for big file trees
1928 PACL pOldDACL = nullptr;
1929 PSECURITY_DESCRIPTOR pSD = nullptr;
1930 ::GetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT,
1931 DACL_SECURITY_INFORMATION,
1932 nullptr, nullptr, &pOldDACL, nullptr, &pSD);
1933 if (pOldDACL)
1934 ::SetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT,
1935 DACL_SECURITY_INFORMATION |
1936 UNPROTECTED_DACL_SECURITY_INFORMATION,
1937 nullptr, nullptr, pOldDACL, nullptr);
1938 if (pSD) {
1939 LocalFree((HLOCAL)pSD);
1943 return rv;
1946 nsresult
1947 nsLocalFile::CopyMove(nsIFile* aParentDir, const nsAString& aNewName,
1948 uint32_t aOptions)
1950 bool move = aOptions & (Move | Rename);
1951 bool followSymlinks = aOptions & FollowSymlinks;
1953 nsCOMPtr<nsIFile> newParentDir = aParentDir;
1954 // check to see if this exists, otherwise return an error.
1955 // we will check this by resolving. If the user wants us
1956 // to follow links, then we are talking about the target,
1957 // hence we can use the |FollowSymlinks| option.
1958 nsresult rv = ResolveAndStat();
1959 if (NS_FAILED(rv)) {
1960 return rv;
1963 if (!newParentDir) {
1964 // no parent was specified. We must rename.
1965 if (aNewName.IsEmpty()) {
1966 return NS_ERROR_INVALID_ARG;
1969 rv = GetParent(getter_AddRefs(newParentDir));
1970 if (NS_FAILED(rv)) {
1971 return rv;
1975 if (!newParentDir) {
1976 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
1979 // make sure it exists and is a directory. Create it if not there.
1980 bool exists;
1981 newParentDir->Exists(&exists);
1982 if (!exists) {
1983 rv = newParentDir->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use
1984 if (NS_FAILED(rv)) {
1985 return rv;
1987 } else {
1988 bool isDir;
1989 newParentDir->IsDirectory(&isDir);
1990 if (!isDir) {
1991 if (followSymlinks) {
1992 bool isLink;
1993 newParentDir->IsSymlink(&isLink);
1994 if (isLink) {
1995 nsAutoString target;
1996 newParentDir->GetTarget(target);
1998 nsCOMPtr<nsIFile> realDest = new nsLocalFile();
1999 if (!realDest) {
2000 return NS_ERROR_OUT_OF_MEMORY;
2003 rv = realDest->InitWithPath(target);
2005 if (NS_FAILED(rv)) {
2006 return rv;
2009 return CopyMove(realDest, aNewName, aOptions);
2011 } else {
2012 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
2017 // Try different ways to move/copy files/directories
2018 bool done = false;
2019 bool isDir;
2020 IsDirectory(&isDir);
2021 bool isSymlink;
2022 IsSymlink(&isSymlink);
2024 // Try to move the file or directory, or try to copy a single file (or non-followed symlink)
2025 if (move || !isDir || (isSymlink && !followSymlinks)) {
2026 // Copy/Move single file, or move a directory
2027 if (!aParentDir) {
2028 aOptions |= SkipNtfsAclReset;
2030 rv = CopySingleFile(this, newParentDir, aNewName, aOptions);
2031 done = NS_SUCCEEDED(rv);
2032 // If we are moving a directory and that fails, fallback on directory
2033 // enumeration. See bug 231300 for details.
2034 if (!done && !(move && isDir)) {
2035 return rv;
2039 // Not able to copy or move directly, so enumerate it
2040 if (!done) {
2041 // create a new target destination in the new parentDir;
2042 nsCOMPtr<nsIFile> target;
2043 rv = newParentDir->Clone(getter_AddRefs(target));
2045 if (NS_FAILED(rv)) {
2046 return rv;
2049 nsAutoString allocatedNewName;
2050 if (aNewName.IsEmpty()) {
2051 bool isLink;
2052 IsSymlink(&isLink);
2053 if (isLink) {
2054 nsAutoString temp;
2055 GetTarget(temp);
2056 int32_t offset = temp.RFindChar(L'\\');
2057 if (offset == kNotFound) {
2058 allocatedNewName = temp;
2059 } else {
2060 allocatedNewName = Substring(temp, offset + 1);
2062 } else {
2063 GetLeafName(allocatedNewName);// this should be the leaf name of the
2065 } else {
2066 allocatedNewName = aNewName;
2069 rv = target->Append(allocatedNewName);
2070 if (NS_FAILED(rv)) {
2071 return rv;
2074 allocatedNewName.Truncate();
2076 // check if the destination directory already exists
2077 target->Exists(&exists);
2078 if (!exists) {
2079 // if the destination directory cannot be created, return an error
2080 rv = target->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use
2081 if (NS_FAILED(rv)) {
2082 return rv;
2084 } else {
2085 // check if the destination directory is writable and empty
2086 bool isWritable;
2088 target->IsWritable(&isWritable);
2089 if (!isWritable) {
2090 return NS_ERROR_FILE_ACCESS_DENIED;
2093 nsCOMPtr<nsISimpleEnumerator> targetIterator;
2094 rv = target->GetDirectoryEntries(getter_AddRefs(targetIterator));
2095 if (NS_FAILED(rv)) {
2096 return rv;
2099 bool more;
2100 targetIterator->HasMoreElements(&more);
2101 // return error if target directory is not empty
2102 if (more) {
2103 return NS_ERROR_FILE_DIR_NOT_EMPTY;
2107 nsRefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator();
2109 rv = dirEnum->Init(this);
2110 if (NS_FAILED(rv)) {
2111 NS_WARNING("dirEnum initialization failed");
2112 return rv;
2115 bool more = false;
2116 while (NS_SUCCEEDED(dirEnum->HasMoreElements(&more)) && more) {
2117 nsCOMPtr<nsISupports> item;
2118 nsCOMPtr<nsIFile> file;
2119 dirEnum->GetNext(getter_AddRefs(item));
2120 file = do_QueryInterface(item);
2121 if (file) {
2122 bool isDir, isLink;
2124 file->IsDirectory(&isDir);
2125 file->IsSymlink(&isLink);
2127 if (move) {
2128 if (followSymlinks) {
2129 return NS_ERROR_FAILURE;
2132 rv = file->MoveTo(target, EmptyString());
2133 if (NS_FAILED(rv)) {
2134 return rv;
2136 } else {
2137 if (followSymlinks) {
2138 rv = file->CopyToFollowingLinks(target, EmptyString());
2139 } else {
2140 rv = file->CopyTo(target, EmptyString());
2142 if (NS_FAILED(rv)) {
2143 return rv;
2148 // we've finished moving all the children of this directory
2149 // in the new directory. so now delete the directory
2150 // note, we don't need to do a recursive delete.
2151 // MoveTo() is recursive. At this point,
2152 // we've already moved the children of the current folder
2153 // to the new location. nothing should be left in the folder.
2154 if (move) {
2155 rv = Remove(false /* recursive */);
2156 if (NS_FAILED(rv)) {
2157 return rv;
2163 // If we moved, we want to adjust this.
2164 if (move) {
2165 MakeDirty();
2167 nsAutoString newParentPath;
2168 newParentDir->GetPath(newParentPath);
2170 if (newParentPath.IsEmpty()) {
2171 return NS_ERROR_FAILURE;
2174 if (aNewName.IsEmpty()) {
2175 nsAutoString aFileName;
2176 GetLeafName(aFileName);
2178 InitWithPath(newParentPath);
2179 Append(aFileName);
2180 } else {
2181 InitWithPath(newParentPath);
2182 Append(aNewName);
2186 return NS_OK;
2189 NS_IMETHODIMP
2190 nsLocalFile::CopyTo(nsIFile* aNewParentDir, const nsAString& aNewName)
2192 return CopyMove(aNewParentDir, aNewName, 0);
2195 NS_IMETHODIMP
2196 nsLocalFile::CopyToFollowingLinks(nsIFile* aNewParentDir,
2197 const nsAString& aNewName)
2199 return CopyMove(aNewParentDir, aNewName, FollowSymlinks);
2202 NS_IMETHODIMP
2203 nsLocalFile::MoveTo(nsIFile* aNewParentDir, const nsAString& aNewName)
2205 return CopyMove(aNewParentDir, aNewName, Move);
2208 NS_IMETHODIMP
2209 nsLocalFile::RenameTo(nsIFile* aNewParentDir, const nsAString& aNewName)
2211 nsCOMPtr<nsIFile> targetParentDir = aNewParentDir;
2212 // check to see if this exists, otherwise return an error.
2213 // we will check this by resolving. If the user wants us
2214 // to follow links, then we are talking about the target,
2215 // hence we can use the |followSymlinks| parameter.
2216 nsresult rv = ResolveAndStat();
2217 if (NS_FAILED(rv)) {
2218 return rv;
2221 if (!targetParentDir) {
2222 // no parent was specified. We must rename.
2223 if (aNewName.IsEmpty()) {
2224 return NS_ERROR_INVALID_ARG;
2226 rv = GetParent(getter_AddRefs(targetParentDir));
2227 if (NS_FAILED(rv)) {
2228 return rv;
2232 if (!targetParentDir) {
2233 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
2236 // make sure it exists and is a directory. Create it if not there.
2237 bool exists;
2238 targetParentDir->Exists(&exists);
2239 if (!exists) {
2240 rv = targetParentDir->Create(DIRECTORY_TYPE, 0644);
2241 if (NS_FAILED(rv)) {
2242 return rv;
2244 } else {
2245 bool isDir;
2246 targetParentDir->IsDirectory(&isDir);
2247 if (!isDir) {
2248 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
2252 uint32_t options = Rename;
2253 if (!aNewParentDir) {
2254 options |= SkipNtfsAclReset;
2256 // Move single file, or move a directory
2257 return CopySingleFile(this, targetParentDir, aNewName, options);
2260 NS_IMETHODIMP
2261 nsLocalFile::Load(PRLibrary** aResult)
2263 // Check we are correctly initialized.
2264 CHECK_mWorkingPath();
2266 bool isFile;
2267 nsresult rv = IsFile(&isFile);
2269 if (NS_FAILED(rv)) {
2270 return rv;
2273 if (!isFile) {
2274 return NS_ERROR_FILE_IS_DIRECTORY;
2277 #ifdef NS_BUILD_REFCNT_LOGGING
2278 nsTraceRefcnt::SetActivityIsLegal(false);
2279 #endif
2281 PRLibSpec libSpec;
2282 libSpec.value.pathname_u = mResolvedPath.get();
2283 libSpec.type = PR_LibSpec_PathnameU;
2284 *aResult = PR_LoadLibraryWithFlags(libSpec, 0);
2286 #ifdef NS_BUILD_REFCNT_LOGGING
2287 nsTraceRefcnt::SetActivityIsLegal(true);
2288 #endif
2290 if (*aResult) {
2291 return NS_OK;
2293 return NS_ERROR_NULL_POINTER;
2296 NS_IMETHODIMP
2297 nsLocalFile::Remove(bool aRecursive)
2299 // NOTE:
2301 // if the working path points to a shortcut, then we will only
2302 // delete the shortcut itself. even if the shortcut points to
2303 // a directory, we will not recurse into that directory or
2304 // delete that directory itself. likewise, if the shortcut
2305 // points to a normal file, we will not delete the real file.
2306 // this is done to be consistent with the other platforms that
2307 // behave this way. we do this even if the followLinks attribute
2308 // is set to true. this helps protect against misuse that could
2309 // lead to security bugs (e.g., bug 210588).
2311 // Since shortcut files are no longer permitted to be used as unix-like
2312 // symlinks interspersed in the path (e.g. "c:/file.lnk/foo/bar.txt")
2313 // this processing is a lot simpler. Even if the shortcut file is
2314 // pointing to a directory, only the mWorkingPath value is used and so
2315 // only the shortcut file will be deleted.
2317 // Check we are correctly initialized.
2318 CHECK_mWorkingPath();
2320 bool isDir, isLink;
2321 nsresult rv;
2323 isDir = false;
2324 rv = IsSymlink(&isLink);
2325 if (NS_FAILED(rv)) {
2326 return rv;
2329 // only check to see if we have a directory if it isn't a link
2330 if (!isLink) {
2331 rv = IsDirectory(&isDir);
2332 if (NS_FAILED(rv)) {
2333 return rv;
2337 if (isDir) {
2338 if (aRecursive) {
2339 nsRefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator();
2341 rv = dirEnum->Init(this);
2342 if (NS_FAILED(rv)) {
2343 return rv;
2346 bool more = false;
2347 while (NS_SUCCEEDED(dirEnum->HasMoreElements(&more)) && more) {
2348 nsCOMPtr<nsISupports> item;
2349 dirEnum->GetNext(getter_AddRefs(item));
2350 nsCOMPtr<nsIFile> file = do_QueryInterface(item);
2351 if (file) {
2352 file->Remove(aRecursive);
2356 if (RemoveDirectoryW(mWorkingPath.get()) == 0) {
2357 return ConvertWinError(GetLastError());
2359 } else {
2360 if (DeleteFileW(mWorkingPath.get()) == 0) {
2361 return ConvertWinError(GetLastError());
2365 MakeDirty();
2366 return rv;
2369 NS_IMETHODIMP
2370 nsLocalFile::GetLastModifiedTime(PRTime* aLastModifiedTime)
2372 // Check we are correctly initialized.
2373 CHECK_mWorkingPath();
2375 if (NS_WARN_IF(!aLastModifiedTime)) {
2376 return NS_ERROR_INVALID_ARG;
2379 // get the modified time of the target as determined by mFollowSymlinks
2380 // If true, then this will be for the target of the shortcut file,
2381 // otherwise it will be for the shortcut file itself (i.e. the same
2382 // results as GetLastModifiedTimeOfLink)
2384 nsresult rv = ResolveAndStat();
2385 if (NS_FAILED(rv)) {
2386 return rv;
2389 // microseconds -> milliseconds
2390 *aLastModifiedTime = mFileInfo64.modifyTime / PR_USEC_PER_MSEC;
2391 return NS_OK;
2395 NS_IMETHODIMP
2396 nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModifiedTime)
2398 // Check we are correctly initialized.
2399 CHECK_mWorkingPath();
2401 if (NS_WARN_IF(!aLastModifiedTime)) {
2402 return NS_ERROR_INVALID_ARG;
2405 // The caller is assumed to have already called IsSymlink
2406 // and to have found that this file is a link.
2408 PRFileInfo64 info;
2409 nsresult rv = GetFileInfo(mWorkingPath, &info);
2410 if (NS_FAILED(rv)) {
2411 return rv;
2414 // microseconds -> milliseconds
2415 *aLastModifiedTime = info.modifyTime / PR_USEC_PER_MSEC;
2416 return NS_OK;
2420 NS_IMETHODIMP
2421 nsLocalFile::SetLastModifiedTime(PRTime aLastModifiedTime)
2423 // Check we are correctly initialized.
2424 CHECK_mWorkingPath();
2426 nsresult rv = ResolveAndStat();
2427 if (NS_FAILED(rv)) {
2428 return rv;
2431 // set the modified time of the target as determined by mFollowSymlinks
2432 // If true, then this will be for the target of the shortcut file,
2433 // otherwise it will be for the shortcut file itself (i.e. the same
2434 // results as SetLastModifiedTimeOfLink)
2436 rv = SetModDate(aLastModifiedTime, mResolvedPath.get());
2437 if (NS_SUCCEEDED(rv)) {
2438 MakeDirty();
2441 return rv;
2445 NS_IMETHODIMP
2446 nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModifiedTime)
2448 // The caller is assumed to have already called IsSymlink
2449 // and to have found that this file is a link.
2451 nsresult rv = SetModDate(aLastModifiedTime, mWorkingPath.get());
2452 if (NS_SUCCEEDED(rv)) {
2453 MakeDirty();
2456 return rv;
2459 nsresult
2460 nsLocalFile::SetModDate(PRTime aLastModifiedTime, const wchar_t* aFilePath)
2462 // The FILE_FLAG_BACKUP_SEMANTICS is required in order to change the
2463 // modification time for directories.
2464 HANDLE file = ::CreateFileW(aFilePath, // pointer to name of the file
2465 GENERIC_WRITE, // access (write) mode
2466 0, // share mode
2467 nullptr, // pointer to security attributes
2468 OPEN_EXISTING, // how to create
2469 FILE_FLAG_BACKUP_SEMANTICS, // file attributes
2470 nullptr);
2472 if (file == INVALID_HANDLE_VALUE) {
2473 return ConvertWinError(GetLastError());
2476 FILETIME ft;
2477 SYSTEMTIME st;
2478 PRExplodedTime pret;
2480 // PR_ExplodeTime expects usecs...
2481 PR_ExplodeTime(aLastModifiedTime * PR_USEC_PER_MSEC, PR_GMTParameters, &pret);
2482 st.wYear = pret.tm_year;
2483 st.wMonth = pret.tm_month + 1; // Convert start offset -- Win32: Jan=1; NSPR: Jan=0
2484 st.wDayOfWeek = pret.tm_wday;
2485 st.wDay = pret.tm_mday;
2486 st.wHour = pret.tm_hour;
2487 st.wMinute = pret.tm_min;
2488 st.wSecond = pret.tm_sec;
2489 st.wMilliseconds = pret.tm_usec / 1000;
2491 nsresult rv = NS_OK;
2492 // if at least one of these fails...
2493 if (!(SystemTimeToFileTime(&st, &ft) != 0 &&
2494 SetFileTime(file, nullptr, &ft, &ft) != 0)) {
2495 rv = ConvertWinError(GetLastError());
2498 CloseHandle(file);
2499 return rv;
2502 NS_IMETHODIMP
2503 nsLocalFile::GetPermissions(uint32_t* aPermissions)
2505 if (NS_WARN_IF(!aPermissions)) {
2506 return NS_ERROR_INVALID_ARG;
2509 // get the permissions of the target as determined by mFollowSymlinks
2510 // If true, then this will be for the target of the shortcut file,
2511 // otherwise it will be for the shortcut file itself (i.e. the same
2512 // results as GetPermissionsOfLink)
2513 nsresult rv = ResolveAndStat();
2514 if (NS_FAILED(rv)) {
2515 return rv;
2518 bool isWritable, isExecutable;
2519 IsWritable(&isWritable);
2520 IsExecutable(&isExecutable);
2522 *aPermissions = PR_IRUSR | PR_IRGRP | PR_IROTH; // all read
2523 if (isWritable) {
2524 *aPermissions |= PR_IWUSR | PR_IWGRP | PR_IWOTH; // all write
2526 if (isExecutable) {
2527 *aPermissions |= PR_IXUSR | PR_IXGRP | PR_IXOTH; // all execute
2530 return NS_OK;
2533 NS_IMETHODIMP
2534 nsLocalFile::GetPermissionsOfLink(uint32_t* aPermissions)
2536 // Check we are correctly initialized.
2537 CHECK_mWorkingPath();
2539 if (NS_WARN_IF(!aPermissions)) {
2540 return NS_ERROR_INVALID_ARG;
2543 // The caller is assumed to have already called IsSymlink
2544 // and to have found that this file is a link. It is not
2545 // possible for a link file to be executable.
2547 DWORD word = ::GetFileAttributesW(mWorkingPath.get());
2548 if (word == INVALID_FILE_ATTRIBUTES) {
2549 return NS_ERROR_FILE_INVALID_PATH;
2552 bool isWritable = !(word & FILE_ATTRIBUTE_READONLY);
2553 *aPermissions = PR_IRUSR | PR_IRGRP | PR_IROTH; // all read
2554 if (isWritable) {
2555 *aPermissions |= PR_IWUSR | PR_IWGRP | PR_IWOTH; // all write
2558 return NS_OK;
2562 NS_IMETHODIMP
2563 nsLocalFile::SetPermissions(uint32_t aPermissions)
2565 // Check we are correctly initialized.
2566 CHECK_mWorkingPath();
2568 // set the permissions of the target as determined by mFollowSymlinks
2569 // If true, then this will be for the target of the shortcut file,
2570 // otherwise it will be for the shortcut file itself (i.e. the same
2571 // results as SetPermissionsOfLink)
2572 nsresult rv = ResolveAndStat();
2573 if (NS_FAILED(rv)) {
2574 return rv;
2577 // windows only knows about the following permissions
2578 int mode = 0;
2579 if (aPermissions & (PR_IRUSR | PR_IRGRP | PR_IROTH)) { // any read
2580 mode |= _S_IREAD;
2582 if (aPermissions & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) { // any write
2583 mode |= _S_IWRITE;
2586 if (_wchmod(mResolvedPath.get(), mode) == -1) {
2587 return NS_ERROR_FAILURE;
2590 return NS_OK;
2593 NS_IMETHODIMP
2594 nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions)
2596 // The caller is assumed to have already called IsSymlink
2597 // and to have found that this file is a link.
2599 // windows only knows about the following permissions
2600 int mode = 0;
2601 if (aPermissions & (PR_IRUSR | PR_IRGRP | PR_IROTH)) { // any read
2602 mode |= _S_IREAD;
2604 if (aPermissions & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) { // any write
2605 mode |= _S_IWRITE;
2608 if (_wchmod(mWorkingPath.get(), mode) == -1) {
2609 return NS_ERROR_FAILURE;
2612 return NS_OK;
2616 NS_IMETHODIMP
2617 nsLocalFile::GetFileSize(int64_t* aFileSize)
2619 if (NS_WARN_IF(!aFileSize)) {
2620 return NS_ERROR_INVALID_ARG;
2623 nsresult rv = ResolveAndStat();
2624 if (NS_FAILED(rv)) {
2625 return rv;
2628 *aFileSize = mFileInfo64.size;
2629 return NS_OK;
2633 NS_IMETHODIMP
2634 nsLocalFile::GetFileSizeOfLink(int64_t* aFileSize)
2636 // Check we are correctly initialized.
2637 CHECK_mWorkingPath();
2639 if (NS_WARN_IF(!aFileSize)) {
2640 return NS_ERROR_INVALID_ARG;
2643 // The caller is assumed to have already called IsSymlink
2644 // and to have found that this file is a link.
2646 PRFileInfo64 info;
2647 if (NS_FAILED(GetFileInfo(mWorkingPath, &info))) {
2648 return NS_ERROR_FILE_INVALID_PATH;
2651 *aFileSize = info.size;
2652 return NS_OK;
2655 NS_IMETHODIMP
2656 nsLocalFile::SetFileSize(int64_t aFileSize)
2658 // Check we are correctly initialized.
2659 CHECK_mWorkingPath();
2661 nsresult rv = ResolveAndStat();
2662 if (NS_FAILED(rv)) {
2663 return rv;
2666 HANDLE hFile = ::CreateFileW(mResolvedPath.get(),// pointer to name of the file
2667 GENERIC_WRITE, // access (write) mode
2668 FILE_SHARE_READ, // share mode
2669 nullptr, // pointer to security attributes
2670 OPEN_EXISTING, // how to create
2671 FILE_ATTRIBUTE_NORMAL, // file attributes
2672 nullptr);
2673 if (hFile == INVALID_HANDLE_VALUE) {
2674 return ConvertWinError(GetLastError());
2677 // seek the file pointer to the new, desired end of file
2678 // and then truncate the file at that position
2679 rv = NS_ERROR_FAILURE;
2680 aFileSize = MyFileSeek64(hFile, aFileSize, FILE_BEGIN);
2681 if (aFileSize != -1 && SetEndOfFile(hFile)) {
2682 MakeDirty();
2683 rv = NS_OK;
2686 CloseHandle(hFile);
2687 return rv;
2690 NS_IMETHODIMP
2691 nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable)
2693 // Check we are correctly initialized.
2694 CHECK_mWorkingPath();
2696 if (NS_WARN_IF(!aDiskSpaceAvailable)) {
2697 return NS_ERROR_INVALID_ARG;
2700 ResolveAndStat();
2702 if (mFileInfo64.type == PR_FILE_FILE) {
2703 // Since GetDiskFreeSpaceExW works only on directories, use the parent.
2704 nsCOMPtr<nsIFile> parent;
2705 if (NS_SUCCEEDED(GetParent(getter_AddRefs(parent))) && parent) {
2706 return parent->GetDiskSpaceAvailable(aDiskSpaceAvailable);
2710 ULARGE_INTEGER liFreeBytesAvailableToCaller, liTotalNumberOfBytes;
2711 if (::GetDiskFreeSpaceExW(mResolvedPath.get(), &liFreeBytesAvailableToCaller,
2712 &liTotalNumberOfBytes, nullptr)) {
2713 *aDiskSpaceAvailable = liFreeBytesAvailableToCaller.QuadPart;
2714 return NS_OK;
2716 *aDiskSpaceAvailable = 0;
2717 return NS_OK;
2720 NS_IMETHODIMP
2721 nsLocalFile::GetParent(nsIFile** aParent)
2723 // Check we are correctly initialized.
2724 CHECK_mWorkingPath();
2726 if (NS_WARN_IF(!aParent)) {
2727 return NS_ERROR_INVALID_ARG;
2730 // A two-character path must be a drive such as C:, so it has no parent
2731 if (mWorkingPath.Length() == 2) {
2732 *aParent = nullptr;
2733 return NS_OK;
2736 int32_t offset = mWorkingPath.RFindChar(char16_t('\\'));
2737 // adding this offset check that was removed in bug 241708 fixes mail
2738 // directories that aren't relative to/underneath the profile dir.
2739 // e.g., on a different drive. Before you remove them, please make
2740 // sure local mail directories that aren't underneath the profile dir work.
2741 if (offset == kNotFound) {
2742 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
2745 // A path of the form \\NAME is a top-level path and has no parent
2746 if (offset == 1 && mWorkingPath[0] == L'\\') {
2747 *aParent = nullptr;
2748 return NS_OK;
2751 nsAutoString parentPath(mWorkingPath);
2753 if (offset > 0) {
2754 parentPath.Truncate(offset);
2755 } else {
2756 parentPath.AssignLiteral("\\\\.");
2759 nsCOMPtr<nsIFile> localFile;
2760 nsresult rv = NS_NewLocalFile(parentPath, mFollowSymlinks,
2761 getter_AddRefs(localFile));
2763 if (NS_FAILED(rv)) {
2764 return rv;
2767 localFile.forget(aParent);
2768 return NS_OK;
2771 NS_IMETHODIMP
2772 nsLocalFile::Exists(bool* aResult)
2774 // Check we are correctly initialized.
2775 CHECK_mWorkingPath();
2777 if (NS_WARN_IF(!aResult)) {
2778 return NS_ERROR_INVALID_ARG;
2780 *aResult = false;
2782 MakeDirty();
2783 nsresult rv = ResolveAndStat();
2784 *aResult = NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_IS_LOCKED;
2786 return NS_OK;
2789 NS_IMETHODIMP
2790 nsLocalFile::IsWritable(bool* aIsWritable)
2792 // Check we are correctly initialized.
2793 CHECK_mWorkingPath();
2795 // The read-only attribute on a FAT directory only means that it can't
2796 // be deleted. It is still possible to modify the contents of the directory.
2797 nsresult rv = IsDirectory(aIsWritable);
2798 if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
2799 *aIsWritable = true;
2800 return NS_OK;
2801 } else if (rv == NS_ERROR_FILE_IS_LOCKED) {
2802 // If the file is normally allowed write access
2803 // we should still return that the file is writable.
2804 } else if (NS_FAILED(rv)) {
2805 return rv;
2807 if (*aIsWritable) {
2808 return NS_OK;
2811 // writable if the file doesn't have the readonly attribute
2812 rv = HasFileAttribute(FILE_ATTRIBUTE_READONLY, aIsWritable);
2813 if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
2814 *aIsWritable = false;
2815 return NS_OK;
2816 } else if (rv == NS_ERROR_FILE_IS_LOCKED) {
2817 // If the file is normally allowed write access
2818 // we should still return that the file is writable.
2819 } else if (NS_FAILED(rv)) {
2820 return rv;
2822 *aIsWritable = !*aIsWritable;
2824 // If the read only attribute is not set, check to make sure
2825 // we can open the file with write access.
2826 if (*aIsWritable) {
2827 PRFileDesc* file;
2828 rv = OpenFile(mResolvedPath, PR_WRONLY, 0, &file);
2829 if (NS_SUCCEEDED(rv)) {
2830 PR_Close(file);
2831 } else if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
2832 *aIsWritable = false;
2833 } else if (rv == NS_ERROR_FILE_IS_LOCKED) {
2834 // If it is locked and read only we would have
2835 // gotten access denied
2836 *aIsWritable = true;
2837 } else {
2838 return rv;
2841 return NS_OK;
2844 NS_IMETHODIMP
2845 nsLocalFile::IsReadable(bool* aResult)
2847 // Check we are correctly initialized.
2848 CHECK_mWorkingPath();
2850 if (NS_WARN_IF(!aResult)) {
2851 return NS_ERROR_INVALID_ARG;
2853 *aResult = false;
2855 nsresult rv = ResolveAndStat();
2856 if (NS_FAILED(rv)) {
2857 return rv;
2860 *aResult = true;
2861 return NS_OK;
2865 NS_IMETHODIMP
2866 nsLocalFile::IsExecutable(bool* aResult)
2868 // Check we are correctly initialized.
2869 CHECK_mWorkingPath();
2871 if (NS_WARN_IF(!aResult)) {
2872 return NS_ERROR_INVALID_ARG;
2874 *aResult = false;
2876 nsresult rv;
2878 // only files can be executables
2879 bool isFile;
2880 rv = IsFile(&isFile);
2881 if (NS_FAILED(rv)) {
2882 return rv;
2884 if (!isFile) {
2885 return NS_OK;
2888 //TODO: shouldn't we be checking mFollowSymlinks here?
2889 bool symLink;
2890 rv = IsSymlink(&symLink);
2891 if (NS_FAILED(rv)) {
2892 return rv;
2895 nsAutoString path;
2896 if (symLink) {
2897 GetTarget(path);
2898 } else {
2899 GetPath(path);
2902 // kill trailing dots and spaces.
2903 int32_t filePathLen = path.Length() - 1;
2904 while (filePathLen > 0 && (path[filePathLen] == L' ' ||
2905 path[filePathLen] == L'.')) {
2906 path.Truncate(filePathLen--);
2909 // Get extension.
2910 int32_t dotIdx = path.RFindChar(char16_t('.'));
2911 if (dotIdx != kNotFound) {
2912 // Convert extension to lower case.
2913 char16_t* p = path.BeginWriting();
2914 for (p += dotIdx + 1; *p; ++p) {
2915 *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0;
2918 // Search for any of the set of executable extensions.
2919 static const char* const executableExts[] = {
2920 "ad",
2921 "ade", // access project extension
2922 "adp",
2923 "air", // Adobe AIR installer
2924 "app", // executable application
2925 "application", // from bug 348763
2926 "asp",
2927 "bas",
2928 "bat",
2929 "chm",
2930 "cmd",
2931 "com",
2932 "cpl",
2933 "crt",
2934 "exe",
2935 "fxp", // FoxPro compiled app
2936 "hlp",
2937 "hta",
2938 "inf",
2939 "ins",
2940 "isp",
2941 "jar", // java application bundle
2942 "js",
2943 "jse",
2944 "lnk",
2945 "mad", // Access Module Shortcut
2946 "maf", // Access
2947 "mag", // Access Diagram Shortcut
2948 "mam", // Access Macro Shortcut
2949 "maq", // Access Query Shortcut
2950 "mar", // Access Report Shortcut
2951 "mas", // Access Stored Procedure
2952 "mat", // Access Table Shortcut
2953 "mau", // Media Attachment Unit
2954 "mav", // Access View Shortcut
2955 "maw", // Access Data Access Page
2956 "mda", // Access Add-in, MDA Access 2 Workgroup
2957 "mdb",
2958 "mde",
2959 "mdt", // Access Add-in Data
2960 "mdw", // Access Workgroup Information
2961 "mdz", // Access Wizard Template
2962 "msc",
2963 "msh", // Microsoft Shell
2964 "mshxml", // Microsoft Shell
2965 "msi",
2966 "msp",
2967 "mst",
2968 "ops", // Office Profile Settings
2969 "pcd",
2970 "pif",
2971 "plg", // Developer Studio Build Log
2972 "prf", // windows system file
2973 "prg",
2974 "pst",
2975 "reg",
2976 "scf", // Windows explorer command
2977 "scr",
2978 "sct",
2979 "shb",
2980 "shs",
2981 "url",
2982 "vb",
2983 "vbe",
2984 "vbs",
2985 "vsd",
2986 "vsmacros", // Visual Studio .NET Binary-based Macro Project
2987 "vss",
2988 "vst",
2989 "vsw",
2990 "ws",
2991 "wsc",
2992 "wsf",
2993 "wsh"
2995 nsDependentSubstring ext = Substring(path, dotIdx + 1);
2996 for (size_t i = 0; i < ArrayLength(executableExts); ++i) {
2997 if (ext.EqualsASCII(executableExts[i])) {
2998 // Found a match. Set result and quit.
2999 *aResult = true;
3000 break;
3005 return NS_OK;
3009 NS_IMETHODIMP
3010 nsLocalFile::IsDirectory(bool* aResult)
3012 return HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, aResult);
3015 NS_IMETHODIMP
3016 nsLocalFile::IsFile(bool* aResult)
3018 nsresult rv = HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, aResult);
3019 if (NS_SUCCEEDED(rv)) {
3020 *aResult = !*aResult;
3022 return rv;
3025 NS_IMETHODIMP
3026 nsLocalFile::IsHidden(bool* aResult)
3028 return HasFileAttribute(FILE_ATTRIBUTE_HIDDEN, aResult);
3031 nsresult
3032 nsLocalFile::HasFileAttribute(DWORD aFileAttrib, bool* aResult)
3034 if (NS_WARN_IF(!aResult)) {
3035 return NS_ERROR_INVALID_ARG;
3038 nsresult rv = Resolve();
3039 if (NS_FAILED(rv)) {
3040 return rv;
3043 DWORD attributes = GetFileAttributesW(mResolvedPath.get());
3044 if (INVALID_FILE_ATTRIBUTES == attributes) {
3045 return ConvertWinError(GetLastError());
3048 *aResult = ((attributes & aFileAttrib) != 0);
3049 return NS_OK;
3052 NS_IMETHODIMP
3053 nsLocalFile::IsSymlink(bool* aResult)
3055 // Check we are correctly initialized.
3056 CHECK_mWorkingPath();
3058 if (NS_WARN_IF(!aResult)) {
3059 return NS_ERROR_INVALID_ARG;
3062 // unless it is a valid shortcut path it's not a symlink
3063 if (!IsShortcutPath(mWorkingPath)) {
3064 *aResult = false;
3065 return NS_OK;
3068 // we need to know if this is a file or directory
3069 nsresult rv = ResolveAndStat();
3070 if (NS_FAILED(rv)) {
3071 return rv;
3074 // We should not check mFileInfo64.type here for PR_FILE_FILE because lnk
3075 // files can point to directories or files. Important security checks
3076 // depend on correctly identifying lnk files. mFileInfo64 now holds info
3077 // about the target of the lnk file, not the actual lnk file!
3078 *aResult = true;
3079 return NS_OK;
3082 NS_IMETHODIMP
3083 nsLocalFile::IsSpecial(bool* aResult)
3085 return HasFileAttribute(FILE_ATTRIBUTE_SYSTEM, aResult);
3088 NS_IMETHODIMP
3089 nsLocalFile::Equals(nsIFile* aInFile, bool* aResult)
3091 if (NS_WARN_IF(!aInFile)) {
3092 return NS_ERROR_INVALID_ARG;
3094 if (NS_WARN_IF(!aResult)) {
3095 return NS_ERROR_INVALID_ARG;
3098 EnsureShortPath();
3100 nsCOMPtr<nsILocalFileWin> lf(do_QueryInterface(aInFile));
3101 if (!lf) {
3102 *aResult = false;
3103 return NS_OK;
3106 nsAutoString inFilePath;
3107 lf->GetCanonicalPath(inFilePath);
3109 // Ok : Win9x
3110 *aResult = _wcsicmp(mShortWorkingPath.get(), inFilePath.get()) == 0;
3112 return NS_OK;
3116 NS_IMETHODIMP
3117 nsLocalFile::Contains(nsIFile* aInFile, bool* aResult)
3119 // Check we are correctly initialized.
3120 CHECK_mWorkingPath();
3122 *aResult = false;
3124 nsAutoString myFilePath;
3125 if (NS_FAILED(GetTarget(myFilePath))) {
3126 GetPath(myFilePath);
3129 uint32_t myFilePathLen = myFilePath.Length();
3131 nsAutoString inFilePath;
3132 if (NS_FAILED(aInFile->GetTarget(inFilePath))) {
3133 aInFile->GetPath(inFilePath);
3136 // make sure that the |aInFile|'s path has a trailing separator.
3137 if (inFilePath.Length() >= myFilePathLen &&
3138 inFilePath[myFilePathLen] == L'\\') {
3139 if (_wcsnicmp(myFilePath.get(), inFilePath.get(), myFilePathLen) == 0) {
3140 *aResult = true;
3145 return NS_OK;
3149 NS_IMETHODIMP
3150 nsLocalFile::GetTarget(nsAString& aResult)
3152 aResult.Truncate();
3153 #if STRICT_FAKE_SYMLINKS
3154 bool symLink;
3156 nsresult rv = IsSymlink(&symLink);
3157 if (NS_FAILED(rv)) {
3158 return rv;
3161 if (!symLink) {
3162 return NS_ERROR_FILE_INVALID_PATH;
3164 #endif
3165 ResolveAndStat();
3167 aResult = mResolvedPath;
3168 return NS_OK;
3172 /* attribute bool followLinks; */
3173 NS_IMETHODIMP
3174 nsLocalFile::GetFollowLinks(bool* aFollowLinks)
3176 *aFollowLinks = mFollowSymlinks;
3177 return NS_OK;
3179 NS_IMETHODIMP
3180 nsLocalFile::SetFollowLinks(bool aFollowLinks)
3182 MakeDirty();
3183 mFollowSymlinks = aFollowLinks;
3184 return NS_OK;
3188 NS_IMETHODIMP
3189 nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator** aEntries)
3191 nsresult rv;
3193 *aEntries = nullptr;
3194 if (mWorkingPath.EqualsLiteral("\\\\.")) {
3195 nsDriveEnumerator* drives = new nsDriveEnumerator;
3196 if (!drives) {
3197 return NS_ERROR_OUT_OF_MEMORY;
3199 NS_ADDREF(drives);
3200 rv = drives->Init();
3201 if (NS_FAILED(rv)) {
3202 NS_RELEASE(drives);
3203 return rv;
3205 *aEntries = drives;
3206 return NS_OK;
3209 nsRefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator();
3210 rv = dirEnum->Init(this);
3211 if (NS_FAILED(rv)) {
3212 return rv;
3215 dirEnum.forget(aEntries);
3217 return NS_OK;
3220 NS_IMETHODIMP
3221 nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor)
3223 CopyUTF16toUTF8(mWorkingPath, aPersistentDescriptor);
3224 return NS_OK;
3227 NS_IMETHODIMP
3228 nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor)
3230 if (IsUTF8(aPersistentDescriptor)) {
3231 return InitWithPath(NS_ConvertUTF8toUTF16(aPersistentDescriptor));
3232 } else {
3233 return InitWithNativePath(aPersistentDescriptor);
3237 /* attrib unsigned long fileAttributesWin; */
3238 NS_IMETHODIMP
3239 nsLocalFile::GetFileAttributesWin(uint32_t* aAttribs)
3241 *aAttribs = 0;
3242 DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get());
3243 if (dwAttrs == INVALID_FILE_ATTRIBUTES) {
3244 return NS_ERROR_FILE_INVALID_PATH;
3247 if (!(dwAttrs & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)) {
3248 *aAttribs |= WFA_SEARCH_INDEXED;
3251 return NS_OK;
3254 NS_IMETHODIMP
3255 nsLocalFile::SetFileAttributesWin(uint32_t aAttribs)
3257 DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get());
3258 if (dwAttrs == INVALID_FILE_ATTRIBUTES) {
3259 return NS_ERROR_FILE_INVALID_PATH;
3262 if (aAttribs & WFA_SEARCH_INDEXED) {
3263 dwAttrs &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
3264 } else {
3265 dwAttrs |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
3268 if (aAttribs & WFA_READONLY) {
3269 dwAttrs |= FILE_ATTRIBUTE_READONLY;
3270 } else if ((aAttribs & WFA_READWRITE) &&
3271 (dwAttrs & FILE_ATTRIBUTE_READONLY)) {
3272 dwAttrs &= ~FILE_ATTRIBUTE_READONLY;
3275 if (SetFileAttributesW(mWorkingPath.get(), dwAttrs) == 0) {
3276 return NS_ERROR_FAILURE;
3278 return NS_OK;
3282 NS_IMETHODIMP
3283 nsLocalFile::Reveal()
3285 // This API should be main thread only
3286 MOZ_ASSERT(NS_IsMainThread());
3288 // make sure mResolvedPath is set
3289 nsresult rv = Resolve();
3290 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
3291 return rv;
3294 // To create a new thread, get the thread manager
3295 nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID);
3296 nsCOMPtr<nsIThread> mythread;
3297 rv = tm->NewThread(0, 0, getter_AddRefs(mythread));
3298 if (NS_FAILED(rv)) {
3299 return rv;
3302 nsCOMPtr<nsIRunnable> runnable =
3303 new AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::RevealOp,
3304 mResolvedPath);
3306 // After the dispatch, the result runnable will shut down the worker
3307 // thread, so we can let it go.
3308 mythread->Dispatch(runnable, NS_DISPATCH_NORMAL);
3309 return NS_OK;
3312 NS_IMETHODIMP
3313 nsLocalFile::Launch()
3315 // This API should be main thread only
3316 MOZ_ASSERT(NS_IsMainThread());
3318 // make sure mResolvedPath is set
3319 nsresult rv = Resolve();
3320 if (NS_FAILED(rv)) {
3321 return rv;
3324 // To create a new thread, get the thread manager
3325 nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID);
3326 nsCOMPtr<nsIThread> mythread;
3327 rv = tm->NewThread(0, 0, getter_AddRefs(mythread));
3328 if (NS_FAILED(rv)) {
3329 return rv;
3332 nsCOMPtr<nsIRunnable> runnable =
3333 new AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::LaunchOp,
3334 mResolvedPath);
3336 // After the dispatch, the result runnable will shut down the worker
3337 // thread, so we can let it go.
3338 mythread->Dispatch(runnable, NS_DISPATCH_NORMAL);
3339 return NS_OK;
3342 nsresult
3343 NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks, nsIFile** aResult)
3345 nsLocalFile* file = new nsLocalFile();
3346 if (!file) {
3347 return NS_ERROR_OUT_OF_MEMORY;
3349 NS_ADDREF(file);
3351 file->SetFollowLinks(aFollowLinks);
3353 if (!aPath.IsEmpty()) {
3354 nsresult rv = file->InitWithPath(aPath);
3355 if (NS_FAILED(rv)) {
3356 NS_RELEASE(file);
3357 return rv;
3361 *aResult = file;
3362 return NS_OK;
3365 //-----------------------------------------------------------------------------
3366 // Native (lossy) interface
3367 //-----------------------------------------------------------------------------
3369 NS_IMETHODIMP
3370 nsLocalFile::InitWithNativePath(const nsACString& aFilePath)
3372 nsAutoString tmp;
3373 nsresult rv = NS_CopyNativeToUnicode(aFilePath, tmp);
3374 if (NS_SUCCEEDED(rv)) {
3375 return InitWithPath(tmp);
3378 return rv;
3381 NS_IMETHODIMP
3382 nsLocalFile::AppendNative(const nsACString& aNode)
3384 nsAutoString tmp;
3385 nsresult rv = NS_CopyNativeToUnicode(aNode, tmp);
3386 if (NS_SUCCEEDED(rv)) {
3387 return Append(tmp);
3390 return rv;
3393 NS_IMETHODIMP
3394 nsLocalFile::AppendRelativeNativePath(const nsACString& aNode)
3396 nsAutoString tmp;
3397 nsresult rv = NS_CopyNativeToUnicode(aNode, tmp);
3398 if (NS_SUCCEEDED(rv)) {
3399 return AppendRelativePath(tmp);
3401 return rv;
3405 NS_IMETHODIMP
3406 nsLocalFile::GetNativeLeafName(nsACString& aLeafName)
3408 //NS_WARNING("This API is lossy. Use GetLeafName !");
3409 nsAutoString tmp;
3410 nsresult rv = GetLeafName(tmp);
3411 if (NS_SUCCEEDED(rv)) {
3412 rv = NS_CopyUnicodeToNative(tmp, aLeafName);
3415 return rv;
3418 NS_IMETHODIMP
3419 nsLocalFile::SetNativeLeafName(const nsACString& aLeafName)
3421 nsAutoString tmp;
3422 nsresult rv = NS_CopyNativeToUnicode(aLeafName, tmp);
3423 if (NS_SUCCEEDED(rv)) {
3424 return SetLeafName(tmp);
3427 return rv;
3431 NS_IMETHODIMP
3432 nsLocalFile::GetNativePath(nsACString& aResult)
3434 //NS_WARNING("This API is lossy. Use GetPath !");
3435 nsAutoString tmp;
3436 nsresult rv = GetPath(tmp);
3437 if (NS_SUCCEEDED(rv)) {
3438 rv = NS_CopyUnicodeToNative(tmp, aResult);
3441 return rv;
3445 NS_IMETHODIMP
3446 nsLocalFile::GetNativeCanonicalPath(nsACString& aResult)
3448 NS_WARNING("This method is lossy. Use GetCanonicalPath !");
3449 EnsureShortPath();
3450 NS_CopyUnicodeToNative(mShortWorkingPath, aResult);
3451 return NS_OK;
3455 NS_IMETHODIMP
3456 nsLocalFile::CopyToNative(nsIFile* aNewParentDir, const nsACString& aNewName)
3458 // Check we are correctly initialized.
3459 CHECK_mWorkingPath();
3461 if (aNewName.IsEmpty()) {
3462 return CopyTo(aNewParentDir, EmptyString());
3465 nsAutoString tmp;
3466 nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
3467 if (NS_SUCCEEDED(rv)) {
3468 return CopyTo(aNewParentDir, tmp);
3471 return rv;
3474 NS_IMETHODIMP
3475 nsLocalFile::CopyToFollowingLinksNative(nsIFile* aNewParentDir,
3476 const nsACString& aNewName)
3478 if (aNewName.IsEmpty()) {
3479 return CopyToFollowingLinks(aNewParentDir, EmptyString());
3482 nsAutoString tmp;
3483 nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
3484 if (NS_SUCCEEDED(rv)) {
3485 return CopyToFollowingLinks(aNewParentDir, tmp);
3488 return rv;
3491 NS_IMETHODIMP
3492 nsLocalFile::MoveToNative(nsIFile* aNewParentDir, const nsACString& aNewName)
3494 // Check we are correctly initialized.
3495 CHECK_mWorkingPath();
3497 if (aNewName.IsEmpty()) {
3498 return MoveTo(aNewParentDir, EmptyString());
3501 nsAutoString tmp;
3502 nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
3503 if (NS_SUCCEEDED(rv)) {
3504 return MoveTo(aNewParentDir, tmp);
3507 return rv;
3510 NS_IMETHODIMP
3511 nsLocalFile::GetNativeTarget(nsACString& aResult)
3513 // Check we are correctly initialized.
3514 CHECK_mWorkingPath();
3516 NS_WARNING("This API is lossy. Use GetTarget !");
3517 nsAutoString tmp;
3518 nsresult rv = GetTarget(tmp);
3519 if (NS_SUCCEEDED(rv)) {
3520 rv = NS_CopyUnicodeToNative(tmp, aResult);
3523 return rv;
3526 nsresult
3527 NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowLinks,
3528 nsIFile** aResult)
3530 nsAutoString buf;
3531 nsresult rv = NS_CopyNativeToUnicode(aPath, buf);
3532 if (NS_FAILED(rv)) {
3533 *aResult = nullptr;
3534 return rv;
3536 return NS_NewLocalFile(buf, aFollowLinks, aResult);
3539 void
3540 nsLocalFile::EnsureShortPath()
3542 if (!mShortWorkingPath.IsEmpty()) {
3543 return;
3546 WCHAR shortPath[MAX_PATH + 1];
3547 DWORD lengthNeeded = ::GetShortPathNameW(mWorkingPath.get(), shortPath,
3548 ArrayLength(shortPath));
3549 // If an error occurred then lengthNeeded is set to 0 or the length of the
3550 // needed buffer including null termination. If it succeeds the number of
3551 // wide characters not including null termination is returned.
3552 if (lengthNeeded != 0 && lengthNeeded < ArrayLength(shortPath)) {
3553 mShortWorkingPath.Assign(shortPath);
3554 } else {
3555 mShortWorkingPath.Assign(mWorkingPath);
3559 // nsIHashable
3561 NS_IMETHODIMP
3562 nsLocalFile::Equals(nsIHashable* aOther, bool* aResult)
3564 nsCOMPtr<nsIFile> otherfile(do_QueryInterface(aOther));
3565 if (!otherfile) {
3566 *aResult = false;
3567 return NS_OK;
3570 return Equals(otherfile, aResult);
3573 NS_IMETHODIMP
3574 nsLocalFile::GetHashCode(uint32_t* aResult)
3576 // In order for short and long path names to hash to the same value we
3577 // always hash on the short pathname.
3578 EnsureShortPath();
3580 *aResult = HashString(mShortWorkingPath);
3581 return NS_OK;
3584 //-----------------------------------------------------------------------------
3585 // nsLocalFile <static members>
3586 //-----------------------------------------------------------------------------
3588 void
3589 nsLocalFile::GlobalInit()
3591 DebugOnly<nsresult> rv = NS_CreateShortcutResolver();
3592 NS_ASSERTION(NS_SUCCEEDED(rv), "Shortcut resolver could not be created");
3595 void
3596 nsLocalFile::GlobalShutdown()
3598 NS_DestroyShortcutResolver();
3601 NS_IMPL_ISUPPORTS(nsDriveEnumerator, nsISimpleEnumerator)
3603 nsDriveEnumerator::nsDriveEnumerator()
3607 nsDriveEnumerator::~nsDriveEnumerator()
3611 nsresult
3612 nsDriveEnumerator::Init()
3614 /* If the length passed to GetLogicalDriveStrings is smaller
3615 * than the length of the string it would return, it returns
3616 * the length required for the string. */
3617 DWORD length = GetLogicalDriveStringsW(0, 0);
3618 /* The string is null terminated */
3619 if (!mDrives.SetLength(length + 1, fallible_t())) {
3620 return NS_ERROR_OUT_OF_MEMORY;
3622 if (!GetLogicalDriveStringsW(length, wwc(mDrives.BeginWriting()))) {
3623 return NS_ERROR_FAILURE;
3625 mDrives.BeginReading(mStartOfCurrentDrive);
3626 mDrives.EndReading(mEndOfDrivesString);
3627 return NS_OK;
3630 NS_IMETHODIMP
3631 nsDriveEnumerator::HasMoreElements(bool* aHasMore)
3633 *aHasMore = *mStartOfCurrentDrive != L'\0';
3634 return NS_OK;
3637 NS_IMETHODIMP
3638 nsDriveEnumerator::GetNext(nsISupports** aNext)
3640 /* GetLogicalDrives stored in mDrives is a concatenation
3641 * of null terminated strings, followed by a null terminator.
3642 * mStartOfCurrentDrive is an iterator pointing at the first
3643 * character of the current drive. */
3644 if (*mStartOfCurrentDrive == L'\0') {
3645 *aNext = nullptr;
3646 return NS_OK;
3649 nsAString::const_iterator driveEnd = mStartOfCurrentDrive;
3650 FindCharInReadable(L'\0', driveEnd, mEndOfDrivesString);
3651 nsString drive(Substring(mStartOfCurrentDrive, driveEnd));
3652 mStartOfCurrentDrive = ++driveEnd;
3654 nsIFile* file;
3655 nsresult rv = NS_NewLocalFile(drive, false, &file);
3657 *aNext = file;
3658 return rv;