/* -*- 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 * * 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 "nsString.h" #include "nsReadableUtils.h" #include "nsISimpleEnumerator.h" #include "nsITimelineService.h" #include "nsVoidArray.h" #include "plbase64.h" #include "prmem.h" #include "nsCRT.h" #include "MoreFilesX.h" #include "FSCopyObject.h" #include "nsAutoBuffer.h" // Mac Includes #include #include #include #include #include #include // Unix Includes #include //***************************************************************************** // Static Function Prototypes //***************************************************************************** static nsresult MacErrorMapper(OSErr inErr); static OSErr FindRunningAppBySignature(OSType aAppSig, ProcessSerialNumber& outPsn); static void CopyUTF8toUTF16NFC(const nsACString& aSrc, nsAString& aResult); //***************************************************************************** // Local Helper Classes //***************************************************************************** #pragma mark - #pragma mark [FSRef operator==] bool operator==(const FSRef& lhs, const FSRef& rhs) { return (::FSCompareFSRefs(&lhs, &rhs) == noErr); } #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: NS_DECL_ISUPPORTS nsDirEnumerator() : mIterator(nsnull), mFSRefsArray(nsnull), mArrayCnt(0), mArrayIndex(0) { } nsresult Init(nsILocalFileMac* parent) { 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_IMETHOD HasMoreElements(PRBool *result) { 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) { nsLocalFile *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; return NS_OK; } 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; } private: ~nsDirEnumerator() { if (mIterator) ::FSCloseIterator(mIterator); if (mFSRefsArray) nsMemory::Free(mFSRefsArray); } 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_ISUPPORTS1(nsDirEnumerator, nsISimpleEnumerator) #pragma mark - #pragma mark [StAEDesc] class StAEDesc: public AEDesc { public: StAEDesc() { descriptorType = typeNull; dataHandle = nil; } ~StAEDesc() { ::AEDisposeDesc(this); } }; #define FILENAME_BUFFER_SIZE 512 //***************************************************************************** // nsLocalFile //***************************************************************************** const char nsLocalFile::kPathSepChar = '/'; const PRUnichar nsLocalFile::kPathSepUnichar = '/'; // The HFS+ epoch is Jan. 1, 1904 GMT - differs from HFS in which times were local // The NSPR epoch is Jan. 1, 1970 GMT // 2082844800 is the difference in seconds between those dates const PRInt64 nsLocalFile::kJanuaryFirst1970Seconds = 2082844800LL; #pragma mark - #pragma mark [CTORs/DTOR] nsLocalFile::nsLocalFile() : mBaseRef(nsnull), mTargetRef(nsnull), mCachedFSRefValid(PR_FALSE), mFollowLinks(PR_TRUE), mFollowLinksDirty(PR_TRUE) { } nsLocalFile::nsLocalFile(const nsLocalFile& src) : mBaseRef(src.mBaseRef), mTargetRef(src.mTargetRef), mCachedFSRef(src.mCachedFSRef), mCachedFSRefValid(src.mCachedFSRefValid), mFollowLinks(src.mFollowLinks), mFollowLinksDirty(src.mFollowLinksDirty) { // A CFURLRef is immutable so no need to copy, just retain. if (mBaseRef) ::CFRetain(mBaseRef); if (mTargetRef) ::CFRetain(mTargetRef); } nsLocalFile::~nsLocalFile() { if (mBaseRef) ::CFRelease(mBaseRef); if (mTargetRef) ::CFRelease(mTargetRef); } //***************************************************************************** // nsLocalFile::nsISupports //***************************************************************************** #pragma mark - #pragma mark [nsISupports] NS_IMPL_THREADSAFE_ISUPPORTS3(nsLocalFile, nsILocalFileMac, nsILocalFile, nsIFile) 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::nsIFile //***************************************************************************** #pragma mark - #pragma mark [nsIFile] /* void append (in AString node); */ NS_IMETHODIMP nsLocalFile::Append(const nsAString& aNode) { return AppendNative(NS_ConvertUCS2toUTF8(aNode)); } /* [noscript] void appendNative (in ACString node); */ NS_IMETHODIMP nsLocalFile::AppendNative(const nsACString& aNode) { if (!mBaseRef) return NS_ERROR_NOT_INITIALIZED; 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, mBaseRef, nodeStrRef, PR_FALSE); ::CFRelease(nodeStrRef); if (newRef) { SetBaseRef(newRef); ::CFRelease(newRef); return NS_OK; } } return NS_ERROR_FAILURE; } /* void normalize (); */ NS_IMETHODIMP nsLocalFile::Normalize() { return NS_OK; } /* void create (in unsigned long type, in unsigned long permissions); */ NS_IMETHODIMP nsLocalFile::Create(PRUint32 type, PRUint32 permissions) { if (type != NORMAL_FILE_TYPE && type != DIRECTORY_TYPE) return NS_ERROR_FILE_UNKNOWN_TYPE; if (!mBaseRef) return NS_ERROR_NOT_INITIALIZED; nsStringArray nonExtantNodes; CFURLRef pathURLRef = mBaseRef; FSRef pathFSRef; CFStringRef leafStrRef = nsnull; nsAutoBuffer 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.EnsureElemCapacity(leafLen + 1)) break; ::CFStringGetCharacters(leafStrRef, CFRangeMake(0, leafLen), buffer.get()); buffer.get()[leafLen] = '\0'; nonExtantNodes.AppendString(nsString(nsDependentString(buffer.get()))); ::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 != mBaseRef) ::CFRelease(pathURLRef); pathURLRef = parent; } if (pathURLRef != mBaseRef) ::CFRelease(pathURLRef); if (leafStrRef != nsnull) ::CFRelease(leafStrRef); if (!success) return NS_ERROR_FAILURE; PRInt32 nodesToCreate = nonExtantNodes.Count(); if (nodesToCreate == 0) return NS_ERROR_FILE_ALREADY_EXISTS; OSErr err; nsAutoString nextNodeName; for (PRInt32 i = nodesToCreate - 1; i > 0; i--) { nonExtantNodes.StringAt(i, nextNodeName); err = ::FSCreateDirectoryUnicode(&pathFSRef, nextNodeName.Length(), (const UniChar *)nextNodeName.get(), kFSCatInfoNone, nsnull, &pathFSRef, nsnull, nsnull); if (err != noErr) return MacErrorMapper(err); } nonExtantNodes.StringAt(0, nextNodeName); 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); } /* attribute AString leafName; */ 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_ConvertUCS2toUTF8(aLeafName)); } /* [noscript] attribute ACString nativeLeafName; */ NS_IMETHODIMP nsLocalFile::GetNativeLeafName(nsACString& aNativeLeafName) { if (!mBaseRef) return NS_ERROR_NOT_INITIALIZED; nsresult rv = NS_ERROR_FAILURE; CFStringRef leafStrRef = ::CFURLCopyLastPathComponent(mBaseRef); if (leafStrRef) { rv = CFStringReftoUTF8(leafStrRef, aNativeLeafName); ::CFRelease(leafStrRef); } return rv; } NS_IMETHODIMP nsLocalFile::SetNativeLeafName(const nsACString& aNativeLeafName) { if (!mBaseRef) return NS_ERROR_NOT_INITIALIZED; nsresult rv = NS_ERROR_FAILURE; CFURLRef parentURLRef = ::CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorDefault, mBaseRef); if (parentURLRef) { CFStringRef nodeStrRef = ::CFStringCreateWithCString(kCFAllocatorDefault, PromiseFlatCString(aNativeLeafName).get(), kCFStringEncodingUTF8); if (nodeStrRef) { CFURLRef newURLRef = ::CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault, parentURLRef, nodeStrRef, PR_FALSE); if (newURLRef) { SetBaseRef(newURLRef); ::CFRelease(newURLRef); rv = NS_OK; } ::CFRelease(nodeStrRef); } ::CFRelease(parentURLRef); } return rv; } /* void copyTo (in nsIFile newParentDir, in AString newName); */ NS_IMETHODIMP nsLocalFile::CopyTo(nsIFile *newParentDir, const nsAString& newName) { return MoveCopy(newParentDir, newName, PR_TRUE, PR_FALSE); } /* [noscrpit] void CopyToNative (in nsIFile newParentDir, in ACString newName); */ NS_IMETHODIMP nsLocalFile::CopyToNative(nsIFile *newParentDir, const nsACString& newName) { return MoveCopy(newParentDir, NS_ConvertUTF8toUCS2(newName), PR_TRUE, PR_FALSE); } /* void copyToFollowingLinks (in nsIFile newParentDir, in AString newName); */ NS_IMETHODIMP nsLocalFile::CopyToFollowingLinks(nsIFile *newParentDir, const nsAString& newName) { return MoveCopy(newParentDir, newName, PR_TRUE, PR_TRUE); } /* [noscript] void copyToFollowingLinksNative (in nsIFile newParentDir, in ACString newName); */ NS_IMETHODIMP nsLocalFile::CopyToFollowingLinksNative(nsIFile *newParentDir, const nsACString& newName) { return MoveCopy(newParentDir, NS_ConvertUTF8toUCS2(newName), PR_TRUE, PR_TRUE); } /* void moveTo (in nsIFile newParentDir, in AString newName); */ NS_IMETHODIMP nsLocalFile::MoveTo(nsIFile *newParentDir, const nsAString& newName) { return MoveCopy(newParentDir, newName, FALSE, FALSE); } /* [noscript] void moveToNative (in nsIFile newParentDir, in ACString newName); */ NS_IMETHODIMP nsLocalFile::MoveToNative(nsIFile *newParentDir, const nsACString& newName) { return MoveCopy(newParentDir, NS_ConvertUTF8toUCS2(newName), FALSE, FALSE); } /* void remove (in boolean recursive); */ NS_IMETHODIMP nsLocalFile::Remove(PRBool recursive) { StFollowLinksState followLinks(*this, PR_FALSE); // XXX If we're an alias, never remove target FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; PRBool isDir; rv = IsDirectory(&isDir); if (NS_FAILED(rv)) return rv; OSErr err; if (recursive && isDir) err = ::FSDeleteContainer(&fsRef); else err = ::FSDeleteObject(&fsRef); mCachedFSRefValid = PR_FALSE; return MacErrorMapper(err); } /* attribute unsigned long permissions; */ NS_IMETHODIMP nsLocalFile::GetPermissions(PRUint32 *aPermissions) { NS_ENSURE_ARG_POINTER(aPermissions); FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; FSCatalogInfo catalogInfo; OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoPermissions, &catalogInfo, nsnull, nsnull, nsnull); if (err != noErr) return MacErrorMapper(err); FSPermissionInfo *permPtr = (FSPermissionInfo*)catalogInfo.permissions; *aPermissions = permPtr->mode; return NS_OK; } NS_IMETHODIMP nsLocalFile::SetPermissions(PRUint32 aPermissions) { FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; FSCatalogInfo catalogInfo; OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoPermissions, &catalogInfo, nsnull, nsnull, nsnull); if (err != noErr) return MacErrorMapper(err); FSPermissionInfo *permPtr = (FSPermissionInfo*)catalogInfo.permissions; permPtr->mode = (UInt16)aPermissions; err = ::FSSetCatalogInfo(&fsRef, kFSCatInfoPermissions, &catalogInfo); return MacErrorMapper(err); } /* attribute unsigned long permissionsOfLink; */ 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; } /* attribute PRInt64 lastModifiedTime; */ NS_IMETHODIMP nsLocalFile::GetLastModifiedTime(PRInt64 *aLastModifiedTime) { NS_ENSURE_ARG_POINTER(aLastModifiedTime); FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; FSCatalogInfo catalogInfo; OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoContentMod, &catalogInfo, nsnull, nsnull, nsnull); if (err != noErr) return MacErrorMapper(err); *aLastModifiedTime = HFSPlustoNSPRTime(catalogInfo.contentModDate); return NS_OK; } NS_IMETHODIMP nsLocalFile::SetLastModifiedTime(PRInt64 aLastModifiedTime) { OSErr err; nsresult rv; FSRef fsRef; FSCatalogInfo catalogInfo; rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; FSRef parentRef; PRBool notifyParent; /* Get the node flags, the content modification date and time, and the parent ref */ err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNodeFlags + kFSCatInfoContentMod, &catalogInfo, NULL, NULL, &parentRef); if (err != noErr) return MacErrorMapper(err); /* Notify the parent if this is a file */ notifyParent = (0 == (catalogInfo.nodeFlags & kFSNodeIsDirectoryMask)); NSPRtoHFSPlusTime(aLastModifiedTime, catalogInfo.contentModDate); err = ::FSSetCatalogInfo(&fsRef, kFSCatInfoContentMod, &catalogInfo); if (err != noErr) return MacErrorMapper(err); /* Send a notification for the parent of the file, or for the directory */ err = FNNotify(notifyParent ? &parentRef : &fsRef, kFNDirectoryModifiedMessage, kNilOptions); if (err != noErr) return MacErrorMapper(err); return NS_OK; } /* attribute PRInt64 lastModifiedTimeOfLink; */ NS_IMETHODIMP nsLocalFile::GetLastModifiedTimeOfLink(PRInt64 *aLastModifiedTimeOfLink) { NS_ERROR("NS_ERROR_NOT_IMPLEMENTED"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsLocalFile::SetLastModifiedTimeOfLink(PRInt64 aLastModifiedTimeOfLink) { NS_ERROR("NS_ERROR_NOT_IMPLEMENTED"); return NS_ERROR_NOT_IMPLEMENTED; } /* attribute PRInt64 fileSize; */ NS_IMETHODIMP nsLocalFile::GetFileSize(PRInt64 *aFileSize) { NS_ENSURE_ARG_POINTER(aFileSize); *aFileSize = 0; FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; FSCatalogInfo catalogInfo; OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNodeFlags + kFSCatInfoDataSizes, &catalogInfo, nsnull, nsnull, nsnull); if (err != noErr) return MacErrorMapper(err); // FSGetCatalogInfo can return a bogus size for directories sometimes, so only // rely on the answer for files if ((catalogInfo.nodeFlags & kFSNodeIsDirectoryMask) == 0) *aFileSize = catalogInfo.dataLogicalSize; return NS_OK; } NS_IMETHODIMP nsLocalFile::SetFileSize(PRInt64 aFileSize) { FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; SInt16 refNum; OSErr err = ::FSOpenFork(&fsRef, 0, nsnull, fsWrPerm, &refNum); if (err != noErr) return MacErrorMapper(err); err = ::FSSetForkSize(refNum, fsFromStart, aFileSize); ::FSCloseFork(refNum); return MacErrorMapper(err); } /* readonly attribute PRInt64 fileSizeOfLink; */ NS_IMETHODIMP nsLocalFile::GetFileSizeOfLink(PRInt64 *aFileSizeOfLink) { NS_ENSURE_ARG_POINTER(aFileSizeOfLink); StFollowLinksState followLinks(*this, PR_FALSE); return GetFileSize(aFileSizeOfLink); } /* readonly attribute AString target; */ NS_IMETHODIMP nsLocalFile::GetTarget(nsAString& aTarget) { NS_ERROR("NS_ERROR_NOT_IMPLEMENTED"); return NS_ERROR_NOT_IMPLEMENTED; } /* [noscript] readonly attribute ACString nativeTarget; */ NS_IMETHODIMP nsLocalFile::GetNativeTarget(nsACString& aNativeTarget) { NS_ERROR("NS_ERROR_NOT_IMPLEMENTED"); return NS_ERROR_NOT_IMPLEMENTED; } /* readonly attribute AString path; */ NS_IMETHODIMP nsLocalFile::GetPath(nsAString& aPath) { nsCAutoString nativeString; nsresult rv = GetNativePath(nativeString); if (NS_FAILED(rv)) return rv; CopyUTF8toUTF16NFC(nativeString, aPath); return NS_OK; } /* [noscript] readonly attribute ACString nativePath; */ NS_IMETHODIMP nsLocalFile::GetNativePath(nsACString& aNativePath) { if (!mBaseRef) return NS_ERROR_NOT_INITIALIZED; nsresult rv = NS_ERROR_FAILURE; CFStringRef pathStrRef = ::CFURLCopyFileSystemPath(mBaseRef, kCFURLPOSIXPathStyle); if (pathStrRef) { rv = CFStringReftoUTF8(pathStrRef, aNativePath); ::CFRelease(pathStrRef); } return rv; } /* boolean exists (); */ NS_IMETHODIMP nsLocalFile::Exists(PRBool *_retval) { NS_ENSURE_ARG_POINTER(_retval); *_retval = PR_FALSE; FSRef fsRef; if (NS_SUCCEEDED(GetFSRefInternal(fsRef, PR_TRUE))) { *_retval = PR_TRUE; } return NS_OK; } /* boolean isWritable (); */ NS_IMETHODIMP nsLocalFile::IsWritable(PRBool *_retval) { NS_ENSURE_ARG_POINTER(_retval); *_retval = PR_FALSE; FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; if (::FSCheckLock(&fsRef) == noErr) { PRUint32 permissions; rv = GetPermissions(&permissions); if (NS_FAILED(rv)) return rv; *_retval = ((permissions & S_IWUSR) != 0); } return NS_OK; } /* boolean isReadable (); */ NS_IMETHODIMP nsLocalFile::IsReadable(PRBool *_retval) { 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; } /* boolean isExecutable (); */ NS_IMETHODIMP nsLocalFile::IsExecutable(PRBool *_retval) { 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; } /* boolean isHidden (); */ NS_IMETHODIMP nsLocalFile::IsHidden(PRBool *_retval) { 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, kFSCatInfoNodeFlags, &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; } /* boolean isDirectory (); */ NS_IMETHODIMP nsLocalFile::IsDirectory(PRBool *_retval) { NS_ENSURE_ARG_POINTER(_retval); *_retval = PR_FALSE; FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef, PR_FALSE); if (NS_FAILED(rv)) return rv; FSCatalogInfo catalogInfo; OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNodeFlags, &catalogInfo, nsnull, nsnull, nsnull); if (err != noErr) return MacErrorMapper(err); *_retval = ((catalogInfo.nodeFlags & kFSNodeIsDirectoryMask) != 0); return NS_OK; } /* boolean isFile (); */ NS_IMETHODIMP nsLocalFile::IsFile(PRBool *_retval) { NS_ENSURE_ARG_POINTER(_retval); *_retval = PR_FALSE; FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef, PR_FALSE); if (NS_FAILED(rv)) return rv; FSCatalogInfo catalogInfo; OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNodeFlags, &catalogInfo, nsnull, nsnull, nsnull); if (err != noErr) return MacErrorMapper(err); *_retval = ((catalogInfo.nodeFlags & kFSNodeIsDirectoryMask) == 0); return NS_OK; } /* boolean isSymlink (); */ NS_IMETHODIMP nsLocalFile::IsSymlink(PRBool *_retval) { NS_ENSURE_ARG(_retval); *_retval = PR_FALSE; if (!mBaseRef) return NS_ERROR_NOT_INITIALIZED; FSRef fsRef; if (::CFURLGetFSRef(mBaseRef, &fsRef)) { Boolean isAlias, isFolder; if (::FSIsAliasFile(&fsRef, &isAlias, &isFolder) == noErr) *_retval = isAlias; } return NS_OK; } /* boolean isSpecial (); */ NS_IMETHODIMP nsLocalFile::IsSpecial(PRBool *_retval) { NS_ERROR("NS_ERROR_NOT_IMPLEMENTED"); return NS_ERROR_NOT_IMPLEMENTED; } /* nsIFile clone (); */ 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; } /* boolean equals (in nsIFile inFile); */ NS_IMETHODIMP nsLocalFile::Equals(nsIFile *inFile, PRBool *_retval) { NS_ENSURE_ARG_POINTER(_retval); *_retval = PR_FALSE; nsCOMPtr inMacFile(do_QueryInterface(inFile)); if (!inFile) return NS_OK; // If both exist, compare FSRefs FSRef thisFSRef, inFSRef; nsresult rv1 = GetFSRef(&thisFSRef); nsresult rv2 = inMacFile->GetFSRef(&inFSRef); if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) { *_retval = (thisFSRef == inFSRef); return NS_OK; } // If one exists and the other doesn't, not equal if (rv1 != rv2) return NS_OK; // Arg, we have to get their paths and compare nsCAutoString thisPath, inPath; if (NS_FAILED(GetNativePath(thisPath))) return NS_ERROR_FAILURE; if (NS_FAILED(inMacFile->GetNativePath(inPath))) return NS_ERROR_FAILURE; *_retval = thisPath.Equals(inPath); return NS_OK; } /* boolean contains (in nsIFile inFile, in boolean recur); */ NS_IMETHODIMP nsLocalFile::Contains(nsIFile *inFile, PRBool recur, PRBool *_retval) { 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; } /* readonly attribute nsIFile parent; */ NS_IMETHODIMP nsLocalFile::GetParent(nsIFile * *aParent) { NS_ENSURE_ARG_POINTER(aParent); *aParent = nsnull; if (!mBaseRef) return NS_ERROR_NOT_INITIALIZED; nsLocalFile *newFile = nsnull; // 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, mBaseRef); if (parentURLRef) { rv = NS_ERROR_FAILURE; newFile = new nsLocalFile; if (newFile) { rv = newFile->InitWithCFURL(parentURLRef); if (NS_SUCCEEDED(rv)) { NS_ADDREF(*aParent = newFile); rv = NS_OK; } } ::CFRelease(parentURLRef); } return rv; } /* readonly attribute nsISimpleEnumerator directoryEntries; */ 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; } //***************************************************************************** // nsLocalFile::nsILocalFile //***************************************************************************** #pragma mark - #pragma mark [nsILocalFile] /* void initWithPath (in AString filePath); */ NS_IMETHODIMP nsLocalFile::InitWithPath(const nsAString& filePath) { return InitWithNativePath(NS_ConvertUCS2toUTF8(filePath)); } /* [noscript] void initWithNativePath (in ACString filePath); */ NS_IMETHODIMP nsLocalFile::InitWithNativePath(const nsACString& filePath) { if (filePath.IsEmpty() || filePath.First() != '/') return NS_ERROR_FILE_UNRECOGNIZED_PATH; // On 10.2, huge paths crash CFURLGetFSRef() if (filePath.Length() > PATH_MAX) return NS_ERROR_FILE_NAME_TOO_LONG; // And, a path with consecutive '/'s which are not between // nodes also crashes CFURLGetFSRef(). Consecutive '/'s which // are between actual nodes are OK. So, convert consecutive // '/'s to a single one. nsCAutoString fixedPath; fixedPath.Assign(filePath); fixedPath.ReplaceSubstring("//", "/"); 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; } SetBaseRef(pathAsCFURL); ::CFRelease(pathAsCFURL); ::CFRelease(pathAsCFString); return NS_OK; } /* void initWithFile (in nsILocalFile aFile); */ NS_IMETHODIMP nsLocalFile::InitWithFile(nsILocalFile *aFile) { 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; } /* attribute PRBool followLinks; */ NS_IMETHODIMP nsLocalFile::GetFollowLinks(PRBool *aFollowLinks) { NS_ENSURE_ARG_POINTER(aFollowLinks); *aFollowLinks = mFollowLinks; return NS_OK; } NS_IMETHODIMP nsLocalFile::SetFollowLinks(PRBool aFollowLinks) { if (aFollowLinks != mFollowLinks) { mFollowLinks = aFollowLinks; UpdateTargetRef(); } return NS_OK; } /* [noscript] PRFileDescStar openNSPRFileDesc (in long flags, in long mode); */ 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(); return NS_OK; } /* [noscript] FILE openANSIFileDesc (in string mode); */ 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; } /* [noscript] PRLibraryStar load (); */ NS_IMETHODIMP nsLocalFile::Load(PRLibrary **_retval) { NS_ENSURE_ARG_POINTER(_retval); NS_TIMELINE_START_TIMER("PR_LoadLibrary"); nsCAutoString path; nsresult rv = GetPathInternal(path); if (NS_FAILED(rv)) return rv; *_retval = PR_LoadLibrary(path.get()); NS_TIMELINE_STOP_TIMER("PR_LoadLibrary"); NS_TIMELINE_MARK_TIMER1("PR_LoadLibrary", path.get()); if (!*_retval) return NS_ERROR_FAILURE; return NS_OK; } /* readonly attribute PRInt64 diskSpaceAvailable; */ NS_IMETHODIMP nsLocalFile::GetDiskSpaceAvailable(PRInt64 *aDiskSpaceAvailable) { NS_ENSURE_ARG_POINTER(aDiskSpaceAvailable); FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; OSErr err; FSCatalogInfo catalogInfo; err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoVolume, &catalogInfo, nsnull, nsnull, nsnull); if (err != noErr) return MacErrorMapper(err); FSVolumeInfo volumeInfo; err = ::FSGetVolumeInfo(catalogInfo.volume, 0, nsnull, kFSVolInfoSizes, &volumeInfo, nsnull, nsnull); if (err != noErr) return MacErrorMapper(err); *aDiskSpaceAvailable = volumeInfo.freeBytes; return NS_OK; } /* void appendRelativePath (in AString relativeFilePath); */ NS_IMETHODIMP nsLocalFile::AppendRelativePath(const nsAString& relativeFilePath) { return AppendRelativeNativePath(NS_ConvertUCS2toUTF8(relativeFilePath)); } /* [noscript] void appendRelativeNativePath (in ACString 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; } /* attribute ACString persistentDescriptor; */ NS_IMETHODIMP nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor) { 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_IMETHODIMP nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor) { if (aPersistentDescriptor.IsEmpty()) return NS_ERROR_INVALID_ARG; 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. PRInt32 aliasSize = (dataSize * 3) / 4; AliasRecord aliasHeader = *(AliasPtr)decodedData; if (aliasHeader.aliasSize > aliasSize) { // be paranoid about having too few data PR_Free(decodedData); return NS_ERROR_FAILURE; } aliasSize = aliasHeader.aliasSize; // 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); } /* void reveal (); */ NS_IMETHODIMP nsLocalFile::Reveal() { FSRef fsRefToReveal; AppleEvent aeEvent = {0, nil}; AppleEvent aeReply = {0, nil}; StAEDesc aeDirDesc, listElem, myAddressDesc, fileList; OSErr err; ProcessSerialNumber process; nsresult rv = GetFSRefInternal(fsRefToReveal); if (NS_FAILED(rv)) return rv; err = ::FindRunningAppBySignature ('MACS', process); if (err == noErr) { err = ::AECreateDesc(typeProcessSerialNumber, (Ptr)&process, sizeof(process), &myAddressDesc); if (err == noErr) { // Create the FinderEvent err = ::AECreateAppleEvent(kAEMiscStandards, kAEMakeObjectsVisible, &myAddressDesc, kAutoGenerateReturnID, kAnyTransactionID, &aeEvent); if (err == noErr) { // Create the file list err = ::AECreateList(nil, 0, false, &fileList); if (err == noErr) { FSSpec fsSpecToReveal; err = ::FSRefMakeFSSpec(&fsRefToReveal, &fsSpecToReveal); if (err == noErr) { err = ::AEPutPtr(&fileList, 0, typeFSS, &fsSpecToReveal, sizeof(FSSpec)); if (err == noErr) { err = ::AEPutParamDesc(&aeEvent, keyDirectObject, &fileList); if (err == noErr) { err = ::AESend(&aeEvent, &aeReply, kAENoReply, kAENormalPriority, kAEDefaultTimeout, nil, nil); if (err == noErr) ::SetFrontProcess(&process); } } } } } } } return NS_OK; } /* void launch (); */ NS_IMETHODIMP nsLocalFile::Launch() { FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; OSErr err = ::LSOpenFSRef(&fsRef, NULL); return MacErrorMapper(err); } //***************************************************************************** // nsLocalFile::nsILocalFileMac //***************************************************************************** #pragma mark - #pragma mark [nsILocalFileMac] /* void initWithCFURL (in CFURLRef aCFURL); */ NS_IMETHODIMP nsLocalFile::InitWithCFURL(CFURLRef aCFURL) { NS_ENSURE_ARG(aCFURL); SetBaseRef(aCFURL); return NS_OK; } /* void initWithFSRef ([const] in FSRefPtr aFSRef); */ NS_IMETHODIMP nsLocalFile::InitWithFSRef(const FSRef *aFSRef) { NS_ENSURE_ARG(aFSRef); nsresult rv = NS_ERROR_FAILURE; CFURLRef newURLRef = ::CFURLCreateFromFSRef(kCFAllocatorDefault, aFSRef); if (newURLRef) { SetBaseRef(newURLRef); ::CFRelease(newURLRef); rv = NS_OK; } return rv; } /* void initWithFSSpec ([const] in FSSpecPtr aFileSpec); */ NS_IMETHODIMP nsLocalFile::InitWithFSSpec(const FSSpec *aFileSpec) { NS_ENSURE_ARG(aFileSpec); FSRef fsRef; OSErr err = ::FSpMakeFSRef(aFileSpec, &fsRef); if (err == noErr) return InitWithFSRef(&fsRef); else if (err == fnfErr) { CInfoPBRec pBlock; FSSpec parentDirSpec; memset(&pBlock, 0, sizeof(CInfoPBRec)); parentDirSpec.name[0] = 0; pBlock.dirInfo.ioVRefNum = aFileSpec->vRefNum; pBlock.dirInfo.ioDrDirID = aFileSpec->parID; pBlock.dirInfo.ioNamePtr = (StringPtr)parentDirSpec.name; pBlock.dirInfo.ioFDirIndex = -1; //get info on parID err = ::PBGetCatInfoSync(&pBlock); if (err != noErr) return MacErrorMapper(err); parentDirSpec.vRefNum = aFileSpec->vRefNum; parentDirSpec.parID = pBlock.dirInfo.ioDrParID; err = ::FSpMakeFSRef(&parentDirSpec, &fsRef); if (err != noErr) return MacErrorMapper(err); HFSUniStr255 unicodeName; err = ::HFSNameGetUnicodeName(aFileSpec->name, kTextEncodingUnknown, &unicodeName); if (err != noErr) return MacErrorMapper(err); nsresult rv = InitWithFSRef(&fsRef); if (NS_FAILED(rv)) return rv; return Append(nsDependentString(unicodeName.unicode, unicodeName.length)); } return MacErrorMapper(err); } /* void initToAppWithCreatorCode (in OSType aAppCreator); */ NS_IMETHODIMP nsLocalFile::InitToAppWithCreatorCode(OSType aAppCreator) { FSRef fsRef; OSErr err = ::LSFindApplicationForInfo(aAppCreator, nsnull, nsnull, &fsRef, nsnull); if (err != noErr) return MacErrorMapper(err); return InitWithFSRef(&fsRef); } /* CFURLRef getCFURL (); */ NS_IMETHODIMP nsLocalFile::GetCFURL(CFURLRef *_retval) { NS_ENSURE_ARG_POINTER(_retval); CFURLRef whichURLRef = mFollowLinks ? mTargetRef : mBaseRef; if (whichURLRef) ::CFRetain(whichURLRef); *_retval = whichURLRef; return whichURLRef ? NS_OK : NS_ERROR_FAILURE; } /* FSRef getFSRef (); */ NS_IMETHODIMP nsLocalFile::GetFSRef(FSRef *_retval) { NS_ENSURE_ARG_POINTER(_retval); return GetFSRefInternal(*_retval); } /* FSSpec getFSSpec (); */ NS_IMETHODIMP nsLocalFile::GetFSSpec(FSSpec *_retval) { NS_ENSURE_ARG_POINTER(_retval); if (!mBaseRef) return NS_ERROR_NOT_INITIALIZED; OSErr err; FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_SUCCEEDED(rv)) { // If the leaf node exists, things are simple. err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNone, nsnull, nsnull, _retval, nsnull); return MacErrorMapper(err); } else if (rv == NS_ERROR_FILE_NOT_FOUND) { // If the parent of the leaf exists, make an FSSpec from that. CFURLRef parentURLRef = ::CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorDefault, mBaseRef); if (!parentURLRef) return NS_ERROR_FAILURE; err = fnfErr; if (::CFURLGetFSRef(parentURLRef, &fsRef)) { FSCatalogInfo catalogInfo; if ((err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoVolume + kFSCatInfoNodeID + kFSCatInfoTextEncoding, &catalogInfo, nsnull, nsnull, nsnull)) == noErr) { nsAutoString leafName; if (NS_SUCCEEDED(GetLeafName(leafName))) { Str31 hfsName; if ((err = ::UnicodeNameGetHFSName(leafName.Length(), leafName.get(), catalogInfo.textEncodingHint, catalogInfo.nodeID == fsRtDirID, hfsName)) == noErr) err = ::FSMakeFSSpec(catalogInfo.volume, catalogInfo.nodeID, hfsName, _retval); } } } ::CFRelease(parentURLRef); rv = MacErrorMapper(err); } return rv; } /* readonly attribute PRInt64 fileSizeWithResFork; */ NS_IMETHODIMP nsLocalFile::GetFileSizeWithResFork(PRInt64 *aFileSizeWithResFork) { 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; } /* attribute OSType fileType; */ NS_IMETHODIMP nsLocalFile::GetFileType(OSType *aFileType) { NS_ENSURE_ARG_POINTER(aFileType); FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; FinderInfo fInfo; OSErr err = ::FSGetFinderInfo(&fsRef, &fInfo, nsnull, nsnull); if (err != noErr) return MacErrorMapper(err); *aFileType = fInfo.file.fileType; return NS_OK; } NS_IMETHODIMP nsLocalFile::SetFileType(OSType aFileType) { FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; OSErr err = ::FSChangeCreatorType(&fsRef, 0, aFileType); return MacErrorMapper(err); } /* attribute OSType fileCreator; */ NS_IMETHODIMP nsLocalFile::GetFileCreator(OSType *aFileCreator) { NS_ENSURE_ARG_POINTER(aFileCreator); FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; FinderInfo fInfo; OSErr err = ::FSGetFinderInfo(&fsRef, &fInfo, nsnull, nsnull); if (err != noErr) return MacErrorMapper(err); *aFileCreator = fInfo.file.fileCreator; return NS_OK; } NS_IMETHODIMP nsLocalFile::SetFileCreator(OSType aFileCreator) { FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; OSErr err = ::FSChangeCreatorType(&fsRef, aFileCreator, 0); return MacErrorMapper(err); } /* void setFileTypeAndCreatorFromMIMEType (in string aMIMEType); */ NS_IMETHODIMP nsLocalFile::SetFileTypeAndCreatorFromMIMEType(const char *aMIMEType) { // XXX - This should be cut from the API. Would create an evil dependency. NS_ERROR("NS_ERROR_NOT_IMPLEMENTED"); return NS_ERROR_NOT_IMPLEMENTED; } /* void setFileTypeAndCreatorFromExtension (in string aExtension); */ NS_IMETHODIMP nsLocalFile::SetFileTypeAndCreatorFromExtension(const char *aExtension) { // XXX - This should be cut from the API. Would create an evil dependency. NS_ERROR("NS_ERROR_NOT_IMPLEMENTED"); return NS_ERROR_NOT_IMPLEMENTED; } /* void launchWithDoc (in nsILocalFile aDocToLoad, in boolean aLaunchInBackground); */ NS_IMETHODIMP nsLocalFile::LaunchWithDoc(nsILocalFile *aDocToLoad, PRBool aLaunchInBackground) { 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; } /* void openDocWithApp (in nsILocalFile aAppToOpenWith, in boolean aLaunchInBackground); */ NS_IMETHODIMP nsLocalFile::OpenDocWithApp(nsILocalFile *aAppToOpenWith, PRBool aLaunchInBackground) { 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; } /* boolean isPackage (); */ NS_IMETHODIMP nsLocalFile::IsPackage(PRBool *_retval) { NS_ENSURE_ARG(_retval); *_retval = PR_FALSE; FSRef fsRef; nsresult rv = GetFSRefInternal(fsRef); if (NS_FAILED(rv)) return rv; FSCatalogInfo catalogInfo; OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNodeFlags + kFSCatInfoFinderInfo, &catalogInfo, nsnull, nsnull, nsnull); if (err != noErr) return MacErrorMapper(err); if ((catalogInfo.nodeFlags & kFSNodeIsDirectoryMask) != 0) { FileInfo *fInfoPtr = (FileInfo *)(catalogInfo.finderInfo); if ((fInfoPtr->finderFlags & kHasBundle) != 0) { *_retval = PR_TRUE; } else { // Folders ending with ".app" are also considered to // be packages, even if the top-level folder doesn't have bundle set nsCAutoString name; if (NS_SUCCEEDED(rv = GetNativeLeafName(name))) { const char *extPtr = strrchr(name.get(), '.'); if (extPtr) { if ((nsCRT::strcasecmp(extPtr, ".app") == 0)) *_retval = PR_TRUE; } } } } return NS_OK; } //***************************************************************************** // nsLocalFile Methods //***************************************************************************** #pragma mark - #pragma mark [Protected Methods] nsresult nsLocalFile::SetBaseRef(CFURLRef aCFURLRef) { NS_ENSURE_ARG(aCFURLRef); ::CFRetain(aCFURLRef); if (mBaseRef) ::CFRelease(mBaseRef); mBaseRef = aCFURLRef; mFollowLinksDirty = PR_TRUE; UpdateTargetRef(); mCachedFSRefValid = PR_FALSE; return NS_OK; } nsresult nsLocalFile::UpdateTargetRef() { if (!mBaseRef) return NS_ERROR_NOT_INITIALIZED; if (mFollowLinksDirty) { if (mTargetRef) { ::CFRelease(mTargetRef); mTargetRef = nsnull; } if (mFollowLinks) { mTargetRef = mBaseRef; ::CFRetain(mTargetRef); FSRef fsRef; if (::CFURLGetFSRef(mBaseRef, &fsRef)) { Boolean targetIsFolder, wasAliased; if (FSResolveAliasFile(&fsRef, true /*resolveAliasChains*/, &targetIsFolder, &wasAliased) == noErr && wasAliased) { ::CFRelease(mTargetRef); mTargetRef = CFURLCreateFromFSRef(NULL, &fsRef); if (!mTargetRef) return NS_ERROR_FAILURE; } } mFollowLinksDirty = PR_FALSE; } } return NS_OK; } nsresult nsLocalFile::GetFSRefInternal(FSRef& aFSRef, PRBool bForceUpdateCache) { if (bForceUpdateCache || !mCachedFSRefValid) { mCachedFSRefValid = PR_FALSE; CFURLRef whichURLRef = mFollowLinks ? mTargetRef : mBaseRef; NS_ENSURE_TRUE(whichURLRef, NS_ERROR_NULL_POINTER); if (::CFURLGetFSRef(whichURLRef, &mCachedFSRef)) mCachedFSRefValid = PR_TRUE; } if (mCachedFSRefValid) { aFSRef = mCachedFSRef; return NS_OK; } // CFURLGetFSRef only returns a Boolean for success, // so we have to assume what the error was. This is // the only probable cause. return NS_ERROR_FILE_NOT_FOUND; } nsresult nsLocalFile::GetPathInternal(nsACString& path) { nsresult rv = NS_ERROR_FAILURE; CFURLRef whichURLRef = mFollowLinks ? mTargetRef : mBaseRef; NS_ENSURE_TRUE(whichURLRef, NS_ERROR_NULL_POINTER); CFStringRef pathStrRef = ::CFURLCopyFileSystemPath(whichURLRef, kCFURLPOSIXPathStyle); if (pathStrRef) { rv = CFStringReftoUTF8(pathStrRef, path); ::CFRelease(pathStrRef); } return rv; } nsresult nsLocalFile::MoveCopy(nsIFile* aParentDir, const nsAString& newName, PRBool isCopy, PRBool followLinks) { StFollowLinksState srcFollowState(*this, followLinks); nsresult rv; OSErr err; FSRef srcFSRef, newFSRef; rv = GetFSRefInternal(srcFSRef); if (NS_FAILED(rv)) return rv; nsCOMPtr newParentDir = aParentDir; CFURLRef newBaseURLRef; 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, 0666); 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; if (isCopy) { err = ::FSCopyObject(&srcFSRef, &destFSRef, newName.Length(), newName.Length() ? PromiseFlatString(newName).get() : nsnull, 0, kFSCatInfoNone, false, false, nsnull, nsnull, &newFSRef); // don't update ourselves on a copy } else { // According to the API: "If 'this' is a file, and the destination file already // exists, moveTo will replace the old file." FSCatalogInfo catalogInfo; HFSUniStr255 leafName; err = ::FSGetCatalogInfo(&srcFSRef, kFSCatInfoNodeFlags, &catalogInfo, newName.IsEmpty() ? &leafName : nsnull, nsnull, nsnull); if (err == noErr) { if (!(catalogInfo.nodeFlags & kFSNodeIsDirectoryMask)) { FSRef oldFileRef; if (newName.IsEmpty()) err = ::FSMakeFSRefUnicode(&destFSRef, leafName.length, leafName.unicode, kTextEncodingUnknown, &oldFileRef); else err = ::FSMakeFSRefUnicode(&destFSRef, newName.Length(), PromiseFlatString(newName).get(), kTextEncodingUnknown, &oldFileRef); if (err == noErr) ::FSDeleteObject(&oldFileRef); } } // First, try the quick way which works only within the same volume err = ::FSMoveRenameObjectUnicode(&srcFSRef, &destFSRef, newName.Length(), newName.Length() ? PromiseFlatString(newName).get() : nsnull, kTextEncodingUnknown, &newFSRef); if (err == diffVolErr) { // If on different volumes, resort to copy & delete err = ::FSCopyObject(&srcFSRef, &destFSRef, newName.Length(), newName.Length() ? PromiseFlatString(newName).get() : nsnull, 0, kFSCatInfoNone, false, false, nsnull, nsnull, &newFSRef); ::FSDeleteObjects(&srcFSRef); } if (err == noErr) { newBaseURLRef = ::CFURLCreateFromFSRef(kCFAllocatorDefault, &newFSRef); if (!newBaseURLRef) return NS_ERROR_FAILURE; SetBaseRef(newBaseURLRef); ::CFRelease(newBaseURLRef); } } return MacErrorMapper(err); } const PRInt64 kMilisecsPerSec = 1000LL; const PRInt64 kUTCDateTimeFractionDivisor = 65535LL; PRInt64 nsLocalFile::HFSPlustoNSPRTime(const UTCDateTime& utcTime) { // Start with seconds since Jan. 1, 1904 GMT PRInt64 result = ((PRInt64)utcTime.highSeconds << 32) + (PRInt64)utcTime.lowSeconds; // Subtract to convert to NSPR epoch of 1970 result -= kJanuaryFirst1970Seconds; // Convert to milisecs result *= kMilisecsPerSec; // Convert the fraction to milisecs and add it result += ((PRInt64)utcTime.fraction * kMilisecsPerSec) / kUTCDateTimeFractionDivisor; return result; } void nsLocalFile::NSPRtoHFSPlusTime(PRInt64 nsprTime, UTCDateTime& utcTime) { PRInt64 fraction = nsprTime % kMilisecsPerSec; PRInt64 seconds = (nsprTime / kMilisecsPerSec) + kJanuaryFirst1970Seconds; utcTime.highSeconds = (UInt16)((PRUint64)seconds >> 32); utcTime.lowSeconds = (UInt32)seconds; utcTime.fraction = (UInt16)((fraction * kUTCDateTimeFractionDivisor) / kMilisecsPerSec); } nsresult nsLocalFile::CFStringReftoUTF8(CFStringRef aInStrRef, nsACString& aOutStr) { nsresult rv = NS_ERROR_FAILURE; CFIndex usedBufLen, inStrLen = ::CFStringGetLength(aInStrRef); CFIndex charsConverted = ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen), kCFStringEncodingUTF8, 0, PR_FALSE, nsnull, 0, &usedBufLen); if (charsConverted == inStrLen) { nsAutoBuffer buffer; if (buffer.EnsureElemCapacity(usedBufLen + 1)) { ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen), kCFStringEncodingUTF8, 0, false, buffer.get(), usedBufLen, &usedBufLen); buffer.get()[usedBufLen] = '\0'; aOutStr.Assign(nsDependentCString((char*)buffer.get())); rv = NS_OK; } } return rv; } //***************************************************************************** // Global Functions //***************************************************************************** #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_ConvertUTF8toUCS2(path), followLinks, result); } nsresult NS_NewLocalFileWithFSSpec(const FSSpec* inSpec, PRBool followLinks, nsILocalFileMac **result) { nsLocalFile* file = new nsLocalFile(); if (file == nsnull) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(file); file->SetFollowLinks(followLinks); nsresult rv = file->InitWithFSSpec(inSpec); if (NS_FAILED(rv)) { NS_RELEASE(file); return rv; } *result = file; return NS_OK; } //***************************************************************************** // Static Functions //***************************************************************************** static nsresult MacErrorMapper(OSErr inErr) { nsresult outErr; switch (inErr) { case noErr: outErr = NS_OK; break; case fnfErr: outErr = NS_ERROR_FILE_NOT_FOUND; break; case dupFNErr: outErr = NS_ERROR_FILE_ALREADY_EXISTS; break; case dskFulErr: outErr = NS_ERROR_FILE_DISK_FULL; break; case fLckdErr: outErr = NS_ERROR_FILE_IS_LOCKED; break; // Can't find good map for some case bdNamErr: outErr = NS_ERROR_FAILURE; break; default: outErr = NS_ERROR_FAILURE; break; } return outErr; } static OSErr FindRunningAppBySignature(OSType aAppSig, ProcessSerialNumber& outPsn) { ProcessInfoRec info; OSErr err = noErr; outPsn.highLongOfPSN = 0; outPsn.lowLongOfPSN = kNoProcess; while (PR_TRUE) { err = ::GetNextProcess(&outPsn); if (err == procNotFound) break; if (err != noErr) return err; info.processInfoLength = sizeof(ProcessInfoRec); info.processName = nil; info.processAppSpec = nil; err = ::GetProcessInformation(&outPsn, &info); if (err != noErr) return err; if (info.processSignature == aAppSig) return noErr; } return procNotFound; } // 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. typedef void (*UnicodeNormalizer) (CFMutableStringRef, CFStringNormalizationForm); static void CopyUTF8toUTF16NFC(const nsACString& aSrc, nsAString& aResult) { static PRBool sChecked = PR_FALSE; static UnicodeNormalizer sUnicodeNormalizer = NULL; // CFStringNormalize was not introduced until Mac OS 10.2 if (!sChecked) { CFBundleRef carbonBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Carbon")); if (carbonBundle) sUnicodeNormalizer = (UnicodeNormalizer) ::CFBundleGetFunctionPointerForName(carbonBundle, CFSTR("CFStringNormalize")); sChecked = PR_TRUE; } if (!sUnicodeNormalizer) { // OS X 10.2 or earlier CopyUTF8toUTF16(aSrc, aResult); return; } 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); sUnicodeNormalizer(inStr, kCFStringNormalizationFormC); CFIndex length = CFStringGetLength(inStr); const UniChar* chars = CFStringGetCharactersPtr(inStr); if (chars) aResult.Assign(chars, length); else { nsAutoBuffer buffer; if (!buffer.EnsureElemCapacity(length)) CopyUTF8toUTF16(aSrc, aResult); else { CFStringGetCharacters(inStr, CFRangeMake(0, length), buffer.get()); aResult.Assign(buffer.get(), length); } } CFRelease(inStr); }