michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/WindowsVersion.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsMemory.h" michael@0: michael@0: #include "nsLocalFile.h" michael@0: #include "nsIDirectoryEnumerator.h" michael@0: #include "nsNativeCharsetUtils.h" michael@0: michael@0: #include "nsISimpleEnumerator.h" michael@0: #include "nsIComponentManager.h" michael@0: #include "prio.h" michael@0: #include "private/pprio.h" // To get PR_ImportFile michael@0: #include "prprf.h" michael@0: #include "prmem.h" michael@0: #include "nsHashKeys.h" michael@0: michael@0: #include "nsXPIDLString.h" michael@0: #include "nsReadableUtils.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "shellapi.h" michael@0: #include "shlguid.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "nsXPIDLString.h" michael@0: #include "prproces.h" michael@0: #include "prlink.h" michael@0: michael@0: #include "mozilla/Mutex.h" michael@0: #include "SpecialSystemDirectory.h" michael@0: michael@0: #include "nsTraceRefcnt.h" michael@0: #include "nsXPCOMCIDInternal.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXULAppAPI.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #define CHECK_mWorkingPath() \ michael@0: PR_BEGIN_MACRO \ michael@0: if (mWorkingPath.IsEmpty()) \ michael@0: return NS_ERROR_NOT_INITIALIZED; \ michael@0: PR_END_MACRO michael@0: michael@0: // CopyFileEx only supports unbuffered I/O in Windows Vista and above michael@0: #ifndef COPY_FILE_NO_BUFFERING michael@0: #define COPY_FILE_NO_BUFFERING 0x00001000 michael@0: #endif michael@0: michael@0: #ifndef FILE_ATTRIBUTE_NOT_CONTENT_INDEXED michael@0: #define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000 michael@0: #endif michael@0: michael@0: #ifndef DRIVE_REMOTE michael@0: #define DRIVE_REMOTE 4 michael@0: #endif michael@0: michael@0: /** michael@0: * A runnable to dispatch back to the main thread when michael@0: * AsyncLocalFileWinOperation completes. michael@0: */ michael@0: class AsyncLocalFileWinDone : public nsRunnable michael@0: { michael@0: public: michael@0: AsyncLocalFileWinDone() : michael@0: mWorkerThread(do_GetCurrentThread()) michael@0: { michael@0: // Objects of this type must only be created on worker threads michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: } michael@0: michael@0: NS_IMETHOD Run() { michael@0: // This event shuts down the worker thread and so must be main thread. michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // If we don't destroy the thread when we're done with it, it will hang michael@0: // around forever... and that is bad! michael@0: mWorkerThread->Shutdown(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mWorkerThread; michael@0: }; michael@0: michael@0: /** michael@0: * A runnable to dispatch from the main thread when an async operation should michael@0: * be performed. michael@0: */ michael@0: class AsyncLocalFileWinOperation : public nsRunnable michael@0: { michael@0: public: michael@0: enum FileOp { RevealOp, LaunchOp }; michael@0: michael@0: AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::FileOp aOperation, michael@0: const nsAString &aResolvedPath) : michael@0: mOperation(aOperation), michael@0: mResolvedPath(aResolvedPath) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() { michael@0: MOZ_ASSERT(!NS_IsMainThread(), michael@0: "AsyncLocalFileWinOperation should not be run on the main thread!"); michael@0: michael@0: CoInitialize(nullptr); michael@0: switch(mOperation) { michael@0: case RevealOp: { michael@0: Reveal(); michael@0: } michael@0: break; michael@0: case LaunchOp: { michael@0: Launch(); michael@0: } michael@0: break; michael@0: } michael@0: CoUninitialize(); michael@0: michael@0: // Send the result back to the main thread so that it can shutdown michael@0: nsCOMPtr resultrunnable = new AsyncLocalFileWinDone(); michael@0: NS_DispatchToMainThread(resultrunnable); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: // Reveals the path in explorer. michael@0: nsresult Reveal() michael@0: { michael@0: DWORD attributes = GetFileAttributesW(mResolvedPath.get()); michael@0: if (INVALID_FILE_ATTRIBUTES == attributes) { michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: } michael@0: michael@0: HRESULT hr; michael@0: if (attributes & FILE_ATTRIBUTE_DIRECTORY) { michael@0: // We have a directory so we should open the directory itself. michael@0: ITEMIDLIST *dir = ILCreateFromPathW(mResolvedPath.get()); michael@0: if (!dir) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: const ITEMIDLIST* selection[] = { dir }; michael@0: UINT count = ArrayLength(selection); michael@0: michael@0: //Perform the open of the directory. michael@0: hr = SHOpenFolderAndSelectItems(dir, count, selection, 0); michael@0: CoTaskMemFree(dir); michael@0: } else { michael@0: int32_t len = mResolvedPath.Length(); michael@0: // We don't currently handle UNC long paths of the form \\?\ anywhere so michael@0: // this should be fine. michael@0: if (len > MAX_PATH) { michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: } michael@0: WCHAR parentDirectoryPath[MAX_PATH + 1] = { 0 }; michael@0: wcsncpy(parentDirectoryPath, mResolvedPath.get(), MAX_PATH); michael@0: PathRemoveFileSpecW(parentDirectoryPath); michael@0: michael@0: // We have a file so we should open the parent directory. michael@0: ITEMIDLIST *dir = ILCreateFromPathW(parentDirectoryPath); michael@0: if (!dir) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Set the item in the directory to select to the file we want to reveal. michael@0: ITEMIDLIST *item = ILCreateFromPathW(mResolvedPath.get()); michael@0: if (!item) { michael@0: CoTaskMemFree(dir); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: const ITEMIDLIST* selection[] = { item }; michael@0: UINT count = ArrayLength(selection); michael@0: michael@0: //Perform the selection of the file. michael@0: hr = SHOpenFolderAndSelectItems(dir, count, selection, 0); michael@0: michael@0: CoTaskMemFree(dir); michael@0: CoTaskMemFree(item); michael@0: } michael@0: michael@0: return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Launches the default shell operation for the file path michael@0: nsresult Launch() michael@0: { michael@0: // use the app registry name to launch a shell execute.... michael@0: SHELLEXECUTEINFOW seinfo; michael@0: memset(&seinfo, 0, sizeof(seinfo)); michael@0: seinfo.cbSize = sizeof(SHELLEXECUTEINFOW); michael@0: if (XRE_GetWindowsEnvironment() == WindowsEnvironmentType_Metro) { michael@0: seinfo.fMask = SEE_MASK_FLAG_LOG_USAGE; michael@0: } michael@0: seinfo.hwnd = nullptr; michael@0: seinfo.lpVerb = nullptr; michael@0: seinfo.lpFile = mResolvedPath.get(); michael@0: seinfo.lpParameters = nullptr; michael@0: seinfo.lpDirectory = nullptr; michael@0: seinfo.nShow = SW_SHOWNORMAL; michael@0: michael@0: // Use the directory of the file we're launching as the working michael@0: // directory. That way if we have a self extracting EXE it won't michael@0: // suggest to extract to the install directory. michael@0: WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' }; michael@0: wcsncpy(workingDirectory, mResolvedPath.get(), MAX_PATH); michael@0: if (PathRemoveFileSpecW(workingDirectory)) { michael@0: seinfo.lpDirectory = workingDirectory; michael@0: } else { michael@0: NS_WARNING("Could not set working directory for launched file."); michael@0: } michael@0: michael@0: if (ShellExecuteExW(&seinfo)) { michael@0: return NS_OK; michael@0: } michael@0: DWORD r = GetLastError(); michael@0: // if the file has no association, we launch windows' michael@0: // "what do you want to do" dialog michael@0: if (r == SE_ERR_NOASSOC) { michael@0: nsAutoString shellArg; michael@0: shellArg.Assign(NS_LITERAL_STRING("shell32.dll,OpenAs_RunDLL ") + michael@0: mResolvedPath); michael@0: seinfo.lpFile = L"RUNDLL32.EXE"; michael@0: seinfo.lpParameters = shellArg.get(); michael@0: if (ShellExecuteExW(&seinfo)) michael@0: return NS_OK; michael@0: r = GetLastError(); michael@0: } michael@0: if (r < 32) { michael@0: switch (r) { michael@0: case 0: michael@0: case SE_ERR_OOM: michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: case ERROR_FILE_NOT_FOUND: michael@0: return NS_ERROR_FILE_NOT_FOUND; michael@0: case ERROR_PATH_NOT_FOUND: michael@0: return NS_ERROR_FILE_UNRECOGNIZED_PATH; michael@0: case ERROR_BAD_FORMAT: michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: case SE_ERR_ACCESSDENIED: michael@0: return NS_ERROR_FILE_ACCESS_DENIED; michael@0: case SE_ERR_ASSOCINCOMPLETE: michael@0: case SE_ERR_NOASSOC: michael@0: return NS_ERROR_UNEXPECTED; michael@0: case SE_ERR_DDEBUSY: michael@0: case SE_ERR_DDEFAIL: michael@0: case SE_ERR_DDETIMEOUT: michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: case SE_ERR_DLLNOTFOUND: michael@0: return NS_ERROR_FAILURE; michael@0: case SE_ERR_SHARE: michael@0: return NS_ERROR_FILE_IS_LOCKED; michael@0: default: michael@0: return NS_ERROR_FILE_EXECUTION_FAILED; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Stores the operation that will be performed on the thread michael@0: AsyncLocalFileWinOperation::FileOp mOperation; michael@0: michael@0: // Stores the path to perform the operation on michael@0: nsString mResolvedPath; michael@0: }; michael@0: michael@0: class nsDriveEnumerator : public nsISimpleEnumerator michael@0: { michael@0: public: michael@0: nsDriveEnumerator(); michael@0: virtual ~nsDriveEnumerator(); michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSISIMPLEENUMERATOR michael@0: nsresult Init(); michael@0: private: michael@0: /* mDrives stores the null-separated drive names. michael@0: * Init sets them. michael@0: * HasMoreElements checks mStartOfCurrentDrive. michael@0: * GetNext advances mStartOfCurrentDrive. michael@0: */ michael@0: nsString mDrives; michael@0: nsAString::const_iterator mStartOfCurrentDrive; michael@0: nsAString::const_iterator mEndOfDrivesString; michael@0: }; michael@0: michael@0: //---------------------------------------------------------------------------- michael@0: // short cut resolver michael@0: //---------------------------------------------------------------------------- michael@0: class ShortcutResolver michael@0: { michael@0: public: michael@0: ShortcutResolver(); michael@0: // nonvirtual since we're not subclassed michael@0: ~ShortcutResolver(); michael@0: michael@0: nsresult Init(); michael@0: nsresult Resolve(const WCHAR* in, WCHAR* out); michael@0: nsresult SetShortcut(bool updateExisting, michael@0: const WCHAR* shortcutPath, michael@0: const WCHAR* targetPath, michael@0: const WCHAR* workingDir, michael@0: const WCHAR* args, michael@0: const WCHAR* description, michael@0: const WCHAR* iconFile, michael@0: int32_t iconIndex); michael@0: michael@0: private: michael@0: Mutex mLock; michael@0: nsRefPtr mPersistFile; michael@0: nsRefPtr mShellLink; michael@0: }; michael@0: michael@0: ShortcutResolver::ShortcutResolver() : michael@0: mLock("ShortcutResolver.mLock") michael@0: { michael@0: CoInitialize(nullptr); michael@0: } michael@0: michael@0: ShortcutResolver::~ShortcutResolver() michael@0: { michael@0: CoUninitialize(); michael@0: } michael@0: michael@0: nsresult michael@0: ShortcutResolver::Init() michael@0: { michael@0: // Get a pointer to the IPersistFile interface. michael@0: if (FAILED(CoCreateInstance(CLSID_ShellLink, michael@0: nullptr, michael@0: CLSCTX_INPROC_SERVER, michael@0: IID_IShellLinkW, michael@0: getter_AddRefs(mShellLink))) || michael@0: FAILED(mShellLink->QueryInterface(IID_IPersistFile, michael@0: getter_AddRefs(mPersistFile)))) { michael@0: mShellLink = nullptr; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // |out| must be an allocated buffer of size MAX_PATH michael@0: nsresult michael@0: ShortcutResolver::Resolve(const WCHAR* in, WCHAR* out) michael@0: { michael@0: if (!mShellLink) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: if (FAILED(mPersistFile->Load(in, STGM_READ)) || michael@0: FAILED(mShellLink->Resolve(nullptr, SLR_NO_UI)) || michael@0: FAILED(mShellLink->GetPath(out, MAX_PATH, nullptr, SLGP_UNCPRIORITY))) michael@0: return NS_ERROR_FAILURE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ShortcutResolver::SetShortcut(bool updateExisting, michael@0: const WCHAR* shortcutPath, michael@0: const WCHAR* targetPath, michael@0: const WCHAR* workingDir, michael@0: const WCHAR* args, michael@0: const WCHAR* description, michael@0: const WCHAR* iconPath, michael@0: int32_t iconIndex) michael@0: { michael@0: if (!mShellLink) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!shortcutPath) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: if (updateExisting) { michael@0: if (FAILED(mPersistFile->Load(shortcutPath, STGM_READWRITE))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } else { michael@0: if (!targetPath) { michael@0: return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; michael@0: } michael@0: michael@0: // Since we reuse our IPersistFile, we have to clear out any values that michael@0: // may be left over from previous calls to SetShortcut. michael@0: if (FAILED(mShellLink->SetWorkingDirectory(L"")) michael@0: || FAILED(mShellLink->SetArguments(L"")) michael@0: || FAILED(mShellLink->SetDescription(L"")) michael@0: || FAILED(mShellLink->SetIconLocation(L"", 0))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: if (targetPath && FAILED(mShellLink->SetPath(targetPath))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (workingDir && FAILED(mShellLink->SetWorkingDirectory(workingDir))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (args && FAILED(mShellLink->SetArguments(args))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (description && FAILED(mShellLink->SetDescription(description))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (iconPath && FAILED(mShellLink->SetIconLocation(iconPath, iconIndex))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (FAILED(mPersistFile->Save(shortcutPath, michael@0: TRUE))) { michael@0: // Second argument indicates whether the file path specified in the michael@0: // first argument should become the "current working file" for this michael@0: // IPersistFile michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static ShortcutResolver * gResolver = nullptr; michael@0: michael@0: static nsresult NS_CreateShortcutResolver() michael@0: { michael@0: gResolver = new ShortcutResolver(); michael@0: if (!gResolver) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: return gResolver->Init(); michael@0: } michael@0: michael@0: static void NS_DestroyShortcutResolver() michael@0: { michael@0: delete gResolver; michael@0: gResolver = nullptr; michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // static helper functions michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: // certainly not all the error that can be michael@0: // encountered, but many of them common ones michael@0: static nsresult ConvertWinError(DWORD winErr) michael@0: { michael@0: nsresult rv; michael@0: michael@0: switch (winErr) michael@0: { michael@0: case ERROR_FILE_NOT_FOUND: michael@0: case ERROR_PATH_NOT_FOUND: michael@0: case ERROR_INVALID_DRIVE: michael@0: rv = NS_ERROR_FILE_NOT_FOUND; michael@0: break; michael@0: case ERROR_ACCESS_DENIED: michael@0: case ERROR_NOT_SAME_DEVICE: michael@0: rv = NS_ERROR_FILE_ACCESS_DENIED; michael@0: break; michael@0: case ERROR_SHARING_VIOLATION: // CreateFile without sharing flags michael@0: case ERROR_LOCK_VIOLATION: // LockFile, LockFileEx michael@0: rv = NS_ERROR_FILE_IS_LOCKED; michael@0: break; michael@0: case ERROR_NOT_ENOUGH_MEMORY: michael@0: case ERROR_INVALID_BLOCK: michael@0: case ERROR_INVALID_HANDLE: michael@0: case ERROR_ARENA_TRASHED: michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: break; michael@0: case ERROR_CURRENT_DIRECTORY: michael@0: rv = NS_ERROR_FILE_DIR_NOT_EMPTY; michael@0: break; michael@0: case ERROR_WRITE_PROTECT: michael@0: rv = NS_ERROR_FILE_READ_ONLY; michael@0: break; michael@0: case ERROR_HANDLE_DISK_FULL: michael@0: rv = NS_ERROR_FILE_TOO_BIG; michael@0: break; michael@0: case ERROR_FILE_EXISTS: michael@0: case ERROR_ALREADY_EXISTS: michael@0: case ERROR_CANNOT_MAKE: michael@0: rv = NS_ERROR_FILE_ALREADY_EXISTS; michael@0: break; michael@0: case ERROR_FILENAME_EXCED_RANGE: michael@0: rv = NS_ERROR_FILE_NAME_TOO_LONG; michael@0: break; michael@0: case ERROR_DIRECTORY: michael@0: rv = NS_ERROR_FILE_NOT_DIRECTORY; michael@0: break; michael@0: case 0: michael@0: rv = NS_OK; michael@0: break; michael@0: default: michael@0: rv = NS_ERROR_FAILURE; michael@0: break; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: // as suggested in the MSDN documentation on SetFilePointer michael@0: static __int64 michael@0: MyFileSeek64(HANDLE aHandle, __int64 aDistance, DWORD aMoveMethod) michael@0: { michael@0: LARGE_INTEGER li; michael@0: michael@0: li.QuadPart = aDistance; michael@0: li.LowPart = SetFilePointer(aHandle, li.LowPart, &li.HighPart, aMoveMethod); michael@0: if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) michael@0: { michael@0: li.QuadPart = -1; michael@0: } michael@0: michael@0: return li.QuadPart; michael@0: } michael@0: michael@0: static bool michael@0: IsShortcutPath(const nsAString &path) michael@0: { michael@0: // Under Windows, the shortcuts are just files with a ".lnk" extension. michael@0: // Note also that we don't resolve links in the middle of paths. michael@0: // i.e. "c:\foo.lnk\bar.txt" is invalid. michael@0: NS_ABORT_IF_FALSE(!path.IsEmpty(), "don't pass an empty string"); michael@0: int32_t len = path.Length(); michael@0: return len >= 4 && (StringTail(path, 4).LowerCaseEqualsASCII(".lnk")); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // We need the following three definitions to make |OpenFile| convert a file michael@0: // handle to an NSPR file descriptor correctly when |O_APPEND| flag is michael@0: // specified. It is defined in a private header of NSPR (primpl.h) we can't michael@0: // include. As a temporary workaround until we decide how to extend michael@0: // |PR_ImportFile|, we define it here. Currently, |_PR_HAVE_PEEK_BUFFER| michael@0: // and |PR_STRICT_ADDR_LEN| are not defined for the 'w95'-dependent portion michael@0: // of NSPR so that fields of |PRFilePrivate| #ifdef'd by them are not copied. michael@0: // Similarly, |_MDFileDesc| is taken from nsprpub/pr/include/md/_win95.h. michael@0: // In an unlikely case we switch to 'NT'-dependent NSPR AND this temporary michael@0: // workaround last beyond the switch, |PRFilePrivate| and |_MDFileDesc| michael@0: // need to be changed to match the definitions for WinNT. michael@0: //----------------------------------------------------------------------------- michael@0: typedef enum { michael@0: _PR_TRI_TRUE = 1, michael@0: _PR_TRI_FALSE = 0, michael@0: _PR_TRI_UNKNOWN = -1 michael@0: } _PRTriStateBool; michael@0: michael@0: struct _MDFileDesc { michael@0: PROsfd osfd; michael@0: }; michael@0: michael@0: struct PRFilePrivate { michael@0: int32_t state; michael@0: bool nonblocking; michael@0: _PRTriStateBool inheritable; michael@0: PRFileDesc *next; michael@0: int lockCount; /* 0: not locked michael@0: * -1: a native lockfile call is in progress michael@0: * > 0: # times the file is locked */ michael@0: bool appendMode; michael@0: _MDFileDesc md; michael@0: }; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // Six static methods defined below (OpenFile, FileTimeToPRTime, GetFileInfo, michael@0: // OpenDir, CloseDir, ReadDir) should go away once the corresponding michael@0: // UTF-16 APIs are implemented on all the supported platforms (or at least michael@0: // Windows 9x/ME) in NSPR. Currently, they're only implemented on michael@0: // Windows NT4 or later. (bug 330665) michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} : michael@0: // PR_Open and _PR_MD_OPEN michael@0: nsresult michael@0: OpenFile(const nsAFlatString &name, int osflags, int mode, michael@0: PRFileDesc **fd) michael@0: { michael@0: int32_t access = 0; michael@0: michael@0: int32_t disposition = 0; michael@0: int32_t attributes = 0; michael@0: michael@0: if (osflags & PR_SYNC) michael@0: attributes = FILE_FLAG_WRITE_THROUGH; michael@0: if (osflags & PR_RDONLY || osflags & PR_RDWR) michael@0: access |= GENERIC_READ; michael@0: if (osflags & PR_WRONLY || osflags & PR_RDWR) michael@0: access |= GENERIC_WRITE; michael@0: michael@0: if ( osflags & PR_CREATE_FILE && osflags & PR_EXCL ) michael@0: disposition = CREATE_NEW; michael@0: else if (osflags & PR_CREATE_FILE) { michael@0: if (osflags & PR_TRUNCATE) michael@0: disposition = CREATE_ALWAYS; michael@0: else michael@0: disposition = OPEN_ALWAYS; michael@0: } else { michael@0: if (osflags & PR_TRUNCATE) michael@0: disposition = TRUNCATE_EXISTING; michael@0: else michael@0: disposition = OPEN_EXISTING; michael@0: } michael@0: michael@0: if (osflags & nsIFile::DELETE_ON_CLOSE) { michael@0: attributes |= FILE_FLAG_DELETE_ON_CLOSE; michael@0: } michael@0: michael@0: if (osflags & nsIFile::OS_READAHEAD) { michael@0: attributes |= FILE_FLAG_SEQUENTIAL_SCAN; michael@0: } michael@0: michael@0: // If no write permissions are requested, and if we are possibly creating michael@0: // the file, then set the new file as read only. michael@0: // The flag has no effect if we happen to open the file. michael@0: if (!(mode & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) && michael@0: disposition != OPEN_EXISTING) { michael@0: attributes |= FILE_ATTRIBUTE_READONLY; michael@0: } michael@0: michael@0: HANDLE file = ::CreateFileW(name.get(), access, michael@0: FILE_SHARE_READ|FILE_SHARE_WRITE, michael@0: nullptr, disposition, attributes, nullptr); michael@0: michael@0: if (file == INVALID_HANDLE_VALUE) { michael@0: *fd = nullptr; michael@0: return ConvertWinError(GetLastError()); michael@0: } michael@0: michael@0: *fd = PR_ImportFile((PROsfd) file); michael@0: if (*fd) { michael@0: // On Windows, _PR_HAVE_O_APPEND is not defined so that we have to michael@0: // add it manually. (see |PR_Open| in nsprpub/pr/src/io/prfile.c) michael@0: (*fd)->secret->appendMode = (PR_APPEND & osflags) ? true : false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv = NS_ErrorAccordingToNSPR(); michael@0: michael@0: CloseHandle(file); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} : michael@0: // PR_FileTimeToPRTime and _PR_FileTimeToPRTime michael@0: static michael@0: void FileTimeToPRTime(const FILETIME *filetime, PRTime *prtm) michael@0: { michael@0: #ifdef __GNUC__ michael@0: const PRTime _pr_filetime_offset = 116444736000000000LL; michael@0: #else michael@0: const PRTime _pr_filetime_offset = 116444736000000000i64; michael@0: #endif michael@0: michael@0: PR_ASSERT(sizeof(FILETIME) == sizeof(PRTime)); michael@0: ::CopyMemory(prtm, filetime, sizeof(PRTime)); michael@0: #ifdef __GNUC__ michael@0: *prtm = (*prtm - _pr_filetime_offset) / 10LL; michael@0: #else michael@0: *prtm = (*prtm - _pr_filetime_offset) / 10i64; michael@0: #endif michael@0: } michael@0: michael@0: // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} with some michael@0: // changes : PR_GetFileInfo64, _PR_MD_GETFILEINFO64 michael@0: static nsresult michael@0: GetFileInfo(const nsAFlatString &name, PRFileInfo64 *info) michael@0: { michael@0: WIN32_FILE_ATTRIBUTE_DATA fileData; michael@0: michael@0: if (name.IsEmpty() || name.FindCharInSet(MOZ_UTF16("?*")) != kNotFound) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: if (!::GetFileAttributesExW(name.get(), GetFileExInfoStandard, &fileData)) michael@0: return ConvertWinError(GetLastError()); michael@0: michael@0: if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { michael@0: info->type = PR_FILE_DIRECTORY; michael@0: } else { michael@0: info->type = PR_FILE_FILE; michael@0: } michael@0: michael@0: info->size = fileData.nFileSizeHigh; michael@0: info->size = (info->size << 32) + fileData.nFileSizeLow; michael@0: michael@0: FileTimeToPRTime(&fileData.ftLastWriteTime, &info->modifyTime); michael@0: michael@0: if (0 == fileData.ftCreationTime.dwLowDateTime && michael@0: 0 == fileData.ftCreationTime.dwHighDateTime) { michael@0: info->creationTime = info->modifyTime; michael@0: } else { michael@0: FileTimeToPRTime(&fileData.ftCreationTime, &info->creationTime); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: struct nsDir michael@0: { michael@0: HANDLE handle; michael@0: WIN32_FIND_DATAW data; michael@0: bool firstEntry; michael@0: }; michael@0: michael@0: static nsresult michael@0: OpenDir(const nsAFlatString &name, nsDir * *dir) michael@0: { michael@0: if (NS_WARN_IF(!dir)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: *dir = nullptr; michael@0: if (name.Length() + 3 >= MAX_PATH) michael@0: return NS_ERROR_FILE_NAME_TOO_LONG; michael@0: michael@0: nsDir *d = PR_NEW(nsDir); michael@0: if (!d) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: nsAutoString filename(name); michael@0: michael@0: //If 'name' ends in a slash or backslash, do not append michael@0: //another backslash. michael@0: if (filename.Last() == L'/' || filename.Last() == L'\\') michael@0: filename.Append('*'); michael@0: else michael@0: filename.AppendLiteral("\\*"); michael@0: michael@0: filename.ReplaceChar(L'/', L'\\'); michael@0: michael@0: // FindFirstFileW Will have a last error of ERROR_DIRECTORY if michael@0: // \* is passed in. If \* is passed in then michael@0: // ERROR_PATH_NOT_FOUND will be the last error. michael@0: d->handle = ::FindFirstFileW(filename.get(), &(d->data) ); michael@0: michael@0: if (d->handle == INVALID_HANDLE_VALUE) { michael@0: PR_Free(d); michael@0: return ConvertWinError(GetLastError()); michael@0: } michael@0: d->firstEntry = true; michael@0: michael@0: *dir = d; michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: ReadDir(nsDir *dir, PRDirFlags flags, nsString& name) michael@0: { michael@0: name.Truncate(); michael@0: if (NS_WARN_IF(!dir)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: while (1) { michael@0: BOOL rv; michael@0: if (dir->firstEntry) michael@0: { michael@0: dir->firstEntry = false; michael@0: rv = 1; michael@0: } else michael@0: rv = ::FindNextFileW(dir->handle, &(dir->data)); michael@0: michael@0: if (rv == 0) michael@0: break; michael@0: michael@0: const wchar_t *fileName; michael@0: nsString tmp; michael@0: fileName = (dir)->data.cFileName; michael@0: michael@0: if ((flags & PR_SKIP_DOT) && michael@0: (fileName[0] == L'.') && (fileName[1] == L'\0')) michael@0: continue; michael@0: if ((flags & PR_SKIP_DOT_DOT) && michael@0: (fileName[0] == L'.') && (fileName[1] == L'.') && michael@0: (fileName[2] == L'\0')) michael@0: continue; michael@0: michael@0: DWORD attrib = dir->data.dwFileAttributes; michael@0: if ((flags & PR_SKIP_HIDDEN) && (attrib & FILE_ATTRIBUTE_HIDDEN)) michael@0: continue; michael@0: michael@0: if (fileName == tmp.get()) michael@0: name = tmp; michael@0: else michael@0: name = fileName; michael@0: return NS_OK; michael@0: } michael@0: michael@0: DWORD err = GetLastError(); michael@0: return err == ERROR_NO_MORE_FILES ? NS_OK : ConvertWinError(err); michael@0: } michael@0: michael@0: static nsresult michael@0: CloseDir(nsDir *&d) michael@0: { michael@0: if (NS_WARN_IF(!d)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: BOOL isOk = FindClose(d->handle); michael@0: // PR_DELETE also nulls out the passed in pointer. michael@0: PR_DELETE(d); michael@0: return isOk ? NS_OK : ConvertWinError(GetLastError()); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsDirEnumerator michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: class nsDirEnumerator MOZ_FINAL : public nsISimpleEnumerator, michael@0: public nsIDirectoryEnumerator michael@0: { michael@0: public: michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: nsDirEnumerator() : mDir(nullptr) michael@0: { michael@0: } michael@0: michael@0: nsresult Init(nsIFile* parent) michael@0: { michael@0: nsAutoString filepath; michael@0: parent->GetTarget(filepath); michael@0: michael@0: if (filepath.IsEmpty()) michael@0: { michael@0: parent->GetPath(filepath); michael@0: } michael@0: michael@0: if (filepath.IsEmpty()) michael@0: { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // IsDirectory is not needed here because OpenDir will return michael@0: // NS_ERROR_FILE_NOT_DIRECTORY if the passed in path is a file. michael@0: nsresult rv = OpenDir(filepath, &mDir); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: mParent = parent; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD HasMoreElements(bool *result) michael@0: { michael@0: nsresult rv; michael@0: if (mNext == nullptr && mDir) michael@0: { michael@0: nsString name; michael@0: rv = ReadDir(mDir, PR_SKIP_BOTH, name); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: if (name.IsEmpty()) michael@0: { michael@0: // end of dir entries michael@0: if (NS_FAILED(CloseDir(mDir))) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: *result = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr file; michael@0: rv = mParent->Clone(getter_AddRefs(file)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = file->Append(name); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: mNext = do_QueryInterface(file); michael@0: } michael@0: *result = mNext != nullptr; michael@0: if (!*result) michael@0: Close(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD GetNext(nsISupports **result) michael@0: { michael@0: nsresult rv; michael@0: bool hasMore; michael@0: rv = HasMoreElements(&hasMore); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: *result = mNext; // might return nullptr michael@0: NS_IF_ADDREF(*result); michael@0: michael@0: mNext = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD GetNextFile(nsIFile **result) michael@0: { michael@0: *result = nullptr; michael@0: bool hasMore = false; michael@0: nsresult rv = HasMoreElements(&hasMore); michael@0: if (NS_FAILED(rv) || !hasMore) michael@0: return rv; michael@0: *result = mNext; michael@0: NS_IF_ADDREF(*result); michael@0: mNext = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD Close() michael@0: { michael@0: if (mDir) michael@0: { michael@0: nsresult rv = CloseDir(mDir); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "close failed"); michael@0: if (NS_FAILED(rv)) michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // dtor can be non-virtual since there are no subclasses, but must be michael@0: // public to use the class on the stack. michael@0: ~nsDirEnumerator() michael@0: { michael@0: Close(); michael@0: } michael@0: michael@0: protected: michael@0: nsDir* mDir; michael@0: nsCOMPtr mParent; michael@0: nsCOMPtr mNext; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsDirEnumerator, nsISimpleEnumerator, nsIDirectoryEnumerator) michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsLocalFile michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsLocalFile::nsLocalFile() michael@0: : mDirty(true) michael@0: , mResolveDirty(true) michael@0: , mFollowSymlinks(false) michael@0: { michael@0: } michael@0: michael@0: nsresult michael@0: nsLocalFile::nsLocalFileConstructor(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr) michael@0: { michael@0: if (NS_WARN_IF(!aInstancePtr)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: if (NS_WARN_IF(outer)) michael@0: return NS_ERROR_NO_AGGREGATION; michael@0: michael@0: nsLocalFile* inst = new nsLocalFile(); michael@0: if (inst == nullptr) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: nsresult rv = inst->QueryInterface(aIID, aInstancePtr); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: delete inst; michael@0: return rv; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsLocalFile::nsISupports michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_ISUPPORTS(nsLocalFile, michael@0: nsILocalFile, michael@0: nsIFile, michael@0: nsILocalFileWin, michael@0: nsIHashable) michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsLocalFile michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsLocalFile::nsLocalFile(const nsLocalFile& other) michael@0: : mDirty(true) michael@0: , mResolveDirty(true) michael@0: , mFollowSymlinks(other.mFollowSymlinks) michael@0: , mWorkingPath(other.mWorkingPath) michael@0: { michael@0: } michael@0: michael@0: // Resolve the shortcut file from mWorkingPath and write the path michael@0: // it points to into mResolvedPath. michael@0: nsresult michael@0: nsLocalFile::ResolveShortcut() michael@0: { michael@0: // we can't do anything without the resolver michael@0: if (!gResolver) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: mResolvedPath.SetLength(MAX_PATH); michael@0: if (mResolvedPath.Length() != MAX_PATH) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: wchar_t *resolvedPath = wwc(mResolvedPath.BeginWriting()); michael@0: michael@0: // resolve this shortcut michael@0: nsresult rv = gResolver->Resolve(mWorkingPath.get(), resolvedPath); michael@0: michael@0: size_t len = NS_FAILED(rv) ? 0 : wcslen(resolvedPath); michael@0: mResolvedPath.SetLength(len); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // Resolve any shortcuts and stat the resolved path. After a successful return michael@0: // the path is guaranteed valid and the members of mFileInfo64 can be used. michael@0: nsresult michael@0: nsLocalFile::ResolveAndStat() michael@0: { michael@0: // if we aren't dirty then we are already done michael@0: if (!mDirty) michael@0: return NS_OK; michael@0: michael@0: // we can't resolve/stat anything that isn't a valid NSPR addressable path michael@0: if (mWorkingPath.IsEmpty()) michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: michael@0: // this is usually correct michael@0: mResolvedPath.Assign(mWorkingPath); michael@0: michael@0: // slutty hack designed to work around bug 134796 until it is fixed michael@0: nsAutoString nsprPath(mWorkingPath.get()); michael@0: if (mWorkingPath.Length() == 2 && mWorkingPath.CharAt(1) == L':') michael@0: nsprPath.Append('\\'); michael@0: michael@0: // first we will see if the working path exists. If it doesn't then michael@0: // there is nothing more that can be done michael@0: nsresult rv = GetFileInfo(nsprPath, &mFileInfo64); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // if this isn't a shortcut file or we aren't following symlinks then we're done michael@0: if (!mFollowSymlinks michael@0: || mFileInfo64.type != PR_FILE_FILE michael@0: || !IsShortcutPath(mWorkingPath)) michael@0: { michael@0: mDirty = false; michael@0: mResolveDirty = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // we need to resolve this shortcut to what it points to, this will michael@0: // set mResolvedPath. Even if it fails we need to have the resolved michael@0: // path equal to working path for those functions that always use michael@0: // the resolved path. michael@0: rv = ResolveShortcut(); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: mResolvedPath.Assign(mWorkingPath); michael@0: return rv; michael@0: } michael@0: mResolveDirty = false; michael@0: michael@0: // get the details of the resolved path michael@0: rv = GetFileInfo(mResolvedPath, &mFileInfo64); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: mDirty = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Fills the mResolvedPath member variable with the file or symlink target michael@0: * if follow symlinks is on. This is a copy of the Resolve parts from michael@0: * ResolveAndStat. ResolveAndStat is much slower though because of the stat. michael@0: * michael@0: * @return NS_OK on success. michael@0: */ michael@0: nsresult michael@0: nsLocalFile::Resolve() michael@0: { michael@0: // if we aren't dirty then we are already done michael@0: if (!mResolveDirty) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // we can't resolve/stat anything that isn't a valid NSPR addressable path michael@0: if (mWorkingPath.IsEmpty()) { michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: } michael@0: michael@0: // this is usually correct michael@0: mResolvedPath.Assign(mWorkingPath); michael@0: michael@0: // if this isn't a shortcut file or we aren't following symlinks then michael@0: // we're done. michael@0: if (!mFollowSymlinks || michael@0: !IsShortcutPath(mWorkingPath)) { michael@0: mResolveDirty = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // we need to resolve this shortcut to what it points to, this will michael@0: // set mResolvedPath. Even if it fails we need to have the resolved michael@0: // path equal to working path for those functions that always use michael@0: // the resolved path. michael@0: nsresult rv = ResolveShortcut(); michael@0: if (NS_FAILED(rv)) { michael@0: mResolvedPath.Assign(mWorkingPath); michael@0: return rv; michael@0: } michael@0: michael@0: mResolveDirty = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsLocalFile::nsIFile,nsILocalFile michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::Clone(nsIFile **file) michael@0: { michael@0: // Just copy-construct ourselves michael@0: *file = new nsLocalFile(*this); michael@0: if (!*file) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: NS_ADDREF(*file); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::InitWithFile(nsIFile *aFile) michael@0: { michael@0: if (NS_WARN_IF(!aFile)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: nsAutoString path; michael@0: aFile->GetPath(path); michael@0: if (path.IsEmpty()) michael@0: return NS_ERROR_INVALID_ARG; michael@0: return InitWithPath(path); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::InitWithPath(const nsAString &filePath) michael@0: { michael@0: MakeDirty(); michael@0: michael@0: nsAString::const_iterator begin, end; michael@0: filePath.BeginReading(begin); michael@0: filePath.EndReading(end); michael@0: michael@0: // input string must not be empty michael@0: if (begin == end) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: char16_t firstChar = *begin; michael@0: char16_t secondChar = *(++begin); michael@0: michael@0: // just do a sanity check. if it has any forward slashes, it is not a Native path michael@0: // on windows. Also, it must have a colon at after the first char. michael@0: if (FindCharInReadable(L'/', begin, end)) michael@0: return NS_ERROR_FILE_UNRECOGNIZED_PATH; michael@0: michael@0: if (secondChar != L':' && (secondChar != L'\\' || firstChar != L'\\')) michael@0: return NS_ERROR_FILE_UNRECOGNIZED_PATH; michael@0: michael@0: if (secondChar == L':') { michael@0: // Make sure we have a valid drive, later code assumes the drive letter michael@0: // is a single char a-z or A-Z. michael@0: if (PathGetDriveNumberW(filePath.Data()) == -1) { michael@0: return NS_ERROR_FILE_UNRECOGNIZED_PATH; michael@0: } michael@0: } michael@0: michael@0: mWorkingPath = filePath; michael@0: // kill any trailing '\' michael@0: if (mWorkingPath.Last() == L'\\') michael@0: mWorkingPath.Truncate(mWorkingPath.Length() - 1); michael@0: michael@0: return NS_OK; michael@0: michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::OpenNSPRFileDesc(int32_t flags, int32_t mode, PRFileDesc **_retval) michael@0: { michael@0: nsresult rv = Resolve(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: return OpenFile(mResolvedPath, flags, mode, _retval); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::OpenANSIFileDesc(const char *mode, FILE * *_retval) michael@0: { michael@0: nsresult rv = ResolveAndStat(); michael@0: if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) michael@0: return rv; michael@0: michael@0: *_retval = _wfopen(mResolvedPath.get(), NS_ConvertASCIItoUTF16(mode).get()); michael@0: if (*_retval) michael@0: return NS_OK; michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::Create(uint32_t type, uint32_t attributes) michael@0: { michael@0: if (type != NORMAL_FILE_TYPE && type != DIRECTORY_TYPE) michael@0: return NS_ERROR_FILE_UNKNOWN_TYPE; michael@0: michael@0: nsresult rv = ResolveAndStat(); michael@0: if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) michael@0: return rv; michael@0: michael@0: // create directories to target michael@0: // michael@0: // A given local file can be either one of these forms: michael@0: // michael@0: // - normal: X:\some\path\on\this\drive michael@0: // ^--- start here michael@0: // michael@0: // - UNC path: \\machine\volume\some\path\on\this\drive michael@0: // ^--- start here michael@0: // michael@0: // Skip the first 'X:\' for the first form, and skip the first full michael@0: // '\\machine\volume\' segment for the second form. michael@0: michael@0: wchar_t* path = wwc(mResolvedPath.BeginWriting()); michael@0: michael@0: if (path[0] == L'\\' && path[1] == L'\\') michael@0: { michael@0: // dealing with a UNC path here; skip past '\\machine\' michael@0: path = wcschr(path + 2, L'\\'); michael@0: if (!path) michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: ++path; michael@0: } michael@0: michael@0: // search for first slash after the drive (or volume) name michael@0: wchar_t* slash = wcschr(path, L'\\'); michael@0: michael@0: nsresult directoryCreateError = NS_OK; michael@0: if (slash) michael@0: { michael@0: // skip the first '\\' michael@0: ++slash; michael@0: slash = wcschr(slash, L'\\'); michael@0: michael@0: while (slash) michael@0: { michael@0: *slash = L'\0'; michael@0: michael@0: if (!::CreateDirectoryW(mResolvedPath.get(), nullptr)) { michael@0: rv = ConvertWinError(GetLastError()); michael@0: if (NS_ERROR_FILE_NOT_FOUND == rv && michael@0: NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { michael@0: // If a previous CreateDirectory failed due to access, return that. michael@0: return NS_ERROR_FILE_ACCESS_DENIED; michael@0: } michael@0: // perhaps the base path already exists, or perhaps we don't have michael@0: // permissions to create the directory. NOTE: access denied could michael@0: // occur on a parent directory even though it exists. michael@0: else if (NS_ERROR_FILE_ALREADY_EXISTS != rv && michael@0: NS_ERROR_FILE_ACCESS_DENIED != rv) { michael@0: return rv; michael@0: } michael@0: michael@0: directoryCreateError = rv; michael@0: } michael@0: *slash = L'\\'; michael@0: ++slash; michael@0: slash = wcschr(slash, L'\\'); michael@0: } michael@0: } michael@0: michael@0: if (type == NORMAL_FILE_TYPE) michael@0: { michael@0: PRFileDesc* file; michael@0: rv = OpenFile(mResolvedPath, michael@0: PR_RDONLY | PR_CREATE_FILE | PR_APPEND | PR_EXCL, attributes, michael@0: &file); michael@0: if (file) michael@0: PR_Close(file); michael@0: michael@0: if (rv == NS_ERROR_FILE_ACCESS_DENIED) michael@0: { michael@0: // need to return already-exists for directories (bug 452217) michael@0: bool isdir; michael@0: if (NS_SUCCEEDED(IsDirectory(&isdir)) && isdir) michael@0: rv = NS_ERROR_FILE_ALREADY_EXISTS; michael@0: } else if (NS_ERROR_FILE_NOT_FOUND == rv && michael@0: NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { michael@0: // If a previous CreateDirectory failed due to access, return that. michael@0: return NS_ERROR_FILE_ACCESS_DENIED; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: if (type == DIRECTORY_TYPE) michael@0: { michael@0: if (!::CreateDirectoryW(mResolvedPath.get(), nullptr)) { michael@0: rv = ConvertWinError(GetLastError()); michael@0: if (NS_ERROR_FILE_NOT_FOUND == rv && michael@0: NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { michael@0: // If a previous CreateDirectory failed due to access, return that. michael@0: return NS_ERROR_FILE_ACCESS_DENIED; michael@0: } else { michael@0: return rv; michael@0: } michael@0: } michael@0: else michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_FILE_UNKNOWN_TYPE; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::Append(const nsAString &node) michael@0: { michael@0: // append this path, multiple components are not permitted michael@0: return AppendInternal(PromiseFlatString(node), false); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::AppendRelativePath(const nsAString &node) michael@0: { michael@0: // append this path, multiple components are permitted michael@0: return AppendInternal(PromiseFlatString(node), true); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsLocalFile::AppendInternal(const nsAFlatString &node, bool multipleComponents) michael@0: { michael@0: if (node.IsEmpty()) michael@0: return NS_OK; michael@0: michael@0: // check the relative path for validity michael@0: if (node.First() == L'\\' // can't start with an '\' michael@0: || node.FindChar(L'/') != kNotFound // can't contain / michael@0: || node.EqualsASCII("..")) // can't be .. michael@0: return NS_ERROR_FILE_UNRECOGNIZED_PATH; michael@0: michael@0: if (multipleComponents) michael@0: { michael@0: // can't contain .. as a path component. Ensure that the valid components michael@0: // "foo..foo", "..foo", and "foo.." are not falsely detected, michael@0: // but the invalid paths "..\", "foo\..", "foo\..\foo", michael@0: // "..\foo", etc are. michael@0: NS_NAMED_LITERAL_STRING(doubleDot, "\\.."); michael@0: nsAString::const_iterator start, end, offset; michael@0: node.BeginReading(start); michael@0: node.EndReading(end); michael@0: offset = end; michael@0: while (FindInReadable(doubleDot, start, offset)) michael@0: { michael@0: if (offset == end || *offset == L'\\') michael@0: return NS_ERROR_FILE_UNRECOGNIZED_PATH; michael@0: start = offset; michael@0: offset = end; michael@0: } michael@0: michael@0: // catches the remaining cases of prefixes michael@0: if (StringBeginsWith(node, NS_LITERAL_STRING("..\\"))) michael@0: return NS_ERROR_FILE_UNRECOGNIZED_PATH; michael@0: } michael@0: // single components can't contain '\' michael@0: else if (node.FindChar(L'\\') != kNotFound) michael@0: return NS_ERROR_FILE_UNRECOGNIZED_PATH; michael@0: michael@0: MakeDirty(); michael@0: michael@0: mWorkingPath.Append(NS_LITERAL_STRING("\\") + node); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: #define TOUPPER(u) (((u) >= L'a' && (u) <= L'z') ? \ michael@0: (u) - (L'a' - L'A') : (u)) michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::Normalize() michael@0: { michael@0: // XXX See bug 187957 comment 18 for possible problems with this implementation. michael@0: michael@0: if (mWorkingPath.IsEmpty()) michael@0: return NS_OK; michael@0: michael@0: nsAutoString path(mWorkingPath); michael@0: michael@0: // find the index of the root backslash for the path. Everything before michael@0: // this is considered fully normalized and cannot be ascended beyond michael@0: // using ".." For a local drive this is the first slash (e.g. "c:\"). michael@0: // For a UNC path it is the slash following the share name michael@0: // (e.g. "\\server\share\"). michael@0: int32_t rootIdx = 2; // default to local drive michael@0: if (path.First() == L'\\') // if a share then calculate the rootIdx michael@0: { michael@0: rootIdx = path.FindChar(L'\\', 2); // skip \\ in front of the server michael@0: if (rootIdx == kNotFound) michael@0: return NS_OK; // already normalized michael@0: rootIdx = path.FindChar(L'\\', rootIdx+1); michael@0: if (rootIdx == kNotFound) michael@0: return NS_OK; // already normalized michael@0: } michael@0: else if (path.CharAt(rootIdx) != L'\\') michael@0: { michael@0: // The path has been specified relative to the current working directory michael@0: // for that drive. To normalize it, the current working directory for michael@0: // that drive needs to be inserted before the supplied relative path michael@0: // which will provide an absolute path (and the rootIdx will still be 2). michael@0: WCHAR cwd[MAX_PATH]; michael@0: WCHAR * pcwd = cwd; michael@0: int drive = TOUPPER(path.First()) - 'A' + 1; michael@0: /* We need to worry about IPH, for details read bug 419326. michael@0: * _getdrives - http://msdn2.microsoft.com/en-us/library/xdhk0xd2.aspx michael@0: * uses a bitmask, bit 0 is 'a:' michael@0: * _chdrive - http://msdn2.microsoft.com/en-us/library/0d1409hb.aspx michael@0: * _getdcwd - http://msdn2.microsoft.com/en-us/library/7t2zk3s4.aspx michael@0: * take an int, 1 is 'a:'. michael@0: * michael@0: * Because of this, we need to do some math. Subtract 1 to convert from michael@0: * _chdrive/_getdcwd format to _getdrives drive numbering. michael@0: * Shift left x bits to convert from integer indexing to bitfield indexing. michael@0: * And of course, we need to find out if the drive is in the bitmask. michael@0: * michael@0: * If we're really unlucky, we can still lose, but only if the user michael@0: * manages to eject the drive between our call to _getdrives() and michael@0: * our *calls* to _wgetdcwd. michael@0: */ michael@0: if (!((1 << (drive - 1)) & _getdrives())) michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: if (!_wgetdcwd(drive, pcwd, MAX_PATH)) michael@0: pcwd = _wgetdcwd(drive, 0, 0); michael@0: if (!pcwd) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: nsAutoString currentDir(pcwd); michael@0: if (pcwd != cwd) michael@0: free(pcwd); michael@0: michael@0: if (currentDir.Last() == '\\') michael@0: path.Replace(0, 2, currentDir); michael@0: else michael@0: path.Replace(0, 2, currentDir + NS_LITERAL_STRING("\\")); michael@0: } michael@0: NS_POSTCONDITION(0 < rootIdx && rootIdx < (int32_t)path.Length(), "rootIdx is invalid"); michael@0: NS_POSTCONDITION(path.CharAt(rootIdx) == '\\', "rootIdx is invalid"); michael@0: michael@0: // if there is nothing following the root path then it is already normalized michael@0: if (rootIdx + 1 == (int32_t)path.Length()) michael@0: return NS_OK; michael@0: michael@0: // assign the root michael@0: const char16_t * pathBuffer = path.get(); // simplify access to the buffer michael@0: mWorkingPath.SetCapacity(path.Length()); // it won't ever grow longer michael@0: mWorkingPath.Assign(pathBuffer, rootIdx); michael@0: michael@0: // Normalize the path components. The actions taken are: michael@0: // michael@0: // "\\" condense to single backslash michael@0: // "." remove from path michael@0: // ".." up a directory michael@0: // "..." remove from path (any number of dots > 2) michael@0: // michael@0: // The last form is something that Windows 95 and 98 supported and michael@0: // is a shortcut for changing up multiple directories. Windows XP michael@0: // and ilk ignore it in a path, as is done here. michael@0: int32_t len, begin, end = rootIdx; michael@0: while (end < (int32_t)path.Length()) michael@0: { michael@0: // find the current segment (text between the backslashes) to michael@0: // be examined, this will set the following variables: michael@0: // begin == index of first char in segment michael@0: // end == index 1 char after last char in segment michael@0: // len == length of segment michael@0: begin = end + 1; michael@0: end = path.FindChar('\\', begin); michael@0: if (end == kNotFound) michael@0: end = path.Length(); michael@0: len = end - begin; michael@0: michael@0: // ignore double backslashes michael@0: if (len == 0) michael@0: continue; michael@0: michael@0: // len != 0, and interesting paths always begin with a dot michael@0: if (pathBuffer[begin] == '.') michael@0: { michael@0: // ignore single dots michael@0: if (len == 1) michael@0: continue; michael@0: michael@0: // handle multiple dots michael@0: if (len >= 2 && pathBuffer[begin+1] == L'.') michael@0: { michael@0: // back up a path component on double dot michael@0: if (len == 2) michael@0: { michael@0: int32_t prev = mWorkingPath.RFindChar('\\'); michael@0: if (prev >= rootIdx) michael@0: mWorkingPath.Truncate(prev); michael@0: continue; michael@0: } michael@0: michael@0: // length is > 2 and the first two characters are dots. michael@0: // if the rest of the string is dots, then ignore it. michael@0: int idx = len - 1; michael@0: for (; idx >= 2; --idx) michael@0: { michael@0: if (pathBuffer[begin+idx] != L'.') michael@0: break; michael@0: } michael@0: michael@0: // this is true if the loop above didn't break michael@0: // and all characters in this segment are dots. michael@0: if (idx < 2) michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: // add the current component to the path, including the preceding backslash michael@0: mWorkingPath.Append(pathBuffer + begin - 1, len + 1); michael@0: } michael@0: michael@0: // kill trailing dots and spaces. michael@0: int32_t filePathLen = mWorkingPath.Length() - 1; michael@0: while(filePathLen > 0 && (mWorkingPath[filePathLen] == L' ' || michael@0: mWorkingPath[filePathLen] == L'.')) michael@0: { michael@0: mWorkingPath.Truncate(filePathLen--); michael@0: } michael@0: michael@0: MakeDirty(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetLeafName(nsAString &aLeafName) michael@0: { michael@0: aLeafName.Truncate(); michael@0: michael@0: if (mWorkingPath.IsEmpty()) michael@0: return NS_ERROR_FILE_UNRECOGNIZED_PATH; michael@0: michael@0: int32_t offset = mWorkingPath.RFindChar(L'\\'); michael@0: michael@0: // if the working path is just a node without any lashes. michael@0: if (offset == kNotFound) michael@0: aLeafName = mWorkingPath; michael@0: else michael@0: aLeafName = Substring(mWorkingPath, offset + 1); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::SetLeafName(const nsAString &aLeafName) michael@0: { michael@0: MakeDirty(); michael@0: michael@0: if (mWorkingPath.IsEmpty()) michael@0: return NS_ERROR_FILE_UNRECOGNIZED_PATH; michael@0: michael@0: // cannot use nsCString::RFindChar() due to 0x5c problem michael@0: int32_t offset = mWorkingPath.RFindChar(L'\\'); michael@0: if (offset) michael@0: { michael@0: mWorkingPath.Truncate(offset+1); michael@0: } michael@0: mWorkingPath.Append(aLeafName); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetPath(nsAString &_retval) michael@0: { michael@0: _retval = mWorkingPath; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetCanonicalPath(nsAString &aResult) michael@0: { michael@0: EnsureShortPath(); michael@0: aResult.Assign(mShortWorkingPath); michael@0: return NS_OK; michael@0: } michael@0: michael@0: typedef struct { michael@0: WORD wLanguage; michael@0: WORD wCodePage; michael@0: } LANGANDCODEPAGE; michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetVersionInfoField(const char* aField, nsAString& _retval) michael@0: { michael@0: nsresult rv = ResolveAndStat(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = NS_ERROR_FAILURE; michael@0: michael@0: const WCHAR *path = mFollowSymlinks ? mResolvedPath.get() : mWorkingPath.get(); michael@0: michael@0: DWORD dummy; michael@0: DWORD size = ::GetFileVersionInfoSizeW(path, &dummy); michael@0: if (!size) michael@0: return rv; michael@0: michael@0: void* ver = calloc(size, 1); michael@0: if (!ver) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: if (::GetFileVersionInfoW(path, 0, size, ver)) michael@0: { michael@0: LANGANDCODEPAGE* translate = nullptr; michael@0: UINT pageCount; michael@0: BOOL queryResult = ::VerQueryValueW(ver, L"\\VarFileInfo\\Translation", michael@0: (void**)&translate, &pageCount); michael@0: if (queryResult && translate) michael@0: { michael@0: for (int32_t i = 0; i < 2; ++i) michael@0: { michael@0: wchar_t subBlock[MAX_PATH]; michael@0: _snwprintf(subBlock, MAX_PATH, michael@0: L"\\StringFileInfo\\%04x%04x\\%s", michael@0: (i == 0 ? translate[0].wLanguage michael@0: : ::GetUserDefaultLangID()), michael@0: translate[0].wCodePage, michael@0: NS_ConvertASCIItoUTF16( michael@0: nsDependentCString(aField)).get()); michael@0: subBlock[MAX_PATH - 1] = 0; michael@0: LPVOID value = nullptr; michael@0: UINT size; michael@0: queryResult = ::VerQueryValueW(ver, subBlock, &value, &size); michael@0: if (queryResult && value) michael@0: { michael@0: _retval.Assign(static_cast(value)); michael@0: if (!_retval.IsEmpty()) michael@0: { michael@0: rv = NS_OK; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: free(ver); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::SetShortcut(nsIFile* targetFile, michael@0: nsIFile* workingDir, michael@0: const char16_t* args, michael@0: const char16_t* description, michael@0: nsIFile* iconFile, michael@0: int32_t iconIndex) michael@0: { michael@0: bool exists; michael@0: nsresult rv = this->Exists(&exists); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: const WCHAR* targetFilePath = nullptr; michael@0: const WCHAR* workingDirPath = nullptr; michael@0: const WCHAR* iconFilePath = nullptr; michael@0: michael@0: nsAutoString targetFilePathAuto; michael@0: if (targetFile) { michael@0: rv = targetFile->GetPath(targetFilePathAuto); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: targetFilePath = targetFilePathAuto.get(); michael@0: } michael@0: michael@0: nsAutoString workingDirPathAuto; michael@0: if (workingDir) { michael@0: rv = workingDir->GetPath(workingDirPathAuto); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: workingDirPath = workingDirPathAuto.get(); michael@0: } michael@0: michael@0: nsAutoString iconPathAuto; michael@0: if (iconFile) { michael@0: rv = iconFile->GetPath(iconPathAuto); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: iconFilePath = iconPathAuto.get(); michael@0: } michael@0: michael@0: rv = gResolver->SetShortcut(exists, michael@0: mWorkingPath.get(), michael@0: targetFilePath, michael@0: workingDirPath, michael@0: char16ptr_t(args), michael@0: char16ptr_t(description), michael@0: iconFilePath, michael@0: iconFilePath? iconIndex : 0); michael@0: if (targetFilePath && NS_SUCCEEDED(rv)) { michael@0: MakeDirty(); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: /** michael@0: * Determines if the drive type for the specified file is rmeote or local. michael@0: * michael@0: * @param path The path of the file to check michael@0: * @param remote Out parameter, on function success holds true if the specified michael@0: * file path is remote, or false if the file path is local. michael@0: * @return true on success. The return value implies absolutely nothing about michael@0: * wether the file is local or remote. michael@0: */ michael@0: static bool michael@0: IsRemoteFilePath(LPCWSTR path, bool &remote) michael@0: { michael@0: // Obtain the parent directory path and make sure it ends with michael@0: // a trailing backslash. michael@0: WCHAR dirPath[MAX_PATH + 1] = { 0 }; michael@0: wcsncpy(dirPath, path, MAX_PATH); michael@0: if (!PathRemoveFileSpecW(dirPath)) { michael@0: return false; michael@0: } michael@0: size_t len = wcslen(dirPath); michael@0: // In case the dirPath holds exaclty MAX_PATH and remains unchanged, we michael@0: // recheck the required length here since we need to terminate it with michael@0: // a backslash. michael@0: if (len >= MAX_PATH) { michael@0: return false; michael@0: } michael@0: michael@0: dirPath[len] = L'\\'; michael@0: dirPath[len + 1] = L'\0'; michael@0: UINT driveType = GetDriveTypeW(dirPath); michael@0: remote = driveType == DRIVE_REMOTE; michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: nsLocalFile::CopySingleFile(nsIFile *sourceFile, nsIFile *destParent, michael@0: const nsAString &newName, uint32_t options) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: nsAutoString filePath; michael@0: michael@0: bool move = options & (Move | Rename); michael@0: michael@0: // get the path that we are going to copy to. michael@0: // Since windows does not know how to auto michael@0: // resolve shortcuts, we must work with the michael@0: // target. michael@0: nsAutoString destPath; michael@0: destParent->GetTarget(destPath); michael@0: michael@0: destPath.Append('\\'); michael@0: michael@0: if (newName.IsEmpty()) michael@0: { michael@0: nsAutoString aFileName; michael@0: sourceFile->GetLeafName(aFileName); michael@0: destPath.Append(aFileName); michael@0: } michael@0: else michael@0: { michael@0: destPath.Append(newName); michael@0: } michael@0: michael@0: michael@0: if (options & FollowSymlinks) michael@0: { michael@0: rv = sourceFile->GetTarget(filePath); michael@0: if (filePath.IsEmpty()) michael@0: rv = sourceFile->GetPath(filePath); michael@0: } michael@0: else michael@0: { michael@0: rv = sourceFile->GetPath(filePath); michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // Pass the flag COPY_FILE_NO_BUFFERING to CopyFileEx as we may be copying michael@0: // to a SMBV2 remote drive. Without this parameter subsequent append mode michael@0: // file writes can cause the resultant file to become corrupt. We only need to do michael@0: // this if the major version of Windows is > 5(Only Windows Vista and above michael@0: // can support SMBV2). With a 7200RPM hard drive: michael@0: // Copying a 1KB file with COPY_FILE_NO_BUFFERING takes about 30-60ms. michael@0: // Copying a 1KB file without COPY_FILE_NO_BUFFERING takes < 1ms. michael@0: // So we only use COPY_FILE_NO_BUFFERING when we have a remote drive. michael@0: int copyOK; michael@0: DWORD dwCopyFlags = COPY_FILE_ALLOW_DECRYPTED_DESTINATION; michael@0: if (IsVistaOrLater()) { michael@0: bool path1Remote, path2Remote; michael@0: if (!IsRemoteFilePath(filePath.get(), path1Remote) || michael@0: !IsRemoteFilePath(destPath.get(), path2Remote) || michael@0: path1Remote || path2Remote) { michael@0: dwCopyFlags |= COPY_FILE_NO_BUFFERING; michael@0: } michael@0: } michael@0: michael@0: if (!move) michael@0: { michael@0: copyOK = ::CopyFileExW(filePath.get(), destPath.get(), nullptr, michael@0: nullptr, nullptr, dwCopyFlags); michael@0: } michael@0: else michael@0: { michael@0: copyOK = ::MoveFileExW(filePath.get(), destPath.get(), MOVEFILE_REPLACE_EXISTING); michael@0: michael@0: // Check if copying the source file to a different volume, michael@0: // as this could be an SMBV2 mapped drive. michael@0: if (!copyOK && GetLastError() == ERROR_NOT_SAME_DEVICE) michael@0: { michael@0: if (options & Rename) { michael@0: return NS_ERROR_FILE_ACCESS_DENIED; michael@0: } michael@0: copyOK = CopyFileExW(filePath.get(), destPath.get(), nullptr, michael@0: nullptr, nullptr, dwCopyFlags); michael@0: michael@0: if (copyOK) michael@0: DeleteFileW(filePath.get()); michael@0: } michael@0: } michael@0: michael@0: if (!copyOK) // CopyFileEx and MoveFileEx return zero at failure. michael@0: rv = ConvertWinError(GetLastError()); michael@0: else if (move && !(options & SkipNtfsAclReset)) michael@0: { michael@0: // Set security permissions to inherit from parent. michael@0: // Note: propagates to all children: slow for big file trees michael@0: PACL pOldDACL = nullptr; michael@0: PSECURITY_DESCRIPTOR pSD = nullptr; michael@0: ::GetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT, michael@0: DACL_SECURITY_INFORMATION, michael@0: nullptr, nullptr, &pOldDACL, nullptr, &pSD); michael@0: if (pOldDACL) michael@0: ::SetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT, michael@0: DACL_SECURITY_INFORMATION | michael@0: UNPROTECTED_DACL_SECURITY_INFORMATION, michael@0: nullptr, nullptr, pOldDACL, nullptr); michael@0: if (pSD) michael@0: LocalFree((HLOCAL)pSD); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsLocalFile::CopyMove(nsIFile *aParentDir, const nsAString &newName, uint32_t options) michael@0: { michael@0: bool move = options & (Move | Rename); michael@0: bool followSymlinks = options & FollowSymlinks; michael@0: michael@0: nsCOMPtr newParentDir = aParentDir; michael@0: // check to see if this exists, otherwise return an error. michael@0: // we will check this by resolving. If the user wants us michael@0: // to follow links, then we are talking about the target, michael@0: // hence we can use the |FollowSymlinks| option. michael@0: nsresult rv = ResolveAndStat(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (!newParentDir) michael@0: { michael@0: // no parent was specified. We must rename. michael@0: if (newName.IsEmpty()) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: rv = GetParent(getter_AddRefs(newParentDir)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: michael@0: if (!newParentDir) michael@0: return NS_ERROR_FILE_DESTINATION_NOT_DIR; michael@0: michael@0: // make sure it exists and is a directory. Create it if not there. michael@0: bool exists; michael@0: newParentDir->Exists(&exists); michael@0: if (!exists) michael@0: { michael@0: rv = newParentDir->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: else michael@0: { michael@0: bool isDir; michael@0: newParentDir->IsDirectory(&isDir); michael@0: if (!isDir) michael@0: { michael@0: if (followSymlinks) michael@0: { michael@0: bool isLink; michael@0: newParentDir->IsSymlink(&isLink); michael@0: if (isLink) michael@0: { michael@0: nsAutoString target; michael@0: newParentDir->GetTarget(target); michael@0: michael@0: nsCOMPtr realDest = new nsLocalFile(); michael@0: if (realDest == nullptr) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: rv = realDest->InitWithPath(target); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: return CopyMove(realDest, newName, options); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: return NS_ERROR_FILE_DESTINATION_NOT_DIR; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Try different ways to move/copy files/directories michael@0: bool done = false; michael@0: bool isDir; michael@0: IsDirectory(&isDir); michael@0: bool isSymlink; michael@0: IsSymlink(&isSymlink); michael@0: michael@0: // Try to move the file or directory, or try to copy a single file (or non-followed symlink) michael@0: if (move || !isDir || (isSymlink && !followSymlinks)) michael@0: { michael@0: // Copy/Move single file, or move a directory michael@0: if (!aParentDir) { michael@0: options |= SkipNtfsAclReset; michael@0: } michael@0: rv = CopySingleFile(this, newParentDir, newName, options); michael@0: done = NS_SUCCEEDED(rv); michael@0: // If we are moving a directory and that fails, fallback on directory michael@0: // enumeration. See bug 231300 for details. michael@0: if (!done && !(move && isDir)) michael@0: return rv; michael@0: } michael@0: michael@0: // Not able to copy or move directly, so enumerate it michael@0: if (!done) michael@0: { michael@0: // create a new target destination in the new parentDir; michael@0: nsCOMPtr target; michael@0: rv = newParentDir->Clone(getter_AddRefs(target)); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsAutoString allocatedNewName; michael@0: if (newName.IsEmpty()) michael@0: { michael@0: bool isLink; michael@0: IsSymlink(&isLink); michael@0: if (isLink) michael@0: { michael@0: nsAutoString temp; michael@0: GetTarget(temp); michael@0: int32_t offset = temp.RFindChar(L'\\'); michael@0: if (offset == kNotFound) michael@0: allocatedNewName = temp; michael@0: else michael@0: allocatedNewName = Substring(temp, offset + 1); michael@0: } michael@0: else michael@0: { michael@0: GetLeafName(allocatedNewName);// this should be the leaf name of the michael@0: } michael@0: } michael@0: else michael@0: { michael@0: allocatedNewName = newName; michael@0: } michael@0: michael@0: rv = target->Append(allocatedNewName); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: allocatedNewName.Truncate(); michael@0: michael@0: // check if the destination directory already exists michael@0: target->Exists(&exists); michael@0: if (!exists) michael@0: { michael@0: // if the destination directory cannot be created, return an error michael@0: rv = target->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: else michael@0: { michael@0: // check if the destination directory is writable and empty michael@0: bool isWritable; michael@0: michael@0: target->IsWritable(&isWritable); michael@0: if (!isWritable) michael@0: return NS_ERROR_FILE_ACCESS_DENIED; michael@0: michael@0: nsCOMPtr targetIterator; michael@0: rv = target->GetDirectoryEntries(getter_AddRefs(targetIterator)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: bool more; michael@0: targetIterator->HasMoreElements(&more); michael@0: // return error if target directory is not empty michael@0: if (more) michael@0: return NS_ERROR_FILE_DIR_NOT_EMPTY; michael@0: } michael@0: michael@0: nsDirEnumerator dirEnum; michael@0: michael@0: rv = dirEnum.Init(this); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("dirEnum initialization failed"); michael@0: return rv; michael@0: } michael@0: michael@0: bool more = false; michael@0: while (NS_SUCCEEDED(dirEnum.HasMoreElements(&more)) && more) michael@0: { michael@0: nsCOMPtr item; michael@0: nsCOMPtr file; michael@0: dirEnum.GetNext(getter_AddRefs(item)); michael@0: file = do_QueryInterface(item); michael@0: if (file) michael@0: { michael@0: bool isDir, isLink; michael@0: michael@0: file->IsDirectory(&isDir); michael@0: file->IsSymlink(&isLink); michael@0: michael@0: if (move) michael@0: { michael@0: if (followSymlinks) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: rv = file->MoveTo(target, EmptyString()); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: else michael@0: { michael@0: if (followSymlinks) michael@0: rv = file->CopyToFollowingLinks(target, EmptyString()); michael@0: else michael@0: rv = file->CopyTo(target, EmptyString()); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: } michael@0: } michael@0: // we've finished moving all the children of this directory michael@0: // in the new directory. so now delete the directory michael@0: // note, we don't need to do a recursive delete. michael@0: // MoveTo() is recursive. At this point, michael@0: // we've already moved the children of the current folder michael@0: // to the new location. nothing should be left in the folder. michael@0: if (move) michael@0: { michael@0: rv = Remove(false /* recursive */); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: michael@0: // If we moved, we want to adjust this. michael@0: if (move) michael@0: { michael@0: MakeDirty(); michael@0: michael@0: nsAutoString newParentPath; michael@0: newParentDir->GetPath(newParentPath); michael@0: michael@0: if (newParentPath.IsEmpty()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (newName.IsEmpty()) michael@0: { michael@0: nsAutoString aFileName; michael@0: GetLeafName(aFileName); michael@0: michael@0: InitWithPath(newParentPath); michael@0: Append(aFileName); michael@0: } michael@0: else michael@0: { michael@0: InitWithPath(newParentPath); michael@0: Append(newName); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::CopyTo(nsIFile *newParentDir, const nsAString &newName) michael@0: { michael@0: return CopyMove(newParentDir, newName, 0); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::CopyToFollowingLinks(nsIFile *newParentDir, const nsAString &newName) michael@0: { michael@0: return CopyMove(newParentDir, newName, FollowSymlinks); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::MoveTo(nsIFile *newParentDir, const nsAString &newName) michael@0: { michael@0: return CopyMove(newParentDir, newName, Move); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::RenameTo(nsIFile *newParentDir, const nsAString & newName) michael@0: { michael@0: nsCOMPtr targetParentDir = newParentDir; michael@0: // check to see if this exists, otherwise return an error. michael@0: // we will check this by resolving. If the user wants us michael@0: // to follow links, then we are talking about the target, michael@0: // hence we can use the |followSymlinks| parameter. michael@0: nsresult rv = ResolveAndStat(); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: if (!targetParentDir) { michael@0: // no parent was specified. We must rename. michael@0: if (newName.IsEmpty()) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: rv = GetParent(getter_AddRefs(targetParentDir)); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: if (!targetParentDir) { michael@0: return NS_ERROR_FILE_DESTINATION_NOT_DIR; michael@0: } michael@0: michael@0: // make sure it exists and is a directory. Create it if not there. michael@0: bool exists; michael@0: targetParentDir->Exists(&exists); michael@0: if (!exists) { michael@0: rv = targetParentDir->Create(DIRECTORY_TYPE, 0644); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } else { michael@0: bool isDir; michael@0: targetParentDir->IsDirectory(&isDir); michael@0: if (!isDir) { michael@0: return NS_ERROR_FILE_DESTINATION_NOT_DIR; michael@0: } michael@0: } michael@0: michael@0: uint32_t options = Rename; michael@0: if (!newParentDir) { michael@0: options |= SkipNtfsAclReset; michael@0: } michael@0: // Move single file, or move a directory michael@0: return CopySingleFile(this, targetParentDir, newName, options); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::Load(PRLibrary * *_retval) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: bool isFile; michael@0: nsresult rv = IsFile(&isFile); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (! isFile) michael@0: return NS_ERROR_FILE_IS_DIRECTORY; michael@0: michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: nsTraceRefcnt::SetActivityIsLegal(false); michael@0: #endif michael@0: michael@0: PRLibSpec libSpec; michael@0: libSpec.value.pathname_u = mResolvedPath.get(); michael@0: libSpec.type = PR_LibSpec_PathnameU; michael@0: *_retval = PR_LoadLibraryWithFlags(libSpec, 0); michael@0: michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: nsTraceRefcnt::SetActivityIsLegal(true); michael@0: #endif michael@0: michael@0: if (*_retval) michael@0: return NS_OK; michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::Remove(bool recursive) michael@0: { michael@0: // NOTE: michael@0: // michael@0: // if the working path points to a shortcut, then we will only michael@0: // delete the shortcut itself. even if the shortcut points to michael@0: // a directory, we will not recurse into that directory or michael@0: // delete that directory itself. likewise, if the shortcut michael@0: // points to a normal file, we will not delete the real file. michael@0: // this is done to be consistent with the other platforms that michael@0: // behave this way. we do this even if the followLinks attribute michael@0: // is set to true. this helps protect against misuse that could michael@0: // lead to security bugs (e.g., bug 210588). michael@0: // michael@0: // Since shortcut files are no longer permitted to be used as unix-like michael@0: // symlinks interspersed in the path (e.g. "c:/file.lnk/foo/bar.txt") michael@0: // this processing is a lot simpler. Even if the shortcut file is michael@0: // pointing to a directory, only the mWorkingPath value is used and so michael@0: // only the shortcut file will be deleted. michael@0: michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: bool isDir, isLink; michael@0: nsresult rv; michael@0: michael@0: isDir = false; michael@0: rv = IsSymlink(&isLink); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // only check to see if we have a directory if it isn't a link michael@0: if (!isLink) michael@0: { michael@0: rv = IsDirectory(&isDir); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: michael@0: if (isDir) michael@0: { michael@0: if (recursive) michael@0: { michael@0: nsDirEnumerator dirEnum; michael@0: michael@0: rv = dirEnum.Init(this); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: bool more = false; michael@0: while (NS_SUCCEEDED(dirEnum.HasMoreElements(&more)) && more) michael@0: { michael@0: nsCOMPtr item; michael@0: dirEnum.GetNext(getter_AddRefs(item)); michael@0: nsCOMPtr file = do_QueryInterface(item); michael@0: if (file) michael@0: file->Remove(recursive); michael@0: } michael@0: } michael@0: if (RemoveDirectoryW(mWorkingPath.get()) == 0) michael@0: return ConvertWinError(GetLastError()); michael@0: } michael@0: else michael@0: { michael@0: if (DeleteFileW(mWorkingPath.get()) == 0) michael@0: return ConvertWinError(GetLastError()); michael@0: } michael@0: michael@0: MakeDirty(); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetLastModifiedTime(PRTime *aLastModifiedTime) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: if (NS_WARN_IF(!aLastModifiedTime)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: // get the modified time of the target as determined by mFollowSymlinks michael@0: // If true, then this will be for the target of the shortcut file, michael@0: // otherwise it will be for the shortcut file itself (i.e. the same michael@0: // results as GetLastModifiedTimeOfLink) michael@0: michael@0: nsresult rv = ResolveAndStat(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // microseconds -> milliseconds michael@0: *aLastModifiedTime = mFileInfo64.modifyTime / PR_USEC_PER_MSEC; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetLastModifiedTimeOfLink(PRTime *aLastModifiedTime) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: if (NS_WARN_IF(!aLastModifiedTime)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: // The caller is assumed to have already called IsSymlink michael@0: // and to have found that this file is a link. michael@0: michael@0: PRFileInfo64 info; michael@0: nsresult rv = GetFileInfo(mWorkingPath, &info); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // microseconds -> milliseconds michael@0: *aLastModifiedTime = info.modifyTime / PR_USEC_PER_MSEC; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::SetLastModifiedTime(PRTime aLastModifiedTime) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: nsresult rv = ResolveAndStat(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // set the modified time of the target as determined by mFollowSymlinks michael@0: // If true, then this will be for the target of the shortcut file, michael@0: // otherwise it will be for the shortcut file itself (i.e. the same michael@0: // results as SetLastModifiedTimeOfLink) michael@0: michael@0: rv = SetModDate(aLastModifiedTime, mResolvedPath.get()); michael@0: if (NS_SUCCEEDED(rv)) michael@0: MakeDirty(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModifiedTime) michael@0: { michael@0: // The caller is assumed to have already called IsSymlink michael@0: // and to have found that this file is a link. michael@0: michael@0: nsresult rv = SetModDate(aLastModifiedTime, mWorkingPath.get()); michael@0: if (NS_SUCCEEDED(rv)) michael@0: MakeDirty(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsLocalFile::SetModDate(PRTime aLastModifiedTime, const wchar_t *filePath) michael@0: { michael@0: // The FILE_FLAG_BACKUP_SEMANTICS is required in order to change the michael@0: // modification time for directories. michael@0: HANDLE file = ::CreateFileW(filePath, // pointer to name of the file michael@0: GENERIC_WRITE, // access (write) mode michael@0: 0, // share mode michael@0: nullptr, // pointer to security attributes michael@0: OPEN_EXISTING, // how to create michael@0: FILE_FLAG_BACKUP_SEMANTICS, // file attributes michael@0: nullptr); michael@0: michael@0: if (file == INVALID_HANDLE_VALUE) michael@0: { michael@0: return ConvertWinError(GetLastError()); michael@0: } michael@0: michael@0: FILETIME ft; michael@0: SYSTEMTIME st; michael@0: PRExplodedTime pret; michael@0: michael@0: // PR_ExplodeTime expects usecs... michael@0: PR_ExplodeTime(aLastModifiedTime * PR_USEC_PER_MSEC, PR_GMTParameters, &pret); michael@0: st.wYear = pret.tm_year; michael@0: st.wMonth = pret.tm_month + 1; // Convert start offset -- Win32: Jan=1; NSPR: Jan=0 michael@0: st.wDayOfWeek = pret.tm_wday; michael@0: st.wDay = pret.tm_mday; michael@0: st.wHour = pret.tm_hour; michael@0: st.wMinute = pret.tm_min; michael@0: st.wSecond = pret.tm_sec; michael@0: st.wMilliseconds = pret.tm_usec/1000; michael@0: michael@0: nsresult rv = NS_OK; michael@0: // if at least one of these fails... michael@0: if (!(SystemTimeToFileTime(&st, &ft) != 0 && michael@0: SetFileTime(file, nullptr, &ft, &ft) != 0)) michael@0: { michael@0: rv = ConvertWinError(GetLastError()); michael@0: } michael@0: michael@0: CloseHandle(file); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetPermissions(uint32_t *aPermissions) michael@0: { michael@0: if (NS_WARN_IF(!aPermissions)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: // get the permissions of the target as determined by mFollowSymlinks michael@0: // If true, then this will be for the target of the shortcut file, michael@0: // otherwise it will be for the shortcut file itself (i.e. the same michael@0: // results as GetPermissionsOfLink) michael@0: nsresult rv = ResolveAndStat(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: bool isWritable, isExecutable; michael@0: IsWritable(&isWritable); michael@0: IsExecutable(&isExecutable); michael@0: michael@0: *aPermissions = PR_IRUSR|PR_IRGRP|PR_IROTH; // all read michael@0: if (isWritable) michael@0: *aPermissions |= PR_IWUSR|PR_IWGRP|PR_IWOTH; // all write michael@0: if (isExecutable) michael@0: *aPermissions |= PR_IXUSR|PR_IXGRP|PR_IXOTH; // all execute michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetPermissionsOfLink(uint32_t *aPermissions) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: if (NS_WARN_IF(!aPermissions)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: // The caller is assumed to have already called IsSymlink michael@0: // and to have found that this file is a link. It is not michael@0: // possible for a link file to be executable. michael@0: michael@0: DWORD word = ::GetFileAttributesW(mWorkingPath.get()); michael@0: if (word == INVALID_FILE_ATTRIBUTES) michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: michael@0: bool isWritable = !(word & FILE_ATTRIBUTE_READONLY); michael@0: *aPermissions = PR_IRUSR|PR_IRGRP|PR_IROTH; // all read michael@0: if (isWritable) michael@0: *aPermissions |= PR_IWUSR|PR_IWGRP|PR_IWOTH; // all write michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::SetPermissions(uint32_t aPermissions) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: // set the permissions of the target as determined by mFollowSymlinks michael@0: // If true, then this will be for the target of the shortcut file, michael@0: // otherwise it will be for the shortcut file itself (i.e. the same michael@0: // results as SetPermissionsOfLink) michael@0: nsresult rv = ResolveAndStat(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // windows only knows about the following permissions michael@0: int mode = 0; michael@0: if (aPermissions & (PR_IRUSR|PR_IRGRP|PR_IROTH)) // any read michael@0: mode |= _S_IREAD; michael@0: if (aPermissions & (PR_IWUSR|PR_IWGRP|PR_IWOTH)) // any write michael@0: mode |= _S_IWRITE; michael@0: michael@0: if (_wchmod(mResolvedPath.get(), mode) == -1) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions) michael@0: { michael@0: // The caller is assumed to have already called IsSymlink michael@0: // and to have found that this file is a link. michael@0: michael@0: // windows only knows about the following permissions michael@0: int mode = 0; michael@0: if (aPermissions & (PR_IRUSR|PR_IRGRP|PR_IROTH)) // any read michael@0: mode |= _S_IREAD; michael@0: if (aPermissions & (PR_IWUSR|PR_IWGRP|PR_IWOTH)) // any write michael@0: mode |= _S_IWRITE; michael@0: michael@0: if (_wchmod(mWorkingPath.get(), mode) == -1) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetFileSize(int64_t *aFileSize) michael@0: { michael@0: if (NS_WARN_IF(!aFileSize)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: nsresult rv = ResolveAndStat(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: *aFileSize = mFileInfo64.size; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetFileSizeOfLink(int64_t *aFileSize) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: if (NS_WARN_IF(!aFileSize)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: // The caller is assumed to have already called IsSymlink michael@0: // and to have found that this file is a link. michael@0: michael@0: PRFileInfo64 info; michael@0: if (NS_FAILED(GetFileInfo(mWorkingPath, &info))) michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: michael@0: *aFileSize = info.size; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::SetFileSize(int64_t aFileSize) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: nsresult rv = ResolveAndStat(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: HANDLE hFile = ::CreateFileW(mResolvedPath.get(),// pointer to name of the file michael@0: GENERIC_WRITE, // access (write) mode michael@0: FILE_SHARE_READ, // share mode michael@0: nullptr, // pointer to security attributes michael@0: OPEN_EXISTING, // how to create michael@0: FILE_ATTRIBUTE_NORMAL, // file attributes michael@0: nullptr); michael@0: if (hFile == INVALID_HANDLE_VALUE) michael@0: { michael@0: return ConvertWinError(GetLastError()); michael@0: } michael@0: michael@0: // seek the file pointer to the new, desired end of file michael@0: // and then truncate the file at that position michael@0: rv = NS_ERROR_FAILURE; michael@0: aFileSize = MyFileSeek64(hFile, aFileSize, FILE_BEGIN); michael@0: if (aFileSize != -1 && SetEndOfFile(hFile)) michael@0: { michael@0: MakeDirty(); michael@0: rv = NS_OK; michael@0: } michael@0: michael@0: CloseHandle(hFile); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetDiskSpaceAvailable(int64_t *aDiskSpaceAvailable) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: if (NS_WARN_IF(!aDiskSpaceAvailable)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: ResolveAndStat(); michael@0: michael@0: if (mFileInfo64.type == PR_FILE_FILE) { michael@0: // Since GetDiskFreeSpaceExW works only on directories, use the parent. michael@0: nsCOMPtr parent; michael@0: if (NS_SUCCEEDED(GetParent(getter_AddRefs(parent))) && parent) { michael@0: return parent->GetDiskSpaceAvailable(aDiskSpaceAvailable); michael@0: } michael@0: } michael@0: michael@0: ULARGE_INTEGER liFreeBytesAvailableToCaller, liTotalNumberOfBytes; michael@0: if (::GetDiskFreeSpaceExW(mResolvedPath.get(), &liFreeBytesAvailableToCaller, michael@0: &liTotalNumberOfBytes, nullptr)) michael@0: { michael@0: *aDiskSpaceAvailable = liFreeBytesAvailableToCaller.QuadPart; michael@0: return NS_OK; michael@0: } michael@0: *aDiskSpaceAvailable = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetParent(nsIFile * *aParent) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: if (NS_WARN_IF(!aParent)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: // A two-character path must be a drive such as C:, so it has no parent michael@0: if (mWorkingPath.Length() == 2) { michael@0: *aParent = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t offset = mWorkingPath.RFindChar(char16_t('\\')); michael@0: // adding this offset check that was removed in bug 241708 fixes mail michael@0: // directories that aren't relative to/underneath the profile dir. michael@0: // e.g., on a different drive. Before you remove them, please make michael@0: // sure local mail directories that aren't underneath the profile dir work. michael@0: if (offset == kNotFound) michael@0: return NS_ERROR_FILE_UNRECOGNIZED_PATH; michael@0: michael@0: // A path of the form \\NAME is a top-level path and has no parent michael@0: if (offset == 1 && mWorkingPath[0] == L'\\') { michael@0: *aParent = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoString parentPath(mWorkingPath); michael@0: michael@0: if (offset > 0) michael@0: parentPath.Truncate(offset); michael@0: else michael@0: parentPath.AssignLiteral("\\\\."); michael@0: michael@0: nsCOMPtr localFile; michael@0: nsresult rv = NS_NewLocalFile(parentPath, mFollowSymlinks, getter_AddRefs(localFile)); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: localFile.forget(aParent); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::Exists(bool *_retval) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: if (NS_WARN_IF(!_retval)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: *_retval = false; michael@0: michael@0: MakeDirty(); michael@0: nsresult rv = ResolveAndStat(); michael@0: *_retval = NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_IS_LOCKED; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::IsWritable(bool *aIsWritable) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: // The read-only attribute on a FAT directory only means that it can't michael@0: // be deleted. It is still possible to modify the contents of the directory. michael@0: nsresult rv = IsDirectory(aIsWritable); michael@0: if (rv == NS_ERROR_FILE_ACCESS_DENIED) { michael@0: *aIsWritable = true; michael@0: return NS_OK; michael@0: } else if (rv == NS_ERROR_FILE_IS_LOCKED) { michael@0: // If the file is normally allowed write access michael@0: // we should still return that the file is writable. michael@0: } else if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: if (*aIsWritable) michael@0: return NS_OK; michael@0: michael@0: // writable if the file doesn't have the readonly attribute michael@0: rv = HasFileAttribute(FILE_ATTRIBUTE_READONLY, aIsWritable); michael@0: if (rv == NS_ERROR_FILE_ACCESS_DENIED) { michael@0: *aIsWritable = false; michael@0: return NS_OK; michael@0: } else if (rv == NS_ERROR_FILE_IS_LOCKED) { michael@0: // If the file is normally allowed write access michael@0: // we should still return that the file is writable. michael@0: } else if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: *aIsWritable = !*aIsWritable; michael@0: michael@0: // If the read only attribute is not set, check to make sure michael@0: // we can open the file with write access. michael@0: if (*aIsWritable) { michael@0: PRFileDesc* file; michael@0: rv = OpenFile(mResolvedPath, PR_WRONLY, 0, &file); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: PR_Close(file); michael@0: } else if (rv == NS_ERROR_FILE_ACCESS_DENIED) { michael@0: *aIsWritable = false; michael@0: } else if (rv == NS_ERROR_FILE_IS_LOCKED) { michael@0: // If it is locked and read only we would have michael@0: // gotten access denied michael@0: *aIsWritable = true; michael@0: } else { michael@0: return rv; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::IsReadable(bool *_retval) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: if (NS_WARN_IF(!_retval)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: *_retval = false; michael@0: michael@0: nsresult rv = ResolveAndStat(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: *_retval = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::IsExecutable(bool *_retval) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: if (NS_WARN_IF(!_retval)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: *_retval = false; michael@0: michael@0: nsresult rv; michael@0: michael@0: // only files can be executables michael@0: bool isFile; michael@0: rv = IsFile(&isFile); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: if (!isFile) michael@0: return NS_OK; michael@0: michael@0: //TODO: shouldn't we be checking mFollowSymlinks here? michael@0: bool symLink; michael@0: rv = IsSymlink(&symLink); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsAutoString path; michael@0: if (symLink) michael@0: GetTarget(path); michael@0: else michael@0: GetPath(path); michael@0: michael@0: // kill trailing dots and spaces. michael@0: int32_t filePathLen = path.Length() - 1; michael@0: while(filePathLen > 0 && (path[filePathLen] == L' ' || path[filePathLen] == L'.')) michael@0: { michael@0: path.Truncate(filePathLen--); michael@0: } michael@0: michael@0: // Get extension. michael@0: int32_t dotIdx = path.RFindChar(char16_t('.')); michael@0: if ( dotIdx != kNotFound ) { michael@0: // Convert extension to lower case. michael@0: char16_t *p = path.BeginWriting(); michael@0: for( p+= dotIdx + 1; *p; p++ ) michael@0: *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0; michael@0: michael@0: // Search for any of the set of executable extensions. michael@0: static const char * const executableExts[] = { michael@0: "ad", michael@0: "ade", // access project extension michael@0: "adp", michael@0: "air", // Adobe AIR installer michael@0: "app", // executable application michael@0: "application", // from bug 348763 michael@0: "asp", michael@0: "bas", michael@0: "bat", michael@0: "chm", michael@0: "cmd", michael@0: "com", michael@0: "cpl", michael@0: "crt", michael@0: "exe", michael@0: "fxp", // FoxPro compiled app michael@0: "hlp", michael@0: "hta", michael@0: "inf", michael@0: "ins", michael@0: "isp", michael@0: "jar", // java application bundle michael@0: "js", michael@0: "jse", michael@0: "lnk", michael@0: "mad", // Access Module Shortcut michael@0: "maf", // Access michael@0: "mag", // Access Diagram Shortcut michael@0: "mam", // Access Macro Shortcut michael@0: "maq", // Access Query Shortcut michael@0: "mar", // Access Report Shortcut michael@0: "mas", // Access Stored Procedure michael@0: "mat", // Access Table Shortcut michael@0: "mau", // Media Attachment Unit michael@0: "mav", // Access View Shortcut michael@0: "maw", // Access Data Access Page michael@0: "mda", // Access Add-in, MDA Access 2 Workgroup michael@0: "mdb", michael@0: "mde", michael@0: "mdt", // Access Add-in Data michael@0: "mdw", // Access Workgroup Information michael@0: "mdz", // Access Wizard Template michael@0: "msc", michael@0: "msh", // Microsoft Shell michael@0: "mshxml", // Microsoft Shell michael@0: "msi", michael@0: "msp", michael@0: "mst", michael@0: "ops", // Office Profile Settings michael@0: "pcd", michael@0: "pif", michael@0: "plg", // Developer Studio Build Log michael@0: "prf", // windows system file michael@0: "prg", michael@0: "pst", michael@0: "reg", michael@0: "scf", // Windows explorer command michael@0: "scr", michael@0: "sct", michael@0: "shb", michael@0: "shs", michael@0: "url", michael@0: "vb", michael@0: "vbe", michael@0: "vbs", michael@0: "vsd", michael@0: "vsmacros", // Visual Studio .NET Binary-based Macro Project michael@0: "vss", michael@0: "vst", michael@0: "vsw", michael@0: "ws", michael@0: "wsc", michael@0: "wsf", michael@0: "wsh"}; michael@0: nsDependentSubstring ext = Substring(path, dotIdx + 1); michael@0: for ( size_t i = 0; i < ArrayLength(executableExts); i++ ) { michael@0: if ( ext.EqualsASCII(executableExts[i])) { michael@0: // Found a match. Set result and quit. michael@0: *_retval = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::IsDirectory(bool *_retval) michael@0: { michael@0: return HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, _retval); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::IsFile(bool *_retval) michael@0: { michael@0: nsresult rv = HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, _retval); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: *_retval = !*_retval; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::IsHidden(bool *_retval) michael@0: { michael@0: return HasFileAttribute(FILE_ATTRIBUTE_HIDDEN, _retval); michael@0: } michael@0: michael@0: nsresult michael@0: nsLocalFile::HasFileAttribute(DWORD fileAttrib, bool *_retval) michael@0: { michael@0: if (NS_WARN_IF(!_retval)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: nsresult rv = Resolve(); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: DWORD attributes = GetFileAttributesW(mResolvedPath.get()); michael@0: if (INVALID_FILE_ATTRIBUTES == attributes) { michael@0: return ConvertWinError(GetLastError()); michael@0: } michael@0: michael@0: *_retval = ((attributes & fileAttrib) != 0); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::IsSymlink(bool *_retval) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: if (NS_WARN_IF(!_retval)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: // unless it is a valid shortcut path it's not a symlink michael@0: if (!IsShortcutPath(mWorkingPath)) { michael@0: *_retval = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // we need to know if this is a file or directory michael@0: nsresult rv = ResolveAndStat(); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: // We should not check mFileInfo64.type here for PR_FILE_FILE because lnk michael@0: // files can point to directories or files. Important security checks michael@0: // depend on correctly identifying lnk files. mFileInfo64 now holds info michael@0: // about the target of the lnk file, not the actual lnk file! michael@0: *_retval = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::IsSpecial(bool *_retval) michael@0: { michael@0: return HasFileAttribute(FILE_ATTRIBUTE_SYSTEM, _retval); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::Equals(nsIFile *inFile, bool *_retval) michael@0: { michael@0: if (NS_WARN_IF(!inFile)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: if (NS_WARN_IF(!_retval)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: EnsureShortPath(); michael@0: michael@0: nsCOMPtr lf(do_QueryInterface(inFile)); michael@0: if (!lf) { michael@0: *_retval = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoString inFilePath; michael@0: lf->GetCanonicalPath(inFilePath); michael@0: michael@0: // Ok : Win9x michael@0: *_retval = _wcsicmp(mShortWorkingPath.get(), inFilePath.get()) == 0; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::Contains(nsIFile *inFile, bool recur, bool *_retval) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: *_retval = false; michael@0: michael@0: nsAutoString myFilePath; michael@0: if (NS_FAILED(GetTarget(myFilePath))) michael@0: GetPath(myFilePath); michael@0: michael@0: uint32_t myFilePathLen = myFilePath.Length(); michael@0: michael@0: nsAutoString inFilePath; michael@0: if (NS_FAILED(inFile->GetTarget(inFilePath))) michael@0: inFile->GetPath(inFilePath); michael@0: michael@0: // make sure that the |inFile|'s path has a trailing separator. michael@0: if (inFilePath.Length() >= myFilePathLen && inFilePath[myFilePathLen] == L'\\') michael@0: { michael@0: if (_wcsnicmp(myFilePath.get(), inFilePath.get(), myFilePathLen) == 0) michael@0: { michael@0: *_retval = true; michael@0: } michael@0: michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetTarget(nsAString &_retval) michael@0: { michael@0: _retval.Truncate(); michael@0: #if STRICT_FAKE_SYMLINKS michael@0: bool symLink; michael@0: michael@0: nsresult rv = IsSymlink(&symLink); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (!symLink) michael@0: { michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: } michael@0: #endif michael@0: ResolveAndStat(); michael@0: michael@0: _retval = mResolvedPath; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /* attribute bool followLinks; */ michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetFollowLinks(bool *aFollowLinks) michael@0: { michael@0: *aFollowLinks = mFollowSymlinks; michael@0: return NS_OK; michael@0: } michael@0: NS_IMETHODIMP michael@0: nsLocalFile::SetFollowLinks(bool aFollowLinks) michael@0: { michael@0: MakeDirty(); michael@0: mFollowSymlinks = aFollowLinks; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator * *entries) michael@0: { michael@0: nsresult rv; michael@0: michael@0: *entries = nullptr; michael@0: if (mWorkingPath.EqualsLiteral("\\\\.")) { michael@0: nsDriveEnumerator *drives = new nsDriveEnumerator; michael@0: if (!drives) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ADDREF(drives); michael@0: rv = drives->Init(); michael@0: if (NS_FAILED(rv)) { michael@0: NS_RELEASE(drives); michael@0: return rv; michael@0: } michael@0: *entries = drives; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsDirEnumerator* dirEnum = new nsDirEnumerator(); michael@0: if (dirEnum == nullptr) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ADDREF(dirEnum); michael@0: rv = dirEnum->Init(this); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: NS_RELEASE(dirEnum); michael@0: return rv; michael@0: } michael@0: michael@0: *entries = dirEnum; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetPersistentDescriptor(nsACString &aPersistentDescriptor) michael@0: { michael@0: CopyUTF16toUTF8(mWorkingPath, aPersistentDescriptor); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::SetPersistentDescriptor(const nsACString &aPersistentDescriptor) michael@0: { michael@0: if (IsUTF8(aPersistentDescriptor)) michael@0: return InitWithPath(NS_ConvertUTF8toUTF16(aPersistentDescriptor)); michael@0: else michael@0: return InitWithNativePath(aPersistentDescriptor); michael@0: } michael@0: michael@0: /* attrib unsigned long fileAttributesWin; */ michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetFileAttributesWin(uint32_t *aAttribs) michael@0: { michael@0: *aAttribs = 0; michael@0: DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get()); michael@0: if (dwAttrs == INVALID_FILE_ATTRIBUTES) michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: michael@0: if (!(dwAttrs & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)) michael@0: *aAttribs |= WFA_SEARCH_INDEXED; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::SetFileAttributesWin(uint32_t aAttribs) michael@0: { michael@0: DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get()); michael@0: if (dwAttrs == INVALID_FILE_ATTRIBUTES) michael@0: return NS_ERROR_FILE_INVALID_PATH; michael@0: michael@0: if (aAttribs & WFA_SEARCH_INDEXED) { michael@0: dwAttrs &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; michael@0: } else { michael@0: dwAttrs |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; michael@0: } michael@0: michael@0: if (aAttribs & WFA_READONLY) { michael@0: dwAttrs |= FILE_ATTRIBUTE_READONLY; michael@0: } else if ((aAttribs & WFA_READWRITE) && michael@0: (dwAttrs & FILE_ATTRIBUTE_READONLY)) { michael@0: dwAttrs &= ~FILE_ATTRIBUTE_READONLY; michael@0: } michael@0: michael@0: if (SetFileAttributesW(mWorkingPath.get(), dwAttrs) == 0) michael@0: return NS_ERROR_FAILURE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::Reveal() michael@0: { michael@0: // This API should be main thread only michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // make sure mResolvedPath is set michael@0: nsresult rv = Resolve(); michael@0: if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) { michael@0: return rv; michael@0: } michael@0: michael@0: // To create a new thread, get the thread manager michael@0: nsCOMPtr tm = do_GetService(NS_THREADMANAGER_CONTRACTID); michael@0: nsCOMPtr mythread; michael@0: rv = tm->NewThread(0, 0, getter_AddRefs(mythread)); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr runnable = michael@0: new AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::RevealOp, michael@0: mResolvedPath); michael@0: michael@0: // After the dispatch, the result runnable will shut down the worker michael@0: // thread, so we can let it go. michael@0: mythread->Dispatch(runnable, NS_DISPATCH_NORMAL); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::Launch() michael@0: { michael@0: // This API should be main thread only michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // make sure mResolvedPath is set michael@0: nsresult rv = Resolve(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // To create a new thread, get the thread manager michael@0: nsCOMPtr tm = do_GetService(NS_THREADMANAGER_CONTRACTID); michael@0: nsCOMPtr mythread; michael@0: rv = tm->NewThread(0, 0, getter_AddRefs(mythread)); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr runnable = michael@0: new AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::LaunchOp, michael@0: mResolvedPath); michael@0: michael@0: // After the dispatch, the result runnable will shut down the worker michael@0: // thread, so we can let it go. michael@0: mythread->Dispatch(runnable, NS_DISPATCH_NORMAL); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: NS_NewLocalFile(const nsAString &path, bool followLinks, nsIFile* *result) michael@0: { michael@0: nsLocalFile* file = new nsLocalFile(); michael@0: if (file == nullptr) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ADDREF(file); michael@0: michael@0: file->SetFollowLinks(followLinks); michael@0: michael@0: if (!path.IsEmpty()) { michael@0: nsresult rv = file->InitWithPath(path); michael@0: if (NS_FAILED(rv)) { michael@0: NS_RELEASE(file); michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: *result = file; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // Native (lossy) interface michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::InitWithNativePath(const nsACString &filePath) michael@0: { michael@0: nsAutoString tmp; michael@0: nsresult rv = NS_CopyNativeToUnicode(filePath, tmp); michael@0: if (NS_SUCCEEDED(rv)) michael@0: return InitWithPath(tmp); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::AppendNative(const nsACString &node) michael@0: { michael@0: nsAutoString tmp; michael@0: nsresult rv = NS_CopyNativeToUnicode(node, tmp); michael@0: if (NS_SUCCEEDED(rv)) michael@0: return Append(tmp); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::AppendRelativeNativePath(const nsACString &node) michael@0: { michael@0: nsAutoString tmp; michael@0: nsresult rv = NS_CopyNativeToUnicode(node, tmp); michael@0: if (NS_SUCCEEDED(rv)) michael@0: return AppendRelativePath(tmp); michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetNativeLeafName(nsACString &aLeafName) michael@0: { michael@0: //NS_WARNING("This API is lossy. Use GetLeafName !"); michael@0: nsAutoString tmp; michael@0: nsresult rv = GetLeafName(tmp); michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = NS_CopyUnicodeToNative(tmp, aLeafName); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::SetNativeLeafName(const nsACString &aLeafName) michael@0: { michael@0: nsAutoString tmp; michael@0: nsresult rv = NS_CopyNativeToUnicode(aLeafName, tmp); michael@0: if (NS_SUCCEEDED(rv)) michael@0: return SetLeafName(tmp); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetNativePath(nsACString &_retval) michael@0: { michael@0: //NS_WARNING("This API is lossy. Use GetPath !"); michael@0: nsAutoString tmp; michael@0: nsresult rv = GetPath(tmp); michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = NS_CopyUnicodeToNative(tmp, _retval); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetNativeCanonicalPath(nsACString &aResult) michael@0: { michael@0: NS_WARNING("This method is lossy. Use GetCanonicalPath !"); michael@0: EnsureShortPath(); michael@0: NS_CopyUnicodeToNative(mShortWorkingPath, aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::CopyToNative(nsIFile *newParentDir, const nsACString &newName) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: if (newName.IsEmpty()) michael@0: return CopyTo(newParentDir, EmptyString()); michael@0: michael@0: nsAutoString tmp; michael@0: nsresult rv = NS_CopyNativeToUnicode(newName, tmp); michael@0: if (NS_SUCCEEDED(rv)) michael@0: return CopyTo(newParentDir, tmp); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::CopyToFollowingLinksNative(nsIFile *newParentDir, const nsACString &newName) michael@0: { michael@0: if (newName.IsEmpty()) michael@0: return CopyToFollowingLinks(newParentDir, EmptyString()); michael@0: michael@0: nsAutoString tmp; michael@0: nsresult rv = NS_CopyNativeToUnicode(newName, tmp); michael@0: if (NS_SUCCEEDED(rv)) michael@0: return CopyToFollowingLinks(newParentDir, tmp); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::MoveToNative(nsIFile *newParentDir, const nsACString &newName) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: if (newName.IsEmpty()) michael@0: return MoveTo(newParentDir, EmptyString()); michael@0: michael@0: nsAutoString tmp; michael@0: nsresult rv = NS_CopyNativeToUnicode(newName, tmp); michael@0: if (NS_SUCCEEDED(rv)) michael@0: return MoveTo(newParentDir, tmp); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetNativeTarget(nsACString &_retval) michael@0: { michael@0: // Check we are correctly initialized. michael@0: CHECK_mWorkingPath(); michael@0: michael@0: NS_WARNING("This API is lossy. Use GetTarget !"); michael@0: nsAutoString tmp; michael@0: nsresult rv = GetTarget(tmp); michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = NS_CopyUnicodeToNative(tmp, _retval); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: NS_NewNativeLocalFile(const nsACString &path, bool followLinks, nsIFile* *result) michael@0: { michael@0: nsAutoString buf; michael@0: nsresult rv = NS_CopyNativeToUnicode(path, buf); michael@0: if (NS_FAILED(rv)) { michael@0: *result = nullptr; michael@0: return rv; michael@0: } michael@0: return NS_NewLocalFile(buf, followLinks, result); michael@0: } michael@0: michael@0: void michael@0: nsLocalFile::EnsureShortPath() michael@0: { michael@0: if (!mShortWorkingPath.IsEmpty()) michael@0: return; michael@0: michael@0: WCHAR shortPath[MAX_PATH + 1]; michael@0: DWORD lengthNeeded = ::GetShortPathNameW(mWorkingPath.get(), shortPath, michael@0: ArrayLength(shortPath)); michael@0: // If an error occurred then lengthNeeded is set to 0 or the length of the michael@0: // needed buffer including null termination. If it succeeds the number of michael@0: // wide characters not including null termination is returned. michael@0: if (lengthNeeded != 0 && lengthNeeded < ArrayLength(shortPath)) michael@0: mShortWorkingPath.Assign(shortPath); michael@0: else michael@0: mShortWorkingPath.Assign(mWorkingPath); michael@0: } michael@0: michael@0: // nsIHashable michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::Equals(nsIHashable* aOther, bool *aResult) michael@0: { michael@0: nsCOMPtr otherfile(do_QueryInterface(aOther)); michael@0: if (!otherfile) { michael@0: *aResult = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: return Equals(otherfile, aResult); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocalFile::GetHashCode(uint32_t *aResult) michael@0: { michael@0: // In order for short and long path names to hash to the same value we michael@0: // always hash on the short pathname. michael@0: EnsureShortPath(); michael@0: michael@0: *aResult = HashString(mShortWorkingPath); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsLocalFile michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: void michael@0: nsLocalFile::GlobalInit() michael@0: { michael@0: DebugOnly rv = NS_CreateShortcutResolver(); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "Shortcut resolver could not be created"); michael@0: } michael@0: michael@0: void michael@0: nsLocalFile::GlobalShutdown() michael@0: { michael@0: NS_DestroyShortcutResolver(); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsDriveEnumerator, nsISimpleEnumerator) michael@0: michael@0: nsDriveEnumerator::nsDriveEnumerator() michael@0: { michael@0: } michael@0: michael@0: nsDriveEnumerator::~nsDriveEnumerator() michael@0: { michael@0: } michael@0: michael@0: nsresult nsDriveEnumerator::Init() michael@0: { michael@0: /* If the length passed to GetLogicalDriveStrings is smaller michael@0: * than the length of the string it would return, it returns michael@0: * the length required for the string. */ michael@0: DWORD length = GetLogicalDriveStringsW(0, 0); michael@0: /* The string is null terminated */ michael@0: if (!mDrives.SetLength(length+1, fallible_t())) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: if (!GetLogicalDriveStringsW(length, wwc(mDrives.BeginWriting()))) michael@0: return NS_ERROR_FAILURE; michael@0: mDrives.BeginReading(mStartOfCurrentDrive); michael@0: mDrives.EndReading(mEndOfDrivesString); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDriveEnumerator::HasMoreElements(bool *aHasMore) michael@0: { michael@0: *aHasMore = *mStartOfCurrentDrive != L'\0'; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDriveEnumerator::GetNext(nsISupports **aNext) michael@0: { michael@0: /* GetLogicalDrives stored in mDrives is a concatenation michael@0: * of null terminated strings, followed by a null terminator. michael@0: * mStartOfCurrentDrive is an iterator pointing at the first michael@0: * character of the current drive. */ michael@0: if (*mStartOfCurrentDrive == L'\0') { michael@0: *aNext = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAString::const_iterator driveEnd = mStartOfCurrentDrive; michael@0: FindCharInReadable(L'\0', driveEnd, mEndOfDrivesString); michael@0: nsString drive(Substring(mStartOfCurrentDrive, driveEnd)); michael@0: mStartOfCurrentDrive = ++driveEnd; michael@0: michael@0: nsIFile *file; michael@0: nsresult rv = NS_NewLocalFile(drive, false, &file); michael@0: michael@0: *aNext = file; michael@0: return rv; michael@0: }