On x86 compilers without fastcall, simulate it when invoking traces and un-simulate...
[wine-gecko.git] / xpcom / io / nsLocalFileWin.cpp
blob5e24c0b2536e8e6c96a00cf5e16b937b5b36e8b0
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla Communicator client code, released
16 * March 31, 1998.
18 * The Initial Developer of the Original Code is
19 * Netscape Communications Corporation.
20 * Portions created by the Initial Developer are Copyright (C) 1998-1999
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
24 * Doug Turner <dougt@netscape.com>
25 * Dean Tessman <dean_tessman@hotmail.com>
26 * Brodie Thiesfield <brofield@jellycan.com>
27 * Jungshik Shin <jshin@i18nl10n.com>
29 * Alternatively, the contents of this file may be used under the terms of
30 * either of the GNU General Public License Version 2 or later (the "GPL"),
31 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32 * in which case the provisions of the GPL or the LGPL are applicable instead
33 * of those above. If you wish to allow use of your version of this file only
34 * under the terms of either the GPL or the LGPL, and not to allow others to
35 * use your version of this file under the terms of the MPL, indicate your
36 * decision by deleting the provisions above and replace them with the notice
37 * and other provisions required by the GPL or the LGPL. If you do not delete
38 * the provisions above, a recipient may use your version of this file under
39 * the terms of any one of the MPL, the GPL or the LGPL.
41 * ***** END LICENSE BLOCK ***** */
44 #include "nsCOMPtr.h"
45 #include "nsMemory.h"
47 #include "nsLocalFile.h"
48 #include "nsIDirectoryEnumerator.h"
49 #include "nsNativeCharsetUtils.h"
51 #include "nsISimpleEnumerator.h"
52 #include "nsIComponentManager.h"
53 #include "prtypes.h"
54 #include "prio.h"
55 #include "private/pprio.h" // To get PR_ImportFile
56 #include "prprf.h"
57 #include "prmem.h"
58 #include "nsHashKeys.h"
60 #include "nsXPIDLString.h"
61 #include "nsReadableUtils.h"
63 #include <direct.h>
64 #include <windows.h>
65 #include <aclapi.h>
67 #include "shellapi.h"
68 #include "shlguid.h"
70 #include <io.h>
71 #include <stdio.h>
72 #include <stdlib.h>
73 #include <mbstring.h>
75 #include "nsXPIDLString.h"
76 #include "prproces.h"
77 #include "nsITimelineService.h"
79 #include "nsAutoLock.h"
80 #include "SpecialSystemDirectory.h"
82 #include "nsTraceRefcntImpl.h"
84 #define CHECK_mWorkingPath() \
85 PR_BEGIN_MACRO \
86 if (mWorkingPath.IsEmpty()) \
87 return NS_ERROR_NOT_INITIALIZED; \
88 PR_END_MACRO
90 // _mbsstr isn't declared in w32api headers but it's there in the libs
91 #ifdef __MINGW32__
92 extern "C" {
93 unsigned char *_mbsstr( const unsigned char *str,
94 const unsigned char *substr );
96 #endif
98 class nsDriveEnumerator : public nsISimpleEnumerator
100 public:
101 nsDriveEnumerator();
102 virtual ~nsDriveEnumerator();
103 NS_DECL_ISUPPORTS
104 NS_DECL_NSISIMPLEENUMERATOR
105 nsresult Init();
106 private:
107 /* mDrives and mLetter share data
108 * Init sets them.
109 * HasMoreElements reads mLetter.
110 * GetNext advances mLetter.
112 nsCString mDrives;
113 const char *mLetter;
116 //----------------------------------------------------------------------------
117 // short cut resolver
118 //----------------------------------------------------------------------------
119 #ifndef WINCE
120 class ShortcutResolver
122 public:
123 ShortcutResolver();
124 // nonvirtual since we're not subclassed
125 ~ShortcutResolver();
127 nsresult Init();
128 nsresult Resolve(const WCHAR* in, WCHAR* out);
130 private:
131 PRLock* mLock;
132 IPersistFile* mPersistFile;
133 // Win 95 and 98 don't have IShellLinkW
134 IShellLinkW* mShellLink;
137 ShortcutResolver::ShortcutResolver()
139 mLock = nsnull;
140 mPersistFile = nsnull;
141 mShellLink = nsnull;
144 ShortcutResolver::~ShortcutResolver()
146 if (mLock)
147 PR_DestroyLock(mLock);
149 // Release the pointer to the IPersistFile interface.
150 if (mPersistFile)
151 mPersistFile->Release();
153 // Release the pointer to the IShellLink interface.
154 if (mShellLink)
155 mShellLink->Release();
157 CoUninitialize();
160 nsresult
161 ShortcutResolver::Init()
163 CoInitialize(NULL); // FIX: we should probably move somewhere higher up during startup
165 mLock = PR_NewLock();
166 if (!mLock)
167 return NS_ERROR_FAILURE;
169 HRESULT hres;
170 hres = CoCreateInstance(CLSID_ShellLink,
171 NULL,
172 CLSCTX_INPROC_SERVER,
173 IID_IShellLinkW,
174 (void**)&(mShellLink));
175 if (SUCCEEDED(hres))
177 // Get a pointer to the IPersistFile interface.
178 hres = mShellLink->QueryInterface(IID_IPersistFile,
179 (void**)&mPersistFile);
182 if (mPersistFile == nsnull || mShellLink == nsnull)
183 return NS_ERROR_FAILURE;
185 return NS_OK;
188 // |out| must be an allocated buffer of size MAX_PATH
189 nsresult
190 ShortcutResolver::Resolve(const WCHAR* in, WCHAR* out)
192 nsAutoLock lock(mLock);
194 // see if we can Load the path.
195 HRESULT hres = mPersistFile->Load(in, STGM_READ);
197 if (FAILED(hres))
198 return NS_ERROR_FAILURE;
200 // Resolve the link.
201 hres = mShellLink->Resolve(nsnull, SLR_NO_UI);
203 if (FAILED(hres))
204 return NS_ERROR_FAILURE;
206 // Get the path to the link target.
207 hres = mShellLink->GetPath(out, MAX_PATH, NULL, SLGP_UNCPRIORITY);
209 if (FAILED(hres))
210 return NS_ERROR_FAILURE;
211 return NS_OK;
214 static ShortcutResolver * gResolver = nsnull;
216 static nsresult NS_CreateShortcutResolver()
218 gResolver = new ShortcutResolver();
219 if (!gResolver)
220 return NS_ERROR_OUT_OF_MEMORY;
222 return gResolver->Init();
225 static void NS_DestroyShortcutResolver()
227 delete gResolver;
228 gResolver = nsnull;
230 #endif
233 //-----------------------------------------------------------------------------
234 // static helper functions
235 //-----------------------------------------------------------------------------
237 // certainly not all the error that can be
238 // encountered, but many of them common ones
239 static nsresult ConvertWinError(DWORD winErr)
241 nsresult rv;
243 switch (winErr)
245 case ERROR_FILE_NOT_FOUND:
246 case ERROR_PATH_NOT_FOUND:
247 case ERROR_INVALID_DRIVE:
248 rv = NS_ERROR_FILE_NOT_FOUND;
249 break;
250 case ERROR_ACCESS_DENIED:
251 case ERROR_NOT_SAME_DEVICE:
252 rv = NS_ERROR_FILE_ACCESS_DENIED;
253 break;
254 case ERROR_NOT_ENOUGH_MEMORY:
255 case ERROR_INVALID_BLOCK:
256 case ERROR_INVALID_HANDLE:
257 case ERROR_ARENA_TRASHED:
258 rv = NS_ERROR_OUT_OF_MEMORY;
259 break;
260 case ERROR_CURRENT_DIRECTORY:
261 rv = NS_ERROR_FILE_DIR_NOT_EMPTY;
262 break;
263 case ERROR_WRITE_PROTECT:
264 rv = NS_ERROR_FILE_READ_ONLY;
265 break;
266 case ERROR_HANDLE_DISK_FULL:
267 rv = NS_ERROR_FILE_TOO_BIG;
268 break;
269 case ERROR_FILE_EXISTS:
270 case ERROR_ALREADY_EXISTS:
271 case ERROR_CANNOT_MAKE:
272 rv = NS_ERROR_FILE_ALREADY_EXISTS;
273 break;
274 case ERROR_FILENAME_EXCED_RANGE:
275 rv = NS_ERROR_FILE_NAME_TOO_LONG;
276 break;
277 case 0:
278 rv = NS_OK;
279 break;
280 default:
281 rv = NS_ERROR_FAILURE;
282 break;
284 return rv;
287 // definition of INVALID_SET_FILE_POINTER from VC.NET header files
288 // it doesn't appear to be defined by VC6
289 #ifndef INVALID_SET_FILE_POINTER
290 # define INVALID_SET_FILE_POINTER ((DWORD)-1)
291 #endif
292 // same goes for INVALID_FILE_ATTRIBUTES
293 #ifndef INVALID_FILE_ATTRIBUTES
294 # define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
295 #endif
297 // as suggested in the MSDN documentation on SetFilePointer
298 static __int64
299 MyFileSeek64(HANDLE aHandle, __int64 aDistance, DWORD aMoveMethod)
301 LARGE_INTEGER li;
303 li.QuadPart = aDistance;
304 li.LowPart = SetFilePointer(aHandle, li.LowPart, &li.HighPart, aMoveMethod);
305 if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
307 li.QuadPart = -1;
310 return li.QuadPart;
313 static PRBool
314 IsShortcutPath(const nsAString &path)
316 // Under Windows, the shortcuts are just files with a ".lnk" extension.
317 // Note also that we don't resolve links in the middle of paths.
318 // i.e. "c:\foo.lnk\bar.txt" is invalid.
319 NS_ABORT_IF_FALSE(!path.IsEmpty(), "don't pass an empty string");
320 PRInt32 len = path.Length();
321 return (StringTail(path, 4).LowerCaseEqualsASCII(".lnk"));
324 //-----------------------------------------------------------------------------
325 // We need the following three definitions to make |OpenFile| convert a file
326 // handle to an NSPR file descriptor correctly when |O_APPEND| flag is
327 // specified. It is defined in a private header of NSPR (primpl.h) we can't
328 // include. As a temporary workaround until we decide how to extend
329 // |PR_ImportFile|, we define it here. Currently, |_PR_HAVE_PEEK_BUFFER|
330 // and |PR_STRICT_ADDR_LEN| are not defined for the 'w95'-dependent portion
331 // of NSPR so that fields of |PRFilePrivate| #ifdef'd by them are not copied.
332 // Similarly, |_MDFileDesc| is taken from nsprpub/pr/include/md/_win95.h.
333 // In an unlikely case we switch to 'NT'-dependent NSPR AND this temporary
334 // workaround last beyond the switch, |PRFilePrivate| and |_MDFileDesc|
335 // need to be changed to match the definitions for WinNT.
336 //-----------------------------------------------------------------------------
337 typedef enum {
338 _PR_TRI_TRUE = 1,
339 _PR_TRI_FALSE = 0,
340 _PR_TRI_UNKNOWN = -1
341 } _PRTriStateBool;
343 struct _MDFileDesc {
344 PROsfd osfd;
347 struct PRFilePrivate {
348 PRInt32 state;
349 PRBool nonblocking;
350 _PRTriStateBool inheritable;
351 PRFileDesc *next;
352 PRIntn lockCount; /* 0: not locked
353 * -1: a native lockfile call is in progress
354 * > 0: # times the file is locked */
355 PRBool appendMode;
356 _MDFileDesc md;
359 //-----------------------------------------------------------------------------
360 // Six static methods defined below (OpenFile, FileTimeToPRTime, GetFileInfo,
361 // OpenDir, CloseDir, ReadDir) should go away once the corresponding
362 // UTF-16 APIs are implemented on all the supported platforms (or at least
363 // Windows 9x/ME) in NSPR. Currently, they're only implemented on
364 // Windows NT4 or later. (bug 330665)
365 //-----------------------------------------------------------------------------
367 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} :
368 // PR_Open and _PR_MD_OPEN
369 static nsresult
370 OpenFile(const nsAFlatString &name, PRIntn osflags, PRIntn mode,
371 PRFileDesc **fd)
373 // XXX : 'mode' is not translated !!!
374 PRInt32 access = 0;
375 PRInt32 flags = 0;
376 PRInt32 flag6 = 0;
378 if (osflags & PR_SYNC) flag6 = FILE_FLAG_WRITE_THROUGH;
380 if (osflags & PR_RDONLY || osflags & PR_RDWR)
381 access |= GENERIC_READ;
382 if (osflags & PR_WRONLY || osflags & PR_RDWR)
383 access |= GENERIC_WRITE;
385 if ( osflags & PR_CREATE_FILE && osflags & PR_EXCL )
386 flags = CREATE_NEW;
387 else if (osflags & PR_CREATE_FILE) {
388 if (osflags & PR_TRUNCATE)
389 flags = CREATE_ALWAYS;
390 else
391 flags = OPEN_ALWAYS;
392 } else {
393 if (osflags & PR_TRUNCATE)
394 flags = TRUNCATE_EXISTING;
395 else
396 flags = OPEN_EXISTING;
399 HANDLE file = ::CreateFileW(name.get(), access,
400 FILE_SHARE_READ|FILE_SHARE_WRITE,
401 NULL, flags, flag6, NULL);
403 if (file == INVALID_HANDLE_VALUE) {
404 *fd = nsnull;
405 return ConvertWinError(GetLastError());
408 *fd = PR_ImportFile((PROsfd) file);
409 if (*fd) {
410 // On Windows, _PR_HAVE_O_APPEND is not defined so that we have to
411 // add it manually. (see |PR_Open| in nsprpub/pr/src/io/prfile.c)
412 (*fd)->secret->appendMode = (PR_APPEND & osflags) ? PR_TRUE : PR_FALSE;
413 return NS_OK;
416 nsresult rv = NS_ErrorAccordingToNSPR();
418 CloseHandle(file);
420 return rv;
423 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} :
424 // PR_FileTimeToPRTime and _PR_FileTimeToPRTime
425 static
426 void FileTimeToPRTime(const FILETIME *filetime, PRTime *prtm)
428 #ifdef __GNUC__
429 const PRTime _pr_filetime_offset = 116444736000000000LL;
430 #else
431 const PRTime _pr_filetime_offset = 116444736000000000i64;
432 #endif
434 PR_ASSERT(sizeof(FILETIME) == sizeof(PRTime));
435 ::CopyMemory(prtm, filetime, sizeof(PRTime));
436 #ifdef __GNUC__
437 *prtm = (*prtm - _pr_filetime_offset) / 10LL;
438 #else
439 *prtm = (*prtm - _pr_filetime_offset) / 10i64;
440 #endif
443 // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} with some
444 // changes : PR_GetFileInfo64, _PR_MD_GETFILEINFO64
445 static nsresult
446 GetFileInfo(const nsAFlatString &name, PRFileInfo64 *info)
448 WIN32_FILE_ATTRIBUTE_DATA fileData;
450 if (name.IsEmpty() || name.FindCharInSet(L"?*") != kNotFound)
451 return NS_ERROR_INVALID_ARG;
453 if (!::GetFileAttributesExW(name.get(), GetFileExInfoStandard, &fileData))
454 return ConvertWinError(GetLastError());
456 if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
457 info->type = PR_FILE_DIRECTORY;
458 } else {
459 info->type = PR_FILE_FILE;
462 info->size = fileData.nFileSizeHigh;
463 info->size = (info->size << 32) + fileData.nFileSizeLow;
465 FileTimeToPRTime(&fileData.ftLastWriteTime, &info->modifyTime);
467 if (0 == fileData.ftCreationTime.dwLowDateTime &&
468 0 == fileData.ftCreationTime.dwHighDateTime) {
469 info->creationTime = info->modifyTime;
470 } else {
471 FileTimeToPRTime(&fileData.ftCreationTime, &info->creationTime);
474 return NS_OK;
477 struct nsDir
479 HANDLE handle;
480 WIN32_FIND_DATAW data;
481 PRBool firstEntry;
484 static nsresult
485 OpenDir(const nsAFlatString &name, nsDir * *dir)
487 NS_ENSURE_ARG_POINTER(dir);
489 *dir = nsnull;
490 if (name.Length() + 3 >= MAX_PATH)
491 return NS_ERROR_FILE_NAME_TOO_LONG;
493 nsDir *d = PR_NEW(nsDir);
494 if (!d)
495 return NS_ERROR_OUT_OF_MEMORY;
497 nsAutoString filename(name);
499 //If 'name' ends in a slash or backslash, do not append
500 //another backslash.
501 if (filename.Last() == L'/' || filename.Last() == L'\\')
502 filename.AppendASCII("*");
503 else
504 filename.AppendASCII("\\*");
506 filename.ReplaceChar(L'/', L'\\');
508 d->handle = ::FindFirstFileW(filename.get(), &(d->data) );
510 if ( d->handle == INVALID_HANDLE_VALUE )
512 PR_Free(d);
513 return ConvertWinError(GetLastError());
515 d->firstEntry = PR_TRUE;
517 *dir = d;
518 return NS_OK;
521 static nsresult
522 ReadDir(nsDir *dir, PRDirFlags flags, nsString& name)
524 name.Truncate();
525 NS_ENSURE_ARG(dir);
527 while (1) {
528 BOOL rv;
529 if (dir->firstEntry)
531 dir->firstEntry = PR_FALSE;
532 rv = 1;
533 } else
534 rv = ::FindNextFileW(dir->handle, &(dir->data));
536 if (rv == 0)
537 break;
539 const PRUnichar *fileName;
540 nsString tmp;
541 fileName = (dir)->data.cFileName;
543 if ((flags & PR_SKIP_DOT) &&
544 (fileName[0] == L'.') && (fileName[1] == L'\0'))
545 continue;
546 if ((flags & PR_SKIP_DOT_DOT) &&
547 (fileName[0] == L'.') && (fileName[1] == L'.') &&
548 (fileName[2] == L'\0'))
549 continue;
551 DWORD attrib = dir->data.dwFileAttributes;
552 if ((flags & PR_SKIP_HIDDEN) && (attrib & FILE_ATTRIBUTE_HIDDEN))
553 continue;
555 if (fileName == tmp.get())
556 name = tmp;
557 else
558 name = fileName;
559 return NS_OK;
562 DWORD err = GetLastError();
563 return err == ERROR_NO_MORE_FILES ? NS_OK : ConvertWinError(err);
566 static nsresult
567 CloseDir(nsDir *d)
569 NS_ENSURE_ARG(d);
571 BOOL isOk = FindClose(d->handle);
572 PR_DELETE(d);
573 return isOk ? NS_OK : ConvertWinError(GetLastError());
576 //-----------------------------------------------------------------------------
577 // nsDirEnumerator
578 //-----------------------------------------------------------------------------
580 class nsDirEnumerator : public nsISimpleEnumerator,
581 public nsIDirectoryEnumerator
583 public:
585 NS_DECL_ISUPPORTS
587 nsDirEnumerator() : mDir(nsnull)
591 nsresult Init(nsILocalFile* parent)
593 nsAutoString filepath;
594 parent->GetTarget(filepath);
596 if (filepath.IsEmpty())
598 parent->GetPath(filepath);
601 if (filepath.IsEmpty())
603 return NS_ERROR_UNEXPECTED;
606 nsresult rv = OpenDir(filepath, &mDir);
607 if (NS_FAILED(rv))
608 return rv;
610 mParent = parent;
611 return NS_OK;
614 NS_IMETHOD HasMoreElements(PRBool *result)
616 nsresult rv;
617 if (mNext == nsnull && mDir)
619 nsString name;
620 rv = ReadDir(mDir, PR_SKIP_BOTH, name);
621 if (NS_FAILED(rv))
622 return rv;
623 if (name.IsEmpty())
625 // end of dir entries
626 if (NS_FAILED(CloseDir(mDir)))
627 return NS_ERROR_FAILURE;
629 mDir = nsnull;
631 *result = PR_FALSE;
632 return NS_OK;
635 nsCOMPtr<nsIFile> file;
636 rv = mParent->Clone(getter_AddRefs(file));
637 if (NS_FAILED(rv))
638 return rv;
640 rv = file->Append(name);
641 if (NS_FAILED(rv))
642 return rv;
644 // make sure the thing exists. If it does, try the next one.
645 PRBool exists;
646 rv = file->Exists(&exists);
647 if (NS_FAILED(rv) || !exists)
649 return HasMoreElements(result);
652 mNext = do_QueryInterface(file);
654 *result = mNext != nsnull;
655 if (!*result)
656 Close();
657 return NS_OK;
660 NS_IMETHOD GetNext(nsISupports **result)
662 nsresult rv;
663 PRBool hasMore;
664 rv = HasMoreElements(&hasMore);
665 if (NS_FAILED(rv)) return rv;
667 *result = mNext; // might return nsnull
668 NS_IF_ADDREF(*result);
670 mNext = nsnull;
671 return NS_OK;
674 NS_IMETHOD GetNextFile(nsIFile **result)
676 *result = nsnull;
677 PRBool hasMore = PR_FALSE;
678 nsresult rv = HasMoreElements(&hasMore);
679 if (NS_FAILED(rv) || !hasMore)
680 return rv;
681 *result = mNext;
682 NS_IF_ADDREF(*result);
683 mNext = nsnull;
684 return NS_OK;
687 NS_IMETHOD Close()
689 if (mDir)
691 nsresult rv = CloseDir(mDir);
692 NS_ASSERTION(NS_SUCCEEDED(rv), "close failed");
693 if (NS_FAILED(rv))
694 return NS_ERROR_FAILURE;
695 mDir = nsnull;
697 return NS_OK;
700 // dtor can be non-virtual since there are no subclasses, but must be
701 // public to use the class on the stack.
702 ~nsDirEnumerator()
704 Close();
707 protected:
708 nsDir* mDir;
709 nsCOMPtr<nsILocalFile> mParent;
710 nsCOMPtr<nsILocalFile> mNext;
713 NS_IMPL_ISUPPORTS2(nsDirEnumerator, nsISimpleEnumerator, nsIDirectoryEnumerator)
716 //-----------------------------------------------------------------------------
717 // nsLocalFile <public>
718 //-----------------------------------------------------------------------------
720 nsLocalFile::nsLocalFile()
721 : mDirty(PR_TRUE)
722 , mFollowSymlinks(PR_FALSE)
726 NS_METHOD
727 nsLocalFile::nsLocalFileConstructor(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr)
729 NS_ENSURE_ARG_POINTER(aInstancePtr);
730 NS_ENSURE_NO_AGGREGATION(outer);
732 nsLocalFile* inst = new nsLocalFile();
733 if (inst == NULL)
734 return NS_ERROR_OUT_OF_MEMORY;
736 nsresult rv = inst->QueryInterface(aIID, aInstancePtr);
737 if (NS_FAILED(rv))
739 delete inst;
740 return rv;
742 return NS_OK;
746 //-----------------------------------------------------------------------------
747 // nsLocalFile::nsISupports
748 //-----------------------------------------------------------------------------
750 NS_IMPL_THREADSAFE_ISUPPORTS4(nsLocalFile,
751 nsILocalFile,
752 nsIFile,
753 nsILocalFileWin,
754 nsIHashable)
757 //-----------------------------------------------------------------------------
758 // nsLocalFile <private>
759 //-----------------------------------------------------------------------------
761 nsLocalFile::nsLocalFile(const nsLocalFile& other)
762 : mDirty(PR_TRUE)
763 , mFollowSymlinks(other.mFollowSymlinks)
764 , mWorkingPath(other.mWorkingPath)
768 // Resolve the shortcut file from mWorkingPath and write the path
769 // it points to into mResolvedPath.
770 nsresult
771 nsLocalFile::ResolveShortcut()
773 #ifndef WINCE
774 // we can't do anything without the resolver
775 if (!gResolver)
776 return NS_ERROR_FAILURE;
778 mResolvedPath.SetLength(MAX_PATH);
779 if (mResolvedPath.Length() != MAX_PATH)
780 return NS_ERROR_OUT_OF_MEMORY;
782 PRUnichar *resolvedPath = mResolvedPath.BeginWriting();
784 // resolve this shortcut
785 nsresult rv = gResolver->Resolve(mWorkingPath.get(), resolvedPath);
787 size_t len = NS_FAILED(rv) ? 0 : wcslen(resolvedPath);
788 mResolvedPath.SetLength(len);
790 return rv;
791 #else
792 return NS_OK;
793 #endif
796 // Resolve any shortcuts and stat the resolved path. After a successful return
797 // the path is guaranteed valid and the members of mFileInfo64 can be used.
798 nsresult
799 nsLocalFile::ResolveAndStat()
801 // if we aren't dirty then we are already done
802 if (!mDirty)
803 return NS_OK;
805 // we can't resolve/stat anything that isn't a valid NSPR addressable path
806 if (mWorkingPath.IsEmpty())
807 return NS_ERROR_FILE_INVALID_PATH;
809 // this is usually correct
810 mResolvedPath.Assign(mWorkingPath);
812 // slutty hack designed to work around bug 134796 until it is fixed
813 nsAutoString nsprPath(mWorkingPath.get());
814 if (mWorkingPath.Length() == 2 && mWorkingPath.CharAt(1) == L':')
815 nsprPath.AppendASCII("\\");
817 // first we will see if the working path exists. If it doesn't then
818 // there is nothing more that can be done
819 if (NS_FAILED(GetFileInfo(nsprPath, &mFileInfo64)))
820 return NS_ERROR_FILE_NOT_FOUND;
822 // if this isn't a shortcut file or we aren't following symlinks then we're done
823 if (!mFollowSymlinks
824 || mFileInfo64.type != PR_FILE_FILE
825 || !IsShortcutPath(mWorkingPath))
827 mDirty = PR_FALSE;
828 return NS_OK;
831 // we need to resolve this shortcut to what it points to, this will
832 // set mResolvedPath. Even if it fails we need to have the resolved
833 // path equal to working path for those functions that always use
834 // the resolved path.
835 nsresult rv = ResolveShortcut();
836 if (NS_FAILED(rv))
838 mResolvedPath.Assign(mWorkingPath);
839 return rv;
842 // get the details of the resolved path
843 if (NS_FAILED(GetFileInfo(mResolvedPath, &mFileInfo64)))
844 return NS_ERROR_FILE_NOT_FOUND;
846 mDirty = PR_FALSE;
847 return NS_OK;
851 //-----------------------------------------------------------------------------
852 // nsLocalFile::nsIFile,nsILocalFile
853 //-----------------------------------------------------------------------------
855 NS_IMETHODIMP
856 nsLocalFile::Clone(nsIFile **file)
858 // Just copy-construct ourselves
859 *file = new nsLocalFile(*this);
860 if (!*file)
861 return NS_ERROR_OUT_OF_MEMORY;
863 NS_ADDREF(*file);
865 return NS_OK;
868 NS_IMETHODIMP
869 nsLocalFile::InitWithFile(nsILocalFile *aFile)
871 NS_ENSURE_ARG(aFile);
873 nsAutoString path;
874 aFile->GetPath(path);
875 if (path.IsEmpty())
876 return NS_ERROR_INVALID_ARG;
877 return InitWithPath(path);
880 NS_IMETHODIMP
881 nsLocalFile::InitWithPath(const nsAString &filePath)
883 MakeDirty();
885 nsAString::const_iterator begin, end;
886 filePath.BeginReading(begin);
887 filePath.EndReading(end);
889 // input string must not be empty
890 if (begin == end)
891 return NS_ERROR_FAILURE;
893 PRUnichar firstChar = *begin;
894 PRUnichar secondChar = *(++begin);
896 // just do a sanity check. if it has any forward slashes, it is not a Native path
897 // on windows. Also, it must have a colon at after the first char.
899 PRUnichar *path = nsnull;
900 PRInt32 pathLen = 0;
902 if ( ( (secondChar == L':') && !FindCharInReadable(L'/', begin, end) ) || // normal path
903 #ifdef WINCE
904 ( (firstChar == L'\\') ) // wince absolute path or network path
905 #else
906 ( (firstChar == L'\\') && (secondChar == L'\\') ) // network path
907 #endif
910 // This is a native path
911 path = ToNewUnicode(filePath);
912 pathLen = filePath.Length();
915 if (path == nsnull) {
916 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
919 // kill any trailing '\'
920 PRInt32 len = pathLen - 1;
921 if (path[len] == L'\\')
923 path[len] = L'\0';
924 pathLen = len;
927 mWorkingPath.Adopt(path, pathLen);
928 return NS_OK;
932 NS_IMETHODIMP
933 nsLocalFile::OpenNSPRFileDesc(PRInt32 flags, PRInt32 mode, PRFileDesc **_retval)
935 nsresult rv = ResolveAndStat();
936 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)
937 return rv;
939 return OpenFile(mResolvedPath, flags, mode, _retval);
943 NS_IMETHODIMP
944 nsLocalFile::OpenANSIFileDesc(const char *mode, FILE * *_retval)
946 nsresult rv = ResolveAndStat();
947 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)
948 return rv;
950 *_retval = _wfopen(mResolvedPath.get(), NS_ConvertASCIItoUTF16(mode).get());
951 if (*_retval)
952 return NS_OK;
954 return NS_ERROR_FAILURE;
959 NS_IMETHODIMP
960 nsLocalFile::Create(PRUint32 type, PRUint32 attributes)
962 if (type != NORMAL_FILE_TYPE && type != DIRECTORY_TYPE)
963 return NS_ERROR_FILE_UNKNOWN_TYPE;
965 nsresult rv = ResolveAndStat();
966 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)
967 return rv;
969 // create directories to target
971 // A given local file can be either one of these forms:
973 // - normal: X:\some\path\on\this\drive
974 // ^--- start here
976 // - UNC path: \\machine\volume\some\path\on\this\drive
977 // ^--- start here
979 // Skip the first 'X:\' for the first form, and skip the first full
980 // '\\machine\volume\' segment for the second form.
982 PRUnichar* path = mResolvedPath.BeginWriting();
984 if (path[0] == L'\\' && path[1] == L'\\')
986 #ifdef WINCE
987 ++path;
988 #else
989 // dealing with a UNC path here; skip past '\\machine\'
990 path = wcschr(path + 2, L'\\');
991 if (!path)
992 return NS_ERROR_FILE_INVALID_PATH;
993 ++path;
994 #endif
997 // search for first slash after the drive (or volume) name
998 PRUnichar* slash = wcschr(path, L'\\');
1000 if (slash)
1002 // skip the first '\\'
1003 ++slash;
1004 slash = wcschr(slash, L'\\');
1006 while (slash)
1008 *slash = L'\0';
1010 if (!::CreateDirectoryW(mResolvedPath.get(), NULL)) {
1011 rv = ConvertWinError(GetLastError());
1012 // perhaps the base path already exists, or perhaps we don't have
1013 // permissions to create the directory. NOTE: access denied could
1014 // occur on a parent directory even though it exists.
1015 if (rv != NS_ERROR_FILE_ALREADY_EXISTS &&
1016 rv != NS_ERROR_FILE_ACCESS_DENIED)
1017 return rv;
1019 *slash = L'\\';
1020 ++slash;
1021 slash = wcschr(slash, L'\\');
1025 if (type == NORMAL_FILE_TYPE)
1027 PRFileDesc* file;
1028 rv = OpenFile(mResolvedPath,
1029 PR_RDONLY | PR_CREATE_FILE | PR_APPEND | PR_EXCL, attributes,
1030 &file);
1031 if (file)
1032 PR_Close(file);
1033 return rv;
1036 if (type == DIRECTORY_TYPE)
1038 if (!::CreateDirectoryW(mResolvedPath.get(), NULL))
1039 return ConvertWinError(GetLastError());
1040 else
1041 return NS_OK;
1044 return NS_ERROR_FILE_UNKNOWN_TYPE;
1048 NS_IMETHODIMP
1049 nsLocalFile::Append(const nsAString &node)
1051 // append this path, multiple components are not permitted
1052 return AppendInternal(PromiseFlatString(node), PR_FALSE);
1055 NS_IMETHODIMP
1056 nsLocalFile::AppendRelativePath(const nsAString &node)
1058 // append this path, multiple components are permitted
1059 return AppendInternal(PromiseFlatString(node), PR_TRUE);
1063 nsresult
1064 nsLocalFile::AppendInternal(const nsAFlatString &node, PRBool multipleComponents)
1066 if (node.IsEmpty())
1067 return NS_OK;
1069 // check the relative path for validity
1070 if (node.First() == L'\\' // can't start with an '\'
1071 || node.FindChar(L'/') != kNotFound // can't contain /
1072 || node.EqualsASCII("..")) // can't be ..
1073 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1075 #ifndef WINCE // who cares?
1076 if (multipleComponents)
1078 // can't contain .. as a path component. Ensure that the valid components
1079 // "foo..foo", "..foo", and "foo.." are not falsely detected,
1080 // but the invalid paths "..\", "foo\..", "foo\..\foo",
1081 // "..\foo", etc are.
1082 NS_NAMED_LITERAL_STRING(doubleDot, "\\..");
1083 nsAString::const_iterator start, end, offset;
1084 node.BeginReading(start);
1085 node.EndReading(end);
1086 offset = end;
1087 while (FindInReadable(doubleDot, start, offset))
1089 if (offset == end || *offset == L'\\')
1090 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1091 start = offset;
1092 offset = end;
1095 // catches the remaining cases of prefixes
1096 if (StringBeginsWith(node, NS_LITERAL_STRING("..\\")))
1097 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1099 // single components can't contain '\'
1100 else if (node.FindChar(L'\\') != kNotFound)
1101 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1102 #endif
1104 MakeDirty();
1106 mWorkingPath.Append(NS_LITERAL_STRING("\\") + node);
1108 return NS_OK;
1111 #define TOUPPER(u) (((u) >= L'a' && (u) <= L'z') ? \
1112 (u) - (L'a' - L'A') : (u))
1114 NS_IMETHODIMP
1115 nsLocalFile::Normalize()
1117 #ifndef WINCE
1118 // XXX See bug 187957 comment 18 for possible problems with this implementation.
1120 if (mWorkingPath.IsEmpty())
1121 return NS_OK;
1123 nsAutoString path(mWorkingPath);
1125 // find the index of the root backslash for the path. Everything before
1126 // this is considered fully normalized and cannot be ascended beyond
1127 // using ".." For a local drive this is the first slash (e.g. "c:\").
1128 // For a UNC path it is the slash following the share name
1129 // (e.g. "\\server\share\").
1130 PRInt32 rootIdx = 2; // default to local drive
1131 if (path.First() == L'\\') // if a share then calculate the rootIdx
1133 rootIdx = path.FindChar(L'\\', 2); // skip \\ in front of the server
1134 if (rootIdx == kNotFound)
1135 return NS_OK; // already normalized
1136 rootIdx = path.FindChar(L'\\', rootIdx+1);
1137 if (rootIdx == kNotFound)
1138 return NS_OK; // already normalized
1140 else if (path.CharAt(rootIdx) != L'\\')
1142 // The path has been specified relative to the current working directory
1143 // for that drive. To normalize it, the current working directory for
1144 // that drive needs to be inserted before the supplied relative path
1145 // which will provide an absolute path (and the rootIdx will still be 2).
1146 WCHAR cwd[MAX_PATH];
1147 WCHAR * pcwd = cwd;
1148 int drive = TOUPPER(path.First()) - 'A' + 1;
1149 /* We need to worry about IPH, for details read bug 419326.
1150 * _getdrives - http://msdn2.microsoft.com/en-us/library/xdhk0xd2.aspx
1151 * uses a bitmask, bit 0 is 'a:'
1152 * _chdrive - http://msdn2.microsoft.com/en-us/library/0d1409hb.aspx
1153 * _getdcwd - http://msdn2.microsoft.com/en-us/library/7t2zk3s4.aspx
1154 * take an int, 1 is 'a:'.
1156 * Because of this, we need to do some math. Subtract 1 to convert from
1157 * _chdrive/_getdcwd format to _getdrives drive numbering.
1158 * Shift left x bits to convert from integer indexing to bitfield indexing.
1159 * And of course, we need to find out if the drive is in the bitmask.
1161 * If we're really unlucky, we can still lose, but only if the user
1162 * manages to eject the drive between our call to _getdrives() and
1163 * our *calls* to _wgetdcwd.
1165 if (!((1 << (drive - 1)) & _getdrives()))
1166 return NS_ERROR_FILE_INVALID_PATH;
1167 if (!_wgetdcwd(drive, pcwd, MAX_PATH))
1168 pcwd = _wgetdcwd(drive, 0, 0);
1169 if (!pcwd)
1170 return NS_ERROR_OUT_OF_MEMORY;
1172 nsAutoString currentDir(pcwd);
1173 if (pcwd != cwd)
1174 free(pcwd);
1176 if (currentDir.Last() == '\\')
1177 path.Replace(0, 2, currentDir);
1178 else
1179 path.Replace(0, 2, currentDir + NS_LITERAL_STRING("\\"));
1181 NS_POSTCONDITION(0 < rootIdx && rootIdx < (PRInt32)path.Length(), "rootIdx is invalid");
1182 NS_POSTCONDITION(path.CharAt(rootIdx) == '\\', "rootIdx is invalid");
1184 // if there is nothing following the root path then it is already normalized
1185 if (rootIdx + 1 == (PRInt32)path.Length())
1186 return NS_OK;
1188 // assign the root
1189 const PRUnichar * pathBuffer = path.get(); // simplify access to the buffer
1190 mWorkingPath.SetCapacity(path.Length()); // it won't ever grow longer
1191 mWorkingPath.Assign(pathBuffer, rootIdx);
1193 // Normalize the path components. The actions taken are:
1195 // "\\" condense to single backslash
1196 // "." remove from path
1197 // ".." up a directory
1198 // "..." remove from path (any number of dots > 2)
1200 // The last form is something that Windows 95 and 98 supported and
1201 // is a shortcut for changing up multiple directories. Windows XP
1202 // and ilk ignore it in a path, as is done here.
1203 PRInt32 len, begin, end = rootIdx;
1204 while (end < (PRInt32)path.Length())
1206 // find the current segment (text between the backslashes) to
1207 // be examined, this will set the following variables:
1208 // begin == index of first char in segment
1209 // end == index 1 char after last char in segment
1210 // len == length of segment
1211 begin = end + 1;
1212 end = path.FindChar('\\', begin);
1213 if (end == kNotFound)
1214 end = path.Length();
1215 len = end - begin;
1217 // ignore double backslashes
1218 if (len == 0)
1219 continue;
1221 // len != 0, and interesting paths always begin with a dot
1222 if (pathBuffer[begin] == '.')
1224 // ignore single dots
1225 if (len == 1)
1226 continue;
1228 // handle multiple dots
1229 if (len >= 2 && pathBuffer[begin+1] == L'.')
1231 // back up a path component on double dot
1232 if (len == 2)
1234 PRInt32 prev = mWorkingPath.RFindChar('\\');
1235 if (prev >= rootIdx)
1236 mWorkingPath.Truncate(prev);
1237 continue;
1240 // length is > 2 and the first two characters are dots.
1241 // if the rest of the string is dots, then ignore it.
1242 int idx = len - 1;
1243 for (; idx >= 2; --idx)
1245 if (pathBuffer[begin+idx] != L'.')
1246 break;
1249 // this is true if the loop above didn't break
1250 // and all characters in this segment are dots.
1251 if (idx < 2)
1252 continue;
1256 // add the current component to the path, including the preceding backslash
1257 mWorkingPath.Append(pathBuffer + begin - 1, len + 1);
1260 // kill trailing dots and spaces.
1261 PRInt32 filePathLen = mWorkingPath.Length() - 1;
1262 while(filePathLen > 0 && (mWorkingPath[filePathLen] == L' ' ||
1263 mWorkingPath[filePathLen] == L'.'))
1265 mWorkingPath.Truncate(filePathLen--);
1268 MakeDirty();
1269 #else // WINCE
1270 // WINCE FIX
1271 #endif
1272 return NS_OK;
1275 NS_IMETHODIMP
1276 nsLocalFile::GetLeafName(nsAString &aLeafName)
1278 aLeafName.Truncate();
1280 if(mWorkingPath.IsEmpty())
1281 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1283 PRInt32 offset = mWorkingPath.RFindChar(L'\\');
1285 // if the working path is just a node without any lashes.
1286 if (offset == kNotFound)
1287 aLeafName = mWorkingPath;
1288 else
1289 aLeafName = Substring(mWorkingPath, offset + 1);
1291 return NS_OK;
1294 NS_IMETHODIMP
1295 nsLocalFile::SetLeafName(const nsAString &aLeafName)
1297 MakeDirty();
1299 if(mWorkingPath.IsEmpty())
1300 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
1302 // cannot use nsCString::RFindChar() due to 0x5c problem
1303 PRInt32 offset = mWorkingPath.RFindChar(L'\\');
1304 if (offset)
1306 mWorkingPath.Truncate(offset+1);
1308 mWorkingPath.Append(aLeafName);
1310 return NS_OK;
1314 NS_IMETHODIMP
1315 nsLocalFile::GetPath(nsAString &_retval)
1317 _retval = mWorkingPath;
1318 return NS_OK;
1321 NS_IMETHODIMP
1322 nsLocalFile::GetCanonicalPath(nsAString &aResult)
1324 EnsureShortPath();
1325 aResult.Assign(mShortWorkingPath);
1326 return NS_OK;
1329 typedef struct {
1330 WORD wLanguage;
1331 WORD wCodePage;
1332 } LANGANDCODEPAGE;
1334 NS_IMETHODIMP
1335 nsLocalFile::GetVersionInfoField(const char* aField, nsAString& _retval)
1337 nsresult rv = ResolveAndStat();
1338 if (NS_FAILED(rv))
1339 return rv;
1341 rv = NS_ERROR_FAILURE;
1343 // Cast away const-ness here because WinAPI functions don't understand it,
1344 // the path is used for [in] parameters only however so it's safe.
1345 WCHAR *path = const_cast<WCHAR*>(mFollowSymlinks ? mResolvedPath.get()
1346 : mWorkingPath.get());
1348 DWORD dummy;
1349 DWORD size = ::GetFileVersionInfoSizeW(path, &dummy);
1350 if (!size)
1351 return rv;
1353 void* ver = calloc(size, 1);
1354 if (!ver)
1355 return NS_ERROR_OUT_OF_MEMORY;
1357 if (::GetFileVersionInfoW(path, 0, size, ver))
1359 LANGANDCODEPAGE* translate = nsnull;
1360 UINT pageCount;
1361 BOOL queryResult = ::VerQueryValueW(ver, L"\\VarFileInfo\\Translation",
1362 (void**)&translate, &pageCount);
1363 if (queryResult && translate)
1365 for (PRInt32 i = 0; i < 2; ++i)
1367 PRUnichar subBlock[MAX_PATH];
1368 _snwprintf(subBlock, MAX_PATH,
1369 L"\\StringFileInfo\\%04x%04x\\%s",
1370 (i == 0 ? translate[0].wLanguage
1371 : ::GetUserDefaultLangID()),
1372 translate[0].wCodePage,
1373 NS_ConvertASCIItoUTF16(
1374 nsDependentCString(aField)).get());
1375 subBlock[MAX_PATH - 1] = 0;
1376 LPVOID value = nsnull;
1377 UINT size;
1378 queryResult = ::VerQueryValueW(ver, subBlock, &value, &size);
1379 if (queryResult && value)
1381 _retval.Assign(static_cast<PRUnichar*>(value));
1382 if (!_retval.IsEmpty())
1384 rv = NS_OK;
1385 break;
1391 free(ver);
1393 return rv;
1396 nsresult
1397 nsLocalFile::CopySingleFile(nsIFile *sourceFile, nsIFile *destParent,
1398 const nsAString &newName,
1399 PRBool followSymlinks, PRBool move)
1401 nsresult rv;
1402 nsAutoString filePath;
1404 // get the path that we are going to copy to.
1405 // Since windows does not know how to auto
1406 // resolve shortcuts, we must work with the
1407 // target.
1408 nsAutoString destPath;
1409 destParent->GetTarget(destPath);
1411 destPath.AppendASCII("\\");
1413 if (newName.IsEmpty())
1415 nsAutoString aFileName;
1416 sourceFile->GetLeafName(aFileName);
1417 destPath.Append(aFileName);
1419 else
1421 destPath.Append(newName);
1425 if (followSymlinks)
1427 rv = sourceFile->GetTarget(filePath);
1428 if (filePath.IsEmpty())
1429 rv = sourceFile->GetPath(filePath);
1431 else
1433 rv = sourceFile->GetPath(filePath);
1436 if (NS_FAILED(rv))
1437 return rv;
1439 int copyOK;
1441 if (!move)
1442 copyOK = ::CopyFileW(filePath.get(), destPath.get(), PR_TRUE);
1443 else
1444 copyOK = ::MoveFileExW(filePath.get(), destPath.get(),
1445 MOVEFILE_REPLACE_EXISTING |
1446 MOVEFILE_COPY_ALLOWED |
1447 MOVEFILE_WRITE_THROUGH);
1449 if (!copyOK) // CopyFile and MoveFileEx return zero at failure.
1450 rv = ConvertWinError(GetLastError());
1452 if(move) //Set security permissions to inherit from parent.
1454 ACL empty_acl;
1455 if (InitializeAcl(&empty_acl, (DWORD) sizeof(ACL), ACL_REVISION))
1457 ::SetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT,
1458 DACL_SECURITY_INFORMATION |
1459 UNPROTECTED_DACL_SECURITY_INFORMATION,
1460 NULL, NULL, &empty_acl, NULL);
1465 return rv;
1468 nsresult
1469 nsLocalFile::CopyMove(nsIFile *aParentDir, const nsAString &newName, PRBool followSymlinks, PRBool move)
1471 nsCOMPtr<nsIFile> newParentDir = aParentDir;
1472 // check to see if this exists, otherwise return an error.
1473 // we will check this by resolving. If the user wants us
1474 // to follow links, then we are talking about the target,
1475 // hence we can use the |followSymlinks| parameter.
1476 nsresult rv = ResolveAndStat();
1477 if (NS_FAILED(rv))
1478 return rv;
1480 if (!newParentDir)
1482 // no parent was specified. We must rename.
1484 if (newName.IsEmpty())
1485 return NS_ERROR_INVALID_ARG;
1487 rv = GetParent(getter_AddRefs(newParentDir));
1488 if (NS_FAILED(rv))
1489 return rv;
1492 if (!newParentDir)
1493 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
1495 // make sure it exists and is a directory. Create it if not there.
1496 PRBool exists;
1497 newParentDir->Exists(&exists);
1498 if (!exists)
1500 rv = newParentDir->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use
1501 if (NS_FAILED(rv))
1502 return rv;
1504 else
1506 PRBool isDir;
1507 newParentDir->IsDirectory(&isDir);
1508 if (isDir == PR_FALSE)
1510 if (followSymlinks)
1512 PRBool isLink;
1513 newParentDir->IsSymlink(&isLink);
1514 if (isLink)
1516 nsAutoString target;
1517 newParentDir->GetTarget(target);
1519 nsCOMPtr<nsILocalFile> realDest = new nsLocalFile();
1520 if (realDest == nsnull)
1521 return NS_ERROR_OUT_OF_MEMORY;
1523 rv = realDest->InitWithPath(target);
1525 if (NS_FAILED(rv))
1526 return rv;
1528 return CopyMove(realDest, newName, followSymlinks, move);
1531 else
1533 return NS_ERROR_FILE_DESTINATION_NOT_DIR;
1538 // Try different ways to move/copy files/directories
1539 PRBool done = PR_FALSE;
1540 PRBool isDir;
1541 IsDirectory(&isDir);
1542 PRBool isSymlink;
1543 IsSymlink(&isSymlink);
1545 // Try to move the file or directory, or try to copy a single file (or non-followed symlink)
1546 if (move || !isDir || (isSymlink && !followSymlinks))
1548 // Copy/Move single file, or move a directory
1549 rv = CopySingleFile(this, newParentDir, newName, followSymlinks, move);
1550 done = NS_SUCCEEDED(rv);
1551 // If we are moving a directory and that fails, fallback on directory
1552 // enumeration. See bug 231300 for details.
1553 if (!done && !(move && isDir))
1554 return rv;
1557 // Not able to copy or move directly, so enumerate it
1558 if (!done)
1560 // create a new target destination in the new parentDir;
1561 nsCOMPtr<nsIFile> target;
1562 rv = newParentDir->Clone(getter_AddRefs(target));
1564 if (NS_FAILED(rv))
1565 return rv;
1567 nsAutoString allocatedNewName;
1568 if (newName.IsEmpty())
1570 PRBool isLink;
1571 IsSymlink(&isLink);
1572 if (isLink)
1574 nsAutoString temp;
1575 GetTarget(temp);
1576 PRInt32 offset = temp.RFindChar(L'\\');
1577 if (offset == kNotFound)
1578 allocatedNewName = temp;
1579 else
1580 allocatedNewName = Substring(temp, offset + 1);
1582 else
1584 GetLeafName(allocatedNewName);// this should be the leaf name of the
1587 else
1589 allocatedNewName = newName;
1592 rv = target->Append(allocatedNewName);
1593 if (NS_FAILED(rv))
1594 return rv;
1596 allocatedNewName.Truncate();
1598 // check if the destination directory already exists
1599 target->Exists(&exists);
1600 if (!exists)
1602 // if the destination directory cannot be created, return an error
1603 rv = target->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use
1604 if (NS_FAILED(rv))
1605 return rv;
1607 else
1609 // check if the destination directory is writable and empty
1610 PRBool isWritable;
1612 target->IsWritable(&isWritable);
1613 if (!isWritable)
1614 return NS_ERROR_FILE_ACCESS_DENIED;
1616 nsCOMPtr<nsISimpleEnumerator> targetIterator;
1617 rv = target->GetDirectoryEntries(getter_AddRefs(targetIterator));
1619 PRBool more;
1620 targetIterator->HasMoreElements(&more);
1621 // return error if target directory is not empty
1622 if (more)
1623 return NS_ERROR_FILE_DIR_NOT_EMPTY;
1626 nsDirEnumerator dirEnum;
1628 rv = dirEnum.Init(this);
1629 if (NS_FAILED(rv)) {
1630 NS_WARNING("dirEnum initialization failed");
1631 return rv;
1634 PRBool more;
1635 while (NS_SUCCEEDED(dirEnum.HasMoreElements(&more)) && more)
1637 nsCOMPtr<nsISupports> item;
1638 nsCOMPtr<nsIFile> file;
1639 dirEnum.GetNext(getter_AddRefs(item));
1640 file = do_QueryInterface(item);
1641 if (file)
1643 PRBool isDir, isLink;
1645 file->IsDirectory(&isDir);
1646 file->IsSymlink(&isLink);
1648 if (move)
1650 if (followSymlinks)
1651 return NS_ERROR_FAILURE;
1653 rv = file->MoveTo(target, EmptyString());
1654 NS_ENSURE_SUCCESS(rv,rv);
1656 else
1658 if (followSymlinks)
1659 rv = file->CopyToFollowingLinks(target, EmptyString());
1660 else
1661 rv = file->CopyTo(target, EmptyString());
1662 NS_ENSURE_SUCCESS(rv,rv);
1666 // we've finished moving all the children of this directory
1667 // in the new directory. so now delete the directory
1668 // note, we don't need to do a recursive delete.
1669 // MoveTo() is recursive. At this point,
1670 // we've already moved the children of the current folder
1671 // to the new location. nothing should be left in the folder.
1672 if (move)
1674 rv = Remove(PR_FALSE /* recursive */);
1675 NS_ENSURE_SUCCESS(rv,rv);
1680 // If we moved, we want to adjust this.
1681 if (move)
1683 MakeDirty();
1685 nsAutoString newParentPath;
1686 newParentDir->GetPath(newParentPath);
1688 if (newParentPath.IsEmpty())
1689 return NS_ERROR_FAILURE;
1691 if (newName.IsEmpty())
1693 nsAutoString aFileName;
1694 GetLeafName(aFileName);
1696 InitWithPath(newParentPath);
1697 Append(aFileName);
1699 else
1701 InitWithPath(newParentPath);
1702 Append(newName);
1706 return NS_OK;
1709 NS_IMETHODIMP
1710 nsLocalFile::CopyTo(nsIFile *newParentDir, const nsAString &newName)
1712 return CopyMove(newParentDir, newName, PR_FALSE, PR_FALSE);
1715 NS_IMETHODIMP
1716 nsLocalFile::CopyToFollowingLinks(nsIFile *newParentDir, const nsAString &newName)
1718 return CopyMove(newParentDir, newName, PR_TRUE, PR_FALSE);
1721 NS_IMETHODIMP
1722 nsLocalFile::MoveTo(nsIFile *newParentDir, const nsAString &newName)
1724 return CopyMove(newParentDir, newName, PR_FALSE, PR_TRUE);
1728 NS_IMETHODIMP
1729 nsLocalFile::Load(PRLibrary * *_retval)
1731 // Check we are correctly initialized.
1732 CHECK_mWorkingPath();
1734 PRBool isFile;
1735 nsresult rv = IsFile(&isFile);
1737 if (NS_FAILED(rv))
1738 return rv;
1740 if (! isFile)
1741 return NS_ERROR_FILE_IS_DIRECTORY;
1743 NS_TIMELINE_START_TIMER("PR_LoadLibraryWithFlags");
1745 #ifdef NS_BUILD_REFCNT_LOGGING
1746 nsTraceRefcntImpl::SetActivityIsLegal(PR_FALSE);
1747 #endif
1749 PRLibSpec libSpec;
1750 libSpec.value.pathname_u = mResolvedPath.get();
1751 libSpec.type = PR_LibSpec_PathnameU;
1752 *_retval = PR_LoadLibraryWithFlags(libSpec, 0);
1754 #ifdef NS_BUILD_REFCNT_LOGGING
1755 nsTraceRefcntImpl::SetActivityIsLegal(PR_TRUE);
1756 #endif
1758 NS_TIMELINE_STOP_TIMER("PR_LoadLibraryWithFlags");
1759 NS_TIMELINE_MARK_TIMER1("PR_LoadLibraryWithFlags",
1760 NS_ConvertUTF16toUTF8(mResolvedPath).get());
1762 if (*_retval)
1763 return NS_OK;
1764 return NS_ERROR_NULL_POINTER;
1767 NS_IMETHODIMP
1768 nsLocalFile::Remove(PRBool recursive)
1770 // NOTE:
1772 // if the working path points to a shortcut, then we will only
1773 // delete the shortcut itself. even if the shortcut points to
1774 // a directory, we will not recurse into that directory or
1775 // delete that directory itself. likewise, if the shortcut
1776 // points to a normal file, we will not delete the real file.
1777 // this is done to be consistent with the other platforms that
1778 // behave this way. we do this even if the followLinks attribute
1779 // is set to true. this helps protect against misuse that could
1780 // lead to security bugs (e.g., bug 210588).
1782 // Since shortcut files are no longer permitted to be used as unix-like
1783 // symlinks interspersed in the path (e.g. "c:/file.lnk/foo/bar.txt")
1784 // this processing is a lot simpler. Even if the shortcut file is
1785 // pointing to a directory, only the mWorkingPath value is used and so
1786 // only the shortcut file will be deleted.
1788 // Check we are correctly initialized.
1789 CHECK_mWorkingPath();
1791 PRBool isDir, isLink;
1792 nsresult rv;
1794 isDir = PR_FALSE;
1795 rv = IsSymlink(&isLink);
1796 if (NS_FAILED(rv))
1797 return rv;
1799 // only check to see if we have a directory if it isn't a link
1800 if (!isLink)
1802 rv = IsDirectory(&isDir);
1803 if (NS_FAILED(rv))
1804 return rv;
1807 if (isDir)
1809 if (recursive)
1811 nsDirEnumerator dirEnum;
1813 rv = dirEnum.Init(this);
1814 if (NS_FAILED(rv))
1815 return rv;
1817 PRBool more;
1818 while (NS_SUCCEEDED(dirEnum.HasMoreElements(&more)) && more)
1820 nsCOMPtr<nsISupports> item;
1821 dirEnum.GetNext(getter_AddRefs(item));
1822 nsCOMPtr<nsIFile> file = do_QueryInterface(item);
1823 if (file)
1824 file->Remove(recursive);
1827 rv = _wrmdir(mWorkingPath.get());
1829 else
1831 rv = _wremove(mWorkingPath.get());
1834 // fixup error code if necessary...
1835 if (rv == (nsresult)-1)
1836 rv = NSRESULT_FOR_ERRNO();
1838 MakeDirty();
1839 return rv;
1842 NS_IMETHODIMP
1843 nsLocalFile::GetLastModifiedTime(PRInt64 *aLastModifiedTime)
1845 // Check we are correctly initialized.
1846 CHECK_mWorkingPath();
1848 NS_ENSURE_ARG(aLastModifiedTime);
1850 // get the modified time of the target as determined by mFollowSymlinks
1851 // If PR_TRUE, then this will be for the target of the shortcut file,
1852 // otherwise it will be for the shortcut file itself (i.e. the same
1853 // results as GetLastModifiedTimeOfLink)
1855 nsresult rv = ResolveAndStat();
1856 if (NS_FAILED(rv))
1857 return rv;
1859 // microseconds -> milliseconds
1860 PRInt64 usecPerMsec;
1861 LL_I2L(usecPerMsec, PR_USEC_PER_MSEC);
1862 LL_DIV(*aLastModifiedTime, mFileInfo64.modifyTime, usecPerMsec);
1863 return NS_OK;
1867 NS_IMETHODIMP
1868 nsLocalFile::GetLastModifiedTimeOfLink(PRInt64 *aLastModifiedTime)
1870 // Check we are correctly initialized.
1871 CHECK_mWorkingPath();
1873 NS_ENSURE_ARG(aLastModifiedTime);
1875 // The caller is assumed to have already called IsSymlink
1876 // and to have found that this file is a link.
1878 PRFileInfo64 info;
1879 nsresult rv = GetFileInfo(mWorkingPath, &info);
1880 if (NS_FAILED(rv))
1881 return rv;
1883 // microseconds -> milliseconds
1884 PRInt64 usecPerMsec;
1885 LL_I2L(usecPerMsec, PR_USEC_PER_MSEC);
1886 LL_DIV(*aLastModifiedTime, info.modifyTime, usecPerMsec);
1887 return NS_OK;
1891 NS_IMETHODIMP
1892 nsLocalFile::SetLastModifiedTime(PRInt64 aLastModifiedTime)
1894 // Check we are correctly initialized.
1895 CHECK_mWorkingPath();
1897 nsresult rv = ResolveAndStat();
1898 if (NS_FAILED(rv))
1899 return rv;
1901 // set the modified time of the target as determined by mFollowSymlinks
1902 // If PR_TRUE, then this will be for the target of the shortcut file,
1903 // otherwise it will be for the shortcut file itself (i.e. the same
1904 // results as SetLastModifiedTimeOfLink)
1906 rv = SetModDate(aLastModifiedTime, mResolvedPath.get());
1907 if (NS_SUCCEEDED(rv))
1908 MakeDirty();
1910 return rv;
1914 NS_IMETHODIMP
1915 nsLocalFile::SetLastModifiedTimeOfLink(PRInt64 aLastModifiedTime)
1917 // The caller is assumed to have already called IsSymlink
1918 // and to have found that this file is a link.
1920 nsresult rv = SetModDate(aLastModifiedTime, mWorkingPath.get());
1921 if (NS_SUCCEEDED(rv))
1922 MakeDirty();
1924 return rv;
1927 nsresult
1928 nsLocalFile::SetModDate(PRInt64 aLastModifiedTime, const PRUnichar *filePath)
1930 HANDLE file = ::CreateFileW(filePath, // pointer to name of the file
1931 GENERIC_WRITE, // access (write) mode
1932 0, // share mode
1933 NULL, // pointer to security attributes
1934 OPEN_EXISTING, // how to create
1935 0, // file attributes
1936 NULL);
1938 if (file == INVALID_HANDLE_VALUE)
1940 return ConvertWinError(GetLastError());
1943 FILETIME lft, ft;
1944 SYSTEMTIME st;
1945 PRExplodedTime pret;
1947 // PR_ExplodeTime expects usecs...
1948 PR_ExplodeTime(aLastModifiedTime * PR_USEC_PER_MSEC, PR_LocalTimeParameters, &pret);
1949 st.wYear = pret.tm_year;
1950 st.wMonth = pret.tm_month + 1; // Convert start offset -- Win32: Jan=1; NSPR: Jan=0
1951 st.wDayOfWeek = pret.tm_wday;
1952 st.wDay = pret.tm_mday;
1953 st.wHour = pret.tm_hour;
1954 st.wMinute = pret.tm_min;
1955 st.wSecond = pret.tm_sec;
1956 st.wMilliseconds = pret.tm_usec/1000;
1958 nsresult rv = NS_OK;
1959 // if at least one of these fails...
1960 if (!(SystemTimeToFileTime(&st, &lft) != 0 &&
1961 LocalFileTimeToFileTime(&lft, &ft) != 0 &&
1962 SetFileTime(file, NULL, &ft, &ft) != 0))
1964 rv = ConvertWinError(GetLastError());
1967 CloseHandle(file);
1968 return rv;
1971 NS_IMETHODIMP
1972 nsLocalFile::GetPermissions(PRUint32 *aPermissions)
1974 NS_ENSURE_ARG(aPermissions);
1976 // get the permissions of the target as determined by mFollowSymlinks
1977 // If PR_TRUE, then this will be for the target of the shortcut file,
1978 // otherwise it will be for the shortcut file itself (i.e. the same
1979 // results as GetPermissionsOfLink)
1980 nsresult rv = ResolveAndStat();
1981 if (NS_FAILED(rv))
1982 return rv;
1984 PRBool isWritable, isExecutable;
1985 IsWritable(&isWritable);
1986 IsExecutable(&isExecutable);
1988 *aPermissions = PR_IRUSR|PR_IRGRP|PR_IROTH; // all read
1989 if (isWritable)
1990 *aPermissions |= PR_IWUSR|PR_IWGRP|PR_IWOTH; // all write
1991 if (isExecutable)
1992 *aPermissions |= PR_IXUSR|PR_IXGRP|PR_IXOTH; // all execute
1994 return NS_OK;
1997 NS_IMETHODIMP
1998 nsLocalFile::GetPermissionsOfLink(PRUint32 *aPermissions)
2000 // Check we are correctly initialized.
2001 CHECK_mWorkingPath();
2003 NS_ENSURE_ARG(aPermissions);
2005 // The caller is assumed to have already called IsSymlink
2006 // and to have found that this file is a link. It is not
2007 // possible for a link file to be executable.
2009 DWORD word = ::GetFileAttributesW(mWorkingPath.get());
2010 if (word == INVALID_FILE_ATTRIBUTES)
2011 return NS_ERROR_FILE_INVALID_PATH;
2013 PRBool isWritable = !(word & FILE_ATTRIBUTE_READONLY);
2014 *aPermissions = PR_IRUSR|PR_IRGRP|PR_IROTH; // all read
2015 if (isWritable)
2016 *aPermissions |= PR_IWUSR|PR_IWGRP|PR_IWOTH; // all write
2018 return NS_OK;
2022 NS_IMETHODIMP
2023 nsLocalFile::SetPermissions(PRUint32 aPermissions)
2025 // Check we are correctly initialized.
2026 CHECK_mWorkingPath();
2028 // set the permissions of the target as determined by mFollowSymlinks
2029 // If PR_TRUE, then this will be for the target of the shortcut file,
2030 // otherwise it will be for the shortcut file itself (i.e. the same
2031 // results as SetPermissionsOfLink)
2032 nsresult rv = ResolveAndStat();
2033 if (NS_FAILED(rv))
2034 return rv;
2036 // windows only knows about the following permissions
2037 int mode = 0;
2038 if (aPermissions & (PR_IRUSR|PR_IRGRP|PR_IROTH)) // any read
2039 mode |= _S_IREAD;
2040 if (aPermissions & (PR_IWUSR|PR_IWGRP|PR_IWOTH)) // any write
2041 mode |= _S_IWRITE;
2043 if (_wchmod(mResolvedPath.get(), mode) == -1)
2044 return NS_ERROR_FAILURE;
2046 return NS_OK;
2049 NS_IMETHODIMP
2050 nsLocalFile::SetPermissionsOfLink(PRUint32 aPermissions)
2052 // The caller is assumed to have already called IsSymlink
2053 // and to have found that this file is a link.
2055 // windows only knows about the following permissions
2056 int mode = 0;
2057 if (aPermissions & (PR_IRUSR|PR_IRGRP|PR_IROTH)) // any read
2058 mode |= _S_IREAD;
2059 if (aPermissions & (PR_IWUSR|PR_IWGRP|PR_IWOTH)) // any write
2060 mode |= _S_IWRITE;
2062 if (_wchmod(mWorkingPath.get(), mode) == -1)
2063 return NS_ERROR_FAILURE;
2065 return NS_OK;
2069 NS_IMETHODIMP
2070 nsLocalFile::GetFileSize(PRInt64 *aFileSize)
2072 NS_ENSURE_ARG(aFileSize);
2074 nsresult rv = ResolveAndStat();
2075 if (NS_FAILED(rv))
2076 return rv;
2078 *aFileSize = mFileInfo64.size;
2079 return NS_OK;
2083 NS_IMETHODIMP
2084 nsLocalFile::GetFileSizeOfLink(PRInt64 *aFileSize)
2086 // Check we are correctly initialized.
2087 CHECK_mWorkingPath();
2089 NS_ENSURE_ARG(aFileSize);
2091 // The caller is assumed to have already called IsSymlink
2092 // and to have found that this file is a link.
2094 PRFileInfo64 info;
2095 if (NS_FAILED(GetFileInfo(mWorkingPath, &info)))
2096 return NS_ERROR_FILE_INVALID_PATH;
2098 *aFileSize = info.size;
2099 return NS_OK;
2102 NS_IMETHODIMP
2103 nsLocalFile::SetFileSize(PRInt64 aFileSize)
2105 // Check we are correctly initialized.
2106 CHECK_mWorkingPath();
2108 nsresult rv = ResolveAndStat();
2109 if (NS_FAILED(rv))
2110 return rv;
2112 HANDLE hFile = ::CreateFileW(mResolvedPath.get(),// pointer to name of the file
2113 GENERIC_WRITE, // access (write) mode
2114 FILE_SHARE_READ, // share mode
2115 NULL, // pointer to security attributes
2116 OPEN_EXISTING, // how to create
2117 FILE_ATTRIBUTE_NORMAL, // file attributes
2118 NULL);
2119 if (hFile == INVALID_HANDLE_VALUE)
2121 return ConvertWinError(GetLastError());
2124 // seek the file pointer to the new, desired end of file
2125 // and then truncate the file at that position
2126 rv = NS_ERROR_FAILURE;
2127 aFileSize = MyFileSeek64(hFile, aFileSize, FILE_BEGIN);
2128 if (aFileSize != -1 && SetEndOfFile(hFile))
2130 MakeDirty();
2131 rv = NS_OK;
2134 CloseHandle(hFile);
2135 return rv;
2138 NS_IMETHODIMP
2139 nsLocalFile::GetDiskSpaceAvailable(PRInt64 *aDiskSpaceAvailable)
2141 // Check we are correctly initialized.
2142 CHECK_mWorkingPath();
2144 #ifndef WINCE
2145 NS_ENSURE_ARG(aDiskSpaceAvailable);
2147 ResolveAndStat();
2149 ULARGE_INTEGER liFreeBytesAvailableToCaller, liTotalNumberOfBytes;
2150 if (::GetDiskFreeSpaceExW(mResolvedPath.get(), &liFreeBytesAvailableToCaller,
2151 &liTotalNumberOfBytes, NULL))
2153 *aDiskSpaceAvailable = liFreeBytesAvailableToCaller.QuadPart;
2154 return NS_OK;
2156 #endif
2157 // WINCE FIX
2158 *aDiskSpaceAvailable = 0;
2159 return NS_OK;
2162 NS_IMETHODIMP
2163 nsLocalFile::GetParent(nsIFile * *aParent)
2165 // Check we are correctly initialized.
2166 CHECK_mWorkingPath();
2168 NS_ENSURE_ARG_POINTER(aParent);
2170 // A two-character path must be a drive such as C:, so it has no parent
2171 if (mWorkingPath.Length() == 2) {
2172 *aParent = nsnull;
2173 return NS_OK;
2176 PRInt32 offset = mWorkingPath.RFindChar(PRUnichar('\\'));
2177 // adding this offset check that was removed in bug 241708 fixes mail
2178 // directories that aren't relative to/underneath the profile dir.
2179 // e.g., on a different drive. Before you remove them, please make
2180 // sure local mail directories that aren't underneath the profile dir work.
2181 if (offset == kNotFound)
2182 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
2184 // A path of the form \\NAME is a top-level path and has no parent
2185 if (offset == 1 && mWorkingPath[0] == L'\\') {
2186 *aParent = nsnull;
2187 return NS_OK;
2190 nsAutoString parentPath(mWorkingPath);
2192 if (offset > 0)
2193 parentPath.Truncate(offset);
2194 else
2195 parentPath.AssignLiteral("\\\\.");
2197 nsCOMPtr<nsILocalFile> localFile;
2198 nsresult rv = NS_NewLocalFile(parentPath, mFollowSymlinks, getter_AddRefs(localFile));
2200 if(NS_SUCCEEDED(rv) && localFile)
2202 return CallQueryInterface(localFile, aParent);
2204 return rv;
2207 NS_IMETHODIMP
2208 nsLocalFile::Exists(PRBool *_retval)
2210 // Check we are correctly initialized.
2211 CHECK_mWorkingPath();
2213 NS_ENSURE_ARG(_retval);
2214 *_retval = PR_FALSE;
2216 MakeDirty();
2217 nsresult rv = ResolveAndStat();
2218 *_retval = NS_SUCCEEDED(rv);
2220 return NS_OK;
2223 NS_IMETHODIMP
2224 nsLocalFile::IsWritable(PRBool *aIsWritable)
2226 // Check we are correctly initialized.
2227 CHECK_mWorkingPath();
2229 //TODO: extend to support NTFS file permissions
2231 // The read-only attribute on a FAT directory only means that it can't
2232 // be deleted. It is still possible to modify the contents of the directory.
2233 nsresult rv = IsDirectory(aIsWritable);
2234 if (NS_FAILED(rv))
2235 return rv;
2236 if (*aIsWritable)
2237 return NS_OK;
2239 // writable if the file doesn't have the readonly attribute
2240 rv = HasFileAttribute(FILE_ATTRIBUTE_READONLY, aIsWritable);
2241 if (NS_FAILED(rv))
2242 return rv;
2243 *aIsWritable = !*aIsWritable;
2245 return NS_OK;
2248 NS_IMETHODIMP
2249 nsLocalFile::IsReadable(PRBool *_retval)
2251 // Check we are correctly initialized.
2252 CHECK_mWorkingPath();
2254 NS_ENSURE_ARG(_retval);
2255 *_retval = PR_FALSE;
2257 nsresult rv = ResolveAndStat();
2258 if (NS_FAILED(rv))
2259 return rv;
2261 *_retval = PR_TRUE;
2262 return NS_OK;
2266 NS_IMETHODIMP
2267 nsLocalFile::IsExecutable(PRBool *_retval)
2269 // Check we are correctly initialized.
2270 CHECK_mWorkingPath();
2272 NS_ENSURE_ARG(_retval);
2273 *_retval = PR_FALSE;
2275 nsresult rv;
2277 // only files can be executables
2278 PRBool isFile;
2279 rv = IsFile(&isFile);
2280 if (NS_FAILED(rv))
2281 return rv;
2282 if (!isFile)
2283 return NS_OK;
2285 //TODO: shouldn't we be checking mFollowSymlinks here?
2286 PRBool symLink;
2287 rv = IsSymlink(&symLink);
2288 if (NS_FAILED(rv))
2289 return rv;
2291 nsAutoString path;
2292 if (symLink)
2293 GetTarget(path);
2294 else
2295 GetPath(path);
2297 // kill trailing dots and spaces.
2298 PRInt32 filePathLen = path.Length() - 1;
2299 while(filePathLen > 0 && (path[filePathLen] == L' ' || path[filePathLen] == L'.'))
2301 path.Truncate(filePathLen--);
2304 // Get extension.
2305 PRInt32 dotIdx = path.RFindChar(PRUnichar('.'));
2306 if ( dotIdx != kNotFound ) {
2307 // Convert extension to lower case.
2308 PRUnichar *p = path.BeginWriting();
2309 for( p+= dotIdx + 1; *p; p++ )
2310 *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0;
2312 // Search for any of the set of executable extensions.
2313 static const char * const executableExts[] = {
2314 "ad",
2315 "ade", // access project extension
2316 "adp",
2317 "app", // executable application
2318 "application", // from bug 348763
2319 "asp",
2320 "bas",
2321 "bat",
2322 "chm",
2323 "cmd",
2324 "com",
2325 "cpl",
2326 "crt",
2327 "exe",
2328 "fxp", // FoxPro compiled app
2329 "hlp",
2330 "hta",
2331 "inf",
2332 "ins",
2333 "isp",
2334 "js",
2335 "jse",
2336 "lnk",
2337 "mad", // Access Module Shortcut
2338 "maf", // Access
2339 "mag", // Access Diagram Shortcut
2340 "mam", // Access Macro Shortcut
2341 "maq", // Access Query Shortcut
2342 "mar", // Access Report Shortcut
2343 "mas", // Access Stored Procedure
2344 "mat", // Access Table Shortcut
2345 "mau", // Media Attachment Unit
2346 "mav", // Access View Shortcut
2347 "maw", // Access Data Access Page
2348 "mda", // Access Add-in, MDA Access 2 Workgroup
2349 "mdb",
2350 "mde",
2351 "mdt", // Access Add-in Data
2352 "mdw", // Access Workgroup Information
2353 "mdz", // Access Wizard Template
2354 "msc",
2355 "msh", // Microsoft Shell
2356 "mshxml", // Microsoft Shell
2357 "msi",
2358 "msp",
2359 "mst",
2360 "ops", // Office Profile Settings
2361 "pcd",
2362 "pif",
2363 "plg", // Developer Studio Build Log
2364 "prf", // windows system file
2365 "prg",
2366 "pst",
2367 "reg",
2368 "scf", // Windows explorer command
2369 "scr",
2370 "sct",
2371 "shb",
2372 "shs",
2373 "url",
2374 "vb",
2375 "vbe",
2376 "vbs",
2377 "vsd",
2378 "vsmacros", // Visual Studio .NET Binary-based Macro Project
2379 "vss",
2380 "vst",
2381 "vsw",
2382 "ws",
2383 "wsc",
2384 "wsf",
2385 "wsh"};
2386 nsDependentSubstring ext = Substring(path, dotIdx + 1);
2387 for ( int i = 0; i < NS_ARRAY_LENGTH(executableExts); i++ ) {
2388 if ( ext.EqualsASCII(executableExts[i])) {
2389 // Found a match. Set result and quit.
2390 *_retval = PR_TRUE;
2391 break;
2396 return NS_OK;
2400 NS_IMETHODIMP
2401 nsLocalFile::IsDirectory(PRBool *_retval)
2403 NS_ENSURE_ARG(_retval);
2405 nsresult rv = ResolveAndStat();
2406 if (NS_FAILED(rv))
2407 return rv;
2409 *_retval = (mFileInfo64.type == PR_FILE_DIRECTORY);
2410 return NS_OK;
2413 NS_IMETHODIMP
2414 nsLocalFile::IsFile(PRBool *_retval)
2416 NS_ENSURE_ARG(_retval);
2418 nsresult rv = ResolveAndStat();
2419 if (NS_FAILED(rv))
2420 return rv;
2422 *_retval = (mFileInfo64.type == PR_FILE_FILE);
2423 return NS_OK;
2426 NS_IMETHODIMP
2427 nsLocalFile::IsHidden(PRBool *_retval)
2429 return HasFileAttribute(FILE_ATTRIBUTE_HIDDEN, _retval);
2432 nsresult
2433 nsLocalFile::HasFileAttribute(DWORD fileAttrib, PRBool *_retval)
2435 NS_ENSURE_ARG(_retval);
2437 nsresult rv = ResolveAndStat();
2438 if (NS_FAILED(rv))
2439 return rv;
2441 // get the file attributes for the correct item depending on following symlinks
2442 const PRUnichar *filePath = mFollowSymlinks ?
2443 mResolvedPath.get() : mWorkingPath.get();
2444 DWORD word = ::GetFileAttributesW(filePath);
2446 *_retval = ((word & fileAttrib) != 0);
2447 return NS_OK;
2450 NS_IMETHODIMP
2451 nsLocalFile::IsSymlink(PRBool *_retval)
2453 // Check we are correctly initialized.
2454 CHECK_mWorkingPath();
2456 NS_ENSURE_ARG(_retval);
2458 // unless it is a valid shortcut path it's not a symlink
2459 if (!IsShortcutPath(mWorkingPath))
2461 *_retval = PR_FALSE;
2462 return NS_OK;
2465 // we need to know if this is a file or directory
2466 nsresult rv = ResolveAndStat();
2467 if (NS_FAILED(rv))
2468 return rv;
2470 // it's only a shortcut if it is a file
2471 *_retval = (mFileInfo64.type == PR_FILE_FILE);
2472 return NS_OK;
2475 NS_IMETHODIMP
2476 nsLocalFile::IsSpecial(PRBool *_retval)
2478 return HasFileAttribute(FILE_ATTRIBUTE_SYSTEM, _retval);
2481 NS_IMETHODIMP
2482 nsLocalFile::Equals(nsIFile *inFile, PRBool *_retval)
2484 NS_ENSURE_ARG(inFile);
2485 NS_ENSURE_ARG(_retval);
2487 EnsureShortPath();
2489 nsCOMPtr<nsILocalFileWin> lf(do_QueryInterface(inFile));
2490 if (!lf) {
2491 *_retval = PR_FALSE;
2492 return NS_OK;
2495 nsAutoString inFilePath;
2496 lf->GetCanonicalPath(inFilePath);
2498 // Ok : Win9x
2499 *_retval = _wcsicmp(mShortWorkingPath.get(), inFilePath.get()) == 0;
2501 return NS_OK;
2505 NS_IMETHODIMP
2506 nsLocalFile::Contains(nsIFile *inFile, PRBool recur, PRBool *_retval)
2508 // Check we are correctly initialized.
2509 CHECK_mWorkingPath();
2511 *_retval = PR_FALSE;
2513 nsAutoString myFilePath;
2514 if (NS_FAILED(GetTarget(myFilePath)))
2515 GetPath(myFilePath);
2517 PRUint32 myFilePathLen = myFilePath.Length();
2519 nsAutoString inFilePath;
2520 if (NS_FAILED(inFile->GetTarget(inFilePath)))
2521 inFile->GetPath(inFilePath);
2523 // make sure that the |inFile|'s path has a trailing separator.
2524 if (inFilePath.Length() >= myFilePathLen && inFilePath[myFilePathLen] == L'\\')
2526 if (_wcsnicmp(myFilePath.get(), inFilePath.get(), myFilePathLen) == 0)
2528 *_retval = PR_TRUE;
2533 return NS_OK;
2537 NS_IMETHODIMP
2538 nsLocalFile::GetTarget(nsAString &_retval)
2540 _retval.Truncate();
2541 #if STRICT_FAKE_SYMLINKS
2542 PRBool symLink;
2544 nsresult rv = IsSymlink(&symLink);
2545 if (NS_FAILED(rv))
2546 return rv;
2548 if (!symLink)
2550 return NS_ERROR_FILE_INVALID_PATH;
2552 #endif
2553 ResolveAndStat();
2555 _retval = mResolvedPath;
2556 return NS_OK;
2560 /* attribute PRBool followLinks; */
2561 NS_IMETHODIMP
2562 nsLocalFile::GetFollowLinks(PRBool *aFollowLinks)
2564 *aFollowLinks = mFollowSymlinks;
2565 return NS_OK;
2567 NS_IMETHODIMP
2568 nsLocalFile::SetFollowLinks(PRBool aFollowLinks)
2570 MakeDirty();
2571 mFollowSymlinks = aFollowLinks;
2572 return NS_OK;
2576 NS_IMETHODIMP
2577 nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator * *entries)
2579 nsresult rv;
2581 *entries = nsnull;
2582 if (mWorkingPath.EqualsLiteral("\\\\.")) {
2583 nsDriveEnumerator *drives = new nsDriveEnumerator;
2584 if (!drives)
2585 return NS_ERROR_OUT_OF_MEMORY;
2586 NS_ADDREF(drives);
2587 rv = drives->Init();
2588 if (NS_FAILED(rv)) {
2589 NS_RELEASE(drives);
2590 return rv;
2592 *entries = drives;
2593 return NS_OK;
2596 PRBool isDir;
2597 rv = IsDirectory(&isDir);
2598 if (NS_FAILED(rv))
2599 return rv;
2600 if (!isDir)
2601 return NS_ERROR_FILE_NOT_DIRECTORY;
2603 nsDirEnumerator* dirEnum = new nsDirEnumerator();
2604 if (dirEnum == nsnull)
2605 return NS_ERROR_OUT_OF_MEMORY;
2606 NS_ADDREF(dirEnum);
2607 rv = dirEnum->Init(this);
2608 if (NS_FAILED(rv))
2610 NS_RELEASE(dirEnum);
2611 return rv;
2614 *entries = dirEnum;
2615 return NS_OK;
2618 NS_IMETHODIMP
2619 nsLocalFile::GetPersistentDescriptor(nsACString &aPersistentDescriptor)
2621 CopyUTF16toUTF8(mWorkingPath, aPersistentDescriptor);
2622 return NS_OK;
2625 NS_IMETHODIMP
2626 nsLocalFile::SetPersistentDescriptor(const nsACString &aPersistentDescriptor)
2628 if (IsUTF8(aPersistentDescriptor))
2629 return InitWithPath(NS_ConvertUTF8toUTF16(aPersistentDescriptor));
2630 else
2631 return InitWithNativePath(aPersistentDescriptor);
2634 NS_IMETHODIMP
2635 nsLocalFile::Reveal()
2637 // make sure mResolvedPath is set
2638 nsresult rv = ResolveAndStat();
2639 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)
2640 return rv;
2642 // use the full path to explorer for security
2643 nsCOMPtr<nsILocalFile> winDir;
2644 rv = GetSpecialSystemDirectory(Win_WindowsDirectory, getter_AddRefs(winDir));
2645 NS_ENSURE_SUCCESS(rv, rv);
2646 nsAutoString explorerPath;
2647 rv = winDir->GetPath(explorerPath);
2648 NS_ENSURE_SUCCESS(rv, rv);
2649 explorerPath.Append(L"\\explorer.exe");
2651 // Always open a new window for files because Win2K doesn't appear to select
2652 // the file if a window showing that folder was already open. If the resolved
2653 // path is a directory then instead of opening the parent and selecting it,
2654 // we open the directory itself.
2655 nsAutoString explorerParams;
2656 if (mFileInfo64.type != PR_FILE_DIRECTORY) // valid because we ResolveAndStat above
2657 explorerParams.Append(L"/n,/select,");
2658 explorerParams.Append(L'\"');
2659 explorerParams.Append(mResolvedPath);
2660 explorerParams.Append(L'\"');
2662 if (::ShellExecuteW(NULL, L"open", explorerPath.get(), explorerParams.get(),
2663 NULL, SW_SHOWNORMAL) <= (HINSTANCE) 32)
2664 return NS_ERROR_FAILURE;
2666 return NS_OK;
2670 NS_IMETHODIMP
2671 nsLocalFile::Launch()
2673 const nsString &path = mWorkingPath;
2675 // use the app registry name to launch a shell execute....
2676 LONG r = (LONG) ::ShellExecuteW(NULL, NULL, path.get(), NULL, NULL,
2677 SW_SHOWNORMAL);
2679 // if the file has no association, we launch windows' "what do you want to do" dialog
2680 if (r == SE_ERR_NOASSOC) {
2681 nsAutoString shellArg;
2682 shellArg.Assign(NS_LITERAL_STRING("shell32.dll,OpenAs_RunDLL ") + path);
2683 r = (LONG) ::ShellExecuteW(NULL, NULL, L"RUNDLL32.EXE", shellArg.get(),
2684 NULL, SW_SHOWNORMAL);
2686 if (r < 32) {
2687 switch (r) {
2688 case 0:
2689 case SE_ERR_OOM:
2690 return NS_ERROR_OUT_OF_MEMORY;
2691 case ERROR_FILE_NOT_FOUND:
2692 return NS_ERROR_FILE_NOT_FOUND;
2693 case ERROR_PATH_NOT_FOUND:
2694 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
2695 case ERROR_BAD_FORMAT:
2696 return NS_ERROR_FILE_CORRUPTED;
2697 case SE_ERR_ACCESSDENIED:
2698 return NS_ERROR_FILE_ACCESS_DENIED;
2699 case SE_ERR_ASSOCINCOMPLETE:
2700 case SE_ERR_NOASSOC:
2701 return NS_ERROR_UNEXPECTED;
2702 case SE_ERR_DDEBUSY:
2703 case SE_ERR_DDEFAIL:
2704 case SE_ERR_DDETIMEOUT:
2705 return NS_ERROR_NOT_AVAILABLE;
2706 case SE_ERR_DLLNOTFOUND:
2707 return NS_ERROR_FAILURE;
2708 case SE_ERR_SHARE:
2709 return NS_ERROR_FILE_IS_LOCKED;
2710 default:
2711 return NS_ERROR_FILE_EXECUTION_FAILED;
2714 return NS_OK;
2718 nsresult
2719 NS_NewLocalFile(const nsAString &path, PRBool followLinks, nsILocalFile* *result)
2721 nsLocalFile* file = new nsLocalFile();
2722 if (file == nsnull)
2723 return NS_ERROR_OUT_OF_MEMORY;
2724 NS_ADDREF(file);
2726 file->SetFollowLinks(followLinks);
2728 if (!path.IsEmpty()) {
2729 nsresult rv = file->InitWithPath(path);
2730 if (NS_FAILED(rv)) {
2731 NS_RELEASE(file);
2732 return rv;
2736 *result = file;
2737 return NS_OK;
2740 //-----------------------------------------------------------------------------
2741 // Native (lossy) interface
2742 //-----------------------------------------------------------------------------
2744 NS_IMETHODIMP
2745 nsLocalFile::InitWithNativePath(const nsACString &filePath)
2747 nsAutoString tmp;
2748 nsresult rv = NS_CopyNativeToUnicode(filePath, tmp);
2749 if (NS_SUCCEEDED(rv))
2750 return InitWithPath(tmp);
2752 return rv;
2755 NS_IMETHODIMP
2756 nsLocalFile::AppendNative(const nsACString &node)
2758 nsAutoString tmp;
2759 nsresult rv = NS_CopyNativeToUnicode(node, tmp);
2760 if (NS_SUCCEEDED(rv))
2761 return Append(tmp);
2763 return rv;
2766 NS_IMETHODIMP
2767 nsLocalFile::AppendRelativeNativePath(const nsACString &node)
2769 nsAutoString tmp;
2770 nsresult rv = NS_CopyNativeToUnicode(node, tmp);
2771 if (NS_SUCCEEDED(rv))
2772 return AppendRelativePath(tmp);
2773 return rv;
2777 NS_IMETHODIMP
2778 nsLocalFile::GetNativeLeafName(nsACString &aLeafName)
2780 //NS_WARNING("This API is lossy. Use GetLeafName !");
2781 nsAutoString tmp;
2782 nsresult rv = GetLeafName(tmp);
2783 if (NS_SUCCEEDED(rv))
2784 rv = NS_CopyUnicodeToNative(tmp, aLeafName);
2786 return rv;
2789 NS_IMETHODIMP
2790 nsLocalFile::SetNativeLeafName(const nsACString &aLeafName)
2792 nsAutoString tmp;
2793 nsresult rv = NS_CopyNativeToUnicode(aLeafName, tmp);
2794 if (NS_SUCCEEDED(rv))
2795 return SetLeafName(tmp);
2797 return rv;
2801 NS_IMETHODIMP
2802 nsLocalFile::GetNativePath(nsACString &_retval)
2804 //NS_WARNING("This API is lossy. Use GetPath !");
2805 nsAutoString tmp;
2806 nsresult rv = GetPath(tmp);
2807 if (NS_SUCCEEDED(rv))
2808 rv = NS_CopyUnicodeToNative(tmp, _retval);
2810 return rv;
2814 NS_IMETHODIMP
2815 nsLocalFile::GetNativeCanonicalPath(nsACString &aResult)
2817 NS_WARNING("This method is lossy. Use GetCanoincailPath !");
2818 EnsureShortPath();
2819 NS_CopyUnicodeToNative(mShortWorkingPath, aResult);
2820 return NS_OK;
2824 NS_IMETHODIMP
2825 nsLocalFile::CopyToNative(nsIFile *newParentDir, const nsACString &newName)
2827 // Check we are correctly initialized.
2828 CHECK_mWorkingPath();
2830 if (newName.IsEmpty())
2831 return CopyTo(newParentDir, EmptyString());
2833 nsAutoString tmp;
2834 nsresult rv = NS_CopyNativeToUnicode(newName, tmp);
2835 if (NS_SUCCEEDED(rv))
2836 return CopyTo(newParentDir, tmp);
2838 return rv;
2841 NS_IMETHODIMP
2842 nsLocalFile::CopyToFollowingLinksNative(nsIFile *newParentDir, const nsACString &newName)
2844 if (newName.IsEmpty())
2845 return CopyToFollowingLinks(newParentDir, EmptyString());
2847 nsAutoString tmp;
2848 nsresult rv = NS_CopyNativeToUnicode(newName, tmp);
2849 if (NS_SUCCEEDED(rv))
2850 return CopyToFollowingLinks(newParentDir, tmp);
2852 return rv;
2855 NS_IMETHODIMP
2856 nsLocalFile::MoveToNative(nsIFile *newParentDir, const nsACString &newName)
2858 // Check we are correctly initialized.
2859 CHECK_mWorkingPath();
2861 if (newName.IsEmpty())
2862 return MoveTo(newParentDir, EmptyString());
2864 nsAutoString tmp;
2865 nsresult rv = NS_CopyNativeToUnicode(newName, tmp);
2866 if (NS_SUCCEEDED(rv))
2867 return MoveTo(newParentDir, tmp);
2869 return rv;
2872 NS_IMETHODIMP
2873 nsLocalFile::GetNativeTarget(nsACString &_retval)
2875 // Check we are correctly initialized.
2876 CHECK_mWorkingPath();
2878 NS_WARNING("This API is lossy. Use GetTarget !");
2879 nsAutoString tmp;
2880 nsresult rv = GetTarget(tmp);
2881 if (NS_SUCCEEDED(rv))
2882 rv = NS_CopyUnicodeToNative(tmp, _retval);
2884 return rv;
2887 nsresult
2888 NS_NewNativeLocalFile(const nsACString &path, PRBool followLinks, nsILocalFile* *result)
2890 nsAutoString buf;
2891 nsresult rv = NS_CopyNativeToUnicode(path, buf);
2892 if (NS_FAILED(rv)) {
2893 *result = nsnull;
2894 return rv;
2896 return NS_NewLocalFile(buf, followLinks, result);
2899 void
2900 nsLocalFile::EnsureShortPath()
2902 if (!mShortWorkingPath.IsEmpty())
2903 return;
2904 #ifdef WINCE
2905 mShortWorkingPath.Assign(mWorkingPath);
2906 #else
2907 WCHAR thisshort[MAX_PATH];
2908 DWORD thisr = ::GetShortPathNameW(mWorkingPath.get(), thisshort,
2909 sizeof(thisshort));
2910 // If an error occured (thisr == 0) thisshort is uninitialized memory!
2911 if (thisr != 0 && thisr < sizeof(thisshort))
2912 mShortWorkingPath.Assign(thisshort);
2913 else
2914 mShortWorkingPath.Assign(mWorkingPath);
2915 #endif
2918 // nsIHashable
2920 NS_IMETHODIMP
2921 nsLocalFile::Equals(nsIHashable* aOther, PRBool *aResult)
2923 nsCOMPtr<nsIFile> otherfile(do_QueryInterface(aOther));
2924 if (!otherfile) {
2925 *aResult = PR_FALSE;
2926 return NS_OK;
2929 return Equals(otherfile, aResult);
2932 NS_IMETHODIMP
2933 nsLocalFile::GetHashCode(PRUint32 *aResult)
2935 // In order for short and long path names to hash to the same value we
2936 // always hash on the short pathname.
2937 EnsureShortPath();
2939 *aResult = HashString(mShortWorkingPath);
2940 return NS_OK;
2943 //-----------------------------------------------------------------------------
2944 // nsLocalFile <static members>
2945 //-----------------------------------------------------------------------------
2947 void
2948 nsLocalFile::GlobalInit()
2950 #ifndef WINCE
2951 nsresult rv = NS_CreateShortcutResolver();
2952 NS_ASSERTION(NS_SUCCEEDED(rv), "Shortcut resolver could not be created");
2953 #endif
2956 void
2957 nsLocalFile::GlobalShutdown()
2959 #ifndef WINCE
2960 NS_DestroyShortcutResolver();
2961 #endif
2964 NS_IMPL_ISUPPORTS1(nsDriveEnumerator, nsISimpleEnumerator)
2966 nsDriveEnumerator::nsDriveEnumerator()
2967 : mLetter(0)
2971 nsDriveEnumerator::~nsDriveEnumerator()
2975 nsresult nsDriveEnumerator::Init()
2977 #ifdef WINCE
2978 return NS_OK;
2979 #else
2980 /* If the length passed to GetLogicalDriveStrings is smaller
2981 * than the length of the string it would return, it returns
2982 * the length required for the string. */
2983 DWORD length = GetLogicalDriveStrings(0, 0);
2984 /* The string is null terminated */
2985 if (!EnsureStringLength(mDrives, length+1))
2986 return NS_ERROR_OUT_OF_MEMORY;
2987 if (!GetLogicalDriveStrings(length, mDrives.BeginWriting()))
2988 return NS_ERROR_FAILURE;
2989 mLetter = mDrives.get();
2990 return NS_OK;
2991 #endif
2994 NS_IMETHODIMP nsDriveEnumerator::HasMoreElements(PRBool *aHasMore)
2996 #ifdef WINCE
2997 *aHasMore = FALSE;
2998 #else
2999 *aHasMore = *mLetter != '\0';
3000 #endif
3001 return NS_OK;
3004 NS_IMETHODIMP nsDriveEnumerator::GetNext(nsISupports **aNext)
3006 #ifdef WINCE
3007 nsILocalFile *file;
3008 nsresult rv = NS_NewLocalFile(NS_LITERAL_STRING("\\"), PR_FALSE, &file);
3009 *aNext = file;
3010 #else
3011 /* GetLogicalDrives stored in mLetter is a concatenation
3012 * of null terminated strings, followed by a null terminator. */
3013 if (!*mLetter) {
3014 *aNext = nsnull;
3015 return NS_OK;
3017 NS_ConvertASCIItoUTF16 drive(mLetter);
3018 mLetter += drive.Length() + 1;
3019 nsILocalFile *file;
3020 nsresult rv =
3021 NS_NewLocalFile(drive, PR_FALSE, &file);
3023 *aNext = file;
3024 #endif
3025 return rv;