/* -*- 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.0 (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 Netscape Communications Corporation. All Rights * Reserved. * * Contributors: * Daniel Veditz * Douglas Turner */ #include "nscore.h" #include "nsIFactory.h" #include "nsISupports.h" #include "nsRepository.h" #include "nsIServiceManager.h" #include "nsVector.h" #include "nsHashtable.h" #include "nsFileSpec.h" #include "nsFileStream.h" #include "nsSpecialSystemDirectory.h" #include "nsIPref.h" #include "prmem.h" #include "plstr.h" #include "prprf.h" #include "VerReg.h" #include "nsInstall.h" #include "nsInstallFolder.h" #include "nsInstallVersion.h" #include "nsInstallFile.h" #include "nsInstallDelete.h" #include "nsInstallExecute.h" #include "nsInstallPatch.h" #include "nsInstallUninstall.h" #ifdef NECKO #include "nsNeckoUtil.h" #endif #ifdef _WINDOWS #include "nsWinReg.h" #include "nsWinProfile.h" #endif #include "nsInstallFileOpEnums.h" #include "nsInstallFileOpItem.h" #ifdef XP_MAC #include "Gestalt.h" #include "nsAppleSingleDecoder.h" #endif #ifdef XP_PC #define FILESEP '\\' #elif defined(XP_MAC) #define FILESEP ':' #else #define FILESEP '/' #endif static NS_DEFINE_IID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID); static NS_DEFINE_IID(kIEventQueueServiceIID, NS_IEVENTQUEUESERVICE_IID); #ifdef NECKO #else static NS_DEFINE_IID(kNetServiceCID, NS_NETSERVICE_CID); static NS_DEFINE_IID(kINetServiceIID, NS_INETSERVICE_IID); #endif static NS_DEFINE_IID(kStringBundleServiceCID, NS_STRINGBUNDLESERVICE_CID); static NS_DEFINE_IID(kIStringBundleServiceIID, NS_ISTRINGBUNDLESERVICE_IID); nsInstallInfo::nsInstallInfo(nsIFileSpec* aFile, const PRUnichar* aURL, const PRUnichar* aArgs, long flags, nsIXPINotifier* aNotifier) : mError(0), mFlags(flags), mURL(aURL), mArgs(aArgs), mFile(aFile), mNotifier(aNotifier) {} nsInstallInfo::~nsInstallInfo() { } void nsInstallInfo::GetLocalFile(char **aPath) { if (mFile) mFile->GetNSPRPath(aPath); else *aPath = 0; } static NS_DEFINE_IID(kISoftwareUpdateIID, NS_ISOFTWAREUPDATE_IID); static NS_DEFINE_IID(kSoftwareUpdateCID, NS_SoftwareUpdate_CID); static NS_DEFINE_IID(kIJARIID, NS_IJAR_IID); static NS_DEFINE_IID(kJARCID, NS_JAR_CID); nsInstall::nsInstall() { mScriptObject = nsnull; // this is the jsobject for our context mVersionInfo = nsnull; // this is the version information passed to us in StartInstall() mRegistryPackageName = ""; // this is the name that we will add into the registry for the component we are installing mUIName = ""; // this is the name that will be displayed in UI. mUninstallPackage = PR_FALSE; mRegisterPackage = PR_FALSE; mJarFileLocation = ""; mInstallArguments = ""; // mJarFileData is an opaque handle to the jarfile. nsresult rv = nsComponentManager::CreateInstance(kJARCID, nsnull, kIJARIID, (void**) &mJarFileData); nsISoftwareUpdate *su; rv = nsServiceManager::GetService(kSoftwareUpdateCID, kISoftwareUpdateIID, (nsISupports**) &su); if (NS_SUCCEEDED(rv)) { su->GetMasterNotifier( &mNotifier ); } su->Release(); } nsInstall::~nsInstall() { if (mVersionInfo != nsnull) delete mVersionInfo; } PRInt32 nsInstall::SetScriptObject(void *aScriptObject) { mScriptObject = (JSObject*) aScriptObject; return NS_OK; } #ifdef _WINDOWS nsInstall::SaveWinRegPrototype(void *aScriptObject) { mWinRegObject = (JSObject*) aScriptObject; return NS_OK; } nsInstall::SaveWinProfilePrototype(void *aScriptObject) { mWinProfileObject = (JSObject*) aScriptObject; return NS_OK; } JSObject* nsInstall::RetrieveWinRegPrototype() { return mWinRegObject; } JSObject* nsInstall::RetrieveWinProfilePrototype() { return mWinProfileObject; } #endif PRInt32 nsInstall::GetUserPackageName(nsString& aUserPackageName) { aUserPackageName = mUIName; return NS_OK; } PRInt32 nsInstall::GetRegPackageName(nsString& aRegPackageName) { aRegPackageName = mRegistryPackageName; return NS_OK; } void nsInstall::InternalAbort(PRInt32 errcode) { if (mNotifier) mNotifier->FinalStatus(mInstallURL.GetUnicode(), errcode); nsInstallObject* ie; if (mInstalledFiles != nsnull) { PRUint32 i=0; for (i=0; i < mInstalledFiles->GetSize(); i++) { ie = (nsInstallObject *)mInstalledFiles->Get(i); if (ie) ie->Abort(); } } CleanUp(); } PRInt32 nsInstall::AbortInstall() { InternalAbort(ABORT_INSTALL); return NS_OK; } PRInt32 nsInstall::AddDirectory(const nsString& aRegName, const nsString& aVersion, const nsString& aJarSource, const nsString& aFolder, const nsString& aSubdir, PRBool aForceMode, PRInt32* aReturn) { nsInstallFile* ie = nsnull; PRInt32 result; if ( aJarSource == "null" || aFolder == "null") { *aReturn = SaveError(nsInstall::INVALID_ARGUMENTS); return NS_OK; } result = SanityCheck(); if (result != nsInstall::SUCCESS) { *aReturn = SaveError( result ); return NS_OK; } nsString qualifiedRegName; if ( aRegName == "" || aRegName == "null") { // Default subName = location in jar file *aReturn = GetQualifiedRegName( aJarSource, qualifiedRegName); } else { *aReturn = GetQualifiedRegName( aRegName, qualifiedRegName ); } if (*aReturn != SUCCESS) { return NS_OK; } nsString subdirectory(aSubdir); if (subdirectory != "") { subdirectory.Append("/"); } nsVector *paths = new nsVector(); if (paths == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return NS_OK; } result = ExtractDirEntries(aJarSource, paths); if (result != nsInstall::SUCCESS) { *aReturn = SaveError( result ); return NS_OK; } for (PRUint32 i=0; i < paths->GetSize(); i++) { nsString *thisPath = (nsString *)paths->Get(i); nsString newJarSource = aJarSource; newJarSource += "/"; newJarSource += *thisPath; nsString fullRegName = qualifiedRegName; fullRegName += "/"; fullRegName += *thisPath; nsString newSubDir; if (subdirectory != "") { newSubDir = subdirectory; } newSubDir += *thisPath; ie = new nsInstallFile( this, fullRegName, aVersion, newJarSource, aFolder, newSubDir, aForceMode, &result); if (ie == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return NS_OK; } if (result == nsInstall::SUCCESS) { result = ScheduleForInstall( ie ); } else { delete ie; } } DeleteVector(paths); *aReturn = SaveError( result ); return NS_OK; } PRInt32 nsInstall::AddDirectory(const nsString& aRegName, const nsString& aVersion, const nsString& aJarSource, const nsString& aFolder, const nsString& aSubdir, PRInt32* aReturn) { return AddDirectory(aRegName, aVersion, aJarSource, aFolder, aSubdir, PR_FALSE, aReturn); } PRInt32 nsInstall::AddDirectory(const nsString& aRegName, const nsString& aJarSource, const nsString& aFolder, const nsString& aSubdir, PRInt32* aReturn) { return AddDirectory(aRegName, "", aJarSource, aFolder, aSubdir, PR_FALSE, aReturn); } PRInt32 nsInstall::AddDirectory(const nsString& aJarSource, PRInt32* aReturn) { return AddDirectory("", "", aJarSource, "", "", PR_FALSE, aReturn); } PRInt32 nsInstall::AddSubcomponent(const nsString& aRegName, const nsString& aVersion, const nsString& aJarSource, const nsString& aFolder, const nsString& aTargetName, PRBool aForceMode, PRInt32* aReturn) { nsInstallFile* ie; nsString qualifiedRegName; nsString tempTargetName; PRInt32 errcode = nsInstall::SUCCESS; if((aTargetName == "") || (aTargetName == "null")) { tempTargetName = aJarSource; } else { tempTargetName = aTargetName; } if(aJarSource == "null" || aFolder == "null") { *aReturn = SaveError( nsInstall::INVALID_ARGUMENTS ); return NS_OK; } PRInt32 result = SanityCheck(); if (result != nsInstall::SUCCESS) { *aReturn = SaveError( result ); return NS_OK; } if ( aRegName == "" || aRegName == "null") { // Default subName = location in jar file *aReturn = GetQualifiedRegName( aJarSource, qualifiedRegName); } else { *aReturn = GetQualifiedRegName( aRegName, qualifiedRegName ); } if (*aReturn != SUCCESS) { return NS_OK; } ie = new nsInstallFile( this, qualifiedRegName, aVersion, aJarSource, aFolder, tempTargetName, aForceMode, &errcode ); if (ie == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return NS_OK; } if (errcode == nsInstall::SUCCESS) { errcode = ScheduleForInstall( ie ); } else { delete ie; } *aReturn = SaveError( errcode ); return NS_OK; } PRInt32 nsInstall::AddSubcomponent(const nsString& aRegName, const nsString& aVersion, const nsString& aJarSource, const nsString& aFolder, const nsString& aTargetName, PRInt32* aReturn) { return AddSubcomponent(aRegName, aVersion, aJarSource, aFolder, aTargetName, PR_FALSE, aReturn); } PRInt32 nsInstall::AddSubcomponent(const nsString& aRegName, const nsString& aJarSource, const nsString& aFolder, const nsString& aTargetName, PRInt32* aReturn) { return AddSubcomponent(aRegName, "", aJarSource, aFolder, aTargetName, PR_FALSE, aReturn); } PRInt32 nsInstall::AddSubcomponent(const nsString& aJarSource, PRInt32* aReturn) { if(mPackageFolder == "null") { *aReturn = SaveError( nsInstall::PACKAGE_FOLDER_NOT_SET ); return NS_OK; } return AddSubcomponent("", "", aJarSource, mPackageFolder, "", PR_FALSE, aReturn); } PRInt32 nsInstall::DeleteComponent(const nsString& aRegistryName, PRInt32* aReturn) { PRInt32 result = SanityCheck(); if (result != nsInstall::SUCCESS) { *aReturn = SaveError( result ); return NS_OK; } nsString qualifiedRegName; *aReturn = GetQualifiedRegName( aRegistryName, qualifiedRegName); if (*aReturn != SUCCESS) { return NS_OK; } nsInstallDelete* id = new nsInstallDelete(this, "", qualifiedRegName, &result); if (id == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return NS_OK; } if (result == nsInstall::SUCCESS) { result = ScheduleForInstall( id ); } *aReturn = SaveError(result); return NS_OK; } PRInt32 nsInstall::DeleteFile(const nsString& aFolder, const nsString& aRelativeFileName, PRInt32* aReturn) { PRInt32 result = SanityCheck(); if (result != nsInstall::SUCCESS) { *aReturn = SaveError( result ); return NS_OK; } nsInstallDelete* id = new nsInstallDelete(this, aFolder, aRelativeFileName, &result); if (id == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return NS_OK; } if (result == nsInstall::SUCCESS) { result = ScheduleForInstall( id ); } if (result == nsInstall::FILE_DOES_NOT_EXIST) { result = nsInstall::SUCCESS; } *aReturn = SaveError(result); return NS_OK; } PRInt32 nsInstall::DiskSpaceAvailable(const nsString& aFolder, PRInt32* aReturn) { nsFileSpec fsFolder(aFolder); *aReturn = fsFolder.GetDiskSpaceAvailable(); return NS_OK; } PRInt32 nsInstall::Execute(const nsString& aJarSource, const nsString& aArgs, PRInt32* aReturn) { PRInt32 result = SanityCheck(); if (result != nsInstall::SUCCESS) { *aReturn = SaveError( result ); return NS_OK; } nsInstallExecute* ie = new nsInstallExecute(this, aJarSource, aArgs, &result); if (ie == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return NS_OK; } if (result == nsInstall::SUCCESS) { result = ScheduleForInstall( ie ); } *aReturn = SaveError(result); return NS_OK; } PRInt32 nsInstall::Execute(const nsString& aJarSource, PRInt32* aReturn) { return Execute(aJarSource, "", aReturn); } PRInt32 nsInstall::FinalizeInstall(PRInt32* aReturn) { PRBool rebootNeeded = PR_FALSE; *aReturn = SanityCheck(); if (*aReturn != nsInstall::SUCCESS) { SaveError( *aReturn ); if (mNotifier) mNotifier->FinalStatus(mInstallURL.GetUnicode(), *aReturn); return NS_OK; } if ( mInstalledFiles == NULL || mInstalledFiles->GetSize() == 0 ) { // no actions queued: don't register the package version // and no need for user confirmation CleanUp(); if (mNotifier) mNotifier->FinalStatus(mInstallURL.GetUnicode(), *aReturn); return NS_OK; } if ( mUninstallPackage ) { VR_UninstallCreateNode( (char*)(const char*) nsAutoCString(mRegistryPackageName), (char*)(const char*) nsAutoCString(mUIName)); } // Install the Component into the Version Registry. if (mVersionInfo) { nsString versionString; mVersionInfo->ToString(versionString); VR_Install( (char*)(const char*)nsAutoCString(mRegistryPackageName), nsnull, (char*)(const char*)nsAutoCString(versionString), PR_FALSE ); } PRInt32 result; nsInstallObject* ie = nsnull; PRUint32 i=0; for (i=0; i < mInstalledFiles->GetSize(); i++) { ie = (nsInstallObject*)mInstalledFiles->Get(i); NS_ASSERTION(ie, "NULL object in install queue!"); if (ie == NULL) continue; PRUnichar *objString = ie->toString(); if (mNotifier) mNotifier->FinalizeProgress(objString, i, mInstalledFiles->GetSize()); if (objString) delete [] objString; result = ie->Complete(); if (result != nsInstall::SUCCESS) { if ( result == REBOOT_NEEDED ) { rebootNeeded = PR_TRUE; result = SUCCESS; } else { InternalAbort( result ); break; } } } if ( result != SUCCESS ) *aReturn = SaveError( result ); else if ( rebootNeeded ) *aReturn = SaveError( REBOOT_NEEDED ); if (mNotifier) mNotifier->FinalStatus(mInstallURL.GetUnicode(), *aReturn); return NS_OK; } #ifdef XP_MAC #define GESTALT_CHAR_CODE(x) (((unsigned long) ((x[0]) & 0x000000FF)) << 24) \ | (((unsigned long) ((x[1]) & 0x000000FF)) << 16) \ | (((unsigned long) ((x[2]) & 0x000000FF)) << 8) \ | (((unsigned long) ((x[3]) & 0x000000FF))) #endif /* XP_MAC */ PRInt32 nsInstall::Gestalt(const nsString& aSelector, PRInt32* aReturn) { *aReturn = nsnull; #ifdef XP_MAC long response = 0; char selectorChars[4]; int i; OSErr err = noErr; OSType selector; if (aSelector == "") { return NS_OK; } for (i=0; i<4; i++) selectorChars[i] = aSelector.CharAt(i); selector = GESTALT_CHAR_CODE(selectorChars); err = ::Gestalt(selector, &response); if (err != noErr) *aReturn = err; else *aReturn = response; #endif return NS_OK; } PRInt32 nsInstall::GetComponentFolder(const nsString& aComponentName, const nsString& aSubdirectory, nsString** aFolder) { long err; char* componentCString; char dir[MAXREGPATHLEN]; nsFileSpec nsfsDir; *aFolder = nsnull; nsString tempString; if ( GetQualifiedPackageName(aComponentName, tempString) != SUCCESS ) { return NS_OK; } componentCString = tempString.ToNewCString(); if((err = VR_GetDefaultDirectory( componentCString, MAXREGPATHLEN, dir )) != REGERR_OK) { if((err = VR_GetPath( componentCString, MAXREGPATHLEN, dir )) == REGERR_OK) { int i; nsString dirStr(dir); if ( (i = dirStr.RFindChar(FILESEP)) > 0 ) { // i is the index in the string, not the total number of // characters in the string. ToCString() requires the // total number of characters in the string to copy, // therefore add 1 to it. dirStr.Truncate(i + 1); dirStr.ToCString(dir, MAXREGPATHLEN); } } else { *dir = '\0'; } } else { *dir = '\0'; } if(*dir != '\0') { nsfsDir = dir; if(aSubdirectory != "") { nsfsDir += aSubdirectory; } *aFolder = new nsString(nsfsDir.GetNativePathCString()); } if (componentCString) delete [] componentCString; return NS_OK; } PRInt32 nsInstall::GetComponentFolder(const nsString& aComponentName, nsString** aFolder) { return GetComponentFolder(aComponentName, "", aFolder); } PRInt32 nsInstall::GetFolder(const nsString& targetFolder, const nsString& aSubdirectory, nsString** aFolder) { nsInstallFolder* spec = nsnull; *aFolder = nsnull; spec = new nsInstallFolder(targetFolder, aSubdirectory); if (spec != nsnull) { nsString dirString; spec->GetDirectoryPath(dirString); *aFolder = new nsString(dirString); } return NS_OK; } PRInt32 nsInstall::GetFolder(const nsString& targetFolder, nsString** aFolder) { return GetFolder(targetFolder, "", aFolder); } PRInt32 nsInstall::GetLastError(PRInt32* aReturn) { *aReturn = mLastError; return NS_OK; } PRInt32 nsInstall::GetWinProfile(const nsString& aFolder, const nsString& aFile, JSContext* jscontext, JSClass* WinProfileClass, jsval* aReturn) { *aReturn = JSVAL_NULL; #ifdef _WINDOWS JSObject* winProfileObject; nsWinProfile* nativeWinProfileObject = new nsWinProfile(this, aFolder, aFile); if (nativeWinProfileObject == nsnull) return NS_OK; JSObject* winProfilePrototype = this->RetrieveWinProfilePrototype(); winProfileObject = JS_NewObject(jscontext, WinProfileClass, winProfilePrototype, NULL); if(winProfileObject == NULL) return NS_OK; JS_SetPrivate(jscontext, winProfileObject, nativeWinProfileObject); *aReturn = OBJECT_TO_JSVAL(winProfileObject); #endif /* _WINDOWS */ return NS_OK; } PRInt32 nsInstall::GetWinRegistry(JSContext* jscontext, JSClass* WinRegClass, jsval* aReturn) { *aReturn = JSVAL_NULL; #ifdef _WINDOWS JSObject* winRegObject; nsWinReg* nativeWinRegObject = new nsWinReg(this); if (nativeWinRegObject == nsnull) return NS_OK; JSObject* winRegPrototype = this->RetrieveWinRegPrototype(); winRegObject = JS_NewObject(jscontext, WinRegClass, winRegPrototype, NULL); if(winRegObject == NULL) return NS_OK; JS_SetPrivate(jscontext, winRegObject, nativeWinRegObject); *aReturn = OBJECT_TO_JSVAL(winRegObject); #endif /* _WINDOWS */ return NS_OK; } PRInt32 nsInstall::LoadResources(JSContext* cx, const nsString& aBaseName, jsval* aReturn) { nsresult ret; nsFileSpec* resFile; nsFileURL* resFileURL = nsnull; nsIURI *url = nsnull; nsILocale* locale = nsnull; nsIStringBundleService* service = nsnull; #ifndef NECKO nsINetService* pNetService = nsnull; #endif nsIEventQueueService* pEventQueueService = nsnull; nsIStringBundle* bundle = nsnull; nsIBidirectionalEnumerator* propEnum = nsnull; *aReturn = JSVAL_NULL; jsval v = JSVAL_NULL; // set up JSObject to return JS_GetProperty( cx, JS_GetGlobalObject( cx ), "Object", &v ); if (!v) { return NS_ERROR_NULL_POINTER; } JSClass *objclass = JS_GetClass( cx, JSVAL_TO_OBJECT(v) ); JSObject *res = JS_NewObject( cx, objclass, JSVAL_TO_OBJECT(v), 0 ); // extract properties file // XXX append locale info: lang code, country code, .properties suffix to aBaseName PRInt32 err = ExtractFileFromJar(aBaseName, nsnull, &resFile); if ( (!resFile) || (err != nsInstall::SUCCESS) ) { SaveError( err ); return NS_OK; } // initialize string bundle and related services ret = nsServiceManager::GetService(kStringBundleServiceCID, kIStringBundleServiceIID, (nsISupports**) &service); if (NS_FAILED(ret)) goto cleanup; #ifndef NECKO ret = nsServiceManager::GetService(kNetServiceCID, kINetServiceIID, (nsISupports**) &pNetService); if (NS_FAILED(ret)) goto cleanup; #endif ret = nsServiceManager::GetService(kEventQueueServiceCID, kIEventQueueServiceIID, (nsISupports**) &pEventQueueService); if (NS_FAILED(ret)) goto cleanup; ret = pEventQueueService->CreateThreadEventQueue(); if (NS_FAILED(ret)) goto cleanup; // construct properties file URL as required by StringBundle interface resFileURL = new nsFileURL( *resFile ); #ifdef NECKO ret = NS_NewURI(&url, resFileURL->GetURLString()); #else ret = pNetService->CreateURL(&url, nsString( resFileURL->GetURLString()), nsnull, nsnull, nsnull); #endif if (resFileURL) delete resFileURL; if (NS_FAILED(ret)) goto cleanup; // get the string bundle using the extracted properties file #if 1 { #ifndef NECKO const char* spec = nsnull; #else char* spec = nsnull; #endif /* NECKO */ ret = url->GetSpec(&spec); if (NS_FAILED(ret)) { printf("cannot get url spec\n"); nsServiceManager::ReleaseService(kStringBundleServiceCID, service); #ifdef NECKO nsCRT::free(spec); #endif /* NECKO */ return ret; } ret = service->CreateBundle(spec, locale, &bundle); #ifdef NECKO nsCRT::free(spec); #endif /* NECKO */ } #else ret = service->CreateBundle(url, locale, &bundle); #endif if (NS_FAILED(ret)) goto cleanup; ret = bundle->GetEnumeration(&propEnum); if (NS_FAILED(ret)) goto cleanup; // set the variables of the JSObject to return using the StringBundle's // enumeration service ret = propEnum->First(); if (NS_FAILED(ret)) goto cleanup; while (NS_SUCCEEDED(ret)) { nsIPropertyElement* propElem = nsnull; ret = propEnum->CurrentItem((nsISupports**)&propElem); if (NS_FAILED(ret)) goto cleanup; nsString* key = nsnull; nsString* val = nsnull; ret = propElem->GetKey(&key); if (NS_FAILED(ret)) goto cleanup; ret = propElem->GetValue(&val); if (NS_FAILED(ret)) goto cleanup; char* keyCStr = key->ToNewCString(); PRUnichar* valCStr = val->ToNewUnicode(); if (keyCStr && valCStr) { JSString* propValJSStr = JS_NewUCStringCopyZ(cx, (jschar*) valCStr); jsval propValJSVal = STRING_TO_JSVAL(propValJSStr); JS_SetProperty(cx, res, keyCStr, &propValJSVal); delete[] keyCStr; delete[] valCStr; } if (key) delete key; if (val) delete val; ret = propEnum->Next(); } *aReturn = OBJECT_TO_JSVAL(res); ret = nsInstall::SUCCESS; cleanup: SaveError( ret ); // release services NS_IF_RELEASE( service ); NS_IF_RELEASE( pEventQueueService ); #ifndef NECKO NS_IF_RELEASE( pNetService ); #endif // release file, URL, StringBundle, Enumerator NS_IF_RELEASE( url ); NS_IF_RELEASE( bundle ); NS_IF_RELEASE( propEnum ); if (resFile) { // delete the transient properties file resFile->Delete(PR_FALSE); delete resFile; } return NS_OK; } PRInt32 nsInstall::Patch(const nsString& aRegName, const nsString& aVersion, const nsString& aJarSource, const nsString& aFolder, const nsString& aTargetName, PRInt32* aReturn) { PRInt32 result = SanityCheck(); if (result != nsInstall::SUCCESS) { *aReturn = SaveError( result ); return NS_OK; } nsString qualifiedRegName; *aReturn = GetQualifiedRegName( aRegName, qualifiedRegName); if (*aReturn != SUCCESS) { return NS_OK; } nsInstallPatch* ip = new nsInstallPatch( this, qualifiedRegName, aVersion, aJarSource, aFolder, aTargetName, &result); if (ip == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return NS_OK; } if (result == nsInstall::SUCCESS) { result = ScheduleForInstall( ip ); } *aReturn = SaveError(result); return NS_OK; } PRInt32 nsInstall::Patch(const nsString& aRegName, const nsString& aJarSource, const nsString& aFolder, const nsString& aTargetName, PRInt32* aReturn) { return Patch(aRegName, "", aJarSource, aFolder, aTargetName, aReturn); } PRInt32 nsInstall::ResetError() { mLastError = nsInstall::SUCCESS; return NS_OK; } PRInt32 nsInstall::SetPackageFolder(const nsString& aFolder) { mPackageFolder = aFolder; return NS_OK; } PRInt32 nsInstall::StartInstall(const nsString& aUserPackageName, const nsString& aRegistryPackageName, const nsString& aVersion, PRInt32* aReturn) { char szRegPackagePath[MAXREGPATHLEN]; char* szRegPackageName = aRegistryPackageName.ToNewCString(); if (szRegPackageName == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return PR_FALSE; } *szRegPackagePath = '0'; *aReturn = nsInstall::SUCCESS; ResetError(); mUserCancelled = PR_FALSE; if ( aRegistryPackageName.Equals("") || aRegistryPackageName.EqualsIgnoreCase("null")) { *aReturn = nsInstall::INVALID_ARGUMENTS; return NS_OK; } mUIName = aUserPackageName; *aReturn = GetQualifiedPackageName( aRegistryPackageName, mRegistryPackageName ); if (*aReturn != nsInstall::SUCCESS) { return NS_OK; } if(REGERR_OK == VR_GetDefaultDirectory(szRegPackageName, MAXREGPATHLEN, szRegPackagePath)) { mPackageFolder = szRegPackagePath; } else { mPackageFolder = "null"; } if(szRegPackageName) delete [] szRegPackageName; if (mVersionInfo != nsnull) delete mVersionInfo; mVersionInfo = new nsInstallVersion(); if (mVersionInfo == nsnull) { *aReturn = nsInstall::OUT_OF_MEMORY; return nsInstall::OUT_OF_MEMORY; } mVersionInfo->Init(aVersion); mInstalledFiles = new nsVector(); mPatchList = new nsHashtable(); if (mInstalledFiles == nsnull || mPatchList == nsnull) { *aReturn = nsInstall::OUT_OF_MEMORY; return nsInstall::OUT_OF_MEMORY; } /* this function should also check security!!! */ *aReturn = OpenJARFile(); if (*aReturn != nsInstall::SUCCESS) { /* if we can not continue with the javascript return a JAR error*/ return -1; /* FIX: need real error code */ } SaveError(*aReturn); if (mNotifier) mNotifier->InstallStarted(mInstallURL.GetUnicode(), mUIName.GetUnicode()); return NS_OK; } PRInt32 nsInstall::Uninstall(const nsString& aRegistryPackageName, PRInt32* aReturn) { PRInt32 result = SanityCheck(); if (result != nsInstall::SUCCESS) { *aReturn = SaveError( result ); return NS_OK; } nsString qualifiedPackageName; *aReturn = GetQualifiedPackageName( aRegistryPackageName, qualifiedPackageName ); if (*aReturn != SUCCESS) { return NS_OK; } nsInstallUninstall *ie = new nsInstallUninstall( this, qualifiedPackageName, &result ); if (ie == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return NS_OK; } if (result == nsInstall::SUCCESS) { result = ScheduleForInstall( ie ); } else { delete ie; } *aReturn = SaveError(result); return NS_OK; } //////////////////////////////////////// void nsInstall::AddPatch(nsHashKey *aKey, nsFileSpec* fileName) { if (mPatchList != nsnull) { mPatchList->Put(aKey, fileName); } } void nsInstall::GetPatch(nsHashKey *aKey, nsFileSpec** fileName) { if (mPatchList != nsnull) { *fileName = (nsFileSpec*) mPatchList->Get(aKey); } } PRInt32 nsInstall::FileOpDirCreate(nsFileSpec& aTarget, PRInt32* aReturn) { nsInstallFileOpItem* ifop = new nsInstallFileOpItem(this, NS_FOP_DIR_CREATE, aTarget, aReturn); PRInt32 result = SanityCheck(); if (result != nsInstall::SUCCESS) { *aReturn = SaveError( result ); return NS_OK; } if (ifop == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return NS_OK; } if (*aReturn == nsInstall::SUCCESS) { *aReturn = ScheduleForInstall( ifop ); } if (*aReturn == nsInstall::FILE_DOES_NOT_EXIST) { *aReturn = nsInstall::SUCCESS; } SaveError(*aReturn); return NS_OK; } PRInt32 nsInstall::FileOpDirGetParent(nsFileSpec& aTarget, nsFileSpec* aReturn) { // nsInstallFileOpItem ifop(this, aTarget, aReturn); aTarget.GetParent(*aReturn); return NS_OK; } PRInt32 nsInstall::FileOpDirRemove(nsFileSpec& aTarget, PRInt32 aFlags, PRInt32* aReturn) { nsInstallFileOpItem* ifop = new nsInstallFileOpItem(this, NS_FOP_DIR_REMOVE, aTarget, aFlags, aReturn); PRInt32 result = SanityCheck(); if (result != nsInstall::SUCCESS) { *aReturn = SaveError( result ); return NS_OK; } if (ifop == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return NS_OK; } if (*aReturn == nsInstall::SUCCESS) { *aReturn = ScheduleForInstall( ifop ); } if (*aReturn == nsInstall::FILE_DOES_NOT_EXIST) { *aReturn = nsInstall::SUCCESS; } SaveError(*aReturn); return NS_OK; } PRInt32 nsInstall::FileOpDirRename(nsFileSpec& aSrc, nsString& aTarget, PRInt32* aReturn) { nsInstallFileOpItem* ifop = new nsInstallFileOpItem(this, NS_FOP_DIR_RENAME, aSrc, aTarget, aReturn); PRInt32 result = SanityCheck(); if (result != nsInstall::SUCCESS) { *aReturn = SaveError( result ); return NS_OK; } if (ifop == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return NS_OK; } if (*aReturn == nsInstall::SUCCESS) { *aReturn = ScheduleForInstall( ifop ); } if (*aReturn == nsInstall::FILE_DOES_NOT_EXIST) { *aReturn = nsInstall::SUCCESS; } SaveError(*aReturn); return NS_OK; } PRInt32 nsInstall::FileOpFileCopy(nsFileSpec& aSrc, nsFileSpec& aTarget, PRInt32* aReturn) { nsInstallFileOpItem* ifop = new nsInstallFileOpItem(this, NS_FOP_FILE_COPY, aSrc, aTarget, aReturn); PRInt32 result = SanityCheck(); if (result != nsInstall::SUCCESS) { *aReturn = SaveError( result ); return NS_OK; } if (ifop == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return NS_OK; } if (*aReturn == nsInstall::SUCCESS) { *aReturn = ScheduleForInstall( ifop ); } if (*aReturn == nsInstall::FILE_DOES_NOT_EXIST) { *aReturn = nsInstall::SUCCESS; } SaveError(*aReturn); return NS_OK; } PRInt32 nsInstall::FileOpFileDelete(nsFileSpec& aTarget, PRInt32 aFlags, PRInt32* aReturn) { nsInstallFileOpItem* ifop = new nsInstallFileOpItem(this, NS_FOP_FILE_DELETE, aTarget, aFlags, aReturn); PRInt32 result = SanityCheck(); if (result != nsInstall::SUCCESS) { *aReturn = SaveError( result ); return NS_OK; } if (ifop == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return NS_OK; } if (*aReturn == nsInstall::SUCCESS) { *aReturn = ScheduleForInstall( ifop ); } if (*aReturn == nsInstall::FILE_DOES_NOT_EXIST) { *aReturn = nsInstall::SUCCESS; } SaveError(*aReturn); return NS_OK; } PRInt32 nsInstall::FileOpFileExecute(nsFileSpec& aTarget, nsString& aParams, PRInt32* aReturn) { nsInstallFileOpItem* ifop = new nsInstallFileOpItem(this, NS_FOP_FILE_EXECUTE, aTarget, aParams, aReturn); PRInt32 result = SanityCheck(); if (result != nsInstall::SUCCESS) { *aReturn = SaveError( result ); return NS_OK; } if (ifop == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return NS_OK; } if (*aReturn == nsInstall::SUCCESS) { *aReturn = ScheduleForInstall( ifop ); } if (*aReturn == nsInstall::FILE_DOES_NOT_EXIST) { *aReturn = nsInstall::SUCCESS; } SaveError(*aReturn); return NS_OK; } PRInt32 nsInstall::FileOpFileExists(nsFileSpec& aTarget, PRBool* aReturn) { *aReturn = aTarget.Exists(); return NS_OK; } PRInt32 nsInstall::FileOpFileGetNativeVersion(nsFileSpec& aTarget, nsString* aReturn) { return NS_OK; } PRInt32 nsInstall::FileOpFileGetDiskSpaceAvailable(nsFileSpec& aTarget, PRUint32* aReturn) { *aReturn = aTarget.GetDiskSpaceAvailable(); return NS_OK; } PRInt32 nsInstall::FileOpFileGetModDate(nsFileSpec& aTarget, nsFileSpec::TimeStamp* aReturn) { aTarget.GetModDate(*aReturn); return NS_OK; } PRInt32 nsInstall::FileOpFileGetSize(nsFileSpec& aTarget, PRUint32* aReturn) { *aReturn = aTarget.GetFileSize(); return NS_OK; } PRInt32 nsInstall::FileOpFileIsDirectory(nsFileSpec& aTarget, PRBool* aReturn) { *aReturn = aTarget.IsDirectory(); return NS_OK; } PRInt32 nsInstall::FileOpFileIsFile(nsFileSpec& aTarget, PRBool* aReturn) { *aReturn = aTarget.IsFile(); return NS_OK; } PRInt32 nsInstall::FileOpFileModDateChanged(nsFileSpec& aTarget, nsFileSpec::TimeStamp& aOldStamp, PRBool* aReturn) { *aReturn = aTarget.ModDateChanged(aOldStamp); return NS_OK; } PRInt32 nsInstall::FileOpFileMove(nsFileSpec& aSrc, nsFileSpec& aTarget, PRInt32* aReturn) { nsInstallFileOpItem* ifop = new nsInstallFileOpItem(this, NS_FOP_FILE_MOVE, aSrc, aTarget, aReturn); PRInt32 result = SanityCheck(); if (result != nsInstall::SUCCESS) { *aReturn = SaveError( result ); return NS_OK; } if (ifop == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return NS_OK; } if (*aReturn == nsInstall::SUCCESS) { *aReturn = ScheduleForInstall( ifop ); } if (*aReturn == nsInstall::FILE_DOES_NOT_EXIST) { *aReturn = nsInstall::SUCCESS; } SaveError(*aReturn); return NS_OK; } PRInt32 nsInstall::FileOpFileRename(nsFileSpec& aSrc, nsString& aTarget, PRInt32* aReturn) { nsInstallFileOpItem* ifop = new nsInstallFileOpItem(this, NS_FOP_FILE_RENAME, aSrc, aTarget, aReturn); PRInt32 result = SanityCheck(); if (result != nsInstall::SUCCESS) { *aReturn = SaveError( result ); return NS_OK; } if (ifop == nsnull) { *aReturn = SaveError(nsInstall::OUT_OF_MEMORY); return NS_OK; } if (*aReturn == nsInstall::SUCCESS) { *aReturn = ScheduleForInstall( ifop ); } if (*aReturn == nsInstall::FILE_DOES_NOT_EXIST) { *aReturn = nsInstall::SUCCESS; } SaveError(*aReturn); return NS_OK; } PRInt32 nsInstall::FileOpFileWinShortcutCreate(nsFileSpec& aTarget, PRInt32 aFlags, PRInt32* aReturn) { return NS_OK; } PRInt32 nsInstall::FileOpFileMacAliasCreate(nsFileSpec& aTarget, PRInt32 aFlags, PRInt32* aReturn) { return NS_OK; } PRInt32 nsInstall::FileOpFileUnixLinkCreate(nsFileSpec& aTarget, PRInt32 aFlags, PRInt32* aReturn) { return NS_OK; } void nsInstall::LogComment(nsString& aComment) { if(mNotifier) mNotifier->LogComment(aComment.GetUnicode()); } ///////////////////////////////////////////////////////////////////////// // Private Methods ///////////////////////////////////////////////////////////////////////// /** * ScheduleForInstall * call this to put an InstallObject on the install queue * Do not call installedFiles.addElement directly, because this routine also * handles progress messages */ PRInt32 nsInstall::ScheduleForInstall(nsInstallObject* ob) { PRInt32 error = nsInstall::SUCCESS; PRUnichar *objString = ob->toString(); // flash current item if (mNotifier) mNotifier->ItemScheduled(objString); // do any unpacking or other set-up error = ob->Prepare(); if (error == nsInstall::SUCCESS) { // Add to installation list mInstalledFiles->Add( ob ); // turn on flags for creating the uninstall node and // the package node for each InstallObject if (ob->CanUninstall()) mUninstallPackage = PR_TRUE; if (ob->RegisterPackageNode()) mRegisterPackage = PR_TRUE; } else if ( mNotifier ) { // error in preparation step -- log it char* errprefix = PR_smprintf("ERROR (%d): ",error); nsString errstr = errprefix; errstr += objString; mNotifier->LogComment( errstr.GetUnicode() ); PR_smprintf_free(errprefix); } if (objString) delete [] objString; return error; } /** * SanityCheck * * This routine checks if the packageName is null. It also checks the flag if the user cancels * the install progress dialog is set and acccordingly aborts the install. */ PRInt32 nsInstall::SanityCheck(void) { if ( mRegistryPackageName == "" || mUIName == "") { return INSTALL_NOT_STARTED; } if (mUserCancelled) { InternalAbort(USER_CANCELLED); return USER_CANCELLED; } return 0; } /** * GetQualifiedPackageName * * This routine converts a package-relative component registry name * into a full name that can be used in calls to the version registry. */ PRInt32 nsInstall::GetQualifiedPackageName( const nsString& name, nsString& qualifiedName ) { nsString startOfName; name.Left(startOfName, 7); if ( startOfName.Equals( "=USER=/") ) { CurrentUserNode(qualifiedName); qualifiedName += name; } else { qualifiedName = name; } if (BadRegName(qualifiedName)) { return BAD_PACKAGE_NAME; } /* Check to see if the PackageName ends in a '/'. If it does nuke it. */ if (qualifiedName.Last() == '/') { PRInt32 index = qualifiedName.Length(); qualifiedName.Truncate(--index); } return SUCCESS; } /** * GetQualifiedRegName * * This routine converts a package-relative component registry name * into a full name that can be used in calls to the version registry. */ PRInt32 nsInstall::GetQualifiedRegName(const nsString& name, nsString& qualifiedRegName ) { nsString startOfName; name.Left(startOfName, 7); nsString usr (); if ( startOfName.Equals("=COMM=/") || startOfName.Equals("=USER=/")) { qualifiedRegName = name; qualifiedRegName.Cut( 0, 7 ); } else if ( name.CharAt(0) != '/' ) { if (mRegistryPackageName != "") { qualifiedRegName = mRegistryPackageName; qualifiedRegName += "/"; qualifiedRegName += name; } else { qualifiedRegName = name; } } else { qualifiedRegName = name; } if (BadRegName(qualifiedRegName)) { return BAD_PACKAGE_NAME; } return SUCCESS; } static NS_DEFINE_IID(kPrefsIID, NS_IPREF_IID); static NS_DEFINE_IID(kPrefsCID, NS_PREF_CID); void nsInstall::CurrentUserNode(nsString& userRegNode) { char *profname; nsIPref * prefs; nsresult rv = nsServiceManager::GetService(kPrefsCID, kPrefsIID, (nsISupports**) &prefs); if ( NS_SUCCEEDED(rv) ) { rv = prefs->CopyCharPref("profile.name", &profname); if ( NS_FAILED(rv) ) { PR_FREEIF(profname); // Allocated by PREF_CopyCharPref profname = NULL; } NS_RELEASE(prefs); } else { profname = NULL; } userRegNode = "/Netscape/Users/"; if (profname != nsnull) { userRegNode += nsString(profname); userRegNode += "/"; PR_FREEIF(profname); } } // catch obvious registry name errors proactively // rather than returning some cryptic libreg error PRBool nsInstall::BadRegName(const nsString& regName) { if ((regName.First() == ' ' ) || (regName.Last() == ' ' )) return PR_TRUE; if ( regName.Find("//") != -1 ) return PR_TRUE; if ( regName.Find(" /") != -1 ) return PR_TRUE; if ( regName.Find("/ ") != -1 ) return PR_TRUE; if ( regName.Find("=") != -1 ) return PR_TRUE; return PR_FALSE; } PRInt32 nsInstall::SaveError(PRInt32 errcode) { if ( errcode != nsInstall::SUCCESS ) mLastError = errcode; return errcode; } /* * CleanUp * call it when done with the install * */ void nsInstall::CleanUp(void) { nsInstallObject* ie; CloseJARFile(); if ( mInstalledFiles != NULL ) { PRUint32 i=0; for (; i < mInstalledFiles->GetSize(); i++) { ie = (nsInstallObject*)mInstalledFiles->Get(i); if (ie) delete (ie); } mInstalledFiles->RemoveAll(); delete (mInstalledFiles); mInstalledFiles = nsnull; } if (mPatchList) { // XXX do I need to delete every entry? delete mPatchList; } mRegistryPackageName = ""; // used to see if StartInstall() has been called } void nsInstall::GetJarFileLocation(nsString& aFile) { aFile = mJarFileLocation; } void nsInstall::SetJarFileLocation(const nsString& aFile) { mJarFileLocation = aFile; } void nsInstall::GetInstallArguments(nsString& args) { args = mInstallArguments; } void nsInstall::SetInstallArguments(const nsString& args) { mInstallArguments = args; } void nsInstall::GetInstallURL(nsString& url) { url = mInstallURL; } void nsInstall::SetInstallURL(const nsString& url) { mInstallURL = url; } PRInt32 nsInstall::OpenJARFile(void) { PRInt32 result; nsresult rv = mJarFileData->Open( nsAutoCString(mJarFileLocation), &result ); if (NS_FAILED(rv)) return UNEXPECTED_ERROR; return result; } void nsInstall::CloseJARFile(void) { NS_IF_RELEASE(mJarFileData); } // aJarFile - This is the filepath within the jar file. // aSuggestedName - This is the name that we should try to extract to. If we can, we will create a new temporary file. // aRealName - This is the name that we did extract to. This will be allocated by use and should be disposed by the caller. PRInt32 nsInstall::ExtractFileFromJar(const nsString& aJarfile, nsFileSpec* aSuggestedName, nsFileSpec** aRealName) { PRInt32 result; nsFileSpec *extractHereSpec; if (aSuggestedName == nsnull) { nsSpecialSystemDirectory tempFile(nsSpecialSystemDirectory::OS_TemporaryDirectory); nsString tempFileName = "xpinstall"; // Get the extension of the file in the JAR result = aJarfile.RFindChar('.'); if (result != -1) { // We found the extension; add it to the tempFileName string nsString extension; aJarfile.Right(extension, (aJarfile.Length() - result) ); tempFileName += extension; } tempFile += tempFileName; // Create a temporary file to extract to tempFile.MakeUnique(); extractHereSpec = new nsFileSpec(tempFile); if (extractHereSpec == nsnull) return nsInstall::OUT_OF_MEMORY; } else { // extract to the final destination. extractHereSpec = new nsFileSpec(*aSuggestedName); if (extractHereSpec == nsnull) return nsInstall::OUT_OF_MEMORY; extractHereSpec->MakeUnique(); } // We will overwrite what is in the way. is this something that we want to do? extractHereSpec->Delete(PR_FALSE); nsresult rv = mJarFileData->Extract( nsAutoCString(aJarfile), nsNSPRPath( *extractHereSpec ), &result ); if (NS_FAILED(rv)) { if (extractHereSpec != nsnull) delete extractHereSpec; return EXTRACTION_FAILED; } if (result == 0) { #ifdef XP_MAC FSSpec finalSpec, extractedSpec = extractHereSpec->GetFSSpec(); if ( nsAppleSingleDecoder::IsAppleSingleFile(&extractedSpec) ) { nsAppleSingleDecoder *asd = new nsAppleSingleDecoder(&extractedSpec, &finalSpec); OSErr decodeErr = asd->Decode(); if (decodeErr != noErr) { if (extractHereSpec) delete extractHereSpec; if (asd) delete asd; return EXTRACTION_FAILED; } if ( !(extractedSpec.vRefNum == finalSpec.vRefNum) || !(extractedSpec.parID == finalSpec.parID) || !(nsAppleSingleDecoder::PLstrcmp(extractedSpec.name, finalSpec.name)) ) { // delete the unique extracted file that got renamed in AS decoding FSpDelete(&extractedSpec); // "real name" in AppleSingle entry may cause file rename *extractHereSpec = finalSpec; } } #endif *aRealName = extractHereSpec; } else { if (extractHereSpec != nsnull) delete extractHereSpec; } return result; } PRInt32 nsInstall::ExtractDirEntries(const nsString& directory, nsVector *paths) { char *buf; nsISimpleEnumerator *jarEnum = nsnull; nsIJARItem *currJARItem = nsnull; if ( paths ) { nsString pattern(directory); pattern += "/*"; PRInt32 prefix_length = directory.Length()+1; // account for slash nsresult rv = mJarFileData->Find( nsAutoCString(pattern), &jarEnum ); if (NS_FAILED(rv) || !jarEnum) goto handle_err; PRBool bMore; rv = jarEnum->HasMoreElements(&bMore); while (bMore && NS_SUCCEEDED(rv)) { rv = jarEnum->GetNext( (nsISupports**) &currJARItem ); if (currJARItem) { // expensive 'buf' callee malloc per iteration! rv = currJARItem->GetName(&buf); if (NS_FAILED(rv)) goto handle_err; if (buf) { PRInt32 namelen = PL_strlen(buf); NS_ASSERTION( prefix_length <= namelen, "Match must be longer than pattern!" ); if ( buf[namelen-1] != '/' ) { paths->Add( new nsString(buf+prefix_length) ); // XXX manipulation should be in caller } PR_FREEIF( buf ); } NS_IF_RELEASE(currJARItem); } rv = jarEnum->HasMoreElements(&bMore); } } NS_IF_RELEASE(jarEnum); return SUCCESS; handle_err: NS_IF_RELEASE(jarEnum); NS_IF_RELEASE(currJARItem); return EXTRACTION_FAILED; } void nsInstall::DeleteVector(nsVector* vector) { if (vector != nsnull) { PRUint32 i=0; for (; i < vector->GetSize(); i++) { nsString* element = (nsString*)vector->Get(i); if (element != nsnull) delete element; } vector->RemoveAll(); delete (vector); vector = nsnull; } }