pjs/xpcom/io/nsLocalFileWin.cpp

2487 строки
68 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Communicator client code, released
* March 31, 1998.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Doug Turner <dougt@netscape.com>
* Dean Tessman <dean_tessman@hotmail.com>
* Brodie Thiesfield <brofield@jellycan.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsCOMPtr.h"
#include "nsMemory.h"
#include "nsLocalFile.h"
#include "nsNativeCharsetUtils.h"
#include "nsISimpleEnumerator.h"
#include "nsIComponentManager.h"
#include "prtypes.h"
#include "prio.h"
#include "nsXPIDLString.h"
#include "nsReadableUtils.h"
#include <direct.h>
#include <windows.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 "nsITimelineService.h"
#include "nsAutoLock.h"
#include "SpecialSystemDirectory.h"
// _mbsstr isn't declared in w32api headers but it's there in the libs
#ifdef __MINGW32__
extern "C" {
unsigned char *_mbsstr( const unsigned char *str,
const unsigned char *substr );
}
#endif
class nsDriveEnumerator : public nsISimpleEnumerator
{
public:
nsDriveEnumerator();
virtual ~nsDriveEnumerator();
NS_DECL_ISUPPORTS
NS_DECL_NSISIMPLEENUMERATOR
nsresult Init();
private:
/* mDrives and mLetter share data
* Init sets them.
* HasMoreElements reads mLetter.
* GetNext advances mLetter.
*/
nsCString mDrives;
const char *mLetter;
};
//----------------------------------------------------------------------------
// short cut resolver
//----------------------------------------------------------------------------
#ifndef WINCE
class ShortcutResolver
{
public:
ShortcutResolver();
// nonvirtual since we're not subclassed
~ShortcutResolver();
nsresult Init();
nsresult Resolve(const WCHAR* in, char* out);
private:
PRLock* mLock;
IPersistFile* mPersistFile;
IShellLink* mShellLink;
};
ShortcutResolver::ShortcutResolver()
{
mLock = nsnull;
mPersistFile = nsnull;
mShellLink = nsnull;
}
ShortcutResolver::~ShortcutResolver()
{
if (mLock)
PR_DestroyLock(mLock);
// Release the pointer to the IPersistFile interface.
if (mPersistFile)
mPersistFile->Release();
// Release the pointer to the IShellLink interface.
if(mShellLink)
mShellLink->Release();
CoUninitialize();
}
nsresult
ShortcutResolver::Init()
{
CoInitialize(NULL); // FIX: we should probably move somewhere higher up during startup
mLock = PR_NewLock();
if (!mLock)
return NS_ERROR_FAILURE;
HRESULT hres = CoCreateInstance(CLSID_ShellLink,
NULL,
CLSCTX_INPROC_SERVER,
IID_IShellLink,
(void**)&mShellLink);
if (SUCCEEDED(hres))
{
// Get a pointer to the IPersistFile interface.
hres = mShellLink->QueryInterface(IID_IPersistFile, (void**)&mPersistFile);
}
if (mPersistFile == nsnull || mShellLink == nsnull)
return NS_ERROR_FAILURE;
return NS_OK;
}
// |out| must be an allocated buffer of size MAX_PATH
nsresult
ShortcutResolver::Resolve(const WCHAR* in, char* out)
{
nsAutoLock lock(mLock);
// see if we can Load the path.
HRESULT hres = mPersistFile->Load(in, STGM_READ);
if (FAILED(hres))
return NS_ERROR_FAILURE;
// Resolve the link.
hres = mShellLink->Resolve(nsnull, SLR_NO_UI );
if (FAILED(hres))
return NS_ERROR_FAILURE;
WIN32_FIND_DATA wfd;
// Get the path to the link target.
hres = mShellLink->GetPath( out, MAX_PATH, &wfd, SLGP_UNCPRIORITY );
if (FAILED(hres))
return NS_ERROR_FAILURE;
return NS_OK;
}
static ShortcutResolver * gResolver = nsnull;
static nsresult NS_CreateShortcutResolver()
{
gResolver = new ShortcutResolver();
if (!gResolver)
return NS_ERROR_OUT_OF_MEMORY;
return gResolver->Init();
}
static void NS_DestroyShortcutResolver()
{
delete gResolver;
gResolver = nsnull;
}
#endif
//-----------------------------------------------------------------------------
// static helper functions
//-----------------------------------------------------------------------------
// certainly not all the error that can be
// encountered, but many of them common ones
static nsresult ConvertWinError(DWORD winErr)
{
nsresult rv;
switch (winErr)
{
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
case ERROR_INVALID_DRIVE:
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_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 0:
rv = NS_OK;
default:
rv = NS_ERROR_FAILURE;
}
return rv;
}
// definition of INVALID_SET_FILE_POINTER from VC.NET header files
// it doesn't appear to be defined by VC6
#ifndef INVALID_SET_FILE_POINTER
# define INVALID_SET_FILE_POINTER ((DWORD)-1)
#endif
// same goes for INVALID_FILE_ATTRIBUTES
#ifndef INVALID_FILE_ATTRIBUTES
# define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
#endif
// 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 PRBool
IsShortcutPath(const char *path)
{
// 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.
NS_ABORT_IF_FALSE(path, "don't pass nulls");
const char * ext = (const char *) _mbsrchr((const unsigned char *)path, '.');
if (!ext || 0 != stricmp(ext + 1, "lnk"))
return PR_FALSE;
return PR_TRUE;
}
//-----------------------------------------------------------------------------
// nsDirEnumerator
//-----------------------------------------------------------------------------
class nsDirEnumerator : public nsISimpleEnumerator
{
public:
NS_DECL_ISUPPORTS
nsDirEnumerator() : mDir(nsnull)
{
}
nsresult Init(nsILocalFile* parent)
{
nsCAutoString filepath;
parent->GetNativeTarget(filepath);
if (filepath.IsEmpty())
{
parent->GetNativePath(filepath);
}
if (filepath.IsEmpty())
{
return NS_ERROR_UNEXPECTED;
}
mDir = PR_OpenDir(filepath.get());
if (mDir == nsnull) // not a directory?
return NS_ERROR_FAILURE;
mParent = parent;
return NS_OK;
}
NS_IMETHOD HasMoreElements(PRBool *result)
{
nsresult rv;
if (mNext == nsnull && mDir)
{
PRDirEntry* entry = PR_ReadDir(mDir, PR_SKIP_BOTH);
if (entry == nsnull)
{
// end of dir entries
PRStatus status = PR_CloseDir(mDir);
if (status != PR_SUCCESS)
return NS_ERROR_FAILURE;
mDir = nsnull;
*result = PR_FALSE;
return NS_OK;
}
nsCOMPtr<nsIFile> file;
rv = mParent->Clone(getter_AddRefs(file));
if (NS_FAILED(rv))
return rv;
rv = file->AppendNative(nsDependentCString(entry->name));
if (NS_FAILED(rv))
return rv;
// make sure the thing exists. If it does, try the next one.
PRBool exists;
rv = file->Exists(&exists);
if (NS_FAILED(rv) || !exists)
{
return HasMoreElements(result);
}
mNext = do_QueryInterface(file);
}
*result = mNext != nsnull;
return NS_OK;
}
NS_IMETHOD GetNext(nsISupports **result)
{
nsresult rv;
PRBool hasMore;
rv = HasMoreElements(&hasMore);
if (NS_FAILED(rv)) return rv;
*result = mNext; // might return nsnull
NS_IF_ADDREF(*result);
mNext = nsnull;
return NS_OK;
}
// dtor can be non-virtual since there are no subclasses, but must be
// public to use the class on the stack.
~nsDirEnumerator()
{
if (mDir)
{
PRStatus status = PR_CloseDir(mDir);
NS_ASSERTION(status == PR_SUCCESS, "close failed");
}
}
protected:
PRDir* mDir;
nsCOMPtr<nsILocalFile> mParent;
nsCOMPtr<nsILocalFile> mNext;
};
NS_IMPL_ISUPPORTS1(nsDirEnumerator, nsISimpleEnumerator)
//-----------------------------------------------------------------------------
// nsLocalFile <public>
//-----------------------------------------------------------------------------
nsLocalFile::nsLocalFile()
: mFollowSymlinks(PR_FALSE)
{
MakeDirty();
memset(&mFileInfo64, 0, sizeof(mFileInfo64));
}
NS_METHOD
nsLocalFile::nsLocalFileConstructor(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr)
{
NS_ENSURE_ARG_POINTER(aInstancePtr);
NS_ENSURE_NO_AGGREGATION(outer);
nsLocalFile* inst = new nsLocalFile();
if (inst == NULL)
return NS_ERROR_OUT_OF_MEMORY;
nsresult rv = inst->QueryInterface(aIID, aInstancePtr);
if (NS_FAILED(rv))
{
delete inst;
return rv;
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsLocalFile::nsISupports
//-----------------------------------------------------------------------------
NS_IMPL_THREADSAFE_ISUPPORTS2(nsLocalFile, nsILocalFile, nsIFile)
//-----------------------------------------------------------------------------
// nsLocalFile <private>
//-----------------------------------------------------------------------------
nsLocalFile::nsLocalFile(const nsLocalFile& other)
: mDirty(other.mDirty)
, mFollowSymlinks(other.mFollowSymlinks)
, mWorkingPath(other.mWorkingPath)
, mResolvedPath(other.mResolvedPath)
, mFileInfo64(other.mFileInfo64)
{
}
// Resolve the shortcut file from mWorkingPath and write the path
// it points to into mResolvedPath.
nsresult
nsLocalFile::ResolveShortcut()
{
#ifndef WINCE
// we can't do anything without the resolver
if (!gResolver)
return NS_ERROR_FAILURE;
// allocate the memory for the result of the resolution
nsAutoString ucsBuf;
NS_CopyNativeToUnicode(mWorkingPath, ucsBuf);
mResolvedPath.SetLength(MAX_PATH);
char *resolvedPath = mResolvedPath.BeginWriting();
// resolve this shortcut
nsresult rv = gResolver->Resolve(ucsBuf.get(), resolvedPath);
size_t len = NS_FAILED(rv) ? 0 : strlen(resolvedPath);
mResolvedPath.SetLength(len);
return rv;
#else
return NS_OK;
#endif
}
// 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;
// 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
char temp[4];
const char *nsprPath = mWorkingPath.get();
if (mWorkingPath.Length() == 2 && mWorkingPath.CharAt(1) == ':')
{
temp[0] = mWorkingPath[0];
temp[1] = mWorkingPath[1];
temp[2] = '\\';
temp[3] = '\0';
nsprPath = temp;
}
// first we will see if the working path exists. If it doesn't then
// there is nothing more that can be done
PRStatus status = PR_GetFileInfo64(nsprPath, &mFileInfo64);
if (status != PR_SUCCESS)
return NS_ERROR_FILE_NOT_FOUND;
// 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.get()))
{
mDirty = PR_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;
}
// get the details of the resolved path
status = PR_GetFileInfo64(mResolvedPath.get(), &mFileInfo64);
if (status != PR_SUCCESS)
return NS_ERROR_FILE_NOT_FOUND;
mDirty = PR_FALSE;
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsLocalFile::nsIFile,nsILocalFile
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsLocalFile::Clone(nsIFile **file)
{
// Just copy-construct ourselves
*file = new nsLocalFile(*this);
if (!*file)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*file);
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::InitWithNativePath(const nsACString &filePath)
{
MakeDirty();
nsACString::const_iterator begin, end;
filePath.BeginReading(begin);
filePath.EndReading(end);
// input string must not be empty
if (begin == end)
return NS_ERROR_FAILURE;
char firstChar = *begin;
char 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.
char *path = nsnull;
PRInt32 pathLen = 0;
if ( ( (secondChar == ':') && !FindCharInReadable('/', begin, end) ) || // normal path
#ifdef WINCE
( (firstChar == '\\') ) // wince absolute path or network path
#else
( (firstChar == '\\') && (secondChar == '\\') ) // network path
#endif
)
{
// This is a native path
path = ToNewCString(filePath);
pathLen = filePath.Length();
}
if (path == nsnull) {
return NS_ERROR_FILE_UNRECOGNIZED_PATH;
}
// kill any trailing '\' provided it isn't the second char of DBCS
PRInt32 len = pathLen - 1;
if (path[len] == '\\' &&
(!::IsDBCSLeadByte(path[len-1]) ||
_mbsrchr((const unsigned char *)path, '\\') == (const unsigned char *)path+len))
{
path[len] = '\0';
pathLen = len;
}
mWorkingPath.Adopt(path, pathLen);
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::OpenNSPRFileDesc(PRInt32 flags, PRInt32 mode, PRFileDesc **_retval)
{
nsresult rv = ResolveAndStat();
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)
return rv;
*_retval = PR_Open(mResolvedPath.get(), flags, mode);
if (*_retval)
return NS_OK;
return NS_ErrorAccordingToNSPR();
}
NS_IMETHODIMP
nsLocalFile::OpenANSIFileDesc(const char *mode, FILE * *_retval)
{
nsresult rv = ResolveAndStat();
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)
return rv;
*_retval = fopen(mResolvedPath.get(), mode);
if (*_retval)
return NS_OK;
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsLocalFile::Create(PRUint32 type, PRUint32 attributes)
{
if (type != NORMAL_FILE_TYPE && type != 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.
const unsigned char* path = (const unsigned char*) mResolvedPath.get();
if (path[0] == '\\' && path[1] == '\\')
{
#ifdef WINCE
++path;
#else
// dealing with a UNC path here; skip past '\\machine\'
path = _mbschr(path + 2, '\\');
if (!path)
return NS_ERROR_FILE_INVALID_PATH;
++path;
#endif
}
// search for first slash after the drive (or volume) name
unsigned char* slash = _mbschr(path, '\\');
if (slash)
{
// skip the first '\\'
++slash;
slash = _mbschr(slash, '\\');
while (slash)
{
*slash = '\0';
if (!CreateDirectoryA(mResolvedPath.get(), NULL)) {
rv = ConvertWinError(GetLastError());
// 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.
if (rv != NS_ERROR_FILE_ALREADY_EXISTS &&
rv != NS_ERROR_FILE_ACCESS_DENIED)
return rv;
}
*slash = '\\';
++slash;
slash = _mbschr(slash, '\\');
}
}
if (type == NORMAL_FILE_TYPE)
{
PRFileDesc* file = PR_Open(mResolvedPath.get(), PR_RDONLY | PR_CREATE_FILE | PR_APPEND | PR_EXCL, attributes);
if (!file) return NS_ERROR_FILE_ALREADY_EXISTS;
PR_Close(file);
return NS_OK;
}
if (type == DIRECTORY_TYPE)
{
if (!CreateDirectoryA(mResolvedPath.get(), NULL))
return ConvertWinError(GetLastError());
else
return NS_OK;
}
return NS_ERROR_FILE_UNKNOWN_TYPE;
}
NS_IMETHODIMP
nsLocalFile::AppendNative(const nsACString &node)
{
// append this path, multiple components are not permitted
return AppendNativeInternal(PromiseFlatCString(node), PR_FALSE);
}
NS_IMETHODIMP
nsLocalFile::AppendRelativeNativePath(const nsACString &node)
{
// append this path, multiple components are permitted
return AppendNativeInternal(PromiseFlatCString(node), PR_TRUE);
}
nsresult
nsLocalFile::AppendNativeInternal(const nsAFlatCString &node, PRBool multipleComponents)
{
if (node.IsEmpty())
return NS_OK;
// check the relative path for validity
const unsigned char * nodePath = (const unsigned char *) node.get();
if (*nodePath == '\\' // can't start with an '\'
|| _mbschr(nodePath, '/') // can't contain /
|| node.Equals("..")) // can't be ..
return NS_ERROR_FILE_UNRECOGNIZED_PATH;
#ifndef WINCE // who cares?
if (multipleComponents)
{
// 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.
unsigned char * doubleDot = _mbsstr(nodePath, (unsigned char *)"\\..");
while (doubleDot)
{
doubleDot += 3;
if (*doubleDot == '\0' || *doubleDot == '\\')
return NS_ERROR_FILE_UNRECOGNIZED_PATH;
doubleDot = _mbsstr(doubleDot, (unsigned char *)"\\..");
}
if (0 == _mbsncmp(nodePath, (unsigned char *)"..\\", 3)) // catches the remaining cases of prefixes
return NS_ERROR_FILE_UNRECOGNIZED_PATH;
}
else if (_mbschr(nodePath, '\\')) // single components can't contain '\'
return NS_ERROR_FILE_UNRECOGNIZED_PATH;
#endif
MakeDirty();
mWorkingPath.Append(NS_LITERAL_CSTRING("\\") + node);
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::Normalize()
{
#ifndef WINCE
// XXX See bug 187957 comment 18 for possible problems with this implementation.
if (mWorkingPath.IsEmpty())
return NS_OK;
// work in unicode for ease
nsAutoString path;
NS_CopyNativeToUnicode(mWorkingPath, path);
// 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\").
PRInt32 rootIdx = 2; // default to local drive
if (path.First() == '\\') // if a share then calculate the rootIdx
{
rootIdx = path.FindChar('\\', 2); // skip \\ in front of the server
if (rootIdx == kNotFound)
return NS_OK; // already normalized
rootIdx = path.FindChar('\\', rootIdx+1);
if (rootIdx == kNotFound)
return NS_OK; // already normalized
}
else if (path.CharAt(rootIdx) != '\\')
{
// 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).
char cwd[MAX_PATH];
char * pcwd = cwd;
int drive = toupper(path.First()) - 'A' + 1;
if (!_getdcwd(drive, pcwd, MAX_PATH))
pcwd = _getdcwd(drive, 0, 0);
if (!pcwd)
return NS_ERROR_OUT_OF_MEMORY;
nsAutoString currentDir;
NS_CopyNativeToUnicode(nsDependentCString(pcwd), currentDir);
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 < (PRInt32)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 == (PRInt32)path.Length())
return NS_OK;
// assign the root
nsAutoString normal;
const PRUnichar * pathBuffer = path.get(); // simplify access to the buffer
normal.SetCapacity(path.Length()); // it won't ever grow longer
normal.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.
PRInt32 len, begin, end = rootIdx;
while (end < (PRInt32)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] == '.')
{
// back up a path component on double dot
if (len == 2)
{
PRInt32 prev = normal.RFindChar('\\');
if (prev >= rootIdx)
normal.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] != '.')
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
normal.Append(pathBuffer + begin - 1, len + 1);
}
NS_CopyUnicodeToNative(normal, mWorkingPath);
MakeDirty();
#else // WINCE
// WINCE FIX
#endif
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::GetNativeLeafName(nsACString &aLeafName)
{
aLeafName.Truncate();
const char* temp = mWorkingPath.get();
if(temp == nsnull)
return NS_ERROR_FILE_UNRECOGNIZED_PATH;
const char* leaf = (const char*) _mbsrchr((const unsigned char*) temp, '\\');
// if the working path is just a node without any lashes.
if (leaf == nsnull)
leaf = temp;
else
leaf++;
aLeafName.Assign(leaf);
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::SetNativeLeafName(const nsACString &aLeafName)
{
MakeDirty();
const unsigned char* temp = (const unsigned char*) mWorkingPath.get();
if(temp == nsnull)
return NS_ERROR_FILE_UNRECOGNIZED_PATH;
// cannot use nsCString::RFindChar() due to 0x5c problem
PRInt32 offset = (PRInt32) (_mbsrchr(temp, '\\') - temp);
if (offset)
{
mWorkingPath.Truncate(offset+1);
}
mWorkingPath.Append(aLeafName);
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::GetNativePath(nsACString &_retval)
{
_retval = mWorkingPath;
return NS_OK;
}
nsresult
nsLocalFile::CopySingleFile(nsIFile *sourceFile, nsIFile *destParent, const nsACString &newName,
PRBool followSymlinks, PRBool move)
{
nsresult rv;
nsCAutoString filePath;
// 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.
nsCAutoString destPath;
destParent->GetNativeTarget(destPath);
destPath.Append("\\");
if (newName.IsEmpty())
{
nsCAutoString aFileName;
sourceFile->GetNativeLeafName(aFileName);
destPath.Append(aFileName);
}
else
{
destPath.Append(newName);
}
if (followSymlinks)
{
rv = sourceFile->GetNativeTarget(filePath);
if (filePath.IsEmpty())
rv = sourceFile->GetNativePath(filePath);
}
else
{
rv = sourceFile->GetNativePath(filePath);
}
if (NS_FAILED(rv))
return rv;
int copyOK;
if (!move)
copyOK = CopyFile(filePath.get(), destPath.get(), PR_TRUE);
else
{
// What we have to do is check to see if the destPath exists. If it
// does, we have to move it out of the say so that MoveFile will
// succeed. However, we don't want to just remove it since MoveFile
// can fail leaving us without a file.
nsCAutoString backup;
PRFileInfo64 fileInfo64;
PRStatus status = PR_GetFileInfo64(destPath.get(), &fileInfo64);
if (status == PR_SUCCESS)
{
// the file exists. Check to make sure it is not a directory,
// then move it out of the way.
if (fileInfo64.type == PR_FILE_FILE)
{
backup.Append(destPath);
backup.Append(".moztmp");
// remove any existing backup file that we may already have.
// maybe we should be doing some kind of unique naming here,
// but why bother.
remove(backup.get());
// move destination file to backup file
copyOK = MoveFile(destPath.get(), backup.get());
if (!copyOK)
{
// I guess we can't do the backup copy, so return.
rv = ConvertWinError(GetLastError());
return rv;
}
}
}
// move source file to destination file
copyOK = MoveFile(filePath.get(), destPath.get());
if (!backup.IsEmpty())
{
if (copyOK)
{
// remove the backup copy.
remove(backup.get());
}
else
{
// restore backup
int backupOk = MoveFile(backup.get(), destPath.get());
NS_ASSERTION(backupOk, "move backup failed");
}
}
}
if (!copyOK) // CopyFile and MoveFile returns non-zero if succeeds (backward if you ask me).
rv = ConvertWinError(GetLastError());
return rv;
}
nsresult
nsLocalFile::CopyMove(nsIFile *aParentDir, const nsACString &newName, PRBool followSymlinks, PRBool move)
{
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| parameter.
nsresult rv = ResolveAndStat();
if (NS_FAILED(rv))
return rv;
if (!newParentDir)
{
// no parent was specified. We must rename.
if (newName.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.
PRBool 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
{
PRBool isDir;
newParentDir->IsDirectory(&isDir);
if (isDir == PR_FALSE)
{
if (followSymlinks)
{
PRBool isLink;
newParentDir->IsSymlink(&isLink);
if (isLink)
{
nsCAutoString target;
newParentDir->GetNativeTarget(target);
nsCOMPtr<nsILocalFile> realDest = new nsLocalFile();
if (realDest == nsnull)
return NS_ERROR_OUT_OF_MEMORY;
rv = realDest->InitWithNativePath(target);
if (NS_FAILED(rv))
return rv;
return CopyMove(realDest, newName, followSymlinks, move);
}
}
else
{
return NS_ERROR_FILE_DESTINATION_NOT_DIR;
}
}
}
// check to see if we are a directory, if so enumerate it.
PRBool isDir;
IsDirectory(&isDir);
PRBool isSymlink;
IsSymlink(&isSymlink);
if (!isDir || (isSymlink && !followSymlinks))
{
rv = CopySingleFile(this, newParentDir, newName, followSymlinks, move);
if (NS_FAILED(rv))
return rv;
}
else
{
// create a new target destination in the new parentDir;
nsCOMPtr<nsIFile> target;
rv = newParentDir->Clone(getter_AddRefs(target));
if (NS_FAILED(rv))
return rv;
nsCAutoString allocatedNewName;
if (newName.IsEmpty())
{
PRBool isLink;
IsSymlink(&isLink);
if (isLink)
{
nsCAutoString temp;
GetNativeTarget(temp);
const char* leaf = (const char*) _mbsrchr((const unsigned char*) temp.get(), '\\');
if (leaf[0] == '\\')
leaf++;
allocatedNewName = leaf;
}
else
{
GetNativeLeafName(allocatedNewName);// this should be the leaf name of the
}
}
else
{
allocatedNewName = newName;
}
rv = target->AppendNative(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
PRBool isWritable;
target->IsWritable(&isWritable);
if (!isWritable)
return NS_ERROR_FILE_ACCESS_DENIED;
nsCOMPtr<nsISimpleEnumerator> targetIterator;
rv = target->GetDirectoryEntries(getter_AddRefs(targetIterator));
PRBool more;
targetIterator->HasMoreElements(&more);
// return error if target directory is not empty
if (more)
return NS_ERROR_FILE_DIR_NOT_EMPTY;
}
nsDirEnumerator dirEnum;
rv = dirEnum.Init(this);
if (NS_FAILED(rv)) {
NS_WARNING("dirEnum initalization failed");
return rv;
}
PRBool more;
while (NS_SUCCEEDED(dirEnum.HasMoreElements(&more)) && more)
{
nsCOMPtr<nsISupports> item;
nsCOMPtr<nsIFile> file;
dirEnum.GetNext(getter_AddRefs(item));
file = do_QueryInterface(item);
if (file)
{
PRBool isDir, isLink;
file->IsDirectory(&isDir);
file->IsSymlink(&isLink);
if (move)
{
if (followSymlinks)
return NS_ERROR_FAILURE;
rv = file->MoveToNative(target, EmptyCString());
NS_ENSURE_SUCCESS(rv,rv);
}
else
{
if (followSymlinks)
rv = file->CopyToFollowingLinksNative(target, EmptyCString());
else
rv = file->CopyToNative(target, EmptyCString());
NS_ENSURE_SUCCESS(rv,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(PR_FALSE /* recursive */);
NS_ENSURE_SUCCESS(rv,rv);
}
}
// If we moved, we want to adjust this.
if (move)
{
MakeDirty();
nsCAutoString newParentPath;
newParentDir->GetNativePath(newParentPath);
if (newParentPath.IsEmpty())
return NS_ERROR_FAILURE;
if (newName.IsEmpty())
{
nsCAutoString aFileName;
GetNativeLeafName(aFileName);
InitWithNativePath(newParentPath);
AppendNative(aFileName);
}
else
{
InitWithNativePath(newParentPath);
AppendNative(newName);
}
}
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::CopyToNative(nsIFile *newParentDir, const nsACString &newName)
{
return CopyMove(newParentDir, newName, PR_FALSE, PR_FALSE);
}
NS_IMETHODIMP
nsLocalFile::CopyToFollowingLinksNative(nsIFile *newParentDir, const nsACString &newName)
{
return CopyMove(newParentDir, newName, PR_TRUE, PR_FALSE);
}
NS_IMETHODIMP
nsLocalFile::MoveToNative(nsIFile *newParentDir, const nsACString &newName)
{
return CopyMove(newParentDir, newName, PR_FALSE, PR_TRUE);
}
NS_IMETHODIMP
nsLocalFile::Load(PRLibrary * *_retval)
{
PRBool isFile;
nsresult rv = IsFile(&isFile);
if (NS_FAILED(rv))
return rv;
if (! isFile)
return NS_ERROR_FILE_IS_DIRECTORY;
NS_TIMELINE_START_TIMER("PR_LoadLibrary");
*_retval = PR_LoadLibrary(mResolvedPath.get());
NS_TIMELINE_STOP_TIMER("PR_LoadLibrary");
NS_TIMELINE_MARK_TIMER1("PR_LoadLibrary", mResolvedPath.get());
if (*_retval)
return NS_OK;
return NS_ERROR_NULL_POINTER;
}
NS_IMETHODIMP
nsLocalFile::Remove(PRBool recursive)
{
// 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.
PRBool isDir, isLink;
nsresult rv;
isDir = PR_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 (recursive)
{
nsDirEnumerator dirEnum;
rv = dirEnum.Init(this);
if (NS_FAILED(rv))
return rv;
PRBool more;
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(recursive);
}
}
rv = rmdir(mWorkingPath.get());
}
else
{
rv = remove(mWorkingPath.get());
}
// fixup error code if necessary...
if (rv == (nsresult)-1)
rv = NSRESULT_FOR_ERRNO();
MakeDirty();
return rv;
}
NS_IMETHODIMP
nsLocalFile::GetLastModifiedTime(PRInt64 *aLastModifiedTime)
{
NS_ENSURE_ARG(aLastModifiedTime);
// get the modified time of the target as determined by mFollowSymlinks
// If PR_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
PRInt64 usecPerMsec;
LL_I2L(usecPerMsec, PR_USEC_PER_MSEC);
LL_DIV(*aLastModifiedTime, mFileInfo64.modifyTime, usecPerMsec);
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::GetLastModifiedTimeOfLink(PRInt64 *aLastModifiedTime)
{
NS_ENSURE_ARG(aLastModifiedTime);
// The caller is assumed to have already called IsSymlink
// and to have found that this file is a link.
PRFileInfo64 info;
if (PR_GetFileInfo64(mWorkingPath.get(), &info) != PR_SUCCESS)
return NSRESULT_FOR_ERRNO();
// microseconds -> milliseconds
PRInt64 usecPerMsec;
LL_I2L(usecPerMsec, PR_USEC_PER_MSEC);
LL_DIV(*aLastModifiedTime, info.modifyTime, usecPerMsec);
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::SetLastModifiedTime(PRInt64 aLastModifiedTime)
{
nsresult rv = ResolveAndStat();
if (NS_FAILED(rv))
return rv;
// set the modified time of the target as determined by mFollowSymlinks
// If PR_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(PRInt64 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(PRInt64 aLastModifiedTime, const char *filePath)
{
HANDLE file = CreateFile(filePath, // pointer to name of the file
GENERIC_WRITE, // access (write) mode
0, // share mode
NULL, // pointer to security attributes
OPEN_EXISTING, // how to create
0, // file attributes
NULL);
if (file == INVALID_HANDLE_VALUE)
{
return ConvertWinError(GetLastError());
}
FILETIME lft, ft;
SYSTEMTIME st;
PRExplodedTime pret;
// PR_ExplodeTime expects usecs...
PR_ExplodeTime(aLastModifiedTime * PR_USEC_PER_MSEC, PR_LocalTimeParameters, &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 ( 0 != SystemTimeToFileTime(&st, &lft)
|| 0 != LocalFileTimeToFileTime(&lft, &ft)
|| 0 != SetFileTime(file, NULL, &ft, &ft) )
{
rv = ConvertWinError(GetLastError());
}
CloseHandle(file);
return rv;
}
NS_IMETHODIMP
nsLocalFile::GetPermissions(PRUint32 *aPermissions)
{
NS_ENSURE_ARG(aPermissions);
// get the permissions of the target as determined by mFollowSymlinks
// If PR_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;
PRBool isWritable, isExecutable;
IsWritable(&isWritable);
IsExecutable(&isExecutable);
*aPermissions = PR_IRUSR|PR_IRGRP|PR_IROTH; // all read
if (isWritable)
*aPermissions |= PR_IWUSR|PR_IWGRP|PR_IWOTH; // all write
if (isExecutable)
*aPermissions |= PR_IXUSR|PR_IXGRP|PR_IXOTH; // all execute
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::GetPermissionsOfLink(PRUint32 *aPermissions)
{
NS_ENSURE_ARG(aPermissions);
// The caller is assumed to have already called IsSymlink
// and to have found that this file is a link. It is not
// possible for a link file to be executable.
DWORD word = GetFileAttributes(mWorkingPath.get());
if (word == INVALID_FILE_ATTRIBUTES)
return NS_ERROR_FILE_INVALID_PATH;
PRBool isWritable = !(word & FILE_ATTRIBUTE_READONLY);
*aPermissions = PR_IRUSR|PR_IRGRP|PR_IROTH; // all read
if (isWritable)
*aPermissions |= PR_IWUSR|PR_IWGRP|PR_IWOTH; // all write
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::SetPermissions(PRUint32 aPermissions)
{
// set the permissions of the target as determined by mFollowSymlinks
// If PR_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 SetPermissionsOfLink)
nsresult rv = ResolveAndStat();
if (NS_FAILED(rv))
return rv;
// windows only knows about the following permissions
int mode = 0;
if (aPermissions & (PR_IRUSR|PR_IRGRP|PR_IROTH)) // any read
mode |= _S_IREAD;
if (aPermissions & (PR_IWUSR|PR_IWGRP|PR_IWOTH)) // any write
mode |= _S_IWRITE;
if (chmod(mResolvedPath.get(), mode) == -1)
return NS_ERROR_FAILURE;
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::SetPermissionsOfLink(PRUint32 aPermissions)
{
// The caller is assumed to have already called IsSymlink
// and to have found that this file is a link.
// windows only knows about the following permissions
int mode = 0;
if (aPermissions & (PR_IRUSR|PR_IRGRP|PR_IROTH)) // any read
mode |= _S_IREAD;
if (aPermissions & (PR_IWUSR|PR_IWGRP|PR_IWOTH)) // any write
mode |= _S_IWRITE;
if (chmod(mWorkingPath.get(), mode) == -1)
return NS_ERROR_FAILURE;
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::GetFileSize(PRInt64 *aFileSize)
{
NS_ENSURE_ARG(aFileSize);
nsresult rv = ResolveAndStat();
if (NS_FAILED(rv))
return rv;
*aFileSize = mFileInfo64.size;
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::GetFileSizeOfLink(PRInt64 *aFileSize)
{
NS_ENSURE_ARG(aFileSize);
// The caller is assumed to have already called IsSymlink
// and to have found that this file is a link.
PRFileInfo64 info;
if (!PR_GetFileInfo64(mWorkingPath.get(), &info))
return NS_ERROR_FILE_INVALID_PATH;
*aFileSize = info.size;
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::SetFileSize(PRInt64 aFileSize)
{
nsresult rv = ResolveAndStat();
if (NS_FAILED(rv))
return rv;
HANDLE hFile = CreateFile(mResolvedPath.get(), // pointer to name of the file
GENERIC_WRITE, // access (write) mode
FILE_SHARE_READ, // share mode
NULL, // pointer to security attributes
OPEN_EXISTING, // how to create
FILE_ATTRIBUTE_NORMAL, // file attributes
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return ConvertWinError(GetLastError());
}
// seek the file pointer to the new, desired end of file
// and then truncate the file at that position
rv = NS_ERROR_FAILURE;
aFileSize = MyFileSeek64(hFile, aFileSize, FILE_BEGIN);
if (aFileSize != -1 && SetEndOfFile(hFile))
{
MakeDirty();
rv = NS_OK;
}
CloseHandle(hFile);
return rv;
}
typedef BOOL (WINAPI *fpGetDiskFreeSpaceExA)(LPCSTR lpDirectoryName,
PULARGE_INTEGER lpFreeBytesAvailableToCaller,
PULARGE_INTEGER lpTotalNumberOfBytes,
PULARGE_INTEGER lpTotalNumberOfFreeBytes);
NS_IMETHODIMP
nsLocalFile::GetDiskSpaceAvailable(PRInt64 *aDiskSpaceAvailable)
{
#ifndef WINCE
NS_ENSURE_ARG(aDiskSpaceAvailable);
ResolveAndStat();
// Attempt to check disk space using the GetDiskFreeSpaceExA function.
// --- FROM MSDN ---
// The GetDiskFreeSpaceEx function is available beginning with Windows 95 OEM Service
// Release 2 (OSR2). To determine whether GetDiskFreeSpaceEx is available, call
// GetModuleHandle to get the handle to Kernel32.dll. Then you can call GetProcAddress.
// It is not necessary to call LoadLibrary on Kernel32.dll because it is already loaded
// into every process address space.
fpGetDiskFreeSpaceExA pGetDiskFreeSpaceExA = (fpGetDiskFreeSpaceExA)
GetProcAddress(GetModuleHandle("kernel32.dll"), "GetDiskFreeSpaceExA");
if (pGetDiskFreeSpaceExA)
{
ULARGE_INTEGER liFreeBytesAvailableToCaller, liTotalNumberOfBytes;
if (pGetDiskFreeSpaceExA(mResolvedPath.get(),
&liFreeBytesAvailableToCaller,
&liTotalNumberOfBytes,
NULL))
{
*aDiskSpaceAvailable = liFreeBytesAvailableToCaller.QuadPart;
return NS_OK;
}
}
// use the old method of getting available disk space
char aDrive[_MAX_DRIVE + 2];
_splitpath( mResolvedPath.get(), aDrive, NULL, NULL, NULL);
strcat(aDrive, "\\");
DWORD dwSecPerClus, dwBytesPerSec, dwFreeClus, dwTotalClus;
if (GetDiskFreeSpace(aDrive, &dwSecPerClus, &dwBytesPerSec, &dwFreeClus, &dwTotalClus))
{
__int64 bytes = dwFreeClus;
bytes *= dwSecPerClus;
bytes *= dwBytesPerSec;
*aDiskSpaceAvailable = bytes;
return NS_OK;
}
#endif
// WINCE FIX
*aDiskSpaceAvailable = 0;
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::GetParent(nsIFile * *aParent)
{
NS_ENSURE_ARG_POINTER(aParent);
nsCAutoString parentPath(mWorkingPath);
// cannot use nsCString::RFindChar() due to 0x5c problem
PRInt32 offset = (PRInt32) (_mbsrchr((const unsigned char *) parentPath.get(), '\\')
- (const unsigned char *) parentPath.get());
// adding this offset check that was removed in bug 241708 fixes mail
// directories that aren't relative to/underneath the profile dir.
// e.g., on a different drive. Before you remove them, please make
// sure local mail directories that aren't underneath the profile dir work.
if (offset < 0)
return NS_ERROR_FILE_UNRECOGNIZED_PATH;
if (offset == 1 && parentPath[0] == '\\') {
aParent = nsnull;
return NS_OK;
}
if (offset > 0)
parentPath.Truncate(offset);
else
parentPath.AssignLiteral("\\\\.");
nsCOMPtr<nsILocalFile> localFile;
nsresult rv = NS_NewNativeLocalFile(parentPath, mFollowSymlinks, getter_AddRefs(localFile));
if(NS_SUCCEEDED(rv) && localFile)
{
return CallQueryInterface(localFile, aParent);
}
return rv;
}
NS_IMETHODIMP
nsLocalFile::Exists(PRBool *_retval)
{
NS_ENSURE_ARG(_retval);
*_retval = PR_FALSE;
MakeDirty();
nsresult rv = ResolveAndStat();
*_retval = NS_SUCCEEDED(rv);
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::IsWritable(PRBool *aIsWritable)
{
//TODO: extend to support NTFS file permissions
// The read-only attribute on a FAT directory only means that it can't
// be deleted. It is still possible to modify the contents of the directory.
nsresult rv = IsDirectory(aIsWritable);
if (NS_FAILED(rv))
return rv;
if (*aIsWritable)
return NS_OK;
// writable if the file doesn't have the readonly attribute
rv = HasFileAttribute(FILE_ATTRIBUTE_READONLY, aIsWritable);
if (NS_FAILED(rv))
return rv;
*aIsWritable = !*aIsWritable;
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::IsReadable(PRBool *_retval)
{
NS_ENSURE_ARG(_retval);
*_retval = PR_FALSE;
nsresult rv = ResolveAndStat();
if (NS_FAILED(rv))
return rv;
*_retval = PR_TRUE;
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::IsExecutable(PRBool *_retval)
{
NS_ENSURE_ARG(_retval);
*_retval = PR_FALSE;
nsresult rv;
// only files can be executables
PRBool isFile;
rv = IsFile(&isFile);
if (NS_FAILED(rv))
return rv;
if (!isFile)
return NS_OK;
//TODO: shouldn't we be checking mFollowSymlinks here?
PRBool symLink;
rv = IsSymlink(&symLink);
if (NS_FAILED(rv))
return rv;
nsCAutoString path;
if (symLink)
GetNativeTarget(path);
else
GetNativePath(path);
// Get extension.
char * ext = (char *) _mbsrchr((unsigned char *)path.BeginWriting(), '.');
if ( ext ) {
// Convert extension to lower case.
for( unsigned char *p = (unsigned char *)ext; *p; p++ )
*p = _mbctolower( *p );
// Search for any of the set of executable extensions.
const char * const executableExts[] = {
".ad",
".adp",
".asp",
".bas",
".bat",
".chm",
".cmd",
".com",
".cpl",
".crt",
".exe",
".hlp",
".hta",
".inf",
".ins",
".isp",
".js",
".jse",
".lnk",
".mdb",
".mde",
".msc",
".msi",
".msp",
".mst",
".pcd",
".pif",
".reg",
".scr",
".sct",
".shb",
".shs",
".url",
".vb",
".vbe",
".vbs",
".vsd",
".vss",
".vst",
".vsw",
".ws",
".wsc",
".wsf",
".wsh",
0 };
for ( int i = 0; executableExts[i]; i++ ) {
if ( ::strcmp( executableExts[i], ext ) == 0 ) {
// Found a match. Set result and quit.
*_retval = PR_TRUE;
break;
}
}
}
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::IsDirectory(PRBool *_retval)
{
NS_ENSURE_ARG(_retval);
nsresult rv = ResolveAndStat();
if (NS_FAILED(rv))
return rv;
*_retval = (mFileInfo64.type == PR_FILE_DIRECTORY);
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::IsFile(PRBool *_retval)
{
NS_ENSURE_ARG(_retval);
nsresult rv = ResolveAndStat();
if (NS_FAILED(rv))
return rv;
*_retval = (mFileInfo64.type == PR_FILE_FILE);
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::IsHidden(PRBool *_retval)
{
return HasFileAttribute(FILE_ATTRIBUTE_HIDDEN, _retval);
}
nsresult
nsLocalFile::HasFileAttribute(DWORD fileAttrib, PRBool *_retval)
{
NS_ENSURE_ARG(_retval);
nsresult rv = ResolveAndStat();
if (NS_FAILED(rv))
return rv;
// get the file attributes for the correct item depending on following symlinks
const char *filePath = mFollowSymlinks ? mResolvedPath.get() : mWorkingPath.get();
DWORD word = GetFileAttributes(filePath);
*_retval = ((word & fileAttrib) != 0);
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::IsSymlink(PRBool *_retval)
{
NS_ENSURE_ARG(_retval);
// unless it is a valid shortcut path it's not a symlink
if (!IsShortcutPath(mWorkingPath.get()))
{
*_retval = PR_FALSE;
return NS_OK;
}
// we need to know if this is a file or directory
nsresult rv = ResolveAndStat();
if (NS_FAILED(rv))
return rv;
// it's only a shortcut if it is a file
*_retval = (mFileInfo64.type == PR_FILE_FILE);
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::IsSpecial(PRBool *_retval)
{
return HasFileAttribute(FILE_ATTRIBUTE_SYSTEM, _retval);
}
NS_IMETHODIMP
nsLocalFile::Equals(nsIFile *inFile, PRBool *_retval)
{
NS_ENSURE_ARG(inFile);
NS_ENSURE_ARG(_retval);
nsCAutoString inFilePath;
inFile->GetNativePath(inFilePath);
*_retval = inFilePath.Equals(mWorkingPath);
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::Contains(nsIFile *inFile, PRBool recur, PRBool *_retval)
{
*_retval = PR_FALSE;
nsCAutoString myFilePath;
if ( NS_FAILED(GetNativeTarget(myFilePath)))
GetNativePath(myFilePath);
PRInt32 myFilePathLen = myFilePath.Length();
nsCAutoString inFilePath;
if ( NS_FAILED(inFile->GetNativeTarget(inFilePath)))
inFile->GetNativePath(inFilePath);
if ( strnicmp( myFilePath.get(), inFilePath.get(), myFilePathLen) == 0)
{
// now make sure that the |inFile|'s path has a trailing
// separator.
if (inFilePath[myFilePathLen] == '\\')
{
*_retval = PR_TRUE;
}
}
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::GetNativeTarget(nsACString &_retval)
{
_retval.Truncate();
#if STRICT_FAKE_SYMLINKS
PRBool symLink;
nsresult rv = IsSymlink(&symLink);
if (NS_FAILED(rv))
return rv;
if (!symLink)
{
return NS_ERROR_FILE_INVALID_PATH;
}
#endif
ResolveAndStat();
_retval = mResolvedPath;
return NS_OK;
}
/* attribute PRBool followLinks; */
NS_IMETHODIMP
nsLocalFile::GetFollowLinks(PRBool *aFollowLinks)
{
*aFollowLinks = mFollowSymlinks;
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::SetFollowLinks(PRBool aFollowLinks)
{
MakeDirty();
mFollowSymlinks = aFollowLinks;
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator * *entries)
{
nsresult rv;
*entries = nsnull;
if (mWorkingPath.EqualsLiteral("\\\\.")) {
nsDriveEnumerator *drives = new nsDriveEnumerator;
if (!drives)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(drives);
rv = drives->Init();
if (NS_FAILED(rv)) {
NS_RELEASE(drives);
return rv;
}
*entries = drives;
return NS_OK;
}
PRBool isDir;
rv = IsDirectory(&isDir);
if (NS_FAILED(rv))
return rv;
if (!isDir)
return NS_ERROR_FILE_NOT_DIRECTORY;
nsDirEnumerator* dirEnum = new nsDirEnumerator();
if (dirEnum == nsnull)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(dirEnum);
rv = dirEnum->Init(this);
if (NS_FAILED(rv))
{
NS_RELEASE(dirEnum);
return rv;
}
*entries = dirEnum;
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::GetPersistentDescriptor(nsACString &aPersistentDescriptor)
{
return GetNativePath(aPersistentDescriptor);
}
NS_IMETHODIMP
nsLocalFile::SetPersistentDescriptor(const nsACString &aPersistentDescriptor)
{
return InitWithNativePath(aPersistentDescriptor);
}
NS_IMETHODIMP
nsLocalFile::Reveal()
{
#ifndef WINCE
// make sure mResolvedPath is set
nsresult rv = ResolveAndStat();
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)
return rv;
// use the full path to explorer for security
nsCOMPtr<nsILocalFile> winDir;
rv = GetSpecialSystemDirectory(Win_WindowsDirectory, getter_AddRefs(winDir));
NS_ENSURE_SUCCESS(rv, rv);
nsCAutoString explorerPath;
rv = winDir->GetNativePath(explorerPath);
NS_ENSURE_SUCCESS(rv, rv);
explorerPath.Append("\\explorer.exe");
// Always open a new window for files because Win2K doesn't appear to select
// the file if a window showing that folder was already open. If the resolved
// path is a directory then instead of opening the parent and selecting it,
// we open the directory itself.
nsCAutoString explorerParams;
if (mFileInfo64.type != PR_FILE_DIRECTORY) // valid because we ResolveAndStat above
explorerParams.Append("/n,/select,");
explorerParams.Append('\"');
explorerParams.Append(mResolvedPath);
explorerParams.Append('\"');
if (::ShellExecute(NULL, "open", explorerPath.get(), explorerParams.get(), NULL, SW_SHOWNORMAL) <= (HINSTANCE) 32)
return NS_ERROR_FAILURE;
#endif
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::Launch()
{
#ifndef WINCE
const nsCString &path = mWorkingPath;
// use the app registry name to launch a shell execute....
LONG r = (LONG) ::ShellExecute( NULL, NULL, path.get(), NULL, NULL, SW_SHOWNORMAL);
// if the file has no association, we launch windows' "what do you want to do" dialog
if (r == SE_ERR_NOASSOC) {
nsCAutoString shellArg;
shellArg.Assign(NS_LITERAL_CSTRING("shell32.dll,OpenAs_RunDLL ") + path);
r = (LONG) ::ShellExecute(NULL, NULL, "RUNDLL32.EXE", shellArg.get(),
NULL, SW_SHOWNORMAL);
}
if (r < 32) {
switch (r) {
case 0:
case SE_ERR_OOM:
return NS_ERROR_OUT_OF_MEMORY;
case ERROR_FILE_NOT_FOUND:
return NS_ERROR_FILE_NOT_FOUND;
case ERROR_PATH_NOT_FOUND:
return NS_ERROR_FILE_UNRECOGNIZED_PATH;
case ERROR_BAD_FORMAT:
return NS_ERROR_FILE_CORRUPTED;
case SE_ERR_ACCESSDENIED:
return NS_ERROR_FILE_ACCESS_DENIED;
case SE_ERR_ASSOCINCOMPLETE:
case SE_ERR_NOASSOC:
return NS_ERROR_UNEXPECTED;
case SE_ERR_DDEBUSY:
case SE_ERR_DDEFAIL:
case SE_ERR_DDETIMEOUT:
return NS_ERROR_NOT_AVAILABLE;
case SE_ERR_DLLNOTFOUND:
return NS_ERROR_FAILURE;
case SE_ERR_SHARE:
return NS_ERROR_FILE_IS_LOCKED;
default:
return NS_ERROR_FILE_EXECUTION_FAILED;
}
}
#endif
return NS_OK;
}
nsresult
NS_NewNativeLocalFile(const nsACString &path, PRBool followLinks, nsILocalFile* *result)
{
nsLocalFile* file = new nsLocalFile();
if (file == nsnull)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(file);
file->SetFollowLinks(followLinks);
if (!path.IsEmpty()) {
nsresult rv = file->InitWithNativePath(path);
if (NS_FAILED(rv)) {
NS_RELEASE(file);
return rv;
}
}
*result = file;
return NS_OK;
}
//-----------------------------------------------------------------------------
// UCS2 interface
//-----------------------------------------------------------------------------
nsresult
nsLocalFile::InitWithPath(const nsAString &filePath)
{
nsCAutoString tmp;
nsresult rv = NS_CopyUnicodeToNative(filePath, tmp);
if (NS_SUCCEEDED(rv))
return InitWithNativePath(tmp);
return rv;
}
nsresult
nsLocalFile::Append(const nsAString &node)
{
nsCAutoString tmp;
nsresult rv = NS_CopyUnicodeToNative(node, tmp);
if (NS_SUCCEEDED(rv))
return AppendNative(tmp);
return rv;
}
nsresult
nsLocalFile::AppendRelativePath(const nsAString &node)
{
nsCAutoString tmp;
nsresult rv = NS_CopyUnicodeToNative(node, tmp);
if (NS_SUCCEEDED(rv))
return AppendRelativeNativePath(tmp);
return rv;
}
nsresult
nsLocalFile::GetLeafName(nsAString &aLeafName)
{
nsCAutoString tmp;
nsresult rv = GetNativeLeafName(tmp);
if (NS_SUCCEEDED(rv))
rv = NS_CopyNativeToUnicode(tmp, aLeafName);
return rv;
}
nsresult
nsLocalFile::SetLeafName(const nsAString &aLeafName)
{
nsCAutoString tmp;
nsresult rv = NS_CopyUnicodeToNative(aLeafName, tmp);
if (NS_SUCCEEDED(rv))
return SetNativeLeafName(tmp);
return rv;
}
nsresult
nsLocalFile::GetPath(nsAString &_retval)
{
return NS_CopyNativeToUnicode(mWorkingPath, _retval);
}
nsresult
nsLocalFile::CopyTo(nsIFile *newParentDir, const nsAString &newName)
{
if (newName.IsEmpty())
return CopyToNative(newParentDir, EmptyCString());
nsCAutoString tmp;
nsresult rv = NS_CopyUnicodeToNative(newName, tmp);
if (NS_SUCCEEDED(rv))
return CopyToNative(newParentDir, tmp);
return rv;
}
nsresult
nsLocalFile::CopyToFollowingLinks(nsIFile *newParentDir, const nsAString &newName)
{
if (newName.IsEmpty())
return CopyToFollowingLinksNative(newParentDir, EmptyCString());
nsCAutoString tmp;
nsresult rv = NS_CopyUnicodeToNative(newName, tmp);
if (NS_SUCCEEDED(rv))
return CopyToFollowingLinksNative(newParentDir, tmp);
return rv;
}
nsresult
nsLocalFile::MoveTo(nsIFile *newParentDir, const nsAString &newName)
{
if (newName.IsEmpty())
return MoveToNative(newParentDir, EmptyCString());
nsCAutoString tmp;
nsresult rv = NS_CopyUnicodeToNative(newName, tmp);
if (NS_SUCCEEDED(rv))
return MoveToNative(newParentDir, tmp);
return rv;
}
nsresult
nsLocalFile::GetTarget(nsAString &_retval)
{
nsCAutoString tmp;
nsresult rv = GetNativeTarget(tmp);
if (NS_SUCCEEDED(rv))
rv = NS_CopyNativeToUnicode(tmp, _retval);
return rv;
}
nsresult
NS_NewLocalFile(const nsAString &path, PRBool followLinks, nsILocalFile* *result)
{
nsCAutoString buf;
nsresult rv = NS_CopyUnicodeToNative(path, buf);
if (NS_FAILED(rv)) {
*result = nsnull;
return rv;
}
return NS_NewNativeLocalFile(buf, followLinks, result);
}
//-----------------------------------------------------------------------------
// nsLocalFile <static members>
//-----------------------------------------------------------------------------
void
nsLocalFile::GlobalInit()
{
#ifndef WINCE
nsresult rv = NS_CreateShortcutResolver();
NS_ASSERTION(NS_SUCCEEDED(rv), "Shortcut resolver could not be created");
#endif
}
void
nsLocalFile::GlobalShutdown()
{
#ifndef WINCE
NS_DestroyShortcutResolver();
#endif
}
NS_IMPL_ISUPPORTS1(nsDriveEnumerator, nsISimpleEnumerator)
nsDriveEnumerator::nsDriveEnumerator()
: mLetter(0)
{
}
nsDriveEnumerator::~nsDriveEnumerator()
{
}
nsresult nsDriveEnumerator::Init()
{
#ifdef WINCE
return NS_OK;
#else
/* If the length passed to GetLogicalDriveStrings is smaller
* than the length of the string it would return, it returns
* the length required for the string. */
DWORD length = GetLogicalDriveStrings(0, 0);
/* The string is null terminated */
mDrives.SetLength(length+1);
if (!GetLogicalDriveStrings(length, mDrives.BeginWriting()))
return NS_ERROR_FAILURE;
mLetter = mDrives.get();
return NS_OK;
#endif
}
NS_IMETHODIMP nsDriveEnumerator::HasMoreElements(PRBool *aHasMore)
{
#ifdef WINCE
*aHasMore = FALSE;
#else
*aHasMore = *mLetter != '\0';
#endif
return NS_OK;
}
NS_IMETHODIMP nsDriveEnumerator::GetNext(nsISupports **aNext)
{
#ifdef WINCE
nsILocalFile *file;
nsresult rv = NS_NewNativeLocalFile(nsDependentCString("\\"), PR_FALSE, &file);
*aNext = file;
#else
/* GetLogicalDrives stored in mLetter is a concatenation
* of null terminated strings, followed by a null terminator. */
if (!*mLetter) {
*aNext = nsnull;
return NS_OK;
}
const char *drive = mLetter;
mLetter += strlen(drive) + 1;
nsILocalFile *file;
nsresult rv =
NS_NewNativeLocalFile(nsDependentCString(drive), PR_FALSE, &file);
*aNext = file;
#endif
return rv;
}