/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Communicator client code, released * March 31, 1998. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Daniel Veditz * Dave Townsend * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nscore.h" #include "prprf.h" #include "plstr.h" #include "nsInt64.h" #include "nsISupports.h" #include "nsIServiceManager.h" #include "nsIURL.h" #include "nsIFileURL.h" #include "nsIJAR.h" #include "nsITransport.h" #include "nsIOutputStream.h" #include "nsNetUtil.h" #include "nsIInputStream.h" #include "nsIFileStreams.h" #include "nsIStreamListener.h" #include "nsICryptoHash.h" #include "nsIExtensionManager.h" #include "nsSoftwareUpdateIIDs.h" #include "nsIStringEnumerator.h" #include "nsXPITriggerInfo.h" #include "nsXPInstallManager.h" #include "nsInstallTrigger.h" #include "nsIWindowWatcher.h" #include "nsIAuthPrompt.h" #include "nsIWindowMediator.h" #include "nsIDocument.h" #include "nsIDOMDocument.h" #include "nsIDOMWindowInternal.h" #include "nsDirectoryService.h" #include "nsDirectoryServiceDefs.h" #include "nsAppDirectoryServiceDefs.h" #include "nsReadableUtils.h" #include "nsIPromptService.h" #include "nsIScriptGlobalObject.h" #include "nsXPCOM.h" #include "nsISupportsPrimitives.h" #include "nsIObserverService.h" #include "nsISSLStatusProvider.h" #include "nsISSLStatus.h" #include "nsIX509Cert.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include "CertReader.h" #include "nsEmbedCID.h" #define PREF_XPINSTALL_ENABLED "xpinstall.enabled" #define PREF_XPINSTALL_CONFIRM_DLG "xpinstall.dialog.confirm" #define PREF_XPINSTALL_STATUS_DLG_SKIN "xpinstall.dialog.progress.skin" #define PREF_XPINSTALL_STATUS_DLG_CHROME "xpinstall.dialog.progress.chrome" #define PREF_XPINSTALL_STATUS_DLG_TYPE_SKIN "xpinstall.dialog.progress.type.skin" #define PREF_XPINSTALL_STATUS_DLG_TYPE_CHROME "xpinstall.dialog.progress.type.chrome" static NS_DEFINE_IID(kZipReaderCID, NS_ZIPREADER_CID); nsXPInstallManager::nsXPInstallManager() : mTriggers(0), mItem(0), mNextItem(0), mChromeType(NOT_CHROME), mContentLength(0), mDialogOpen(PR_FALSE), mCancelled(PR_FALSE), mNeedsShutdown(PR_FALSE), mFromChrome(PR_FALSE) { // we need to own ourself because we have a longer // lifetime than the scriptlet that created us. NS_ADDREF_THIS(); } nsXPInstallManager::~nsXPInstallManager() { NS_ASSERTION(!mTriggers, "Shutdown not called, triggers still alive"); } NS_INTERFACE_MAP_BEGIN(nsXPInstallManager) NS_INTERFACE_MAP_ENTRY(nsIXPIDialogService) NS_INTERFACE_MAP_ENTRY(nsIXPInstallManager) NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_ENTRY(nsIStreamListener) NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink) NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) NS_INTERFACE_MAP_ENTRY(nsPICertNotification) NS_INTERFACE_MAP_ENTRY(nsIBadCertListener2) NS_INTERFACE_MAP_ENTRY(nsISSLErrorListener) NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISupportsWeakReference) NS_INTERFACE_MAP_END NS_IMPL_ADDREF(nsXPInstallManager) NS_IMPL_RELEASE(nsXPInstallManager) NS_IMETHODIMP nsXPInstallManager::InitManagerFromChrome(const PRUnichar **aURLs, PRUint32 aURLCount, nsIXPIProgressDialog* aListener) { return InitManagerWithHashes(aURLs, nsnull, aURLCount, aListener); } NS_IMETHODIMP nsXPInstallManager::InitManagerWithHashes(const PRUnichar **aURLs, const char **aHashes, PRUint32 aURLCount, nsIXPIProgressDialog* aListener) { // If Software Installation is not enabled, we don't want to proceed with // update. PRBool xpinstallEnabled = PR_TRUE; nsCOMPtr pref(do_GetService(NS_PREFSERVICE_CONTRACTID)); if (pref) pref->GetBoolPref(PREF_XPINSTALL_ENABLED, &xpinstallEnabled); if (!xpinstallEnabled) return NS_OK; mTriggers = new nsXPITriggerInfo(); if (!mTriggers) return NS_ERROR_OUT_OF_MEMORY; mNeedsShutdown = PR_TRUE; for (PRUint32 i = 0; i < aURLCount; ++i) { nsXPITriggerItem* item = new nsXPITriggerItem(0, aURLs[i], nsnull, aHashes ? aHashes[i] : nsnull); if (!item) { delete mTriggers; // nsXPITriggerInfo frees any alloc'ed nsXPITriggerItems mTriggers = nsnull; Shutdown(); return NS_ERROR_OUT_OF_MEMORY; } mTriggers->Add(item); } mFromChrome = PR_TRUE; nsresult rv = Observe(aListener, XPI_PROGRESS_TOPIC, NS_LITERAL_STRING("open").get()); if (NS_FAILED(rv)) Shutdown(); return rv; } NS_IMETHODIMP nsXPInstallManager::InitManagerWithInstallInfo(nsIXPIInstallInfo* aInstallInfo) { nsXPITriggerInfo* triggers; nsresult rv = aInstallInfo->GetTriggerInfo(&triggers); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr win; rv = aInstallInfo->GetOriginatingWindow(getter_AddRefs(win)); if (NS_SUCCEEDED(rv)) { PRUint32 type; rv = aInstallInfo->GetChromeType(&type); if (NS_SUCCEEDED(rv)) { // Passing ownership onto InitManager which will free when necessary aInstallInfo->SetTriggerInfo(nsnull); return InitManager(win, triggers, type); } } NS_RELEASE_THIS(); return rv; } NS_IMETHODIMP nsXPInstallManager::InitManager(nsIDOMWindowInternal* aParentWindow, nsXPITriggerInfo* aTriggers, PRUint32 aChromeType) { if ( !aTriggers || aTriggers->Size() == 0 ) { NS_WARNING("XPInstallManager called with no trigger info!"); delete aTriggers; NS_RELEASE_THIS(); return NS_ERROR_INVALID_POINTER; } nsresult rv = NS_OK; mNeedsShutdown = PR_TRUE; mTriggers = aTriggers; mChromeType = aChromeType; mParentWindow = aParentWindow; // Attempt to find a load group, continue if we can't find one though if (aParentWindow) { nsCOMPtr domdoc; rv = aParentWindow->GetDocument(getter_AddRefs(domdoc)); if (NS_SUCCEEDED(rv) && domdoc) { nsCOMPtr doc = do_QueryInterface(domdoc); if (doc) mLoadGroup = doc->GetDocumentLoadGroup(); } } // Start downloading initial chunks looking for signatures, mOutstandingCertLoads = mTriggers->Size(); nsXPITriggerItem *item = mTriggers->Get(--mOutstandingCertLoads); nsCOMPtr uri; NS_NewURI(getter_AddRefs(uri), NS_ConvertUTF16toUTF8(item->mURL)); nsCOMPtr listener = new CertReader(uri, nsnull, this); if (listener) rv = NS_OpenURI(listener, nsnull, uri, nsnull, mLoadGroup); else rv = NS_ERROR_OUT_OF_MEMORY; if (NS_FAILED(rv)) { Shutdown(); } return rv; } nsresult nsXPInstallManager::InitManagerInternal() { nsresult rv; PRBool OKtoInstall = PR_FALSE; // initialize to secure state //----------------------------------------------------- // *** Do not return early after this point *** // // We have to clean up the triggers in case of error //----------------------------------------------------- // --- use embedding dialogs if any registered nsCOMPtr dlgSvc(do_CreateInstance(NS_XPIDIALOGSERVICE_CONTRACTID)); if ( !dlgSvc ) dlgSvc = this; // provide our own dialogs // --- prepare dialog params PRUint32 numTriggers = mTriggers->Size(); PRUint32 numStrings = 4 * numTriggers; const PRUnichar** packageList = (const PRUnichar**)malloc( sizeof(PRUnichar*) * numStrings ); if ( packageList ) { // populate the list. The list doesn't own the strings for ( PRUint32 i=0, j=0; i < numTriggers; i++ ) { nsXPITriggerItem *item = mTriggers->Get(i); packageList[j++] = item->mName.get(); packageList[j++] = item->GetSafeURLString(); packageList[j++] = item->mIconURL.get(); packageList[j++] = item->mCertName.get(); } //----------------------------------------------------- // Get permission to install //----------------------------------------------------- #ifdef ENABLE_SKIN_SIMPLE_INSTALLATION_UI if ( mChromeType == CHROME_SKIN ) { // We may want to enable the simple installation UI once // bug 343037 is fixed // skins get a simpler/friendlier dialog // XXX currently not embeddable OKtoInstall = ConfirmChromeInstall( mParentWindow, packageList ); } else { #endif rv = dlgSvc->ConfirmInstall( mParentWindow, packageList, numStrings, &OKtoInstall ); if (NS_FAILED(rv)) OKtoInstall = PR_FALSE; #ifdef ENABLE_SKIN_SIMPLE_INSTALLATION_UI } #endif if (OKtoInstall) { //----------------------------------------------------- // Open the progress dialog //----------------------------------------------------- rv = dlgSvc->OpenProgressDialog( packageList, numStrings, this ); } } else rv = NS_ERROR_OUT_OF_MEMORY; //----------------------------------------------------- // cleanup and signal callbacks if there were errors //----------------------------------------------------- if (packageList) free(packageList); PRInt32 cbstatus = 0; // callback status if (NS_FAILED(rv)) cbstatus = nsInstall::UNEXPECTED_ERROR; else if (!OKtoInstall) cbstatus = nsInstall::USER_CANCELLED; if ( cbstatus != 0 ) { // --- must shutdown if not continuing Shutdown( cbstatus ); } return rv; } NS_IMETHODIMP nsXPInstallManager::ConfirmInstall(nsIDOMWindow *aParent, const PRUnichar **aPackageList, PRUint32 aCount, PRBool *aRetval) { *aRetval = PR_FALSE; nsCOMPtr parentWindow( do_QueryInterface(aParent) ); nsCOMPtr params; nsresult rv = LoadParams( aCount, aPackageList, getter_AddRefs(params) ); if ( NS_SUCCEEDED(rv) && parentWindow && params) { nsCOMPtr newWindow; nsCOMPtr ifptr = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); ifptr->SetData(params); ifptr->SetDataIID(&NS_GET_IID(nsIDialogParamBlock)); char* confirmDialogURL; nsCOMPtr pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); if (!pref) return rv; rv = pref->GetCharPref(PREF_XPINSTALL_CONFIRM_DLG, &confirmDialogURL); NS_ASSERTION(NS_SUCCEEDED(rv), "Can't invoke XPInstall FE without a FE URL! Set xpinstall.dialog.confirm"); if (NS_FAILED(rv)) return rv; rv = parentWindow->OpenDialog(NS_ConvertASCIItoUTF16(confirmDialogURL), NS_LITERAL_STRING("_blank"), NS_LITERAL_STRING("chrome,centerscreen,modal,titlebar"), ifptr, getter_AddRefs(newWindow)); if (NS_SUCCEEDED(rv)) { //Now get which button was pressed from the ParamBlock PRInt32 buttonPressed = 0; params->GetInt( 0, &buttonPressed ); *aRetval = buttonPressed ? PR_FALSE : PR_TRUE; } } return rv; } #ifdef ENABLE_SKIN_SIMPLE_INSTALLATION_UI PRBool nsXPInstallManager::ConfirmChromeInstall(nsIDOMWindowInternal* aParentWindow, const PRUnichar **aPackage) { // get the dialog strings nsXPIDLString applyNowText; nsXPIDLString confirmText; nsCOMPtr bundleSvc = do_GetService(NS_STRINGBUNDLE_CONTRACTID); if (!bundleSvc) return PR_FALSE; nsCOMPtr xpiBundle; bundleSvc->CreateBundle( XPINSTALL_BUNDLE_URL, getter_AddRefs(xpiBundle) ); if (!xpiBundle) return PR_FALSE; const PRUnichar *formatStrings[2] = { aPackage[0], aPackage[1] }; if ( mChromeType == CHROME_LOCALE ) { xpiBundle->GetStringFromName( NS_LITERAL_STRING("ApplyNowLocale").get(), getter_Copies(applyNowText)); xpiBundle->FormatStringFromName( NS_LITERAL_STRING("ConfirmLocale").get(), formatStrings, 2, getter_Copies(confirmText)); } else { xpiBundle->GetStringFromName( NS_LITERAL_STRING("ApplyNowSkin").get(), getter_Copies(applyNowText)); xpiBundle->FormatStringFromName( NS_LITERAL_STRING("ConfirmSkin").get(), formatStrings, 2, getter_Copies(confirmText)); } if (confirmText.IsEmpty()) return PR_FALSE; // confirmation dialog PRBool bInstall = PR_FALSE; nsCOMPtr dlgService(do_GetService(NS_PROMPTSERVICE_CONTRACTID)); if (dlgService) { dlgService->Confirm( aParentWindow, nsnull, confirmText, &bInstall ); } return bInstall; } #endif NS_IMETHODIMP nsXPInstallManager::OpenProgressDialog(const PRUnichar **aPackageList, PRUint32 aCount, nsIObserver *aObserver) { // --- convert parameters into nsISupportArray members nsCOMPtr list; nsresult rv = LoadParams( aCount, aPackageList, getter_AddRefs(list) ); if (NS_FAILED(rv)) return rv; nsCOMPtr listwrap(do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID)); if (listwrap) { listwrap->SetData(list); listwrap->SetDataIID(&NS_GET_IID(nsIDialogParamBlock)); } nsCOMPtr callbackwrap(do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID)); if (callbackwrap) { callbackwrap->SetData(aObserver); callbackwrap->SetDataIID(&NS_GET_IID(nsIObserver)); } nsCOMPtr params(do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID)); if ( !params || !listwrap || !callbackwrap ) return NS_ERROR_FAILURE; params->AppendElement(listwrap); params->AppendElement(callbackwrap); // --- open the window nsCOMPtr wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv)); if (!wwatch) return rv; char *statusDialogURL, *statusDialogType; nsCOMPtr pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); if (!pref) return rv; const char* statusDlg = mChromeType == CHROME_SKIN ? PREF_XPINSTALL_STATUS_DLG_SKIN : PREF_XPINSTALL_STATUS_DLG_CHROME; rv = pref->GetCharPref(statusDlg, &statusDialogURL); NS_ASSERTION(NS_SUCCEEDED(rv), "Can't invoke XPInstall FE without a FE URL! Set xpinstall.dialog.status"); if (NS_FAILED(rv)) return rv; const char* statusType = mChromeType == CHROME_SKIN ? PREF_XPINSTALL_STATUS_DLG_TYPE_SKIN : PREF_XPINSTALL_STATUS_DLG_TYPE_CHROME; rv = pref->GetCharPref(statusType, &statusDialogType); nsAutoString type; type.AssignWithConversion(statusDialogType); if (NS_SUCCEEDED(rv) && !type.IsEmpty()) { nsCOMPtr wm = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID); nsCOMPtr recentWindow; wm->GetMostRecentWindow(type.get(), getter_AddRefs(recentWindow)); if (recentWindow) { nsCOMPtr os(do_GetService("@mozilla.org/observer-service;1")); os->NotifyObservers(params, "xpinstall-download-started", nsnull); recentWindow->Focus(); return NS_OK; } } nsCOMPtr newWindow; rv = wwatch->OpenWindow(0, statusDialogURL, "_blank", "chrome,centerscreen,titlebar,dialog=no,resizable", params, getter_AddRefs(newWindow)); return rv; } NS_IMETHODIMP nsXPInstallManager::Observe( nsISupports *aSubject, const char *aTopic, const PRUnichar *aData ) { nsresult rv = NS_ERROR_ILLEGAL_VALUE; if ( !aTopic || !aData ) return rv; nsDependentCString topic( aTopic ); if ( topic.Equals( XPI_PROGRESS_TOPIC ) ) { //------------------------------------------------------ // Communication from the XPInstall Progress Dialog //------------------------------------------------------ nsDependentString data( aData ); if ( data.Equals( NS_LITERAL_STRING("open") ) ) { // -- The dialog has been opened if (mDialogOpen) return NS_OK; // We've already been opened, nothing more to do mDialogOpen = PR_TRUE; rv = NS_OK; nsCOMPtr os(do_GetService("@mozilla.org/observer-service;1")); if (os) { os->AddObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC, PR_TRUE); os->AddObserver(this, "quit-application", PR_TRUE); } mDlg = do_QueryInterface(aSubject); // -- get the ball rolling DownloadNext(); } else if ( data.Equals( NS_LITERAL_STRING("cancel") ) ) { // -- The dialog/user wants us to cancel the download mCancelled = PR_TRUE; if ( !mDialogOpen ) { // if we've never been opened then we can shutdown right here, // otherwise we need to let mCancelled get discovered elsewhere Shutdown(); } rv = NS_OK; } } else if ( topic.Equals( NS_IOSERVICE_GOING_OFFLINE_TOPIC ) || topic.Equals( "quit-application" ) ) { mCancelled = PR_TRUE; rv = NS_OK; } return rv; } /////////////////////////////////////////////////////////////////////////////////////////////// // Function name : VerifySigning // Description : Verify that the entire zip file is signed by the certificate displayed to // the user during download // Return type : PRInt32 // Argument : nsIZipReader* hZip - the zip reader // Argument : nsIPrincipal* aPrincipal - a principal, if any, displayed to the user // during download. Would have been retrieved from the first file in the zip /////////////////////////////////////////////////////////////////////////////////////////////// static nsresult VerifySigning(nsIZipReader* hZip, nsIPrincipal* aPrincipal) { // If we didn't detect a principal from the zip file during download then // we didn't suggest it was signed to the user, so just carry on. if (!aPrincipal) return NS_OK; PRBool hasCert; aPrincipal->GetHasCertificate(&hasCert); if (!hasCert) return NS_ERROR_FAILURE; nsCOMPtr jar(do_QueryInterface(hZip)); if (!jar) return NS_ERROR_FAILURE; // See if the archive is signed at all first nsCOMPtr principal; nsresult rv = jar->GetCertificatePrincipal(nsnull, getter_AddRefs(principal)); if (NS_FAILED(rv) || !principal) return NS_ERROR_FAILURE; PRUint32 entryCount = 0; // first verify all files in the jar are also in the manifest. nsCOMPtr entries; rv = hZip->FindEntries(nsnull, getter_AddRefs(entries)); if (NS_FAILED(rv)) return rv; PRBool more; nsCAutoString name; while (NS_SUCCEEDED(entries->HasMore(&more)) && more) { rv = entries->GetNext(name); if (NS_FAILED(rv)) return rv; // Do not verify the directory entries or // entries which are in the meta-inf directory if ((name.Last() == '/') || (PL_strncasecmp("META-INF/", name.get(), 9) == 0)) continue; // Count the entries to be verified entryCount++; // Each entry must be signed rv = jar->GetCertificatePrincipal(name.get(), getter_AddRefs(principal)); if (NS_FAILED(rv) || !principal) return NS_ERROR_FAILURE; PRBool equal; rv = principal->Equals(aPrincipal, &equal); if (NS_FAILED(rv) || !equal) return NS_ERROR_FAILURE; } // next verify all files in the manifest are in the archive. PRUint32 manifestEntryCount; rv = jar->GetManifestEntriesCount(&manifestEntryCount); if (NS_FAILED(rv)) return rv; if (entryCount != manifestEntryCount) return NS_ERROR_FAILURE; // some files were deleted from archive return NS_OK; } /////////////////////////////////////////////////////////////////////////////////////////////// // Function name : OpenAndValidateArchive // Description : Opens install archive and validates contents // Return type : PRInt32 // Argument : nsIZipReader* hZip - the zip reader // Argument : nsIFile* jarFile - the .xpi file // Argument : nsIPrincipal* aPrincipal - a principal, if any, displayed to the user // regarding the cert used to sign this install /////////////////////////////////////////////////////////////////////////////////////////////// static PRInt32 OpenAndValidateArchive(nsIZipReader* hZip, nsIFile* jarFile, nsIPrincipal* aPrincipal) { if (!jarFile) return nsInstall::DOWNLOAD_ERROR; nsCOMPtr jFile; nsresult rv =jarFile->Clone(getter_AddRefs(jFile)); if (NS_SUCCEEDED(rv)) rv = hZip->Open(jFile); if (NS_FAILED(rv)) return nsInstall::CANT_READ_ARCHIVE; // CRC check the integrity of all items in this archive rv = hZip->Test(nsnull); if (NS_FAILED(rv)) { NS_WARNING("CRC check of archive failed!"); return nsInstall::CANT_READ_ARCHIVE; } rv = VerifySigning(hZip, aPrincipal); if (NS_FAILED(rv)) { NS_WARNING("Signing check of archive failed!"); return nsInstall::INVALID_SIGNATURE; } if (NS_FAILED(hZip->Test("install.rdf"))) { NS_WARNING("Archive did not contain an install manifest!"); return nsInstall::NO_INSTALL_SCRIPT; } return nsInstall::SUCCESS; } nsresult nsXPInstallManager::InstallItems() { nsresult rv; nsCOMPtr hZip = do_CreateInstance(kZipReaderCID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr em = do_GetService("@mozilla.org/extensions/manager;1", &rv); NS_ENSURE_SUCCESS(rv, rv); // can't cancel from here on cause we can't undo installs in a multitrigger for (PRUint32 i = 0; i < mTriggers->Size(); ++i) { mItem = (nsXPITriggerItem*)mTriggers->Get(i); if ( !mItem || !mItem->mFile ) { // notification for these errors already handled continue; } // If there was hash info in the trigger, but // there wasn't a hash object created, then the // algorithm used isn't known. if (mItem->mHashFound && !mItem->mHasher) { // report failure mTriggers->SendStatus( mItem->mURL.get(), nsInstall::INVALID_HASH_TYPE ); if (mDlg) mDlg->OnStateChange( i, nsIXPIProgressDialog::INSTALL_DONE, nsInstall::INVALID_HASH_TYPE ); continue; } // Don't install if we can't verify the hash (if specified) if (mItem->mHasher && !VerifyHash(mItem)) { // report failure mTriggers->SendStatus( mItem->mURL.get(), nsInstall::INVALID_HASH ); if (mDlg) mDlg->OnStateChange( i, nsIXPIProgressDialog::INSTALL_DONE, nsInstall::INVALID_HASH ); continue; } if (mDlg) mDlg->OnStateChange( i, nsIXPIProgressDialog::INSTALL_START, 0 ); PRInt32 finalStatus = OpenAndValidateArchive( hZip, mItem->mFile, mItem->mPrincipal); hZip->Close(); if (finalStatus == nsInstall::SUCCESS) { rv = em->InstallItemFromFile( mItem->mFile, NS_INSTALL_LOCATION_APPPROFILE); if (NS_FAILED(rv)) finalStatus = nsInstall::EXECUTION_ERROR; } mTriggers->SendStatus( mItem->mURL.get(), finalStatus ); if (mDlg) mDlg->OnStateChange( i, nsIXPIProgressDialog::INSTALL_DONE, finalStatus ); } return NS_OK; } NS_IMETHODIMP nsXPInstallManager::DownloadNext() { nsresult rv; mContentLength = 0; if (mCancelled) { // Don't download any more if we were cancelled Shutdown(); return NS_OK; } if ( mNextItem < mTriggers->Size() ) { //------------------------------------------------- // There are items to download, get the next one //------------------------------------------------- mItem = (nsXPITriggerItem*)mTriggers->Get(mNextItem++); NS_ASSERTION( mItem, "bogus Trigger slipped through" ); NS_ASSERTION( !mItem->mURL.IsEmpty(), "bogus trigger"); if ( !mItem || mItem->mURL.IsEmpty() ) { // serious problem with trigger! Can't notify anyone of the // error without the URL, just try to carry on. return DownloadNext(); } // --- Tell the dialog we're starting a download if (mDlg) mDlg->OnStateChange( mNextItem-1, nsIXPIProgressDialog::DOWNLOAD_START, 0 ); if ( mItem->IsFileURL() && mChromeType == NOT_CHROME ) { //-------------------------------------------------- // Already local, we can open it where it is //-------------------------------------------------- nsCOMPtr pURL; rv = NS_NewURI(getter_AddRefs(pURL), mItem->mURL); if (NS_SUCCEEDED(rv)) { nsCOMPtr fileURL = do_QueryInterface(pURL,&rv); if (fileURL) { nsCOMPtr localFile; rv = fileURL->GetFile(getter_AddRefs(localFile)); if (NS_SUCCEEDED(rv)) { mItem->mFile = do_QueryInterface(localFile,&rv); } } } if ( NS_FAILED(rv) || !mItem->mFile ) { // send error status back if (mDlg) mDlg->OnStateChange( mNextItem-1, nsIXPIProgressDialog::INSTALL_DONE, nsInstall::UNEXPECTED_ERROR ); mTriggers->SendStatus( mItem->mURL.get(), nsInstall::UNEXPECTED_ERROR ); mItem->mFile = 0; } else if (mDlg) { mDlg->OnStateChange( mNextItem-1, nsIXPIProgressDialog::DOWNLOAD_DONE, 0); } // --- on to the next one return DownloadNext(); } else { //-------------------------------------------------- // We have one to download //-------------------------------------------------- rv = GetDestinationFile(mItem->mURL, getter_AddRefs(mItem->mFile)); if (NS_SUCCEEDED(rv)) { nsCOMPtr pURL; rv = NS_NewURI(getter_AddRefs(pURL), mItem->mURL); if (NS_SUCCEEDED(rv)) { nsCOMPtr channel; rv = NS_NewChannel(getter_AddRefs(channel), pURL, nsnull, mLoadGroup, this); if (NS_SUCCEEDED(rv)) { rv = channel->AsyncOpen(this, nsnull); } } } if (NS_FAILED(rv)) { // announce failure if (mDlg) mDlg->OnStateChange( mNextItem-1, nsIXPIProgressDialog::INSTALL_DONE, nsInstall::DOWNLOAD_ERROR ); mTriggers->SendStatus( mItem->mURL.get(), nsInstall::DOWNLOAD_ERROR ); mItem->mFile = 0; // We won't get Necko callbacks so start the next one now return DownloadNext(); } } } else { //------------------------------------------------------ // all downloaded, install them //------------------------------------------------------ InstallItems(); Shutdown(); } return rv; } //------------------------------------------------------------------- // VerifyHash // // Returns true if the file hash matches the expected value (or if // the item has no hash value). False if we can't verify the hash // for any reason // PRBool nsXPInstallManager::VerifyHash(nsXPITriggerItem* aItem) { NS_ASSERTION(aItem, "Null nsXPITriggerItem passed to VerifyHash"); nsresult rv; if (!aItem->mHasher) return PR_FALSE; nsCOMPtr stream; rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), aItem->mFile); if (NS_FAILED(rv)) return PR_FALSE; rv = aItem->mHasher->UpdateFromStream(stream, PR_UINT32_MAX); if (NS_FAILED(rv)) return PR_FALSE; nsCAutoString binaryHash; rv = aItem->mHasher->Finish(PR_FALSE, binaryHash); if (NS_FAILED(rv)) return PR_FALSE; char* hash = nsnull; for (PRUint32 i=0; i < binaryHash.Length(); ++i) { hash = PR_sprintf_append(hash,"%.2x", (PRUint8)binaryHash[i]); } PRBool result = aItem->mHash.EqualsIgnoreCase(hash); PR_smprintf_free(hash); return result; } void nsXPInstallManager::Shutdown(PRInt32 status) { if (mDlg) { // tell the dialog it can go away mDlg->OnStateChange(0, nsIXPIProgressDialog::DIALOG_CLOSE, 0 ); mDlg = nsnull; } if (mNeedsShutdown) { mNeedsShutdown = PR_FALSE; // Send remaining status notifications if we were cancelled early nsXPITriggerItem* item; while ( mNextItem < mTriggers->Size() ) { item = (nsXPITriggerItem*)mTriggers->Get(mNextItem++); if ( item && !item->mURL.IsEmpty() ) { mTriggers->SendStatus( item->mURL.get(), status ); } } // Clean up downloaded files (regular install only, not chrome installs) for (PRUint32 i = 0; i < mTriggers->Size(); i++ ) { item = static_cast(mTriggers->Get(i)); if ( item && item->mFile && !item->IsFileURL() ) item->mFile->Remove(PR_FALSE); } nsCOMPtr os(do_GetService("@mozilla.org/observer-service;1")); if (os) { os->RemoveObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC); os->RemoveObserver(this, "quit-application"); } if (mTriggers) { delete mTriggers; mTriggers = nsnull; } NS_RELEASE_THIS(); } } NS_IMETHODIMP nsXPInstallManager::LoadParams(PRUint32 aCount, const PRUnichar** aPackageList, nsIDialogParamBlock** aParams) { nsresult rv; nsCOMPtr paramBlock = do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID, &rv); if (NS_SUCCEEDED(rv)) { // set OK and Cancel buttons paramBlock->SetInt( 0, 2 ); // pass in number of strings paramBlock->SetInt( 1, aCount ); // add strings paramBlock->SetNumberStrings( aCount ); for (PRUint32 i = 0; i < aCount; i++) paramBlock->SetString( i, aPackageList[i] ); } NS_IF_ADDREF(*aParams = paramBlock); return rv; } NS_IMETHODIMP nsXPInstallManager::GetDestinationFile(nsString& url, nsILocalFile* *file) { NS_ENSURE_ARG_POINTER(file); nsresult rv; nsCOMPtr directoryService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr temp; rv = directoryService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsIFile), getter_AddRefs(temp)); NS_ENSURE_SUCCESS(rv, rv); temp->AppendNative(NS_LITERAL_CSTRING("tmp.xpi")); temp->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); *file = temp; NS_IF_ADDREF(*file); return NS_OK; } nsresult nsXPInstallManager::CheckCert(nsIChannel* aChannel) { nsCOMPtr uri; nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); nsCAutoString scheme; rv = uri->GetScheme(scheme); NS_ENSURE_SUCCESS(rv, rv); if (!scheme.Equals(NS_LITERAL_CSTRING("https"))) return NS_OK; nsCOMPtr security; rv = aChannel->GetSecurityInfo(getter_AddRefs(security)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr statusProvider(do_QueryInterface(security)); NS_ENSURE_TRUE(statusProvider, NS_ERROR_FAILURE); rv = statusProvider->GetSSLStatus(getter_AddRefs(security)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr status(do_QueryInterface(security)); NS_ENSURE_TRUE(status, NS_ERROR_FAILURE); nsCOMPtr cert; rv = status->GetServerCert(getter_AddRefs(cert)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr issuer; rv = cert->GetIssuer(getter_AddRefs(issuer)); NS_ENSURE_SUCCESS(rv, rv); PRBool equal; while (issuer && NS_SUCCEEDED(cert->Equals(issuer, &equal)) && !equal) { cert = issuer; rv = cert->GetIssuer(getter_AddRefs(issuer)); NS_ENSURE_SUCCESS(rv, rv); } if (issuer) { nsAutoString tokenName; rv = issuer->GetTokenName(tokenName); NS_ENSURE_SUCCESS(rv ,rv); if (tokenName.Equals(NS_LITERAL_STRING("Builtin Object Token"))) return NS_OK; } return NS_ERROR_FAILURE; } NS_IMETHODIMP nsXPInstallManager::OnStartRequest(nsIRequest* request, nsISupports *ctxt) { nsresult rv = NS_ERROR_FAILURE; // If we are dealing with a HTTP request, then treat HTTP error pages as // download failures. nsCOMPtr httpChan = do_QueryInterface(request); if (httpChan) { // If we were chrome lauched check the certificate on the request if (mFromChrome && NS_FAILED(CheckCert(httpChan))) { request->Cancel(NS_BINDING_ABORTED); return NS_OK; } PRBool succeeded; if (NS_SUCCEEDED(httpChan->GetRequestSucceeded(&succeeded)) && !succeeded) { // HTTP response is not a 2xx! request->Cancel(NS_BINDING_ABORTED); return NS_OK; } } if (mLoadGroup) mLoadGroup->RemoveRequest(request, nsnull, NS_BINDING_RETARGETED); NS_ASSERTION( mItem && mItem->mFile, "XPIMgr::OnStartRequest bad state"); if ( mItem && mItem->mFile ) { NS_ASSERTION( !mItem->mOutStream, "Received double OnStartRequest from Necko"); rv = NS_NewLocalFileOutputStream(getter_AddRefs(mItem->mOutStream), mItem->mFile, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0600); } return rv; } NS_IMETHODIMP nsXPInstallManager::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status) { nsresult rv; switch( status ) { case NS_BINDING_SUCCEEDED: rv = NS_OK; break; case NS_BINDING_FAILED: case NS_BINDING_ABORTED: rv = status; // XXX need to note failure, both to send back status // to the callback, and also so we don't try to install // this probably corrupt file. break; default: rv = NS_ERROR_ILLEGAL_VALUE; } NS_ASSERTION( mItem, "Bad state in XPIManager"); NS_ASSERTION( mItem->mOutStream, "XPIManager: output stream doesn't exist"); if ( mItem && mItem->mOutStream ) { mItem->mOutStream->Close(); mItem->mOutStream = nsnull; } if (NS_FAILED(rv) || mCancelled) { // Download error! // -- first clean up partially downloaded file if ( mItem->mFile ) { PRBool flagExists; nsresult rv2 ; rv2 = mItem->mFile->Exists(&flagExists); if (NS_SUCCEEDED(rv2) && flagExists) mItem->mFile->Remove(PR_FALSE); mItem->mFile = 0; } // -- then notify interested parties PRInt32 errorcode = mCancelled ? nsInstall::USER_CANCELLED : nsInstall::DOWNLOAD_ERROR; if (mDlg) mDlg->OnStateChange( mNextItem-1, nsIXPIProgressDialog::INSTALL_DONE, errorcode ); mTriggers->SendStatus( mItem->mURL.get(), errorcode ); } else if (mDlg) { mDlg->OnStateChange( mNextItem-1, nsIXPIProgressDialog::DOWNLOAD_DONE, 0); } DownloadNext(); return rv; } NS_IMETHODIMP nsXPInstallManager::OnDataAvailable(nsIRequest* request, nsISupports *ctxt, nsIInputStream *pIStream, PRUint32 sourceOffset, PRUint32 length) { #define XPI_ODA_BUFFER_SIZE 8*1024 PRUint32 amt = PR_MIN(XPI_ODA_BUFFER_SIZE, length); nsresult err; char buffer[XPI_ODA_BUFFER_SIZE]; PRUint32 writeCount; if (mCancelled) { // We must cancel this download in progress. We may get extra // OnData calls if they were already queued so beware request->Cancel(NS_BINDING_ABORTED); return NS_ERROR_FAILURE; } do { err = pIStream->Read(buffer, amt, &amt); if (amt == 0) break; if (NS_FAILED(err)) return err; err = mItem->mOutStream->Write( buffer, amt, &writeCount); if (NS_FAILED(err) || writeCount != amt) { return NS_ERROR_FAILURE; } length -= amt; amt = PR_MIN(XPI_ODA_BUFFER_SIZE, length); } while (length > 0); return NS_OK; } NS_IMETHODIMP nsXPInstallManager::OnProgress(nsIRequest* request, nsISupports *ctxt, PRUint64 aProgress, PRUint64 aProgressMax) { nsresult rv = NS_OK; if (mDlg && !mCancelled) { if (mContentLength < 1) { nsCOMPtr channel = do_QueryInterface(request,&rv); NS_ASSERTION(channel, "should have a channel"); if (NS_FAILED(rv)) return rv; rv = channel->GetContentLength(&mContentLength); if (NS_FAILED(rv)) return rv; } // XXX once channels support that, use 64-bit contentlength rv = mDlg->OnProgress( mNextItem-1, aProgress, nsUint64(mContentLength) ); } return rv; } NS_IMETHODIMP nsXPInstallManager::OnStatus(nsIRequest* request, nsISupports *ctxt, nsresult aStatus, const PRUnichar *aStatusArg) { // don't need to do anything return NS_OK; } // nsIInterfaceRequestor method NS_IMETHODIMP nsXPInstallManager::GetInterface(const nsIID & eventSinkIID, void* *_retval) { if (eventSinkIID.Equals(NS_GET_IID(nsIAuthPrompt))) { *_retval = nsnull; nsresult rv; nsCOMPtr ww(do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr prompt; rv = ww->GetNewAuthPrompter(nsnull, getter_AddRefs(prompt)); NS_ENSURE_SUCCESS(rv, rv); nsIAuthPrompt *p = prompt.get(); NS_ADDREF(p); *_retval = p; return NS_OK; } else if (eventSinkIID.Equals(NS_GET_IID(nsIBadCertListener2))) { // If we aren't chrome triggered fall back to the default dialogs if (!mFromChrome) return NS_ERROR_NO_INTERFACE; } return QueryInterface(eventSinkIID, (void**)_retval); } // nsIChannelEventSink method NS_IMETHODIMP nsXPInstallManager::OnChannelRedirect(nsIChannel *oldChannel, nsIChannel *newChannel, PRUint32 flags) { // Chrome triggered installs need to have their certificates checked if (mFromChrome) return CheckCert(oldChannel); return NS_OK; } // nsIBadCertListener2 methods NS_IMETHODIMP nsXPInstallManager::NotifyCertProblem(nsIInterfaceRequestor *socketInfo, nsISSLStatus *status, const nsACString &targetSite, PRBool *_retval) { *_retval = PR_TRUE; return NS_OK; } // nsISSLErrorListener methods NS_IMETHODIMP nsXPInstallManager::NotifySSLError(nsIInterfaceRequestor *socketInfo, PRInt32 error, const nsACString &targetSite, PRBool *_retval) { *_retval = PR_TRUE; return NS_OK; } NS_IMETHODIMP nsXPInstallManager::OnCertAvailable(nsIURI *aURI, nsISupports* context, nsresult aStatus, nsIPrincipal *aPrincipal) { if (NS_FAILED(aStatus) && aStatus != NS_BINDING_ABORTED) { // Check for a bad status. The only acceptable failure status code we accept // is NS_BINDING_ABORTED. For all others we want to ensure that the // nsIPrincipal is nsnull. NS_ASSERTION(aPrincipal == nsnull, "There has been an error, but we have a principal!"); aPrincipal = nsnull; } // get the current one and assign the cert name nsXPITriggerItem *item = mTriggers->Get(mOutstandingCertLoads); item->SetPrincipal(aPrincipal); if (mOutstandingCertLoads == 0) { InitManagerInternal(); return NS_OK; } // get the next one to load. If there is any failure, we just go on to the // next trigger. When all triggers items are handled, we call into InitManagerInternal item = mTriggers->Get(--mOutstandingCertLoads); nsCOMPtr uri; NS_NewURI(getter_AddRefs(uri), NS_ConvertUTF16toUTF8(item->mURL.get()).get()); if (!uri || mChromeType != NOT_CHROME) return OnCertAvailable(uri, context, NS_ERROR_FAILURE, nsnull); nsIStreamListener* listener = new CertReader(uri, nsnull, this); if (!listener) return OnCertAvailable(uri, context, NS_ERROR_FAILURE, nsnull); NS_ADDREF(listener); nsresult rv = NS_OpenURI(listener, nsnull, uri, nsnull, mLoadGroup); NS_ASSERTION(NS_SUCCEEDED(rv), "OpenURI failed"); NS_RELEASE(listener); if (NS_FAILED(rv)) return OnCertAvailable(uri, context, NS_ERROR_FAILURE, nsnull); return NS_OK; }