/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * The contents of this file are subject to the Netscape 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/NPL/ * * 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 Netscape are * Copyright (C) 1998-1999 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): * Doug Turner */ #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 #include #include "shellapi.h" #include "shlguid.h" #include #include #include #include #include "nsXPIDLString.h" #include "prproces.h" #include "nsITimelineService.h" #include "nsAutoLock.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 //---------------------------------------------------------------------------- // short cut resolver //---------------------------------------------------------------------------- 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; } //----------------------------------------------------------------------------- // 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; } static void myLL_II2L(PRInt32 hi, PRInt32 lo, PRInt64 *result) { PRInt64 a64, b64; // probably could have been done with // only one PRInt64, but these are macros, // and I am a wimp. // put hi in the low bits of a64. LL_I2L(a64, hi); // now shift it to the upperbit and place it the result in result LL_SHL(b64, a64, 32); // now put the low bits on by adding them to the result. LL_ADD(*result, b64, lo); } static void myLL_L2II(PRInt64 result, PRInt32 *hi, PRInt32 *lo ) { PRInt64 a64, b64; // probably could have been done with // only one PRInt64, but these are macros, // and I am a wimp. // shift the hi word to the low word, then push it into a long. LL_SHR(a64, result, 32); LL_L2I(*hi, a64); // shift the low word to the hi word first, then shift it back. LL_SHL(b64, result, 32); LL_SHR(a64, b64, 32); LL_L2I(*lo, a64); } static PRBool IsShortcut(const char* workingPath, int filePathLen) { // XXX this is badly broken!! // XXX consider "C:\FOO.LNK" // XXX consider "C:\foo.lnkx\bar.lnk" // check to see if it is shortcut, i.e., it has ".lnk" in it unsigned char* dest = _mbsstr((unsigned char*)workingPath, (unsigned char*)".lnk"); if (!dest) return PR_FALSE; // find index of ".lnk" int result = (int)(dest - (unsigned char*)workingPath); // if ".lnk" is not at the leaf of this path, we need to make sure the // next char after ".lnk" is a '\\'. e.g. "c:\\foo.lnk\\a.html" is valid, // whereas "c:\\foo.lnkx" is not. if (result + 4 < filePathLen) { if (workingPath[result + 4] != '\\') 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 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 mParent; nsCOMPtr mNext; }; NS_IMPL_ISUPPORTS1(nsDirEnumerator, nsISimpleEnumerator) //----------------------------------------------------------------------------- // nsLocalFile //----------------------------------------------------------------------------- nsLocalFile::nsLocalFile() { mLastResolution = PR_FALSE; mFollowSymlinks = PR_FALSE; MakeDirty(); } 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 //----------------------------------------------------------------------------- nsLocalFile::nsLocalFile(const nsLocalFile& other) : mDirty(other.mDirty) , mLastResolution(other.mLastResolution) , mFollowSymlinks(other.mFollowSymlinks) , mWorkingPath(other.mWorkingPath) , mResolvedPath(other.mResolvedPath) , mFileInfo64(other.mFileInfo64) { } // ResolvePath // this function will walk the native path of |this| resolving any symbolic // links found. The new resulting path will be placed into mResolvedPath. nsresult nsLocalFile::ResolvePath(const char* workingPath, PRBool resolveTerminal, char** resolvedPath) { nsresult rv = NS_OK; PRBool isDir = PR_FALSE; // check to see if it is shortcut, i.e., it has ".lnk" in it int filePathLen = strlen(workingPath); PRBool isShortcut = IsShortcut(workingPath, filePathLen); if (!isShortcut) return NS_ERROR_FILE_INVALID_PATH; #ifdef DEBUG_dougt printf("localfile - resolving symlink\n"); #endif // Get the native path for |this| // allocate extra bytes incase we need to append '\\' and '\0' to the // workingPath, if it is just a drive letter and a colon char *filePath = (char *) nsMemory::Alloc(filePathLen+2); if (!filePath) return NS_ERROR_NULL_POINTER; memcpy(filePath, workingPath, filePathLen + 1); // We are going to walk the native file path // and stop at each slash. For each partial // path (the string to the left of the slash) // we will check to see if it is a shortcut. // if it is, we will resolve it and continue // with that resolved path. // Get the first slash. unsigned char* slash = _mbschr((unsigned char*) filePath, '\\'); if (slash == nsnull) { if (nsCRT::IsAsciiAlpha(filePath[0]) && filePath[1] == ':' && filePath[2] == '\0') { // we have a drive letter and a colon (eg 'c:' // this is resolve already filePath[filePathLen] = '\\'; filePath[filePathLen+1] = '\0'; *resolvedPath = filePath; return NS_OK; } else { nsMemory::Free(filePath); return NS_ERROR_FILE_INVALID_PATH; } } // We really cant have just a drive letter as // a shortcut, so we will skip the first '\\' slash = _mbschr(++slash, '\\'); while (slash || resolveTerminal) { // Change the slash into a null so that // we can use the partial path. It is is // null, we know it is the terminal node. if (slash) { *slash = '\0'; } else if (resolveTerminal) { // this is our last time in this loop. // set loop condition to false resolveTerminal = PR_FALSE; } else { // something is wrong. we should not have // both slash being null and resolveTerminal // not set! nsMemory::Free(filePath); return NS_ERROR_NULL_POINTER; } // check to see the file is a shortcut by the magic .lnk extension. size_t offset = strlen(filePath) - 4; if ((offset > 0) && (strnicmp( (filePath + offset), ".lnk", 4) == 0)) { nsAutoString ucsBuf; NS_CopyNativeToUnicode(nsDependentCString(filePath), ucsBuf); char *temp = (char*) nsMemory::Alloc( MAX_PATH ); if (temp == nsnull) { nsMemory::Free(filePath); return NS_ERROR_NULL_POINTER; } if (gResolver) rv = gResolver->Resolve(ucsBuf.get(), temp); else rv = NS_ERROR_FAILURE; if (NS_SUCCEEDED(rv)) { // found a new path. // addend a slash on it since it does not come out of GetPath() // with one only if it is a directory. If it is not a directory // and there is more to append, than we have a problem. struct stat st; int statrv = stat(temp, &st); if (0 == statrv && (_S_IFDIR & st.st_mode)) { strcat(temp, "\\"); isDir = PR_TRUE; } if (slash) { // save where we left off. char *carot= (temp + strlen(temp) -1 ); // append all the stuff that we have not done. _mbscat((unsigned char*)temp, ++slash); slash = (unsigned char*)carot; } nsMemory::Free(filePath); filePath = temp; } else { // could not resolve shortcut. Return error; nsMemory::Free(filePath); return NS_ERROR_FILE_INVALID_PATH; } } if (slash) { *slash = '\\'; ++slash; slash = _mbschr(slash, '\\'); } } // kill any trailing separator char* temp = filePath; int len = strlen(temp) - 1; if((temp[len] == '\\') && (!isDir)) temp[len] = '\0'; *resolvedPath = filePath; return rv; } nsresult nsLocalFile::ResolveAndStat(PRBool resolveTerminal) { if (!mDirty && mLastResolution == resolveTerminal) { return NS_OK; } mLastResolution = resolveTerminal; mResolvedPath.Assign(mWorkingPath); //until we know better. // First we will see if the workingPath exists. If it does, then we // can simply use that as the resolved path. This simplification can // be done on windows cause its symlinks (shortcuts) use the .lnk // file extension. char temp[4]; const char* workingFilePath = mWorkingPath.get(); const char* nsprPath = workingFilePath; if (mWorkingPath.Length() == 2 && mWorkingPath.CharAt(1) == ':') { temp[0] = workingFilePath[0]; temp[1] = workingFilePath[1]; temp[2] = '\\'; temp[3] = '\0'; nsprPath = temp; } PRStatus status = PR_GetFileInfo64(nsprPath, &mFileInfo64); if ( status == PR_SUCCESS ) { if (!resolveTerminal) { mDirty = PR_FALSE; return NS_OK; } // check to see that this is shortcut, i.e., the leaf is ".lnk" // if the length < 4, then it's not a link. int pathLen = strlen(workingFilePath); const char* leaf = workingFilePath + pathLen - 4; // if we found the file and we are not following symlinks, then return success. if (!mFollowSymlinks || pathLen < 4 || (stricmp(leaf, ".lnk") != 0)) { mDirty = PR_FALSE; return NS_OK; } } if (!mFollowSymlinks) return NS_ERROR_FILE_NOT_FOUND; // if we are not resolving, we just give up here. nsresult result; // okay, something is wrong with the working path. We will try to resolve it. char *resolvePath; result = ResolvePath(workingFilePath, resolveTerminal, &resolvePath); if (NS_FAILED(result)) return NS_ERROR_FILE_NOT_FOUND; mResolvedPath.Assign(resolvePath); nsMemory::Free(resolvePath); status = PR_GetFileInfo64(mResolvedPath.get(), &mFileInfo64); if ( status == PR_SUCCESS ) mDirty = PR_FALSE; else result = NS_ERROR_FILE_NOT_FOUND; return result; } //----------------------------------------------------------------------------- // 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 ( (firstChar == '\\') && (secondChar == '\\') ) ) // network path { // 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) { // check to see if it is shortcut, i.e., it has ".lnk" in it PRBool isShortcut = IsShortcut(mWorkingPath.get(), mWorkingPath.Length()); if (!isShortcut && mDirty) { // we will optimize here. If we are not a shortcut and we are opening // a file and we are still dirty, assume that the working path is vaild // and try to open it. The working path will be different from its // resolved path for a shortcut file. // If it does work, get the stat info via the file descriptor mResolvedPath.Assign(mWorkingPath); *_retval = PR_Open(mResolvedPath.get(), flags, mode); if (*_retval) { PRStatus status = PR_GetOpenFileInfo64(*_retval, &mFileInfo64); if (status == PR_SUCCESS) { mDirty = PR_FALSE; mLastResolution = PR_TRUE; } else NS_ERROR("FileInfo64 invalid while PR_Open succeeded."); return NS_OK; } } nsresult rv = ResolveAndStat(PR_TRUE); 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(PR_TRUE); 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(PR_FALSE); 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] == '\\') { // dealing with a UNC path here; skip past '\\machine\' path = _mbschr(path + 2, '\\'); if (!path) return NS_ERROR_FILE_INVALID_PATH; ++path; } // 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) { if (node.IsEmpty()) return NS_OK; // Append only one component. Check for subdirs. // XXX can we avoid the PromiseFlatCString call? if (_mbschr((const unsigned char*) PromiseFlatCString(node).get(), '\\') != nsnull) return NS_ERROR_FILE_UNRECOGNIZED_PATH; return AppendRelativeNativePath(node); } NS_IMETHODIMP nsLocalFile::AppendRelativeNativePath(const nsACString &node) { // Cannot start with a / or have .. or have / anywhere nsACString::const_iterator begin, end; node.BeginReading(begin); node.EndReading(end); if (node.IsEmpty() || FindCharInReadable('/', begin, end)) { return NS_ERROR_FILE_UNRECOGNIZED_PATH; } MakeDirty(); mWorkingPath.Append(NS_LITERAL_CSTRING("\\") + node); return NS_OK; } NS_IMETHODIMP nsLocalFile::Normalize() { 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 shortcust, 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 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(followSymlinks); 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 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 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 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 item; nsCOMPtr 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, nsCString()); NS_ENSURE_SUCCESS(rv,rv); } else { if (followSymlinks) rv = file->CopyToFollowingLinksNative(target, nsCString()); else rv = file->CopyToNative(target, nsCString()); 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) { nsresult rv; PRBool isDir, isLink; nsXPIDLCString buf; const char *filePath; // 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). // // in the case of non-terminal shortcuts, those are all followed // unconditionally. this is done because 1) we only delete // terminal nodes and possibly their children, and 2) the remove // and rmdir CRT calls don't know how to handle shortcuts. IsSymlink(&isLink); if (isLink) { isDir = PR_FALSE; // resolve non-terminal nodes only. rv = ResolvePath(mWorkingPath.get(), PR_FALSE, getter_Copies(buf)); if (NS_FAILED(rv)) return rv; filePath = buf.get(); } else { rv = IsDirectory(&isDir); if (NS_FAILED(rv)) return rv; // in this case, it doesn't matter that terminal nodes were // resolved, so we can safely leverage mResolvedPath. filePath = mResolvedPath.get(); } 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 item; dirEnum.GetNext(getter_AddRefs(item)); nsCOMPtr file = do_QueryInterface(item); if (file) file->Remove(recursive); } } rv = rmdir(filePath); } else { rv = remove(filePath); } // fixup error code if necessary... if (rv == -1) rv = NSRESULT_FOR_ERRNO(); MakeDirty(); return rv; } NS_IMETHODIMP nsLocalFile::GetLastModifiedTime(PRInt64 *aLastModifiedTime) { NS_ENSURE_ARG(aLastModifiedTime); *aLastModifiedTime = 0; nsresult rv = ResolveAndStat(PR_TRUE); if (NS_FAILED(rv)) return rv; // microseconds -> milliseconds *aLastModifiedTime = mFileInfo64.modifyTime / PR_USEC_PER_MSEC; return NS_OK; } NS_IMETHODIMP nsLocalFile::GetLastModifiedTimeOfLink(PRInt64 *aLastModifiedTime) { NS_ENSURE_ARG(aLastModifiedTime); *aLastModifiedTime = 0; nsresult rv = ResolveAndStat(PR_FALSE); if (NS_FAILED(rv)) return rv; // microseconds -> milliseconds *aLastModifiedTime = mFileInfo64.modifyTime / PR_USEC_PER_MSEC; return NS_OK; } NS_IMETHODIMP nsLocalFile::SetLastModifiedTime(PRInt64 aLastModifiedTime) { return nsLocalFile::SetModDate(aLastModifiedTime, PR_TRUE); } NS_IMETHODIMP nsLocalFile::SetLastModifiedTimeOfLink(PRInt64 aLastModifiedTime) { return nsLocalFile::SetModDate(aLastModifiedTime, PR_FALSE); } nsresult nsLocalFile::SetModDate(PRInt64 aLastModifiedTime, PRBool resolveTerminal) { nsresult rv = ResolveAndStat(resolveTerminal); if (NS_FAILED(rv)) return rv; const char *filePath = mResolvedPath.get(); 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); MakeDirty(); if (!file) { 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; if ( 0 == SystemTimeToFileTime(&st, &lft) ) { rv = ConvertWinError(GetLastError()); } else if ( 0 == LocalFileTimeToFileTime(&lft, &ft) ) { rv = ConvertWinError(GetLastError()); } else if ( 0 == SetFileTime(file, NULL, &ft, &ft) ) { // could not set time rv = ConvertWinError(GetLastError()); } CloseHandle( file ); return rv; } NS_IMETHODIMP nsLocalFile::GetPermissions(PRUint32 *aPermissions) { nsresult rv = ResolveAndStat(PR_TRUE); if (NS_FAILED(rv)) return rv; const char *filePath = mResolvedPath.get(); return NS_OK; } NS_IMETHODIMP nsLocalFile::GetPermissionsOfLink(PRUint32 *aPermissionsOfLink) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsLocalFile::SetPermissions(PRUint32 aPermissions) { nsresult rv = ResolveAndStat(PR_TRUE); if (NS_FAILED(rv)) return rv; const char *filePath = mResolvedPath.get(); if( chmod(filePath, aPermissions) == -1 ) return NS_ERROR_FAILURE; return NS_OK; } NS_IMETHODIMP nsLocalFile::SetPermissionsOfLink(PRUint32 aPermissions) { nsresult rv = ResolveAndStat(PR_FALSE); if (NS_FAILED(rv)) return rv; const char *filePath = mResolvedPath.get(); if( chmod(filePath, aPermissions) == -1 ) return NS_ERROR_FAILURE; return NS_OK; } NS_IMETHODIMP nsLocalFile::GetFileSize(PRInt64 *aFileSize) { NS_ENSURE_ARG(aFileSize); *aFileSize = 0; nsresult rv = ResolveAndStat(PR_TRUE); if (NS_FAILED(rv)) return rv; *aFileSize = mFileInfo64.size; return NS_OK; } NS_IMETHODIMP nsLocalFile::SetFileSize(PRInt64 aFileSize) { DWORD status; HANDLE hFile; nsresult rv = ResolveAndStat(PR_TRUE); if (NS_FAILED(rv)) return rv; const char *filePath = mResolvedPath.get(); // Leave it to Microsoft to open an existing file with a function // named "CreateFile". hFile = CreateFile(filePath, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { MakeDirty(); return NS_ERROR_FAILURE; } // Seek to new, desired end of file PRInt32 hi, lo; myLL_L2II(aFileSize, &hi, &lo ); status = SetFilePointer(hFile, lo, NULL, FILE_BEGIN); if (status == 0xffffffff) goto error; // Truncate file at current cursor position if (!SetEndOfFile(hFile)) goto error; if (!CloseHandle(hFile)) return NS_ERROR_FAILURE; MakeDirty(); return NS_OK; error: MakeDirty(); CloseHandle(hFile); return NS_ERROR_FAILURE; } NS_IMETHODIMP nsLocalFile::GetFileSizeOfLink(PRInt64 *aFileSize) { NS_ENSURE_ARG(aFileSize); *aFileSize = 0; nsresult rv = ResolveAndStat(PR_FALSE); if (NS_FAILED(rv)) return rv; *aFileSize = mFileInfo64.size; return NS_OK; } NS_IMETHODIMP nsLocalFile::GetDiskSpaceAvailable(PRInt64 *aDiskSpaceAvailable) { NS_ENSURE_ARG(aDiskSpaceAvailable); ResolveAndStat(PR_FALSE); PRInt64 int64; LL_I2L(int64 , LONG_MAX); // Check disk space DWORD dwSecPerClus, dwBytesPerSec, dwFreeClus, dwTotalClus; ULARGE_INTEGER liFreeBytesAvailableToCaller, liTotalNumberOfBytes, liTotalNumberOfFreeBytes; double nBytes = 0; BOOL (WINAPI* getDiskFreeSpaceExA)(LPCTSTR lpDirectoryName, PULARGE_INTEGER lpFreeBytesAvailableToCaller, PULARGE_INTEGER lpTotalNumberOfBytes, PULARGE_INTEGER lpTotalNumberOfFreeBytes) = NULL; HINSTANCE hInst = LoadLibrary("KERNEL32.DLL"); NS_ASSERTION(hInst != NULL, "COULD NOT LOAD KERNEL32.DLL"); if (hInst != NULL) { getDiskFreeSpaceExA = (BOOL (WINAPI*)(LPCTSTR lpDirectoryName, PULARGE_INTEGER lpFreeBytesAvailableToCaller, PULARGE_INTEGER lpTotalNumberOfBytes, PULARGE_INTEGER lpTotalNumberOfFreeBytes)) GetProcAddress(hInst, "GetDiskFreeSpaceExA"); FreeLibrary(hInst); } if (getDiskFreeSpaceExA && (*getDiskFreeSpaceExA)(mResolvedPath.get(), &liFreeBytesAvailableToCaller, &liTotalNumberOfBytes, &liTotalNumberOfFreeBytes)) { nBytes = (double)(signed __int64)liFreeBytesAvailableToCaller.QuadPart; } else { char aDrive[_MAX_DRIVE + 2]; _splitpath( mResolvedPath.get(), aDrive, NULL, NULL, NULL); strcat(aDrive, "\\"); if ( GetDiskFreeSpace(aDrive, &dwSecPerClus, &dwBytesPerSec, &dwFreeClus, &dwTotalClus)) { nBytes = (double)dwFreeClus*(double)dwSecPerClus*(double) dwBytesPerSec; } } LL_D2L(*aDiskSpaceAvailable, nBytes); 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()); if (offset < 0) return NS_ERROR_FILE_UNRECOGNIZED_PATH; parentPath.Truncate(offset); nsCOMPtr 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); MakeDirty(); nsresult rv = ResolveAndStat( PR_TRUE ); if (NS_SUCCEEDED(rv)) *_retval = PR_TRUE; else *_retval = PR_FALSE; return NS_OK; } NS_IMETHODIMP nsLocalFile::IsWritable(PRBool *_retval) { NS_ENSURE_ARG(_retval); *_retval = PR_FALSE; nsresult rv = ResolveAndStat(PR_TRUE); if (NS_FAILED(rv)) return rv; const char *workingFilePath = mWorkingPath.get(); DWORD word = GetFileAttributes(workingFilePath); *_retval = !((word & FILE_ATTRIBUTE_READONLY) != 0); return NS_OK; } NS_IMETHODIMP nsLocalFile::IsReadable(PRBool *_retval) { NS_ENSURE_ARG(_retval); *_retval = PR_FALSE; nsresult rv = ResolveAndStat( PR_TRUE ); 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 = ResolveAndStat( PR_TRUE ); if (NS_FAILED(rv)) return rv; nsCAutoString path; PRBool symLink; rv = IsSymlink(&symLink); if (NS_FAILED(rv)) return rv; if (symLink) GetNativeTarget(path); else GetNativePath(path); // Get extension. char* ext = ::strrchr( path.get(), '.' ); if ( ext ) { // Convert extension to lower case. for( char *p = ext; *p; p++ ) { if ( ::isupper( *p ) ) { *p = ::tolower( *p ); } } // Search for any of the set of executable extensions. const char * const executableExts[] = { ".exe", ".bat", ".com", ".pif", ".cmd", ".js", ".vbs", ".lnk", ".reg", ".wsf", ".hta", 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_PRECONDITION(_retval, "null pointer"); nsresult rv = ResolveAndStat(PR_TRUE); if (NS_FAILED(rv)) { *_retval = PR_FALSE; return rv; } *_retval = (mFileInfo64.type == PR_FILE_DIRECTORY); return NS_OK; } NS_IMETHODIMP nsLocalFile::IsFile(PRBool *_retval) { NS_ENSURE_ARG(_retval); *_retval = PR_FALSE; nsresult rv = ResolveAndStat(PR_TRUE); if (NS_FAILED(rv)) return rv; *_retval = (mFileInfo64.type == PR_FILE_FILE); return rv; } NS_IMETHODIMP nsLocalFile::IsHidden(PRBool *_retval) { NS_ENSURE_ARG(_retval); *_retval = PR_FALSE; nsresult rv = ResolveAndStat(PR_TRUE); if (NS_FAILED(rv)) return rv; DWORD word; if (mFollowSymlinks) { const char *resolvedFilePath = mResolvedPath.get(); word = GetFileAttributes(resolvedFilePath); } else { const char *workingFilePath = mWorkingPath.get(); word = GetFileAttributes(workingFilePath); } *_retval = ((word & FILE_ATTRIBUTE_HIDDEN) != 0); return NS_OK; } NS_IMETHODIMP nsLocalFile::IsSymlink(PRBool *_retval) { NS_PRECONDITION(_retval, "null pointer"); PRUint32 len = mWorkingPath.Length(); if (len < 4) *_retval = PR_FALSE; else { const char* leaf = mWorkingPath.get() + len - 4; *_retval = (strnicmp(leaf, ".lnk", 4) == 0); } return NS_OK; } NS_IMETHODIMP nsLocalFile::IsSpecial(PRBool *_retval) { NS_ENSURE_ARG(_retval); *_retval = PR_FALSE; nsresult rv = ResolveAndStat(PR_TRUE); if (NS_FAILED(rv)) return rv; const char *workingFilePath = mWorkingPath.get(); DWORD word = GetFileAttributes(workingFilePath); *_retval = ((word & FILE_ATTRIBUTE_SYSTEM) != 0); return NS_OK; } 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(PR_TRUE); _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; 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() { nsresult rv = NS_OK; PRBool isDirectory = PR_FALSE; nsCAutoString path; nsAutoString unicodePath; IsDirectory(&isDirectory); if (isDirectory) { GetNativePath(path); } else { nsCOMPtr parent; GetParent(getter_AddRefs(parent)); if (parent) { parent->GetNativePath(path); parent->GetPath(unicodePath); } } // Remember the current fg window. HWND origWin, fgWin; origWin = fgWin = ::GetForegroundWindow(); // use the app registry name to launch a shell execute.... LONG r = (LONG) ::ShellExecute( NULL, "open", path.get(), NULL, NULL, SW_SHOWNORMAL); if (r < 32) return NS_ERROR_FAILURE; // If this is a directory, then we don't need to select a file. if (isDirectory) return NS_OK; // Resources we may need to free when done. IShellFolder *desktopFolder = 0; IMalloc *shellMalloc = 0; IShellFolder *folder = 0; LPITEMIDLIST folder_pidl = 0; LPITEMIDLIST file_pidl = 0; LPITEMIDLIST win95_file_pidl = 0; HMODULE shell32 = 0; // We break out of this do/while non-loop at any point where we have to give up. do { // Wait for the window to open. We wait a maximum of 2 seconds. // If we get the wrong window, that will be dealt with below. for (int iter = 10; iter; iter--) { fgWin = ::GetForegroundWindow(); if (fgWin != origWin) break; // for loopo ::Sleep(200); } // If we failed to locate the new window, give up. if (origWin == fgWin) break; // do/while // Now we have the explorer window. We need to send it the "select item" // message (which isn't trivial, so buckly your seat belt)... // We need the explorer's process id. DWORD pid = 0; ::GetWindowThreadProcessId(fgWin, &pid); // Get desktop folder. HRESULT rc = ::SHGetDesktopFolder(&desktopFolder); if (!desktopFolder) break; // Get IMalloc interface to use for shell pidls. rc = ::SHGetMalloc(&shellMalloc); if (!shellMalloc) break; // Convert folder path to pidl. This requires the Unicode path name. // It returns a pidl that must be freed via shellMalloc->Free. ULONG eaten = 0; rc = desktopFolder->ParseDisplayName( 0, 0, (LPOLESTR)unicodePath.get(), &eaten, &folder_pidl, 0 ); if (!folder_pidl) break; // Now get IShellFolder interface for the folder we opened. rc = desktopFolder->BindToObject( folder_pidl, 0, IID_IShellFolder, (void**)&folder ); if (!folder) break; // Now get file name pidl from that folder. nsAutoString unicodeLeaf; if (NS_FAILED(GetLeafName(unicodeLeaf))) break; rc = folder->ParseDisplayName( 0, 0, (LPOLESTR)unicodeLeaf.get(), &eaten, &file_pidl, 0 ); if (!file_pidl) break; // We need the module handle for shell32.dll. shell32 = ::GetModuleHandle("shell32.dll"); if (!shell32) break; // Allocate shared memory copy of pidl. This uses the undocumented "SHAllocShared" // function. Note that it is freed automatically after the ::SendMessage so we // don't have to free it. static HANDLE(WINAPI*SHAllocShared)(LPVOID,ULONG,DWORD) = (HANDLE(WINAPI*)(LPVOID,ULONG,DWORD))::GetProcAddress(shell32, (LPCTSTR)520); HANDLE pidlHandle = 0; if (SHAllocShared) { // We need the size of the pidl, which we get via another undocumented // API: "ILGetSize". UINT (WINAPI*ILGetSize)(LPCITEMIDLIST) = (UINT(WINAPI*)(LPCITEMIDLIST))::GetProcAddress(shell32, (LPCTSTR)152); if (!ILGetSize) break; pidlHandle = SHAllocShared((void*)(ITEMIDLIST*)file_pidl, ILGetSize(file_pidl), pid); if (!pidlHandle) break; } else { // On Win95, there is no SHAllocShared. Instead, we clone the file's pidl in // the shell's global heap (via ILGlobalClone) and pass that. LPITEMIDLIST(WINAPI*ILGlobalClone)(LPCITEMIDLIST) = (LPITEMIDLIST(WINAPI*)(LPCITEMIDLIST))::GetProcAddress(shell32, (LPCTSTR)20); if (!ILGlobalClone) break; win95_file_pidl = ILGlobalClone(file_pidl); if (!win95_file_pidl) break; // Arrange so that this pidl is passed on the ::SendMessage. pidlHandle = win95_file_pidl; } // Send message to select this file. ::SendMessage(fgWin, WM_USER+5, SVSI_SELECT | SVSI_DESELECTOTHERS | SVSI_ENSUREVISIBLE, (LPARAM)pidlHandle ); } while ( PR_FALSE ); // Clean up (freeing stuff as needed, in reverse order). if (win95_file_pidl) { // We need to free this using ILGlobalFree, another undocumented API. static void (WINAPI*ILGlobalFree)(LPCITEMIDLIST) = (void(WINAPI*)(LPCITEMIDLIST))::GetProcAddress(shell32,(LPCTSTR)156); if (ILGlobalFree) ILGlobalFree(win95_file_pidl); } if (file_pidl) shellMalloc->Free(file_pidl); if (folder_pidl) shellMalloc->Free(folder_pidl); if (folder) folder->Release(); if (shellMalloc) shellMalloc->Release(); if (desktopFolder) desktopFolder->Release(); return rv; } NS_IMETHODIMP nsLocalFile::Launch() { nsresult rv = NS_OK; 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; } } 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, nsCString()); 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, nsCString()); 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, nsCString()); 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 //----------------------------------------------------------------------------- void nsLocalFile::GlobalInit() { nsresult rv = NS_CreateShortcutResolver(); NS_ASSERTION(NS_SUCCEEDED(rv), "Shortcut resolver could not be created"); } void nsLocalFile::GlobalShutdown() { NS_DestroyShortcutResolver(); }