/* -*- Mode: C; tab-width: 2; 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.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 2001, 2002 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Conrad Carlen * Jungshik Shin * Asaf Romano * Mark Mentovai * Josh Aas * * Alternatively, the contents of this file may be used under the terms of * either 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 "nsLocalFile.h" #include "nsDirectoryServiceDefs.h" #include "nsObjCExceptions.h" #include "nsAutoPtr.h" #include "nsString.h" #include "nsReadableUtils.h" #include "nsIDirectoryEnumerator.h" #include "nsISimpleEnumerator.h" #include "nsITimelineService.h" #include "nsTArray.h" #include "plbase64.h" #include "prmem.h" #include "nsCRT.h" #include "nsHashKeys.h" #include "nsTraceRefcntImpl.h" #include #import #include #include #include #include #include #define CHECK_INIT() \ PR_BEGIN_MACRO \ if (!mBaseURL) \ return NS_ERROR_NOT_INITIALIZED; \ PR_END_MACRO static nsresult MacErrorMapper(OSErr inErr); static void CopyUTF8toUTF16NFC(const nsACString& aSrc, nsAString& aResult); static nsresult CFStringReftoUTF8(CFStringRef aInStrRef, nsACString& aOutStr); #pragma mark - #pragma mark [FSRef operator==] bool operator==(const FSRef& lhs, const FSRef& rhs) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; return (::FSCompareFSRefs(&lhs, &rhs) == noErr); NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); } #pragma mark - #pragma mark [StFollowLinksState] class StFollowLinksState { public: StFollowLinksState(nsLocalFile& aFile) : mFile(aFile) { mFile.GetFollowLinks(&mSavedState); } StFollowLinksState(nsLocalFile& aFile, PRBool followLinksState) : mFile(aFile) { mFile.GetFollowLinks(&mSavedState); mFile.SetFollowLinks(followLinksState); } ~StFollowLinksState() { mFile.SetFollowLinks(mSavedState); } private: nsLocalFile& mFile; PRBool mSavedState; }; #pragma mark - #pragma mark [nsDirEnumerator] class nsDirEnumerator : public nsISimpleEnumerator, public nsIDirectoryEnumerator { public: NS_DECL_ISUPPORTS nsDirEnumerator() : mIterator(nsnull), mFSRefsArray(nsnull), mArrayCnt(0), mArrayIndex(0) { } nsresult Init(nsILocalFileMac* parent) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; NS_ENSURE_ARG(parent); OSErr err; nsresult rv; FSRef parentRef; rv = parent->GetFSRef(&parentRef); if (NS_FAILED(rv)) return rv; mFSRefsArray = (FSRef *)nsMemory::Alloc(sizeof(FSRef) * kRequestCountPerIteration); if (!mFSRefsArray) return NS_ERROR_OUT_OF_MEMORY; err = ::FSOpenIterator(&parentRef, kFSIterateFlat, &mIterator); if (err != noErr) return MacErrorMapper(err); return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHOD HasMoreElements(PRBool *result) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; if (!mIterator || !mFSRefsArray) { *result = PR_FALSE; return NS_OK; } if (mNext == nsnull) { if (mArrayIndex >= mArrayCnt) { ItemCount actualCnt; OSErr err = ::FSGetCatalogInfoBulk(mIterator, kRequestCountPerIteration, &actualCnt, nsnull, kFSCatInfoNone, nsnull, mFSRefsArray, nsnull, nsnull); if (err == noErr || err == errFSNoMoreItems) { mArrayCnt = actualCnt; mArrayIndex = 0; } } if (mArrayIndex < mArrayCnt) { nsRefPtr newFile = new nsLocalFile; if (!newFile) return NS_ERROR_OUT_OF_MEMORY; FSRef fsRef = mFSRefsArray[mArrayIndex]; if (NS_FAILED(newFile->InitWithFSRef(&fsRef))) return NS_ERROR_FAILURE; mArrayIndex++; mNext = newFile; } } *result = mNext != nsnull; if (!*result) Close(); return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHOD GetNext(nsISupports **result) { NS_ENSURE_ARG_POINTER(result); *result = nsnull; 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; } NS_IMETHOD GetNextFile(nsIFile **result) { *result = nsnull; PRBool hasMore = PR_FALSE; nsresult rv = HasMoreElements(&hasMore); if (NS_FAILED(rv) || !hasMore) return rv; *result = mNext; NS_IF_ADDREF(*result); mNext = nsnull; return NS_OK; } NS_IMETHOD Close() { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; if (mIterator) { ::FSCloseIterator(mIterator); mIterator = nsnull; } if (mFSRefsArray) { nsMemory::Free(mFSRefsArray); mFSRefsArray = nsnull; } return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } private: ~nsDirEnumerator() { Close(); } protected: // According to Apple doc, request the number of objects // per call that will fit in 4 VM pages. enum { kRequestCountPerIteration = ((4096 * 4) / sizeof(FSRef)) }; nsCOMPtr mNext; FSIterator mIterator; FSRef *mFSRefsArray; PRInt32 mArrayCnt, mArrayIndex; }; NS_IMPL_ISUPPORTS2(nsDirEnumerator, nsISimpleEnumerator, nsIDirectoryEnumerator) const char kPathSepChar = '/'; #pragma mark - #pragma mark [CTORs/DTOR] nsLocalFile::nsLocalFile() : mBaseURL(NULL), mFollowLinks(PR_TRUE) { mPath[0] = '\0'; } nsLocalFile::nsLocalFile(const nsLocalFile& src) : mBaseURL(NULL), mFollowLinks(src.mFollowLinks) { SetBaseURL(src.mBaseURL); } nsLocalFile::~nsLocalFile() { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; if (mBaseURL) ::CFRelease(mBaseURL); NS_OBJC_END_TRY_ABORT_BLOCK; } #pragma mark - #pragma mark [nsISupports] NS_IMPL_THREADSAFE_ISUPPORTS4(nsLocalFile, nsILocalFileMac, nsILocalFile, nsIFile, nsIHashable) NS_IMETHODIMP 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; } #pragma mark - #pragma mark [nsIFile] NS_IMETHODIMP nsLocalFile::Append(const nsAString& aNode) { return AppendNative(NS_ConvertUTF16toUTF8(aNode)); } NS_IMETHODIMP nsLocalFile::AppendNative(const nsACString& aNode) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; CHECK_INIT(); nsACString::const_iterator start, end; aNode.BeginReading(start); aNode.EndReading(end); if (FindCharInReadable(kPathSepChar, start, end)) return NS_ERROR_FILE_UNRECOGNIZED_PATH; CFStringRef nodeStrRef = ::CFStringCreateWithCString(kCFAllocatorDefault, PromiseFlatCString(aNode).get(), kCFStringEncodingUTF8); if (nodeStrRef) { CFURLRef newRef = ::CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault, mBaseURL, nodeStrRef, PR_FALSE); ::CFRelease(nodeStrRef); if (newRef) { SetBaseURL(newRef); ::CFRelease(newRef); return NS_OK; } } return NS_ERROR_FAILURE; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::Normalize() { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; CHECK_INIT(); char resolved_path[PATH_MAX] = ""; if (!realpath(mPath, resolved_path)) return NSRESULT_FOR_ERRNO(); // Need to know whether we're a directory to create a new CFURLRef PRBool isDirectory; nsresult rv = IsDirectory(&isDirectory); NS_ENSURE_SUCCESS(rv, rv); rv = NS_ERROR_FAILURE; CFStringRef pathStrRef = ::CFStringCreateWithCString(kCFAllocatorDefault, resolved_path, kCFStringEncodingUTF8); if (pathStrRef) { CFURLRef newURLRef = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault, pathStrRef, kCFURLPOSIXPathStyle, isDirectory); if (newURLRef) { SetBaseURL(newURLRef); ::CFRelease(newURLRef); rv = NS_OK; } ::CFRelease(pathStrRef); } return rv; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::Create(PRUint32 type, PRUint32 permissions) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; if (type != NORMAL_FILE_TYPE && type != DIRECTORY_TYPE) return NS_ERROR_FILE_UNKNOWN_TYPE; CHECK_INIT(); nsTArray nonExtantNodes; CFURLRef pathURLRef = mBaseURL; FSRef pathFSRef; CFStringRef leafStrRef = nsnull; nsAutoTArray buffer; Boolean success; // Work backwards through the path to find the last node which // exists. Place the nodes which don't exist in an array and we'll // create those below. while ((success = ::CFURLGetFSRef(pathURLRef, &pathFSRef)) == false) { leafStrRef = ::CFURLCopyLastPathComponent(pathURLRef); if (!leafStrRef) break; CFIndex leafLen = ::CFStringGetLength(leafStrRef); if (!buffer.SetLength(leafLen + 1)) break; ::CFStringGetCharacters(leafStrRef, CFRangeMake(0, leafLen), buffer.Elements()); buffer[leafLen] = '\0'; nonExtantNodes.AppendElement(nsString(nsDependentString(buffer.Elements()))); ::CFRelease(leafStrRef); leafStrRef = nsnull; // Get the parent of the leaf for the next go round CFURLRef parent = ::CFURLCreateCopyDeletingLastPathComponent(NULL, pathURLRef); if (!parent) break; if (pathURLRef != mBaseURL) ::CFRelease(pathURLRef); pathURLRef = parent; } if (pathURLRef != mBaseURL) ::CFRelease(pathURLRef); if (leafStrRef != nsnull) ::CFRelease(leafStrRef); if (!success) return NS_ERROR_FAILURE; PRInt32 nodesToCreate = PRInt32(nonExtantNodes.Length()); if (nodesToCreate == 0) return NS_ERROR_FILE_ALREADY_EXISTS; OSErr err; nsAutoString nextNodeName; for (PRInt32 i = nodesToCreate - 1; i > 0; i--) { nextNodeName = nonExtantNodes[i]; err = ::FSCreateDirectoryUnicode(&pathFSRef, nextNodeName.Length(), (const UniChar *)nextNodeName.get(), kFSCatInfoNone, nsnull, &pathFSRef, nsnull, nsnull); if (err != noErr) return MacErrorMapper(err); } nextNodeName = nonExtantNodes[0]; if (type == NORMAL_FILE_TYPE) { err = ::FSCreateFileUnicode(&pathFSRef, nextNodeName.Length(), (const UniChar *)nextNodeName.get(), kFSCatInfoNone, nsnull, nsnull, nsnull); } else { err = ::FSCreateDirectoryUnicode(&pathFSRef, nextNodeName.Length(), (const UniChar *)nextNodeName.get(), kFSCatInfoNone, nsnull, nsnull, nsnull, nsnull); } return MacErrorMapper(err); NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::GetLeafName(nsAString& aLeafName) { nsCAutoString nativeString; nsresult rv = GetNativeLeafName(nativeString); if (NS_FAILED(rv)) return rv; CopyUTF8toUTF16NFC(nativeString, aLeafName); return NS_OK; } NS_IMETHODIMP nsLocalFile::SetLeafName(const nsAString& aLeafName) { return SetNativeLeafName(NS_ConvertUTF16toUTF8(aLeafName)); } NS_IMETHODIMP nsLocalFile::GetNativeLeafName(nsACString& aNativeLeafName) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; CHECK_INIT(); nsresult rv = NS_ERROR_FAILURE; CFStringRef leafStrRef = ::CFURLCopyLastPathComponent(mBaseURL); if (leafStrRef) { rv = CFStringReftoUTF8(leafStrRef, aNativeLeafName); ::CFRelease(leafStrRef); } return rv; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::SetNativeLeafName(const nsACString& aNativeLeafName) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; CHECK_INIT(); nsresult rv = NS_ERROR_FAILURE; CFURLRef parentURLRef = ::CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorDefault, mBaseURL); if (parentURLRef) { CFStringRef nodeStrRef = ::CFStringCreateWithCString(kCFAllocatorDefault, PromiseFlatCString(aNativeLeafName).get(), kCFStringEncodingUTF8); if (nodeStrRef) { CFURLRef newURLRef = ::CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault, parentURLRef, nodeStrRef, PR_FALSE); if (newURLRef) { SetBaseURL(newURLRef); ::CFRelease(newURLRef); rv = NS_OK; } ::CFRelease(nodeStrRef); } ::CFRelease(parentURLRef); } return rv; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::CopyTo(nsIFile *newParentDir, const nsAString& newName) { return CopyInternal(newParentDir, newName, PR_FALSE); } NS_IMETHODIMP nsLocalFile::CopyToNative(nsIFile *newParentDir, const nsACString& newName) { return CopyInternal(newParentDir, NS_ConvertUTF8toUTF16(newName), PR_FALSE); } NS_IMETHODIMP nsLocalFile::CopyToFollowingLinks(nsIFile *newParentDir, const nsAString& newName) { return CopyInternal(newParentDir, newName, PR_TRUE); } NS_IMETHODIMP nsLocalFile::CopyToFollowingLinksNative(nsIFile *newParentDir, const nsACString& newName) { return CopyInternal(newParentDir, NS_ConvertUTF8toUTF16(newName), PR_TRUE); } NS_IMETHODIMP nsLocalFile::MoveTo(nsIFile *newParentDir, const nsAString& newName) { return MoveToNative(newParentDir, NS_ConvertUTF16toUTF8(newName)); } NS_IMETHODIMP nsLocalFile::MoveToNative(nsIFile *newParentDir, const nsACString& newName) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; CHECK_INIT(); StFollowLinksState followLinks(*this, PR_FALSE); PRBool isDirectory; nsresult rv = IsDirectory(&isDirectory); if (NS_FAILED(rv)) return rv; // Build the destination path. nsCOMPtr parentDir = newParentDir; if (!parentDir) { if (newName.IsEmpty()) return NS_ERROR_INVALID_ARG; rv = GetParent(getter_AddRefs(parentDir)); if (NS_FAILED(rv)) return rv; } else { PRBool exists; rv = parentDir->Exists(&exists); if (NS_FAILED(rv)) return rv; if (!exists) { rv = parentDir->Create(nsIFile::DIRECTORY_TYPE, 0777); if (NS_FAILED(rv)) return rv; } } nsCAutoString destPath; rv = parentDir->GetNativePath(destPath); if (NS_FAILED(rv)) return rv; if (!newName.IsEmpty()) destPath.Append(NS_LITERAL_CSTRING("/") + newName); else { nsCAutoString leafName; rv = GetNativeLeafName(leafName); if (NS_FAILED(rv)) return rv; destPath.Append(NS_LITERAL_CSTRING("/") + leafName); } // Perform the move. if (rename(mPath, destPath.get()) != 0) { if (errno == EXDEV) { // Can't move across volume (device) boundaries. Copy and remove. rv = CopyToNative(parentDir, newName); if (NS_SUCCEEDED(rv)) { // Permit removal failure. Remove(PR_TRUE); } } else rv = NSRESULT_FOR_ERRNO(); if (NS_FAILED(rv)) return rv; } // Update |this| to refer to the moved file. CFURLRef newBaseRef = ::CFURLCreateFromFileSystemRepresentation(NULL, (UInt8*)destPath.get(), destPath.Length(), isDirectory); if (!newBaseRef) return NS_ERROR_FAILURE; SetBaseURL(newBaseRef); ::CFRelease(newBaseRef); return rv; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::Remove(PRBool recursive) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; CHECK_INIT(); // if we're a symlink, never remove target StFollowLinksState followLinks(*this, PR_FALSE); PRBool isDirectory; nsresult rv = IsDirectory(&isDirectory); if (NS_FAILED(rv)) return rv; if (recursive && isDirectory) { CFURLRef urlRef; rv = GetCFURL(&urlRef); if (NS_SUCCEEDED(rv) && urlRef) { NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; BOOL removeSuccess = [[NSFileManager defaultManager] removeFileAtPath:[(NSURL*)urlRef path] handler:nil]; [ap release]; ::CFRelease(urlRef); rv = removeSuccess ? NS_OK : NS_ERROR_FAILURE; } } else { int status; if (isDirectory) status = rmdir(mPath); else status = unlink(mPath); if (status != 0) rv = NSRESULT_FOR_ERRNO(); } return rv; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } // Only send back permissions bits: maybe we want to send back the whole // mode_t to permit checks against other file types? #define NORMALIZE_PERMS(mode) ((mode)& (S_IRWXU | S_IRWXG | S_IRWXO)) NS_IMETHODIMP nsLocalFile::GetPermissions(PRUint32 *aPermissions) { NS_ENSURE_ARG_POINTER(aPermissions); CHECK_INIT(); struct STAT buf; nsresult rv = FillStatBufferInternal(&buf); if (NS_FAILED(rv)) return NSRESULT_FOR_ERRNO(); *aPermissions = NORMALIZE_PERMS(buf.st_mode); return NS_OK; } NS_IMETHODIMP nsLocalFile::SetPermissions(PRUint32 aPermissions) { CHECK_INIT(); if (chmod(mPath, aPermissions) < 0) return NSRESULT_FOR_ERRNO(); return NS_OK; } NS_IMETHODIMP nsLocalFile::GetPermissionsOfLink(PRUint32 *aPermissionsOfLink) { NS_ERROR("NS_ERROR_NOT_IMPLEMENTED"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsLocalFile::SetPermissionsOfLink(PRUint32 aPermissionsOfLink) { NS_ERROR("NS_ERROR_NOT_IMPLEMENTED"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsLocalFile::GetLastModifiedTime(PRInt64 *aLastModifiedTime) { NS_ENSURE_ARG_POINTER(aLastModifiedTime); nsCAutoString path; nsresult rv = GetPathInternal(path); if (NS_FAILED(rv)) return rv; PRFileInfo64 info; if (PR_GetFileInfo64(path.get(), &info) != PR_SUCCESS) return NSRESULT_FOR_ERRNO(); PRInt64 modTime = PRInt64(info.modifyTime); if (modTime == 0) *aLastModifiedTime = 0; else *aLastModifiedTime = modTime / PRInt64(PR_USEC_PER_MSEC); return NS_OK; } NS_IMETHODIMP nsLocalFile::SetLastModifiedTime(PRInt64 aLastModifiedTime) { nsCAutoString path; nsresult rv = GetPathInternal(path); if (NS_FAILED(rv)) return rv; int result; if (aLastModifiedTime != 0) { struct STAT statBuf; nsresult rv = FillStatBufferInternal(&statBuf); if (NS_FAILED(rv)) return rv; struct utimbuf ut; ut.actime = statBuf.st_atime; // convert milliseconds to seconds since the unix epoch ut.modtime = (time_t)(PRFloat64(aLastModifiedTime) / PR_MSEC_PER_SEC); result = utime(path.get(), &ut); } else { result = utime(path.get(), nsnull); } return NSRESULT_FOR_RETURN(result); } NS_IMETHODIMP nsLocalFile::GetLastModifiedTimeOfLink(PRInt64 *aLastModifiedTimeOfLink) { NS_ENSURE_ARG(aLastModifiedTimeOfLink); nsCAutoString path; nsresult rv = GetPathInternal(path); if (NS_FAILED(rv)) return rv; struct STAT sbuf; if (LSTAT(path.get(), &sbuf) == -1) return NSRESULT_FOR_ERRNO(); *aLastModifiedTimeOfLink = PRInt64(sbuf.st_mtime) * PRInt64(PR_MSEC_PER_SEC); return NS_OK; } NS_IMETHODIMP nsLocalFile::SetLastModifiedTimeOfLink(PRInt64 aLastModifiedTimeOfLink) { return SetLastModifiedTime(aLastModifiedTimeOfLink); } NS_IMETHODIMP nsLocalFile::GetFileSize(PRInt64 *aFileSize) { NS_ENSURE_ARG_POINTER(aFileSize); *aFileSize = 0; struct STAT buf; nsresult rv = FillStatBufferInternal(&buf); if (NS_FAILED(rv)) return rv; if (!S_ISDIR(buf.st_mode)) *aFileSize = (PRInt64)buf.st_size; return NS_OK; } NS_IMETHODIMP nsLocalFile::SetFileSize(PRInt64 aFileSize) { CHECK_INIT(); off_t size = (off_t)aFileSize; if (truncate(mPath, size) == -1) return NSRESULT_FOR_ERRNO(); return NS_OK; } NS_IMETHODIMP nsLocalFile::GetFileSizeOfLink(PRInt64 *aFileSizeOfLink) { CHECK_INIT(); NS_ENSURE_ARG_POINTER(aFileSizeOfLink); StFollowLinksState followLinks(*this, PR_FALSE); return GetFileSize(aFileSizeOfLink); } NS_IMETHODIMP nsLocalFile::GetTarget(nsAString& aTarget) { nsCAutoString nativeString; nsresult rv = GetNativeTarget(nativeString); if (NS_FAILED(rv)) return rv; CopyUTF8toUTF16NFC(nativeString, aTarget); return NS_OK; } NS_IMETHODIMP nsLocalFile::GetNativeTarget(nsACString& aNativeTarget) { CHECK_INIT(); aNativeTarget.Truncate(); struct STAT symStat; if (LSTAT(mPath, &symStat) == -1) return NSRESULT_FOR_ERRNO(); if (!S_ISLNK(symStat.st_mode)) return NS_ERROR_FILE_INVALID_PATH; PRInt32 size = (PRInt32)symStat.st_size; char *target = (char *)nsMemory::Alloc(size + 1); if (!target) return NS_ERROR_OUT_OF_MEMORY; if (readlink(mPath, target, (size_t)size) < 0) { nsMemory::Free(target); return NSRESULT_FOR_ERRNO(); } target[size] = '\0'; nsresult rv = NS_OK; nsCOMPtr self(this); PRInt32 maxLinks = 40; while (PR_TRUE) { if (maxLinks-- == 0) { rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; break; } if (target[0] != '/') { nsCOMPtr parent; if (NS_FAILED(rv = self->GetParent(getter_AddRefs(parent)))) break; nsCOMPtr localFile(do_QueryInterface(parent, &rv)); if (NS_FAILED(rv)) break; if (NS_FAILED(rv = localFile->AppendRelativeNativePath(nsDependentCString(target)))) break; if (NS_FAILED(rv = localFile->GetNativePath(aNativeTarget))) break; self = parent; } else { aNativeTarget = target; } const nsPromiseFlatCString &flatRetval = PromiseFlatCString(aNativeTarget); // Any failure in testing the current target we'll just interpret // as having reached our destiny. if (LSTAT(flatRetval.get(), &symStat) == -1) break; // And of course we're done if it isn't a symlink. if (!S_ISLNK(symStat.st_mode)) break; PRInt32 newSize = (PRInt32)symStat.st_size; if (newSize > size) { char *newTarget = (char *)nsMemory::Realloc(target, newSize + 1); if (!newTarget) { rv = NS_ERROR_OUT_OF_MEMORY; break; } target = newTarget; size = newSize; } PRInt32 linkLen = readlink(flatRetval.get(), target, size); if (linkLen == -1) { rv = NSRESULT_FOR_ERRNO(); break; } target[linkLen] = '\0'; } nsMemory::Free(target); if (NS_FAILED(rv)) aNativeTarget.Truncate(); return rv; } NS_IMETHODIMP nsLocalFile::GetPath(nsAString& aPath) { nsCAutoString nativeString; nsresult rv = GetNativePath(nativeString); if (NS_FAILED(rv)) return rv; CopyUTF8toUTF16NFC(nativeString, aPath); return NS_OK; } NS_IMETHODIMP nsLocalFile::GetNativePath(nsACString& aNativePath) { CHECK_INIT(); aNativePath.Assign(mPath); return NS_OK; } NS_IMETHODIMP nsLocalFile::Exists(PRBool *_retval) { NS_ENSURE_ARG_POINTER(_retval); struct STAT buf; *_retval = (STAT(mPath, &buf) == 0); return NS_OK; } NS_IMETHODIMP nsLocalFile::IsWritable(PRBool *_retval) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; CHECK_INIT(); NS_ENSURE_ARG_POINTER(_retval); *_retval = PR_FALSE; NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; // don't bother resolving, this always traverses symbolic links *_retval = (PRBool)[[NSFileManager defaultManager] isWritableFileAtPath:[(NSURL*)mBaseURL path]]; [ap release]; return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::IsReadable(PRBool *_retval) { CHECK_INIT(); NS_ENSURE_ARG_POINTER(_retval); *_retval = PR_FALSE; PRUint32 permissions; nsresult rv = GetPermissions(&permissions); if (NS_FAILED(rv)) return rv; *_retval = ((permissions & S_IRUSR) != 0); return NS_OK; } NS_IMETHODIMP nsLocalFile::IsExecutable(PRBool *_retval) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; CHECK_INIT(); NS_ENSURE_ARG_POINTER(_retval); *_retval = PR_FALSE; FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; LSRequestedInfo theInfoRequest = kLSRequestAllInfo; LSItemInfoRecord theInfo; if (::LSCopyItemInfoForRef(&fsRef, theInfoRequest, &theInfo) == noErr) { if ((theInfo.flags & kLSItemInfoIsApplication) != 0) *_retval = PR_TRUE; } return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::IsHidden(PRBool *_retval) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; NS_ENSURE_ARG_POINTER(_retval); *_retval = PR_FALSE; FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; FSCatalogInfo catalogInfo; HFSUniStr255 leafName; OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo, &leafName, nsnull, nsnull); if (err != noErr) return MacErrorMapper(err); FileInfo *fInfoPtr = (FileInfo *)(catalogInfo.finderInfo); // Finder flags are in the same place whether we use FileInfo or FolderInfo if ((fInfoPtr->finderFlags & kIsInvisible) != 0) { *_retval = PR_TRUE; } else { // If the leaf name begins with a '.', consider it invisible if (leafName.length >= 1 && leafName.unicode[0] == UniChar('.')) *_retval = PR_TRUE; } return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::IsDirectory(PRBool *_retval) { NS_ENSURE_ARG_POINTER(_retval); *_retval = PR_FALSE; struct STAT buf; nsresult rv = FillStatBufferInternal(&buf); if (NS_FAILED(rv)) return rv; *_retval = S_ISDIR(buf.st_mode); return NS_OK; } NS_IMETHODIMP nsLocalFile::IsFile(PRBool *_retval) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; NS_ENSURE_ARG_POINTER(_retval); *_retval = PR_FALSE; struct STAT buf; nsresult rv = FillStatBufferInternal(&buf); if (NS_FAILED(rv)) return rv; *_retval = S_ISREG(buf.st_mode); return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::IsSymlink(PRBool *_retval) { CHECK_INIT(); NS_ENSURE_ARG(_retval); *_retval = PR_FALSE; struct stat symStat; if (lstat(mPath, &symStat) < 0) return NSRESULT_FOR_ERRNO(); *_retval = S_ISLNK(symStat.st_mode); return NS_OK; } NS_IMETHODIMP nsLocalFile::IsSpecial(PRBool *_retval) { NS_ERROR("NS_ERROR_NOT_IMPLEMENTED"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsLocalFile::Clone(nsIFile **_retval) { // Just copy-construct ourselves *_retval = new nsLocalFile(*this); if (!*_retval) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(*_retval); return NS_OK; } NS_IMETHODIMP nsLocalFile::Equals(nsIFile *inFile, PRBool *_retval) { NS_ENSURE_ARG(inFile); NS_ENSURE_ARG_POINTER(_retval); *_retval = PR_FALSE; nsCAutoString inPath; nsresult rv = inFile->GetNativePath(inPath); if (NS_FAILED(rv)) return rv; *_retval = !strcmp(mPath, inPath.get()); return NS_OK; } NS_IMETHODIMP nsLocalFile::Contains(nsIFile *inFile, PRBool recur, PRBool *_retval) { CHECK_INIT(); NS_ENSURE_ARG_POINTER(_retval); *_retval = PR_FALSE; PRBool isDir; nsresult rv = IsDirectory(&isDir); if (NS_FAILED(rv)) return rv; if (!isDir) return NS_OK; // must be a dir to contain someone nsCAutoString thisPath, inPath; if (NS_FAILED(GetNativePath(thisPath)) || NS_FAILED(inFile->GetNativePath(inPath))) return NS_ERROR_FAILURE; size_t thisPathLen = thisPath.Length(); if ((inPath.Length() > thisPathLen + 1) && (strncasecmp(thisPath.get(), inPath.get(), thisPathLen) == 0)) { // Now make sure that the |inFile|'s path has a separator at thisPathLen, // and there's at least one more character after that. if (inPath[thisPathLen] == kPathSepChar) *_retval = PR_TRUE; } return NS_OK; } NS_IMETHODIMP nsLocalFile::GetParent(nsIFile * *aParent) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; NS_ENSURE_ARG_POINTER(aParent); *aParent = nsnull; CHECK_INIT(); // If it can be determined without error that a file does not // have a parent, return nsnull for the parent and NS_OK as the result. // See bug 133617. nsresult rv = NS_OK; CFURLRef parentURLRef = ::CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorDefault, mBaseURL); if (parentURLRef) { // If the parent path is longer than file's path then // CFURLCreateCopyDeletingLastPathComponent must have simply added // two dots at the end - in this case indicate that there is no parent. // See bug 332389. CFStringRef path = ::CFURLGetString(mBaseURL); CFStringRef newPath = ::CFURLGetString(parentURLRef); if (::CFStringGetLength(newPath) < ::CFStringGetLength(path)) { rv = NS_ERROR_FAILURE; nsRefPtr newFile = new nsLocalFile; if (newFile) { rv = newFile->InitWithCFURL(parentURLRef); if (NS_SUCCEEDED(rv)) { NS_ADDREF(*aParent = newFile); rv = NS_OK; } } } ::CFRelease(parentURLRef); } return rv; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator **aDirectoryEntries) { NS_ENSURE_ARG_POINTER(aDirectoryEntries); *aDirectoryEntries = nsnull; nsresult rv; 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; } *aDirectoryEntries = dirEnum; return NS_OK; } #pragma mark - #pragma mark [nsILocalFile] NS_IMETHODIMP nsLocalFile::InitWithPath(const nsAString& filePath) { return InitWithNativePath(NS_ConvertUTF16toUTF8(filePath)); } NS_IMETHODIMP nsLocalFile::InitWithNativePath(const nsACString& filePath) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; nsCAutoString fixedPath; if (Substring(filePath, 0, 2).EqualsLiteral("~/")) { nsCOMPtr homeDir; nsCAutoString homePath; nsresult rv = NS_GetSpecialDirectory(NS_OS_HOME_DIR, getter_AddRefs(homeDir)); NS_ENSURE_SUCCESS(rv, rv); rv = homeDir->GetNativePath(homePath); NS_ENSURE_SUCCESS(rv, rv); fixedPath = homePath + Substring(filePath, 1, filePath.Length() - 1); } else if (filePath.IsEmpty() || filePath.First() != '/') return NS_ERROR_FILE_UNRECOGNIZED_PATH; else fixedPath.Assign(filePath); // A path with consecutive '/'s which are not between // nodes crashes CFURLGetFSRef(). Consecutive '/'s which // are between actual nodes are OK. So, convert consecutive // '/'s to a single one. fixedPath.ReplaceSubstring("//", "/"); // On 10.2, huge paths also crash CFURLGetFSRef() if (fixedPath.Length() > PATH_MAX) return NS_ERROR_FILE_NAME_TOO_LONG; CFStringRef pathAsCFString; CFURLRef pathAsCFURL; pathAsCFString = ::CFStringCreateWithCString(nsnull, fixedPath.get(), kCFStringEncodingUTF8); if (!pathAsCFString) return NS_ERROR_FAILURE; pathAsCFURL = ::CFURLCreateWithFileSystemPath(nsnull, pathAsCFString, kCFURLPOSIXPathStyle, PR_FALSE); if (!pathAsCFURL) { ::CFRelease(pathAsCFString); return NS_ERROR_FAILURE; } SetBaseURL(pathAsCFURL); ::CFRelease(pathAsCFURL); ::CFRelease(pathAsCFString); return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::InitWithFile(nsILocalFile *aFile) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; NS_ENSURE_ARG(aFile); nsCOMPtr aFileMac(do_QueryInterface(aFile)); if (!aFileMac) return NS_ERROR_UNEXPECTED; CFURLRef urlRef; nsresult rv = aFileMac->GetCFURL(&urlRef); if (NS_FAILED(rv)) return rv; rv = InitWithCFURL(urlRef); ::CFRelease(urlRef); return rv; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::GetFollowLinks(PRBool *aFollowLinks) { NS_ENSURE_ARG_POINTER(aFollowLinks); *aFollowLinks = mFollowLinks; return NS_OK; } NS_IMETHODIMP nsLocalFile::SetFollowLinks(PRBool aFollowLinks) { mFollowLinks = aFollowLinks; return NS_OK; } NS_IMETHODIMP nsLocalFile::OpenNSPRFileDesc(PRInt32 flags, PRInt32 mode, PRFileDesc **_retval) { NS_ENSURE_ARG_POINTER(_retval); nsCAutoString path; nsresult rv = GetPathInternal(path); if (NS_FAILED(rv)) return rv; *_retval = PR_Open(path.get(), flags, mode); if (! *_retval) return NS_ErrorAccordingToNSPR(); if (flags & DELETE_ON_CLOSE) { PR_Delete(path.get()); } return NS_OK; } NS_IMETHODIMP nsLocalFile::OpenANSIFileDesc(const char *mode, FILE **_retval) { NS_ENSURE_ARG_POINTER(_retval); nsCAutoString path; nsresult rv = GetPathInternal(path); if (NS_FAILED(rv)) return rv; *_retval = fopen(path.get(), mode); if (! *_retval) return NS_ERROR_FAILURE; return NS_OK; } NS_IMETHODIMP nsLocalFile::Load(PRLibrary **_retval) { CHECK_INIT(); NS_ENSURE_ARG_POINTER(_retval); NS_TIMELINE_START_TIMER("PR_LoadLibrary"); nsCAutoString path; nsresult rv = GetPathInternal(path); if (NS_FAILED(rv)) return rv; #ifdef NS_BUILD_REFCNT_LOGGING nsTraceRefcntImpl::SetActivityIsLegal(PR_FALSE); #endif *_retval = PR_LoadLibrary(path.get()); #ifdef NS_BUILD_REFCNT_LOGGING nsTraceRefcntImpl::SetActivityIsLegal(PR_TRUE); #endif NS_TIMELINE_STOP_TIMER("PR_LoadLibrary"); NS_TIMELINE_MARK_TIMER1("PR_LoadLibrary", path.get()); if (!*_retval) return NS_ERROR_FAILURE; return NS_OK; } NS_IMETHODIMP nsLocalFile::GetDiskSpaceAvailable(PRInt64 *aDiskSpaceAvailable) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; NS_ENSURE_ARG_POINTER(aDiskSpaceAvailable); nsCAutoString path; nsresult rv = GetPathInternal(path); if (NS_FAILED(rv)) return rv; struct STATVFS fs_buf; if (STATVFS(path.get(), &fs_buf) < 0) return NS_ERROR_FAILURE; // minus one block for fuzz *aDiskSpaceAvailable = (PRInt64)fs_buf.f_frsize * (fs_buf.f_bavail - 1); return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::AppendRelativePath(const nsAString& relativeFilePath) { return AppendRelativeNativePath(NS_ConvertUTF16toUTF8(relativeFilePath)); } NS_IMETHODIMP nsLocalFile::AppendRelativeNativePath(const nsACString& relativeFilePath) { if (relativeFilePath.IsEmpty()) return NS_OK; // No leading '/' if (relativeFilePath.First() == '/') return NS_ERROR_FILE_UNRECOGNIZED_PATH; // Parse the nodes and call Append() for each nsACString::const_iterator nodeBegin, pathEnd; relativeFilePath.BeginReading(nodeBegin); relativeFilePath.EndReading(pathEnd); nsACString::const_iterator nodeEnd(nodeBegin); while (nodeEnd != pathEnd) { FindCharInReadable(kPathSepChar, nodeEnd, pathEnd); nsresult rv = AppendNative(Substring(nodeBegin, nodeEnd)); if (NS_FAILED(rv)) return rv; if (nodeEnd != pathEnd) // If there's more left in the string, inc over the '/' nodeEnd is on. ++nodeEnd; nodeBegin = nodeEnd; } return NS_OK; } NS_IMETHODIMP nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; AliasHandle aliasH; OSErr err = ::FSNewAlias(nsnull, &fsRef, &aliasH); if (err != noErr) return MacErrorMapper(err); PRUint32 bytes = ::GetHandleSize((Handle) aliasH); ::HLock((Handle) aliasH); // Passing nsnull for dest makes NULL-term string char* buf = PL_Base64Encode((const char*)*aliasH, bytes, nsnull); ::DisposeHandle((Handle) aliasH); NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY); aPersistentDescriptor = buf; PR_Free(buf); return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; if (aPersistentDescriptor.IsEmpty()) return NS_ERROR_INVALID_ARG; // Support pathnames as user-supplied descriptors if they begin with '/' // or '~'. These characters do not collide with the base64 set used for // encoding alias records. char first = aPersistentDescriptor.First(); if (first == '/' || first == '~') return InitWithNativePath(aPersistentDescriptor); nsresult rv = NS_OK; PRUint32 dataSize = aPersistentDescriptor.Length(); char* decodedData = PL_Base64Decode(PromiseFlatCString(aPersistentDescriptor).get(), dataSize, nsnull); if (!decodedData) { NS_ERROR("SetPersistentDescriptor was given bad data"); return NS_ERROR_FAILURE; } // Cast to an alias record and resolve. AliasRecord aliasHeader = *(AliasPtr)decodedData; PRInt32 aliasSize = ::GetAliasSizeFromPtr(&aliasHeader); if (aliasSize > ((PRInt32)dataSize * 3) / 4) { // be paranoid about having too few data PR_Free(decodedData); return NS_ERROR_FAILURE; } // Move the now-decoded data into the Handle. // The size of the decoded data is 3/4 the size of the encoded data. See plbase64.h Handle newHandle = nsnull; if (::PtrToHand(decodedData, &newHandle, aliasSize) != noErr) rv = NS_ERROR_OUT_OF_MEMORY; PR_Free(decodedData); if (NS_FAILED(rv)) return rv; Boolean changed; FSRef resolvedFSRef; OSErr err = ::FSResolveAlias(nsnull, (AliasHandle)newHandle, &resolvedFSRef, &changed); rv = MacErrorMapper(err); DisposeHandle(newHandle); if (NS_FAILED(rv)) return rv; return InitWithFSRef(&resolvedFSRef); NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::Reveal() { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; BOOL success = NO; CFURLRef urlRef; if (NS_SUCCEEDED(GetCFURL(&urlRef)) && urlRef) { NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; success = [[NSWorkspace sharedWorkspace] selectFile:[(NSURL*)urlRef path] inFileViewerRootedAtPath:@""]; [ap release]; ::CFRelease(urlRef); } return (success ? NS_OK : NS_ERROR_FAILURE); NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::Launch() { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; BOOL success = NO; CFURLRef urlRef; if (NS_SUCCEEDED(GetCFURL(&urlRef)) && urlRef) { NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; success = [[NSWorkspace sharedWorkspace] openURL:(NSURL*)urlRef]; [ap release]; ::CFRelease(urlRef); } return (success ? NS_OK : NS_ERROR_FAILURE); NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } #pragma mark - #pragma mark [nsILocalFileMac] NS_IMETHODIMP nsLocalFile::InitWithCFURL(CFURLRef aCFURL) { NS_ENSURE_ARG(aCFURL); SetBaseURL(aCFURL); return NS_OK; } NS_IMETHODIMP nsLocalFile::InitWithFSRef(const FSRef *aFSRef) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; NS_ENSURE_ARG(aFSRef); nsresult rv = NS_ERROR_FAILURE; CFURLRef newURLRef = ::CFURLCreateFromFSRef(kCFAllocatorDefault, aFSRef); if (newURLRef) { SetBaseURL(newURLRef); ::CFRelease(newURLRef); rv = NS_OK; } return rv; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::InitToAppWithCreatorCode(OSType aAppCreator) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; FSRef fsRef; OSErr err = ::LSFindApplicationForInfo(aAppCreator, nsnull, nsnull, &fsRef, nsnull); if (err != noErr) return MacErrorMapper(err); return InitWithFSRef(&fsRef); NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::GetCFURL(CFURLRef *_retval) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; CHECK_INIT(); if (mFollowLinks) { nsCAutoString nativePath; nsresult rv = GetNativeTarget(nativePath); if (NS_SUCCEEDED(rv)) { // This is a symlink if we can get a target path. // We can assume this is not a directory if we know it is a symlink. // I'll say this anyway because it is so dangerous: Do not call IsDirectory // here because that ultimately ends up calling GetCFURL and you get a loop. CFURLRef targetURL = ::CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (UInt8*)nativePath.BeginWriting(), nativePath.Length(), false); if (!targetURL) return NS_ERROR_FAILURE; *_retval = targetURL; return NS_OK; } } ::CFRetain(mBaseURL); *_retval = mBaseURL; return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::GetFSRef(FSRef *_retval) { NS_ENSURE_ARG_POINTER(_retval); return GetFSRefInternal(*_retval); } NS_IMETHODIMP nsLocalFile::GetFSSpec(FSSpec *_retval) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; NS_ENSURE_ARG_POINTER(_retval); CHECK_INIT(); FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_SUCCEEDED(rv)) { OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNone, nsnull, nsnull, _retval, nsnull); return MacErrorMapper(err); } return rv; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::GetFileSizeWithResFork(PRInt64 *aFileSizeWithResFork) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; NS_ENSURE_ARG_POINTER(aFileSizeWithResFork); FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; FSCatalogInfo catalogInfo; OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoDataSizes + kFSCatInfoRsrcSizes, &catalogInfo, nsnull, nsnull, nsnull); if (err != noErr) return MacErrorMapper(err); *aFileSizeWithResFork = catalogInfo.dataLogicalSize + catalogInfo.rsrcLogicalSize; return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::GetFileType(OSType *aFileType) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; NS_ENSURE_ARG_POINTER(aFileType); CHECK_INIT(); nsresult rv = NS_ERROR_FAILURE; NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; NSDictionary* dict = [[NSFileManager defaultManager] fileAttributesAtPath:[(NSURL*)mBaseURL path] traverseLink:YES]; NSNumber* typeNum = (NSNumber*)[dict objectForKey:NSFileHFSTypeCode]; if (typeNum) { *aFileType = [typeNum unsignedLongValue]; rv = NS_OK; } [ap release]; return rv; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::SetFileType(OSType aFileType) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; CHECK_INIT(); NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; NSDictionary* dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:aFileType] forKey:NSFileHFSTypeCode]; BOOL success = [[NSFileManager defaultManager] changeFileAttributes:dict atPath:[(NSURL*)mBaseURL path]]; [ap release]; return (success ? NS_OK : NS_ERROR_FAILURE); NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::GetFileCreator(OSType *aFileCreator) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; NS_ENSURE_ARG_POINTER(aFileCreator); CHECK_INIT(); nsresult rv = NS_ERROR_FAILURE; NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; NSDictionary* dict = [[NSFileManager defaultManager] fileAttributesAtPath:[(NSURL*)mBaseURL path] traverseLink:YES]; id creatorNum = (NSNumber*)[dict objectForKey:NSFileHFSCreatorCode]; if (creatorNum) { *aFileCreator = [creatorNum unsignedLongValue]; rv = NS_OK; } [ap release]; return rv; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::SetFileCreator(OSType aFileCreator) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; CHECK_INIT(); NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; NSDictionary* dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:aFileCreator] forKey:NSFileHFSCreatorCode]; BOOL success = [[NSFileManager defaultManager] changeFileAttributes:dict atPath:[(NSURL*)mBaseURL path]]; [ap release]; return (success ? NS_OK : NS_ERROR_FAILURE); NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::LaunchWithDoc(nsILocalFile *aDocToLoad, PRBool aLaunchInBackground) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; PRBool isExecutable; nsresult rv = IsExecutable(&isExecutable); if (NS_FAILED(rv)) return rv; if (!isExecutable) return NS_ERROR_FILE_EXECUTION_FAILED; FSRef appFSRef, docFSRef; rv = GetFSRefInternal(appFSRef); if (NS_FAILED(rv)) return rv; if (aDocToLoad) { nsCOMPtr macDoc = do_QueryInterface(aDocToLoad); rv = macDoc->GetFSRef(&docFSRef); if (NS_FAILED(rv)) return rv; } LSLaunchFlags theLaunchFlags = kLSLaunchDefaults; LSLaunchFSRefSpec thelaunchSpec; if (aLaunchInBackground) theLaunchFlags |= kLSLaunchDontSwitch; memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec)); thelaunchSpec.appRef = &appFSRef; if (aDocToLoad) { thelaunchSpec.numDocs = 1; thelaunchSpec.itemRefs = &docFSRef; } thelaunchSpec.launchFlags = theLaunchFlags; OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, NULL); if (err != noErr) return MacErrorMapper(err); return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::OpenDocWithApp(nsILocalFile *aAppToOpenWith, PRBool aLaunchInBackground) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; nsresult rv; OSErr err; FSRef docFSRef, appFSRef; rv = GetFSRefInternal(docFSRef); if (NS_FAILED(rv)) return rv; if (aAppToOpenWith) { nsCOMPtr appFileMac = do_QueryInterface(aAppToOpenWith, &rv); if (!appFileMac) return rv; PRBool isExecutable; rv = appFileMac->IsExecutable(&isExecutable); if (NS_FAILED(rv)) return rv; if (!isExecutable) return NS_ERROR_FILE_EXECUTION_FAILED; rv = appFileMac->GetFSRef(&appFSRef); if (NS_FAILED(rv)) return rv; } else { OSType fileCreator; rv = GetFileCreator(&fileCreator); if (NS_FAILED(rv)) return rv; err = ::LSFindApplicationForInfo(fileCreator, nsnull, nsnull, &appFSRef, nsnull); if (err != noErr) return MacErrorMapper(err); } LSLaunchFlags theLaunchFlags = kLSLaunchDefaults; LSLaunchFSRefSpec thelaunchSpec; if (aLaunchInBackground) theLaunchFlags |= kLSLaunchDontSwitch; memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec)); thelaunchSpec.appRef = &appFSRef; thelaunchSpec.numDocs = 1; thelaunchSpec.itemRefs = &docFSRef; thelaunchSpec.launchFlags = theLaunchFlags; err = ::LSOpenFromRefSpec(&thelaunchSpec, NULL); if (err != noErr) return MacErrorMapper(err); return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::IsPackage(PRBool *_retval) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; NS_ENSURE_ARG(_retval); *_retval = PR_FALSE; CFURLRef urlRef; if (NS_SUCCEEDED(GetCFURL(&urlRef)) && urlRef) { *_retval = [[NSWorkspace sharedWorkspace] isFilePackageAtPath:[(NSURL *)urlRef path]]; ::CFRelease(urlRef); return NS_OK; } return NS_ERROR_FAILURE; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } NS_IMETHODIMP nsLocalFile::GetBundleDisplayName(nsAString& outBundleName) { PRBool isPackage = PR_FALSE; nsresult rv = IsPackage(&isPackage); if (NS_FAILED(rv) || !isPackage) return NS_ERROR_FAILURE; nsAutoString name; rv = GetLeafName(name); if (NS_FAILED(rv)) return rv; PRInt32 length = name.Length(); if (Substring(name, length - 4, length).EqualsLiteral(".app")) { // 4 characters in ".app" outBundleName = Substring(name, 0, length - 4); } else outBundleName = name; return NS_OK; } NS_IMETHODIMP nsLocalFile::GetBundleIdentifier(nsACString& outBundleIdentifier) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; nsresult rv = NS_ERROR_FAILURE; CFURLRef urlRef; if (NS_SUCCEEDED(GetCFURL(&urlRef))) { CFBundleRef bundle = ::CFBundleCreate(NULL, urlRef); if (bundle) { CFStringRef bundleIdentifier = ::CFBundleGetIdentifier(bundle); if (bundleIdentifier) rv = CFStringReftoUTF8(bundleIdentifier, outBundleIdentifier); ::CFRelease(bundle); } ::CFRelease(urlRef); } return rv; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } #pragma mark - #pragma mark [Protected Methods] nsresult nsLocalFile::SetBaseURL(CFURLRef aCFURLRef) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; NS_ENSURE_ARG(aCFURLRef); ::CFRetain(aCFURLRef); if (mBaseURL) ::CFRelease(mBaseURL); mBaseURL = aCFURLRef; if (!::CFURLGetFileSystemRepresentation(mBaseURL, NO, (UInt8*)mPath, PATH_MAX)) return NS_ERROR_FAILURE; return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } nsresult nsLocalFile::GetFSRefInternal(FSRef& aFSRef) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; CHECK_INIT(); return (::CFURLGetFSRef(mBaseURL, &aFSRef) ? NS_OK : NS_ERROR_FAILURE); NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } nsresult nsLocalFile::GetPathInternal(nsACString& path) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; CHECK_INIT(); CFURLRef urlRef; nsresult rv = GetCFURL(&urlRef); if (NS_FAILED(rv)) return rv; path.SetLength(PATH_MAX); if (path.Length() != (unsigned int)PATH_MAX) { ::CFRelease(urlRef); return NS_ERROR_OUT_OF_MEMORY; } Boolean gotPath = ::CFURLGetFileSystemRepresentation(urlRef, true, (UInt8*)path.BeginWriting(), PATH_MAX); ::CFRelease(urlRef); return (gotPath ? NS_OK : NS_ERROR_FAILURE); NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } nsresult nsLocalFile::CopyInternal(nsIFile* aParentDir, const nsAString& newName, PRBool followLinks) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; CHECK_INIT(); StFollowLinksState srcFollowState(*this, followLinks); nsresult rv; OSErr err; FSRef srcFSRef; rv = GetFSRefInternal(srcFSRef); if (NS_FAILED(rv)) return rv; nsCOMPtr newParentDir = aParentDir; if (!newParentDir) { if (newName.IsEmpty()) return NS_ERROR_INVALID_ARG; rv = GetParent(getter_AddRefs(newParentDir)); if (NS_FAILED(rv)) return rv; } // If newParentDir does not exist, create it PRBool exists; rv = newParentDir->Exists(&exists); if (NS_FAILED(rv)) return rv; if (!exists) { rv = newParentDir->Create(nsIFile::DIRECTORY_TYPE, 0777); if (NS_FAILED(rv)) return rv; } FSRef destFSRef; nsCOMPtr newParentDirMac(do_QueryInterface(newParentDir)); if (!newParentDirMac) return NS_ERROR_NO_INTERFACE; rv = newParentDirMac->GetFSRef(&destFSRef); if (NS_FAILED(rv)) return rv; CFStringRef destNameStr = NULL; if (newName.Length() > 0) { destNameStr = ::CFStringCreateWithCharacters(kCFAllocatorDefault, PromiseFlatString(newName).get(), newName.Length()); } err = ::FSCopyObjectSync(&srcFSRef, &destFSRef, destNameStr, NULL, kFSFileOperationDefaultOptions); if (destNameStr) ::CFRelease(destNameStr); return MacErrorMapper(err); NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } nsresult nsLocalFile::FillStatBufferInternal(struct STAT *statBuffer) { CHECK_INIT(); if (STAT(mPath, statBuffer) == -1) { if (LSTAT(mPath, statBuffer) == -1) return NSRESULT_FOR_ERRNO(); } return NS_OK; } // nsIHashable NS_IMETHODIMP nsLocalFile::Equals(nsIHashable* aOther, PRBool *aResult) { nsCOMPtr otherFile(do_QueryInterface(aOther)); if (!otherFile) { *aResult = PR_FALSE; return NS_OK; } return Equals(otherFile, aResult); } NS_IMETHODIMP nsLocalFile::GetHashCode(PRUint32 *aResult) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; CFStringRef pathStrRef = ::CFURLCopyFileSystemPath(mBaseURL, kCFURLPOSIXPathStyle); nsCAutoString path; CFStringReftoUTF8(pathStrRef, path); ::CFRelease(pathStrRef); *aResult = HashString(path); return NS_OK; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } #pragma mark - #pragma mark [Global Functions] void nsLocalFile::GlobalInit() { } void nsLocalFile::GlobalShutdown() { } nsresult NS_NewLocalFile(const nsAString& 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->InitWithPath(path); if (NS_FAILED(rv)) { NS_RELEASE(file); return rv; } } *result = file; return NS_OK; } nsresult NS_NewNativeLocalFile(const nsACString& path, PRBool followLinks, nsILocalFile **result) { return NS_NewLocalFile(NS_ConvertUTF8toUTF16(path), followLinks, result); } nsresult NS_NewLocalFileWithFSRef(const FSRef* aFSRef, PRBool aFollowLinks, nsILocalFileMac** result) { nsLocalFile* file = new nsLocalFile(); if (file == nsnull) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(file); file->SetFollowLinks(aFollowLinks); nsresult rv = file->InitWithFSRef(aFSRef); if (NS_FAILED(rv)) { NS_RELEASE(file); return rv; } *result = file; return NS_OK; } nsresult NS_NewLocalFileWithCFURL(const CFURLRef aURL, PRBool aFollowLinks, nsILocalFileMac** result) { nsLocalFile* file = new nsLocalFile(); if (file == nsnull) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(file); file->SetFollowLinks(aFollowLinks); nsresult rv = file->InitWithCFURL(aURL); if (NS_FAILED(rv)) { NS_RELEASE(file); return rv; } *result = file; return NS_OK; } #pragma mark - #pragma mark [Static Functions] static nsresult MacErrorMapper(OSErr inErr) { nsresult outErr; switch (inErr) { case noErr: outErr = NS_OK; break; case fnfErr: case afpObjectNotFound: case afpDirNotFound: outErr = NS_ERROR_FILE_NOT_FOUND; break; case dupFNErr: case afpObjectExists: outErr = NS_ERROR_FILE_ALREADY_EXISTS; break; case dskFulErr: case afpDiskFull: outErr = NS_ERROR_FILE_DISK_FULL; break; case fLckdErr: case afpVolLocked: outErr = NS_ERROR_FILE_IS_LOCKED; break; case afpAccessDenied: outErr = NS_ERROR_FILE_ACCESS_DENIED; break; case afpDirNotEmpty: outErr = NS_ERROR_FILE_DIR_NOT_EMPTY; break; // Can't find good map for some case bdNamErr: outErr = NS_ERROR_FAILURE; break; default: outErr = NS_ERROR_FAILURE; break; } return outErr; } // Convert a UTF-8 string to a UTF-16 string while normalizing to // Normalization Form C (composed Unicode). We need this because // Mac OS X file system uses NFD (Normalization Form D : decomposed Unicode) // while most other OS', server-side programs usually expect NFC. static void CopyUTF8toUTF16NFC(const nsACString& aSrc, nsAString& aResult) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; const nsAFlatCString &inFlatSrc = PromiseFlatCString(aSrc); // The number of 16bit code units in a UTF-16 string will never be // larger than the number of bytes in the corresponding UTF-8 string. CFMutableStringRef inStr = ::CFStringCreateMutable(NULL, inFlatSrc.Length()); if (!inStr) { CopyUTF8toUTF16(aSrc, aResult); return; } ::CFStringAppendCString(inStr, inFlatSrc.get(), kCFStringEncodingUTF8); ::CFStringNormalize(inStr, kCFStringNormalizationFormC); CFIndex length = ::CFStringGetLength(inStr); const UniChar* chars = ::CFStringGetCharactersPtr(inStr); if (chars) { aResult.Assign(chars, length); } else { nsAutoTArray buffer; if (!buffer.SetLength(length)) { CopyUTF8toUTF16(aSrc, aResult); } else { ::CFStringGetCharacters(inStr, ::CFRangeMake(0, length), buffer.Elements()); aResult.Assign(buffer.Elements(), length); } } ::CFRelease(inStr); NS_OBJC_END_TRY_ABORT_BLOCK; } nsresult CFStringReftoUTF8(CFStringRef aInStrRef, nsACString& aOutStr) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; // first see if the conversion would succeed and find the length of the result CFIndex usedBufLen, inStrLen = ::CFStringGetLength(aInStrRef); CFIndex charsConverted = ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen), kCFStringEncodingUTF8, 0, PR_FALSE, NULL, 0, &usedBufLen); if (charsConverted == inStrLen) { // all characters converted, do the actual conversion aOutStr.SetLength(usedBufLen); if (aOutStr.Length() != (unsigned int)usedBufLen) return NS_ERROR_OUT_OF_MEMORY; UInt8 *buffer = (UInt8*)aOutStr.BeginWriting(); ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen), kCFStringEncodingUTF8, 0, false, buffer, usedBufLen, &usedBufLen); return NS_OK; } return NS_ERROR_FAILURE; NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; }