Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/ArrayUtils.h"
7 #include "mozilla/DebugOnly.h"
8 #include "mozilla/WindowsVersion.h"
10 #include "nsCOMPtr.h"
11 #include "nsAutoPtr.h"
12 #include "nsMemory.h"
14 #include "nsLocalFile.h"
15 #include "nsIDirectoryEnumerator.h"
16 #include "nsNativeCharsetUtils.h"
18 #include "nsISimpleEnumerator.h"
19 #include "nsIComponentManager.h"
20 #include "prio.h"
21 #include "private/pprio.h" // To get PR_ImportFile
22 #include "prprf.h"
23 #include "prmem.h"
24 #include "nsHashKeys.h"
26 #include "nsXPIDLString.h"
27 #include "nsReadableUtils.h"
29 #include <direct.h>
30 #include <windows.h>
31 #include <shlwapi.h>
32 #include <aclapi.h>
34 #include "shellapi.h"
35 #include "shlguid.h"
37 #include <io.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <mbstring.h>
42 #include "nsXPIDLString.h"
43 #include "prproces.h"
44 #include "prlink.h"
46 #include "mozilla/Mutex.h"
47 #include "SpecialSystemDirectory.h"
49 #include "nsTraceRefcnt.h"
50 #include "nsXPCOMCIDInternal.h"
51 #include "nsThreadUtils.h"
52 #include "nsXULAppAPI.h"
54 using namespace mozilla;
56 #define CHECK_mWorkingPath() \
57 PR_BEGIN_MACRO \
58 if (mWorkingPath.IsEmpty()) \
59 return NS_ERROR_NOT_INITIALIZED; \
60 PR_END_MACRO
62 // CopyFileEx only supports unbuffered I/O in Windows Vista and above
63 #ifndef COPY_FILE_NO_BUFFERING
64 #define COPY_FILE_NO_BUFFERING 0x00001000
65 #endif
67 #ifndef FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
68 #define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000
69 #endif
71 #ifndef DRIVE_REMOTE
72 #define DRIVE_REMOTE 4
73 #endif
75 /**
76 * A runnable to dispatch back to the main thread when
77 * AsyncLocalFileWinOperation completes.
78 */
79 class AsyncLocalFileWinDone : public nsRunnable
80 {
81 public:
82 AsyncLocalFileWinDone() :
83 mWorkerThread(do_GetCurrentThread())
84 {
85 // Objects of this type must only be created on worker threads
86 MOZ_ASSERT(!NS_IsMainThread());
87 }
89 NS_IMETHOD Run() {
90 // This event shuts down the worker thread and so must be main thread.
91 MOZ_ASSERT(NS_IsMainThread());
93 // If we don't destroy the thread when we're done with it, it will hang
94 // around forever... and that is bad!
95 mWorkerThread->Shutdown();
96 return NS_OK;
97 }
99 private:
100 nsCOMPtr<nsIThread> mWorkerThread;
101 };
103 /**
104 * A runnable to dispatch from the main thread when an async operation should
105 * be performed.
106 */
107 class AsyncLocalFileWinOperation : public nsRunnable
108 {
109 public:
110 enum FileOp { RevealOp, LaunchOp };
112 AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::FileOp aOperation,
113 const nsAString &aResolvedPath) :
114 mOperation(aOperation),
115 mResolvedPath(aResolvedPath)
116 {
117 }
119 NS_IMETHOD Run() {
120 MOZ_ASSERT(!NS_IsMainThread(),
121 "AsyncLocalFileWinOperation should not be run on the main thread!");
123 CoInitialize(nullptr);
124 switch(mOperation) {
125 case RevealOp: {
126 Reveal();
127 }
128 break;
129 case LaunchOp: {
130 Launch();
131 }
132 break;
133 }
134 CoUninitialize();
136 // Send the result back to the main thread so that it can shutdown
137 nsCOMPtr<nsIRunnable> resultrunnable = new AsyncLocalFileWinDone();
138 NS_DispatchToMainThread(resultrunnable);
139 return NS_OK;
140 }
142 private:
143 // Reveals the path in explorer.
144 nsresult Reveal()
145 {
146 DWORD attributes = GetFileAttributesW(mResolvedPath.get());
147 if (INVALID_FILE_ATTRIBUTES == attributes) {
148 return NS_ERROR_FILE_INVALID_PATH;
149 }
151 HRESULT hr;
152 if (attributes & FILE_ATTRIBUTE_DIRECTORY) {
153 // We have a directory so we should open the directory itself.
154 ITEMIDLIST *dir = ILCreateFromPathW(mResolvedPath.get());
155 if (!dir) {
156 return NS_ERROR_FAILURE;
157 }
159 const ITEMIDLIST* selection[] = { dir };
160 UINT count = ArrayLength(selection);
162 //Perform the open of the directory.
163 hr = SHOpenFolderAndSelectItems(dir, count, selection, 0);
164 CoTaskMemFree(dir);
165 } else {
166 int32_t len = mResolvedPath.Length();
167 // We don't currently handle UNC long paths of the form \\?\ anywhere so
168 // this should be fine.
169 if (len > MAX_PATH) {
170 return NS_ERROR_FILE_INVALID_PATH;
171 }
172 WCHAR parentDirectoryPath[MAX_PATH + 1] = { 0 };
173 wcsncpy(parentDirectoryPath, mResolvedPath.get(), MAX_PATH);
174 PathRemoveFileSpecW(parentDirectoryPath);
176 // We have a file so we should open the parent directory.
177 ITEMIDLIST *dir = ILCreateFromPathW(parentDirectoryPath);
178 if (!dir) {
179 return NS_ERROR_FAILURE;
180 }
182 // Set the item in the directory to select to the file we want to reveal.
183 ITEMIDLIST *item = ILCreateFromPathW(mResolvedPath.get());
184 if (!item) {
185 CoTaskMemFree(dir);
186 return NS_ERROR_FAILURE;
187 }
189 const ITEMIDLIST* selection[] = { item };
190 UINT count = ArrayLength(selection);
192 //Perform the selection of the file.
193 hr = SHOpenFolderAndSelectItems(dir, count, selection, 0);
195 CoTaskMemFree(dir);
196 CoTaskMemFree(item);
197 }
199 return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
200 }
202 // Launches the default shell operation for the file path
203 nsresult Launch()
204 {
205 // use the app registry name to launch a shell execute....
206 SHELLEXECUTEINFOW seinfo;
207 memset(&seinfo, 0, sizeof(seinfo));
208 seinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
209 if (XRE_GetWindowsEnvironment() == WindowsEnvironmentType_Metro) {
210 seinfo.fMask = SEE_MASK_FLAG_LOG_USAGE;
211 }
212 seinfo.hwnd = nullptr;
213 seinfo.lpVerb = nullptr;
214 seinfo.lpFile = mResolvedPath.get();
215 seinfo.lpParameters = nullptr;
216 seinfo.lpDirectory = nullptr;
217 seinfo.nShow = SW_SHOWNORMAL;
219 // Use the directory of the file we're launching as the working
220 // directory. That way if we have a self extracting EXE it won't
221 // suggest to extract to the install directory.
222 WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' };
223 wcsncpy(workingDirectory, mResolvedPath.get(), MAX_PATH);
224 if (PathRemoveFileSpecW(workingDirectory)) {
225 seinfo.lpDirectory = workingDirectory;
226 } else {
227 NS_WARNING("Could not set working directory for launched file.");
228 }
230 if (ShellExecuteExW(&seinfo)) {
231 return NS_OK;
232 }
233 DWORD r = GetLastError();
234 // if the file has no association, we launch windows'
235 // "what do you want to do" dialog
236 if (r == SE_ERR_NOASSOC) {
237 nsAutoString shellArg;
238 shellArg.Assign(NS_LITERAL_STRING("shell32.dll,OpenAs_RunDLL ") +
239 mResolvedPath);
240 seinfo.lpFile = L"RUNDLL32.EXE";
241 seinfo.lpParameters = shellArg.get();
242 if (ShellExecuteExW(&seinfo))
243 return NS_OK;
244 r = GetLastError();
245 }
246 if (r < 32) {
247 switch (r) {
248 case 0:
249 case SE_ERR_OOM:
250 return NS_ERROR_OUT_OF_MEMORY;
251 case ERROR_FILE_NOT_FOUND:
252 return NS_ERROR_FILE_NOT_FOUND;
253 case ERROR_PATH_NOT_FOUND:
254 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
255 case ERROR_BAD_FORMAT:
256 return NS_ERROR_FILE_CORRUPTED;
257 case SE_ERR_ACCESSDENIED:
258 return NS_ERROR_FILE_ACCESS_DENIED;
259 case SE_ERR_ASSOCINCOMPLETE:
260 case SE_ERR_NOASSOC:
261 return NS_ERROR_UNEXPECTED;
262 case SE_ERR_DDEBUSY:
263 case SE_ERR_DDEFAIL:
264 case SE_ERR_DDETIMEOUT:
265 return NS_ERROR_NOT_AVAILABLE;
266 case SE_ERR_DLLNOTFOUND:
267 return NS_ERROR_FAILURE;
268 case SE_ERR_SHARE:
269 return NS_ERROR_FILE_IS_LOCKED;
270 default:
271 return NS_ERROR_FILE_EXECUTION_FAILED;
272 }
273 }
274 return NS_OK;
275 }
277 // Stores the operation that will be performed on the thread
278 AsyncLocalFileWinOperation::FileOp mOperation;
280 // Stores the path to perform the operation on
281 nsString mResolvedPath;
282 };
284 class nsDriveEnumerator : public nsISimpleEnumerator
285 {
286 public:
287 nsDriveEnumerator();
288 virtual ~nsDriveEnumerator();
289 NS_DECL_ISUPPORTS
290 NS_DECL_NSISIMPLEENUMERATOR
291 nsresult Init();
292 private:
293 /* mDrives stores the null-separated drive names.
294 * Init sets them.
295 * HasMoreElements checks mStartOfCurrentDrive.
296 * GetNext advances mStartOfCurrentDrive.
297 */
298 nsString mDrives;
299 nsAString::const_iterator mStartOfCurrentDrive;
300 nsAString::const_iterator mEndOfDrivesString;
301 };
303 //----------------------------------------------------------------------------
304 // short cut resolver
305 //----------------------------------------------------------------------------
306 class ShortcutResolver
307 {
308 public:
309 ShortcutResolver();
310 // nonvirtual since we're not subclassed
311 ~ShortcutResolver();
313 nsresult Init();
314 nsresult Resolve(const WCHAR* in, WCHAR* out);
315 nsresult SetShortcut(bool updateExisting,
316 const WCHAR* shortcutPath,
317 const WCHAR* targetPath,
318 const WCHAR* workingDir,
319 const WCHAR* args,
320 const WCHAR* description,
321 const WCHAR* iconFile,
322 int32_t iconIndex);
324 private:
325 Mutex mLock;
326 nsRefPtr<IPersistFile> mPersistFile;
327 nsRefPtr<IShellLinkW> mShellLink;
328 };
330 ShortcutResolver::ShortcutResolver() :
331 mLock("ShortcutResolver.mLock")
332 {
333 CoInitialize(nullptr);
334 }
336 ShortcutResolver::~ShortcutResolver()
337 {
338 CoUninitialize();
339 }
341 nsresult
342 ShortcutResolver::Init()
343 {
344 // Get a pointer to the IPersistFile interface.
345 if (FAILED(CoCreateInstance(CLSID_ShellLink,
346 nullptr,
347 CLSCTX_INPROC_SERVER,
348 IID_IShellLinkW,
349 getter_AddRefs(mShellLink))) ||
350 FAILED(mShellLink->QueryInterface(IID_IPersistFile,
351 getter_AddRefs(mPersistFile)))) {
352 mShellLink = nullptr;
353 return NS_ERROR_FAILURE;
354 }
355 return NS_OK;
356 }
358 // |out| must be an allocated buffer of size MAX_PATH
359 nsresult
360 ShortcutResolver::Resolve(const WCHAR* in, WCHAR* out)
361 {
362 if (!mShellLink)
363 return NS_ERROR_FAILURE;
365 MutexAutoLock lock(mLock);
367 if (FAILED(mPersistFile->Load(in, STGM_READ)) ||
368 FAILED(mShellLink->Resolve(nullptr, SLR_NO_UI)) ||
369 FAILED(mShellLink->GetPath(out, MAX_PATH, nullptr, SLGP_UNCPRIORITY)))
370 return NS_ERROR_FAILURE;
371 return NS_OK;
372 }
374 nsresult
375 ShortcutResolver::SetShortcut(bool updateExisting,
376 const WCHAR* shortcutPath,
377 const WCHAR* targetPath,
378 const WCHAR* workingDir,
379 const WCHAR* args,
380 const WCHAR* description,
381 const WCHAR* iconPath,
382 int32_t iconIndex)
383 {
384 if (!mShellLink) {
385 return NS_ERROR_FAILURE;
386 }
388 if (!shortcutPath) {
389 return NS_ERROR_FAILURE;
390 }
392 MutexAutoLock lock(mLock);
394 if (updateExisting) {
395 if (FAILED(mPersistFile->Load(shortcutPath, STGM_READWRITE))) {
396 return NS_ERROR_FAILURE;
397 }
398 } else {
399 if (!targetPath) {
400 return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
401 }
403 // Since we reuse our IPersistFile, we have to clear out any values that
404 // may be left over from previous calls to SetShortcut.
405 if (FAILED(mShellLink->SetWorkingDirectory(L""))
406 || FAILED(mShellLink->SetArguments(L""))
407 || FAILED(mShellLink->SetDescription(L""))
408 || FAILED(mShellLink->SetIconLocation(L"", 0))) {
409 return NS_ERROR_FAILURE;
410 }
411 }
413 if (targetPath && FAILED(mShellLink->SetPath(targetPath))) {
414 return NS_ERROR_FAILURE;
415 }
417 if (workingDir && FAILED(mShellLink->SetWorkingDirectory(workingDir))) {
418 return NS_ERROR_FAILURE;
419 }
421 if (args && FAILED(mShellLink->SetArguments(args))) {
422 return NS_ERROR_FAILURE;
423 }
425 if (description && FAILED(mShellLink->SetDescription(description))) {
426 return NS_ERROR_FAILURE;
427 }
429 if (iconPath && FAILED(mShellLink->SetIconLocation(iconPath, iconIndex))) {
430 return NS_ERROR_FAILURE;
431 }
433 if (FAILED(mPersistFile->Save(shortcutPath,
434 TRUE))) {
435 // Second argument indicates whether the file path specified in the
436 // first argument should become the "current working file" for this
437 // IPersistFile
438 return NS_ERROR_FAILURE;
439 }
441 return NS_OK;
442 }
444 static ShortcutResolver * gResolver = nullptr;
446 static nsresult NS_CreateShortcutResolver()
447 {
448 gResolver = new ShortcutResolver();
449 if (!gResolver)
450 return NS_ERROR_OUT_OF_MEMORY;
452 return gResolver->Init();
453 }
455 static void NS_DestroyShortcutResolver()
456 {
457 delete gResolver;
458 gResolver = nullptr;
459 }
462 //-----------------------------------------------------------------------------
463 // static helper functions
464 //-----------------------------------------------------------------------------
466 // certainly not all the error that can be
467 // encountered, but many of them common ones
468 static nsresult ConvertWinError(DWORD winErr)
469 {
470 nsresult rv;
472 switch (winErr)
473 {
474 case ERROR_FILE_NOT_FOUND:
475 case ERROR_PATH_NOT_FOUND:
476 case ERROR_INVALID_DRIVE:
477 rv = NS_ERROR_FILE_NOT_FOUND;
478 break;
479 case ERROR_ACCESS_DENIED:
480 case ERROR_NOT_SAME_DEVICE:
481 rv = NS_ERROR_FILE_ACCESS_DENIED;
482 break;
483 case ERROR_SHARING_VIOLATION: // CreateFile without sharing flags
484 case ERROR_LOCK_VIOLATION: // LockFile, LockFileEx
485 rv = NS_ERROR_FILE_IS_LOCKED;
486 break;
487 case ERROR_NOT_ENOUGH_MEMORY:
488 case ERROR_INVALID_BLOCK:
489 case ERROR_INVALID_HANDLE:
490 case ERROR_ARENA_TRASHED:
491 rv = NS_ERROR_OUT_OF_MEMORY;
492 break;
493 case ERROR_CURRENT_DIRECTORY:
494 rv = NS_ERROR_FILE_DIR_NOT_EMPTY;
495 break;
496 case ERROR_WRITE_PROTECT:
497 rv = NS_ERROR_FILE_READ_ONLY;
498 break;
499 case ERROR_HANDLE_DISK_FULL:
500 rv = NS_ERROR_FILE_TOO_BIG;
501 break;
502 case ERROR_FILE_EXISTS:
503 case ERROR_ALREADY_EXISTS:
504 case ERROR_CANNOT_MAKE:
505 rv = NS_ERROR_FILE_ALREADY_EXISTS;
506 break;
507 case ERROR_FILENAME_EXCED_RANGE:
508 rv = NS_ERROR_FILE_NAME_TOO_LONG;
509 break;
510 case ERROR_DIRECTORY:
511 rv = NS_ERROR_FILE_NOT_DIRECTORY;
512 break;
513 case 0:
514 rv = NS_OK;
515 break;
516 default:
517 rv = NS_ERROR_FAILURE;
518 break;
519 }
520 return rv;
521 }
523 // as suggested in the MSDN documentation on SetFilePointer
524 static __int64
525 MyFileSeek64(HANDLE aHandle, __int64 aDistance, DWORD aMoveMethod)
526 {
527 LARGE_INTEGER li;
529 li.QuadPart = aDistance;
530 li.LowPart = SetFilePointer(aHandle, li.LowPart, &li.HighPart, aMoveMethod);
531 if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
532 {
533 li.QuadPart = -1;
534 }
536 return li.QuadPart;
537 }
539 static bool
540 IsShortcutPath(const nsAString &path)
541 {
542 // Under Windows, the shortcuts are just files with a ".lnk" extension.
543 // Note also that we don't resolve links in the middle of paths.
544 // i.e. "c:\foo.lnk\bar.txt" is invalid.
545 NS_ABORT_IF_FALSE(!path.IsEmpty(), "don't pass an empty string");
546 int32_t len = path.Length();
547 return len >= 4 && (StringTail(path, 4).LowerCaseEqualsASCII(".lnk"));
548 }
550 //-----------------------------------------------------------------------------
551 // We need the following three definitions to make |OpenFile| convert a file
552 // handle to an NSPR file descriptor correctly when |O_APPEND| flag is
553 // specified. It is defined in a private header of NSPR (primpl.h) we can't
554 // include. As a temporary workaround until we decide how to extend
555 // |PR_ImportFile|, we define it here. Currently, |_PR_HAVE_PEEK_BUFFER|
556 // and |PR_STRICT_ADDR_LEN| are not defined for the 'w95'-dependent portion
557 // of NSPR so that fields of |PRFilePrivate| #ifdef'd by them are not copied.
558 // Similarly, |_MDFileDesc| is taken from nsprpub/pr/include/md/_win95.h.
559 // In an unlikely case we switch to 'NT'-dependent NSPR AND this temporary
560 // workaround last beyond the switch, |PRFilePrivate| and |_MDFileDesc|
561 // need to be changed to match the definitions for WinNT.
562 //-----------------------------------------------------------------------------
563 typedef enum {
564 _PR_TRI_TRUE = 1,
565 _PR_TRI_FALSE = 0,
566 _PR_TRI_UNKNOWN = -1
567 } _PRTriStateBool;
569 struct _MDFileDesc {
570 PROsfd osfd;
571 };
573 struct PRFilePrivate {
574 int32_t state;
575 bool nonblocking;
576 _PRTriStateBool inheritable;
577 PRFileDesc *next;
578 int lockCount; /* 0: not locked
579 * -1: a native lockfile call is in progress
580 * > 0: # times the file is locked */
581 bool appendMode;
582 _MDFileDesc md;
583 };
585 //-----------------------------------------------------------------------------
586 // Six static methods defined below (OpenFile, FileTimeToPRTime, GetFileInfo,
587 // OpenDir, CloseDir, ReadDir) should go away once the corresponding
588 // UTF-16 APIs are implemented on all the supported platforms (or at least
589 // Windows 9x/ME) in NSPR. Currently, they're only implemented on
590 // Windows NT4 or later. (bug 330665)
591 //-----------------------------------------------------------------------------
593 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} :
594 // PR_Open and _PR_MD_OPEN
595 nsresult
596 OpenFile(const nsAFlatString &name, int osflags, int mode,
597 PRFileDesc **fd)
598 {
599 int32_t access = 0;
601 int32_t disposition = 0;
602 int32_t attributes = 0;
604 if (osflags & PR_SYNC)
605 attributes = FILE_FLAG_WRITE_THROUGH;
606 if (osflags & PR_RDONLY || osflags & PR_RDWR)
607 access |= GENERIC_READ;
608 if (osflags & PR_WRONLY || osflags & PR_RDWR)
609 access |= GENERIC_WRITE;
611 if ( osflags & PR_CREATE_FILE && osflags & PR_EXCL )
612 disposition = CREATE_NEW;
613 else if (osflags & PR_CREATE_FILE) {
614 if (osflags & PR_TRUNCATE)
615 disposition = CREATE_ALWAYS;
616 else
617 disposition = OPEN_ALWAYS;
618 } else {
619 if (osflags & PR_TRUNCATE)
620 disposition = TRUNCATE_EXISTING;
621 else
622 disposition = OPEN_EXISTING;
623 }
625 if (osflags & nsIFile::DELETE_ON_CLOSE) {
626 attributes |= FILE_FLAG_DELETE_ON_CLOSE;
627 }
629 if (osflags & nsIFile::OS_READAHEAD) {
630 attributes |= FILE_FLAG_SEQUENTIAL_SCAN;
631 }
633 // If no write permissions are requested, and if we are possibly creating
634 // the file, then set the new file as read only.
635 // The flag has no effect if we happen to open the file.
636 if (!(mode & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) &&
637 disposition != OPEN_EXISTING) {
638 attributes |= FILE_ATTRIBUTE_READONLY;
639 }
641 HANDLE file = ::CreateFileW(name.get(), access,
642 FILE_SHARE_READ|FILE_SHARE_WRITE,
643 nullptr, disposition, attributes, nullptr);
645 if (file == INVALID_HANDLE_VALUE) {
646 *fd = nullptr;
647 return ConvertWinError(GetLastError());
648 }
650 *fd = PR_ImportFile((PROsfd) file);
651 if (*fd) {
652 // On Windows, _PR_HAVE_O_APPEND is not defined so that we have to
653 // add it manually. (see |PR_Open| in nsprpub/pr/src/io/prfile.c)
654 (*fd)->secret->appendMode = (PR_APPEND & osflags) ? true : false;
655 return NS_OK;
656 }
658 nsresult rv = NS_ErrorAccordingToNSPR();
660 CloseHandle(file);
662 return rv;
663 }
665 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} :
666 // PR_FileTimeToPRTime and _PR_FileTimeToPRTime
667 static
668 void FileTimeToPRTime(const FILETIME *filetime, PRTime *prtm)
669 {
670 #ifdef __GNUC__
671 const PRTime _pr_filetime_offset = 116444736000000000LL;
672 #else
673 const PRTime _pr_filetime_offset = 116444736000000000i64;
674 #endif
676 PR_ASSERT(sizeof(FILETIME) == sizeof(PRTime));
677 ::CopyMemory(prtm, filetime, sizeof(PRTime));
678 #ifdef __GNUC__
679 *prtm = (*prtm - _pr_filetime_offset) / 10LL;
680 #else
681 *prtm = (*prtm - _pr_filetime_offset) / 10i64;
682 #endif
683 }
685 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} with some
686 // changes : PR_GetFileInfo64, _PR_MD_GETFILEINFO64
687 static nsresult
688 GetFileInfo(const nsAFlatString &name, PRFileInfo64 *info)
689 {
690 WIN32_FILE_ATTRIBUTE_DATA fileData;
692 if (name.IsEmpty() || name.FindCharInSet(MOZ_UTF16("?*")) != kNotFound)
693 return NS_ERROR_INVALID_ARG;
695 if (!::GetFileAttributesExW(name.get(), GetFileExInfoStandard, &fileData))
696 return ConvertWinError(GetLastError());
698 if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
699 info->type = PR_FILE_DIRECTORY;
700 } else {
701 info->type = PR_FILE_FILE;
702 }
704 info->size = fileData.nFileSizeHigh;
705 info->size = (info->size << 32) + fileData.nFileSizeLow;
707 FileTimeToPRTime(&fileData.ftLastWriteTime, &info->modifyTime);
709 if (0 == fileData.ftCreationTime.dwLowDateTime &&
710 0 == fileData.ftCreationTime.dwHighDateTime) {
711 info->creationTime = info->modifyTime;
712 } else {
713 FileTimeToPRTime(&fileData.ftCreationTime, &info->creationTime);
714 }
716 return NS_OK;
717 }
719 struct nsDir
720 {
721 HANDLE handle;
722 WIN32_FIND_DATAW data;
723 bool firstEntry;
724 };
726 static nsresult
727 OpenDir(const nsAFlatString &name, nsDir * *dir)
728 {
729 if (NS_WARN_IF(!dir))
730 return NS_ERROR_INVALID_ARG;
732 *dir = nullptr;
733 if (name.Length() + 3 >= MAX_PATH)
734 return NS_ERROR_FILE_NAME_TOO_LONG;
736 nsDir *d = PR_NEW(nsDir);
737 if (!d)
738 return NS_ERROR_OUT_OF_MEMORY;
740 nsAutoString filename(name);
742 //If 'name' ends in a slash or backslash, do not append
743 //another backslash.
744 if (filename.Last() == L'/' || filename.Last() == L'\\')
745 filename.Append('*');
746 else
747 filename.AppendLiteral("\\*");
749 filename.ReplaceChar(L'/', L'\\');
751 // FindFirstFileW Will have a last error of ERROR_DIRECTORY if
752 // <file_path>\* is passed in. If <unknown_path>\* is passed in then
753 // ERROR_PATH_NOT_FOUND will be the last error.
754 d->handle = ::FindFirstFileW(filename.get(), &(d->data) );
756 if (d->handle == INVALID_HANDLE_VALUE) {
757 PR_Free(d);
758 return ConvertWinError(GetLastError());
759 }
760 d->firstEntry = true;
762 *dir = d;
763 return NS_OK;
764 }
766 static nsresult
767 ReadDir(nsDir *dir, PRDirFlags flags, nsString& name)
768 {
769 name.Truncate();
770 if (NS_WARN_IF(!dir))
771 return NS_ERROR_INVALID_ARG;
773 while (1) {
774 BOOL rv;
775 if (dir->firstEntry)
776 {
777 dir->firstEntry = false;
778 rv = 1;
779 } else
780 rv = ::FindNextFileW(dir->handle, &(dir->data));
782 if (rv == 0)
783 break;
785 const wchar_t *fileName;
786 nsString tmp;
787 fileName = (dir)->data.cFileName;
789 if ((flags & PR_SKIP_DOT) &&
790 (fileName[0] == L'.') && (fileName[1] == L'\0'))
791 continue;
792 if ((flags & PR_SKIP_DOT_DOT) &&
793 (fileName[0] == L'.') && (fileName[1] == L'.') &&
794 (fileName[2] == L'\0'))
795 continue;
797 DWORD attrib = dir->data.dwFileAttributes;
798 if ((flags & PR_SKIP_HIDDEN) && (attrib & FILE_ATTRIBUTE_HIDDEN))
799 continue;
801 if (fileName == tmp.get())
802 name = tmp;
803 else
804 name = fileName;
805 return NS_OK;
806 }
808 DWORD err = GetLastError();
809 return err == ERROR_NO_MORE_FILES ? NS_OK : ConvertWinError(err);
810 }
812 static nsresult
813 CloseDir(nsDir *&d)
814 {
815 if (NS_WARN_IF(!d))
816 return NS_ERROR_INVALID_ARG;
818 BOOL isOk = FindClose(d->handle);
819 // PR_DELETE also nulls out the passed in pointer.
820 PR_DELETE(d);
821 return isOk ? NS_OK : ConvertWinError(GetLastError());
822 }
824 //-----------------------------------------------------------------------------
825 // nsDirEnumerator
826 //-----------------------------------------------------------------------------
828 class nsDirEnumerator MOZ_FINAL : public nsISimpleEnumerator,
829 public nsIDirectoryEnumerator
830 {
831 public:
833 NS_DECL_ISUPPORTS
835 nsDirEnumerator() : mDir(nullptr)
836 {
837 }
839 nsresult Init(nsIFile* parent)
840 {
841 nsAutoString filepath;
842 parent->GetTarget(filepath);
844 if (filepath.IsEmpty())
845 {
846 parent->GetPath(filepath);
847 }
849 if (filepath.IsEmpty())
850 {
851 return NS_ERROR_UNEXPECTED;
852 }
854 // IsDirectory is not needed here because OpenDir will return
855 // NS_ERROR_FILE_NOT_DIRECTORY if the passed in path is a file.
856 nsresult rv = OpenDir(filepath, &mDir);
857 if (NS_FAILED(rv))
858 return rv;
860 mParent = parent;
861 return NS_OK;
862 }
864 NS_IMETHOD HasMoreElements(bool *result)
865 {
866 nsresult rv;
867 if (mNext == nullptr && mDir)
868 {
869 nsString name;
870 rv = ReadDir(mDir, PR_SKIP_BOTH, name);
871 if (NS_FAILED(rv))
872 return rv;
873 if (name.IsEmpty())
874 {
875 // end of dir entries
876 if (NS_FAILED(CloseDir(mDir)))
877 return NS_ERROR_FAILURE;
879 *result = false;
880 return NS_OK;
881 }
883 nsCOMPtr<nsIFile> file;
884 rv = mParent->Clone(getter_AddRefs(file));
885 if (NS_FAILED(rv))
886 return rv;
888 rv = file->Append(name);
889 if (NS_FAILED(rv))
890 return rv;
892 mNext = do_QueryInterface(file);
893 }
894 *result = mNext != nullptr;
895 if (!*result)
896 Close();
897 return NS_OK;
898 }
900 NS_IMETHOD GetNext(nsISupports **result)
901 {
902 nsresult rv;
903 bool hasMore;
904 rv = HasMoreElements(&hasMore);
905 if (NS_FAILED(rv)) return rv;
907 *result = mNext; // might return nullptr
908 NS_IF_ADDREF(*result);
910 mNext = nullptr;
911 return NS_OK;
912 }
914 NS_IMETHOD GetNextFile(nsIFile **result)
915 {
916 *result = nullptr;
917 bool hasMore = false;
918 nsresult rv = HasMoreElements(&hasMore);
919 if (NS_FAILED(rv) || !hasMore)
920 return rv;
921 *result = mNext;
922 NS_IF_ADDREF(*result);
923 mNext = nullptr;
924 return NS_OK;
925 }
927 NS_IMETHOD Close()
928 {
929 if (mDir)
930 {
931 nsresult rv = CloseDir(mDir);
932 NS_ASSERTION(NS_SUCCEEDED(rv), "close failed");
933 if (NS_FAILED(rv))
934 return NS_ERROR_FAILURE;
935 }
936 return NS_OK;
937 }
939 // dtor can be non-virtual since there are no subclasses, but must be
940 // public to use the class on the stack.
941 ~nsDirEnumerator()
942 {
943 Close();
944 }
946 protected:
947 nsDir* mDir;
948 nsCOMPtr<nsIFile> mParent;
949 nsCOMPtr<nsIFile> mNext;
950 };
952 NS_IMPL_ISUPPORTS(nsDirEnumerator, nsISimpleEnumerator, nsIDirectoryEnumerator)
955 //-----------------------------------------------------------------------------
956 // nsLocalFile <public>
957 //-----------------------------------------------------------------------------
959 nsLocalFile::nsLocalFile()
960 : mDirty(true)
961 , mResolveDirty(true)
962 , mFollowSymlinks(false)
963 {
964 }
966 nsresult
967 nsLocalFile::nsLocalFileConstructor(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr)
968 {
969 if (NS_WARN_IF(!aInstancePtr))
970 return NS_ERROR_INVALID_ARG;
971 if (NS_WARN_IF(outer))
972 return NS_ERROR_NO_AGGREGATION;
974 nsLocalFile* inst = new nsLocalFile();
975 if (inst == nullptr)
976 return NS_ERROR_OUT_OF_MEMORY;
978 nsresult rv = inst->QueryInterface(aIID, aInstancePtr);
979 if (NS_FAILED(rv))
980 {
981 delete inst;
982 return rv;
983 }
984 return NS_OK;
985 }
988 //-----------------------------------------------------------------------------
989 // nsLocalFile::nsISupports
990 //-----------------------------------------------------------------------------
992 NS_IMPL_ISUPPORTS(nsLocalFile,
993 nsILocalFile,
994 nsIFile,
995 nsILocalFileWin,
996 nsIHashable)
999 //-----------------------------------------------------------------------------
1000 // nsLocalFile <private>
1001 //-----------------------------------------------------------------------------
1003 nsLocalFile::nsLocalFile(const nsLocalFile& other)
1004 : mDirty(true)
1005 , mResolveDirty(true)
1006 , mFollowSymlinks(other.mFollowSymlinks)
1007 , mWorkingPath(other.mWorkingPath)
1008 {
1009 }
1011 // Resolve the shortcut file from mWorkingPath and write the path
1012 // it points to into mResolvedPath.
1013 nsresult
1014 nsLocalFile::ResolveShortcut()
1015 {
1016 // we can't do anything without the resolver
1017 if (!gResolver)
1018 return NS_ERROR_FAILURE;
1020 mResolvedPath.SetLength(MAX_PATH);
1021 if (mResolvedPath.Length() != MAX_PATH)
1022 return NS_ERROR_OUT_OF_MEMORY;
1024 wchar_t *resolvedPath = wwc(mResolvedPath.BeginWriting());
1026 // resolve this shortcut
1027 nsresult rv = gResolver->Resolve(mWorkingPath.get(), resolvedPath);
1029 size_t len = NS_FAILED(rv) ? 0 : wcslen(resolvedPath);
1030 mResolvedPath.SetLength(len);
1032 return rv;
1033 }
1035 // Resolve any shortcuts and stat the resolved path. After a successful return
1036 // the path is guaranteed valid and the members of mFileInfo64 can be used.
1037 nsresult
1038 nsLocalFile::ResolveAndStat()
1039 {
1040 // if we aren't dirty then we are already done
1041 if (!mDirty)
1042 return NS_OK;
1044 // we can't resolve/stat anything that isn't a valid NSPR addressable path
1045 if (mWorkingPath.IsEmpty())
1046 return NS_ERROR_FILE_INVALID_PATH;
1048 // this is usually correct
1049 mResolvedPath.Assign(mWorkingPath);
1051 // slutty hack designed to work around bug 134796 until it is fixed
1052 nsAutoString nsprPath(mWorkingPath.get());
1053 if (mWorkingPath.Length() == 2 && mWorkingPath.CharAt(1) == L':')
1054 nsprPath.Append('\\');
1056 // first we will see if the working path exists. If it doesn't then
1057 // there is nothing more that can be done
1058 nsresult rv = GetFileInfo(nsprPath, &mFileInfo64);
1059 if (NS_FAILED(rv))
1060 return rv;
1062 // if this isn't a shortcut file or we aren't following symlinks then we're done
1063 if (!mFollowSymlinks
1064 || mFileInfo64.type != PR_FILE_FILE
1065 || !IsShortcutPath(mWorkingPath))
1066 {
1067 mDirty = false;
1068 mResolveDirty = false;
1069 return NS_OK;
1070 }
1072 // we need to resolve this shortcut to what it points to, this will
1073 // set mResolvedPath. Even if it fails we need to have the resolved
1074 // path equal to working path for those functions that always use
1075 // the resolved path.
1076 rv = ResolveShortcut();
1077 if (NS_FAILED(rv))
1078 {
1079 mResolvedPath.Assign(mWorkingPath);
1080 return rv;
1081 }
1082 mResolveDirty = false;
1084 // get the details of the resolved path
1085 rv = GetFileInfo(mResolvedPath, &mFileInfo64);
1086 if (NS_FAILED(rv))
1087 return rv;
1089 mDirty = false;
1090 return NS_OK;
1091 }
1093 /**
1094 * Fills the mResolvedPath member variable with the file or symlink target
1095 * if follow symlinks is on. This is a copy of the Resolve parts from
1096 * ResolveAndStat. ResolveAndStat is much slower though because of the stat.
1097 *
1098 * @return NS_OK on success.
1099 */
1100 nsresult
1101 nsLocalFile::Resolve()
1102 {
1103 // if we aren't dirty then we are already done
1104 if (!mResolveDirty) {
1105 return NS_OK;
1106 }
1108 // we can't resolve/stat anything that isn't a valid NSPR addressable path
1109 if (mWorkingPath.IsEmpty()) {
1110 return NS_ERROR_FILE_INVALID_PATH;
1111 }
1113 // this is usually correct
1114 mResolvedPath.Assign(mWorkingPath);
1116 // if this isn't a shortcut file or we aren't following symlinks then
1117 // we're done.
1118 if (!mFollowSymlinks ||
1119 !IsShortcutPath(mWorkingPath)) {
1120 mResolveDirty = false;
1121 return NS_OK;
1122 }
1124 // we need to resolve this shortcut to what it points to, this will
1125 // set mResolvedPath. Even if it fails we need to have the resolved
1126 // path equal to working path for those functions that always use
1127 // the resolved path.
1128 nsresult rv = ResolveShortcut();
1129 if (NS_FAILED(rv)) {
1130 mResolvedPath.Assign(mWorkingPath);
1131 return rv;
1132 }
1134 mResolveDirty = false;
1135 return NS_OK;
1136 }
1138 //-----------------------------------------------------------------------------
1139 // nsLocalFile::nsIFile,nsILocalFile
1140 //-----------------------------------------------------------------------------
1142 NS_IMETHODIMP
1143 nsLocalFile::Clone(nsIFile **file)
1144 {
1145 // Just copy-construct ourselves
1146 *file = new nsLocalFile(*this);
1147 if (!*file)
1148 return NS_ERROR_OUT_OF_MEMORY;
1150 NS_ADDREF(*file);
1152 return NS_OK;
1153 }
1155 NS_IMETHODIMP
1156 nsLocalFile::InitWithFile(nsIFile *aFile)
1157 {
1158 if (NS_WARN_IF(!aFile))
1159 return NS_ERROR_INVALID_ARG;
1161 nsAutoString path;
1162 aFile->GetPath(path);
1163 if (path.IsEmpty())
1164 return NS_ERROR_INVALID_ARG;
1165 return InitWithPath(path);
1166 }
1168 NS_IMETHODIMP
1169 nsLocalFile::InitWithPath(const nsAString &filePath)
1170 {
1171 MakeDirty();
1173 nsAString::const_iterator begin, end;
1174 filePath.BeginReading(begin);
1175 filePath.EndReading(end);
1177 // input string must not be empty
1178 if (begin == end)
1179 return NS_ERROR_FAILURE;
1181 char16_t firstChar = *begin;
1182 char16_t secondChar = *(++begin);
1184 // just do a sanity check. if it has any forward slashes, it is not a Native path
1185 // on windows. Also, it must have a colon at after the first char.
1186 if (FindCharInReadable(L'/', begin, end))
1187 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1189 if (secondChar != L':' && (secondChar != L'\\' || firstChar != L'\\'))
1190 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1192 if (secondChar == L':') {
1193 // Make sure we have a valid drive, later code assumes the drive letter
1194 // is a single char a-z or A-Z.
1195 if (PathGetDriveNumberW(filePath.Data()) == -1) {
1196 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1197 }
1198 }
1200 mWorkingPath = filePath;
1201 // kill any trailing '\'
1202 if (mWorkingPath.Last() == L'\\')
1203 mWorkingPath.Truncate(mWorkingPath.Length() - 1);
1205 return NS_OK;
1207 }
1209 NS_IMETHODIMP
1210 nsLocalFile::OpenNSPRFileDesc(int32_t flags, int32_t mode, PRFileDesc **_retval)
1211 {
1212 nsresult rv = Resolve();
1213 if (NS_FAILED(rv))
1214 return rv;
1216 return OpenFile(mResolvedPath, flags, mode, _retval);
1217 }
1220 NS_IMETHODIMP
1221 nsLocalFile::OpenANSIFileDesc(const char *mode, FILE * *_retval)
1222 {
1223 nsresult rv = ResolveAndStat();
1224 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)
1225 return rv;
1227 *_retval = _wfopen(mResolvedPath.get(), NS_ConvertASCIItoUTF16(mode).get());
1228 if (*_retval)
1229 return NS_OK;
1231 return NS_ERROR_FAILURE;
1232 }
1236 NS_IMETHODIMP
1237 nsLocalFile::Create(uint32_t type, uint32_t attributes)
1238 {
1239 if (type != NORMAL_FILE_TYPE && type != DIRECTORY_TYPE)
1240 return NS_ERROR_FILE_UNKNOWN_TYPE;
1242 nsresult rv = ResolveAndStat();
1243 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)
1244 return rv;
1246 // create directories to target
1247 //
1248 // A given local file can be either one of these forms:
1249 //
1250 // - normal: X:\some\path\on\this\drive
1251 // ^--- start here
1252 //
1253 // - UNC path: \\machine\volume\some\path\on\this\drive
1254 // ^--- start here
1255 //
1256 // Skip the first 'X:\' for the first form, and skip the first full
1257 // '\\machine\volume\' segment for the second form.
1259 wchar_t* path = wwc(mResolvedPath.BeginWriting());
1261 if (path[0] == L'\\' && path[1] == L'\\')
1262 {
1263 // dealing with a UNC path here; skip past '\\machine\'
1264 path = wcschr(path + 2, L'\\');
1265 if (!path)
1266 return NS_ERROR_FILE_INVALID_PATH;
1267 ++path;
1268 }
1270 // search for first slash after the drive (or volume) name
1271 wchar_t* slash = wcschr(path, L'\\');
1273 nsresult directoryCreateError = NS_OK;
1274 if (slash)
1275 {
1276 // skip the first '\\'
1277 ++slash;
1278 slash = wcschr(slash, L'\\');
1280 while (slash)
1281 {
1282 *slash = L'\0';
1284 if (!::CreateDirectoryW(mResolvedPath.get(), nullptr)) {
1285 rv = ConvertWinError(GetLastError());
1286 if (NS_ERROR_FILE_NOT_FOUND == rv &&
1287 NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) {
1288 // If a previous CreateDirectory failed due to access, return that.
1289 return NS_ERROR_FILE_ACCESS_DENIED;
1290 }
1291 // perhaps the base path already exists, or perhaps we don't have
1292 // permissions to create the directory. NOTE: access denied could
1293 // occur on a parent directory even though it exists.
1294 else if (NS_ERROR_FILE_ALREADY_EXISTS != rv &&
1295 NS_ERROR_FILE_ACCESS_DENIED != rv) {
1296 return rv;
1297 }
1299 directoryCreateError = rv;
1300 }
1301 *slash = L'\\';
1302 ++slash;
1303 slash = wcschr(slash, L'\\');
1304 }
1305 }
1307 if (type == NORMAL_FILE_TYPE)
1308 {
1309 PRFileDesc* file;
1310 rv = OpenFile(mResolvedPath,
1311 PR_RDONLY | PR_CREATE_FILE | PR_APPEND | PR_EXCL, attributes,
1312 &file);
1313 if (file)
1314 PR_Close(file);
1316 if (rv == NS_ERROR_FILE_ACCESS_DENIED)
1317 {
1318 // need to return already-exists for directories (bug 452217)
1319 bool isdir;
1320 if (NS_SUCCEEDED(IsDirectory(&isdir)) && isdir)
1321 rv = NS_ERROR_FILE_ALREADY_EXISTS;
1322 } else if (NS_ERROR_FILE_NOT_FOUND == rv &&
1323 NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) {
1324 // If a previous CreateDirectory failed due to access, return that.
1325 return NS_ERROR_FILE_ACCESS_DENIED;
1326 }
1327 return rv;
1328 }
1330 if (type == DIRECTORY_TYPE)
1331 {
1332 if (!::CreateDirectoryW(mResolvedPath.get(), nullptr)) {
1333 rv = ConvertWinError(GetLastError());
1334 if (NS_ERROR_FILE_NOT_FOUND == rv &&
1335 NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) {
1336 // If a previous CreateDirectory failed due to access, return that.
1337 return NS_ERROR_FILE_ACCESS_DENIED;
1338 } else {
1339 return rv;
1340 }
1341 }
1342 else
1343 return NS_OK;
1344 }
1346 return NS_ERROR_FILE_UNKNOWN_TYPE;
1347 }
1350 NS_IMETHODIMP
1351 nsLocalFile::Append(const nsAString &node)
1352 {
1353 // append this path, multiple components are not permitted
1354 return AppendInternal(PromiseFlatString(node), false);
1355 }
1357 NS_IMETHODIMP
1358 nsLocalFile::AppendRelativePath(const nsAString &node)
1359 {
1360 // append this path, multiple components are permitted
1361 return AppendInternal(PromiseFlatString(node), true);
1362 }
1365 nsresult
1366 nsLocalFile::AppendInternal(const nsAFlatString &node, bool multipleComponents)
1367 {
1368 if (node.IsEmpty())
1369 return NS_OK;
1371 // check the relative path for validity
1372 if (node.First() == L'\\' // can't start with an '\'
1373 || node.FindChar(L'/') != kNotFound // can't contain /
1374 || node.EqualsASCII("..")) // can't be ..
1375 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1377 if (multipleComponents)
1378 {
1379 // can't contain .. as a path component. Ensure that the valid components
1380 // "foo..foo", "..foo", and "foo.." are not falsely detected,
1381 // but the invalid paths "..\", "foo\..", "foo\..\foo",
1382 // "..\foo", etc are.
1383 NS_NAMED_LITERAL_STRING(doubleDot, "\\..");
1384 nsAString::const_iterator start, end, offset;
1385 node.BeginReading(start);
1386 node.EndReading(end);
1387 offset = end;
1388 while (FindInReadable(doubleDot, start, offset))
1389 {
1390 if (offset == end || *offset == L'\\')
1391 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1392 start = offset;
1393 offset = end;
1394 }
1396 // catches the remaining cases of prefixes
1397 if (StringBeginsWith(node, NS_LITERAL_STRING("..\\")))
1398 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1399 }
1400 // single components can't contain '\'
1401 else if (node.FindChar(L'\\') != kNotFound)
1402 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1404 MakeDirty();
1406 mWorkingPath.Append(NS_LITERAL_STRING("\\") + node);
1408 return NS_OK;
1409 }
1411 #define TOUPPER(u) (((u) >= L'a' && (u) <= L'z') ? \
1412 (u) - (L'a' - L'A') : (u))
1414 NS_IMETHODIMP
1415 nsLocalFile::Normalize()
1416 {
1417 // XXX See bug 187957 comment 18 for possible problems with this implementation.
1419 if (mWorkingPath.IsEmpty())
1420 return NS_OK;
1422 nsAutoString path(mWorkingPath);
1424 // find the index of the root backslash for the path. Everything before
1425 // this is considered fully normalized and cannot be ascended beyond
1426 // using ".." For a local drive this is the first slash (e.g. "c:\").
1427 // For a UNC path it is the slash following the share name
1428 // (e.g. "\\server\share\").
1429 int32_t rootIdx = 2; // default to local drive
1430 if (path.First() == L'\\') // if a share then calculate the rootIdx
1431 {
1432 rootIdx = path.FindChar(L'\\', 2); // skip \\ in front of the server
1433 if (rootIdx == kNotFound)
1434 return NS_OK; // already normalized
1435 rootIdx = path.FindChar(L'\\', rootIdx+1);
1436 if (rootIdx == kNotFound)
1437 return NS_OK; // already normalized
1438 }
1439 else if (path.CharAt(rootIdx) != L'\\')
1440 {
1441 // The path has been specified relative to the current working directory
1442 // for that drive. To normalize it, the current working directory for
1443 // that drive needs to be inserted before the supplied relative path
1444 // which will provide an absolute path (and the rootIdx will still be 2).
1445 WCHAR cwd[MAX_PATH];
1446 WCHAR * pcwd = cwd;
1447 int drive = TOUPPER(path.First()) - 'A' + 1;
1448 /* We need to worry about IPH, for details read bug 419326.
1449 * _getdrives - http://msdn2.microsoft.com/en-us/library/xdhk0xd2.aspx
1450 * uses a bitmask, bit 0 is 'a:'
1451 * _chdrive - http://msdn2.microsoft.com/en-us/library/0d1409hb.aspx
1452 * _getdcwd - http://msdn2.microsoft.com/en-us/library/7t2zk3s4.aspx
1453 * take an int, 1 is 'a:'.
1454 *
1455 * Because of this, we need to do some math. Subtract 1 to convert from
1456 * _chdrive/_getdcwd format to _getdrives drive numbering.
1457 * Shift left x bits to convert from integer indexing to bitfield indexing.
1458 * And of course, we need to find out if the drive is in the bitmask.
1459 *
1460 * If we're really unlucky, we can still lose, but only if the user
1461 * manages to eject the drive between our call to _getdrives() and
1462 * our *calls* to _wgetdcwd.
1463 */
1464 if (!((1 << (drive - 1)) & _getdrives()))
1465 return NS_ERROR_FILE_INVALID_PATH;
1466 if (!_wgetdcwd(drive, pcwd, MAX_PATH))
1467 pcwd = _wgetdcwd(drive, 0, 0);
1468 if (!pcwd)
1469 return NS_ERROR_OUT_OF_MEMORY;
1470 nsAutoString currentDir(pcwd);
1471 if (pcwd != cwd)
1472 free(pcwd);
1474 if (currentDir.Last() == '\\')
1475 path.Replace(0, 2, currentDir);
1476 else
1477 path.Replace(0, 2, currentDir + NS_LITERAL_STRING("\\"));
1478 }
1479 NS_POSTCONDITION(0 < rootIdx && rootIdx < (int32_t)path.Length(), "rootIdx is invalid");
1480 NS_POSTCONDITION(path.CharAt(rootIdx) == '\\', "rootIdx is invalid");
1482 // if there is nothing following the root path then it is already normalized
1483 if (rootIdx + 1 == (int32_t)path.Length())
1484 return NS_OK;
1486 // assign the root
1487 const char16_t * pathBuffer = path.get(); // simplify access to the buffer
1488 mWorkingPath.SetCapacity(path.Length()); // it won't ever grow longer
1489 mWorkingPath.Assign(pathBuffer, rootIdx);
1491 // Normalize the path components. The actions taken are:
1492 //
1493 // "\\" condense to single backslash
1494 // "." remove from path
1495 // ".." up a directory
1496 // "..." remove from path (any number of dots > 2)
1497 //
1498 // The last form is something that Windows 95 and 98 supported and
1499 // is a shortcut for changing up multiple directories. Windows XP
1500 // and ilk ignore it in a path, as is done here.
1501 int32_t len, begin, end = rootIdx;
1502 while (end < (int32_t)path.Length())
1503 {
1504 // find the current segment (text between the backslashes) to
1505 // be examined, this will set the following variables:
1506 // begin == index of first char in segment
1507 // end == index 1 char after last char in segment
1508 // len == length of segment
1509 begin = end + 1;
1510 end = path.FindChar('\\', begin);
1511 if (end == kNotFound)
1512 end = path.Length();
1513 len = end - begin;
1515 // ignore double backslashes
1516 if (len == 0)
1517 continue;
1519 // len != 0, and interesting paths always begin with a dot
1520 if (pathBuffer[begin] == '.')
1521 {
1522 // ignore single dots
1523 if (len == 1)
1524 continue;
1526 // handle multiple dots
1527 if (len >= 2 && pathBuffer[begin+1] == L'.')
1528 {
1529 // back up a path component on double dot
1530 if (len == 2)
1531 {
1532 int32_t prev = mWorkingPath.RFindChar('\\');
1533 if (prev >= rootIdx)
1534 mWorkingPath.Truncate(prev);
1535 continue;
1536 }
1538 // length is > 2 and the first two characters are dots.
1539 // if the rest of the string is dots, then ignore it.
1540 int idx = len - 1;
1541 for (; idx >= 2; --idx)
1542 {
1543 if (pathBuffer[begin+idx] != L'.')
1544 break;
1545 }
1547 // this is true if the loop above didn't break
1548 // and all characters in this segment are dots.
1549 if (idx < 2)
1550 continue;
1551 }
1552 }
1554 // add the current component to the path, including the preceding backslash
1555 mWorkingPath.Append(pathBuffer + begin - 1, len + 1);
1556 }
1558 // kill trailing dots and spaces.
1559 int32_t filePathLen = mWorkingPath.Length() - 1;
1560 while(filePathLen > 0 && (mWorkingPath[filePathLen] == L' ' ||
1561 mWorkingPath[filePathLen] == L'.'))
1562 {
1563 mWorkingPath.Truncate(filePathLen--);
1564 }
1566 MakeDirty();
1567 return NS_OK;
1568 }
1570 NS_IMETHODIMP
1571 nsLocalFile::GetLeafName(nsAString &aLeafName)
1572 {
1573 aLeafName.Truncate();
1575 if (mWorkingPath.IsEmpty())
1576 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1578 int32_t offset = mWorkingPath.RFindChar(L'\\');
1580 // if the working path is just a node without any lashes.
1581 if (offset == kNotFound)
1582 aLeafName = mWorkingPath;
1583 else
1584 aLeafName = Substring(mWorkingPath, offset + 1);
1586 return NS_OK;
1587 }
1589 NS_IMETHODIMP
1590 nsLocalFile::SetLeafName(const nsAString &aLeafName)
1591 {
1592 MakeDirty();
1594 if (mWorkingPath.IsEmpty())
1595 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1597 // cannot use nsCString::RFindChar() due to 0x5c problem
1598 int32_t offset = mWorkingPath.RFindChar(L'\\');
1599 if (offset)
1600 {
1601 mWorkingPath.Truncate(offset+1);
1602 }
1603 mWorkingPath.Append(aLeafName);
1605 return NS_OK;
1606 }
1609 NS_IMETHODIMP
1610 nsLocalFile::GetPath(nsAString &_retval)
1611 {
1612 _retval = mWorkingPath;
1613 return NS_OK;
1614 }
1616 NS_IMETHODIMP
1617 nsLocalFile::GetCanonicalPath(nsAString &aResult)
1618 {
1619 EnsureShortPath();
1620 aResult.Assign(mShortWorkingPath);
1621 return NS_OK;
1622 }
1624 typedef struct {
1625 WORD wLanguage;
1626 WORD wCodePage;
1627 } LANGANDCODEPAGE;
1629 NS_IMETHODIMP
1630 nsLocalFile::GetVersionInfoField(const char* aField, nsAString& _retval)
1631 {
1632 nsresult rv = ResolveAndStat();
1633 if (NS_FAILED(rv))
1634 return rv;
1636 rv = NS_ERROR_FAILURE;
1638 const WCHAR *path = mFollowSymlinks ? mResolvedPath.get() : mWorkingPath.get();
1640 DWORD dummy;
1641 DWORD size = ::GetFileVersionInfoSizeW(path, &dummy);
1642 if (!size)
1643 return rv;
1645 void* ver = calloc(size, 1);
1646 if (!ver)
1647 return NS_ERROR_OUT_OF_MEMORY;
1649 if (::GetFileVersionInfoW(path, 0, size, ver))
1650 {
1651 LANGANDCODEPAGE* translate = nullptr;
1652 UINT pageCount;
1653 BOOL queryResult = ::VerQueryValueW(ver, L"\\VarFileInfo\\Translation",
1654 (void**)&translate, &pageCount);
1655 if (queryResult && translate)
1656 {
1657 for (int32_t i = 0; i < 2; ++i)
1658 {
1659 wchar_t subBlock[MAX_PATH];
1660 _snwprintf(subBlock, MAX_PATH,
1661 L"\\StringFileInfo\\%04x%04x\\%s",
1662 (i == 0 ? translate[0].wLanguage
1663 : ::GetUserDefaultLangID()),
1664 translate[0].wCodePage,
1665 NS_ConvertASCIItoUTF16(
1666 nsDependentCString(aField)).get());
1667 subBlock[MAX_PATH - 1] = 0;
1668 LPVOID value = nullptr;
1669 UINT size;
1670 queryResult = ::VerQueryValueW(ver, subBlock, &value, &size);
1671 if (queryResult && value)
1672 {
1673 _retval.Assign(static_cast<char16_t*>(value));
1674 if (!_retval.IsEmpty())
1675 {
1676 rv = NS_OK;
1677 break;
1678 }
1679 }
1680 }
1681 }
1682 }
1683 free(ver);
1685 return rv;
1686 }
1688 NS_IMETHODIMP
1689 nsLocalFile::SetShortcut(nsIFile* targetFile,
1690 nsIFile* workingDir,
1691 const char16_t* args,
1692 const char16_t* description,
1693 nsIFile* iconFile,
1694 int32_t iconIndex)
1695 {
1696 bool exists;
1697 nsresult rv = this->Exists(&exists);
1698 if (NS_FAILED(rv)) {
1699 return rv;
1700 }
1702 const WCHAR* targetFilePath = nullptr;
1703 const WCHAR* workingDirPath = nullptr;
1704 const WCHAR* iconFilePath = nullptr;
1706 nsAutoString targetFilePathAuto;
1707 if (targetFile) {
1708 rv = targetFile->GetPath(targetFilePathAuto);
1709 if (NS_FAILED(rv)) {
1710 return rv;
1711 }
1712 targetFilePath = targetFilePathAuto.get();
1713 }
1715 nsAutoString workingDirPathAuto;
1716 if (workingDir) {
1717 rv = workingDir->GetPath(workingDirPathAuto);
1718 if (NS_FAILED(rv)) {
1719 return rv;
1720 }
1721 workingDirPath = workingDirPathAuto.get();
1722 }
1724 nsAutoString iconPathAuto;
1725 if (iconFile) {
1726 rv = iconFile->GetPath(iconPathAuto);
1727 if (NS_FAILED(rv)) {
1728 return rv;
1729 }
1730 iconFilePath = iconPathAuto.get();
1731 }
1733 rv = gResolver->SetShortcut(exists,
1734 mWorkingPath.get(),
1735 targetFilePath,
1736 workingDirPath,
1737 char16ptr_t(args),
1738 char16ptr_t(description),
1739 iconFilePath,
1740 iconFilePath? iconIndex : 0);
1741 if (targetFilePath && NS_SUCCEEDED(rv)) {
1742 MakeDirty();
1743 }
1745 return rv;
1746 }
1748 /**
1749 * Determines if the drive type for the specified file is rmeote or local.
1750 *
1751 * @param path The path of the file to check
1752 * @param remote Out parameter, on function success holds true if the specified
1753 * file path is remote, or false if the file path is local.
1754 * @return true on success. The return value implies absolutely nothing about
1755 * wether the file is local or remote.
1756 */
1757 static bool
1758 IsRemoteFilePath(LPCWSTR path, bool &remote)
1759 {
1760 // Obtain the parent directory path and make sure it ends with
1761 // a trailing backslash.
1762 WCHAR dirPath[MAX_PATH + 1] = { 0 };
1763 wcsncpy(dirPath, path, MAX_PATH);
1764 if (!PathRemoveFileSpecW(dirPath)) {
1765 return false;
1766 }
1767 size_t len = wcslen(dirPath);
1768 // In case the dirPath holds exaclty MAX_PATH and remains unchanged, we
1769 // recheck the required length here since we need to terminate it with
1770 // a backslash.
1771 if (len >= MAX_PATH) {
1772 return false;
1773 }
1775 dirPath[len] = L'\\';
1776 dirPath[len + 1] = L'\0';
1777 UINT driveType = GetDriveTypeW(dirPath);
1778 remote = driveType == DRIVE_REMOTE;
1779 return true;
1780 }
1782 nsresult
1783 nsLocalFile::CopySingleFile(nsIFile *sourceFile, nsIFile *destParent,
1784 const nsAString &newName, uint32_t options)
1785 {
1786 nsresult rv = NS_OK;
1787 nsAutoString filePath;
1789 bool move = options & (Move | Rename);
1791 // get the path that we are going to copy to.
1792 // Since windows does not know how to auto
1793 // resolve shortcuts, we must work with the
1794 // target.
1795 nsAutoString destPath;
1796 destParent->GetTarget(destPath);
1798 destPath.Append('\\');
1800 if (newName.IsEmpty())
1801 {
1802 nsAutoString aFileName;
1803 sourceFile->GetLeafName(aFileName);
1804 destPath.Append(aFileName);
1805 }
1806 else
1807 {
1808 destPath.Append(newName);
1809 }
1812 if (options & FollowSymlinks)
1813 {
1814 rv = sourceFile->GetTarget(filePath);
1815 if (filePath.IsEmpty())
1816 rv = sourceFile->GetPath(filePath);
1817 }
1818 else
1819 {
1820 rv = sourceFile->GetPath(filePath);
1821 }
1823 if (NS_FAILED(rv))
1824 return rv;
1826 // Pass the flag COPY_FILE_NO_BUFFERING to CopyFileEx as we may be copying
1827 // to a SMBV2 remote drive. Without this parameter subsequent append mode
1828 // file writes can cause the resultant file to become corrupt. We only need to do
1829 // this if the major version of Windows is > 5(Only Windows Vista and above
1830 // can support SMBV2). With a 7200RPM hard drive:
1831 // Copying a 1KB file with COPY_FILE_NO_BUFFERING takes about 30-60ms.
1832 // Copying a 1KB file without COPY_FILE_NO_BUFFERING takes < 1ms.
1833 // So we only use COPY_FILE_NO_BUFFERING when we have a remote drive.
1834 int copyOK;
1835 DWORD dwCopyFlags = COPY_FILE_ALLOW_DECRYPTED_DESTINATION;
1836 if (IsVistaOrLater()) {
1837 bool path1Remote, path2Remote;
1838 if (!IsRemoteFilePath(filePath.get(), path1Remote) ||
1839 !IsRemoteFilePath(destPath.get(), path2Remote) ||
1840 path1Remote || path2Remote) {
1841 dwCopyFlags |= COPY_FILE_NO_BUFFERING;
1842 }
1843 }
1845 if (!move)
1846 {
1847 copyOK = ::CopyFileExW(filePath.get(), destPath.get(), nullptr,
1848 nullptr, nullptr, dwCopyFlags);
1849 }
1850 else
1851 {
1852 copyOK = ::MoveFileExW(filePath.get(), destPath.get(), MOVEFILE_REPLACE_EXISTING);
1854 // Check if copying the source file to a different volume,
1855 // as this could be an SMBV2 mapped drive.
1856 if (!copyOK && GetLastError() == ERROR_NOT_SAME_DEVICE)
1857 {
1858 if (options & Rename) {
1859 return NS_ERROR_FILE_ACCESS_DENIED;
1860 }
1861 copyOK = CopyFileExW(filePath.get(), destPath.get(), nullptr,
1862 nullptr, nullptr, dwCopyFlags);
1864 if (copyOK)
1865 DeleteFileW(filePath.get());
1866 }
1867 }
1869 if (!copyOK) // CopyFileEx and MoveFileEx return zero at failure.
1870 rv = ConvertWinError(GetLastError());
1871 else if (move && !(options & SkipNtfsAclReset))
1872 {
1873 // Set security permissions to inherit from parent.
1874 // Note: propagates to all children: slow for big file trees
1875 PACL pOldDACL = nullptr;
1876 PSECURITY_DESCRIPTOR pSD = nullptr;
1877 ::GetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT,
1878 DACL_SECURITY_INFORMATION,
1879 nullptr, nullptr, &pOldDACL, nullptr, &pSD);
1880 if (pOldDACL)
1881 ::SetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT,
1882 DACL_SECURITY_INFORMATION |
1883 UNPROTECTED_DACL_SECURITY_INFORMATION,
1884 nullptr, nullptr, pOldDACL, nullptr);
1885 if (pSD)
1886 LocalFree((HLOCAL)pSD);
1887 }
1889 return rv;
1890 }
1892 nsresult
1893 nsLocalFile::CopyMove(nsIFile *aParentDir, const nsAString &newName, uint32_t options)
1894 {
1895 bool move = options & (Move | Rename);
1896 bool followSymlinks = options & FollowSymlinks;
1898 nsCOMPtr<nsIFile> newParentDir = aParentDir;
1899 // check to see if this exists, otherwise return an error.
1900 // we will check this by resolving. If the user wants us
1901 // to follow links, then we are talking about the target,
1902 // hence we can use the |FollowSymlinks| option.
1903 nsresult rv = ResolveAndStat();
1904 if (NS_FAILED(rv))
1905 return rv;
1907 if (!newParentDir)
1908 {
1909 // no parent was specified. We must rename.
1910 if (newName.IsEmpty())
1911 return NS_ERROR_INVALID_ARG;
1913 rv = GetParent(getter_AddRefs(newParentDir));
1914 if (NS_FAILED(rv))
1915 return rv;
1916 }
1918 if (!newParentDir)
1919 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
1921 // make sure it exists and is a directory. Create it if not there.
1922 bool exists;
1923 newParentDir->Exists(&exists);
1924 if (!exists)
1925 {
1926 rv = newParentDir->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use
1927 if (NS_FAILED(rv))
1928 return rv;
1929 }
1930 else
1931 {
1932 bool isDir;
1933 newParentDir->IsDirectory(&isDir);
1934 if (!isDir)
1935 {
1936 if (followSymlinks)
1937 {
1938 bool isLink;
1939 newParentDir->IsSymlink(&isLink);
1940 if (isLink)
1941 {
1942 nsAutoString target;
1943 newParentDir->GetTarget(target);
1945 nsCOMPtr<nsIFile> realDest = new nsLocalFile();
1946 if (realDest == nullptr)
1947 return NS_ERROR_OUT_OF_MEMORY;
1949 rv = realDest->InitWithPath(target);
1951 if (NS_FAILED(rv))
1952 return rv;
1954 return CopyMove(realDest, newName, options);
1955 }
1956 }
1957 else
1958 {
1959 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
1960 }
1961 }
1962 }
1964 // Try different ways to move/copy files/directories
1965 bool done = false;
1966 bool isDir;
1967 IsDirectory(&isDir);
1968 bool isSymlink;
1969 IsSymlink(&isSymlink);
1971 // Try to move the file or directory, or try to copy a single file (or non-followed symlink)
1972 if (move || !isDir || (isSymlink && !followSymlinks))
1973 {
1974 // Copy/Move single file, or move a directory
1975 if (!aParentDir) {
1976 options |= SkipNtfsAclReset;
1977 }
1978 rv = CopySingleFile(this, newParentDir, newName, options);
1979 done = NS_SUCCEEDED(rv);
1980 // If we are moving a directory and that fails, fallback on directory
1981 // enumeration. See bug 231300 for details.
1982 if (!done && !(move && isDir))
1983 return rv;
1984 }
1986 // Not able to copy or move directly, so enumerate it
1987 if (!done)
1988 {
1989 // create a new target destination in the new parentDir;
1990 nsCOMPtr<nsIFile> target;
1991 rv = newParentDir->Clone(getter_AddRefs(target));
1993 if (NS_FAILED(rv))
1994 return rv;
1996 nsAutoString allocatedNewName;
1997 if (newName.IsEmpty())
1998 {
1999 bool isLink;
2000 IsSymlink(&isLink);
2001 if (isLink)
2002 {
2003 nsAutoString temp;
2004 GetTarget(temp);
2005 int32_t offset = temp.RFindChar(L'\\');
2006 if (offset == kNotFound)
2007 allocatedNewName = temp;
2008 else
2009 allocatedNewName = Substring(temp, offset + 1);
2010 }
2011 else
2012 {
2013 GetLeafName(allocatedNewName);// this should be the leaf name of the
2014 }
2015 }
2016 else
2017 {
2018 allocatedNewName = newName;
2019 }
2021 rv = target->Append(allocatedNewName);
2022 if (NS_FAILED(rv))
2023 return rv;
2025 allocatedNewName.Truncate();
2027 // check if the destination directory already exists
2028 target->Exists(&exists);
2029 if (!exists)
2030 {
2031 // if the destination directory cannot be created, return an error
2032 rv = target->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use
2033 if (NS_FAILED(rv))
2034 return rv;
2035 }
2036 else
2037 {
2038 // check if the destination directory is writable and empty
2039 bool isWritable;
2041 target->IsWritable(&isWritable);
2042 if (!isWritable)
2043 return NS_ERROR_FILE_ACCESS_DENIED;
2045 nsCOMPtr<nsISimpleEnumerator> targetIterator;
2046 rv = target->GetDirectoryEntries(getter_AddRefs(targetIterator));
2047 if (NS_FAILED(rv))
2048 return rv;
2050 bool more;
2051 targetIterator->HasMoreElements(&more);
2052 // return error if target directory is not empty
2053 if (more)
2054 return NS_ERROR_FILE_DIR_NOT_EMPTY;
2055 }
2057 nsDirEnumerator dirEnum;
2059 rv = dirEnum.Init(this);
2060 if (NS_FAILED(rv)) {
2061 NS_WARNING("dirEnum initialization failed");
2062 return rv;
2063 }
2065 bool more = false;
2066 while (NS_SUCCEEDED(dirEnum.HasMoreElements(&more)) && more)
2067 {
2068 nsCOMPtr<nsISupports> item;
2069 nsCOMPtr<nsIFile> file;
2070 dirEnum.GetNext(getter_AddRefs(item));
2071 file = do_QueryInterface(item);
2072 if (file)
2073 {
2074 bool isDir, isLink;
2076 file->IsDirectory(&isDir);
2077 file->IsSymlink(&isLink);
2079 if (move)
2080 {
2081 if (followSymlinks)
2082 return NS_ERROR_FAILURE;
2084 rv = file->MoveTo(target, EmptyString());
2085 if (NS_FAILED(rv))
2086 return rv;
2087 }
2088 else
2089 {
2090 if (followSymlinks)
2091 rv = file->CopyToFollowingLinks(target, EmptyString());
2092 else
2093 rv = file->CopyTo(target, EmptyString());
2094 if (NS_FAILED(rv))
2095 return rv;
2096 }
2097 }
2098 }
2099 // we've finished moving all the children of this directory
2100 // in the new directory. so now delete the directory
2101 // note, we don't need to do a recursive delete.
2102 // MoveTo() is recursive. At this point,
2103 // we've already moved the children of the current folder
2104 // to the new location. nothing should be left in the folder.
2105 if (move)
2106 {
2107 rv = Remove(false /* recursive */);
2108 if (NS_FAILED(rv))
2109 return rv;
2110 }
2111 }
2114 // If we moved, we want to adjust this.
2115 if (move)
2116 {
2117 MakeDirty();
2119 nsAutoString newParentPath;
2120 newParentDir->GetPath(newParentPath);
2122 if (newParentPath.IsEmpty())
2123 return NS_ERROR_FAILURE;
2125 if (newName.IsEmpty())
2126 {
2127 nsAutoString aFileName;
2128 GetLeafName(aFileName);
2130 InitWithPath(newParentPath);
2131 Append(aFileName);
2132 }
2133 else
2134 {
2135 InitWithPath(newParentPath);
2136 Append(newName);
2137 }
2138 }
2140 return NS_OK;
2141 }
2143 NS_IMETHODIMP
2144 nsLocalFile::CopyTo(nsIFile *newParentDir, const nsAString &newName)
2145 {
2146 return CopyMove(newParentDir, newName, 0);
2147 }
2149 NS_IMETHODIMP
2150 nsLocalFile::CopyToFollowingLinks(nsIFile *newParentDir, const nsAString &newName)
2151 {
2152 return CopyMove(newParentDir, newName, FollowSymlinks);
2153 }
2155 NS_IMETHODIMP
2156 nsLocalFile::MoveTo(nsIFile *newParentDir, const nsAString &newName)
2157 {
2158 return CopyMove(newParentDir, newName, Move);
2159 }
2161 NS_IMETHODIMP
2162 nsLocalFile::RenameTo(nsIFile *newParentDir, const nsAString & newName)
2163 {
2164 nsCOMPtr<nsIFile> targetParentDir = newParentDir;
2165 // check to see if this exists, otherwise return an error.
2166 // we will check this by resolving. If the user wants us
2167 // to follow links, then we are talking about the target,
2168 // hence we can use the |followSymlinks| parameter.
2169 nsresult rv = ResolveAndStat();
2170 if (NS_FAILED(rv)) {
2171 return rv;
2172 }
2174 if (!targetParentDir) {
2175 // no parent was specified. We must rename.
2176 if (newName.IsEmpty()) {
2177 return NS_ERROR_INVALID_ARG;
2178 }
2179 rv = GetParent(getter_AddRefs(targetParentDir));
2180 if (NS_FAILED(rv)) {
2181 return rv;
2182 }
2183 }
2185 if (!targetParentDir) {
2186 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
2187 }
2189 // make sure it exists and is a directory. Create it if not there.
2190 bool exists;
2191 targetParentDir->Exists(&exists);
2192 if (!exists) {
2193 rv = targetParentDir->Create(DIRECTORY_TYPE, 0644);
2194 if (NS_FAILED(rv)) {
2195 return rv;
2196 }
2197 } else {
2198 bool isDir;
2199 targetParentDir->IsDirectory(&isDir);
2200 if (!isDir) {
2201 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
2202 }
2203 }
2205 uint32_t options = Rename;
2206 if (!newParentDir) {
2207 options |= SkipNtfsAclReset;
2208 }
2209 // Move single file, or move a directory
2210 return CopySingleFile(this, targetParentDir, newName, options);
2211 }
2213 NS_IMETHODIMP
2214 nsLocalFile::Load(PRLibrary * *_retval)
2215 {
2216 // Check we are correctly initialized.
2217 CHECK_mWorkingPath();
2219 bool isFile;
2220 nsresult rv = IsFile(&isFile);
2222 if (NS_FAILED(rv))
2223 return rv;
2225 if (! isFile)
2226 return NS_ERROR_FILE_IS_DIRECTORY;
2228 #ifdef NS_BUILD_REFCNT_LOGGING
2229 nsTraceRefcnt::SetActivityIsLegal(false);
2230 #endif
2232 PRLibSpec libSpec;
2233 libSpec.value.pathname_u = mResolvedPath.get();
2234 libSpec.type = PR_LibSpec_PathnameU;
2235 *_retval = PR_LoadLibraryWithFlags(libSpec, 0);
2237 #ifdef NS_BUILD_REFCNT_LOGGING
2238 nsTraceRefcnt::SetActivityIsLegal(true);
2239 #endif
2241 if (*_retval)
2242 return NS_OK;
2243 return NS_ERROR_NULL_POINTER;
2244 }
2246 NS_IMETHODIMP
2247 nsLocalFile::Remove(bool recursive)
2248 {
2249 // NOTE:
2250 //
2251 // if the working path points to a shortcut, then we will only
2252 // delete the shortcut itself. even if the shortcut points to
2253 // a directory, we will not recurse into that directory or
2254 // delete that directory itself. likewise, if the shortcut
2255 // points to a normal file, we will not delete the real file.
2256 // this is done to be consistent with the other platforms that
2257 // behave this way. we do this even if the followLinks attribute
2258 // is set to true. this helps protect against misuse that could
2259 // lead to security bugs (e.g., bug 210588).
2260 //
2261 // Since shortcut files are no longer permitted to be used as unix-like
2262 // symlinks interspersed in the path (e.g. "c:/file.lnk/foo/bar.txt")
2263 // this processing is a lot simpler. Even if the shortcut file is
2264 // pointing to a directory, only the mWorkingPath value is used and so
2265 // only the shortcut file will be deleted.
2267 // Check we are correctly initialized.
2268 CHECK_mWorkingPath();
2270 bool isDir, isLink;
2271 nsresult rv;
2273 isDir = false;
2274 rv = IsSymlink(&isLink);
2275 if (NS_FAILED(rv))
2276 return rv;
2278 // only check to see if we have a directory if it isn't a link
2279 if (!isLink)
2280 {
2281 rv = IsDirectory(&isDir);
2282 if (NS_FAILED(rv))
2283 return rv;
2284 }
2286 if (isDir)
2287 {
2288 if (recursive)
2289 {
2290 nsDirEnumerator dirEnum;
2292 rv = dirEnum.Init(this);
2293 if (NS_FAILED(rv))
2294 return rv;
2296 bool more = false;
2297 while (NS_SUCCEEDED(dirEnum.HasMoreElements(&more)) && more)
2298 {
2299 nsCOMPtr<nsISupports> item;
2300 dirEnum.GetNext(getter_AddRefs(item));
2301 nsCOMPtr<nsIFile> file = do_QueryInterface(item);
2302 if (file)
2303 file->Remove(recursive);
2304 }
2305 }
2306 if (RemoveDirectoryW(mWorkingPath.get()) == 0)
2307 return ConvertWinError(GetLastError());
2308 }
2309 else
2310 {
2311 if (DeleteFileW(mWorkingPath.get()) == 0)
2312 return ConvertWinError(GetLastError());
2313 }
2315 MakeDirty();
2316 return rv;
2317 }
2319 NS_IMETHODIMP
2320 nsLocalFile::GetLastModifiedTime(PRTime *aLastModifiedTime)
2321 {
2322 // Check we are correctly initialized.
2323 CHECK_mWorkingPath();
2325 if (NS_WARN_IF(!aLastModifiedTime))
2326 return NS_ERROR_INVALID_ARG;
2328 // get the modified time of the target as determined by mFollowSymlinks
2329 // If true, then this will be for the target of the shortcut file,
2330 // otherwise it will be for the shortcut file itself (i.e. the same
2331 // results as GetLastModifiedTimeOfLink)
2333 nsresult rv = ResolveAndStat();
2334 if (NS_FAILED(rv))
2335 return rv;
2337 // microseconds -> milliseconds
2338 *aLastModifiedTime = mFileInfo64.modifyTime / PR_USEC_PER_MSEC;
2339 return NS_OK;
2340 }
2343 NS_IMETHODIMP
2344 nsLocalFile::GetLastModifiedTimeOfLink(PRTime *aLastModifiedTime)
2345 {
2346 // Check we are correctly initialized.
2347 CHECK_mWorkingPath();
2349 if (NS_WARN_IF(!aLastModifiedTime))
2350 return NS_ERROR_INVALID_ARG;
2352 // The caller is assumed to have already called IsSymlink
2353 // and to have found that this file is a link.
2355 PRFileInfo64 info;
2356 nsresult rv = GetFileInfo(mWorkingPath, &info);
2357 if (NS_FAILED(rv))
2358 return rv;
2360 // microseconds -> milliseconds
2361 *aLastModifiedTime = info.modifyTime / PR_USEC_PER_MSEC;
2362 return NS_OK;
2363 }
2366 NS_IMETHODIMP
2367 nsLocalFile::SetLastModifiedTime(PRTime aLastModifiedTime)
2368 {
2369 // Check we are correctly initialized.
2370 CHECK_mWorkingPath();
2372 nsresult rv = ResolveAndStat();
2373 if (NS_FAILED(rv))
2374 return rv;
2376 // set the modified time of the target as determined by mFollowSymlinks
2377 // If true, then this will be for the target of the shortcut file,
2378 // otherwise it will be for the shortcut file itself (i.e. the same
2379 // results as SetLastModifiedTimeOfLink)
2381 rv = SetModDate(aLastModifiedTime, mResolvedPath.get());
2382 if (NS_SUCCEEDED(rv))
2383 MakeDirty();
2385 return rv;
2386 }
2389 NS_IMETHODIMP
2390 nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModifiedTime)
2391 {
2392 // The caller is assumed to have already called IsSymlink
2393 // and to have found that this file is a link.
2395 nsresult rv = SetModDate(aLastModifiedTime, mWorkingPath.get());
2396 if (NS_SUCCEEDED(rv))
2397 MakeDirty();
2399 return rv;
2400 }
2402 nsresult
2403 nsLocalFile::SetModDate(PRTime aLastModifiedTime, const wchar_t *filePath)
2404 {
2405 // The FILE_FLAG_BACKUP_SEMANTICS is required in order to change the
2406 // modification time for directories.
2407 HANDLE file = ::CreateFileW(filePath, // pointer to name of the file
2408 GENERIC_WRITE, // access (write) mode
2409 0, // share mode
2410 nullptr, // pointer to security attributes
2411 OPEN_EXISTING, // how to create
2412 FILE_FLAG_BACKUP_SEMANTICS, // file attributes
2413 nullptr);
2415 if (file == INVALID_HANDLE_VALUE)
2416 {
2417 return ConvertWinError(GetLastError());
2418 }
2420 FILETIME ft;
2421 SYSTEMTIME st;
2422 PRExplodedTime pret;
2424 // PR_ExplodeTime expects usecs...
2425 PR_ExplodeTime(aLastModifiedTime * PR_USEC_PER_MSEC, PR_GMTParameters, &pret);
2426 st.wYear = pret.tm_year;
2427 st.wMonth = pret.tm_month + 1; // Convert start offset -- Win32: Jan=1; NSPR: Jan=0
2428 st.wDayOfWeek = pret.tm_wday;
2429 st.wDay = pret.tm_mday;
2430 st.wHour = pret.tm_hour;
2431 st.wMinute = pret.tm_min;
2432 st.wSecond = pret.tm_sec;
2433 st.wMilliseconds = pret.tm_usec/1000;
2435 nsresult rv = NS_OK;
2436 // if at least one of these fails...
2437 if (!(SystemTimeToFileTime(&st, &ft) != 0 &&
2438 SetFileTime(file, nullptr, &ft, &ft) != 0))
2439 {
2440 rv = ConvertWinError(GetLastError());
2441 }
2443 CloseHandle(file);
2444 return rv;
2445 }
2447 NS_IMETHODIMP
2448 nsLocalFile::GetPermissions(uint32_t *aPermissions)
2449 {
2450 if (NS_WARN_IF(!aPermissions))
2451 return NS_ERROR_INVALID_ARG;
2453 // get the permissions of the target as determined by mFollowSymlinks
2454 // If true, then this will be for the target of the shortcut file,
2455 // otherwise it will be for the shortcut file itself (i.e. the same
2456 // results as GetPermissionsOfLink)
2457 nsresult rv = ResolveAndStat();
2458 if (NS_FAILED(rv))
2459 return rv;
2461 bool isWritable, isExecutable;
2462 IsWritable(&isWritable);
2463 IsExecutable(&isExecutable);
2465 *aPermissions = PR_IRUSR|PR_IRGRP|PR_IROTH; // all read
2466 if (isWritable)
2467 *aPermissions |= PR_IWUSR|PR_IWGRP|PR_IWOTH; // all write
2468 if (isExecutable)
2469 *aPermissions |= PR_IXUSR|PR_IXGRP|PR_IXOTH; // all execute
2471 return NS_OK;
2472 }
2474 NS_IMETHODIMP
2475 nsLocalFile::GetPermissionsOfLink(uint32_t *aPermissions)
2476 {
2477 // Check we are correctly initialized.
2478 CHECK_mWorkingPath();
2480 if (NS_WARN_IF(!aPermissions))
2481 return NS_ERROR_INVALID_ARG;
2483 // The caller is assumed to have already called IsSymlink
2484 // and to have found that this file is a link. It is not
2485 // possible for a link file to be executable.
2487 DWORD word = ::GetFileAttributesW(mWorkingPath.get());
2488 if (word == INVALID_FILE_ATTRIBUTES)
2489 return NS_ERROR_FILE_INVALID_PATH;
2491 bool isWritable = !(word & FILE_ATTRIBUTE_READONLY);
2492 *aPermissions = PR_IRUSR|PR_IRGRP|PR_IROTH; // all read
2493 if (isWritable)
2494 *aPermissions |= PR_IWUSR|PR_IWGRP|PR_IWOTH; // all write
2496 return NS_OK;
2497 }
2500 NS_IMETHODIMP
2501 nsLocalFile::SetPermissions(uint32_t aPermissions)
2502 {
2503 // Check we are correctly initialized.
2504 CHECK_mWorkingPath();
2506 // set the permissions of the target as determined by mFollowSymlinks
2507 // If true, then this will be for the target of the shortcut file,
2508 // otherwise it will be for the shortcut file itself (i.e. the same
2509 // results as SetPermissionsOfLink)
2510 nsresult rv = ResolveAndStat();
2511 if (NS_FAILED(rv))
2512 return rv;
2514 // windows only knows about the following permissions
2515 int mode = 0;
2516 if (aPermissions & (PR_IRUSR|PR_IRGRP|PR_IROTH)) // any read
2517 mode |= _S_IREAD;
2518 if (aPermissions & (PR_IWUSR|PR_IWGRP|PR_IWOTH)) // any write
2519 mode |= _S_IWRITE;
2521 if (_wchmod(mResolvedPath.get(), mode) == -1)
2522 return NS_ERROR_FAILURE;
2524 return NS_OK;
2525 }
2527 NS_IMETHODIMP
2528 nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions)
2529 {
2530 // The caller is assumed to have already called IsSymlink
2531 // and to have found that this file is a link.
2533 // windows only knows about the following permissions
2534 int mode = 0;
2535 if (aPermissions & (PR_IRUSR|PR_IRGRP|PR_IROTH)) // any read
2536 mode |= _S_IREAD;
2537 if (aPermissions & (PR_IWUSR|PR_IWGRP|PR_IWOTH)) // any write
2538 mode |= _S_IWRITE;
2540 if (_wchmod(mWorkingPath.get(), mode) == -1)
2541 return NS_ERROR_FAILURE;
2543 return NS_OK;
2544 }
2547 NS_IMETHODIMP
2548 nsLocalFile::GetFileSize(int64_t *aFileSize)
2549 {
2550 if (NS_WARN_IF(!aFileSize))
2551 return NS_ERROR_INVALID_ARG;
2553 nsresult rv = ResolveAndStat();
2554 if (NS_FAILED(rv))
2555 return rv;
2557 *aFileSize = mFileInfo64.size;
2558 return NS_OK;
2559 }
2562 NS_IMETHODIMP
2563 nsLocalFile::GetFileSizeOfLink(int64_t *aFileSize)
2564 {
2565 // Check we are correctly initialized.
2566 CHECK_mWorkingPath();
2568 if (NS_WARN_IF(!aFileSize))
2569 return NS_ERROR_INVALID_ARG;
2571 // The caller is assumed to have already called IsSymlink
2572 // and to have found that this file is a link.
2574 PRFileInfo64 info;
2575 if (NS_FAILED(GetFileInfo(mWorkingPath, &info)))
2576 return NS_ERROR_FILE_INVALID_PATH;
2578 *aFileSize = info.size;
2579 return NS_OK;
2580 }
2582 NS_IMETHODIMP
2583 nsLocalFile::SetFileSize(int64_t aFileSize)
2584 {
2585 // Check we are correctly initialized.
2586 CHECK_mWorkingPath();
2588 nsresult rv = ResolveAndStat();
2589 if (NS_FAILED(rv))
2590 return rv;
2592 HANDLE hFile = ::CreateFileW(mResolvedPath.get(),// pointer to name of the file
2593 GENERIC_WRITE, // access (write) mode
2594 FILE_SHARE_READ, // share mode
2595 nullptr, // pointer to security attributes
2596 OPEN_EXISTING, // how to create
2597 FILE_ATTRIBUTE_NORMAL, // file attributes
2598 nullptr);
2599 if (hFile == INVALID_HANDLE_VALUE)
2600 {
2601 return ConvertWinError(GetLastError());
2602 }
2604 // seek the file pointer to the new, desired end of file
2605 // and then truncate the file at that position
2606 rv = NS_ERROR_FAILURE;
2607 aFileSize = MyFileSeek64(hFile, aFileSize, FILE_BEGIN);
2608 if (aFileSize != -1 && SetEndOfFile(hFile))
2609 {
2610 MakeDirty();
2611 rv = NS_OK;
2612 }
2614 CloseHandle(hFile);
2615 return rv;
2616 }
2618 NS_IMETHODIMP
2619 nsLocalFile::GetDiskSpaceAvailable(int64_t *aDiskSpaceAvailable)
2620 {
2621 // Check we are correctly initialized.
2622 CHECK_mWorkingPath();
2624 if (NS_WARN_IF(!aDiskSpaceAvailable))
2625 return NS_ERROR_INVALID_ARG;
2627 ResolveAndStat();
2629 if (mFileInfo64.type == PR_FILE_FILE) {
2630 // Since GetDiskFreeSpaceExW works only on directories, use the parent.
2631 nsCOMPtr<nsIFile> parent;
2632 if (NS_SUCCEEDED(GetParent(getter_AddRefs(parent))) && parent) {
2633 return parent->GetDiskSpaceAvailable(aDiskSpaceAvailable);
2634 }
2635 }
2637 ULARGE_INTEGER liFreeBytesAvailableToCaller, liTotalNumberOfBytes;
2638 if (::GetDiskFreeSpaceExW(mResolvedPath.get(), &liFreeBytesAvailableToCaller,
2639 &liTotalNumberOfBytes, nullptr))
2640 {
2641 *aDiskSpaceAvailable = liFreeBytesAvailableToCaller.QuadPart;
2642 return NS_OK;
2643 }
2644 *aDiskSpaceAvailable = 0;
2645 return NS_OK;
2646 }
2648 NS_IMETHODIMP
2649 nsLocalFile::GetParent(nsIFile * *aParent)
2650 {
2651 // Check we are correctly initialized.
2652 CHECK_mWorkingPath();
2654 if (NS_WARN_IF(!aParent))
2655 return NS_ERROR_INVALID_ARG;
2657 // A two-character path must be a drive such as C:, so it has no parent
2658 if (mWorkingPath.Length() == 2) {
2659 *aParent = nullptr;
2660 return NS_OK;
2661 }
2663 int32_t offset = mWorkingPath.RFindChar(char16_t('\\'));
2664 // adding this offset check that was removed in bug 241708 fixes mail
2665 // directories that aren't relative to/underneath the profile dir.
2666 // e.g., on a different drive. Before you remove them, please make
2667 // sure local mail directories that aren't underneath the profile dir work.
2668 if (offset == kNotFound)
2669 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
2671 // A path of the form \\NAME is a top-level path and has no parent
2672 if (offset == 1 && mWorkingPath[0] == L'\\') {
2673 *aParent = nullptr;
2674 return NS_OK;
2675 }
2677 nsAutoString parentPath(mWorkingPath);
2679 if (offset > 0)
2680 parentPath.Truncate(offset);
2681 else
2682 parentPath.AssignLiteral("\\\\.");
2684 nsCOMPtr<nsIFile> localFile;
2685 nsresult rv = NS_NewLocalFile(parentPath, mFollowSymlinks, getter_AddRefs(localFile));
2687 if (NS_FAILED(rv)) {
2688 return rv;
2689 }
2691 localFile.forget(aParent);
2692 return NS_OK;
2693 }
2695 NS_IMETHODIMP
2696 nsLocalFile::Exists(bool *_retval)
2697 {
2698 // Check we are correctly initialized.
2699 CHECK_mWorkingPath();
2701 if (NS_WARN_IF(!_retval))
2702 return NS_ERROR_INVALID_ARG;
2703 *_retval = false;
2705 MakeDirty();
2706 nsresult rv = ResolveAndStat();
2707 *_retval = NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_IS_LOCKED;
2709 return NS_OK;
2710 }
2712 NS_IMETHODIMP
2713 nsLocalFile::IsWritable(bool *aIsWritable)
2714 {
2715 // Check we are correctly initialized.
2716 CHECK_mWorkingPath();
2718 // The read-only attribute on a FAT directory only means that it can't
2719 // be deleted. It is still possible to modify the contents of the directory.
2720 nsresult rv = IsDirectory(aIsWritable);
2721 if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
2722 *aIsWritable = true;
2723 return NS_OK;
2724 } else if (rv == NS_ERROR_FILE_IS_LOCKED) {
2725 // If the file is normally allowed write access
2726 // we should still return that the file is writable.
2727 } else if (NS_FAILED(rv)) {
2728 return rv;
2729 }
2730 if (*aIsWritable)
2731 return NS_OK;
2733 // writable if the file doesn't have the readonly attribute
2734 rv = HasFileAttribute(FILE_ATTRIBUTE_READONLY, aIsWritable);
2735 if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
2736 *aIsWritable = false;
2737 return NS_OK;
2738 } else if (rv == NS_ERROR_FILE_IS_LOCKED) {
2739 // If the file is normally allowed write access
2740 // we should still return that the file is writable.
2741 } else if (NS_FAILED(rv)) {
2742 return rv;
2743 }
2744 *aIsWritable = !*aIsWritable;
2746 // If the read only attribute is not set, check to make sure
2747 // we can open the file with write access.
2748 if (*aIsWritable) {
2749 PRFileDesc* file;
2750 rv = OpenFile(mResolvedPath, PR_WRONLY, 0, &file);
2751 if (NS_SUCCEEDED(rv)) {
2752 PR_Close(file);
2753 } else if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
2754 *aIsWritable = false;
2755 } else if (rv == NS_ERROR_FILE_IS_LOCKED) {
2756 // If it is locked and read only we would have
2757 // gotten access denied
2758 *aIsWritable = true;
2759 } else {
2760 return rv;
2761 }
2762 }
2763 return NS_OK;
2764 }
2766 NS_IMETHODIMP
2767 nsLocalFile::IsReadable(bool *_retval)
2768 {
2769 // Check we are correctly initialized.
2770 CHECK_mWorkingPath();
2772 if (NS_WARN_IF(!_retval))
2773 return NS_ERROR_INVALID_ARG;
2774 *_retval = false;
2776 nsresult rv = ResolveAndStat();
2777 if (NS_FAILED(rv))
2778 return rv;
2780 *_retval = true;
2781 return NS_OK;
2782 }
2785 NS_IMETHODIMP
2786 nsLocalFile::IsExecutable(bool *_retval)
2787 {
2788 // Check we are correctly initialized.
2789 CHECK_mWorkingPath();
2791 if (NS_WARN_IF(!_retval))
2792 return NS_ERROR_INVALID_ARG;
2793 *_retval = false;
2795 nsresult rv;
2797 // only files can be executables
2798 bool isFile;
2799 rv = IsFile(&isFile);
2800 if (NS_FAILED(rv))
2801 return rv;
2802 if (!isFile)
2803 return NS_OK;
2805 //TODO: shouldn't we be checking mFollowSymlinks here?
2806 bool symLink;
2807 rv = IsSymlink(&symLink);
2808 if (NS_FAILED(rv))
2809 return rv;
2811 nsAutoString path;
2812 if (symLink)
2813 GetTarget(path);
2814 else
2815 GetPath(path);
2817 // kill trailing dots and spaces.
2818 int32_t filePathLen = path.Length() - 1;
2819 while(filePathLen > 0 && (path[filePathLen] == L' ' || path[filePathLen] == L'.'))
2820 {
2821 path.Truncate(filePathLen--);
2822 }
2824 // Get extension.
2825 int32_t dotIdx = path.RFindChar(char16_t('.'));
2826 if ( dotIdx != kNotFound ) {
2827 // Convert extension to lower case.
2828 char16_t *p = path.BeginWriting();
2829 for( p+= dotIdx + 1; *p; p++ )
2830 *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0;
2832 // Search for any of the set of executable extensions.
2833 static const char * const executableExts[] = {
2834 "ad",
2835 "ade", // access project extension
2836 "adp",
2837 "air", // Adobe AIR installer
2838 "app", // executable application
2839 "application", // from bug 348763
2840 "asp",
2841 "bas",
2842 "bat",
2843 "chm",
2844 "cmd",
2845 "com",
2846 "cpl",
2847 "crt",
2848 "exe",
2849 "fxp", // FoxPro compiled app
2850 "hlp",
2851 "hta",
2852 "inf",
2853 "ins",
2854 "isp",
2855 "jar", // java application bundle
2856 "js",
2857 "jse",
2858 "lnk",
2859 "mad", // Access Module Shortcut
2860 "maf", // Access
2861 "mag", // Access Diagram Shortcut
2862 "mam", // Access Macro Shortcut
2863 "maq", // Access Query Shortcut
2864 "mar", // Access Report Shortcut
2865 "mas", // Access Stored Procedure
2866 "mat", // Access Table Shortcut
2867 "mau", // Media Attachment Unit
2868 "mav", // Access View Shortcut
2869 "maw", // Access Data Access Page
2870 "mda", // Access Add-in, MDA Access 2 Workgroup
2871 "mdb",
2872 "mde",
2873 "mdt", // Access Add-in Data
2874 "mdw", // Access Workgroup Information
2875 "mdz", // Access Wizard Template
2876 "msc",
2877 "msh", // Microsoft Shell
2878 "mshxml", // Microsoft Shell
2879 "msi",
2880 "msp",
2881 "mst",
2882 "ops", // Office Profile Settings
2883 "pcd",
2884 "pif",
2885 "plg", // Developer Studio Build Log
2886 "prf", // windows system file
2887 "prg",
2888 "pst",
2889 "reg",
2890 "scf", // Windows explorer command
2891 "scr",
2892 "sct",
2893 "shb",
2894 "shs",
2895 "url",
2896 "vb",
2897 "vbe",
2898 "vbs",
2899 "vsd",
2900 "vsmacros", // Visual Studio .NET Binary-based Macro Project
2901 "vss",
2902 "vst",
2903 "vsw",
2904 "ws",
2905 "wsc",
2906 "wsf",
2907 "wsh"};
2908 nsDependentSubstring ext = Substring(path, dotIdx + 1);
2909 for ( size_t i = 0; i < ArrayLength(executableExts); i++ ) {
2910 if ( ext.EqualsASCII(executableExts[i])) {
2911 // Found a match. Set result and quit.
2912 *_retval = true;
2913 break;
2914 }
2915 }
2916 }
2918 return NS_OK;
2919 }
2922 NS_IMETHODIMP
2923 nsLocalFile::IsDirectory(bool *_retval)
2924 {
2925 return HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, _retval);
2926 }
2928 NS_IMETHODIMP
2929 nsLocalFile::IsFile(bool *_retval)
2930 {
2931 nsresult rv = HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, _retval);
2932 if (NS_SUCCEEDED(rv)) {
2933 *_retval = !*_retval;
2934 }
2935 return rv;
2936 }
2938 NS_IMETHODIMP
2939 nsLocalFile::IsHidden(bool *_retval)
2940 {
2941 return HasFileAttribute(FILE_ATTRIBUTE_HIDDEN, _retval);
2942 }
2944 nsresult
2945 nsLocalFile::HasFileAttribute(DWORD fileAttrib, bool *_retval)
2946 {
2947 if (NS_WARN_IF(!_retval))
2948 return NS_ERROR_INVALID_ARG;
2950 nsresult rv = Resolve();
2951 if (NS_FAILED(rv)) {
2952 return rv;
2953 }
2955 DWORD attributes = GetFileAttributesW(mResolvedPath.get());
2956 if (INVALID_FILE_ATTRIBUTES == attributes) {
2957 return ConvertWinError(GetLastError());
2958 }
2960 *_retval = ((attributes & fileAttrib) != 0);
2961 return NS_OK;
2962 }
2964 NS_IMETHODIMP
2965 nsLocalFile::IsSymlink(bool *_retval)
2966 {
2967 // Check we are correctly initialized.
2968 CHECK_mWorkingPath();
2970 if (NS_WARN_IF(!_retval))
2971 return NS_ERROR_INVALID_ARG;
2973 // unless it is a valid shortcut path it's not a symlink
2974 if (!IsShortcutPath(mWorkingPath)) {
2975 *_retval = false;
2976 return NS_OK;
2977 }
2979 // we need to know if this is a file or directory
2980 nsresult rv = ResolveAndStat();
2981 if (NS_FAILED(rv)) {
2982 return rv;
2983 }
2985 // We should not check mFileInfo64.type here for PR_FILE_FILE because lnk
2986 // files can point to directories or files. Important security checks
2987 // depend on correctly identifying lnk files. mFileInfo64 now holds info
2988 // about the target of the lnk file, not the actual lnk file!
2989 *_retval = true;
2990 return NS_OK;
2991 }
2993 NS_IMETHODIMP
2994 nsLocalFile::IsSpecial(bool *_retval)
2995 {
2996 return HasFileAttribute(FILE_ATTRIBUTE_SYSTEM, _retval);
2997 }
2999 NS_IMETHODIMP
3000 nsLocalFile::Equals(nsIFile *inFile, bool *_retval)
3001 {
3002 if (NS_WARN_IF(!inFile))
3003 return NS_ERROR_INVALID_ARG;
3004 if (NS_WARN_IF(!_retval))
3005 return NS_ERROR_INVALID_ARG;
3007 EnsureShortPath();
3009 nsCOMPtr<nsILocalFileWin> lf(do_QueryInterface(inFile));
3010 if (!lf) {
3011 *_retval = false;
3012 return NS_OK;
3013 }
3015 nsAutoString inFilePath;
3016 lf->GetCanonicalPath(inFilePath);
3018 // Ok : Win9x
3019 *_retval = _wcsicmp(mShortWorkingPath.get(), inFilePath.get()) == 0;
3021 return NS_OK;
3022 }
3025 NS_IMETHODIMP
3026 nsLocalFile::Contains(nsIFile *inFile, bool recur, bool *_retval)
3027 {
3028 // Check we are correctly initialized.
3029 CHECK_mWorkingPath();
3031 *_retval = false;
3033 nsAutoString myFilePath;
3034 if (NS_FAILED(GetTarget(myFilePath)))
3035 GetPath(myFilePath);
3037 uint32_t myFilePathLen = myFilePath.Length();
3039 nsAutoString inFilePath;
3040 if (NS_FAILED(inFile->GetTarget(inFilePath)))
3041 inFile->GetPath(inFilePath);
3043 // make sure that the |inFile|'s path has a trailing separator.
3044 if (inFilePath.Length() >= myFilePathLen && inFilePath[myFilePathLen] == L'\\')
3045 {
3046 if (_wcsnicmp(myFilePath.get(), inFilePath.get(), myFilePathLen) == 0)
3047 {
3048 *_retval = true;
3049 }
3051 }
3053 return NS_OK;
3054 }
3057 NS_IMETHODIMP
3058 nsLocalFile::GetTarget(nsAString &_retval)
3059 {
3060 _retval.Truncate();
3061 #if STRICT_FAKE_SYMLINKS
3062 bool symLink;
3064 nsresult rv = IsSymlink(&symLink);
3065 if (NS_FAILED(rv))
3066 return rv;
3068 if (!symLink)
3069 {
3070 return NS_ERROR_FILE_INVALID_PATH;
3071 }
3072 #endif
3073 ResolveAndStat();
3075 _retval = mResolvedPath;
3076 return NS_OK;
3077 }
3080 /* attribute bool followLinks; */
3081 NS_IMETHODIMP
3082 nsLocalFile::GetFollowLinks(bool *aFollowLinks)
3083 {
3084 *aFollowLinks = mFollowSymlinks;
3085 return NS_OK;
3086 }
3087 NS_IMETHODIMP
3088 nsLocalFile::SetFollowLinks(bool aFollowLinks)
3089 {
3090 MakeDirty();
3091 mFollowSymlinks = aFollowLinks;
3092 return NS_OK;
3093 }
3096 NS_IMETHODIMP
3097 nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator * *entries)
3098 {
3099 nsresult rv;
3101 *entries = nullptr;
3102 if (mWorkingPath.EqualsLiteral("\\\\.")) {
3103 nsDriveEnumerator *drives = new nsDriveEnumerator;
3104 if (!drives)
3105 return NS_ERROR_OUT_OF_MEMORY;
3106 NS_ADDREF(drives);
3107 rv = drives->Init();
3108 if (NS_FAILED(rv)) {
3109 NS_RELEASE(drives);
3110 return rv;
3111 }
3112 *entries = drives;
3113 return NS_OK;
3114 }
3116 nsDirEnumerator* dirEnum = new nsDirEnumerator();
3117 if (dirEnum == nullptr)
3118 return NS_ERROR_OUT_OF_MEMORY;
3119 NS_ADDREF(dirEnum);
3120 rv = dirEnum->Init(this);
3121 if (NS_FAILED(rv))
3122 {
3123 NS_RELEASE(dirEnum);
3124 return rv;
3125 }
3127 *entries = dirEnum;
3129 return NS_OK;
3130 }
3132 NS_IMETHODIMP
3133 nsLocalFile::GetPersistentDescriptor(nsACString &aPersistentDescriptor)
3134 {
3135 CopyUTF16toUTF8(mWorkingPath, aPersistentDescriptor);
3136 return NS_OK;
3137 }
3139 NS_IMETHODIMP
3140 nsLocalFile::SetPersistentDescriptor(const nsACString &aPersistentDescriptor)
3141 {
3142 if (IsUTF8(aPersistentDescriptor))
3143 return InitWithPath(NS_ConvertUTF8toUTF16(aPersistentDescriptor));
3144 else
3145 return InitWithNativePath(aPersistentDescriptor);
3146 }
3148 /* attrib unsigned long fileAttributesWin; */
3149 NS_IMETHODIMP
3150 nsLocalFile::GetFileAttributesWin(uint32_t *aAttribs)
3151 {
3152 *aAttribs = 0;
3153 DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get());
3154 if (dwAttrs == INVALID_FILE_ATTRIBUTES)
3155 return NS_ERROR_FILE_INVALID_PATH;
3157 if (!(dwAttrs & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED))
3158 *aAttribs |= WFA_SEARCH_INDEXED;
3160 return NS_OK;
3161 }
3163 NS_IMETHODIMP
3164 nsLocalFile::SetFileAttributesWin(uint32_t aAttribs)
3165 {
3166 DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get());
3167 if (dwAttrs == INVALID_FILE_ATTRIBUTES)
3168 return NS_ERROR_FILE_INVALID_PATH;
3170 if (aAttribs & WFA_SEARCH_INDEXED) {
3171 dwAttrs &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
3172 } else {
3173 dwAttrs |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
3174 }
3176 if (aAttribs & WFA_READONLY) {
3177 dwAttrs |= FILE_ATTRIBUTE_READONLY;
3178 } else if ((aAttribs & WFA_READWRITE) &&
3179 (dwAttrs & FILE_ATTRIBUTE_READONLY)) {
3180 dwAttrs &= ~FILE_ATTRIBUTE_READONLY;
3181 }
3183 if (SetFileAttributesW(mWorkingPath.get(), dwAttrs) == 0)
3184 return NS_ERROR_FAILURE;
3185 return NS_OK;
3186 }
3189 NS_IMETHODIMP
3190 nsLocalFile::Reveal()
3191 {
3192 // This API should be main thread only
3193 MOZ_ASSERT(NS_IsMainThread());
3195 // make sure mResolvedPath is set
3196 nsresult rv = Resolve();
3197 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
3198 return rv;
3199 }
3201 // To create a new thread, get the thread manager
3202 nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID);
3203 nsCOMPtr<nsIThread> mythread;
3204 rv = tm->NewThread(0, 0, getter_AddRefs(mythread));
3205 if (NS_FAILED(rv)) {
3206 return rv;
3207 }
3209 nsCOMPtr<nsIRunnable> runnable =
3210 new AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::RevealOp,
3211 mResolvedPath);
3213 // After the dispatch, the result runnable will shut down the worker
3214 // thread, so we can let it go.
3215 mythread->Dispatch(runnable, NS_DISPATCH_NORMAL);
3216 return NS_OK;
3217 }
3219 NS_IMETHODIMP
3220 nsLocalFile::Launch()
3221 {
3222 // This API should be main thread only
3223 MOZ_ASSERT(NS_IsMainThread());
3225 // make sure mResolvedPath is set
3226 nsresult rv = Resolve();
3227 if (NS_FAILED(rv))
3228 return rv;
3230 // To create a new thread, get the thread manager
3231 nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID);
3232 nsCOMPtr<nsIThread> mythread;
3233 rv = tm->NewThread(0, 0, getter_AddRefs(mythread));
3234 if (NS_FAILED(rv)) {
3235 return rv;
3236 }
3238 nsCOMPtr<nsIRunnable> runnable =
3239 new AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::LaunchOp,
3240 mResolvedPath);
3242 // After the dispatch, the result runnable will shut down the worker
3243 // thread, so we can let it go.
3244 mythread->Dispatch(runnable, NS_DISPATCH_NORMAL);
3245 return NS_OK;
3246 }
3248 nsresult
3249 NS_NewLocalFile(const nsAString &path, bool followLinks, nsIFile* *result)
3250 {
3251 nsLocalFile* file = new nsLocalFile();
3252 if (file == nullptr)
3253 return NS_ERROR_OUT_OF_MEMORY;
3254 NS_ADDREF(file);
3256 file->SetFollowLinks(followLinks);
3258 if (!path.IsEmpty()) {
3259 nsresult rv = file->InitWithPath(path);
3260 if (NS_FAILED(rv)) {
3261 NS_RELEASE(file);
3262 return rv;
3263 }
3264 }
3266 *result = file;
3267 return NS_OK;
3268 }
3270 //-----------------------------------------------------------------------------
3271 // Native (lossy) interface
3272 //-----------------------------------------------------------------------------
3274 NS_IMETHODIMP
3275 nsLocalFile::InitWithNativePath(const nsACString &filePath)
3276 {
3277 nsAutoString tmp;
3278 nsresult rv = NS_CopyNativeToUnicode(filePath, tmp);
3279 if (NS_SUCCEEDED(rv))
3280 return InitWithPath(tmp);
3282 return rv;
3283 }
3285 NS_IMETHODIMP
3286 nsLocalFile::AppendNative(const nsACString &node)
3287 {
3288 nsAutoString tmp;
3289 nsresult rv = NS_CopyNativeToUnicode(node, tmp);
3290 if (NS_SUCCEEDED(rv))
3291 return Append(tmp);
3293 return rv;
3294 }
3296 NS_IMETHODIMP
3297 nsLocalFile::AppendRelativeNativePath(const nsACString &node)
3298 {
3299 nsAutoString tmp;
3300 nsresult rv = NS_CopyNativeToUnicode(node, tmp);
3301 if (NS_SUCCEEDED(rv))
3302 return AppendRelativePath(tmp);
3303 return rv;
3304 }
3307 NS_IMETHODIMP
3308 nsLocalFile::GetNativeLeafName(nsACString &aLeafName)
3309 {
3310 //NS_WARNING("This API is lossy. Use GetLeafName !");
3311 nsAutoString tmp;
3312 nsresult rv = GetLeafName(tmp);
3313 if (NS_SUCCEEDED(rv))
3314 rv = NS_CopyUnicodeToNative(tmp, aLeafName);
3316 return rv;
3317 }
3319 NS_IMETHODIMP
3320 nsLocalFile::SetNativeLeafName(const nsACString &aLeafName)
3321 {
3322 nsAutoString tmp;
3323 nsresult rv = NS_CopyNativeToUnicode(aLeafName, tmp);
3324 if (NS_SUCCEEDED(rv))
3325 return SetLeafName(tmp);
3327 return rv;
3328 }
3331 NS_IMETHODIMP
3332 nsLocalFile::GetNativePath(nsACString &_retval)
3333 {
3334 //NS_WARNING("This API is lossy. Use GetPath !");
3335 nsAutoString tmp;
3336 nsresult rv = GetPath(tmp);
3337 if (NS_SUCCEEDED(rv))
3338 rv = NS_CopyUnicodeToNative(tmp, _retval);
3340 return rv;
3341 }
3344 NS_IMETHODIMP
3345 nsLocalFile::GetNativeCanonicalPath(nsACString &aResult)
3346 {
3347 NS_WARNING("This method is lossy. Use GetCanonicalPath !");
3348 EnsureShortPath();
3349 NS_CopyUnicodeToNative(mShortWorkingPath, aResult);
3350 return NS_OK;
3351 }
3354 NS_IMETHODIMP
3355 nsLocalFile::CopyToNative(nsIFile *newParentDir, const nsACString &newName)
3356 {
3357 // Check we are correctly initialized.
3358 CHECK_mWorkingPath();
3360 if (newName.IsEmpty())
3361 return CopyTo(newParentDir, EmptyString());
3363 nsAutoString tmp;
3364 nsresult rv = NS_CopyNativeToUnicode(newName, tmp);
3365 if (NS_SUCCEEDED(rv))
3366 return CopyTo(newParentDir, tmp);
3368 return rv;
3369 }
3371 NS_IMETHODIMP
3372 nsLocalFile::CopyToFollowingLinksNative(nsIFile *newParentDir, const nsACString &newName)
3373 {
3374 if (newName.IsEmpty())
3375 return CopyToFollowingLinks(newParentDir, EmptyString());
3377 nsAutoString tmp;
3378 nsresult rv = NS_CopyNativeToUnicode(newName, tmp);
3379 if (NS_SUCCEEDED(rv))
3380 return CopyToFollowingLinks(newParentDir, tmp);
3382 return rv;
3383 }
3385 NS_IMETHODIMP
3386 nsLocalFile::MoveToNative(nsIFile *newParentDir, const nsACString &newName)
3387 {
3388 // Check we are correctly initialized.
3389 CHECK_mWorkingPath();
3391 if (newName.IsEmpty())
3392 return MoveTo(newParentDir, EmptyString());
3394 nsAutoString tmp;
3395 nsresult rv = NS_CopyNativeToUnicode(newName, tmp);
3396 if (NS_SUCCEEDED(rv))
3397 return MoveTo(newParentDir, tmp);
3399 return rv;
3400 }
3402 NS_IMETHODIMP
3403 nsLocalFile::GetNativeTarget(nsACString &_retval)
3404 {
3405 // Check we are correctly initialized.
3406 CHECK_mWorkingPath();
3408 NS_WARNING("This API is lossy. Use GetTarget !");
3409 nsAutoString tmp;
3410 nsresult rv = GetTarget(tmp);
3411 if (NS_SUCCEEDED(rv))
3412 rv = NS_CopyUnicodeToNative(tmp, _retval);
3414 return rv;
3415 }
3417 nsresult
3418 NS_NewNativeLocalFile(const nsACString &path, bool followLinks, nsIFile* *result)
3419 {
3420 nsAutoString buf;
3421 nsresult rv = NS_CopyNativeToUnicode(path, buf);
3422 if (NS_FAILED(rv)) {
3423 *result = nullptr;
3424 return rv;
3425 }
3426 return NS_NewLocalFile(buf, followLinks, result);
3427 }
3429 void
3430 nsLocalFile::EnsureShortPath()
3431 {
3432 if (!mShortWorkingPath.IsEmpty())
3433 return;
3435 WCHAR shortPath[MAX_PATH + 1];
3436 DWORD lengthNeeded = ::GetShortPathNameW(mWorkingPath.get(), shortPath,
3437 ArrayLength(shortPath));
3438 // If an error occurred then lengthNeeded is set to 0 or the length of the
3439 // needed buffer including null termination. If it succeeds the number of
3440 // wide characters not including null termination is returned.
3441 if (lengthNeeded != 0 && lengthNeeded < ArrayLength(shortPath))
3442 mShortWorkingPath.Assign(shortPath);
3443 else
3444 mShortWorkingPath.Assign(mWorkingPath);
3445 }
3447 // nsIHashable
3449 NS_IMETHODIMP
3450 nsLocalFile::Equals(nsIHashable* aOther, bool *aResult)
3451 {
3452 nsCOMPtr<nsIFile> otherfile(do_QueryInterface(aOther));
3453 if (!otherfile) {
3454 *aResult = false;
3455 return NS_OK;
3456 }
3458 return Equals(otherfile, aResult);
3459 }
3461 NS_IMETHODIMP
3462 nsLocalFile::GetHashCode(uint32_t *aResult)
3463 {
3464 // In order for short and long path names to hash to the same value we
3465 // always hash on the short pathname.
3466 EnsureShortPath();
3468 *aResult = HashString(mShortWorkingPath);
3469 return NS_OK;
3470 }
3472 //-----------------------------------------------------------------------------
3473 // nsLocalFile <static members>
3474 //-----------------------------------------------------------------------------
3476 void
3477 nsLocalFile::GlobalInit()
3478 {
3479 DebugOnly<nsresult> rv = NS_CreateShortcutResolver();
3480 NS_ASSERTION(NS_SUCCEEDED(rv), "Shortcut resolver could not be created");
3481 }
3483 void
3484 nsLocalFile::GlobalShutdown()
3485 {
3486 NS_DestroyShortcutResolver();
3487 }
3489 NS_IMPL_ISUPPORTS(nsDriveEnumerator, nsISimpleEnumerator)
3491 nsDriveEnumerator::nsDriveEnumerator()
3492 {
3493 }
3495 nsDriveEnumerator::~nsDriveEnumerator()
3496 {
3497 }
3499 nsresult nsDriveEnumerator::Init()
3500 {
3501 /* If the length passed to GetLogicalDriveStrings is smaller
3502 * than the length of the string it would return, it returns
3503 * the length required for the string. */
3504 DWORD length = GetLogicalDriveStringsW(0, 0);
3505 /* The string is null terminated */
3506 if (!mDrives.SetLength(length+1, fallible_t()))
3507 return NS_ERROR_OUT_OF_MEMORY;
3508 if (!GetLogicalDriveStringsW(length, wwc(mDrives.BeginWriting())))
3509 return NS_ERROR_FAILURE;
3510 mDrives.BeginReading(mStartOfCurrentDrive);
3511 mDrives.EndReading(mEndOfDrivesString);
3512 return NS_OK;
3513 }
3515 NS_IMETHODIMP nsDriveEnumerator::HasMoreElements(bool *aHasMore)
3516 {
3517 *aHasMore = *mStartOfCurrentDrive != L'\0';
3518 return NS_OK;
3519 }
3521 NS_IMETHODIMP nsDriveEnumerator::GetNext(nsISupports **aNext)
3522 {
3523 /* GetLogicalDrives stored in mDrives is a concatenation
3524 * of null terminated strings, followed by a null terminator.
3525 * mStartOfCurrentDrive is an iterator pointing at the first
3526 * character of the current drive. */
3527 if (*mStartOfCurrentDrive == L'\0') {
3528 *aNext = nullptr;
3529 return NS_OK;
3530 }
3532 nsAString::const_iterator driveEnd = mStartOfCurrentDrive;
3533 FindCharInReadable(L'\0', driveEnd, mEndOfDrivesString);
3534 nsString drive(Substring(mStartOfCurrentDrive, driveEnd));
3535 mStartOfCurrentDrive = ++driveEnd;
3537 nsIFile *file;
3538 nsresult rv = NS_NewLocalFile(drive, false, &file);
3540 *aNext = file;
3541 return rv;
3542 }