/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is Mozilla Communicator client code, * released March 31, 1998. * * The Initial Developer of the Original Code is Netscape Communications * Corporation. Portions created by Netscape are * Copyright (C) 1998-1999 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): * Steve Dagley * John R. McMullen */ #include "nsCOMPtr.h" #include "nsMemory.h" #include "nsXPIDLString.h" #include "nsLocalFileMac.h" #include "nsNativeCharsetUtils.h" #include "nsISimpleEnumerator.h" #include "nsIComponentManager.h" #include "nsIInternetConfigService.h" #include "nsIMIMEInfo.h" #include "prtypes.h" #include "prerror.h" #include "nsReadableUtils.h" #include "nsITimelineService.h" #ifdef XP_MACOSX #include "nsXPIDLString.h" #include "private/pprio.h" #else #include "pprio.h" // Include this rather than prio.h so we get def of PR_ImportFile #endif #include "prmem.h" #include "plbase64.h" #include "FullPath.h" #include "FileCopy.h" #include "MoreFilesExtras.h" #include "DirectoryCopy.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "macDirectoryCopy.h" #include // Stupid @#$% header looks like its got extern mojo but it doesn't really extern "C" { #ifndef XP_MACOSX // BADPINK - this MSL header doesn't exist under macosx :-( #include #endif } #if TARGET_CARBON #include // Needed for definition of kUnresolvedCFragSymbolAddress #include #endif #pragma mark [Constants] const OSType kDefaultCreator = 'MOSS'; #pragma mark - #pragma mark [nsPathParser] class nsPathParser { public: nsPathParser(const nsACString &path); ~nsPathParser() { if (mAllocatedBuffer) nsMemory::Free(mAllocatedBuffer); } const char* First() { return nsCRT::strtok(mBuffer, ":", &mNewString); } const char* Next() { return nsCRT::strtok(mNewString, ":", &mNewString); } const char* Remainder() { return mNewString; } private: char mAutoBuffer[512]; char *mAllocatedBuffer; char *mBuffer, *mNewString; }; nsPathParser::nsPathParser(const nsACString &inPath) : mAllocatedBuffer(nsnull), mNewString(nsnull) { PRUint32 inPathLen = inPath.Length(); if (inPathLen >= sizeof(mAutoBuffer)) { mAllocatedBuffer = (char *)nsMemory::Alloc(inPathLen + 1); mBuffer = mAllocatedBuffer; } else mBuffer = mAutoBuffer; // copy inPath into mBuffer nsACString::const_iterator start, end; inPath.BeginReading(start); inPath.EndReading(end); PRUint32 size, offset = 0; for ( ; start != end; start.advance(size)) { const char* buf = start.get(); size = start.size_forward(); memcpy(mBuffer + offset, buf, size); offset += size; } mBuffer[offset] = '\0'; } #pragma mark - #pragma mark [static util funcs] static inline void ClearFSSpec(FSSpec& aSpec) { aSpec.vRefNum = 0; aSpec.parID = 0; aSpec.name[0] = 0; } // Simple func to map Mac OS errors into nsresults 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; } /*---------------------------------------------------------------------------- IsEqualFSSpec Compare two canonical FSSpec records. Entry: file1 = pointer to first FSSpec record. file2 = pointer to second FSSpec record. Exit: function result = true if the FSSpec records are equal. ----------------------------------------------------------------------------*/ static PRBool IsEqualFSSpec(const FSSpec& file1, const FSSpec& file2) { return file1.vRefNum == file2.vRefNum && file1.parID == file2.parID && EqualString(file1.name, file2.name, false, true); } /*---------------------------------------------------------------------------- GetParentFolderSpec Given an FSSpec to a (possibly non-existent) file, get an FSSpec for its parent directory. ----------------------------------------------------------------------------*/ static OSErr GetParentFolderSpec(const FSSpec& fileSpec, FSSpec& parentDirSpec) { CInfoPBRec pBlock = {0}; OSErr err = noErr; parentDirSpec.name[0] = 0; pBlock.dirInfo.ioVRefNum = fileSpec.vRefNum; pBlock.dirInfo.ioDrDirID = fileSpec.parID; pBlock.dirInfo.ioNamePtr = (StringPtr)parentDirSpec.name; pBlock.dirInfo.ioFDirIndex = -1; //get info on parID err = PBGetCatInfoSync(&pBlock); if (err != noErr) return err; parentDirSpec.vRefNum = fileSpec.vRefNum; parentDirSpec.parID = pBlock.dirInfo.ioDrParID; return err; } /*---------------------------------------------------------------------------- VolHasDesktopDB Check to see if a volume supports the new desktop database. Entry: vRefNum = vol ref num of volumn Exit: function result = error code. *hasDesktop = true if volume has the new desktop database. ----------------------------------------------------------------------------*/ static OSErr VolHasDesktopDB (short vRefNum, Boolean *hasDesktop) { HParamBlockRec pb; GetVolParmsInfoBuffer info; OSErr err = noErr; pb.ioParam.ioCompletion = nil; pb.ioParam.ioNamePtr = nil; pb.ioParam.ioVRefNum = vRefNum; pb.ioParam.ioBuffer = (Ptr)&info; pb.ioParam.ioReqCount = sizeof(info); err = PBHGetVolParmsSync(&pb); *hasDesktop = err == noErr && (info.vMAttrib & (1L << bHasDesktopMgr)) != 0; return err; } /*---------------------------------------------------------------------------- GetLastModDateTime Get the last mod date and time of a file. Entry: fSpec = pointer to file spec. Exit: function result = error code. *lastModDateTime = last mod date and time. ----------------------------------------------------------------------------*/ static OSErr GetLastModDateTime(const FSSpec *fSpec, unsigned long *lastModDateTime) { CInfoPBRec pBlock; OSErr err = noErr; pBlock.hFileInfo.ioNamePtr = (StringPtr)fSpec->name; pBlock.hFileInfo.ioVRefNum = fSpec->vRefNum; pBlock.hFileInfo.ioFDirIndex = 0; pBlock.hFileInfo.ioDirID = fSpec->parID; err = PBGetCatInfoSync(&pBlock); if (err != noErr) return err; *lastModDateTime = pBlock.hFileInfo.ioFlMdDat; return noErr; } /*---------------------------------------------------------------------------- FindAppOnVolume Find an application on a volume. Entry: sig = application signature. vRefNum = vol ref num Exit: function result = error code = afpItemNotFound if app not found on vol. *file = file spec for application on volume. ----------------------------------------------------------------------------*/ static OSErr FindAppOnVolume (OSType sig, short vRefNum, FSSpec *file) { DTPBRec pb; OSErr err = noErr; short ioDTRefNum, i; FInfo fInfo; FSSpec candidate; unsigned long lastModDateTime, maxLastModDateTime; memset(&pb, 0, sizeof(DTPBRec)); pb.ioCompletion = nil; pb.ioVRefNum = vRefNum; pb.ioNamePtr = nil; err = PBDTGetPath(&pb); if (err != noErr) return err; ioDTRefNum = pb.ioDTRefNum; memset(&pb, 0, sizeof(DTPBRec)); pb.ioCompletion = nil; pb.ioIndex = 0; pb.ioFileCreator = sig; pb.ioNamePtr = file->name; pb.ioDTRefNum = ioDTRefNum; err = PBDTGetAPPLSync(&pb); if (err == fnfErr || err == paramErr) return afpItemNotFound; if (err != noErr) return err; file->vRefNum = vRefNum; file->parID = pb.ioAPPLParID; err = FSpGetFInfo(file, &fInfo); if (err == noErr) return noErr; i = 1; maxLastModDateTime = 0; while (true) { memset(&pb, 0, sizeof(DTPBRec)); pb.ioCompletion = nil; pb.ioIndex = i; pb.ioFileCreator = sig; pb.ioNamePtr = candidate.name; pb.ioDTRefNum = ioDTRefNum; err = PBDTGetAPPLSync(&pb); if (err != noErr) break; candidate.vRefNum = vRefNum; candidate.parID = pb.ioAPPLParID; err = GetLastModDateTime(file, &lastModDateTime); if (err == noErr) { if (lastModDateTime > maxLastModDateTime) { maxLastModDateTime = lastModDateTime; *file = candidate; } } i++; } return maxLastModDateTime > 0 ? noErr : afpItemNotFound; } /*---------------------------------------------------------------------------- GetIndVolume Get a volume reference number by volume index. Entry: index = volume index Exit: function result = error code. *vRefNum = vol ref num of indexed volume. ----------------------------------------------------------------------------*/ static OSErr GetIndVolume(short index, short *vRefNum) { HParamBlockRec pb; Str63 volumeName; OSErr err = noErr; pb.volumeParam.ioCompletion = nil; pb.volumeParam.ioNamePtr = volumeName; pb.volumeParam.ioVolIndex = index; err = PBHGetVInfoSync(&pb); *vRefNum = pb.volumeParam.ioVRefNum; return err; } // Private NSPR functions static unsigned long gJanuaryFirst1970Seconds = 0; /* * The geographic location and time zone information of a Mac * are stored in extended parameter RAM. The ReadLocation * produdure uses the geographic location record, MachineLocation, * to read the geographic location and time zone information in * extended parameter RAM. * * Because serial port and SLIP conflict with ReadXPram calls, * we cache the call here. * * Caveat: this caching will give the wrong result if a session * extend across the DST changeover time. */ static void MyReadLocation(MachineLocation *loc) { static MachineLocation storedLoc; static Boolean didReadLocation = false; if (!didReadLocation) { ReadLocation(&storedLoc); didReadLocation = true; } *loc = storedLoc; } static long GMTDelta(void) { MachineLocation loc; long gmtDelta; MyReadLocation(&loc); gmtDelta = loc.u.gmtDelta & 0x00ffffff; if (gmtDelta & 0x00800000) { /* test sign extend bit */ gmtDelta |= 0xff000000; } return gmtDelta; } static void MacintoshInitializeTime(void) { /* * The NSPR epoch is midnight, Jan. 1, 1970 GMT. * * At midnight Jan. 1, 1970 GMT, the local time was * midnight Jan. 1, 1970 + GMTDelta(). * * Midnight Jan. 1, 1970 is 86400 * (365 * (1970 - 1904) + 17) * = 2082844800 seconds since the Mac epoch. * (There were 17 leap years from 1904 to 1970.) * * So the NSPR epoch is 2082844800 + GMTDelta() seconds since * the Mac epoch. Whew! :-) */ gJanuaryFirst1970Seconds = 2082844800 + GMTDelta(); } static nsresult ConvertMacTimeToMilliseconds( PRInt64* aLastModifiedTime, PRUint32 timestamp ) { if ( gJanuaryFirst1970Seconds == 0) MacintoshInitializeTime(); timestamp -= gJanuaryFirst1970Seconds; PRTime usecPerSec, dateInMicroSeconds; LL_I2L(dateInMicroSeconds, timestamp); LL_I2L(usecPerSec, PR_MSEC_PER_SEC); LL_MUL(*aLastModifiedTime, usecPerSec, dateInMicroSeconds); return NS_OK; } static nsresult ConvertMillisecondsToMacTime(PRInt64 aTime, PRUint32 *aOutMacTime) { NS_ENSURE_ARG( aOutMacTime ); PRTime usecPerSec, dateInSeconds; dateInSeconds = LL_ZERO; LL_I2L(usecPerSec, PR_MSEC_PER_SEC); LL_DIV(dateInSeconds, aTime, usecPerSec); // dateInSeconds = aTime/1,000 LL_L2UI(*aOutMacTime, dateInSeconds); *aOutMacTime += 2082844800; // date + Mac epoch return NS_OK; } static void myPLstrcpy(Str255 dst, const char* src) { int srcLength = strlen(src); NS_ASSERTION(srcLength <= 255, "Oops, Str255 can't hold >255 chars"); if (srcLength > 255) srcLength = 255; dst[0] = srcLength; memcpy(&dst[1], src, srcLength); } static void myPLstrncpy(Str255 dst, const char* src, int inMax) { int srcLength = strlen(src); if (srcLength > inMax) srcLength = inMax; dst[0] = srcLength; memcpy(&dst[1], src, srcLength); } /* NS_TruncNodeName Utility routine to do a mid-trunc on a potential file name so that it is no longer than 31 characters. Until we move to the HFS+ APIs we need this to come up with legal Mac file names. Entry: aNode = initial file name outBuf = scratch buffer for the truncated name (MUST be >= 32 characters) Exit: function result = pointer to truncated name. Will be either aNode or outBuf. */ const char* NS_TruncNodeName(const char *aNode, char *outBuf) { PRUint32 nodeLen; if ((nodeLen = strlen(aNode)) > 31) { static PRBool sInitialized = PR_FALSE; static CharByteTable sTable; // Init to "..." in case we fail to get the ellipsis token static char sEllipsisTokenStr[4] = { '.', '.', '.', 0 }; static PRUint8 sEllipsisTokenLen = 3; if (!sInitialized) { // Entries in the table are: // 0 == 1 byte char // 1 == 2 byte char FillParseTable(sTable, smSystemScript); Handle itl4ResHandle = nsnull; long offset, len; ::GetIntlResourceTable(smSystemScript, smUnTokenTable, &itl4ResHandle, &offset, &len); if (itl4ResHandle) { UntokenTable *untokenTableRec = (UntokenTable *)(*itl4ResHandle + offset); if (untokenTableRec->lastToken >= tokenEllipsis) { offset += untokenTableRec->index[tokenEllipsis]; char *tokenStr = (*itl4ResHandle + offset); sEllipsisTokenLen = tokenStr[0]; memcpy(sEllipsisTokenStr, &tokenStr[1], sEllipsisTokenLen); } ::ReleaseResource(itl4ResHandle); } sInitialized = PR_TRUE; } PRInt32 halfLen = (31 - sEllipsisTokenLen) / 2; PRInt32 charSize = 0, srcPos, destPos; for (srcPos = 0; srcPos + charSize <= halfLen; srcPos += charSize) charSize = sTable[aNode[srcPos]] ? 2 : 1; memcpy(outBuf, aNode, srcPos); memcpy(outBuf + srcPos, sEllipsisTokenStr, sEllipsisTokenLen); destPos = srcPos + sEllipsisTokenLen; for (; srcPos < nodeLen - halfLen; srcPos += charSize) charSize = sTable[aNode[srcPos]] ? 2 : 1; memcpy(outBuf + destPos, aNode + srcPos, nodeLen - srcPos); destPos += (nodeLen - srcPos); outBuf[destPos] = '\0'; return outBuf; } return aNode; } /** * HFSPlusGetRawPath returns the path for an FSSpec as a unicode string. * * The reason for this routine instead of just calling FSRefMakePath is * (1) inSpec does not have to exist * (2) FSRefMakePath uses '/' as the separator under OSX and ':' under OS9 */ static OSErr HFSPlusGetRawPath(const FSSpec& inSpec, nsAString& outStr) { OSErr err; nsAutoString ucPathString; outStr.Truncate(0); FSRef nodeRef; FSCatalogInfo catalogInfo; catalogInfo.parentDirID = 0; err = ::FSpMakeFSRef(&inSpec, &nodeRef); if (err == fnfErr) { FSSpec parentDirSpec; err = GetParentFolderSpec(inSpec, parentDirSpec); if (err == noErr) { const char *startPtr = (const char*)&inSpec.name[1]; NS_CopyNativeToUnicode(Substring(startPtr, startPtr + PRUint32(inSpec.name[0])), outStr); err = ::FSpMakeFSRef(&parentDirSpec, &nodeRef); } } while (err == noErr && catalogInfo.parentDirID != fsRtParID) { HFSUniStr255 nodeName; FSRef parentRef; err = ::FSGetCatalogInfo(&nodeRef, kFSCatInfoNodeFlags + kFSCatInfoParentDirID, &catalogInfo, &nodeName, nsnull, &parentRef); if (err == noErr) { if (catalogInfo.nodeFlags & kFSNodeIsDirectoryMask) nodeName.unicode[nodeName.length++] = PRUnichar(':'); const PRUnichar* nodeNameUni = (const PRUnichar*) nodeName.unicode; outStr.Insert(Substring(nodeNameUni, nodeNameUni + nodeName.length), 0); nodeRef = parentRef; } } return err; } // The R**co FSSpec resolver - // it slices, it dices, it juliannes fries and it even creates FSSpecs out of whatever you feed it // This function will take a path and a starting FSSpec and generate a FSSpec to represent // the target of the two. If the intial FSSpec is null the path alone will be resolved static OSErr ResolvePathAndSpec(const char * filePath, FSSpec *inSpec, PRBool createDirs, FSSpec *outSpec) { OSErr err = noErr; size_t inLength = strlen(filePath); Boolean isRelative = (filePath && inSpec); FSSpec tempSpec; Str255 ppath; Boolean isDirectory; if (isRelative && inSpec) { outSpec->vRefNum = inSpec->vRefNum; outSpec->parID = inSpec->parID; if (inSpec->name[0] != 0) { long theDirID; err = FSpGetDirectoryID(inSpec, &theDirID, &isDirectory); if (err == noErr && isDirectory) outSpec->parID = theDirID; else if (err == fnfErr && createDirs) { err = FSpDirCreate(inSpec, smCurrentScript, &theDirID); if (err == noErr) outSpec->parID = theDirID; else if (err == fnfErr) err = dirNFErr; } } } else { outSpec->vRefNum = 0; outSpec->parID = 0; } if (err) return err; // Try making an FSSpec from the path if (inLength < 255) { // Use tempSpec as dest because if FSMakeFSSpec returns dirNFErr, it // will reset the dest spec and we'll lose what we determined above. myPLstrcpy(ppath, filePath); err = ::FSMakeFSSpec(outSpec->vRefNum, outSpec->parID, ppath, &tempSpec); if (err == noErr || err == fnfErr) *outSpec = tempSpec; } else if (!isRelative) { err = ::FSpLocationFromFullPath(inLength, filePath, outSpec); } else { // If the path is relative and >255 characters we need to manually walk the // path appending each node to the initial FSSpec so to reach that code we // set the err to bdNamErr and fall into the code below err = bdNamErr; } // If we successfully created a spec then leave if (err == noErr) return err; // We get here when the directory hierarchy needs to be created or we're resolving // a relative path >255 characters long if (err == dirNFErr || err == bdNamErr) { const char* path = filePath; if (!isRelative) // If path is relative, we still need vRefNum & parID. { outSpec->vRefNum = 0; outSpec->parID = 0; } do { // Locate the colon that terminates the node. // But if we've a partial path (starting with a colon), find the second one. const char* nextColon = strchr(path + (*path == ':'), ':'); // Well, if there are no more colons, point to the end of the string. if (!nextColon) nextColon = path + strlen(path); // Make a pascal string out of this node. Include initial // and final colon, if any! myPLstrncpy(ppath, path, nextColon - path + 1); // Use this string as a relative path using the directory created // on the previous round (or directory 0,0 on the first round). err = ::FSMakeFSSpec(outSpec->vRefNum, outSpec->parID, ppath, outSpec); // If this was the leaf node, then we are done. if (!*nextColon) break; // Since there's more to go, we have to get the directory ID, which becomes // the parID for the next round. if (err == noErr) { // The directory (or perhaps a file) exists. Find its dirID. long dirID; err = ::FSpGetDirectoryID(outSpec, &dirID, &isDirectory); if (!isDirectory) err = dupFNErr; // oops! a file exists with that name. if (err != noErr) break; // bail if we've got an error outSpec->parID = dirID; } else if ((err == fnfErr) && createDirs) { // If we got "file not found" and we're allowed to create directories // then we need to create one err = ::FSpDirCreate(outSpec, smCurrentScript, &outSpec->parID); // For some reason, this usually returns fnfErr, even though it works. if (err == fnfErr) err = noErr; } if (err != noErr) break; path = nextColon; // next round } while (true); } return err; } #pragma mark - #pragma mark [StFollowLinksState] class StFollowLinksState { public: StFollowLinksState(nsILocalFile *aFile) : mFile(aFile) { NS_ASSERTION(mFile, "StFollowLinksState passed a NULL file."); if (mFile) mFile->GetFollowLinks(&mSavedState); } StFollowLinksState(nsILocalFile *aFile, PRBool followLinksState) : mFile(aFile) { NS_ASSERTION(mFile, "StFollowLinksState passed a NULL file."); if (mFile) { mFile->GetFollowLinks(&mSavedState); mFile->SetFollowLinks(followLinksState); } } ~StFollowLinksState() { if (mFile) mFile->SetFollowLinks(mSavedState); } private: nsCOMPtr mFile; PRBool mSavedState; }; #pragma mark - #pragma mark [nsDirEnumerator] class nsDirEnumerator : public nsISimpleEnumerator { public: NS_DECL_ISUPPORTS nsDirEnumerator() { NS_INIT_ISUPPORTS(); } nsresult Init(nsILocalFileMac* parent) { NS_ENSURE_ARG(parent); nsresult rv; FSSpec fileSpec; rv = parent->GetFSSpec(&fileSpec); if (NS_FAILED(rv)) return rv; OSErr err; Boolean isDirectory; err = ::FSpGetDirectoryID(&fileSpec, &mDirID, &isDirectory); if (err || !isDirectory) return NS_ERROR_FILE_NOT_DIRECTORY; mCatInfo.hFileInfo.ioNamePtr = mItemName; mCatInfo.hFileInfo.ioVRefNum = fileSpec.vRefNum; mItemIndex = 1; return NS_OK; } NS_IMETHOD HasMoreElements(PRBool *result) { nsresult rv = NS_OK; if (mNext == nsnull) { mItemName[0] = 0; mCatInfo.dirInfo.ioFDirIndex = mItemIndex; mCatInfo.dirInfo.ioDrDirID = mDirID; OSErr err = ::PBGetCatInfoSync(&mCatInfo); if (err == fnfErr) { // end of dir entries *result = PR_FALSE; return NS_OK; } // Make a new nsILocalFile for the new element FSSpec tempSpec; tempSpec.vRefNum = mCatInfo.hFileInfo.ioVRefNum; tempSpec.parID = mDirID; ::BlockMoveData(mItemName, tempSpec.name, mItemName[0] + 1); rv = NS_NewLocalFileWithFSSpec(&tempSpec, PR_TRUE, getter_AddRefs(mNext)); if (NS_FAILED(rv)) return rv; } *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; ++mItemIndex; return NS_OK; } virtual ~nsDirEnumerator() { } protected: nsCOMPtr mNext; CInfoPBRec mCatInfo; short mItemIndex; long mDirID; Str63 mItemName; }; NS_IMPL_ISUPPORTS1(nsDirEnumerator, nsISimpleEnumerator) #pragma mark - OSType nsLocalFile::sCurrentProcessSignature = 0; PRBool nsLocalFile::sHasHFSPlusAPIs = PR_FALSE; PRBool nsLocalFile::sRunningOSX = PR_FALSE; #pragma mark [CTOR/DTOR] nsLocalFile::nsLocalFile() : mFollowLinks(PR_TRUE), mFollowLinksDirty(PR_TRUE), mSpecDirty(PR_TRUE), mCatInfoDirty(PR_TRUE), mType('TEXT'), mCreator(kDefaultCreator) { NS_INIT_ISUPPORTS(); ClearFSSpec(mSpec); ClearFSSpec(mTargetSpec); InitClassStatics(); if (sCurrentProcessSignature != 0) mCreator = sCurrentProcessSignature; } nsLocalFile::nsLocalFile(const nsLocalFile& srcFile) { NS_INIT_ISUPPORTS(); *this = srcFile; } nsLocalFile::nsLocalFile(const FSSpec& aSpec, const nsACString& aAppendedPath) : mFollowLinks(PR_TRUE), mFollowLinksDirty(PR_TRUE), mSpecDirty(PR_TRUE), mSpec(aSpec), mAppendedPath(aAppendedPath), mCatInfoDirty(PR_TRUE), mType('TEXT'), mCreator(kDefaultCreator) { NS_INIT_ISUPPORTS(); ClearFSSpec(mTargetSpec); InitClassStatics(); if (sCurrentProcessSignature != 0) mCreator = sCurrentProcessSignature; } nsLocalFile& nsLocalFile::operator=(const nsLocalFile& rhs) { mFollowLinks = rhs.mFollowLinks; mFollowLinksDirty = rhs.mFollowLinksDirty; mSpecDirty = rhs.mSpecDirty; mSpec = rhs.mSpec; mAppendedPath = rhs.mAppendedPath; mTargetSpec = rhs.mTargetSpec; mCatInfoDirty = rhs.mCatInfoDirty; mType = rhs.mType; mCreator = rhs.mCreator; if (!rhs.mCatInfoDirty) mCachedCatInfo = rhs.mCachedCatInfo; return *this; } nsLocalFile::~nsLocalFile() { } #pragma mark - #pragma mark [nsISupports interface implementation] 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; } // This function resets any cached information about the file. void nsLocalFile::MakeDirty() { mSpecDirty = PR_TRUE; mFollowLinksDirty = PR_TRUE; mCatInfoDirty = PR_TRUE; ClearFSSpec(mTargetSpec); } /* 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; mFollowLinksDirty = PR_TRUE; } return NS_OK; } NS_IMETHODIMP nsLocalFile::ResolveAndStat() { OSErr err = noErr; FSSpec resolvedSpec; // fnfErr means target spec is valid but doesn't exist. // If the end result is fnfErr, we're cleanly resolved. if (mSpecDirty) { if (mAppendedPath.Length()) { err = ResolvePathAndSpec(mAppendedPath.get(), &mSpec, PR_FALSE, &resolvedSpec); if (err == noErr) mAppendedPath.Truncate(0); } else err = ::FSMakeFSSpec(mSpec.vRefNum, mSpec.parID, mSpec.name, &resolvedSpec); if (err == noErr) { mSpec = resolvedSpec; mSpecDirty = PR_FALSE; } mFollowLinksDirty = PR_TRUE; } if (mFollowLinksDirty && (err == noErr)) { if (mFollowLinks) { // Resolve the alias to the original file. resolvedSpec = mSpec; Boolean resolvedWasFolder, resolvedWasAlias; err = ::ResolveAliasFile(&resolvedSpec, TRUE, &resolvedWasFolder, &resolvedWasAlias); if (err == noErr || err == fnfErr) { err = noErr; mTargetSpec = resolvedSpec; mFollowLinksDirty = PR_FALSE; } } else { mTargetSpec = mSpec; mFollowLinksDirty = PR_FALSE; } mCatInfoDirty = PR_TRUE; } return (MacErrorMapper(err)); } NS_IMETHODIMP nsLocalFile::Clone(nsIFile **file) { NS_ENSURE_ARG(file); *file = nsnull; // Just copy-construct ourselves nsCOMPtr localFile = new nsLocalFile(*this); if (localFile == NULL) return NS_ERROR_OUT_OF_MEMORY; *file = localFile; NS_ADDREF(*file); return NS_OK; } NS_IMETHODIMP nsLocalFile::InitWithNativePath(const nsACString &filePath) { // The incoming path must be a FULL path if (filePath.IsEmpty()) return NS_ERROR_INVALID_ARG; MakeDirty(); // If it starts with a colon, it's invalid if (filePath.First() == ':') return NS_ERROR_FILE_UNRECOGNIZED_PATH; nsPathParser parser(filePath); OSErr err; Str255 pascalNode; FSSpec nodeSpec; const char *root = parser.First(); if (root == nsnull) return NS_ERROR_FILE_UNRECOGNIZED_PATH; // The first component must be an existing volume myPLstrcpy(pascalNode, root); pascalNode[++pascalNode[0]] = ':'; err = ::FSMakeFSSpec(0, 0, pascalNode, &nodeSpec); if (err) return NS_ERROR_FILE_UNRECOGNIZED_PATH; // Build as much of a spec as possible from the rest of the path // What doesn't exist will be left over in mAppendedPath const char *nextNode; while ((nextNode = parser.Next()) != nsnull) { long dirID; Boolean isDir; err = ::FSpGetDirectoryID(&nodeSpec, &dirID, &isDir); if (err || !isDir) break; myPLstrcpy(pascalNode, nextNode); err = ::FSMakeFSSpec(nodeSpec.vRefNum, dirID, pascalNode, &nodeSpec); if (err == fnfErr) break; } mSpec = nodeSpec; mAppendedPath = parser.Remainder(); return NS_OK; } NS_IMETHODIMP nsLocalFile::InitWithPath(const nsAString &filePath) { nsresult rv; nsCAutoString fsStr; if (NS_SUCCEEDED(rv = NS_CopyUnicodeToNative(filePath, fsStr))) rv = InitWithNativePath(fsStr); return rv; } NS_IMETHODIMP nsLocalFile::InitWithFile(nsILocalFile *aFile) { NS_ENSURE_ARG(aFile); nsLocalFile *asLocalFile = dynamic_cast(aFile); if (!asLocalFile) return NS_ERROR_NO_INTERFACE; // Well, sort of. *this = *asLocalFile; return NS_OK; } NS_IMETHODIMP nsLocalFile::OpenNSPRFileDesc(PRInt32 flags, PRInt32 mode, PRFileDesc **_retval) { // Macintosh doesn't really have mode bits, just drop them #pragma unused (mode) NS_ENSURE_ARG(_retval); nsresult rv = NS_OK; FSSpec spec; OSErr err = noErr; rv = ResolveAndStat(); if (rv == NS_ERROR_FILE_NOT_FOUND && (flags & PR_CREATE_FILE)) rv = NS_OK; if (flags & PR_CREATE_FILE) { rv = Create(nsIFile::NORMAL_FILE_TYPE, 0); /* If opening with the PR_EXCL flag the existence of the file prior to opening is an error */ if ((flags & PR_EXCL) && (rv == NS_ERROR_FILE_ALREADY_EXISTS)) return rv; } rv = GetFSSpec(&spec); if (NS_FAILED(rv)) return rv; SInt8 perm; if (flags & PR_RDWR) perm = fsRdWrPerm; else if (flags & PR_WRONLY) perm = fsWrPerm; else perm = fsRdPerm; short refnum; err = ::FSpOpenDF(&spec, perm, &refnum); if (err == noErr && (flags & PR_TRUNCATE)) err = ::SetEOF(refnum, 0); if (err == noErr && (flags & PR_APPEND)) err = ::SetFPos(refnum, fsFromLEOF, 0); if (err != noErr) return MacErrorMapper(err); if ((*_retval = PR_ImportFile(refnum)) == 0) return NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES,(PR_GetError() & 0xFFFF)); return NS_OK; } NS_IMETHODIMP nsLocalFile::OpenANSIFileDesc(const char *mode, FILE * *_retval) { NS_ENSURE_ARG(mode); NS_ENSURE_ARG_POINTER(_retval); nsresult rv; FSSpec spec; rv = ResolveAndStat(); if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) return rv; if (mode[0] == 'w' || mode[0] == 'a') // Create if it doesn't exist { if (rv == NS_ERROR_FILE_NOT_FOUND) { mType = (mode[1] == 'b') ? 'BiNA' : 'TEXT'; rv = Create(nsIFile::NORMAL_FILE_TYPE, 0); if (NS_FAILED(rv)) return rv; } } rv = GetFSSpec(&spec); if (NS_FAILED(rv)) return rv; #ifdef MACOSX // FSp_fopen() doesn't exist under macosx :-( nsXPIDLCString ourPath; rv = GetPath(getter_Copies(ourPath)); if (NS_FAILED(rv)) return rv; *_retval = fopen(ourPath, mode); #else *_retval = FSp_fopen(&spec, mode); #endif if (*_retval) return NS_OK; return NS_ERROR_FAILURE; } NS_IMETHODIMP nsLocalFile::Create(PRUint32 type, PRUint32 attributes) { OSErr err; if (type != NORMAL_FILE_TYPE && type != DIRECTORY_TYPE) return NS_ERROR_FILE_UNKNOWN_TYPE; FSSpec newSpec; if (mAppendedPath.Length()) { // We've got an FSSpec and an appended path so pass 'em both to ResolvePathAndSpec err = ResolvePathAndSpec(mAppendedPath.get(), &mSpec, PR_TRUE, &newSpec); } else { err = ::FSMakeFSSpec(mSpec.vRefNum, mSpec.parID, mSpec.name, &newSpec); } if (err != noErr && err != fnfErr) return (MacErrorMapper(err)); switch (type) { case NORMAL_FILE_TYPE: SetOSTypeAndCreatorFromExtension(); err = ::FSpCreate(&newSpec, mCreator, mType, smCurrentScript); break; case DIRECTORY_TYPE: { long newDirID; err = ::FSpDirCreate(&newSpec, smCurrentScript, &newDirID); // For some reason, this usually returns fnfErr, even though it works. if (err == fnfErr) err = noErr; } break; default: return NS_ERROR_FILE_UNKNOWN_TYPE; break; } if (err == noErr) { mSpec = mTargetSpec = newSpec; mAppendedPath.Truncate(0); } return (MacErrorMapper(err)); } NS_IMETHODIMP nsLocalFile::AppendNative(const nsACString &aNode) { if (aNode.IsEmpty()) return NS_OK; nsACString::const_iterator start, end; aNode.BeginReading(start); aNode.EndReading(end); if (FindCharInReadable(':', start, end)) return NS_ERROR_FILE_UNRECOGNIZED_PATH; MakeDirty(); char truncBuffer[32]; const char *node = NS_TruncNodeName(PromiseFlatCString(aNode).get(), truncBuffer); if (!mAppendedPath.Length()) { OSErr err; Boolean resolvedWasFolder, resolvedWasAlias; err = ::ResolveAliasFile(&mSpec, TRUE, &resolvedWasFolder, &resolvedWasAlias); if (err == noErr) { long dirID; Boolean isDir; if (!resolvedWasFolder) return NS_ERROR_FILE_NOT_DIRECTORY; if ((err = ::FSpGetDirectoryID(&mSpec, &dirID, &isDir)) != noErr) return MacErrorMapper(err); FSSpec childSpec; Str255 pascalNode; myPLstrcpy(pascalNode, node); err = ::FSMakeFSSpec(mSpec.vRefNum, dirID, pascalNode, &childSpec); if (err && err != fnfErr) return MacErrorMapper(err); mSpec = childSpec; } else if (err == fnfErr) mAppendedPath.Assign(node); else return MacErrorMapper(err); } else { if (mAppendedPath.First() != ':') mAppendedPath.Insert(':', 0); mAppendedPath.Append(":"); mAppendedPath.Append(node); } return NS_OK; } NS_IMETHODIMP nsLocalFile::Append(const nsAString &node) { nsresult rv; nsCAutoString fsStr; if (NS_SUCCEEDED(rv = NS_CopyUnicodeToNative(node, fsStr))) rv = AppendNative(fsStr); return rv; } NS_IMETHODIMP nsLocalFile::AppendRelativeNativePath(const nsACString &relPath) { if (relPath.IsEmpty()) return NS_ERROR_INVALID_ARG; nsresult rv; nsPathParser parser(relPath); const char* node = parser.First(); while (node) { if (NS_FAILED(rv = AppendNative(nsDependentCString(node)))) return rv; node = parser.Next(); } return NS_OK; } NS_IMETHODIMP nsLocalFile::AppendRelativePath(const nsAString &relPath) { nsresult rv; nsCAutoString fsStr; if (NS_SUCCEEDED(rv = NS_CopyUnicodeToNative(relPath, fsStr))) rv = AppendRelativeNativePath(fsStr); return rv; } NS_IMETHODIMP nsLocalFile::GetNativeLeafName(nsACString &aLeafName) { aLeafName.Truncate(); // See if we've had a path appended if (mAppendedPath.Length()) { const char* temp = mAppendedPath.get(); if (temp == nsnull) return NS_ERROR_FILE_UNRECOGNIZED_PATH; const char* leaf = strrchr(temp, ':'); // if the working path is just a node without any directory delimeters. if (leaf == nsnull) leaf = temp; else leaf++; aLeafName = leaf; } else { // We don't have an appended path so grab the leaf name from the FSSpec // Convert the Pascal string to a C string PRInt32 len = mSpec.name[0]; char* leafName = (char *)malloc(len + 1); if (!leafName) return NS_ERROR_OUT_OF_MEMORY; ::BlockMoveData(&mSpec.name[1], leafName, len); leafName[len] = '\0'; aLeafName = leafName; free(leafName); } return NS_OK; } NS_IMETHODIMP nsLocalFile::GetLeafName(nsAString &aLeafName) { nsresult rv; nsCAutoString fsStr; if (NS_SUCCEEDED(rv = GetNativeLeafName(fsStr))) rv = NS_CopyNativeToUnicode(fsStr, aLeafName); return rv; } NS_IMETHODIMP nsLocalFile::SetNativeLeafName(const nsACString &aLeafName) { if (aLeafName.IsEmpty()) return NS_ERROR_INVALID_ARG; MakeDirty(); char truncBuffer[32]; const char *leafName = NS_TruncNodeName(PromiseFlatCString(aLeafName).get(), truncBuffer); if (mAppendedPath.Length()) { // Lop off the end of the appended path and replace it with the new leaf name PRInt32 offset = mAppendedPath.RFindChar(':'); if (offset || ((!offset) && (1 < mAppendedPath.Length()))) { mAppendedPath.Truncate(offset + 1); } mAppendedPath.Append(leafName); } else { // We don't have an appended path so directly modify the FSSpec myPLstrcpy(mSpec.name, leafName); } return NS_OK; } NS_IMETHODIMP nsLocalFile::SetLeafName(const nsAString &aLeafName) { nsresult rv; nsCAutoString fsStr; if (NS_SUCCEEDED(rv = NS_CopyUnicodeToNative(aLeafName, fsStr))) rv = SetNativeLeafName(fsStr); return rv; } NS_IMETHODIMP nsLocalFile::GetNativePath(nsACString &_retval) { _retval.Truncate(); nsCAutoString fsCharSetPathStr; #if TARGET_CARBON if (sHasHFSPlusAPIs) // should always be true under Carbon, but in case... { OSErr err; nsresult rv; nsAutoString ucPathString; if ((err = HFSPlusGetRawPath(mSpec, ucPathString)) != noErr) return MacErrorMapper(err); rv = NS_CopyUnicodeToNative(ucPathString, fsCharSetPathStr); if (NS_FAILED(rv)) return rv; } else #endif { // Now would be a good time to call the code that makes an FSSpec into a path short fullPathLen; Handle fullPathHandle; (void)::FSpGetFullPath(&mSpec, &fullPathLen, &fullPathHandle); if (!fullPathHandle) return NS_ERROR_OUT_OF_MEMORY; ::HLock(fullPathHandle); fsCharSetPathStr.Assign(*fullPathHandle, fullPathLen); ::DisposeHandle(fullPathHandle); } // We need to make sure that even if we have a path to a // directory we don't return the trailing colon. It breaks // the component manager. (Bugzilla bug #26102) if (fsCharSetPathStr.Last() == ':') fsCharSetPathStr.Truncate(fsCharSetPathStr.Length() - 1); // Now, tack on mAppendedPath. It never ends in a colon. if (mAppendedPath.Length()) { if (mAppendedPath.First() != ':') fsCharSetPathStr.Append(":"); fsCharSetPathStr.Append(mAppendedPath); } _retval = fsCharSetPathStr; return NS_OK; } NS_IMETHODIMP nsLocalFile::GetPath(nsAString &_retval) { nsresult rv = NS_OK; #if TARGET_CARBON if (sHasHFSPlusAPIs) // should always be true under Carbon, but in case... { OSErr err; nsAutoString ucPathString; if ((err = HFSPlusGetRawPath(mSpec, ucPathString)) != noErr) return MacErrorMapper(err); // We need to make sure that even if we have a path to a // directory we don't return the trailing colon. It breaks // the component manager. (Bugzilla bug #26102) if (ucPathString.Last() == PRUnichar(':')) ucPathString.Truncate(ucPathString.Length() - 1); // Now, tack on mAppendedPath. It never ends in a colon. if (mAppendedPath.Length()) { nsAutoString ucAppendage; if (mAppendedPath.First() != ':') ucPathString.Append(PRUnichar(':')); rv = NS_CopyNativeToUnicode(mAppendedPath, ucAppendage); if (NS_FAILED(rv)) return rv; ucPathString.Append(ucAppendage); } _retval = ucPathString; } else #endif { nsCAutoString fsStr; if (NS_SUCCEEDED(rv = GetNativePath(fsStr))) { rv = NS_CopyNativeToUnicode(fsStr, _retval); } } return rv; } nsresult nsLocalFile::MoveCopy( nsIFile* newParentDir, const nsACString &newName, PRBool isCopy, PRBool followLinks ) { OSErr macErr; FSSpec srcSpec; Str255 newPascalName; nsresult rv; StFollowLinksState srcFollowState(this, followLinks); rv = GetFSSpec(&srcSpec); if ( NS_FAILED( rv ) ) return rv; // If newParentDir == nsnull, it's a simple rename if ( !newParentDir ) { myPLstrncpy( newPascalName, PromiseFlatCString(newName).get(), 255 ); macErr = ::FSpRename( &srcSpec, newPascalName ); return MacErrorMapper( macErr ); } nsCOMPtr destDir(do_QueryInterface( newParentDir )); StFollowLinksState destFollowState(destDir, followLinks); FSSpec destSpec; rv = destDir->GetFSSpec(&destSpec); if ( NS_FAILED( rv ) ) return rv; long dirID; Boolean isDirectory; macErr = ::FSpGetDirectoryID(&destSpec, &dirID, &isDirectory); if ( macErr || !isDirectory ) return NS_ERROR_FILE_DESTINATION_NOT_DIR; if ( !newName.IsEmpty() ) myPLstrncpy( newPascalName, PromiseFlatCString(newName).get(), 255); else memcpy(newPascalName, srcSpec.name, srcSpec.name[0] + 1); if ( isCopy ) { macErr = ::FSpGetDirectoryID(&srcSpec, &dirID, &isDirectory); if (macErr == noErr) { const PRInt32 kCopyBufferSize = (1024 * 512); // allocate our own buffer to speed file copies. Bug #103202 OSErr tempErr; Handle copyBufferHand = ::TempNewHandle(kCopyBufferSize, &tempErr); void* copyBuffer = nsnull; PRInt32 copyBufferSize = 0; // it's OK if the allocated failed; FSpFileCopy will just fall back on its own internal 16k buffer if (copyBufferHand) { ::HLock(copyBufferHand); copyBuffer = *copyBufferHand; copyBufferSize = kCopyBufferSize; } if ( isDirectory ) macErr = MacFSpDirectoryCopyRename( &srcSpec, &destSpec, newPascalName, copyBuffer, copyBufferSize, true, NULL ); else macErr = ::FSpFileCopy( &srcSpec, &destSpec, newPascalName, copyBuffer, copyBufferSize, true ); if (copyBufferHand) ::DisposeHandle(copyBufferHand); } } else { macErr= ::FSpMoveRenameCompat(&srcSpec, &destSpec, newPascalName); if ( macErr == diffVolErr) { // On a different Volume so go for Copy and then delete rv = CopyToNative( newParentDir, newName ); if ( NS_FAILED ( rv ) ) return rv; return Remove( PR_TRUE ); } } return MacErrorMapper( macErr ); } NS_IMETHODIMP nsLocalFile::CopyToNative(nsIFile *newParentDir, const nsACString &newName) { return MoveCopy( newParentDir, newName, PR_TRUE, PR_FALSE ); } NS_IMETHODIMP nsLocalFile::CopyTo(nsIFile *newParentDir, const nsAString &newName) { if (newName.IsEmpty()) return CopyToNative(newParentDir, nsCString()); nsresult rv; nsCAutoString fsStr; if (NS_SUCCEEDED(rv = NS_CopyUnicodeToNative(newName, fsStr))) rv = CopyToNative(newParentDir, fsStr); return rv; } NS_IMETHODIMP nsLocalFile::CopyToFollowingLinksNative(nsIFile *newParentDir, const nsACString &newName) { return MoveCopy( newParentDir, newName, PR_TRUE, PR_TRUE ); } NS_IMETHODIMP nsLocalFile::CopyToFollowingLinks(nsIFile *newParentDir, const nsAString &newName) { if (newName.IsEmpty()) return CopyToFollowingLinksNative(newParentDir, nsCString()); nsresult rv; nsCAutoString fsStr; if (NS_SUCCEEDED(rv = NS_CopyUnicodeToNative(newName, fsStr))) rv = CopyToFollowingLinksNative(newParentDir, fsStr); return rv; } NS_IMETHODIMP nsLocalFile::MoveToNative(nsIFile *newParentDir, const nsACString &newName) { return MoveCopy( newParentDir, newName, PR_FALSE, PR_FALSE ); } NS_IMETHODIMP nsLocalFile::MoveTo(nsIFile *newParentDir, const nsAString &newName) { if (newName.IsEmpty()) return MoveToNative(newParentDir, nsCString()); nsresult rv; nsCAutoString fsStr; if (NS_SUCCEEDED(rv = NS_CopyUnicodeToNative(newName, fsStr))) rv = MoveToNative(newParentDir, fsStr); return rv; } NS_IMETHODIMP nsLocalFile::Load(PRLibrary * *_retval) { PRBool isFile; nsresult rv = IsFile(&isFile); if (NS_FAILED(rv)) return rv; if (! isFile) return NS_ERROR_FILE_IS_DIRECTORY; NS_TIMELINE_START_TIMER("PR_LoadLibrary"); #if !TARGET_CARBON // This call to SystemTask is here to give the OS time to grow its // FCB (file control block) list, which it seems to be unable to // do unless we yield some time to the OS. See bugs 64978 & 70543 // for the whole story. ::SystemTask(); #endif // Use the new PR_LoadLibraryWithFlags which allows us to use a FSSpec PRLibSpec libSpec; libSpec.type = PR_LibSpec_MacIndexedFragment; libSpec.value.mac_indexed_fragment.fsspec = &mTargetSpec; libSpec.value.mac_indexed_fragment.index = 0; *_retval = PR_LoadLibraryWithFlags(libSpec, 0); NS_TIMELINE_STOP_TIMER("PR_LoadLibrary"); NS_TIMELINE_MARK_TIMER("PR_LoadLibrary"); if (*_retval) return NS_OK; return NS_ERROR_NULL_POINTER; } NS_IMETHODIMP nsLocalFile::Remove(PRBool recursive) { OSErr err; nsresult rv; FSSpec specToDelete; PRBool isDir; StFollowLinksState(this, PR_FALSE); rv = IsDirectory(&isDir); // Calls ResolveAndStat() if (NS_FAILED(rv)) return rv; rv = GetFSSpec(&specToDelete); if (NS_FAILED(rv)) return rv; if (isDir && recursive) err = ::DeleteDirectory( specToDelete.vRefNum, specToDelete.parID, specToDelete.name ); else err = ::HDelete( specToDelete.vRefNum, specToDelete.parID, specToDelete.name ); return MacErrorMapper( err ); } NS_IMETHODIMP nsLocalFile::GetLastModifiedTime(PRInt64 *aLastModifiedTime) { NS_ENSURE_ARG(aLastModifiedTime); *aLastModifiedTime = 0; nsresult rv = ResolveAndStat(); if ( NS_FAILED( rv ) ) return rv; rv = UpdateCachedCatInfo(PR_TRUE); if ( NS_FAILED( rv ) ) return rv; // The mod date is in the same spot for files and dirs. return ConvertMacTimeToMilliseconds( aLastModifiedTime, mCachedCatInfo.hFileInfo.ioFlMdDat ); } NS_IMETHODIMP nsLocalFile::SetLastModifiedTime(PRInt64 aLastModifiedTime) { nsresult rv = ResolveAndStat(); if ( NS_FAILED(rv) ) return rv; PRUint32 macTime = 0; OSErr err = noErr; ConvertMillisecondsToMacTime(aLastModifiedTime, &macTime); if (NS_SUCCEEDED(rv = UpdateCachedCatInfo(PR_TRUE))) { if (mCachedCatInfo.hFileInfo.ioFlAttrib & ioDirMask) { mCachedCatInfo.dirInfo.ioDrMdDat = macTime; mCachedCatInfo.dirInfo.ioDrParID = mFollowLinks ? mTargetSpec.parID : mSpec.parID; } else { mCachedCatInfo.hFileInfo.ioFlMdDat = macTime; mCachedCatInfo.hFileInfo.ioDirID = mFollowLinks ? mTargetSpec.parID : mSpec.parID; } err = ::PBSetCatInfoSync(&mCachedCatInfo); if (err != noErr) return MacErrorMapper(err); } return rv; } NS_IMETHODIMP nsLocalFile::GetLastModifiedTimeOfLink(PRInt64 *aLastModifiedTime) { NS_ENSURE_ARG(aLastModifiedTime); nsresult rv; PRBool isLink; rv = IsSymlink(&isLink); if (NS_FAILED(rv)) return rv; if (!isLink) return NS_ERROR_FAILURE; StFollowLinksState followState(this, PR_FALSE); return GetLastModifiedTime(aLastModifiedTime); } NS_IMETHODIMP nsLocalFile::SetLastModifiedTimeOfLink(PRInt64 aLastModifiedTime) { nsresult rv; PRBool isLink; rv = IsSymlink(&isLink); if (NS_FAILED(rv)) return rv; if (!isLink) return NS_ERROR_FAILURE; StFollowLinksState followState(this, PR_FALSE); return SetLastModifiedTime(aLastModifiedTime); } NS_IMETHODIMP nsLocalFile::GetFileSize(PRInt64 *aFileSize) { NS_ENSURE_ARG(aFileSize); nsresult rv; *aFileSize = LL_Zero(); if (NS_SUCCEEDED(rv = ResolveAndStat()) && NS_SUCCEEDED(rv = UpdateCachedCatInfo(PR_TRUE))) { if (!(mCachedCatInfo.hFileInfo.ioFlAttrib & ioDirMask)) { long dataSize = mCachedCatInfo.hFileInfo.ioFlLgLen; long resSize = mCachedCatInfo.hFileInfo.ioFlRLgLen; // For now we've only got 32 bits of file size info PRInt64 dataInt64 = LL_Zero(); PRInt64 resInt64 = LL_Zero(); // WARNING!!!!!! // // For now we do NOT add the data and resource fork sizes as there are several // assumptions in the code (notably in form submit) that only the data fork is // used. // LL_I2L(resInt64, resSize); LL_I2L(dataInt64, dataSize); LL_ADD((*aFileSize), dataInt64, resInt64); } // leave size at zero for dirs } return rv; } NS_IMETHODIMP nsLocalFile::SetFileSize(PRInt64 aFileSize) { nsresult rv = ResolveAndStat(); if (NS_FAILED(rv)) return rv; short refNum; OSErr err; PRInt32 aNewLength; LL_L2I(aNewLength, aFileSize); // Need to open the file to set the size if (::FSpOpenDF(&mTargetSpec, fsWrPerm, &refNum) != noErr) return NS_ERROR_FILE_ACCESS_DENIED; err = ::SetEOF(refNum, aNewLength); // Close the file unless we got an error that it was already closed if (err != fnOpnErr) (void)::FSClose(refNum); if (err != noErr) return MacErrorMapper(err); return MacErrorMapper(err); } NS_IMETHODIMP nsLocalFile::GetFileSizeOfLink(PRInt64 *aFileSize) { NS_ENSURE_ARG(aFileSize); StFollowLinksState followState(this, PR_FALSE); return GetFileSize(aFileSize); } NS_IMETHODIMP nsLocalFile::GetDiskSpaceAvailable(PRInt64 *aDiskSpaceAvailable) { NS_ENSURE_ARG(aDiskSpaceAvailable); PRInt64 space64Bits; LL_I2L(space64Bits , LONG_MAX); nsresult rv = ResolveAndStat(); if (NS_FAILED(rv)) return rv; XVolumeParam pb; pb.ioCompletion = nsnull; pb.ioVolIndex = 0; pb.ioNamePtr = nsnull; pb.ioVRefNum = mFollowLinks ? mTargetSpec.vRefNum : mSpec.vRefNum; // we should check if this call is available OSErr err = ::PBXGetVolInfoSync(&pb); if (err == noErr) { const UnsignedWide& freeBytes = UInt64ToUnsignedWide(pb.ioVFreeBytes); #ifdef HAVE_LONG_LONG space64Bits = UnsignedWideToUInt64(freeBytes); #else space64Bits.lo = freeBytes.lo; space64Bits.hi = freeBytes.hi; #endif } *aDiskSpaceAvailable = space64Bits; return NS_OK; } NS_IMETHODIMP nsLocalFile::GetParent(nsIFile * *aParent) { NS_ENSURE_ARG_POINTER(aParent); *aParent = nsnull; nsresult rv = NS_OK; PRInt32 offset; nsCOMPtr localFile; PRInt32 appendedLen = mAppendedPath.Length(); OSErr err; if (!appendedLen || (appendedLen == 1 && mAppendedPath.CharAt(0) == ':')) { rv = ResolveAndStat(); //if the file does not exist, does not mean that the parent does not. if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) return rv; CInfoPBRec pBlock = {0}; FSSpec parentFolderSpec; parentFolderSpec.name[0] = 0; pBlock.dirInfo.ioVRefNum = mSpec.vRefNum; pBlock.dirInfo.ioDrDirID = mSpec.parID; pBlock.dirInfo.ioNamePtr = (StringPtr)parentFolderSpec.name; pBlock.dirInfo.ioFDirIndex = -1; //get info on parID err = PBGetCatInfoSync(&pBlock); if (err != noErr) return MacErrorMapper(err); parentFolderSpec.vRefNum = mSpec.vRefNum; parentFolderSpec.parID = pBlock.dirInfo.ioDrParID; localFile = new nsLocalFile; if (!localFile) return NS_ERROR_OUT_OF_MEMORY; rv = localFile->InitWithFSSpec(&parentFolderSpec); if (NS_FAILED(rv)) return rv; } else { // trim off the last component of the appended path // construct a new file from our spec + trimmed path nsCAutoString parentAppendage(mAppendedPath); if (parentAppendage.Last() == ':') parentAppendage.Truncate(appendedLen - 1); if ((offset = parentAppendage.RFindChar(':')) != -1) parentAppendage.Truncate(offset); else parentAppendage.Truncate(0); localFile = new nsLocalFile(mSpec, parentAppendage); if (!localFile) return NS_ERROR_OUT_OF_MEMORY; } *aParent = localFile; NS_ADDREF(*aParent); return rv; } NS_IMETHODIMP nsLocalFile::Exists(PRBool *_retval) { NS_ENSURE_ARG(_retval); *_retval = PR_FALSE; nsresult rv = ResolveAndStat(); if (NS_SUCCEEDED(rv)) { if (NS_SUCCEEDED(UpdateCachedCatInfo(PR_TRUE))) *_retval = PR_TRUE; } return NS_OK; } NS_IMETHODIMP nsLocalFile::IsPackage(PRBool *outIsPackage) { NS_ENSURE_ARG(outIsPackage); *outIsPackage = PR_FALSE; // Note: IsDirectory() calls ResolveAndStat() & UpdateCachedCatInfo PRBool isDir; nsresult rv = IsDirectory(&isDir); if (NS_FAILED(rv)) return rv; *outIsPackage = ((mCachedCatInfo.dirInfo.ioFlAttrib & kioFlAttribDirMask) && (mCachedCatInfo.dirInfo.ioDrUsrWds.frFlags & kHasBundle)); if ((!*outIsPackage) && isDir) { // Believe it or not, 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")) { *outIsPackage = PR_TRUE; } } } } return NS_OK; } NS_IMETHODIMP nsLocalFile::IsWritable(PRBool *outIsWritable) { NS_ENSURE_ARG(outIsWritable); *outIsWritable = PR_TRUE; nsresult rv = ResolveAndStat(); if (NS_FAILED(rv)) return rv; rv = UpdateCachedCatInfo(PR_TRUE); if (NS_FAILED(rv)) return rv; *outIsWritable = !(mCachedCatInfo.hFileInfo.ioFlAttrib & kioFlAttribLockedMask); return NS_OK; } NS_IMETHODIMP nsLocalFile::IsReadable(PRBool *_retval) { NS_ENSURE_ARG(_retval); // is it ever not readable on Mac? *_retval = PR_TRUE; return NS_OK; } NS_IMETHODIMP nsLocalFile::IsExecutable(PRBool *outIsExecutable) { NS_ENSURE_ARG(outIsExecutable); *outIsExecutable = PR_FALSE; // Assume failure nsresult rv = ResolveAndStat(); if (NS_FAILED(rv)) return rv; #if TARGET_CARBON // If we're running under OS X ask LaunchServices if we're executable if (sRunningOSX) { if ( (UInt32)LSCopyItemInfoForRef != (UInt32)kUnresolvedCFragSymbolAddress ) { FSRef theRef; LSRequestedInfo theInfoRequest = kLSRequestAllInfo; LSItemInfoRecord theInfo; if (::FSpMakeFSRef(&mTargetSpec, &theRef) == noErr) { if (::LSCopyItemInfoForRef(&theRef, theInfoRequest, &theInfo) == noErr) { if ((theInfo.flags & kLSItemInfoIsApplication) != 0) *outIsExecutable = PR_TRUE; } } } } else #endif { OSType fileType; rv = GetFileType(&fileType); if (NS_FAILED(rv)) return rv; *outIsExecutable = (fileType == 'APPL' || fileType == 'appe' || fileType == 'FNDR'); } return NS_OK; } NS_IMETHODIMP nsLocalFile::IsDirectory(PRBool *outIsDir) { NS_ENSURE_ARG(outIsDir); *outIsDir = PR_FALSE; nsresult rv = ResolveAndStat(); if (NS_FAILED(rv)) return rv; rv = UpdateCachedCatInfo(PR_FALSE); if (NS_FAILED(rv)) return rv; *outIsDir = (mCachedCatInfo.hFileInfo.ioFlAttrib & ioDirMask) != 0; return NS_OK; } NS_IMETHODIMP nsLocalFile::IsFile(PRBool *outIsFile) { NS_ENSURE_ARG(outIsFile); *outIsFile = PR_FALSE; nsresult rv = ResolveAndStat(); if (NS_FAILED(rv)) return rv; rv = UpdateCachedCatInfo(PR_FALSE); if (NS_FAILED(rv)) return rv; *outIsFile = (mCachedCatInfo.hFileInfo.ioFlAttrib & ioDirMask) == 0; return NS_OK; } NS_IMETHODIMP nsLocalFile::IsHidden(PRBool *_retval) { NS_ENSURE_ARG(_retval); *_retval = PR_FALSE; nsresult rv = ResolveAndStat(); if (NS_FAILED(rv)) return rv; rv = UpdateCachedCatInfo(PR_FALSE); if (NS_FAILED(rv)) return rv; *_retval = (mCachedCatInfo.hFileInfo.ioFlFndrInfo.fdFlags & kIsInvisible) != 0; if (sRunningOSX) { // on Mac OS X, also follow Unix "convention" where files // beginning with a period are considered to be hidden nsCAutoString name; if (NS_SUCCEEDED(rv = GetNativeLeafName(name))) { if (name.First() == '.') { *_retval = PR_TRUE; } } } return NS_OK; } NS_IMETHODIMP nsLocalFile::IsSymlink(PRBool *_retval) { NS_ENSURE_ARG(_retval); *_retval = PR_FALSE; nsresult rv = ResolveAndStat(); if (NS_FAILED(rv)) return rv; Boolean isAlias, isFolder; if (::IsAliasFile(&mSpec, &isAlias, &isFolder) == noErr) *_retval = isAlias; return NS_OK; } NS_IMETHODIMP nsLocalFile::Equals(nsIFile *inFile, PRBool *_retval) { NS_ENSURE_ARG(inFile); NS_ENSURE_ARG(_retval); *_retval = PR_FALSE; // Building paths is expensive. If we can get the FSSpecs of // both (they or their parents exist) just compare the specs. nsCOMPtr inMacFile(do_QueryInterface(inFile)); FSSpec fileSpec, inFileSpec; if (NS_SUCCEEDED(GetFSSpec(&fileSpec)) && inMacFile && NS_SUCCEEDED(inMacFile->GetFSSpec(&inFileSpec))) *_retval = IsEqualFSSpec(fileSpec, inFileSpec); else { nsCAutoString filePath; GetNativePath(filePath); nsXPIDLCString inFilePath; inFile->GetNativePath(inFilePath); if (nsCRT::strcasecmp(inFilePath.get(), filePath.get()) == 0) *_retval = PR_TRUE; } return NS_OK; } NS_IMETHODIMP nsLocalFile::Contains(nsIFile *inFile, PRBool recur, PRBool *outContains) { /* Note here that we make no attempt to deal with the problem of folder aliases. Doing a 'Contains' test and dealing with folder aliases is Hard. Think about it. */ *outContains = PR_FALSE; PRBool isDir; nsresult rv = IsDirectory(&isDir); // need to cache this if (NS_FAILED(rv)) return rv; if (!isDir) return NS_OK; // must be a dir to contain someone nsCOMPtr macFile(do_QueryInterface(inFile)); if (!macFile) return NS_OK; // trying to compare non-local with local file FSSpec mySpec = mSpec; FSSpec compareSpec; // NOTE: we're not resolving inFile if it was an alias StFollowLinksState followState(macFile, PR_FALSE); rv = macFile->GetFSSpec(&compareSpec); if (NS_FAILED(rv)) return rv; // if they are on different volumes, bail if (mSpec.vRefNum != compareSpec.vRefNum) return NS_OK; // if recur == true, test every parent, otherwise just test the first one // (yes, recur does not get set in this loop) OSErr err = noErr; do { FSSpec parentFolderSpec; err = GetParentFolderSpec(compareSpec, parentFolderSpec); if (err != noErr) break; // we reached the top if (IsEqualFSSpec(parentFolderSpec, mySpec)) { *outContains = PR_TRUE; break; } compareSpec = parentFolderSpec; } while (recur); return NS_OK; } NS_IMETHODIMP nsLocalFile::GetNativeTarget(nsACString &_retval) { _retval.Truncate(); PRBool symLink; nsresult rv = IsSymlink(&symLink); if (NS_FAILED(rv)) return rv; if (!symLink) return NS_ERROR_FILE_INVALID_PATH; StFollowLinksState followState(this, PR_TRUE); return GetNativePath(_retval); } NS_IMETHODIMP nsLocalFile::GetTarget(nsAString &_retval) { nsresult rv; nsCAutoString fsStr; if (NS_SUCCEEDED(rv = GetNativeTarget(fsStr))) { rv = NS_CopyNativeToUnicode(fsStr, _retval); } return rv; } NS_IMETHODIMP nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator * *entries) { nsresult rv; *entries = nsnull; PRBool isDir; rv = IsDirectory(&isDir); if (NS_FAILED(rv)) return rv; if (!isDir) return NS_ERROR_FILE_NOT_DIRECTORY; nsDirEnumerator* dirEnum = new nsDirEnumerator(); if (dirEnum == nsnull) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(dirEnum); rv = dirEnum->Init(this); if (NS_FAILED(rv)) { NS_RELEASE(dirEnum); return rv; } *entries = dirEnum; return NS_OK; } NS_IMETHODIMP nsLocalFile::GetPersistentDescriptor(nsACString &aPersistentDescriptor) { aPersistentDescriptor.Truncate(); nsresult rv = ResolveAndStat(); if ( NS_FAILED( rv ) ) return rv; AliasHandle aliasH; OSErr err = ::NewAlias(nil, &mTargetSpec, &aliasH); if (err != noErr) return MacErrorMapper(err); PRUint32 bytes = ::GetHandleSize((Handle) aliasH); HLock((Handle) aliasH); char* buf = PL_Base64Encode((const char*)*aliasH, bytes, nsnull); // Passing nsnull for dest makes NULL-term string ::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); // Cast to an alias record and resolve. AliasHandle aliasH = nsnull; if (::PtrToHand(decodedData, &(Handle)aliasH, (dataSize * 3) / 4) != noErr) rv = NS_ERROR_OUT_OF_MEMORY; PR_Free(decodedData); NS_ENSURE_SUCCESS(rv, rv); Boolean changed; FSSpec resolvedSpec; OSErr err; err = ::ResolveAlias(nsnull, aliasH, &resolvedSpec, &changed); if (err == fnfErr) // resolvedSpec is valid in this case err = noErr; rv = MacErrorMapper(err); DisposeHandle((Handle) aliasH); NS_ENSURE_SUCCESS(rv, rv); return InitWithFSSpec(&resolvedSpec); } #pragma mark - // a stack-based, exception safe class for an AEDesc #pragma mark class StAEDesc: public AEDesc { public: StAEDesc() { descriptorType = typeNull; dataHandle = nil; } ~StAEDesc() { ::AEDisposeDesc(this); } void Clear() { ::AEDisposeDesc(this); descriptorType = typeNull; dataHandle = nil; } private: // disallow copies and assigns StAEDesc(const StAEDesc& rhs); // copy constructor StAEDesc& operator= (const StAEDesc&rhs); // throws OSErrs }; #pragma mark - #pragma mark [Utility methods] nsresult nsLocalFile::UpdateCachedCatInfo(PRBool forceUpdate) { if (!mCatInfoDirty && !forceUpdate) return NS_OK; FSSpec spectoUse = mFollowLinks ? mTargetSpec : mSpec; mCachedCatInfo.hFileInfo.ioCompletion = nsnull; mCachedCatInfo.hFileInfo.ioFDirIndex = 0; // use dirID and name mCachedCatInfo.hFileInfo.ioVRefNum = spectoUse.vRefNum; mCachedCatInfo.hFileInfo.ioDirID = spectoUse.parID; mCachedCatInfo.hFileInfo.ioNamePtr = spectoUse.name; OSErr err = ::PBGetCatInfoSync(&mCachedCatInfo); if (err == noErr) { mCatInfoDirty = PR_FALSE; return NS_OK; } return MacErrorMapper(err); } nsresult nsLocalFile::FindRunningAppBySignature (OSType aAppSig, FSSpec& outSpec, ProcessSerialNumber& outPsn) { ProcessInfoRec info; FSSpec tempFSSpec; OSErr err = noErr; outPsn.highLongOfPSN = 0; outPsn.lowLongOfPSN = kNoProcess; while (PR_TRUE) { err = ::GetNextProcess(&outPsn); if (err == procNotFound) break; if (err != noErr) return NS_ERROR_FAILURE; info.processInfoLength = sizeof(ProcessInfoRec); info.processName = nil; info.processAppSpec = &tempFSSpec; err = ::GetProcessInformation(&outPsn, &info); if (err != noErr) return NS_ERROR_FAILURE; if (info.processSignature == aAppSig) { outSpec = tempFSSpec; return NS_OK; } } return NS_ERROR_FILE_NOT_FOUND; // really process not found } nsresult nsLocalFile::FindRunningAppByFSSpec(const FSSpec& appSpec, ProcessSerialNumber& outPsn) { ProcessInfoRec info; FSSpec tempFSSpec; OSErr err = noErr; outPsn.highLongOfPSN = 0; outPsn.lowLongOfPSN = kNoProcess; while (PR_TRUE) { err = ::GetNextProcess(&outPsn); if (err == procNotFound) break; if (err != noErr) return NS_ERROR_FAILURE; info.processInfoLength = sizeof(ProcessInfoRec); info.processName = nil; info.processAppSpec = &tempFSSpec; err = ::GetProcessInformation(&outPsn, &info); if (err != noErr) return NS_ERROR_FAILURE; if (IsEqualFSSpec(appSpec, *info.processAppSpec)) { return NS_OK; } } return NS_ERROR_FILE_NOT_FOUND; // really process not found } nsresult nsLocalFile::FindAppOnLocalVolumes(OSType sig, FSSpec &outSpec) { OSErr err; // get the system volume long systemFolderDirID; short sysVRefNum; err = FindFolder(kOnSystemDisk, kSystemFolderType, false, &sysVRefNum, &systemFolderDirID); if (err != noErr) return NS_ERROR_FAILURE; short vRefNum = sysVRefNum; short index = 0; while (true) { if (index == 0 || vRefNum != sysVRefNum) { // should we avoid AppleShare volumes? Boolean hasDesktopDB; err = VolHasDesktopDB(vRefNum, &hasDesktopDB); if (err != noErr) return err; if (hasDesktopDB) { err = FindAppOnVolume(sig, vRefNum, &outSpec); if (err != afpItemNotFound) return err; } } index++; err = GetIndVolume(index, &vRefNum); if (err == nsvErr) return fnfErr; if (err != noErr) return err; } return NS_OK; } #define aeSelectionKeyword 'fsel' #define kAEOpenSelection 'sope' #define kAERevealSelection 'srev' #define kFinderType 'FNDR' NS_IMETHODIMP nsLocalFile::Launch() { AppleEvent aeEvent = {0, nil}; AppleEvent aeReply = {0, nil}; StAEDesc aeDirDesc, listElem, myAddressDesc, fileList; FSSpec dirSpec, appSpec; AliasHandle DirAlias, FileAlias; OSErr errorResult = noErr; ProcessSerialNumber process; // for launching a file, we'll use mTargetSpec (which is both a resolved spec and a resolved alias) ResolveAndStat(); #if TARGET_CARBON if (sRunningOSX) { // We're running under Mac OS X, LaunchServices here we come // First we make sure the LaunchServices routine we want is implemented if ( (UInt32)LSOpenFSRef != (UInt32)kUnresolvedCFragSymbolAddress ) { FSRef theRef; if (::FSpMakeFSRef(&mTargetSpec, &theRef) == noErr) { (void)::LSOpenFSRef(&theRef, NULL); } } } else #endif { // We're running under Mac OS 8.x/9.x, use the Finder Luke nsresult rv = FindRunningAppBySignature ('MACS', appSpec, process); if (NS_SUCCEEDED(rv)) { errorResult = AECreateDesc(typeProcessSerialNumber, (Ptr)&process, sizeof(process), &myAddressDesc); if (errorResult == noErr) { /* Create the FinderEvent */ errorResult = AECreateAppleEvent(kFinderType, kAEOpenSelection, &myAddressDesc, kAutoGenerateReturnID, kAnyTransactionID, &aeEvent); if (errorResult == noErr) { errorResult = FSMakeFSSpec(mTargetSpec.vRefNum, mTargetSpec.parID, nil, &dirSpec); NewAlias(nil, &dirSpec, &DirAlias); /* Create alias for file */ NewAlias(nil, &mTargetSpec, &FileAlias); /* Create the file list */ errorResult = AECreateList(nil, 0, false, &fileList); /* create the folder descriptor */ HLock((Handle)DirAlias); errorResult = AECreateDesc(typeAlias, (Ptr)*DirAlias, GetHandleSize((Handle)DirAlias), &aeDirDesc); HUnlock((Handle)DirAlias); if (errorResult == noErr) { errorResult = AEPutParamDesc(&aeEvent, keyDirectObject, &aeDirDesc); if ( errorResult == noErr) { /* create the file descriptor and add to aliasList */ HLock((Handle)FileAlias); errorResult = AECreateDesc(typeAlias, (Ptr)*FileAlias, GetHandleSize((Handle)FileAlias), &listElem); HLock((Handle)FileAlias); if (errorResult == noErr) { errorResult = AEPutDesc(&fileList, 0, &listElem); if (errorResult == noErr) { /* Add the file alias list to the event */ errorResult = AEPutParamDesc(&aeEvent, aeSelectionKeyword, &fileList); if (errorResult == noErr) AESend(&aeEvent, &aeReply, kAEWaitReply + kAENeverInteract + kAECanSwitchLayer, kAEHighPriority, kAEDefaultTimeout, nil, nil); } } } } } } } } return NS_OK; } NS_IMETHODIMP nsLocalFile::Reveal() { FSSpec specToReveal; AppleEvent aeEvent = {0, nil}; AppleEvent aeReply = {0, nil}; StAEDesc aeDirDesc, listElem, myAddressDesc, fileList; OSErr errorResult = noErr; ProcessSerialNumber process; FSSpec appSpec; nsresult rv = ResolveAndStat(); if (NS_FAILED(rv)) return rv; rv = GetFSSpec(&specToReveal); // Pay attention to followLinks if (NS_FAILED(rv)) return rv; rv = FindRunningAppBySignature ('MACS', appSpec, process); if (NS_SUCCEEDED(rv)) { errorResult = AECreateDesc(typeProcessSerialNumber, (Ptr)&process, sizeof(process), &myAddressDesc); if (errorResult == noErr) { /* Create the FinderEvent */ #if TARGET_CARBON // The Finder under OS X uses a different event to reveal if (sRunningOSX) errorResult = AECreateAppleEvent(kAEMiscStandards, kAEMakeObjectsVisible, &myAddressDesc, kAutoGenerateReturnID, kAnyTransactionID, &aeEvent); else #endif errorResult = AECreateAppleEvent(kFinderType, kAERevealSelection, &myAddressDesc, kAutoGenerateReturnID, kAnyTransactionID, &aeEvent); if (errorResult == noErr) { /* Create the file list */ errorResult = AECreateList(nil, 0, false, &fileList); if (errorResult == noErr) { errorResult = AEPutPtr(&fileList, 0, typeFSS, &specToReveal, sizeof(FSSpec)); if (errorResult == noErr) { #if TARGET_CARBON // When we're sending the event under OS X the FSSpec must be a keyDirectObject if (sRunningOSX) errorResult = AEPutParamDesc(&aeEvent, keyDirectObject, &fileList); else #endif errorResult = AEPutParamDesc(&aeEvent,keySelection, &fileList); if (errorResult == noErr) { errorResult = AESend(&aeEvent, &aeReply, kAENoReply, kAENormalPriority, kAEDefaultTimeout, nil, nil); if (errorResult == noErr) SetFrontProcess(&process); } } } } } } return NS_OK; } nsresult nsLocalFile::MyLaunchAppWithDoc(const FSSpec& appSpec, const FSSpec* aDocToLoad, PRBool aLaunchInBackground) { ProcessSerialNumber thePSN = {0}; StAEDesc target; StAEDesc docDesc; StAEDesc launchDesc; StAEDesc docList; AppleEvent theEvent = {0, nil}; AppleEvent theReply = {0, nil}; OSErr err = noErr; Boolean autoParamValue = false; Boolean running = false; nsresult rv = NS_OK; #if TARGET_CARBON if (sRunningOSX) { // Under Mac OS X we'll use LaunchServices // First we make sure the LaunchServices routine we want is implemented if ( (UInt32)LSOpenFromRefSpec != (UInt32)kUnresolvedCFragSymbolAddress ) { FSRef appRef; FSRef docRef; LSLaunchFlags theLaunchFlags = kLSLaunchDefaults; LSLaunchFSRefSpec thelaunchSpec; if (::FSpMakeFSRef(&appSpec, &appRef) != noErr) return NS_ERROR_FAILURE; if (aDocToLoad) if (::FSpMakeFSRef(aDocToLoad, &docRef) != noErr) return NS_ERROR_FAILURE; if (aLaunchInBackground) theLaunchFlags |= kLSLaunchDontSwitch; memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec)); thelaunchSpec.appRef = &appRef; if (aDocToLoad) { thelaunchSpec.numDocs = 1; thelaunchSpec.itemRefs = &docRef; } thelaunchSpec.launchFlags = theLaunchFlags; err = ::LSOpenFromRefSpec(&thelaunchSpec, NULL); NS_ASSERTION((err != noErr), "Error calling LSOpenFromRefSpec"); if (err != noErr) return NS_ERROR_FAILURE; } } else #endif { // The old fashioned way for Mac OS 8.x/9.x rv = FindRunningAppByFSSpec(appSpec, thePSN); running = NS_SUCCEEDED(rv); err = AECreateDesc(typeProcessSerialNumber, &thePSN, sizeof(thePSN), &target); if (err != noErr) return NS_ERROR_FAILURE; err = AECreateAppleEvent(kCoreEventClass, aDocToLoad ? kAEOpenDocuments : kAEOpenApplication, &target, kAutoGenerateReturnID, kAnyTransactionID, &theEvent); if (err != noErr) return NS_ERROR_FAILURE; if (aDocToLoad) { err = AECreateList(nil, 0, false, &docList); if (err != noErr) return NS_ERROR_FAILURE; err = AECreateDesc(typeFSS, aDocToLoad, sizeof(FSSpec), &docDesc); if (err != noErr) return NS_ERROR_FAILURE; err = AEPutDesc(&docList, 0, &docDesc); if (err != noErr) return NS_ERROR_FAILURE; err = AEPutParamDesc(&theEvent, keyDirectObject, &docList); if (err != noErr) return NS_ERROR_FAILURE; } if (running) { err = AESend(&theEvent, &theReply, kAENoReply, kAENormalPriority, kNoTimeOut, nil, nil); if (err != noErr) return NS_ERROR_FAILURE; if (!aLaunchInBackground) { err = ::SetFrontProcess(&thePSN); if (err != noErr) return NS_ERROR_FAILURE; } } else { LaunchParamBlockRec launchThis = {0}; PRUint16 launchControlFlags = (launchContinue | launchNoFileFlags); if (aLaunchInBackground) launchControlFlags |= launchDontSwitch; err = AECoerceDesc(&theEvent, typeAppParameters, &launchDesc); if (err != noErr) return NS_ERROR_FAILURE; launchThis.launchAppSpec = (FSSpecPtr)&appSpec; #if TARGET_CARBON && ACCESSOR_CALLS_ARE_FUNCTIONS ::AEGetDescData(&launchDesc, &launchThis.launchAppParameters, sizeof(launchThis.launchAppParameters)); #else // no need to lock this handle. launchThis.launchAppParameters = (AppParametersPtr) *(launchDesc.dataHandle); #endif launchThis.launchBlockID = extendedBlock; launchThis.launchEPBLength = extendedBlockLen; launchThis.launchFileFlags = 0; launchThis.launchControlFlags = launchControlFlags; err = ::LaunchApplication(&launchThis); if (err != noErr) return NS_ERROR_FAILURE; // let's be nice and wait until it's running const PRUint32 kMaxTimeToWait = 60; // wait 1 sec max PRUint32 endTicks = ::TickCount() + kMaxTimeToWait; PRBool foundApp = PR_FALSE; do { EventRecord theEvent; (void)WaitNextEvent(nullEvent, &theEvent, 1, NULL); ProcessSerialNumber psn; foundApp = NS_SUCCEEDED(FindRunningAppByFSSpec(appSpec, psn)); } while (!foundApp && (::TickCount() <= endTicks)); NS_ASSERTION(foundApp, "Failed to find app after launching it"); } if (theEvent.dataHandle != nil) AEDisposeDesc(&theEvent); if (theReply.dataHandle != nil) AEDisposeDesc(&theReply); } return NS_OK; } #pragma mark - #pragma mark [Methods that will not be implemented on Mac] NS_IMETHODIMP nsLocalFile::Normalize() { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsLocalFile::GetPermissions(PRUint32 *aPermissions) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsLocalFile::GetPermissionsOfLink(PRUint32 *aPermissionsOfLink) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsLocalFile::SetPermissions(PRUint32 aPermissions) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsLocalFile::SetPermissionsOfLink(PRUint32 aPermissions) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsLocalFile::IsSpecial(PRBool *_retval) { return NS_ERROR_NOT_IMPLEMENTED; } #pragma mark - #pragma mark [nsILocalFileMac] // Implementation of Mac specific finctions from nsILocalFileMac NS_IMETHODIMP nsLocalFile::InitWithCFURL(CFURLRef aCFURL) { nsresult rv = NS_ERROR_FAILURE; #if TARGET_CARBON NS_ENSURE_ARG(aCFURL); // CFURLGetFSRef can only succeed if the entire path exists. FSRef fsRef; if (::CFURLGetFSRef(aCFURL, &fsRef) == PR_TRUE) rv = InitWithFSRef(&fsRef); else { CFURLRef parentURL = ::CFURLCreateCopyDeletingLastPathComponent(NULL, aCFURL); if (!parentURL) return NS_ERROR_FAILURE; // Get the FSRef from the parent and the FSSpec from that FSRef parentFSRef; FSSpec parentFSSpec; if ((::CFURLGetFSRef(parentURL, &parentFSRef) == PR_TRUE) && (::FSGetCatalogInfo(&parentFSRef, kFSCatInfoNone, nsnull, nsnull, &parentFSSpec, nsnull) == noErr)) { // Get the leaf name of the file and turn it into a string HFS can use. CFStringRef fileNameRef = ::CFURLCopyLastPathComponent(aCFURL); if (fileNameRef) { TextEncoding theEncoding; if (::UpgradeScriptInfoToTextEncoding(smSystemScript, kTextLanguageDontCare, kTextRegionDontCare, NULL, &theEncoding) != noErr) theEncoding = kTextEncodingMacRoman; char origName[256]; char truncBuf[32]; if (::CFStringGetCString(fileNameRef, origName, sizeof(origName), theEncoding)) { MakeDirty(); mSpec = parentFSSpec; mAppendedPath = NS_TruncNodeName(origName, truncBuf); rv = NS_OK; } ::CFRelease(fileNameRef); } } ::CFRelease(parentURL); } #endif return rv; } NS_IMETHODIMP nsLocalFile::InitWithFSRef(const FSRef * aFSRef) { nsresult rv = NS_ERROR_FAILURE; #if TARGET_CARBON NS_ENSURE_ARG(aFSRef); FSSpec fsSpec; OSErr err = ::FSGetCatalogInfo(aFSRef, kFSCatInfoNone, nsnull, nsnull, &fsSpec, nsnull); if (err == noErr) rv = InitWithFSSpec(&fsSpec); else rv = MacErrorMapper(err); #endif return rv; } NS_IMETHODIMP nsLocalFile::InitWithFSSpec(const FSSpec *fileSpec) { MakeDirty(); mSpec = *fileSpec; mTargetSpec = *fileSpec; mAppendedPath = ""; return NS_OK; } NS_IMETHODIMP nsLocalFile::InitToAppWithCreatorCode(OSType aAppCreator) { FSSpec appSpec; ProcessSerialNumber psn; #if TARGET_CARBON if (sRunningOSX) { // If we're running under OS X use LaunchServices to determine the app // corresponding to the creator code if ( (UInt32)LSFindApplicationForInfo != (UInt32)kUnresolvedCFragSymbolAddress ) { FSRef theRef; if (::LSFindApplicationForInfo(aAppCreator, NULL, NULL, &theRef, NULL) == noErr) { FSCatalogInfoBitmap whichInfo = kFSCatInfoNone; if (::FSGetCatalogInfo(&theRef, whichInfo, NULL, NULL, &appSpec, NULL) == noErr) return InitWithFSSpec(&appSpec); } // If we get here we didn't find an app return NS_ERROR_FILE_NOT_FOUND; } } #endif // is the app running? nsresult rv = FindRunningAppBySignature(aAppCreator, appSpec, psn); if (rv == NS_ERROR_FILE_NOT_FOUND) { // we have to look on disk rv = FindAppOnLocalVolumes(aAppCreator, appSpec); if (NS_FAILED(rv)) return rv; } else if (NS_FAILED(rv)) return rv; // init with the spec here return InitWithFSSpec(&appSpec); } NS_IMETHODIMP nsLocalFile::GetFSRef(FSRef *_retval) { nsresult rv = NS_ERROR_FAILURE; #if TARGET_CARBON NS_ENSURE_ARG_POINTER(_retval); FSSpec fsSpec; rv = GetFSSpec(&fsSpec); if (NS_SUCCEEDED(rv)) rv = MacErrorMapper(::FSpMakeFSRef(&fsSpec, _retval)); #endif return rv; } NS_IMETHODIMP nsLocalFile::GetFSSpec(FSSpec *fileSpec) { NS_ENSURE_ARG(fileSpec); nsresult rv = ResolveAndStat(); if (rv == NS_ERROR_FILE_NOT_FOUND) rv = NS_OK; if (NS_SUCCEEDED(rv)) *fileSpec = mFollowLinks ? mTargetSpec : mSpec; return NS_OK; } NS_IMETHODIMP nsLocalFile::GetFileType(OSType *aFileType) { NS_ENSURE_ARG(aFileType); FSSpec fileSpec; (void)GetFSSpec(&fileSpec); FInfo info; OSErr err = ::FSpGetFInfo(&fileSpec, &info); if (err != noErr) { *aFileType = mType; return NS_ERROR_FILE_NOT_FOUND; } *aFileType = info.fdType; return NS_OK; } NS_IMETHODIMP nsLocalFile::SetFileType(OSType aFileType) { mType = aFileType; FSSpec fileSpec; (void)GetFSSpec(&fileSpec); FInfo info; OSErr err = ::FSpGetFInfo(&fileSpec, &info); if (err != noErr) return NS_ERROR_FILE_NOT_FOUND; info.fdType = aFileType; err = ::FSpSetFInfo(&fileSpec, &info); if (err != noErr) return NS_ERROR_FILE_ACCESS_DENIED; return NS_OK; } NS_IMETHODIMP nsLocalFile::GetFileCreator(OSType *aCreator) { NS_ENSURE_ARG(aCreator); FSSpec fileSpec; (void)GetFSSpec(&fileSpec); FInfo info; OSErr err = ::FSpGetFInfo(&fileSpec, &info); if (err != noErr) { *aCreator = mCreator; return NS_ERROR_FILE_NOT_FOUND; } *aCreator = info.fdCreator; return NS_OK; } NS_IMETHODIMP nsLocalFile::SetFileCreator(OSType aCreator) { if (aCreator == CURRENT_PROCESS_CREATOR) aCreator = sCurrentProcessSignature; mCreator = aCreator; FSSpec fileSpec; (void)GetFSSpec(&fileSpec); FInfo info; OSErr err = ::FSpGetFInfo(&fileSpec, &info); if (err != noErr) return NS_ERROR_FILE_NOT_FOUND; info.fdCreator = aCreator; err = ::FSpSetFInfo(&fileSpec, &info); if (err != noErr) return NS_ERROR_FILE_ACCESS_DENIED; return NS_OK; } NS_IMETHODIMP nsLocalFile::SetFileTypeAndCreatorFromExtension(const char *aExtension) { NS_ENSURE_ARG(aExtension); return SetOSTypeAndCreatorFromExtension(aExtension); } NS_IMETHODIMP nsLocalFile::SetFileTypeAndCreatorFromMIMEType(const char *aMIMEType) { NS_ENSURE_ARG(aMIMEType); nsresult rv; nsCOMPtr icService(do_GetService (NS_INTERNETCONFIGSERVICE_CONTRACTID, &rv)); if (NS_SUCCEEDED(rv)) { nsCOMPtr mimeInfo; PRUint32 fileType = 'TEXT'; PRUint32 fileCreator = nsILocalFileMac::CURRENT_PROCESS_CREATOR; rv = icService->FillInMIMEInfo(aMIMEType, nsnull, getter_AddRefs(mimeInfo)); if (NS_SUCCEEDED(rv)) rv = mimeInfo->GetMacType(&fileType); if (NS_SUCCEEDED(rv)) rv = mimeInfo->GetMacCreator(&fileCreator); if (NS_SUCCEEDED(rv)) rv = SetFileType(fileType); if (NS_SUCCEEDED(rv)) rv = SetFileCreator(fileCreator); } return rv; } NS_IMETHODIMP nsLocalFile::GetFileSizeWithResFork(PRInt64 *aFileSize) { NS_ENSURE_ARG(aFileSize); *aFileSize = LL_Zero(); ResolveAndStat(); long dataSize = 0; long resSize = 0; OSErr err = FSpGetFileSize(&mTargetSpec, &dataSize, &resSize); if (err != noErr) return MacErrorMapper(err); // For now we've only got 32 bits of file size info PRInt64 dataInt64 = LL_Zero(); PRInt64 resInt64 = LL_Zero(); // Combine the size of the resource and data forks LL_I2L(resInt64, resSize); LL_I2L(dataInt64, dataSize); LL_ADD((*aFileSize), dataInt64, resInt64); return NS_OK; } // this nsLocalFile points to the app. We want to launch it, optionally with the document. NS_IMETHODIMP nsLocalFile::LaunchWithDoc(nsILocalFile* aDocToLoad, PRBool aLaunchInBackground) { // are we launchable? PRBool isExecutable; nsresult rv = IsExecutable(&isExecutable); if (NS_FAILED(rv)) return rv; if (!isExecutable) return NS_ERROR_FILE_EXECUTION_FAILED; FSSpec docSpec; FSSpecPtr docSpecPtr = nsnull; nsCOMPtr macDoc = do_QueryInterface(aDocToLoad); if (macDoc) { rv = macDoc->GetFSSpec(&docSpec); // XXX GetTargetFSSpec if (NS_FAILED(rv)) return rv; docSpecPtr = &docSpec; } FSSpec appSpec; rv = GetFSSpec(&appSpec); // XXX GetResolvedFSSpec if (NS_FAILED(rv)) return rv; rv = MyLaunchAppWithDoc(appSpec, docSpecPtr, aLaunchInBackground); return rv; } NS_IMETHODIMP nsLocalFile::OpenDocWithApp(nsILocalFile* aAppToOpenWith, PRBool aLaunchInBackground) { // if aAppToOpenWith is nil, we have to find the app from the creator code // of the document nsresult rv = NS_OK; FSSpec appSpec; if (aAppToOpenWith) { nsCOMPtr appFileMac = do_QueryInterface(aAppToOpenWith, &rv); if (!appFileMac) return rv; rv = appFileMac->GetFSSpec(&appSpec); // XXX GetTargetFSSpec if (NS_FAILED(rv)) return rv; // is it launchable? PRBool isExecutable; rv = aAppToOpenWith->IsExecutable(&isExecutable); if (NS_FAILED(rv)) return rv; if (!isExecutable) return NS_ERROR_FILE_EXECUTION_FAILED; } else { // look for one OSType fileCreator; rv = GetFileCreator(&fileCreator); if (NS_FAILED(rv)) return rv; // just make one on the stack nsLocalFile localAppFile; rv = localAppFile.InitToAppWithCreatorCode(fileCreator); if (NS_FAILED(rv)) return rv; rv = localAppFile.GetFSSpec(&appSpec); // GetTargetFSSpec if (NS_FAILED(rv)) return rv; } FSSpec docSpec; rv = GetFSSpec(&docSpec); // XXX GetResolvedFSSpec if (NS_FAILED(rv)) return rv; rv = MyLaunchAppWithDoc(appSpec, &docSpec, aLaunchInBackground); return rv; } nsresult nsLocalFile::SetOSTypeAndCreatorFromExtension(const char* extension) { nsresult rv; nsCAutoString localExtBuf; const char *extPtr; if (!extension) { rv = GetNativeLeafName(localExtBuf); extPtr = strrchr(localExtBuf.get(), '.'); if (!extPtr) return NS_ERROR_FAILURE; ++extPtr; } else { extPtr = extension; if (*extPtr == '.') ++extPtr; } nsCOMPtr icService = do_GetService(NS_INTERNETCONFIGSERVICE_CONTRACTID, &rv); if (NS_SUCCEEDED(rv)) { nsCOMPtr mimeInfo; rv = icService->GetMIMEInfoFromExtension(extPtr, getter_AddRefs(mimeInfo)); if (NS_SUCCEEDED(rv)) { PRUint32 osType; rv = mimeInfo->GetMacType(&osType); if (NS_SUCCEEDED(rv)) mType = osType; PRBool skip; rv = ExtensionIsOnExceptionList(extPtr, &skip); if (NS_SUCCEEDED(rv) && !skip) { rv = mimeInfo->GetMacCreator(&osType); if (NS_SUCCEEDED(rv)) mCreator = osType; } } } return rv; } nsresult nsLocalFile::ExtensionIsOnExceptionList(const char *extension, PRBool *onList) { // Probably want to make a global list somewhere in the future // for now, just check for "html" and "htm" *onList = PR_FALSE; if (!nsCRT::strcasecmp(extension, "html") || !nsCRT::strcasecmp(extension, "htm")) *onList = PR_TRUE; return NS_OK; } void nsLocalFile::InitClassStatics() { OSErr err; if (sCurrentProcessSignature == 0) { ProcessSerialNumber psn; ProcessInfoRec info; psn.highLongOfPSN = 0; psn.lowLongOfPSN = kCurrentProcess; info.processInfoLength = sizeof(ProcessInfoRec); info.processName = nil; info.processAppSpec = nil; err = ::GetProcessInformation(&psn, &info); if (err == noErr) sCurrentProcessSignature = info.processSignature; // Try again next time if error } static PRBool didHFSPlusCheck = PR_FALSE; if (!didHFSPlusCheck) { long response; err = ::Gestalt(gestaltFSAttr, &response); sHasHFSPlusAPIs = (err == noErr && (response & (1 << gestaltHasHFSPlusAPIs)) != 0); didHFSPlusCheck = PR_TRUE; } static PRBool didOSXCheck = PR_FALSE; if (!didOSXCheck) { long version; sRunningOSX = (::Gestalt(gestaltSystemVersion, &version) == noErr && version >= 0x00001000); didOSXCheck = PR_TRUE; } } #pragma mark - // Handy dandy utility create routine for something or the other nsresult NS_NewNativeLocalFile(const nsACString &path, PRBool followLinks, nsILocalFile* *result) { nsLocalFile* file = new nsLocalFile(); if (file == nsnull) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(file); file->SetFollowLinks(followLinks); if (!path.IsEmpty()) { nsresult rv = file->InitWithNativePath(path); if (NS_FAILED(rv)) { NS_RELEASE(file); return rv; } } *result = file; return NS_OK; } nsresult NS_NewLocalFile(const nsAString &path, PRBool followLinks, nsILocalFile* *result) { nsCAutoString fsCharSetStr; nsresult rv = NS_CopyUnicodeToNative(path, fsCharSetStr); if (NS_FAILED(rv)) return rv; return NS_NewNativeLocalFile(fsCharSetStr, 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; } void nsLocalFile::GlobalInit() { } void nsLocalFile::GlobalShutdown() { }