mirror of https://github.com/roytam1/UXP
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3797 lines
98 KiB
3797 lines
98 KiB
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
|
/* This Source Code Form is subject to the terms of the Mozilla Public |
|
* License, v. 2.0. If a copy of the MPL was not distributed with this |
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
|
|
#include "mozilla/ArrayUtils.h" |
|
#include "mozilla/DebugOnly.h" |
|
#include "mozilla/UniquePtrExtensions.h" |
|
#include "mozilla/WindowsVersion.h" |
|
|
|
#include "nsCOMPtr.h" |
|
#include "nsAutoPtr.h" |
|
#include "nsMemory.h" |
|
#include "GeckoProfiler.h" |
|
|
|
#include "nsLocalFile.h" |
|
#include "nsIDirectoryEnumerator.h" |
|
#include "nsNativeCharsetUtils.h" |
|
|
|
#include "nsISimpleEnumerator.h" |
|
#include "nsIComponentManager.h" |
|
#include "prio.h" |
|
#include "private/pprio.h" // To get PR_ImportFile |
|
#include "prprf.h" |
|
#include "prmem.h" |
|
#include "nsHashKeys.h" |
|
|
|
#include "nsXPIDLString.h" |
|
#include "nsReadableUtils.h" |
|
|
|
#include <direct.h> |
|
#include <windows.h> |
|
#include <shlwapi.h> |
|
#include <aclapi.h> |
|
|
|
#include "shellapi.h" |
|
#include "shlguid.h" |
|
|
|
#include <io.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <mbstring.h> |
|
|
|
#include "nsXPIDLString.h" |
|
#include "prproces.h" |
|
#include "prlink.h" |
|
|
|
#include "mozilla/FilePreferences.h" |
|
#include "mozilla/Mutex.h" |
|
#include "SpecialSystemDirectory.h" |
|
|
|
#include "nsTraceRefcnt.h" |
|
#include "nsXPCOMCIDInternal.h" |
|
#include "nsThreadUtils.h" |
|
#include "nsXULAppAPI.h" |
|
|
|
#include "nsIWindowMediator.h" |
|
#include "mozIDOMWindow.h" |
|
#include "nsPIDOMWindow.h" |
|
#include "nsIWidget.h" |
|
#include "mozilla/WidgetUtils.h" |
|
|
|
using namespace mozilla; |
|
|
|
#define CHECK_mWorkingPath() \ |
|
PR_BEGIN_MACRO \ |
|
if (mWorkingPath.IsEmpty()) \ |
|
return NS_ERROR_NOT_INITIALIZED; \ |
|
PR_END_MACRO |
|
|
|
// CopyFileEx only supports unbuffered I/O in Windows Vista and above |
|
#ifndef COPY_FILE_NO_BUFFERING |
|
#define COPY_FILE_NO_BUFFERING 0x00001000 |
|
#endif |
|
|
|
#ifndef FILE_ATTRIBUTE_NOT_CONTENT_INDEXED |
|
#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000 |
|
#endif |
|
|
|
#ifndef DRIVE_REMOTE |
|
#define DRIVE_REMOTE 4 |
|
#endif |
|
|
|
static HWND |
|
GetMostRecentNavigatorHWND() |
|
{ |
|
nsresult rv; |
|
nsCOMPtr<nsIWindowMediator> winMediator( |
|
do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv)); |
|
if (NS_FAILED(rv)) { |
|
return nullptr; |
|
} |
|
|
|
nsCOMPtr<mozIDOMWindowProxy> navWin; |
|
rv = winMediator->GetMostRecentWindow(u"navigator:browser", |
|
getter_AddRefs(navWin)); |
|
if (NS_FAILED(rv) || !navWin) { |
|
return nullptr; |
|
} |
|
|
|
nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin); |
|
nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(win); |
|
if (!widget) { |
|
return nullptr; |
|
} |
|
|
|
return reinterpret_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW)); |
|
} |
|
|
|
|
|
/** |
|
* A runnable to dispatch back to the main thread when |
|
* AsyncRevealOperation completes. |
|
*/ |
|
class AsyncLocalFileWinDone : public Runnable |
|
{ |
|
public: |
|
AsyncLocalFileWinDone() : |
|
mWorkerThread(do_GetCurrentThread()) |
|
{ |
|
// Objects of this type must only be created on worker threads |
|
MOZ_ASSERT(!NS_IsMainThread()); |
|
} |
|
|
|
NS_IMETHOD Run() override |
|
{ |
|
// This event shuts down the worker thread and so must be main thread. |
|
MOZ_ASSERT(NS_IsMainThread()); |
|
|
|
// If we don't destroy the thread when we're done with it, it will hang |
|
// around forever... and that is bad! |
|
mWorkerThread->Shutdown(); |
|
return NS_OK; |
|
} |
|
|
|
private: |
|
nsCOMPtr<nsIThread> mWorkerThread; |
|
}; |
|
|
|
/** |
|
* A runnable to dispatch from the main thread when an async operation should |
|
* be performed. |
|
*/ |
|
class AsyncRevealOperation : public Runnable |
|
{ |
|
public: |
|
explicit AsyncRevealOperation(const nsAString& aResolvedPath) |
|
: mResolvedPath(aResolvedPath) |
|
{ |
|
} |
|
|
|
NS_IMETHOD Run() override |
|
{ |
|
MOZ_ASSERT(!NS_IsMainThread(), |
|
"AsyncRevealOperation should not be run on the main thread!"); |
|
|
|
bool doCoUninitialize = SUCCEEDED( |
|
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)); |
|
Reveal(); |
|
if (doCoUninitialize) { |
|
CoUninitialize(); |
|
} |
|
|
|
// Send the result back to the main thread so that this thread can be |
|
// cleanly shut down |
|
nsCOMPtr<nsIRunnable> resultrunnable = new AsyncLocalFileWinDone(); |
|
NS_DispatchToMainThread(resultrunnable); |
|
return NS_OK; |
|
} |
|
|
|
private: |
|
// Reveals the path in explorer. |
|
nsresult Reveal() |
|
{ |
|
DWORD attributes = GetFileAttributesW(mResolvedPath.get()); |
|
if (INVALID_FILE_ATTRIBUTES == attributes) { |
|
return NS_ERROR_FILE_INVALID_PATH; |
|
} |
|
|
|
HRESULT hr; |
|
if (attributes & FILE_ATTRIBUTE_DIRECTORY) { |
|
// We have a directory so we should open the directory itself. |
|
LPITEMIDLIST dir = ILCreateFromPathW(mResolvedPath.get()); |
|
if (!dir) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
LPCITEMIDLIST selection[] = { dir }; |
|
UINT count = ArrayLength(selection); |
|
|
|
//Perform the open of the directory. |
|
hr = SHOpenFolderAndSelectItems(dir, count, selection, 0); |
|
CoTaskMemFree(dir); |
|
} else { |
|
int32_t len = mResolvedPath.Length(); |
|
// We don't currently handle UNC long paths of the form \\?\ anywhere so |
|
// this should be fine. |
|
if (len > MAX_PATH) { |
|
return NS_ERROR_FILE_INVALID_PATH; |
|
} |
|
WCHAR parentDirectoryPath[MAX_PATH + 1] = { 0 }; |
|
wcsncpy(parentDirectoryPath, mResolvedPath.get(), MAX_PATH); |
|
PathRemoveFileSpecW(parentDirectoryPath); |
|
|
|
// We have a file so we should open the parent directory. |
|
LPITEMIDLIST dir = ILCreateFromPathW(parentDirectoryPath); |
|
if (!dir) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
// Set the item in the directory to select to the file we want to reveal. |
|
LPITEMIDLIST item = ILCreateFromPathW(mResolvedPath.get()); |
|
if (!item) { |
|
CoTaskMemFree(dir); |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
LPCITEMIDLIST selection[] = { item }; |
|
UINT count = ArrayLength(selection); |
|
|
|
//Perform the selection of the file. |
|
hr = SHOpenFolderAndSelectItems(dir, count, selection, 0); |
|
|
|
CoTaskMemFree(dir); |
|
CoTaskMemFree(item); |
|
} |
|
|
|
return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE; |
|
} |
|
|
|
// Stores the path to perform the operation on |
|
nsString mResolvedPath; |
|
}; |
|
|
|
class nsDriveEnumerator : public nsISimpleEnumerator |
|
{ |
|
public: |
|
nsDriveEnumerator(); |
|
NS_DECL_ISUPPORTS |
|
NS_DECL_NSISIMPLEENUMERATOR |
|
nsresult Init(); |
|
private: |
|
virtual ~nsDriveEnumerator(); |
|
|
|
/* mDrives stores the null-separated drive names. |
|
* Init sets them. |
|
* HasMoreElements checks mStartOfCurrentDrive. |
|
* GetNext advances mStartOfCurrentDrive. |
|
*/ |
|
nsString mDrives; |
|
nsAString::const_iterator mStartOfCurrentDrive; |
|
nsAString::const_iterator mEndOfDrivesString; |
|
}; |
|
|
|
//---------------------------------------------------------------------------- |
|
// short cut resolver |
|
//---------------------------------------------------------------------------- |
|
class ShortcutResolver |
|
{ |
|
public: |
|
ShortcutResolver(); |
|
// nonvirtual since we're not subclassed |
|
~ShortcutResolver(); |
|
|
|
nsresult Init(); |
|
nsresult Resolve(const WCHAR* aIn, WCHAR* aOut); |
|
nsresult SetShortcut(bool aUpdateExisting, |
|
const WCHAR* aShortcutPath, |
|
const WCHAR* aTargetPath, |
|
const WCHAR* aWorkingDir, |
|
const WCHAR* aArgs, |
|
const WCHAR* aDescription, |
|
const WCHAR* aIconFile, |
|
int32_t aIconIndex); |
|
|
|
private: |
|
Mutex mLock; |
|
RefPtr<IPersistFile> mPersistFile; |
|
RefPtr<IShellLinkW> mShellLink; |
|
}; |
|
|
|
ShortcutResolver::ShortcutResolver() : |
|
mLock("ShortcutResolver.mLock") |
|
{ |
|
CoInitialize(nullptr); |
|
} |
|
|
|
ShortcutResolver::~ShortcutResolver() |
|
{ |
|
CoUninitialize(); |
|
} |
|
|
|
nsresult |
|
ShortcutResolver::Init() |
|
{ |
|
// Get a pointer to the IPersistFile interface. |
|
if (FAILED(CoCreateInstance(CLSID_ShellLink, |
|
nullptr, |
|
CLSCTX_INPROC_SERVER, |
|
IID_IShellLinkW, |
|
getter_AddRefs(mShellLink))) || |
|
FAILED(mShellLink->QueryInterface(IID_IPersistFile, |
|
getter_AddRefs(mPersistFile)))) { |
|
mShellLink = nullptr; |
|
return NS_ERROR_FAILURE; |
|
} |
|
return NS_OK; |
|
} |
|
|
|
// |out| must be an allocated buffer of size MAX_PATH |
|
nsresult |
|
ShortcutResolver::Resolve(const WCHAR* aIn, WCHAR* aOut) |
|
{ |
|
if (!mShellLink) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
MutexAutoLock lock(mLock); |
|
|
|
if (FAILED(mPersistFile->Load(aIn, STGM_READ)) || |
|
FAILED(mShellLink->Resolve(nullptr, SLR_NO_UI)) || |
|
FAILED(mShellLink->GetPath(aOut, MAX_PATH, nullptr, SLGP_UNCPRIORITY))) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
return NS_OK; |
|
} |
|
|
|
nsresult |
|
ShortcutResolver::SetShortcut(bool aUpdateExisting, |
|
const WCHAR* aShortcutPath, |
|
const WCHAR* aTargetPath, |
|
const WCHAR* aWorkingDir, |
|
const WCHAR* aArgs, |
|
const WCHAR* aDescription, |
|
const WCHAR* aIconPath, |
|
int32_t aIconIndex) |
|
{ |
|
if (!mShellLink) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
if (!aShortcutPath) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
MutexAutoLock lock(mLock); |
|
|
|
if (aUpdateExisting) { |
|
if (FAILED(mPersistFile->Load(aShortcutPath, STGM_READWRITE))) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
} else { |
|
if (!aTargetPath) { |
|
return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; |
|
} |
|
|
|
// Since we reuse our IPersistFile, we have to clear out any values that |
|
// may be left over from previous calls to SetShortcut. |
|
if (FAILED(mShellLink->SetWorkingDirectory(L"")) || |
|
FAILED(mShellLink->SetArguments(L"")) || |
|
FAILED(mShellLink->SetDescription(L"")) || |
|
FAILED(mShellLink->SetIconLocation(L"", 0))) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
} |
|
|
|
if (aTargetPath && FAILED(mShellLink->SetPath(aTargetPath))) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
if (aWorkingDir && FAILED(mShellLink->SetWorkingDirectory(aWorkingDir))) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
if (aArgs && FAILED(mShellLink->SetArguments(aArgs))) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
if (aDescription && FAILED(mShellLink->SetDescription(aDescription))) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
if (aIconPath && FAILED(mShellLink->SetIconLocation(aIconPath, aIconIndex))) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
if (FAILED(mPersistFile->Save(aShortcutPath, |
|
TRUE))) { |
|
// Second argument indicates whether the file path specified in the |
|
// first argument should become the "current working file" for this |
|
// IPersistFile |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
return NS_OK; |
|
} |
|
|
|
static ShortcutResolver* gResolver = nullptr; |
|
|
|
static nsresult |
|
NS_CreateShortcutResolver() |
|
{ |
|
gResolver = new ShortcutResolver(); |
|
return gResolver->Init(); |
|
} |
|
|
|
static void |
|
NS_DestroyShortcutResolver() |
|
{ |
|
delete gResolver; |
|
gResolver = nullptr; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// static helper functions |
|
//----------------------------------------------------------------------------- |
|
|
|
// certainly not all the error that can be |
|
// encountered, but many of them common ones |
|
static nsresult |
|
ConvertWinError(DWORD aWinErr) |
|
{ |
|
nsresult rv; |
|
|
|
switch (aWinErr) { |
|
case ERROR_FILE_NOT_FOUND: |
|
case ERROR_PATH_NOT_FOUND: |
|
case ERROR_INVALID_DRIVE: |
|
case ERROR_NOT_READY: |
|
rv = NS_ERROR_FILE_NOT_FOUND; |
|
break; |
|
case ERROR_ACCESS_DENIED: |
|
case ERROR_NOT_SAME_DEVICE: |
|
rv = NS_ERROR_FILE_ACCESS_DENIED; |
|
break; |
|
case ERROR_SHARING_VIOLATION: // CreateFile without sharing flags |
|
case ERROR_LOCK_VIOLATION: // LockFile, LockFileEx |
|
rv = NS_ERROR_FILE_IS_LOCKED; |
|
break; |
|
case ERROR_NOT_ENOUGH_MEMORY: |
|
case ERROR_INVALID_BLOCK: |
|
case ERROR_INVALID_HANDLE: |
|
case ERROR_ARENA_TRASHED: |
|
rv = NS_ERROR_OUT_OF_MEMORY; |
|
break; |
|
case ERROR_CURRENT_DIRECTORY: |
|
rv = NS_ERROR_FILE_DIR_NOT_EMPTY; |
|
break; |
|
case ERROR_WRITE_PROTECT: |
|
rv = NS_ERROR_FILE_READ_ONLY; |
|
break; |
|
case ERROR_HANDLE_DISK_FULL: |
|
rv = NS_ERROR_FILE_TOO_BIG; |
|
break; |
|
case ERROR_FILE_EXISTS: |
|
case ERROR_ALREADY_EXISTS: |
|
case ERROR_CANNOT_MAKE: |
|
rv = NS_ERROR_FILE_ALREADY_EXISTS; |
|
break; |
|
case ERROR_FILENAME_EXCED_RANGE: |
|
rv = NS_ERROR_FILE_NAME_TOO_LONG; |
|
break; |
|
case ERROR_DIRECTORY: |
|
rv = NS_ERROR_FILE_NOT_DIRECTORY; |
|
break; |
|
case 0: |
|
rv = NS_OK; |
|
break; |
|
default: |
|
rv = NS_ERROR_FAILURE; |
|
break; |
|
} |
|
return rv; |
|
} |
|
|
|
// as suggested in the MSDN documentation on SetFilePointer |
|
static __int64 |
|
MyFileSeek64(HANDLE aHandle, __int64 aDistance, DWORD aMoveMethod) |
|
{ |
|
LARGE_INTEGER li; |
|
|
|
li.QuadPart = aDistance; |
|
li.LowPart = SetFilePointer(aHandle, li.LowPart, &li.HighPart, aMoveMethod); |
|
if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) { |
|
li.QuadPart = -1; |
|
} |
|
|
|
return li.QuadPart; |
|
} |
|
|
|
static bool |
|
IsShortcutPath(const nsAString& aPath) |
|
{ |
|
// Under Windows, the shortcuts are just files with a ".lnk" extension. |
|
// Note also that we don't resolve links in the middle of paths. |
|
// i.e. "c:\foo.lnk\bar.txt" is invalid. |
|
MOZ_ASSERT(!aPath.IsEmpty(), "don't pass an empty string"); |
|
int32_t len = aPath.Length(); |
|
return len >= 4 && (StringTail(aPath, 4).LowerCaseEqualsASCII(".lnk")); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// We need the following three definitions to make |OpenFile| convert a file |
|
// handle to an NSPR file descriptor correctly when |O_APPEND| flag is |
|
// specified. It is defined in a private header of NSPR (primpl.h) we can't |
|
// include. As a temporary workaround until we decide how to extend |
|
// |PR_ImportFile|, we define it here. Currently, |_PR_HAVE_PEEK_BUFFER| |
|
// and |PR_STRICT_ADDR_LEN| are not defined for the 'w95'-dependent portion |
|
// of NSPR so that fields of |PRFilePrivate| #ifdef'd by them are not copied. |
|
// Similarly, |_MDFileDesc| is taken from nsprpub/pr/include/md/_win95.h. |
|
// In an unlikely case we switch to 'NT'-dependent NSPR AND this temporary |
|
// workaround last beyond the switch, |PRFilePrivate| and |_MDFileDesc| |
|
// need to be changed to match the definitions for WinNT. |
|
//----------------------------------------------------------------------------- |
|
typedef enum |
|
{ |
|
_PR_TRI_TRUE = 1, |
|
_PR_TRI_FALSE = 0, |
|
_PR_TRI_UNKNOWN = -1 |
|
} _PRTriStateBool; |
|
|
|
struct _MDFileDesc |
|
{ |
|
PROsfd osfd; |
|
}; |
|
|
|
struct PRFilePrivate |
|
{ |
|
int32_t state; |
|
bool nonblocking; |
|
_PRTriStateBool inheritable; |
|
PRFileDesc* next; |
|
int lockCount; /* 0: not locked |
|
* -1: a native lockfile call is in progress |
|
* > 0: # times the file is locked */ |
|
bool appendMode; |
|
_MDFileDesc md; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Six static methods defined below (OpenFile, FileTimeToPRTime, GetFileInfo, |
|
// OpenDir, CloseDir, ReadDir) should go away once the corresponding |
|
// UTF-16 APIs are implemented on all the supported platforms (or at least |
|
// Windows 9x/ME) in NSPR. Currently, they're only implemented on |
|
// Windows NT4 or later. (bug 330665) |
|
//----------------------------------------------------------------------------- |
|
|
|
// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} : |
|
// PR_Open and _PR_MD_OPEN |
|
nsresult |
|
OpenFile(const nsAFlatString& aName, |
|
int aOsflags, |
|
int aMode, |
|
bool aShareDelete, |
|
PRFileDesc** aFd) |
|
{ |
|
int32_t access = 0; |
|
|
|
int32_t shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; |
|
int32_t disposition = 0; |
|
int32_t attributes = 0; |
|
|
|
if (aShareDelete) { |
|
shareMode |= FILE_SHARE_DELETE; |
|
} |
|
|
|
if (aOsflags & PR_SYNC) { |
|
attributes = FILE_FLAG_WRITE_THROUGH; |
|
} |
|
if (aOsflags & PR_RDONLY || aOsflags & PR_RDWR) { |
|
access |= GENERIC_READ; |
|
} |
|
if (aOsflags & PR_WRONLY || aOsflags & PR_RDWR) { |
|
access |= GENERIC_WRITE; |
|
} |
|
|
|
if (aOsflags & PR_CREATE_FILE && aOsflags & PR_EXCL) { |
|
disposition = CREATE_NEW; |
|
} else if (aOsflags & PR_CREATE_FILE) { |
|
if (aOsflags & PR_TRUNCATE) { |
|
disposition = CREATE_ALWAYS; |
|
} else { |
|
disposition = OPEN_ALWAYS; |
|
} |
|
} else { |
|
if (aOsflags & PR_TRUNCATE) { |
|
disposition = TRUNCATE_EXISTING; |
|
} else { |
|
disposition = OPEN_EXISTING; |
|
} |
|
} |
|
|
|
if (aOsflags & nsIFile::DELETE_ON_CLOSE) { |
|
attributes |= FILE_FLAG_DELETE_ON_CLOSE; |
|
} |
|
|
|
if (aOsflags & nsIFile::OS_READAHEAD) { |
|
attributes |= FILE_FLAG_SEQUENTIAL_SCAN; |
|
} |
|
|
|
// If no write permissions are requested, and if we are possibly creating |
|
// the file, then set the new file as read only. |
|
// The flag has no effect if we happen to open the file. |
|
if (!(aMode & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) && |
|
disposition != OPEN_EXISTING) { |
|
attributes |= FILE_ATTRIBUTE_READONLY; |
|
} |
|
|
|
HANDLE file = ::CreateFileW(aName.get(), access, shareMode, |
|
nullptr, disposition, attributes, nullptr); |
|
|
|
if (file == INVALID_HANDLE_VALUE) { |
|
*aFd = nullptr; |
|
return ConvertWinError(GetLastError()); |
|
} |
|
|
|
*aFd = PR_ImportFile((PROsfd) file); |
|
if (*aFd) { |
|
// On Windows, _PR_HAVE_O_APPEND is not defined so that we have to |
|
// add it manually. (see |PR_Open| in nsprpub/pr/src/io/prfile.c) |
|
(*aFd)->secret->appendMode = (PR_APPEND & aOsflags) ? true : false; |
|
return NS_OK; |
|
} |
|
|
|
nsresult rv = NS_ErrorAccordingToNSPR(); |
|
|
|
CloseHandle(file); |
|
|
|
return rv; |
|
} |
|
|
|
// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} : |
|
// PR_FileTimeToPRTime and _PR_FileTimeToPRTime |
|
static void |
|
FileTimeToPRTime(const FILETIME* aFiletime, PRTime* aPrtm) |
|
{ |
|
#ifdef __GNUC__ |
|
const PRTime _pr_filetime_offset = 116444736000000000LL; |
|
#else |
|
const PRTime _pr_filetime_offset = 116444736000000000i64; |
|
#endif |
|
|
|
MOZ_ASSERT(sizeof(FILETIME) == sizeof(PRTime)); |
|
::CopyMemory(aPrtm, aFiletime, sizeof(PRTime)); |
|
#ifdef __GNUC__ |
|
*aPrtm = (*aPrtm - _pr_filetime_offset) / 10LL; |
|
#else |
|
*aPrtm = (*aPrtm - _pr_filetime_offset) / 10i64; |
|
#endif |
|
} |
|
|
|
// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} with some |
|
// changes : PR_GetFileInfo64, _PR_MD_GETFILEINFO64 |
|
static nsresult |
|
GetFileInfo(const nsAFlatString& aName, PRFileInfo64* aInfo) |
|
{ |
|
WIN32_FILE_ATTRIBUTE_DATA fileData; |
|
|
|
if (aName.IsEmpty() || aName.FindCharInSet(u"?*") != kNotFound) { |
|
return NS_ERROR_INVALID_ARG; |
|
} |
|
|
|
if (!::GetFileAttributesExW(aName.get(), GetFileExInfoStandard, &fileData)) { |
|
return ConvertWinError(GetLastError()); |
|
} |
|
|
|
if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { |
|
aInfo->type = PR_FILE_DIRECTORY; |
|
} else { |
|
aInfo->type = PR_FILE_FILE; |
|
} |
|
|
|
aInfo->size = fileData.nFileSizeHigh; |
|
aInfo->size = (aInfo->size << 32) + fileData.nFileSizeLow; |
|
|
|
FileTimeToPRTime(&fileData.ftLastWriteTime, &aInfo->modifyTime); |
|
|
|
if (0 == fileData.ftCreationTime.dwLowDateTime && |
|
0 == fileData.ftCreationTime.dwHighDateTime) { |
|
aInfo->creationTime = aInfo->modifyTime; |
|
} else { |
|
FileTimeToPRTime(&fileData.ftCreationTime, &aInfo->creationTime); |
|
} |
|
|
|
return NS_OK; |
|
} |
|
|
|
struct nsDir |
|
{ |
|
HANDLE handle; |
|
WIN32_FIND_DATAW data; |
|
bool firstEntry; |
|
}; |
|
|
|
static nsresult |
|
OpenDir(const nsAFlatString& aName, nsDir** aDir) |
|
{ |
|
if (NS_WARN_IF(!aDir)) { |
|
return NS_ERROR_INVALID_ARG; |
|
} |
|
|
|
*aDir = nullptr; |
|
if (aName.Length() + 3 >= MAX_PATH) { |
|
return NS_ERROR_FILE_NAME_TOO_LONG; |
|
} |
|
|
|
nsDir* d = new nsDir(); |
|
nsAutoString filename(aName); |
|
|
|
// If |aName| ends in a slash or backslash, do not append another backslash. |
|
if (filename.Last() == L'/' || filename.Last() == L'\\') { |
|
filename.Append('*'); |
|
} else { |
|
filename.AppendLiteral("\\*"); |
|
} |
|
|
|
filename.ReplaceChar(L'/', L'\\'); |
|
|
|
// FindFirstFileW Will have a last error of ERROR_DIRECTORY if |
|
// <file_path>\* is passed in. If <unknown_path>\* is passed in then |
|
// ERROR_PATH_NOT_FOUND will be the last error. |
|
d->handle = ::FindFirstFileW(filename.get(), &(d->data)); |
|
|
|
if (d->handle == INVALID_HANDLE_VALUE) { |
|
delete d; |
|
return ConvertWinError(GetLastError()); |
|
} |
|
d->firstEntry = true; |
|
|
|
*aDir = d; |
|
return NS_OK; |
|
} |
|
|
|
static nsresult |
|
ReadDir(nsDir* aDir, PRDirFlags aFlags, nsString& aName) |
|
{ |
|
aName.Truncate(); |
|
if (NS_WARN_IF(!aDir)) { |
|
return NS_ERROR_INVALID_ARG; |
|
} |
|
|
|
while (1) { |
|
BOOL rv; |
|
if (aDir->firstEntry) { |
|
aDir->firstEntry = false; |
|
rv = 1; |
|
} else { |
|
rv = ::FindNextFileW(aDir->handle, &(aDir->data)); |
|
} |
|
|
|
if (rv == 0) { |
|
break; |
|
} |
|
|
|
const wchar_t* fileName; |
|
fileName = (aDir)->data.cFileName; |
|
|
|
if ((aFlags & PR_SKIP_DOT) && |
|
(fileName[0] == L'.') && (fileName[1] == L'\0')) { |
|
continue; |
|
} |
|
if ((aFlags & PR_SKIP_DOT_DOT) && |
|
(fileName[0] == L'.') && (fileName[1] == L'.') && |
|
(fileName[2] == L'\0')) { |
|
continue; |
|
} |
|
|
|
DWORD attrib = aDir->data.dwFileAttributes; |
|
if ((aFlags & PR_SKIP_HIDDEN) && (attrib & FILE_ATTRIBUTE_HIDDEN)) { |
|
continue; |
|
} |
|
|
|
aName = fileName; |
|
return NS_OK; |
|
} |
|
|
|
DWORD err = GetLastError(); |
|
return err == ERROR_NO_MORE_FILES ? NS_OK : ConvertWinError(err); |
|
} |
|
|
|
static nsresult |
|
CloseDir(nsDir*& aDir) |
|
{ |
|
if (NS_WARN_IF(!aDir)) { |
|
return NS_ERROR_INVALID_ARG; |
|
} |
|
|
|
BOOL isOk = FindClose(aDir->handle); |
|
delete aDir; |
|
aDir = nullptr; |
|
return isOk ? NS_OK : ConvertWinError(GetLastError()); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// nsDirEnumerator |
|
//----------------------------------------------------------------------------- |
|
|
|
class nsDirEnumerator final |
|
: public nsISimpleEnumerator |
|
, public nsIDirectoryEnumerator |
|
{ |
|
private: |
|
~nsDirEnumerator() |
|
{ |
|
Close(); |
|
} |
|
|
|
public: |
|
NS_DECL_ISUPPORTS |
|
|
|
nsDirEnumerator() : mDir(nullptr) |
|
{ |
|
} |
|
|
|
nsresult Init(nsIFile* aParent) |
|
{ |
|
nsAutoString filepath; |
|
aParent->GetTarget(filepath); |
|
|
|
if (filepath.IsEmpty()) { |
|
aParent->GetPath(filepath); |
|
} |
|
|
|
if (filepath.IsEmpty()) { |
|
return NS_ERROR_UNEXPECTED; |
|
} |
|
|
|
// IsDirectory is not needed here because OpenDir will return |
|
// NS_ERROR_FILE_NOT_DIRECTORY if the passed in path is a file. |
|
nsresult rv = OpenDir(filepath, &mDir); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
mParent = aParent; |
|
return NS_OK; |
|
} |
|
|
|
NS_IMETHOD HasMoreElements(bool* aResult) |
|
{ |
|
nsresult rv; |
|
if (!mNext && mDir) { |
|
nsString name; |
|
rv = ReadDir(mDir, PR_SKIP_BOTH, name); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
if (name.IsEmpty()) { |
|
// end of dir entries |
|
if (NS_FAILED(CloseDir(mDir))) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
*aResult = false; |
|
return NS_OK; |
|
} |
|
|
|
nsCOMPtr<nsIFile> file; |
|
rv = mParent->Clone(getter_AddRefs(file)); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
rv = file->Append(name); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
mNext = do_QueryInterface(file); |
|
} |
|
*aResult = mNext != nullptr; |
|
if (!*aResult) { |
|
Close(); |
|
} |
|
return NS_OK; |
|
} |
|
|
|
NS_IMETHOD GetNext(nsISupports** aResult) |
|
{ |
|
nsresult rv; |
|
bool hasMore; |
|
rv = HasMoreElements(&hasMore); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
*aResult = mNext; // might return nullptr |
|
NS_IF_ADDREF(*aResult); |
|
|
|
mNext = nullptr; |
|
return NS_OK; |
|
} |
|
|
|
NS_IMETHOD GetNextFile(nsIFile** aResult) |
|
{ |
|
*aResult = nullptr; |
|
bool hasMore = false; |
|
nsresult rv = HasMoreElements(&hasMore); |
|
if (NS_FAILED(rv) || !hasMore) { |
|
return rv; |
|
} |
|
*aResult = mNext; |
|
NS_IF_ADDREF(*aResult); |
|
mNext = nullptr; |
|
return NS_OK; |
|
} |
|
|
|
NS_IMETHOD Close() |
|
{ |
|
if (mDir) { |
|
nsresult rv = CloseDir(mDir); |
|
NS_ASSERTION(NS_SUCCEEDED(rv), "close failed"); |
|
if (NS_FAILED(rv)) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
} |
|
return NS_OK; |
|
} |
|
|
|
protected: |
|
nsDir* mDir; |
|
nsCOMPtr<nsIFile> mParent; |
|
nsCOMPtr<nsIFile> mNext; |
|
}; |
|
|
|
NS_IMPL_ISUPPORTS(nsDirEnumerator, nsISimpleEnumerator, nsIDirectoryEnumerator) |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// nsLocalFile <public> |
|
//----------------------------------------------------------------------------- |
|
|
|
nsLocalFile::nsLocalFile() |
|
: mDirty(true) |
|
, mResolveDirty(true) |
|
, mFollowSymlinks(false) |
|
{ |
|
} |
|
|
|
nsresult |
|
nsLocalFile::nsLocalFileConstructor(nsISupports* aOuter, const nsIID& aIID, |
|
void** aInstancePtr) |
|
{ |
|
if (NS_WARN_IF(!aInstancePtr)) { |
|
return NS_ERROR_INVALID_ARG; |
|
} |
|
if (NS_WARN_IF(aOuter)) { |
|
return NS_ERROR_NO_AGGREGATION; |
|
} |
|
|
|
nsLocalFile* inst = new nsLocalFile(); |
|
nsresult rv = inst->QueryInterface(aIID, aInstancePtr); |
|
if (NS_FAILED(rv)) { |
|
delete inst; |
|
return rv; |
|
} |
|
return NS_OK; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// nsLocalFile::nsISupports |
|
//----------------------------------------------------------------------------- |
|
|
|
NS_IMPL_ISUPPORTS(nsLocalFile, |
|
nsILocalFile, |
|
nsIFile, |
|
nsILocalFileWin, |
|
nsIHashable) |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// nsLocalFile <private> |
|
//----------------------------------------------------------------------------- |
|
|
|
nsLocalFile::nsLocalFile(const nsLocalFile& aOther) |
|
: mDirty(true) |
|
, mResolveDirty(true) |
|
, mFollowSymlinks(aOther.mFollowSymlinks) |
|
, mWorkingPath(aOther.mWorkingPath) |
|
{ |
|
} |
|
|
|
// Resolve the shortcut file from mWorkingPath and write the path |
|
// it points to into mResolvedPath. |
|
nsresult |
|
nsLocalFile::ResolveShortcut() |
|
{ |
|
// we can't do anything without the resolver |
|
if (!gResolver) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
mResolvedPath.SetLength(MAX_PATH); |
|
if (mResolvedPath.Length() != MAX_PATH) { |
|
return NS_ERROR_OUT_OF_MEMORY; |
|
} |
|
|
|
wchar_t* resolvedPath = wwc(mResolvedPath.BeginWriting()); |
|
|
|
// resolve this shortcut |
|
nsresult rv = gResolver->Resolve(mWorkingPath.get(), resolvedPath); |
|
|
|
size_t len = NS_FAILED(rv) ? 0 : wcslen(resolvedPath); |
|
mResolvedPath.SetLength(len); |
|
|
|
return rv; |
|
} |
|
|
|
// Resolve any shortcuts and stat the resolved path. After a successful return |
|
// the path is guaranteed valid and the members of mFileInfo64 can be used. |
|
nsresult |
|
nsLocalFile::ResolveAndStat() |
|
{ |
|
// if we aren't dirty then we are already done |
|
if (!mDirty) { |
|
return NS_OK; |
|
} |
|
|
|
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); |
|
// we can't resolve/stat anything that isn't a valid NSPR addressable path |
|
if (mWorkingPath.IsEmpty()) { |
|
return NS_ERROR_FILE_INVALID_PATH; |
|
} |
|
|
|
// this is usually correct |
|
mResolvedPath.Assign(mWorkingPath); |
|
|
|
// slutty hack designed to work around bug 134796 until it is fixed |
|
nsAutoString nsprPath(mWorkingPath.get()); |
|
if (mWorkingPath.Length() == 2 && mWorkingPath.CharAt(1) == L':') { |
|
nsprPath.Append('\\'); |
|
} |
|
|
|
// first we will see if the working path exists. If it doesn't then |
|
// there is nothing more that can be done |
|
nsresult rv = GetFileInfo(nsprPath, &mFileInfo64); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
// if this isn't a shortcut file or we aren't following symlinks then we're done |
|
if (!mFollowSymlinks || |
|
mFileInfo64.type != PR_FILE_FILE || |
|
!IsShortcutPath(mWorkingPath)) { |
|
mDirty = false; |
|
mResolveDirty = false; |
|
return NS_OK; |
|
} |
|
|
|
// we need to resolve this shortcut to what it points to, this will |
|
// set mResolvedPath. Even if it fails we need to have the resolved |
|
// path equal to working path for those functions that always use |
|
// the resolved path. |
|
rv = ResolveShortcut(); |
|
if (NS_FAILED(rv)) { |
|
mResolvedPath.Assign(mWorkingPath); |
|
return rv; |
|
} |
|
mResolveDirty = false; |
|
|
|
// get the details of the resolved path |
|
rv = GetFileInfo(mResolvedPath, &mFileInfo64); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
mDirty = false; |
|
return NS_OK; |
|
} |
|
|
|
/** |
|
* Fills the mResolvedPath member variable with the file or symlink target |
|
* if follow symlinks is on. This is a copy of the Resolve parts from |
|
* ResolveAndStat. ResolveAndStat is much slower though because of the stat. |
|
* |
|
* @return NS_OK on success. |
|
*/ |
|
nsresult |
|
nsLocalFile::Resolve() |
|
{ |
|
// if we aren't dirty then we are already done |
|
if (!mResolveDirty) { |
|
return NS_OK; |
|
} |
|
|
|
// we can't resolve/stat anything that isn't a valid NSPR addressable path |
|
if (mWorkingPath.IsEmpty()) { |
|
return NS_ERROR_FILE_INVALID_PATH; |
|
} |
|
|
|
// this is usually correct |
|
mResolvedPath.Assign(mWorkingPath); |
|
|
|
// if this isn't a shortcut file or we aren't following symlinks then |
|
// we're done. |
|
if (!mFollowSymlinks || |
|
!IsShortcutPath(mWorkingPath)) { |
|
mResolveDirty = false; |
|
return NS_OK; |
|
} |
|
|
|
// we need to resolve this shortcut to what it points to, this will |
|
// set mResolvedPath. Even if it fails we need to have the resolved |
|
// path equal to working path for those functions that always use |
|
// the resolved path. |
|
nsresult rv = ResolveShortcut(); |
|
if (NS_FAILED(rv)) { |
|
mResolvedPath.Assign(mWorkingPath); |
|
return rv; |
|
} |
|
|
|
mResolveDirty = false; |
|
return NS_OK; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// nsLocalFile::nsIFile,nsILocalFile |
|
//----------------------------------------------------------------------------- |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::Clone(nsIFile** aFile) |
|
{ |
|
// Just copy-construct ourselves |
|
RefPtr<nsLocalFile> file = new nsLocalFile(*this); |
|
file.forget(aFile); |
|
|
|
return NS_OK; |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::InitWithFile(nsIFile* aFile) |
|
{ |
|
if (NS_WARN_IF(!aFile)) { |
|
return NS_ERROR_INVALID_ARG; |
|
} |
|
|
|
nsAutoString path; |
|
aFile->GetPath(path); |
|
if (path.IsEmpty()) { |
|
return NS_ERROR_INVALID_ARG; |
|
} |
|
return InitWithPath(path); |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::InitWithPath(const nsAString& aFilePath) |
|
{ |
|
MakeDirty(); |
|
|
|
nsAString::const_iterator begin, end; |
|
aFilePath.BeginReading(begin); |
|
aFilePath.EndReading(end); |
|
|
|
// input string must not be empty |
|
if (begin == end) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
char16_t firstChar = *begin; |
|
char16_t secondChar = *(++begin); |
|
|
|
// just do a sanity check. if it has any forward slashes, it is not a Native path |
|
// on windows. Also, it must have a colon at after the first char. |
|
if (FindCharInReadable(L'/', begin, end)) { |
|
return NS_ERROR_FILE_UNRECOGNIZED_PATH; |
|
} |
|
|
|
if (FilePreferences::IsBlockedUNCPath(aFilePath)) { |
|
return NS_ERROR_FILE_ACCESS_DENIED; |
|
} |
|
|
|
if (secondChar != L':' && (secondChar != L'\\' || firstChar != L'\\')) { |
|
return NS_ERROR_FILE_UNRECOGNIZED_PATH; |
|
} |
|
|
|
if (secondChar == L':') { |
|
// Make sure we have a valid drive, later code assumes the drive letter |
|
// is a single char a-z or A-Z. |
|
if (PathGetDriveNumberW(aFilePath.Data()) == -1) { |
|
return NS_ERROR_FILE_UNRECOGNIZED_PATH; |
|
} |
|
} |
|
|
|
mWorkingPath = aFilePath; |
|
// kill any trailing '\' |
|
if (mWorkingPath.Last() == L'\\') { |
|
mWorkingPath.Truncate(mWorkingPath.Length() - 1); |
|
} |
|
|
|
return NS_OK; |
|
|
|
} |
|
|
|
// Strip a handler command string of its quotes and parameters. |
|
static void |
|
CleanupHandlerPath(nsString& aPath) |
|
{ |
|
// Example command strings passed into this routine: |
|
|
|
// 1) C:\Program Files\Company\some.exe -foo -bar |
|
// 2) C:\Program Files\Company\some.dll |
|
// 3) C:\Windows\some.dll,-foo -bar |
|
// 4) C:\Windows\some.cpl,-foo -bar |
|
|
|
int32_t lastCommaPos = aPath.RFindChar(','); |
|
if (lastCommaPos != kNotFound) |
|
aPath.Truncate(lastCommaPos); |
|
|
|
aPath.Append(' '); |
|
|
|
// case insensitive |
|
uint32_t index = aPath.Find(".exe ", true); |
|
if (index == kNotFound) |
|
index = aPath.Find(".dll ", true); |
|
if (index == kNotFound) |
|
index = aPath.Find(".cpl ", true); |
|
|
|
if (index != kNotFound) |
|
aPath.Truncate(index + 4); |
|
aPath.Trim(" ", true, true); |
|
} |
|
|
|
// Strip the windows host process bootstrap executable rundll32.exe |
|
// from a handler's command string if it exists. |
|
static void |
|
StripRundll32(nsString& aCommandString) |
|
{ |
|
// Example rundll formats: |
|
// C:\Windows\System32\rundll32.exe "path to dll" |
|
// rundll32.exe "path to dll" |
|
// C:\Windows\System32\rundll32.exe "path to dll", var var |
|
// rundll32.exe "path to dll", var var |
|
|
|
NS_NAMED_LITERAL_STRING(rundllSegment, "rundll32.exe "); |
|
NS_NAMED_LITERAL_STRING(rundllSegmentShort, "rundll32 "); |
|
|
|
// case insensitive |
|
int32_t strLen = rundllSegment.Length(); |
|
int32_t index = aCommandString.Find(rundllSegment, true); |
|
if (index == kNotFound) { |
|
strLen = rundllSegmentShort.Length(); |
|
index = aCommandString.Find(rundllSegmentShort, true); |
|
} |
|
|
|
if (index != kNotFound) { |
|
uint32_t rundllSegmentLength = index + strLen; |
|
aCommandString.Cut(0, rundllSegmentLength); |
|
} |
|
} |
|
|
|
// Returns the fully qualified path to an application handler based on |
|
// a parameterized command string. Note this routine should not be used |
|
// to launch the associated application as it strips parameters and |
|
// rundll.exe from the string. Designed for retrieving display information |
|
// on a particular handler. |
|
/* static */ bool |
|
nsLocalFile::CleanupCmdHandlerPath(nsAString& aCommandHandler) |
|
{ |
|
nsAutoString handlerCommand(aCommandHandler); |
|
|
|
// Straight command path: |
|
// |
|
// %SystemRoot%\system32\NOTEPAD.EXE var |
|
// "C:\Program Files\iTunes\iTunes.exe" var var |
|
// C:\Program Files\iTunes\iTunes.exe var var |
|
// |
|
// Example rundll handlers: |
|
// |
|
// rundll32.exe "%ProgramFiles%\Win...ery\PhotoViewer.dll", var var |
|
// rundll32.exe "%ProgramFiles%\Windows Photo Gallery\PhotoViewer.dll" |
|
// C:\Windows\System32\rundll32.exe "path to dll", var var |
|
// %SystemRoot%\System32\rundll32.exe "%ProgramFiles%\Win...ery\Photo |
|
// Viewer.dll", var var |
|
|
|
// Expand environment variables so we have full path strings. |
|
uint32_t bufLength = ::ExpandEnvironmentStringsW(handlerCommand.get(), |
|
L"", 0); |
|
if (bufLength == 0) // Error |
|
return false; |
|
|
|
auto destination = mozilla::MakeUniqueFallible<wchar_t[]>(bufLength); |
|
if (!destination) |
|
return false; |
|
if (!::ExpandEnvironmentStringsW(handlerCommand.get(), destination.get(), |
|
bufLength)) |
|
return false; |
|
|
|
handlerCommand.Assign(destination.get()); |
|
|
|
// Remove quotes around paths |
|
handlerCommand.StripChars("\""); |
|
|
|
// Strip windows host process bootstrap so we can get to the actual |
|
// handler. |
|
StripRundll32(handlerCommand); |
|
|
|
// Trim any command parameters so that we have a native path we can |
|
// initialize a local file with. |
|
CleanupHandlerPath(handlerCommand); |
|
|
|
aCommandHandler.Assign(handlerCommand); |
|
return true; |
|
} |
|
|
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::InitWithCommandLine(const nsAString& aCommandLine) |
|
{ |
|
nsAutoString commandLine(aCommandLine); |
|
if (!CleanupCmdHandlerPath(commandLine)) { |
|
return NS_ERROR_FILE_UNRECOGNIZED_PATH; |
|
} |
|
return InitWithPath(commandLine); |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode, |
|
PRFileDesc** aResult) |
|
{ |
|
nsresult rv = OpenNSPRFileDescMaybeShareDelete(aFlags, aMode, false, aResult); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
return NS_OK; |
|
} |
|
|
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::OpenANSIFileDesc(const char* aMode, FILE** aResult) |
|
{ |
|
nsresult rv = ResolveAndStat(); |
|
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) { |
|
return rv; |
|
} |
|
|
|
*aResult = _wfopen(mResolvedPath.get(), NS_ConvertASCIItoUTF16(aMode).get()); |
|
if (*aResult) { |
|
return NS_OK; |
|
} |
|
|
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
|
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::Create(uint32_t aType, uint32_t aAttributes) |
|
{ |
|
if (aType != NORMAL_FILE_TYPE && aType != DIRECTORY_TYPE) { |
|
return NS_ERROR_FILE_UNKNOWN_TYPE; |
|
} |
|
|
|
nsresult rv = ResolveAndStat(); |
|
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) { |
|
return rv; |
|
} |
|
|
|
// create directories to target |
|
// |
|
// A given local file can be either one of these forms: |
|
// |
|
// - normal: X:\some\path\on\this\drive |
|
// ^--- start here |
|
// |
|
// - UNC path: \\machine\volume\some\path\on\this\drive |
|
// ^--- start here |
|
// |
|
// Skip the first 'X:\' for the first form, and skip the first full |
|
// '\\machine\volume\' segment for the second form. |
|
|
|
wchar_t* path = wwc(mResolvedPath.BeginWriting()); |
|
|
|
if (path[0] == L'\\' && path[1] == L'\\') { |
|
// dealing with a UNC path here; skip past '\\machine\' |
|
path = wcschr(path + 2, L'\\'); |
|
if (!path) { |
|
return NS_ERROR_FILE_INVALID_PATH; |
|
} |
|
++path; |
|
} |
|
|
|
// search for first slash after the drive (or volume) name |
|
wchar_t* slash = wcschr(path, L'\\'); |
|
|
|
nsresult directoryCreateError = NS_OK; |
|
if (slash) { |
|
// skip the first '\\' |
|
++slash; |
|
slash = wcschr(slash, L'\\'); |
|
|
|
while (slash) { |
|
*slash = L'\0'; |
|
|
|
if (!::CreateDirectoryW(mResolvedPath.get(), nullptr)) { |
|
rv = ConvertWinError(GetLastError()); |
|
if (NS_ERROR_FILE_NOT_FOUND == rv && |
|
NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { |
|
// If a previous CreateDirectory failed due to access, return that. |
|
return NS_ERROR_FILE_ACCESS_DENIED; |
|
} |
|
// perhaps the base path already exists, or perhaps we don't have |
|
// permissions to create the directory. NOTE: access denied could |
|
// occur on a parent directory even though it exists. |
|
else if (rv != NS_ERROR_FILE_ALREADY_EXISTS && |
|
rv != NS_ERROR_FILE_ACCESS_DENIED) { |
|
return rv; |
|
} |
|
|
|
directoryCreateError = rv; |
|
} |
|
*slash = L'\\'; |
|
++slash; |
|
slash = wcschr(slash, L'\\'); |
|
} |
|
} |
|
|
|
if (aType == NORMAL_FILE_TYPE) { |
|
PRFileDesc* file; |
|
rv = OpenFile(mResolvedPath, |
|
PR_RDONLY | PR_CREATE_FILE | PR_APPEND | PR_EXCL, |
|
aAttributes, false, &file); |
|
if (file) { |
|
PR_Close(file); |
|
} |
|
|
|
if (rv == NS_ERROR_FILE_ACCESS_DENIED) { |
|
// need to return already-exists for directories (bug 452217) |
|
bool isdir; |
|
if (NS_SUCCEEDED(IsDirectory(&isdir)) && isdir) { |
|
rv = NS_ERROR_FILE_ALREADY_EXISTS; |
|
} |
|
} else if (NS_ERROR_FILE_NOT_FOUND == rv && |
|
NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { |
|
// If a previous CreateDirectory failed due to access, return that. |
|
return NS_ERROR_FILE_ACCESS_DENIED; |
|
} |
|
return rv; |
|
} |
|
|
|
if (aType == DIRECTORY_TYPE) { |
|
if (!::CreateDirectoryW(mResolvedPath.get(), nullptr)) { |
|
rv = ConvertWinError(GetLastError()); |
|
if (NS_ERROR_FILE_NOT_FOUND == rv && |
|
NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { |
|
// If a previous CreateDirectory failed due to access, return that. |
|
return NS_ERROR_FILE_ACCESS_DENIED; |
|
} |
|
return rv; |
|
} |
|
return NS_OK; |
|
} |
|
|
|
return NS_ERROR_FILE_UNKNOWN_TYPE; |
|
} |
|
|
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::Append(const nsAString& aNode) |
|
{ |
|
// append this path, multiple components are not permitted |
|
return AppendInternal(PromiseFlatString(aNode), false); |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::AppendRelativePath(const nsAString& aNode) |
|
{ |
|
// append this path, multiple components are permitted |
|
return AppendInternal(PromiseFlatString(aNode), true); |
|
} |
|
|
|
|
|
nsresult |
|
nsLocalFile::AppendInternal(const nsAFlatString& aNode, |
|
bool aMultipleComponents) |
|
{ |
|
if (aNode.IsEmpty()) { |
|
return NS_OK; |
|
} |
|
|
|
// check the relative path for validity |
|
if (aNode.First() == L'\\' || // can't start with an '\' |
|
aNode.Contains(L'/') || // can't contain / |
|
aNode.EqualsASCII("..")) { // can't be .. |
|
return NS_ERROR_FILE_UNRECOGNIZED_PATH; |
|
} |
|
|
|
if (aMultipleComponents) { |
|
// can't contain .. as a path component. Ensure that the valid components |
|
// "foo..foo", "..foo", and "foo.." are not falsely detected, |
|
// but the invalid paths "..\", "foo\..", "foo\..\foo", |
|
// "..\foo", etc are. |
|
NS_NAMED_LITERAL_STRING(doubleDot, "\\.."); |
|
nsAString::const_iterator start, end, offset; |
|
aNode.BeginReading(start); |
|
aNode.EndReading(end); |
|
offset = end; |
|
while (FindInReadable(doubleDot, start, offset)) { |
|
if (offset == end || *offset == L'\\') { |
|
return NS_ERROR_FILE_UNRECOGNIZED_PATH; |
|
} |
|
start = offset; |
|
offset = end; |
|
} |
|
|
|
// catches the remaining cases of prefixes |
|
if (StringBeginsWith(aNode, NS_LITERAL_STRING("..\\"))) { |
|
return NS_ERROR_FILE_UNRECOGNIZED_PATH; |
|
} |
|
} |
|
// single components can't contain '\' |
|
else if (aNode.Contains(L'\\')) { |
|
return NS_ERROR_FILE_UNRECOGNIZED_PATH; |
|
} |
|
|
|
MakeDirty(); |
|
|
|
mWorkingPath.Append('\\'); |
|
mWorkingPath.Append(aNode); |
|
|
|
return NS_OK; |
|
} |
|
|
|
nsresult |
|
nsLocalFile::OpenNSPRFileDescMaybeShareDelete(int32_t aFlags, |
|
int32_t aMode, |
|
bool aShareDelete, |
|
PRFileDesc** aResult) |
|
{ |
|
nsresult rv = Resolve(); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
return OpenFile(mResolvedPath, aFlags, aMode, aShareDelete, aResult); |
|
} |
|
|
|
#define TOUPPER(u) (((u) >= L'a' && (u) <= L'z') ? \ |
|
(u) - (L'a' - L'A') : (u)) |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::Normalize() |
|
{ |
|
// XXX See bug 187957 comment 18 for possible problems with this implementation. |
|
|
|
if (mWorkingPath.IsEmpty()) { |
|
return NS_OK; |
|
} |
|
|
|
nsAutoString path(mWorkingPath); |
|
|
|
// find the index of the root backslash for the path. Everything before |
|
// this is considered fully normalized and cannot be ascended beyond |
|
// using ".." For a local drive this is the first slash (e.g. "c:\"). |
|
// For a UNC path it is the slash following the share name |
|
// (e.g. "\\server\share\"). |
|
int32_t rootIdx = 2; // default to local drive |
|
if (path.First() == L'\\') { // if a share then calculate the rootIdx |
|
rootIdx = path.FindChar(L'\\', 2); // skip \\ in front of the server |
|
if (rootIdx == kNotFound) { |
|
return NS_OK; // already normalized |
|
} |
|
rootIdx = path.FindChar(L'\\', rootIdx + 1); |
|
if (rootIdx == kNotFound) { |
|
return NS_OK; // already normalized |
|
} |
|
} else if (path.CharAt(rootIdx) != L'\\') { |
|
// The path has been specified relative to the current working directory |
|
// for that drive. To normalize it, the current working directory for |
|
// that drive needs to be inserted before the supplied relative path |
|
// which will provide an absolute path (and the rootIdx will still be 2). |
|
WCHAR cwd[MAX_PATH]; |
|
WCHAR* pcwd = cwd; |
|
int drive = TOUPPER(path.First()) - 'A' + 1; |
|
/* We need to worry about IPH, for details read bug 419326. |
|
* _getdrives - http://msdn2.microsoft.com/en-us/library/xdhk0xd2.aspx |
|
* uses a bitmask, bit 0 is 'a:' |
|
* _chdrive - http://msdn2.microsoft.com/en-us/library/0d1409hb.aspx |
|
* _getdcwd - http://msdn2.microsoft.com/en-us/library/7t2zk3s4.aspx |
|
* take an int, 1 is 'a:'. |
|
* |
|
* Because of this, we need to do some math. Subtract 1 to convert from |
|
* _chdrive/_getdcwd format to _getdrives drive numbering. |
|
* Shift left x bits to convert from integer indexing to bitfield indexing. |
|
* And of course, we need to find out if the drive is in the bitmask. |
|
* |
|
* If we're really unlucky, we can still lose, but only if the user |
|
* manages to eject the drive between our call to _getdrives() and |
|
* our *calls* to _wgetdcwd. |
|
*/ |
|
if (!((1 << (drive - 1)) & _getdrives())) { |
|
return NS_ERROR_FILE_INVALID_PATH; |
|
} |
|
if (!_wgetdcwd(drive, pcwd, MAX_PATH)) { |
|
pcwd = _wgetdcwd(drive, 0, 0); |
|
} |
|
if (!pcwd) { |
|
return NS_ERROR_OUT_OF_MEMORY; |
|
} |
|
nsAutoString currentDir(pcwd); |
|
if (pcwd != cwd) { |
|
free(pcwd); |
|
} |
|
|
|
if (currentDir.Last() == '\\') { |
|
path.Replace(0, 2, currentDir); |
|
} else { |
|
path.Replace(0, 2, currentDir + NS_LITERAL_STRING("\\")); |
|
} |
|
} |
|
NS_POSTCONDITION(0 < rootIdx && rootIdx < (int32_t)path.Length(), "rootIdx is invalid"); |
|
NS_POSTCONDITION(path.CharAt(rootIdx) == '\\', "rootIdx is invalid"); |
|
|
|
// if there is nothing following the root path then it is already normalized |
|
if (rootIdx + 1 == (int32_t)path.Length()) { |
|
return NS_OK; |
|
} |
|
|
|
// assign the root |
|
const char16_t* pathBuffer = path.get(); // simplify access to the buffer |
|
mWorkingPath.SetCapacity(path.Length()); // it won't ever grow longer |
|
mWorkingPath.Assign(pathBuffer, rootIdx); |
|
|
|
// Normalize the path components. The actions taken are: |
|
// |
|
// "\\" condense to single backslash |
|
// "." remove from path |
|
// ".." up a directory |
|
// "..." remove from path (any number of dots > 2) |
|
// |
|
// The last form is something that Windows 95 and 98 supported and |
|
// is a shortcut for changing up multiple directories. Windows XP |
|
// and ilk ignore it in a path, as is done here. |
|
int32_t len, begin, end = rootIdx; |
|
while (end < (int32_t)path.Length()) { |
|
// find the current segment (text between the backslashes) to |
|
// be examined, this will set the following variables: |
|
// begin == index of first char in segment |
|
// end == index 1 char after last char in segment |
|
// len == length of segment |
|
begin = end + 1; |
|
end = path.FindChar('\\', begin); |
|
if (end == kNotFound) { |
|
end = path.Length(); |
|
} |
|
len = end - begin; |
|
|
|
// ignore double backslashes |
|
if (len == 0) { |
|
continue; |
|
} |
|
|
|
// len != 0, and interesting paths always begin with a dot |
|
if (pathBuffer[begin] == '.') { |
|
// ignore single dots |
|
if (len == 1) { |
|
continue; |
|
} |
|
|
|
// handle multiple dots |
|
if (len >= 2 && pathBuffer[begin + 1] == L'.') { |
|
// back up a path component on double dot |
|
if (len == 2) { |
|
int32_t prev = mWorkingPath.RFindChar('\\'); |
|
if (prev >= rootIdx) { |
|
mWorkingPath.Truncate(prev); |
|
} |
|
continue; |
|
} |
|
|
|
// length is > 2 and the first two characters are dots. |
|
// if the rest of the string is dots, then ignore it. |
|
int idx = len - 1; |
|
for (; idx >= 2; --idx) { |
|
if (pathBuffer[begin + idx] != L'.') { |
|
break; |
|
} |
|
} |
|
|
|
// this is true if the loop above didn't break |
|
// and all characters in this segment are dots. |
|
if (idx < 2) { |
|
continue; |
|
} |
|
} |
|
} |
|
|
|
// add the current component to the path, including the preceding backslash |
|
mWorkingPath.Append(pathBuffer + begin - 1, len + 1); |
|
} |
|
|
|
// kill trailing dots and spaces. |
|
int32_t filePathLen = mWorkingPath.Length() - 1; |
|
while (filePathLen > 0 && (mWorkingPath[filePathLen] == L' ' || |
|
mWorkingPath[filePathLen] == L'.')) { |
|
mWorkingPath.Truncate(filePathLen--); |
|
} |
|
|
|
MakeDirty(); |
|
return NS_OK; |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::GetLeafName(nsAString& aLeafName) |
|
{ |
|
aLeafName.Truncate(); |
|
|
|
if (mWorkingPath.IsEmpty()) { |
|
return NS_ERROR_FILE_UNRECOGNIZED_PATH; |
|
} |
|
|
|
int32_t offset = mWorkingPath.RFindChar(L'\\'); |
|
|
|
// if the working path is just a node without any lashes. |
|
if (offset == kNotFound) { |
|
aLeafName = mWorkingPath; |
|
} else { |
|
aLeafName = Substring(mWorkingPath, offset + 1); |
|
} |
|
|
|
return NS_OK; |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::SetLeafName(const nsAString& aLeafName) |
|
{ |
|
MakeDirty(); |
|
|
|
if (mWorkingPath.IsEmpty()) { |
|
return NS_ERROR_FILE_UNRECOGNIZED_PATH; |
|
} |
|
|
|
// cannot use nsCString::RFindChar() due to 0x5c problem |
|
int32_t offset = mWorkingPath.RFindChar(L'\\'); |
|
if (offset) { |
|
mWorkingPath.Truncate(offset + 1); |
|
} |
|
mWorkingPath.Append(aLeafName); |
|
|
|
return NS_OK; |
|
} |
|
|
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::GetPath(nsAString& aResult) |
|
{ |
|
aResult = mWorkingPath; |
|
return NS_OK; |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::GetCanonicalPath(nsAString& aResult) |
|
{ |
|
EnsureShortPath(); |
|
aResult.Assign(mShortWorkingPath); |
|
return NS_OK; |
|
} |
|
|
|
typedef struct |
|
{ |
|
WORD wLanguage; |
|
WORD wCodePage; |
|
} LANGANDCODEPAGE; |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::GetVersionInfoField(const char* aField, nsAString& aResult) |
|
{ |
|
nsresult rv = ResolveAndStat(); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
rv = NS_ERROR_FAILURE; |
|
|
|
const WCHAR* path = |
|
mFollowSymlinks ? mResolvedPath.get() : mWorkingPath.get(); |
|
|
|
DWORD dummy; |
|
DWORD size = ::GetFileVersionInfoSizeW(path, &dummy); |
|
if (!size) { |
|
return rv; |
|
} |
|
|
|
void* ver = moz_xcalloc(size, 1); |
|
if (::GetFileVersionInfoW(path, 0, size, ver)) { |
|
LANGANDCODEPAGE* translate = nullptr; |
|
UINT pageCount; |
|
BOOL queryResult = ::VerQueryValueW(ver, L"\\VarFileInfo\\Translation", |
|
(void**)&translate, &pageCount); |
|
if (queryResult && translate) { |
|
for (int32_t i = 0; i < 2; ++i) { |
|
wchar_t subBlock[MAX_PATH]; |
|
_snwprintf(subBlock, MAX_PATH, |
|
L"\\StringFileInfo\\%04x%04x\\%s", |
|
(i == 0 ? translate[0].wLanguage : ::GetUserDefaultLangID()), |
|
translate[0].wCodePage, |
|
NS_ConvertASCIItoUTF16( |
|
nsDependentCString(aField)).get()); |
|
subBlock[MAX_PATH - 1] = 0; |
|
LPVOID value = nullptr; |
|
UINT size; |
|
queryResult = ::VerQueryValueW(ver, subBlock, &value, &size); |
|
if (queryResult && value) { |
|
aResult.Assign(static_cast<char16_t*>(value)); |
|
if (!aResult.IsEmpty()) { |
|
rv = NS_OK; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
free(ver); |
|
|
|
return rv; |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::SetShortcut(nsIFile* aTargetFile, |
|
nsIFile* aWorkingDir, |
|
const char16_t* aArgs, |
|
const char16_t* aDescription, |
|
nsIFile* aIconFile, |
|
int32_t aIconIndex) |
|
{ |
|
bool exists; |
|
nsresult rv = this->Exists(&exists); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
const WCHAR* targetFilePath = nullptr; |
|
const WCHAR* workingDirPath = nullptr; |
|
const WCHAR* iconFilePath = nullptr; |
|
|
|
nsAutoString targetFilePathAuto; |
|
if (aTargetFile) { |
|
rv = aTargetFile->GetPath(targetFilePathAuto); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
targetFilePath = targetFilePathAuto.get(); |
|
} |
|
|
|
nsAutoString workingDirPathAuto; |
|
if (aWorkingDir) { |
|
rv = aWorkingDir->GetPath(workingDirPathAuto); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
workingDirPath = workingDirPathAuto.get(); |
|
} |
|
|
|
nsAutoString iconPathAuto; |
|
if (aIconFile) { |
|
rv = aIconFile->GetPath(iconPathAuto); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
iconFilePath = iconPathAuto.get(); |
|
} |
|
|
|
rv = gResolver->SetShortcut(exists, |
|
mWorkingPath.get(), |
|
targetFilePath, |
|
workingDirPath, |
|
char16ptr_t(aArgs), |
|
char16ptr_t(aDescription), |
|
iconFilePath, |
|
iconFilePath ? aIconIndex : 0); |
|
if (targetFilePath && NS_SUCCEEDED(rv)) { |
|
MakeDirty(); |
|
} |
|
|
|
return rv; |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::OpenNSPRFileDescShareDelete(int32_t aFlags, |
|
int32_t aMode, |
|
PRFileDesc** aResult) |
|
{ |
|
nsresult rv = OpenNSPRFileDescMaybeShareDelete(aFlags, aMode, true, aResult); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
return NS_OK; |
|
} |
|
|
|
/** |
|
* Determines if the drive type for the specified file is rmeote or local. |
|
* |
|
* @param path The path of the file to check |
|
* @param remote Out parameter, on function success holds true if the specified |
|
* file path is remote, or false if the file path is local. |
|
* @return true on success. The return value implies absolutely nothing about |
|
* wether the file is local or remote. |
|
*/ |
|
static bool |
|
IsRemoteFilePath(LPCWSTR aPath, bool& aRemote) |
|
{ |
|
// Obtain the parent directory path and make sure it ends with |
|
// a trailing backslash. |
|
WCHAR dirPath[MAX_PATH + 1] = { 0 }; |
|
wcsncpy(dirPath, aPath, MAX_PATH); |
|
if (!PathRemoveFileSpecW(dirPath)) { |
|
return false; |
|
} |
|
size_t len = wcslen(dirPath); |
|
// In case the dirPath holds exaclty MAX_PATH and remains unchanged, we |
|
// recheck the required length here since we need to terminate it with |
|
// a backslash. |
|
if (len >= MAX_PATH) { |
|
return false; |
|
} |
|
|
|
dirPath[len] = L'\\'; |
|
dirPath[len + 1] = L'\0'; |
|
UINT driveType = GetDriveTypeW(dirPath); |
|
aRemote = driveType == DRIVE_REMOTE; |
|
return true; |
|
} |
|
|
|
nsresult |
|
nsLocalFile::CopySingleFile(nsIFile* aSourceFile, nsIFile* aDestParent, |
|
const nsAString& aNewName, uint32_t aOptions) |
|
{ |
|
nsresult rv = NS_OK; |
|
nsAutoString filePath; |
|
|
|
bool move = aOptions & (Move | Rename); |
|
|
|
// get the path that we are going to copy to. |
|
// Since windows does not know how to auto |
|
// resolve shortcuts, we must work with the |
|
// target. |
|
nsAutoString destPath; |
|
aDestParent->GetTarget(destPath); |
|
|
|
destPath.Append('\\'); |
|
|
|
if (aNewName.IsEmpty()) { |
|
nsAutoString aFileName; |
|
aSourceFile->GetLeafName(aFileName); |
|
destPath.Append(aFileName); |
|
} else { |
|
destPath.Append(aNewName); |
|
} |
|
|
|
|
|
if (aOptions & FollowSymlinks) { |
|
rv = aSourceFile->GetTarget(filePath); |
|
if (filePath.IsEmpty()) { |
|
rv = aSourceFile->GetPath(filePath); |
|
} |
|
} else { |
|
rv = aSourceFile->GetPath(filePath); |
|
} |
|
|
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
// Pass the flag COPY_FILE_NO_BUFFERING to CopyFileEx as we may be copying |
|
// to a SMBV2 remote drive. Without this parameter subsequent append mode |
|
// file writes can cause the resultant file to become corrupt. We only need to do |
|
// this if the major version of Windows is > 5(Only Windows Vista and above |
|
// can support SMBV2). With a 7200RPM hard drive: |
|
// Copying a 1KB file with COPY_FILE_NO_BUFFERING takes about 30-60ms. |
|
// Copying a 1KB file without COPY_FILE_NO_BUFFERING takes < 1ms. |
|
// So we only use COPY_FILE_NO_BUFFERING when we have a remote drive. |
|
int copyOK; |
|
DWORD dwCopyFlags = COPY_FILE_ALLOW_DECRYPTED_DESTINATION; |
|
if (IsVistaOrLater()) { |
|
bool path1Remote, path2Remote; |
|
if (!IsRemoteFilePath(filePath.get(), path1Remote) || |
|
!IsRemoteFilePath(destPath.get(), path2Remote) || |
|
path1Remote || path2Remote) { |
|
dwCopyFlags |= COPY_FILE_NO_BUFFERING; |
|
} |
|
} |
|
|
|
if (FilePreferences::IsBlockedUNCPath(destPath)) { |
|
return NS_ERROR_FILE_ACCESS_DENIED; |
|
} |
|
|
|
if (!move) { |
|
copyOK = ::CopyFileExW(filePath.get(), destPath.get(), nullptr, |
|
nullptr, nullptr, dwCopyFlags); |
|
} else { |
|
copyOK = ::MoveFileExW(filePath.get(), destPath.get(), |
|
MOVEFILE_REPLACE_EXISTING); |
|
|
|
// Check if copying the source file to a different volume, |
|
// as this could be an SMBV2 mapped drive. |
|
if (!copyOK && GetLastError() == ERROR_NOT_SAME_DEVICE) { |
|
if (aOptions & Rename) { |
|
return NS_ERROR_FILE_ACCESS_DENIED; |
|
} |
|
copyOK = CopyFileExW(filePath.get(), destPath.get(), nullptr, |
|
nullptr, nullptr, dwCopyFlags); |
|
|
|
if (copyOK) { |
|
DeleteFileW(filePath.get()); |
|
} |
|
} |
|
} |
|
|
|
if (!copyOK) { // CopyFileEx and MoveFileEx return zero at failure. |
|
rv = ConvertWinError(GetLastError()); |
|
} else if (move && !(aOptions & SkipNtfsAclReset)) { |
|
// Set security permissions to inherit from parent. |
|
// Note: propagates to all children: slow for big file trees |
|
PACL pOldDACL = nullptr; |
|
PSECURITY_DESCRIPTOR pSD = nullptr; |
|
::GetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT, |
|
DACL_SECURITY_INFORMATION, |
|
nullptr, nullptr, &pOldDACL, nullptr, &pSD); |
|
if (pOldDACL) |
|
::SetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT, |
|
DACL_SECURITY_INFORMATION | |
|
UNPROTECTED_DACL_SECURITY_INFORMATION, |
|
nullptr, nullptr, pOldDACL, nullptr); |
|
if (pSD) { |
|
LocalFree((HLOCAL)pSD); |
|
} |
|
} |
|
|
|
return rv; |
|
} |
|
|
|
nsresult |
|
nsLocalFile::CopyMove(nsIFile* aParentDir, const nsAString& aNewName, |
|
uint32_t aOptions) |
|
{ |
|
bool move = aOptions & (Move | Rename); |
|
bool followSymlinks = aOptions & FollowSymlinks; |
|
|
|
nsCOMPtr<nsIFile> newParentDir = aParentDir; |
|
// check to see if this exists, otherwise return an error. |
|
// we will check this by resolving. If the user wants us |
|
// to follow links, then we are talking about the target, |
|
// hence we can use the |FollowSymlinks| option. |
|
nsresult rv = ResolveAndStat(); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
if (!newParentDir) { |
|
// no parent was specified. We must rename. |
|
if (aNewName.IsEmpty()) { |
|
return NS_ERROR_INVALID_ARG; |
|
} |
|
|
|
rv = GetParent(getter_AddRefs(newParentDir)); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
} |
|
|
|
if (!newParentDir) { |
|
return NS_ERROR_FILE_DESTINATION_NOT_DIR; |
|
} |
|
|
|
// make sure it exists and is a directory. Create it if not there. |
|
bool exists; |
|
newParentDir->Exists(&exists); |
|
if (!exists) { |
|
rv = newParentDir->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
} else { |
|
bool isDir; |
|
newParentDir->IsDirectory(&isDir); |
|
if (!isDir) { |
|
if (followSymlinks) { |
|
bool isLink; |
|
newParentDir->IsSymlink(&isLink); |
|
if (isLink) { |
|
nsAutoString target; |
|
newParentDir->GetTarget(target); |
|
|
|
nsCOMPtr<nsIFile> realDest = new nsLocalFile(); |
|
rv = realDest->InitWithPath(target); |
|
|
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
return CopyMove(realDest, aNewName, aOptions); |
|
} |
|
} else { |
|
return NS_ERROR_FILE_DESTINATION_NOT_DIR; |
|
} |
|
} |
|
} |
|
|
|
// Try different ways to move/copy files/directories |
|
bool done = false; |
|
bool isDir; |
|
IsDirectory(&isDir); |
|
bool isSymlink; |
|
IsSymlink(&isSymlink); |
|
|
|
// Try to move the file or directory, or try to copy a single file (or non-followed symlink) |
|
if (move || !isDir || (isSymlink && !followSymlinks)) { |
|
// Copy/Move single file, or move a directory |
|
if (!aParentDir) { |
|
aOptions |= SkipNtfsAclReset; |
|
} |
|
rv = CopySingleFile(this, newParentDir, aNewName, aOptions); |
|
done = NS_SUCCEEDED(rv); |
|
// If we are moving a directory and that fails, fallback on directory |
|
// enumeration. See bug 231300 for details. |
|
if (!done && !(move && isDir)) { |
|
return rv; |
|
} |
|
} |
|
|
|
// Not able to copy or move directly, so enumerate it |
|
if (!done) { |
|
// create a new target destination in the new parentDir; |
|
nsCOMPtr<nsIFile> target; |
|
rv = newParentDir->Clone(getter_AddRefs(target)); |
|
|
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
nsAutoString allocatedNewName; |
|
if (aNewName.IsEmpty()) { |
|
bool isLink; |
|
IsSymlink(&isLink); |
|
if (isLink) { |
|
nsAutoString temp; |
|
GetTarget(temp); |
|
int32_t offset = temp.RFindChar(L'\\'); |
|
if (offset == kNotFound) { |
|
allocatedNewName = temp; |
|
} else { |
|
allocatedNewName = Substring(temp, offset + 1); |
|
} |
|
} else { |
|
GetLeafName(allocatedNewName);// this should be the leaf name of the |
|
} |
|
} else { |
|
allocatedNewName = aNewName; |
|
} |
|
|
|
rv = target->Append(allocatedNewName); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
allocatedNewName.Truncate(); |
|
|
|
// check if the destination directory already exists |
|
target->Exists(&exists); |
|
if (!exists) { |
|
// if the destination directory cannot be created, return an error |
|
rv = target->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
} else { |
|
// check if the destination directory is writable and empty |
|
bool isWritable; |
|
|
|
target->IsWritable(&isWritable); |
|
if (!isWritable) { |
|
return NS_ERROR_FILE_ACCESS_DENIED; |
|
} |
|
|
|
nsCOMPtr<nsISimpleEnumerator> targetIterator; |
|
rv = target->GetDirectoryEntries(getter_AddRefs(targetIterator)); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
bool more; |
|
targetIterator->HasMoreElements(&more); |
|
// return error if target directory is not empty |
|
if (more) { |
|
return NS_ERROR_FILE_DIR_NOT_EMPTY; |
|
} |
|
} |
|
|
|
RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator(); |
|
|
|
rv = dirEnum->Init(this); |
|
if (NS_FAILED(rv)) { |
|
NS_WARNING("dirEnum initialization failed"); |
|
return rv; |
|
} |
|
|
|
bool more = false; |
|
while (NS_SUCCEEDED(dirEnum->HasMoreElements(&more)) && more) { |
|
nsCOMPtr<nsISupports> item; |
|
nsCOMPtr<nsIFile> file; |
|
dirEnum->GetNext(getter_AddRefs(item)); |
|
file = do_QueryInterface(item); |
|
if (file) { |
|
bool isDir, isLink; |
|
|
|
file->IsDirectory(&isDir); |
|
file->IsSymlink(&isLink); |
|
|
|
if (move) { |
|
if (followSymlinks) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
rv = file->MoveTo(target, EmptyString()); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
} else { |
|
if (followSymlinks) { |
|
rv = file->CopyToFollowingLinks(target, EmptyString()); |
|
} else { |
|
rv = file->CopyTo(target, EmptyString()); |
|
} |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
} |
|
} |
|
} |
|
// we've finished moving all the children of this directory |
|
// in the new directory. so now delete the directory |
|
// note, we don't need to do a recursive delete. |
|
// MoveTo() is recursive. At this point, |
|
// we've already moved the children of the current folder |
|
// to the new location. nothing should be left in the folder. |
|
if (move) { |
|
rv = Remove(false /* recursive */); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
} |
|
} |
|
|
|
|
|
// If we moved, we want to adjust this. |
|
if (move) { |
|
MakeDirty(); |
|
|
|
nsAutoString newParentPath; |
|
newParentDir->GetPath(newParentPath); |
|
|
|
if (newParentPath.IsEmpty()) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
if (aNewName.IsEmpty()) { |
|
nsAutoString aFileName; |
|
GetLeafName(aFileName); |
|
|
|
InitWithPath(newParentPath); |
|
Append(aFileName); |
|
} else { |
|
InitWithPath(newParentPath); |
|
Append(aNewName); |
|
} |
|
} |
|
|
|
return NS_OK; |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::CopyTo(nsIFile* aNewParentDir, const nsAString& aNewName) |
|
{ |
|
return CopyMove(aNewParentDir, aNewName, 0); |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::CopyToFollowingLinks(nsIFile* aNewParentDir, |
|
const nsAString& aNewName) |
|
{ |
|
return CopyMove(aNewParentDir, aNewName, FollowSymlinks); |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::MoveTo(nsIFile* aNewParentDir, const nsAString& aNewName) |
|
{ |
|
return CopyMove(aNewParentDir, aNewName, Move); |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::RenameTo(nsIFile* aNewParentDir, const nsAString& aNewName) |
|
{ |
|
nsCOMPtr<nsIFile> targetParentDir = aNewParentDir; |
|
// check to see if this exists, otherwise return an error. |
|
// we will check this by resolving. If the user wants us |
|
// to follow links, then we are talking about the target, |
|
// hence we can use the |followSymlinks| parameter. |
|
nsresult rv = ResolveAndStat(); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
if (!targetParentDir) { |
|
// no parent was specified. We must rename. |
|
if (aNewName.IsEmpty()) { |
|
return NS_ERROR_INVALID_ARG; |
|
} |
|
rv = GetParent(getter_AddRefs(targetParentDir)); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
} |
|
|
|
if (!targetParentDir) { |
|
return NS_ERROR_FILE_DESTINATION_NOT_DIR; |
|
} |
|
|
|
// make sure it exists and is a directory. Create it if not there. |
|
bool exists; |
|
targetParentDir->Exists(&exists); |
|
if (!exists) { |
|
rv = targetParentDir->Create(DIRECTORY_TYPE, 0644); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
} else { |
|
bool isDir; |
|
targetParentDir->IsDirectory(&isDir); |
|
if (!isDir) { |
|
return NS_ERROR_FILE_DESTINATION_NOT_DIR; |
|
} |
|
} |
|
|
|
uint32_t options = Rename; |
|
if (!aNewParentDir) { |
|
options |= SkipNtfsAclReset; |
|
} |
|
// Move single file, or move a directory |
|
return CopySingleFile(this, targetParentDir, aNewName, options); |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::RenameToNative(nsIFile* aNewParentDir, const nsACString& aNewName) |
|
{ |
|
nsAutoString tmp; |
|
nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp); |
|
if (NS_SUCCEEDED(rv)) { |
|
return RenameTo(aNewParentDir, tmp); |
|
} |
|
|
|
return rv; |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::Load(PRLibrary** aResult) |
|
{ |
|
// Check we are correctly initialized. |
|
CHECK_mWorkingPath(); |
|
|
|
bool isFile; |
|
nsresult rv = IsFile(&isFile); |
|
|
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
if (!isFile) { |
|
return NS_ERROR_FILE_IS_DIRECTORY; |
|
} |
|
|
|
#ifdef NS_BUILD_REFCNT_LOGGING |
|
nsTraceRefcnt::SetActivityIsLegal(false); |
|
#endif |
|
|
|
PRLibSpec libSpec; |
|
libSpec.value.pathname_u = mResolvedPath.get(); |
|
libSpec.type = PR_LibSpec_PathnameU; |
|
*aResult = PR_LoadLibraryWithFlags(libSpec, 0); |
|
|
|
#ifdef NS_BUILD_REFCNT_LOGGING |
|
nsTraceRefcnt::SetActivityIsLegal(true); |
|
#endif |
|
|
|
if (*aResult) { |
|
return NS_OK; |
|
} |
|
return NS_ERROR_NULL_POINTER; |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::Remove(bool aRecursive) |
|
{ |
|
// NOTE: |
|
// |
|
// if the working path points to a shortcut, then we will only |
|
// delete the shortcut itself. even if the shortcut points to |
|
// a directory, we will not recurse into that directory or |
|
// delete that directory itself. likewise, if the shortcut |
|
// points to a normal file, we will not delete the real file. |
|
// this is done to be consistent with the other platforms that |
|
// behave this way. we do this even if the followLinks attribute |
|
// is set to true. this helps protect against misuse that could |
|
// lead to security bugs (e.g., bug 210588). |
|
// |
|
// Since shortcut files are no longer permitted to be used as unix-like |
|
// symlinks interspersed in the path (e.g. "c:/file.lnk/foo/bar.txt") |
|
// this processing is a lot simpler. Even if the shortcut file is |
|
// pointing to a directory, only the mWorkingPath value is used and so |
|
// only the shortcut file will be deleted. |
|
|
|
// Check we are correctly initialized. |
|
CHECK_mWorkingPath(); |
|
|
|
bool isDir, isLink; |
|
nsresult rv; |
|
|
|
isDir = false; |
|
rv = IsSymlink(&isLink); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
// only check to see if we have a directory if it isn't a link |
|
if (!isLink) { |
|
rv = IsDirectory(&isDir); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
} |
|
|
|
if (isDir) { |
|
if (aRecursive) { |
|
RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator(); |
|
|
|
rv = dirEnum->Init(this); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
bool more = false; |
|
while (NS_SUCCEEDED(dirEnum->HasMoreElements(&more)) && more) { |
|
nsCOMPtr<nsISupports> item; |
|
dirEnum->GetNext(getter_AddRefs(item)); |
|
nsCOMPtr<nsIFile> file = do_QueryInterface(item); |
|
if (file) { |
|
file->Remove(aRecursive); |
|
} |
|
} |
|
} |
|
if (RemoveDirectoryW(mWorkingPath.get()) == 0) { |
|
return ConvertWinError(GetLastError()); |
|
} |
|
} else { |
|
if (DeleteFileW(mWorkingPath.get()) == 0) { |
|
return ConvertWinError(GetLastError()); |
|
} |
|
} |
|
|
|
MakeDirty(); |
|
return rv; |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::GetLastModifiedTime(PRTime* aLastModifiedTime) |
|
{ |
|
// Check we are correctly initialized. |
|
CHECK_mWorkingPath(); |
|
|
|
if (NS_WARN_IF(!aLastModifiedTime)) { |
|
return NS_ERROR_INVALID_ARG; |
|
} |
|
|
|
// get the modified time of the target as determined by mFollowSymlinks |
|
// If true, then this will be for the target of the shortcut file, |
|
// otherwise it will be for the shortcut file itself (i.e. the same |
|
// results as GetLastModifiedTimeOfLink) |
|
|
|
nsresult rv = ResolveAndStat(); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
// microseconds -> milliseconds |
|
*aLastModifiedTime = mFileInfo64.modifyTime / PR_USEC_PER_MSEC; |
|
return NS_OK; |
|
} |
|
|
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModifiedTime) |
|
{ |
|
// Check we are correctly initialized. |
|
CHECK_mWorkingPath(); |
|
|
|
if (NS_WARN_IF(!aLastModifiedTime)) { |
|
return NS_ERROR_INVALID_ARG; |
|
} |
|
|
|
// The caller is assumed to have already called IsSymlink |
|
// and to have found that this file is a link. |
|
|
|
PRFileInfo64 info; |
|
nsresult rv = GetFileInfo(mWorkingPath, &info); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
// microseconds -> milliseconds |
|
*aLastModifiedTime = info.modifyTime / PR_USEC_PER_MSEC; |
|
return NS_OK; |
|
} |
|
|
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::SetLastModifiedTime(PRTime aLastModifiedTime) |
|
{ |
|
// Check we are correctly initialized. |
|
CHECK_mWorkingPath(); |
|
|
|
nsresult rv = ResolveAndStat(); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
// set the modified time of the target as determined by mFollowSymlinks |
|
// If true, then this will be for the target of the shortcut file, |
|
// otherwise it will be for the shortcut file itself (i.e. the same |
|
// results as SetLastModifiedTimeOfLink) |
|
|
|
rv = SetModDate(aLastModifiedTime, mResolvedPath.get()); |
|
if (NS_SUCCEEDED(rv)) { |
|
MakeDirty(); |
|
} |
|
|
|
return rv; |
|
} |
|
|
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModifiedTime) |
|
{ |
|
// The caller is assumed to have already called IsSymlink |
|
// and to have found that this file is a link. |
|
|
|
nsresult rv = SetModDate(aLastModifiedTime, mWorkingPath.get()); |
|
if (NS_SUCCEEDED(rv)) { |
|
MakeDirty(); |
|
} |
|
|
|
return rv; |
|
} |
|
|
|
nsresult |
|
nsLocalFile::SetModDate(PRTime aLastModifiedTime, const wchar_t* aFilePath) |
|
{ |
|
// The FILE_FLAG_BACKUP_SEMANTICS is required in order to change the |
|
// modification time for directories. |
|
HANDLE file = ::CreateFileW(aFilePath, // pointer to name of the file |
|
GENERIC_WRITE, // access (write) mode |
|
0, // share mode |
|
nullptr, // pointer to security attributes |
|
OPEN_EXISTING, // how to create |
|
FILE_FLAG_BACKUP_SEMANTICS, // file attributes |
|
nullptr); |
|
|
|
if (file == INVALID_HANDLE_VALUE) { |
|
return ConvertWinError(GetLastError()); |
|
} |
|
|
|
FILETIME ft; |
|
SYSTEMTIME st; |
|
PRExplodedTime pret; |
|
|
|
// PR_ExplodeTime expects usecs... |
|
PR_ExplodeTime(aLastModifiedTime * PR_USEC_PER_MSEC, PR_GMTParameters, &pret); |
|
st.wYear = pret.tm_year; |
|
st.wMonth = pret.tm_month + 1; // Convert start offset -- Win32: Jan=1; NSPR: Jan=0 |
|
st.wDayOfWeek = pret.tm_wday; |
|
st.wDay = pret.tm_mday; |
|
st.wHour = pret.tm_hour; |
|
st.wMinute = pret.tm_min; |
|
st.wSecond = pret.tm_sec; |
|
st.wMilliseconds = pret.tm_usec / 1000; |
|
|
|
nsresult rv = NS_OK; |
|
// if at least one of these fails... |
|
if (!(SystemTimeToFileTime(&st, &ft) != 0 && |
|
SetFileTime(file, nullptr, &ft, &ft) != 0)) { |
|
rv = ConvertWinError(GetLastError()); |
|
} |
|
|
|
CloseHandle(file); |
|
return rv; |
|
} |
|
|
|
NS_IMETHODIMP |
|
nsLocalFile::GetPermissions(uint32_t* aPermissions) |
|
{ |
|
if (NS_WARN_IF(!aPermissions)) { |
|
return NS_ERROR_INVALID_ARG; |
|
} |
|
|
|
// get the permissions of the target as determined by mFollowSymlinks |
|
// If true, then this will be for the target of the shortcut file, |
|
// otherwise it will be for the shortcut file itself (i.e. the same |
|
// results as GetPermissionsOfLink) |
|
nsresult rv = ResolveAndStat(); |
|
if (NS_FAILED(rv)) { |
|
return rv; |
|
} |
|
|
|
bool isWritable, isExecutable; |
|
IsWritable(&isWritable); |
|
IsExecutable(&isExecutable); |
|