/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* nsPluginHost.cpp - top-level plugin management code */ #include "nscore.h" #include "nsPluginHost.h" #include #include #include "prio.h" #include "prmem.h" #include "nsNPAPIPlugin.h" #include "nsNPAPIPluginStreamListener.h" #include "nsNPAPIPluginInstance.h" #include "nsPluginInstanceOwner.h" #include "nsObjectLoadingContent.h" #include "nsIHTTPHeaderListener.h" #include "nsIHttpHeaderVisitor.h" #include "nsIObserverService.h" #include "nsIHttpProtocolHandler.h" #include "nsIHttpChannel.h" #include "nsIUploadChannel.h" #include "nsIByteRangeRequest.h" #include "nsIStreamListener.h" #include "nsIInputStream.h" #include "nsIOutputStream.h" #include "nsIURL.h" #include "nsTArray.h" #include "nsReadableUtils.h" #include "nsIProtocolProxyService2.h" #include "nsIStreamConverterService.h" #include "nsIFile.h" #if defined(XP_MACOSX) #include "nsILocalFileMac.h" #endif #include "nsISeekableStream.h" #include "nsNetUtil.h" #include "nsIProgressEventSink.h" #include "nsIDocument.h" #include "nsHashtable.h" #include "nsPluginLogging.h" #include "nsIScriptChannel.h" #include "nsIBlocklistService.h" #include "nsVersionComparator.h" #include "nsIObjectLoadingContent.h" #include "nsIWritablePropertyBag2.h" #include "nsICategoryManager.h" #include "nsPluginStreamListenerPeer.h" #include "mozilla/Preferences.h" #include "nsEnumeratorUtils.h" #include "nsXPCOM.h" #include "nsXPCOMCID.h" #include "nsISupportsPrimitives.h" #include "nsXULAppAPI.h" #include "nsIXULRuntime.h" // for the dialog #include "nsIWindowWatcher.h" #include "nsIDOMElement.h" #include "nsIDOMWindow.h" #include "nsNetCID.h" #include "prprf.h" #include "nsThreadUtils.h" #include "nsIInputStreamTee.h" #include "nsDirectoryServiceDefs.h" #include "nsAppDirectoryServiceDefs.h" #include "nsPluginDirServiceProvider.h" #include "nsUnicharUtils.h" #include "nsPluginManifestLineReader.h" #include "nsIWeakReferenceUtils.h" #include "nsIPresShell.h" #include "nsPluginNativeWindow.h" #include "nsIScriptSecurityManager.h" #include "nsIContentPolicy.h" #include "nsContentPolicyUtils.h" #include "mozilla/TimeStamp.h" #include "mozilla/Telemetry.h" #include "nsIImageLoadingContent.h" #include "mozilla/Preferences.h" #include "nsVersionComparator.h" #if defined(XP_WIN) #include "nsIWindowMediator.h" #include "nsIBaseWindow.h" #include "windows.h" #include "winbase.h" #endif #ifdef ANDROID #include #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) #endif #if MOZ_CRASHREPORTER #include "nsExceptionHandler.h" #endif using namespace mozilla; using mozilla::TimeStamp; // Null out a strong ref to a linked list iteratively to avoid // exhausting the stack (bug 486349). #define NS_ITERATIVE_UNREF_LIST(type_, list_, mNext_) \ { \ while (list_) { \ type_ temp = list_->mNext_; \ list_->mNext_ = nullptr; \ list_ = temp; \ } \ } // this is the name of the directory which will be created // to cache temporary files. #define kPluginTmpDirName NS_LITERAL_CSTRING("plugtmp") static const char *kPrefWhitelist = "plugin.allowed_types"; static const char *kPrefDisableFullPage = "plugin.disable_full_page_plugin_for_types"; static const char *kPrefJavaMIME = "plugin.java.mime"; // Version of cached plugin info // 0.01 first implementation // 0.02 added caching of CanUnload to fix bug 105935 // 0.03 changed name, description and mime desc from string to bytes, bug 108246 // 0.04 added new mime entry point on Mac, bug 113464 // 0.05 added new entry point check for the default plugin, bug 132430 // 0.06 strip off suffixes in mime description strings, bug 53895 // 0.07 changed nsIRegistry to flat file support for caching plugins info // 0.08 mime entry point on MachO, bug 137535 // 0.09 the file encoding is changed to UTF-8, bug 420285 // 0.10 added plugin versions on appropriate platforms, bug 427743 // 0.11 file name and full path fields now store expected values on all platforms, bug 488181 // 0.12 force refresh due to quicktime pdf claim fix, bug 611197 // 0.13 add architecture and list of invalid plugins, bug 616271 // 0.14 force refresh due to locale comparison fix, bug 611296 // 0.15 force refresh due to bug in reading Java plist MIME data, bug 638171 // 0.16 version bump to avoid importing the plugin flags in newer versions // The current plugin registry version (and the maximum version we know how to read) static const char *kPluginRegistryVersion = "0.16"; // The minimum registry version we know how to read static const char *kMinimumRegistryVersion = "0.9"; static const char kDirectoryServiceContractID[] = "@mozilla.org/file/directory_service;1"; #define kPluginRegistryFilename NS_LITERAL_CSTRING("pluginreg.dat") #ifdef PLUGIN_LOGGING PRLogModuleInfo* nsPluginLogging::gNPNLog = nullptr; PRLogModuleInfo* nsPluginLogging::gNPPLog = nullptr; PRLogModuleInfo* nsPluginLogging::gPluginLog = nullptr; #endif // #defines for plugin cache and prefs #define NS_PREF_MAX_NUM_CACHED_INSTANCES "browser.plugins.max_num_cached_plugins" // Raise this from '10' to '50' to work around a bug in Apple's current Java // plugins on OS X Lion and SnowLeopard. See bug 705931. #define DEFAULT_NUMBER_OF_STOPPED_INSTANCES 50 nsIFile *nsPluginHost::sPluginTempDir; nsPluginHost *nsPluginHost::sInst; NS_IMPL_ISUPPORTS0(nsInvalidPluginTag) nsInvalidPluginTag::nsInvalidPluginTag(const char* aFullPath, int64_t aLastModifiedTime) : mFullPath(aFullPath), mLastModifiedTime(aLastModifiedTime), mSeen(false) {} nsInvalidPluginTag::~nsInvalidPluginTag() {} // Helper to check for a MIME in a comma-delimited preference static bool IsTypeInList(nsCString &aMimeType, nsCString aTypeList) { nsAutoCString searchStr; searchStr.Assign(','); searchStr.Append(aTypeList); searchStr.Append(','); nsACString::const_iterator start, end; searchStr.BeginReading(start); searchStr.EndReading(end); nsAutoCString commaSeparated; commaSeparated.Assign(','); commaSeparated += aMimeType; commaSeparated.Append(','); return FindInReadable(commaSeparated, start, end); } // flat file reg funcs static bool ReadSectionHeader(nsPluginManifestLineReader& reader, const char *token) { do { if (*reader.LinePtr() == '[') { char* p = reader.LinePtr() + (reader.LineLength() - 1); if (*p != ']') break; *p = 0; char* values[1]; if (1 != reader.ParseLine(values, 1)) break; // ignore the leading '[' if (PL_strcmp(values[0]+1, token)) { break; // it's wrong token } return true; } } while (reader.NextLine()); return false; } static bool UnloadPluginsASAP() { return Preferences::GetBool("dom.ipc.plugins.unloadASAP", false); } nsPluginHost::nsPluginHost() // No need to initialize members to nullptr, false etc because this class // has a zeroing operator new. { // check to see if pref is set at startup to let plugins take over in // full page mode for certain image mime types that we handle internally mOverrideInternalTypes = Preferences::GetBool("plugin.override_internal_types", false); mPluginsDisabled = Preferences::GetBool("plugin.disable", false); mPluginsClickToPlay = Preferences::GetBool("plugins.click_to_play", false); Preferences::AddStrongObserver(this, "plugin.disable"); Preferences::AddStrongObserver(this, "plugins.click_to_play"); nsCOMPtr obsService = mozilla::services::GetObserverService(); if (obsService) { obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); obsService->AddObserver(this, "blocklist-updated", false); #ifdef MOZ_WIDGET_ANDROID obsService->AddObserver(this, "application-foreground", false); obsService->AddObserver(this, "application-background", false); #endif } #ifdef PLUGIN_LOGGING nsPluginLogging::gNPNLog = PR_NewLogModule(NPN_LOG_NAME); nsPluginLogging::gNPPLog = PR_NewLogModule(NPP_LOG_NAME); nsPluginLogging::gPluginLog = PR_NewLogModule(PLUGIN_LOG_NAME); PR_LOG(nsPluginLogging::gNPNLog, PLUGIN_LOG_ALWAYS,("NPN Logging Active!\n")); PR_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_ALWAYS,("General Plugin Logging Active! (nsPluginHost::ctor)\n")); PR_LOG(nsPluginLogging::gNPPLog, PLUGIN_LOG_ALWAYS,("NPP Logging Active!\n")); PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("nsPluginHost::ctor\n")); PR_LogFlush(); #endif } nsPluginHost::~nsPluginHost() { PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("nsPluginHost::dtor\n")); UnloadPlugins(); sInst = nullptr; } NS_IMPL_ISUPPORTS4(nsPluginHost, nsIPluginHost, nsIObserver, nsITimerCallback, nsISupportsWeakReference) already_AddRefed nsPluginHost::GetInst() { if (!sInst) { sInst = new nsPluginHost(); if (!sInst) return nullptr; NS_ADDREF(sInst); } nsRefPtr inst = sInst; return inst.forget(); } bool nsPluginHost::IsRunningPlugin(nsPluginTag * aPluginTag) { if (!aPluginTag || !aPluginTag->mPlugin) { return false; } for (uint32_t i = 0; i < mInstances.Length(); i++) { nsNPAPIPluginInstance *instance = mInstances[i].get(); if (instance && instance->GetPlugin() == aPluginTag->mPlugin && instance->IsRunning()) { return true; } } return false; } nsresult nsPluginHost::ReloadPlugins() { PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsPluginHost::ReloadPlugins Begin\n")); nsresult rv = NS_OK; // this will create the initial plugin list out of cache // if it was not created yet if (!mPluginsLoaded) return LoadPlugins(); // we are re-scanning plugins. New plugins may have been added, also some // plugins may have been removed, so we should probably shut everything down // but don't touch running (active and not stopped) plugins // check if plugins changed, no need to do anything else // if no changes to plugins have been made // false instructs not to touch the plugin list, just to // look for possible changes bool pluginschanged = true; FindPlugins(false, &pluginschanged); // if no changed detected, return an appropriate error code if (!pluginschanged) return NS_ERROR_PLUGINS_PLUGINSNOTCHANGED; // shutdown plugins and kill the list if there are no running plugins nsRefPtr prev; nsRefPtr next; for (nsRefPtr p = mPlugins; p != nullptr;) { next = p->mNext; // only remove our plugin from the list if it's not running. if (!IsRunningPlugin(p)) { if (p == mPlugins) mPlugins = next; else prev->mNext = next; p->mNext = nullptr; // attempt to unload plugins whenever they are removed from the list p->TryUnloadPlugin(false); p = next; continue; } prev = p; p = next; } // set flags mPluginsLoaded = false; // load them again rv = LoadPlugins(); PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsPluginHost::ReloadPlugins End\n")); return rv; } #define NS_RETURN_UASTRING_SIZE 128 nsresult nsPluginHost::UserAgent(const char **retstring) { static char resultString[NS_RETURN_UASTRING_SIZE]; nsresult res; nsCOMPtr http = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &res); if (NS_FAILED(res)) return res; nsAutoCString uaString; res = http->GetUserAgent(uaString); if (NS_SUCCEEDED(res)) { if (NS_RETURN_UASTRING_SIZE > uaString.Length()) { PL_strcpy(resultString, uaString.get()); } else { // Copy as much of UA string as we can (terminate at right-most space). PL_strncpy(resultString, uaString.get(), NS_RETURN_UASTRING_SIZE); for (int i = NS_RETURN_UASTRING_SIZE - 1; i >= 0; i--) { if (i == 0) { resultString[NS_RETURN_UASTRING_SIZE - 1] = '\0'; } else if (resultString[i] == ' ') { resultString[i] = '\0'; break; } } } *retstring = resultString; } else { *retstring = nullptr; } PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsPluginHost::UserAgent return=%s\n", *retstring)); return res; } nsresult nsPluginHost::GetURL(nsISupports* pluginInst, const char* url, const char* target, nsNPAPIPluginStreamListener* streamListener, const char* altHost, const char* referrer, bool forceJSEnabled) { return GetURLWithHeaders(static_cast(pluginInst), url, target, streamListener, altHost, referrer, forceJSEnabled, 0, nullptr); } nsresult nsPluginHost::GetURLWithHeaders(nsNPAPIPluginInstance* pluginInst, const char* url, const char* target, nsNPAPIPluginStreamListener* streamListener, const char* altHost, const char* referrer, bool forceJSEnabled, uint32_t getHeadersLength, const char* getHeaders) { // we can only send a stream back to the plugin (as specified by a // null target) if we also have a nsNPAPIPluginStreamListener to talk to if (!target && !streamListener) return NS_ERROR_ILLEGAL_VALUE; nsresult rv = DoURLLoadSecurityCheck(pluginInst, url); if (NS_FAILED(rv)) return rv; if (target) { nsRefPtr owner = pluginInst->GetOwner(); if (owner) { if ((0 == PL_strcmp(target, "newwindow")) || (0 == PL_strcmp(target, "_new"))) target = "_blank"; else if (0 == PL_strcmp(target, "_current")) target = "_self"; rv = owner->GetURL(url, target, nullptr, nullptr, 0); } } if (streamListener) rv = NewPluginURLStream(NS_ConvertUTF8toUTF16(url), pluginInst, streamListener, nullptr, getHeaders, getHeadersLength); return rv; } nsresult nsPluginHost::PostURL(nsISupports* pluginInst, const char* url, uint32_t postDataLen, const char* postData, bool isFile, const char* target, nsNPAPIPluginStreamListener* streamListener, const char* altHost, const char* referrer, bool forceJSEnabled, uint32_t postHeadersLength, const char* postHeaders) { nsresult rv; // we can only send a stream back to the plugin (as specified // by a null target) if we also have a nsNPAPIPluginStreamListener // to talk to also if (!target && !streamListener) return NS_ERROR_ILLEGAL_VALUE; nsNPAPIPluginInstance* instance = static_cast(pluginInst); rv = DoURLLoadSecurityCheck(instance, url); if (NS_FAILED(rv)) return rv; nsCOMPtr postStream; if (isFile) { nsCOMPtr file; rv = CreateTempFileToPost(postData, getter_AddRefs(file)); if (NS_FAILED(rv)) return rv; nsCOMPtr fileStream; rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), file, PR_RDONLY, 0600, nsIFileInputStream::DELETE_ON_CLOSE | nsIFileInputStream::CLOSE_ON_EOF); if (NS_FAILED(rv)) return rv; rv = NS_NewBufferedInputStream(getter_AddRefs(postStream), fileStream, 8192); if (NS_FAILED(rv)) return rv; } else { char *dataToPost; uint32_t newDataToPostLen; ParsePostBufferToFixHeaders(postData, postDataLen, &dataToPost, &newDataToPostLen); if (!dataToPost) return NS_ERROR_UNEXPECTED; nsCOMPtr sis = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); if (!sis) { NS_Free(dataToPost); return rv; } // data allocated by ParsePostBufferToFixHeaders() is managed and // freed by the string stream. postDataLen = newDataToPostLen; sis->AdoptData(dataToPost, postDataLen); postStream = sis; } if (target) { nsRefPtr owner = instance->GetOwner(); if (owner) { if ((0 == PL_strcmp(target, "newwindow")) || (0 == PL_strcmp(target, "_new"))) { target = "_blank"; } else if (0 == PL_strcmp(target, "_current")) { target = "_self"; } rv = owner->GetURL(url, target, postStream, (void*)postHeaders, postHeadersLength); } } // if we don't have a target, just create a stream. This does // NS_OpenURI()! if (streamListener) rv = NewPluginURLStream(NS_ConvertUTF8toUTF16(url), instance, streamListener, postStream, postHeaders, postHeadersLength); return rv; } /* This method queries the prefs for proxy information. * It has been tested and is known to work in the following three cases * when no proxy host or port is specified * when only the proxy host is specified * when only the proxy port is specified * This method conforms to the return code specified in * http://developer.netscape.com/docs/manuals/proxy/adminnt/autoconf.htm#1020923 * with the exception that multiple values are not implemented. */ nsresult nsPluginHost::FindProxyForURL(const char* url, char* *result) { if (!url || !result) { return NS_ERROR_INVALID_ARG; } nsresult res; nsCOMPtr uriIn; nsCOMPtr proxyService; nsCOMPtr proxyService2; nsCOMPtr ioService; proxyService = do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &res); if (NS_FAILED(res) || !proxyService) return res; proxyService2 = do_QueryInterface(proxyService, &res); if (NS_FAILED(res) || !proxyService2) return res; ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &res); if (NS_FAILED(res) || !ioService) return res; // make an nsURI from the argument url res = ioService->NewURI(nsDependentCString(url), nullptr, nullptr, getter_AddRefs(uriIn)); if (NS_FAILED(res)) return res; nsCOMPtr pi; // Remove this with bug 778201 res = proxyService2->DeprecatedBlockingResolve(uriIn, 0, getter_AddRefs(pi)); if (NS_FAILED(res)) return res; nsAutoCString host, type; int32_t port = -1; // These won't fail, and even if they do... we'll be ok. if (pi) { pi->GetType(type); pi->GetHost(host); pi->GetPort(&port); } if (!pi || host.IsEmpty() || port <= 0 || host.EqualsLiteral("direct")) { *result = PL_strdup("DIRECT"); } else if (type.EqualsLiteral("http")) { *result = PR_smprintf("PROXY %s:%d", host.get(), port); } else if (type.EqualsLiteral("socks4")) { *result = PR_smprintf("SOCKS %s:%d", host.get(), port); } else if (type.EqualsLiteral("socks")) { // XXX - this is socks5, but there is no API for us to tell the // plugin that fact. SOCKS for now, in case the proxy server // speaks SOCKS4 as well. See bug 78176 // For a long time this was returning an http proxy type, so // very little is probably broken by this *result = PR_smprintf("SOCKS %s:%d", host.get(), port); } else { NS_ASSERTION(false, "Unknown proxy type!"); *result = PL_strdup("DIRECT"); } if (nullptr == *result) res = NS_ERROR_OUT_OF_MEMORY; return res; } nsresult nsPluginHost::Init() { return NS_OK; } nsresult nsPluginHost::UnloadPlugins() { PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsPluginHost::UnloadPlugins Called\n")); if (!mPluginsLoaded) return NS_OK; // we should call nsIPluginInstance::Stop and nsIPluginInstance::SetWindow // for those plugins who want it DestroyRunningInstances(nullptr); nsPluginTag *pluginTag; for (pluginTag = mPlugins; pluginTag; pluginTag = pluginTag->mNext) { pluginTag->TryUnloadPlugin(true); } NS_ITERATIVE_UNREF_LIST(nsRefPtr, mPlugins, mNext); NS_ITERATIVE_UNREF_LIST(nsRefPtr, mCachedPlugins, mNext); NS_ITERATIVE_UNREF_LIST(nsRefPtr, mInvalidPlugins, mNext); // Lets remove any of the temporary files that we created. if (sPluginTempDir) { sPluginTempDir->Remove(true); NS_RELEASE(sPluginTempDir); } #ifdef XP_WIN if (mPrivateDirServiceProvider) { nsCOMPtr dirService = do_GetService(kDirectoryServiceContractID); if (dirService) dirService->UnregisterProvider(mPrivateDirServiceProvider); mPrivateDirServiceProvider = nullptr; } #endif /* XP_WIN */ mPluginsLoaded = false; return NS_OK; } void nsPluginHost::OnPluginInstanceDestroyed(nsPluginTag* aPluginTag) { bool hasInstance = false; for (uint32_t i = 0; i < mInstances.Length(); i++) { if (TagForPlugin(mInstances[i]->GetPlugin()) == aPluginTag) { hasInstance = true; break; } } // We have some options for unloading plugins if they have no instances. // // Unloading plugins immediately can be bad - some plugins retain state // between instances even when there are none. This is largely limited to // going from one page to another, so state is retained without an instance // for only a very short period of time. In order to allow this to work // we don't unload plugins immediately by default. This is supported // via a hidden user pref though. // // Another reason not to unload immediately is that loading is expensive, // and it is better to leave popular plugins loaded. // // Our default behavior is to try to unload a plugin three minutes after // its last instance is destroyed. This seems like a reasonable compromise // that allows us to reclaim memory while allowing short state retention // and avoid perf hits for loading popular plugins. if (!hasInstance) { if (UnloadPluginsASAP()) { aPluginTag->TryUnloadPlugin(false); } else { if (aPluginTag->mUnloadTimer) { aPluginTag->mUnloadTimer->Cancel(); } else { aPluginTag->mUnloadTimer = do_CreateInstance(NS_TIMER_CONTRACTID); } aPluginTag->mUnloadTimer->InitWithCallback(this, 1000 * 60 * 3, nsITimer::TYPE_ONE_SHOT); } } } nsresult nsPluginHost::GetPluginTempDir(nsIFile **aDir) { if (!sPluginTempDir) { nsCOMPtr tmpDir; nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpDir)); NS_ENSURE_SUCCESS(rv, rv); rv = tmpDir->AppendNative(kPluginTmpDirName); // make it unique, and mode == 0700, not world-readable rv = tmpDir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700); NS_ENSURE_SUCCESS(rv, rv); tmpDir.swap(sPluginTempDir); } return sPluginTempDir->Clone(aDir); } nsresult nsPluginHost::InstantiatePluginInstance(const char *aMimeType, nsIURI* aURL, nsObjectLoadingContent *aContent, nsPluginInstanceOwner** aOwner) { NS_ENSURE_ARG_POINTER(aOwner); #ifdef PLUGIN_LOGGING nsAutoCString urlSpec; if (aURL) aURL->GetAsciiSpec(urlSpec); PR_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, ("nsPluginHost::InstantiatePlugin Begin mime=%s, url=%s\n", aMimeType, urlSpec.get())); PR_LogFlush(); #endif if (!aMimeType) { NS_NOTREACHED("Attempting to spawn a plugin with no mime type"); return NS_ERROR_FAILURE; } nsRefPtr instanceOwner = new nsPluginInstanceOwner(); if (!instanceOwner) { return NS_ERROR_OUT_OF_MEMORY; } nsCOMPtr ourContent = do_QueryInterface(static_cast(aContent)); nsresult rv = instanceOwner->Init(ourContent); if (NS_FAILED(rv)) { return rv; } nsPluginTagType tagType; rv = instanceOwner->GetTagType(&tagType); if (NS_FAILED(rv)) { return rv; } if (tagType != nsPluginTagType_Embed && tagType != nsPluginTagType_Applet && tagType != nsPluginTagType_Object) { return NS_ERROR_FAILURE; } rv = SetUpPluginInstance(aMimeType, aURL, instanceOwner); if (NS_FAILED(rv)) { return NS_ERROR_FAILURE; } nsRefPtr instance; rv = instanceOwner->GetInstance(getter_AddRefs(instance)); if (NS_FAILED(rv)) { return rv; } if (instance) { instanceOwner->CreateWidget(); // If we've got a native window, the let the plugin know about it. instanceOwner->CallSetWindow(); } // At this point we consider instantiation to be successful. Do not return an error. instanceOwner.forget(aOwner); #ifdef PLUGIN_LOGGING nsAutoCString urlSpec2; if (aURL != nullptr) aURL->GetAsciiSpec(urlSpec2); PR_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, ("nsPluginHost::InstantiatePlugin Finished mime=%s, rv=%d, url=%s\n", aMimeType, rv, urlSpec2.get())); PR_LogFlush(); #endif return NS_OK; } nsPluginTag* nsPluginHost::FindTagForLibrary(PRLibrary* aLibrary) { nsPluginTag* pluginTag; for (pluginTag = mPlugins; pluginTag; pluginTag = pluginTag->mNext) { if (pluginTag->mLibrary == aLibrary) { return pluginTag; } } return nullptr; } nsPluginTag* nsPluginHost::TagForPlugin(nsNPAPIPlugin* aPlugin) { nsPluginTag* pluginTag; for (pluginTag = mPlugins; pluginTag; pluginTag = pluginTag->mNext) { if (pluginTag->mPlugin == aPlugin) { return pluginTag; } } // a plugin should never exist without a corresponding tag NS_ERROR("TagForPlugin has failed"); return nullptr; } nsresult nsPluginHost::SetUpPluginInstance(const char *aMimeType, nsIURI *aURL, nsPluginInstanceOwner *aOwner) { NS_ENSURE_ARG_POINTER(aOwner); nsresult rv = TrySetUpPluginInstance(aMimeType, aURL, aOwner); if (NS_SUCCEEDED(rv)) { return rv; } // If we failed to load a plugin instance we'll try again after // reloading our plugin list. Only do that once per document to // avoid redundant high resource usage on pages with multiple // unkown instance types. We'll do that by caching the document. nsCOMPtr document; aOwner->GetDocument(getter_AddRefs(document)); nsCOMPtr currentdocument = do_QueryReferent(mCurrentDocument); if (document == currentdocument) { return rv; } mCurrentDocument = do_GetWeakReference(document); // Don't try to set up an instance again if nothing changed. if (ReloadPlugins() == NS_ERROR_PLUGINS_PLUGINSNOTCHANGED) { return rv; } return TrySetUpPluginInstance(aMimeType, aURL, aOwner); } nsresult nsPluginHost::TrySetUpPluginInstance(const char *aMimeType, nsIURI *aURL, nsPluginInstanceOwner *aOwner) { #ifdef PLUGIN_LOGGING nsAutoCString urlSpec; if (aURL != nullptr) aURL->GetSpec(urlSpec); PR_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, ("nsPluginHost::TrySetupPluginInstance Begin mime=%s, owner=%p, url=%s\n", aMimeType, aOwner, urlSpec.get())); PR_LogFlush(); #endif nsRefPtr plugin; GetPlugin(aMimeType, getter_AddRefs(plugin)); if (!plugin) { return NS_ERROR_FAILURE; } nsPluginTag* pluginTag = FindPluginForType(aMimeType, true); NS_ASSERTION(pluginTag, "Must have plugin tag here!"); #if defined(MOZ_WIDGET_ANDROID) && defined(MOZ_CRASHREPORTER) if (pluginTag->mIsFlashPlugin) { CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("FlashVersion"), pluginTag->mVersion); } #endif nsRefPtr instance = new nsNPAPIPluginInstance(); // This will create the owning reference. The connection must be made between the // instance and the instance owner before initialization. Plugins can call into // the browser during initialization. aOwner->SetInstance(instance.get()); // Add the instance to the instances list before we call NPP_New so that // it is "in play" before NPP_New happens. Take it out if NPP_New fails. mInstances.AppendElement(instance.get()); // this should not addref the instance or owner // except in some cases not Java, see bug 140931 // our COM pointer will free the peer nsresult rv = instance->Initialize(plugin.get(), aOwner, aMimeType); if (NS_FAILED(rv)) { mInstances.RemoveElement(instance.get()); aOwner->SetInstance(nullptr); return rv; } // Cancel the plugin unload timer since we are creating // an instance for it. if (pluginTag->mUnloadTimer) { pluginTag->mUnloadTimer->Cancel(); } #ifdef PLUGIN_LOGGING nsAutoCString urlSpec2; if (aURL) aURL->GetSpec(urlSpec2); PR_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_BASIC, ("nsPluginHost::TrySetupPluginInstance Finished mime=%s, rv=%d, owner=%p, url=%s\n", aMimeType, rv, aOwner, urlSpec2.get())); PR_LogFlush(); #endif return rv; } bool nsPluginHost::PluginExistsForType(const char* aMimeType) { nsPluginTag *plugin = FindPluginForType(aMimeType, false); return nullptr != plugin; } NS_IMETHODIMP nsPluginHost::GetPluginTagForType(const nsACString& aMimeType, nsIPluginTag** aResult) { nsPluginTag* plugin = FindPluginForType(aMimeType.Data(), true); if (!plugin) { plugin = FindPluginForType(aMimeType.Data(), false); } if (!plugin) { return NS_ERROR_NOT_AVAILABLE; } NS_ADDREF(*aResult = plugin); return NS_OK; } NS_IMETHODIMP nsPluginHost::GetStateForType(const nsACString &aMimeType, uint32_t* aResult) { nsPluginTag *plugin = FindPluginForType(aMimeType.Data(), true); if (!plugin) { plugin = FindPluginForType(aMimeType.Data(), false); } if (!plugin) { return NS_ERROR_UNEXPECTED; } return plugin->GetEnabledState(aResult); } NS_IMETHODIMP nsPluginHost::GetBlocklistStateForType(const char *aMimeType, uint32_t *aState) { nsPluginTag *plugin = FindPluginForType(aMimeType, true); if (!plugin) { plugin = FindPluginForType(aMimeType, false); } if (!plugin) { return NS_ERROR_FAILURE; } *aState = plugin->GetBlocklistState(); return NS_OK; } NS_IMETHODIMP nsPluginHost::GetPermissionStringForType(const nsACString &aMimeType, nsACString &aPermissionString) { aPermissionString.Truncate(); uint32_t blocklistState; nsresult rv = GetBlocklistStateForType(aMimeType.Data(), &blocklistState); NS_ENSURE_SUCCESS(rv, rv); nsPluginTag *tag = FindPluginForType(aMimeType.Data(), true); if (!tag) { tag = FindPluginForType(aMimeType.Data(), false); } if (!tag) { return NS_ERROR_FAILURE; } if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE || blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) { aPermissionString.AssignLiteral("plugin-vulnerable:"); } else { aPermissionString.AssignLiteral("plugin:"); } aPermissionString.Append(tag->GetNiceFileName()); return NS_OK; } // check comma delimitered extensions static int CompareExtensions(const char *aExtensionList, const char *aExtension) { if (!aExtensionList || !aExtension) return -1; const char *pExt = aExtensionList; const char *pComma = strchr(pExt, ','); if (!pComma) return PL_strcasecmp(pExt, aExtension); int extlen = strlen(aExtension); while (pComma) { int length = pComma - pExt; if (length == extlen && 0 == PL_strncasecmp(aExtension, pExt, length)) return 0; pComma++; pExt = pComma; pComma = strchr(pExt, ','); } // the last one return PL_strcasecmp(pExt, aExtension); } nsresult nsPluginHost::IsPluginEnabledForExtension(const char* aExtension, const char* &aMimeType) { nsPluginTag *plugin = FindPluginEnabledForExtension(aExtension, aMimeType); if (plugin) return NS_OK; return NS_ERROR_FAILURE; } void nsPluginHost::GetPlugins(nsTArray >& aPluginArray) { aPluginArray.Clear(); LoadPlugins(); nsPluginTag* plugin = mPlugins; while (plugin != nullptr) { if (plugin->IsEnabled()) { aPluginArray.AppendElement(plugin); } plugin = plugin->mNext; } } NS_IMETHODIMP nsPluginHost::GetPluginTags(uint32_t* aPluginCount, nsIPluginTag*** aResults) { LoadPlugins(); uint32_t count = 0; nsRefPtr plugin = mPlugins; while (plugin != nullptr) { count++; plugin = plugin->mNext; } *aResults = static_cast (nsMemory::Alloc(count * sizeof(**aResults))); if (!*aResults) return NS_ERROR_OUT_OF_MEMORY; *aPluginCount = count; plugin = mPlugins; for (uint32_t i = 0; i < count; i++) { (*aResults)[i] = plugin; NS_ADDREF((*aResults)[i]); plugin = plugin->mNext; } return NS_OK; } nsPluginTag* nsPluginHost::FindPreferredPlugin(const InfallibleTArray& matches) { // We prefer the plugin with the highest version number. /// XXX(johns): This seems to assume the only time multiple plugins will have /// the same MIME type is if they're multiple versions of the same /// plugin -- but since plugin filenames and pretty names can both /// update, it's probably less arbitrary than just going at it /// alphabetically. if (matches.IsEmpty()) { return nullptr; } nsPluginTag *preferredPlugin = matches[0]; for (unsigned int i = 1; i < matches.Length(); i++) { if (mozilla::Version(matches[i]->mVersion.get()) > preferredPlugin->mVersion.get()) { preferredPlugin = matches[i]; } } return preferredPlugin; } nsPluginTag* nsPluginHost::FindPluginForType(const char* aMimeType, bool aCheckEnabled) { if (!aMimeType) { return nullptr; } LoadPlugins(); InfallibleTArray matchingPlugins; nsPluginTag *plugin = mPlugins; while (plugin) { if (!aCheckEnabled || plugin->IsActive()) { int32_t mimeCount = plugin->mMimeTypes.Length(); for (int32_t i = 0; i < mimeCount; i++) { if (0 == PL_strcasecmp(plugin->mMimeTypes[i].get(), aMimeType)) { matchingPlugins.AppendElement(plugin); break; } } } plugin = plugin->mNext; } return FindPreferredPlugin(matchingPlugins); } nsPluginTag* nsPluginHost::FindPluginEnabledForExtension(const char* aExtension, const char*& aMimeType) { if (!aExtension) { return nullptr; } LoadPlugins(); InfallibleTArray matchingPlugins; nsPluginTag *plugin = mPlugins; while (plugin) { if (plugin->IsActive()) { int32_t variants = plugin->mExtensions.Length(); for (int32_t i = 0; i < variants; i++) { // mExtensionsArray[cnt] is a list of extensions separated by commas if (0 == CompareExtensions(plugin->mExtensions[i].get(), aExtension)) { matchingPlugins.AppendElement(plugin); break; } } } plugin = plugin->mNext; } nsPluginTag *preferredPlugin = FindPreferredPlugin(matchingPlugins); if (!preferredPlugin) { return nullptr; } int32_t variants = preferredPlugin->mExtensions.Length(); for (int32_t i = 0; i < variants; i++) { // mExtensionsArray[cnt] is a list of extensions separated by commas if (0 == CompareExtensions(preferredPlugin->mExtensions[i].get(), aExtension)) { aMimeType = preferredPlugin->mMimeTypes[i].get(); break; } } return preferredPlugin; } static nsresult CreateNPAPIPlugin(nsPluginTag *aPluginTag, nsNPAPIPlugin **aOutNPAPIPlugin) { // If this is an in-process plugin we'll need to load it here if we haven't already. if (!nsNPAPIPlugin::RunPluginOOP(aPluginTag)) { if (aPluginTag->mFullPath.IsEmpty()) return NS_ERROR_FAILURE; nsCOMPtr file = do_CreateInstance("@mozilla.org/file/local;1"); file->InitWithPath(NS_ConvertUTF8toUTF16(aPluginTag->mFullPath)); nsPluginFile pluginFile(file); PRLibrary* pluginLibrary = nullptr; if (NS_FAILED(pluginFile.LoadPlugin(&pluginLibrary)) || !pluginLibrary) return NS_ERROR_FAILURE; aPluginTag->mLibrary = pluginLibrary; } nsresult rv; rv = nsNPAPIPlugin::CreatePlugin(aPluginTag, aOutNPAPIPlugin); return rv; } nsresult nsPluginHost::EnsurePluginLoaded(nsPluginTag* aPluginTag) { nsRefPtr plugin = aPluginTag->mPlugin; if (!plugin) { nsresult rv = CreateNPAPIPlugin(aPluginTag, getter_AddRefs(plugin)); if (NS_FAILED(rv)) { return rv; } aPluginTag->mPlugin = plugin; } return NS_OK; } nsresult nsPluginHost::GetPlugin(const char *aMimeType, nsNPAPIPlugin** aPlugin) { nsresult rv = NS_ERROR_FAILURE; *aPlugin = nullptr; if (!aMimeType) return NS_ERROR_ILLEGAL_VALUE; // If plugins haven't been scanned yet, do so now LoadPlugins(); nsPluginTag* pluginTag = FindPluginForType(aMimeType, true); if (pluginTag) { rv = NS_OK; PLUGIN_LOG(PLUGIN_LOG_BASIC, ("nsPluginHost::GetPlugin Begin mime=%s, plugin=%s\n", aMimeType, pluginTag->mFileName.get())); #ifdef DEBUG if (aMimeType && !pluginTag->mFileName.IsEmpty()) printf("For %s found plugin %s\n", aMimeType, pluginTag->mFileName.get()); #endif rv = EnsurePluginLoaded(pluginTag); if (NS_FAILED(rv)) { return rv; } NS_ADDREF(*aPlugin = pluginTag->mPlugin); return NS_OK; } PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsPluginHost::GetPlugin End mime=%s, rv=%d, plugin=%p name=%s\n", aMimeType, rv, *aPlugin, (pluginTag ? pluginTag->mFileName.get() : "(not found)"))); return rv; } // Normalize 'host' to ACE. nsresult nsPluginHost::NormalizeHostname(nsCString& host) { if (IsASCII(host)) { ToLowerCase(host); return NS_OK; } if (!mIDNService) { nsresult rv; mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); } return mIDNService->ConvertUTF8toACE(host, host); } // Enumerate a 'sites' array returned by GetSitesWithData and determine if // any of them have a base domain in common with 'domain'; if so, append them // to the 'result' array. If 'firstMatchOnly' is true, return after finding the // first match. nsresult nsPluginHost::EnumerateSiteData(const nsACString& domain, const InfallibleTArray& sites, InfallibleTArray& result, bool firstMatchOnly) { NS_ASSERTION(!domain.IsVoid(), "null domain string"); nsresult rv; if (!mTLDService) { mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); } // Get the base domain from the domain. nsCString baseDomain; rv = mTLDService->GetBaseDomainFromHost(domain, 0, baseDomain); bool isIP = rv == NS_ERROR_HOST_IS_IP_ADDRESS; if (isIP || rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { // The base domain is the site itself. However, we must be careful to // normalize. baseDomain = domain; rv = NormalizeHostname(baseDomain); NS_ENSURE_SUCCESS(rv, rv); } else if (NS_FAILED(rv)) { return rv; } // Enumerate the array of sites with data. for (uint32_t i = 0; i < sites.Length(); ++i) { const nsCString& site = sites[i]; // Check if the site is an IP address. bool siteIsIP = site.Length() >= 2 && site.First() == '[' && site.Last() == ']'; if (siteIsIP != isIP) continue; nsCString siteBaseDomain; if (siteIsIP) { // Strip the '[]'. siteBaseDomain = Substring(site, 1, site.Length() - 2); } else { // Determine the base domain of the site. rv = mTLDService->GetBaseDomainFromHost(site, 0, siteBaseDomain); if (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { // The base domain is the site itself. However, we must be careful to // normalize. siteBaseDomain = site; rv = NormalizeHostname(siteBaseDomain); NS_ENSURE_SUCCESS(rv, rv); } else if (NS_FAILED(rv)) { return rv; } } // At this point, we can do an exact comparison of the two domains. if (baseDomain != siteBaseDomain) { continue; } // Append the site to the result array. result.AppendElement(site); // If we're supposed to return early, do so. if (firstMatchOnly) { break; } } return NS_OK; } NS_IMETHODIMP nsPluginHost::RegisterPlayPreviewMimeType(const nsACString& mimeType, bool ignoreCTP, const nsACString& redirectURL) { nsAutoCString mt(mimeType); nsAutoCString url(redirectURL); if (url.Length() == 0) { // using default play preview iframe URL, if redirectURL is not specified url.Assign("data:application/x-moz-playpreview;,"); url.Append(mimeType); } nsRefPtr playPreview = new nsPluginPlayPreviewInfo(mt.get(), ignoreCTP, url.get()); mPlayPreviewMimeTypes.AppendElement(playPreview); return NS_OK; } NS_IMETHODIMP nsPluginHost::UnregisterPlayPreviewMimeType(const nsACString& mimeType) { nsAutoCString mimeTypeToRemove(mimeType); for (uint32_t i = mPlayPreviewMimeTypes.Length(); i > 0; i--) { nsRefPtr pp = mPlayPreviewMimeTypes[i - 1]; if (PL_strcasecmp(pp.get()->mMimeType.get(), mimeTypeToRemove.get()) == 0) { mPlayPreviewMimeTypes.RemoveElementAt(i - 1); break; } } return NS_OK; } NS_IMETHODIMP nsPluginHost::GetPlayPreviewInfo(const nsACString& mimeType, nsIPluginPlayPreviewInfo** aResult) { nsAutoCString mimeTypeToFind(mimeType); for (uint32_t i = 0; i < mPlayPreviewMimeTypes.Length(); i++) { nsRefPtr pp = mPlayPreviewMimeTypes[i]; if (PL_strcasecmp(pp.get()->mMimeType.get(), mimeTypeToFind.get()) == 0) { *aResult = new nsPluginPlayPreviewInfo(pp.get()); NS_ADDREF(*aResult); return NS_OK; } } *aResult = nullptr; return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP nsPluginHost::ClearSiteData(nsIPluginTag* plugin, const nsACString& domain, uint64_t flags, int64_t maxAge) { // maxAge must be either a nonnegative integer or -1. NS_ENSURE_ARG(maxAge >= 0 || maxAge == -1); // Caller may give us a tag object that is no longer live. if (!IsLiveTag(plugin)) { return NS_ERROR_NOT_AVAILABLE; } nsPluginTag* tag = static_cast(plugin); // We only ensure support for clearing Flash site data for now. // We will also attempt to clear data for any plugin that happens // to be loaded already. if (!tag->mIsFlashPlugin && !tag->mPlugin) { return NS_ERROR_FAILURE; } // Make sure the plugin is loaded. nsresult rv = EnsurePluginLoaded(tag); if (NS_FAILED(rv)) { return rv; } PluginLibrary* library = tag->mPlugin->GetLibrary(); // If 'domain' is the null string, clear everything. if (domain.IsVoid()) { return library->NPP_ClearSiteData(nullptr, flags, maxAge); } // Get the list of sites from the plugin. InfallibleTArray sites; rv = library->NPP_GetSitesWithData(sites); NS_ENSURE_SUCCESS(rv, rv); // Enumerate the sites and build a list of matches. InfallibleTArray matches; rv = EnumerateSiteData(domain, sites, matches, false); NS_ENSURE_SUCCESS(rv, rv); // Clear the matches. for (uint32_t i = 0; i < matches.Length(); ++i) { const nsCString& match = matches[i]; rv = library->NPP_ClearSiteData(match.get(), flags, maxAge); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } NS_IMETHODIMP nsPluginHost::SiteHasData(nsIPluginTag* plugin, const nsACString& domain, bool* result) { // Caller may give us a tag object that is no longer live. if (!IsLiveTag(plugin)) { return NS_ERROR_NOT_AVAILABLE; } nsPluginTag* tag = static_cast(plugin); // We only ensure support for clearing Flash site data for now. // We will also attempt to clear data for any plugin that happens // to be loaded already. if (!tag->mIsFlashPlugin && !tag->mPlugin) { return NS_ERROR_FAILURE; } // Make sure the plugin is loaded. nsresult rv = EnsurePluginLoaded(tag); if (NS_FAILED(rv)) { return rv; } PluginLibrary* library = tag->mPlugin->GetLibrary(); // Get the list of sites from the plugin. InfallibleTArray sites; rv = library->NPP_GetSitesWithData(sites); NS_ENSURE_SUCCESS(rv, rv); // If there's no data, we're done. if (sites.IsEmpty()) { *result = false; return NS_OK; } // If 'domain' is the null string, and there's data for at least one site, // we're done. if (domain.IsVoid()) { *result = true; return NS_OK; } // Enumerate the sites and determine if there's a match. InfallibleTArray matches; rv = EnumerateSiteData(domain, sites, matches, true); NS_ENSURE_SUCCESS(rv, rv); *result = !matches.IsEmpty(); return NS_OK; } bool nsPluginHost::IsJavaMIMEType(const char* aType) { // The java mime pref may well not be one of these, // e.g. application/x-java-test used in the test suite nsAdoptingCString javaMIME = Preferences::GetCString(kPrefJavaMIME); return aType && (javaMIME.EqualsIgnoreCase(aType) || (0 == PL_strncasecmp(aType, "application/x-java-vm", sizeof("application/x-java-vm") - 1)) || (0 == PL_strncasecmp(aType, "application/x-java-applet", sizeof("application/x-java-applet") - 1)) || (0 == PL_strncasecmp(aType, "application/x-java-bean", sizeof("application/x-java-bean") - 1))); } // Check whether or not a tag is a live, valid tag, and that it's loaded. bool nsPluginHost::IsLiveTag(nsIPluginTag* aPluginTag) { nsPluginTag* tag; for (tag = mPlugins; tag; tag = tag->mNext) { if (tag == aPluginTag) { return true; } } return false; } nsPluginTag* nsPluginHost::HaveSamePlugin(const nsPluginTag* aPluginTag) { for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) { if (tag->HasSameNameAndMimes(aPluginTag)) { return tag; } } return nullptr; } nsPluginTag* nsPluginHost::FirstPluginWithPath(const nsCString& path) { for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) { if (tag->mFullPath.Equals(path)) { return tag; } } return nullptr; } namespace { int64_t GetPluginLastModifiedTime(const nsCOMPtr& localfile) { PRTime fileModTime = 0; #if defined(XP_MACOSX) // On OS X the date of a bundle's "contents" (i.e. of its Info.plist file) // is a much better guide to when it was last modified than the date of // its package directory. See bug 313700. nsCOMPtr localFileMac = do_QueryInterface(localfile); if (localFileMac) { localFileMac->GetBundleContentsLastModifiedTime(&fileModTime); } else { localfile->GetLastModifiedTime(&fileModTime); } #else localfile->GetLastModifiedTime(&fileModTime); #endif return fileModTime; } struct CompareFilesByTime { bool LessThan(const nsCOMPtr& a, const nsCOMPtr& b) const { return GetPluginLastModifiedTime(a) < GetPluginLastModifiedTime(b); } bool Equals(const nsCOMPtr& a, const nsCOMPtr& b) const { return GetPluginLastModifiedTime(a) == GetPluginLastModifiedTime(b); } }; } // anonymous namespace typedef NS_NPAPIPLUGIN_CALLBACK(char *, NP_GETMIMEDESCRIPTION)(void); nsresult nsPluginHost::ScanPluginsDirectory(nsIFile *pluginsDir, bool aCreatePluginList, bool *aPluginsChanged) { NS_ENSURE_ARG_POINTER(aPluginsChanged); nsresult rv; *aPluginsChanged = false; #ifdef PLUGIN_LOGGING nsAutoCString dirPath; pluginsDir->GetNativePath(dirPath); PLUGIN_LOG(PLUGIN_LOG_BASIC, ("nsPluginHost::ScanPluginsDirectory dir=%s\n", dirPath.get())); #endif nsCOMPtr iter; rv = pluginsDir->GetDirectoryEntries(getter_AddRefs(iter)); if (NS_FAILED(rv)) return rv; nsAutoTArray, 6> pluginFiles; bool hasMore; while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr supports; rv = iter->GetNext(getter_AddRefs(supports)); if (NS_FAILED(rv)) continue; nsCOMPtr dirEntry(do_QueryInterface(supports, &rv)); if (NS_FAILED(rv)) continue; // Sun's JRE 1.3.1 plugin must have symbolic links resolved or else it'll crash. // See bug 197855. dirEntry->Normalize(); if (nsPluginsDir::IsPluginFile(dirEntry)) { pluginFiles.AppendElement(dirEntry); } } pluginFiles.Sort(CompareFilesByTime()); bool warnOutdated = false; for (int32_t i = (pluginFiles.Length() - 1); i >= 0; i--) { nsCOMPtr& localfile = pluginFiles[i]; nsString utf16FilePath; rv = localfile->GetPath(utf16FilePath); if (NS_FAILED(rv)) continue; int64_t fileModTime = GetPluginLastModifiedTime(localfile); // Look for it in our cache NS_ConvertUTF16toUTF8 filePath(utf16FilePath); nsRefPtr pluginTag; RemoveCachedPluginsInfo(filePath.get(), getter_AddRefs(pluginTag)); bool seenBefore = false; if (pluginTag) { seenBefore = true; // If plugin changed, delete cachedPluginTag and don't use cache if (fileModTime != pluginTag->mLastModifiedTime) { // Plugins has changed. Don't use cached plugin info. pluginTag = nullptr; // plugin file changed, flag this fact *aPluginsChanged = true; } // If we're not creating a list and we already know something changed then // we're done. if (!aCreatePluginList) { if (*aPluginsChanged) { return NS_OK; } continue; } } bool isKnownInvalidPlugin = false; for (nsRefPtr invalidPlugins = mInvalidPlugins; invalidPlugins; invalidPlugins = invalidPlugins->mNext) { // If already marked as invalid, ignore it if (invalidPlugins->mFullPath.Equals(filePath.get()) && invalidPlugins->mLastModifiedTime == fileModTime) { if (aCreatePluginList) { invalidPlugins->mSeen = true; } isKnownInvalidPlugin = true; break; } } if (isKnownInvalidPlugin) { continue; } // if it is not found in cache info list or has been changed, create a new one if (!pluginTag) { nsPluginFile pluginFile(localfile); // create a tag describing this plugin. PRLibrary *library = nullptr; nsPluginInfo info; memset(&info, 0, sizeof(info)); nsresult res; // Opening a block for the telemetry AutoTimer { Telemetry::AutoTimer telemetry; res = pluginFile.GetPluginInfo(info, &library); } // if we don't have mime type don't proceed, this is not a plugin if (NS_FAILED(res) || !info.fMimeTypeArray) { nsRefPtr invalidTag = new nsInvalidPluginTag(filePath.get(), fileModTime); pluginFile.FreePluginInfo(info); if (aCreatePluginList) { invalidTag->mSeen = true; } invalidTag->mNext = mInvalidPlugins; if (mInvalidPlugins) { mInvalidPlugins->mPrev = invalidTag; } mInvalidPlugins = invalidTag; // Mark aPluginsChanged so pluginreg is rewritten *aPluginsChanged = true; continue; } pluginTag = new nsPluginTag(&info, fileModTime); pluginFile.FreePluginInfo(info); if (!pluginTag) return NS_ERROR_OUT_OF_MEMORY; pluginTag->mLibrary = library; uint32_t state = pluginTag->GetBlocklistState(); // If the blocklist says it is risky and we have never seen this // plugin before, then disable it. // If the blocklist says this is an outdated plugin, warn about // outdated plugins. if (state == nsIBlocklistService::STATE_SOFTBLOCKED && !seenBefore) { pluginTag->SetEnabledState(nsIPluginTag::STATE_DISABLED); } if (state == nsIBlocklistService::STATE_OUTDATED && !seenBefore) { warnOutdated = true; } // Plugin unloading is tag-based. If we created a new tag and loaded // the library in the process then we want to attempt to unload it here. // Only do this if the pref is set for aggressive unloading. if (UnloadPluginsASAP()) { pluginTag->TryUnloadPlugin(false); } } // do it if we still want it if (!seenBefore) { // We have a valid new plugin so report that plugins have changed. *aPluginsChanged = true; } // Avoid adding different versions of the same plugin if they are running // in-process, otherwise we risk undefined behaviour. if (!nsNPAPIPlugin::RunPluginOOP(pluginTag)) { if (HaveSamePlugin(pluginTag)) { continue; } } // Don't add the same plugin again if it hasn't changed if (nsPluginTag* duplicate = FirstPluginWithPath(pluginTag->mFullPath)) { if (pluginTag->mLastModifiedTime == duplicate->mLastModifiedTime) { continue; } } // If we're not creating a plugin list, simply looking for changes, // then we're done. if (!aCreatePluginList) { return NS_OK; } // Add plugin tags such that the list is ordered by modification date, // newest to oldest. This is ugly, it'd be easier with just about anything // other than a single-directional linked list. if (mPlugins) { nsPluginTag *prev = nullptr; nsPluginTag *next = mPlugins; while (next) { if (pluginTag->mLastModifiedTime >= next->mLastModifiedTime) { pluginTag->mNext = next; if (prev) { prev->mNext = pluginTag; } else { mPlugins = pluginTag; } break; } prev = next; next = prev->mNext; if (!next) { prev->mNext = pluginTag; } } } else { mPlugins = pluginTag; } if (pluginTag->IsActive()) { nsAdoptingCString disableFullPage = Preferences::GetCString(kPrefDisableFullPage); for (uint32_t i = 0; i < pluginTag->mMimeTypes.Length(); i++) { if (!IsTypeInList(pluginTag->mMimeTypes[i], disableFullPage)) { RegisterWithCategoryManager(pluginTag->mMimeTypes[i], ePluginRegister); } } } } if (warnOutdated) { Preferences::SetBool("plugins.update.notifyUser", true); } return NS_OK; } nsresult nsPluginHost::ScanPluginsDirectoryList(nsISimpleEnumerator *dirEnum, bool aCreatePluginList, bool *aPluginsChanged) { bool hasMore; while (NS_SUCCEEDED(dirEnum->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr supports; nsresult rv = dirEnum->GetNext(getter_AddRefs(supports)); if (NS_FAILED(rv)) continue; nsCOMPtr nextDir(do_QueryInterface(supports, &rv)); if (NS_FAILED(rv)) continue; // don't pass aPluginsChanged directly to prevent it from been reset bool pluginschanged = false; ScanPluginsDirectory(nextDir, aCreatePluginList, &pluginschanged); if (pluginschanged) *aPluginsChanged = true; // if changes are detected and we are not creating the list, do not proceed if (!aCreatePluginList && *aPluginsChanged) break; } return NS_OK; } nsresult nsPluginHost::LoadPlugins() { #ifdef ANDROID if (XRE_GetProcessType() == GeckoProcessType_Content) { return NS_OK; } #endif // do not do anything if it is already done // use ReloadPlugins() to enforce loading if (mPluginsLoaded) return NS_OK; if (mPluginsDisabled) return NS_OK; bool pluginschanged; nsresult rv = FindPlugins(true, &pluginschanged); if (NS_FAILED(rv)) return rv; // only if plugins have changed will we notify plugin-change observers if (pluginschanged) { nsCOMPtr obsService = mozilla::services::GetObserverService(); if (obsService) obsService->NotifyObservers(nullptr, "plugins-list-updated", nullptr); } return NS_OK; } // if aCreatePluginList is false we will just scan for plugins // and see if any changes have been made to the plugins. // This is needed in ReloadPlugins to prevent possible recursive reloads nsresult nsPluginHost::FindPlugins(bool aCreatePluginList, bool * aPluginsChanged) { Telemetry::AutoTimer telemetry; NS_ENSURE_ARG_POINTER(aPluginsChanged); *aPluginsChanged = false; nsresult rv; // Read cached plugins info. If the profile isn't yet available then don't // scan for plugins if (ReadPluginInfo() == NS_ERROR_NOT_AVAILABLE) return NS_OK; #ifdef XP_WIN // Failure here is not a show-stopper so just warn. rv = EnsurePrivateDirServiceProvider(); NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to register dir service provider."); #endif /* XP_WIN */ nsCOMPtr dirService(do_GetService(kDirectoryServiceContractID, &rv)); if (NS_FAILED(rv)) return rv; nsCOMPtr dirList; // Scan plugins directories; // don't pass aPluginsChanged directly, to prevent its // possible reset in subsequent ScanPluginsDirectory calls bool pluginschanged = false; // Scan the app-defined list of plugin dirs. rv = dirService->Get(NS_APP_PLUGINS_DIR_LIST, NS_GET_IID(nsISimpleEnumerator), getter_AddRefs(dirList)); if (NS_SUCCEEDED(rv)) { ScanPluginsDirectoryList(dirList, aCreatePluginList, &pluginschanged); if (pluginschanged) *aPluginsChanged = true; // if we are just looking for possible changes, // no need to proceed if changes are detected if (!aCreatePluginList && *aPluginsChanged) { NS_ITERATIVE_UNREF_LIST(nsRefPtr, mCachedPlugins, mNext); NS_ITERATIVE_UNREF_LIST(nsRefPtr, mInvalidPlugins, mNext); return NS_OK; } } else { #ifdef ANDROID LOG("getting plugins dir failed"); #endif } mPluginsLoaded = true; // at this point 'some' plugins have been loaded, // the rest is optional #ifdef XP_WIN bool bScanPLIDs = Preferences::GetBool("plugin.scan.plid.all", false); // Now lets scan any PLID directories if (bScanPLIDs && mPrivateDirServiceProvider) { rv = mPrivateDirServiceProvider->GetPLIDDirectories(getter_AddRefs(dirList)); if (NS_SUCCEEDED(rv)) { ScanPluginsDirectoryList(dirList, aCreatePluginList, &pluginschanged); if (pluginschanged) *aPluginsChanged = true; // if we are just looking for possible changes, // no need to proceed if changes are detected if (!aCreatePluginList && *aPluginsChanged) { NS_ITERATIVE_UNREF_LIST(nsRefPtr, mCachedPlugins, mNext); NS_ITERATIVE_UNREF_LIST(nsRefPtr, mInvalidPlugins, mNext); return NS_OK; } } } // Scan the installation paths of our popular plugins if the prefs are enabled // This table controls the order of scanning const char* const prefs[] = {NS_WIN_ACROBAT_SCAN_KEY, NS_WIN_QUICKTIME_SCAN_KEY, NS_WIN_WMP_SCAN_KEY}; uint32_t size = sizeof(prefs) / sizeof(prefs[0]); for (uint32_t i = 0; i < size; i+=1) { nsCOMPtr dirToScan; bool bExists; if (NS_SUCCEEDED(dirService->Get(prefs[i], NS_GET_IID(nsIFile), getter_AddRefs(dirToScan))) && dirToScan && NS_SUCCEEDED(dirToScan->Exists(&bExists)) && bExists) { ScanPluginsDirectory(dirToScan, aCreatePluginList, &pluginschanged); if (pluginschanged) *aPluginsChanged = true; // if we are just looking for possible changes, // no need to proceed if changes are detected if (!aCreatePluginList && *aPluginsChanged) { NS_ITERATIVE_UNREF_LIST(nsRefPtr, mCachedPlugins, mNext); NS_ITERATIVE_UNREF_LIST(nsRefPtr, mInvalidPlugins, mNext); return NS_OK; } } } #endif // We should also consider plugins to have changed if any plugins have been removed. // We'll know if any were removed if they weren't taken out of the cached plugins list // during our scan, thus we can assume something was removed if the cached plugins list // contains anything. if (!*aPluginsChanged && mCachedPlugins) { *aPluginsChanged = true; } // Remove unseen invalid plugins nsRefPtr invalidPlugins = mInvalidPlugins; while (invalidPlugins) { if (!invalidPlugins->mSeen) { nsRefPtr invalidPlugin = invalidPlugins; if (invalidPlugin->mPrev) { invalidPlugin->mPrev->mNext = invalidPlugin->mNext; } else { mInvalidPlugins = invalidPlugin->mNext; } if (invalidPlugin->mNext) { invalidPlugin->mNext->mPrev = invalidPlugin->mPrev; } invalidPlugins = invalidPlugin->mNext; invalidPlugin->mPrev = nullptr; invalidPlugin->mNext = nullptr; } else { invalidPlugins->mSeen = false; invalidPlugins = invalidPlugins->mNext; } } // if we are not creating the list, there is no need to proceed if (!aCreatePluginList) { NS_ITERATIVE_UNREF_LIST(nsRefPtr, mCachedPlugins, mNext); NS_ITERATIVE_UNREF_LIST(nsRefPtr, mInvalidPlugins, mNext); return NS_OK; } // if we are creating the list, it is already done; // update the plugins info cache if changes are detected if (*aPluginsChanged) WritePluginInfo(); // No more need for cached plugins. Clear it up. NS_ITERATIVE_UNREF_LIST(nsRefPtr, mCachedPlugins, mNext); NS_ITERATIVE_UNREF_LIST(nsRefPtr, mInvalidPlugins, mNext); return NS_OK; } nsresult nsPluginHost::UpdatePluginInfo(nsPluginTag* aPluginTag) { ReadPluginInfo(); WritePluginInfo(); NS_ITERATIVE_UNREF_LIST(nsRefPtr, mCachedPlugins, mNext); NS_ITERATIVE_UNREF_LIST(nsRefPtr, mInvalidPlugins, mNext); if (!aPluginTag) { return NS_OK; } // Update types with category manager nsAdoptingCString disableFullPage = Preferences::GetCString(kPrefDisableFullPage); for (uint32_t i = 0; i < aPluginTag->mMimeTypes.Length(); i++) { nsRegisterType shouldRegister; if (IsTypeInList(aPluginTag->mMimeTypes[i], disableFullPage)) { shouldRegister = ePluginUnregister; } else { nsPluginTag *plugin = FindPluginForType(aPluginTag->mMimeTypes[i].get(), true); shouldRegister = plugin ? ePluginRegister : ePluginUnregister; } RegisterWithCategoryManager(aPluginTag->mMimeTypes[i], shouldRegister); } nsCOMPtr obsService = mozilla::services::GetObserverService(); if (obsService) obsService->NotifyObservers(nullptr, "plugin-info-updated", nullptr); // Reload instances if needed if (aPluginTag->IsActive()) { return NS_OK; } return NS_OK; } /* static */ bool nsPluginHost::IsTypeWhitelisted(const char *aMimeType) { nsAdoptingCString whitelist = Preferences::GetCString(kPrefWhitelist); if (!whitelist.Length()) { return true; } nsDependentCString wrap(aMimeType); return IsTypeInList(wrap, whitelist); } void nsPluginHost::RegisterWithCategoryManager(nsCString &aMimeType, nsRegisterType aType) { PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsPluginTag::RegisterWithCategoryManager type = %s, removing = %s\n", aMimeType.get(), aType == ePluginUnregister ? "yes" : "no")); nsCOMPtr catMan = do_GetService(NS_CATEGORYMANAGER_CONTRACTID); if (!catMan) { return; } const char *contractId = "@mozilla.org/content/plugin/document-loader-factory;1"; if (aType == ePluginRegister) { catMan->AddCategoryEntry("Gecko-Content-Viewers", aMimeType.get(), contractId, false, /* persist: broken by bug 193031 */ mOverrideInternalTypes, nullptr); } else { // Only delete the entry if a plugin registered for it nsXPIDLCString value; nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", aMimeType.get(), getter_Copies(value)); if (NS_SUCCEEDED(rv) && strcmp(value, contractId) == 0) { catMan->DeleteCategoryEntry("Gecko-Content-Viewers", aMimeType.get(), true); } } } nsresult nsPluginHost::WritePluginInfo() { nsresult rv = NS_OK; nsCOMPtr directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID,&rv)); if (NS_FAILED(rv)) return rv; directoryService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile), getter_AddRefs(mPluginRegFile)); if (!mPluginRegFile) return NS_ERROR_FAILURE; PRFileDesc* fd = nullptr; nsCOMPtr pluginReg; rv = mPluginRegFile->Clone(getter_AddRefs(pluginReg)); if (NS_FAILED(rv)) return rv; nsAutoCString filename(kPluginRegistryFilename); filename.Append(".tmp"); rv = pluginReg->AppendNative(filename); if (NS_FAILED(rv)) return rv; rv = pluginReg->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0600, &fd); if (NS_FAILED(rv)) return rv; nsCOMPtr runtime = do_GetService("@mozilla.org/xre/runtime;1"); if (!runtime) { return NS_ERROR_FAILURE; } nsAutoCString arch; rv = runtime->GetXPCOMABI(arch); if (NS_FAILED(rv)) { return rv; } PR_fprintf(fd, "Generated File. Do not edit.\n"); PR_fprintf(fd, "\n[HEADER]\nVersion%c%s%c%c\nArch%c%s%c%c\n", PLUGIN_REGISTRY_FIELD_DELIMITER, kPluginRegistryVersion, PLUGIN_REGISTRY_FIELD_DELIMITER, PLUGIN_REGISTRY_END_OF_LINE_MARKER, PLUGIN_REGISTRY_FIELD_DELIMITER, arch.get(), PLUGIN_REGISTRY_FIELD_DELIMITER, PLUGIN_REGISTRY_END_OF_LINE_MARKER); // Store all plugins in the mPlugins list - all plugins currently in use. PR_fprintf(fd, "\n[PLUGINS]\n"); for (nsPluginTag *tag = mPlugins; tag; tag = tag->mNext) { // store each plugin info into the registry // filename & fullpath are on separate line // because they can contain field delimiter char PR_fprintf(fd, "%s%c%c\n%s%c%c\n%s%c%c\n", (tag->mFileName.get()), PLUGIN_REGISTRY_FIELD_DELIMITER, PLUGIN_REGISTRY_END_OF_LINE_MARKER, (tag->mFullPath.get()), PLUGIN_REGISTRY_FIELD_DELIMITER, PLUGIN_REGISTRY_END_OF_LINE_MARKER, (tag->mVersion.get()), PLUGIN_REGISTRY_FIELD_DELIMITER, PLUGIN_REGISTRY_END_OF_LINE_MARKER); // lastModifiedTimeStamp|canUnload|tag->mFlags PR_fprintf(fd, "%lld%c%d%c%lu%c%c\n", tag->mLastModifiedTime, PLUGIN_REGISTRY_FIELD_DELIMITER, false, // did store whether or not to unload in-process plugins PLUGIN_REGISTRY_FIELD_DELIMITER, 0, // legacy field for flags PLUGIN_REGISTRY_FIELD_DELIMITER, PLUGIN_REGISTRY_END_OF_LINE_MARKER); //description, name & mtypecount are on separate line PR_fprintf(fd, "%s%c%c\n%s%c%c\n%d\n", (tag->mDescription.get()), PLUGIN_REGISTRY_FIELD_DELIMITER, PLUGIN_REGISTRY_END_OF_LINE_MARKER, (tag->mName.get()), PLUGIN_REGISTRY_FIELD_DELIMITER, PLUGIN_REGISTRY_END_OF_LINE_MARKER, tag->mMimeTypes.Length()); // Add in each mimetype this plugin supports for (uint32_t i = 0; i < tag->mMimeTypes.Length(); i++) { PR_fprintf(fd, "%d%c%s%c%s%c%s%c%c\n", i,PLUGIN_REGISTRY_FIELD_DELIMITER, (tag->mMimeTypes[i].get()), PLUGIN_REGISTRY_FIELD_DELIMITER, (tag->mMimeDescriptions[i].get()), PLUGIN_REGISTRY_FIELD_DELIMITER, (tag->mExtensions[i].get()), PLUGIN_REGISTRY_FIELD_DELIMITER, PLUGIN_REGISTRY_END_OF_LINE_MARKER); } } PR_fprintf(fd, "\n[INVALID]\n"); nsRefPtr invalidPlugins = mInvalidPlugins; while (invalidPlugins) { // fullPath PR_fprintf(fd, "%s%c%c\n", (!invalidPlugins->mFullPath.IsEmpty() ? invalidPlugins->mFullPath.get() : ""), PLUGIN_REGISTRY_FIELD_DELIMITER, PLUGIN_REGISTRY_END_OF_LINE_MARKER); // lastModifiedTimeStamp PR_fprintf(fd, "%lld%c%c\n", invalidPlugins->mLastModifiedTime, PLUGIN_REGISTRY_FIELD_DELIMITER, PLUGIN_REGISTRY_END_OF_LINE_MARKER); invalidPlugins = invalidPlugins->mNext; } PRStatus prrc; prrc = PR_Close(fd); if (prrc != PR_SUCCESS) { // we should obtain a refined value based on prrc; rv = NS_ERROR_FAILURE; MOZ_ASSERT(false, "PR_Close() failed."); return rv; } nsCOMPtr parent; rv = pluginReg->GetParent(getter_AddRefs(parent)); NS_ENSURE_SUCCESS(rv, rv); rv = pluginReg->MoveToNative(parent, kPluginRegistryFilename); return rv; } nsresult nsPluginHost::ReadPluginInfo() { const long PLUGIN_REG_MIMETYPES_ARRAY_SIZE = 12; const long PLUGIN_REG_MAX_MIMETYPES = 1000; // we need to import the legacy flags from the plugin registry once const bool pluginStateImported = Preferences::GetDefaultBool("plugin.importedState", false); nsresult rv; nsCOMPtr directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID,&rv)); if (NS_FAILED(rv)) return rv; directoryService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile), getter_AddRefs(mPluginRegFile)); if (!mPluginRegFile) { // There is no profile yet, this will tell us if there is going to be one // in the future. directoryService->Get(NS_APP_PROFILE_DIR_STARTUP, NS_GET_IID(nsIFile), getter_AddRefs(mPluginRegFile)); if (!mPluginRegFile) return NS_ERROR_FAILURE; else return NS_ERROR_NOT_AVAILABLE; } PRFileDesc* fd = nullptr; nsCOMPtr pluginReg; rv = mPluginRegFile->Clone(getter_AddRefs(pluginReg)); if (NS_FAILED(rv)) return rv; rv = pluginReg->AppendNative(kPluginRegistryFilename); if (NS_FAILED(rv)) return rv; int64_t fileSize; rv = pluginReg->GetFileSize(&fileSize); if (NS_FAILED(rv)) return rv; if (fileSize > INT32_MAX) { return NS_ERROR_FAILURE; } int32_t flen = int32_t(fileSize); if (flen == 0) { NS_WARNING("Plugins Registry Empty!"); return NS_OK; // ERROR CONDITION } nsPluginManifestLineReader reader; char* registry = reader.Init(flen); if (!registry) return NS_ERROR_OUT_OF_MEMORY; rv = pluginReg->OpenNSPRFileDesc(PR_RDONLY, 0444, &fd); if (NS_FAILED(rv)) return rv; // set rv to return an error on goto out rv = NS_ERROR_FAILURE; int32_t bread = PR_Read(fd, registry, flen); PRStatus prrc; prrc = PR_Close(fd); if (prrc != PR_SUCCESS) { // Strange error: this is one of those "Should not happen" error. // we may want to report something more refined than NS_ERROR_FAILURE. MOZ_ASSERT(false, "PR_Close() failed."); return rv; } if (flen > bread) return rv; if (!ReadSectionHeader(reader, "HEADER")) return rv;; if (!reader.NextLine()) return rv; char* values[6]; // VersionLiteral, kPluginRegistryVersion if (2 != reader.ParseLine(values, 2)) return rv; // VersionLiteral if (PL_strcmp(values[0], "Version")) return rv; // kPluginRegistryVersion int32_t vdiff = mozilla::CompareVersions(values[1], kPluginRegistryVersion); mozilla::Version version(values[1]); // If this is a registry from some future version then don't attempt to read it if (vdiff > 0) return rv; // If this is a registry from before the minimum then don't attempt to read it if (version < kMinimumRegistryVersion) return rv; // Registry v0.10 and upwards includes the plugin version field bool regHasVersion = (version >= "0.10"); // Registry v0.13 and upwards includes the architecture if (version >= "0.13") { char* archValues[6]; if (!reader.NextLine()) { return rv; } // ArchLiteral, Architecture if (2 != reader.ParseLine(archValues, 2)) { return rv; } // ArchLiteral if (PL_strcmp(archValues[0], "Arch")) { return rv; } nsCOMPtr runtime = do_GetService("@mozilla.org/xre/runtime;1"); if (!runtime) { return rv; } nsAutoCString arch; if (NS_FAILED(runtime->GetXPCOMABI(arch))) { return rv; } // If this is a registry from a different architecture then don't attempt to read it if (PL_strcmp(archValues[1], arch.get())) { return rv; } } // Registry v0.13 and upwards includes the list of invalid plugins bool hasInvalidPlugins = (version >= "0.13"); // Registry v0.16 and upwards always have 0 for their plugin flags, prefs are used instead const bool hasValidFlags = (version < "0.16"); if (!ReadSectionHeader(reader, "PLUGINS")) return rv; #if defined(XP_MACOSX) bool hasFullPathInFileNameField = false; #else bool hasFullPathInFileNameField = (version < "0.11"); #endif while (reader.NextLine()) { const char *filename; const char *fullpath; nsAutoCString derivedFileName; if (hasInvalidPlugins && *reader.LinePtr() == '[') { break; } if (hasFullPathInFileNameField) { fullpath = reader.LinePtr(); if (!reader.NextLine()) return rv; // try to derive a file name from the full path if (fullpath) { nsCOMPtr file = do_CreateInstance("@mozilla.org/file/local;1"); file->InitWithNativePath(nsDependentCString(fullpath)); file->GetNativeLeafName(derivedFileName); filename = derivedFileName.get(); } else { filename = nullptr; } // skip the next line, useless in this version if (!reader.NextLine()) return rv; } else { filename = reader.LinePtr(); if (!reader.NextLine()) return rv; fullpath = reader.LinePtr(); if (!reader.NextLine()) return rv; } const char *version; if (regHasVersion) { version = reader.LinePtr(); if (!reader.NextLine()) return rv; } else { version = "0"; } // lastModifiedTimeStamp|canUnload|tag.mFlag if (reader.ParseLine(values, 3) != 3) return rv; // If this is an old plugin registry mark this plugin tag to be refreshed int64_t lastmod = (vdiff == 0) ? nsCRT::atoll(values[0]) : -1; uint32_t tagflag = atoi(values[2]); if (!reader.NextLine()) return rv; char *description = reader.LinePtr(); if (!reader.NextLine()) return rv; #if MOZ_WIDGET_ANDROID // Flash on Android does not populate the version field, but it is tacked on to the description. // For example, "Shockwave Flash 11.1 r115" if (PL_strncmp("Shockwave Flash ", description, 16) == 0 && description[16]) { version = &description[16]; } #endif const char *name = reader.LinePtr(); if (!reader.NextLine()) return rv; long mimetypecount = std::strtol(reader.LinePtr(), nullptr, 10); if (mimetypecount == LONG_MAX || mimetypecount == LONG_MIN || mimetypecount >= PLUGIN_REG_MAX_MIMETYPES || mimetypecount < 0) { return NS_ERROR_FAILURE; } char *stackalloced[PLUGIN_REG_MIMETYPES_ARRAY_SIZE * 3]; char **mimetypes; char **mimedescriptions; char **extensions; char **heapalloced = 0; if (mimetypecount > PLUGIN_REG_MIMETYPES_ARRAY_SIZE - 1) { heapalloced = new char *[mimetypecount * 3]; mimetypes = heapalloced; } else { mimetypes = stackalloced; } mimedescriptions = mimetypes + mimetypecount; extensions = mimedescriptions + mimetypecount; int mtr = 0; //mimetype read for (; mtr < mimetypecount; mtr++) { if (!reader.NextLine()) break; //line number|mimetype|description|extension if (4 != reader.ParseLine(values, 4)) break; int line = atoi(values[0]); if (line != mtr) break; mimetypes[mtr] = values[1]; mimedescriptions[mtr] = values[2]; extensions[mtr] = values[3]; } if (mtr != mimetypecount) { if (heapalloced) { delete [] heapalloced; } return rv; } nsRefPtr tag = new nsPluginTag(name, description, filename, fullpath, version, (const char* const*)mimetypes, (const char* const*)mimedescriptions, (const char* const*)extensions, mimetypecount, lastmod, true); if (heapalloced) delete [] heapalloced; // Import flags from registry into prefs for old registry versions if (hasValidFlags && !pluginStateImported) { tag->ImportFlagsToPrefs(tagflag); } PR_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_BASIC, ("LoadCachedPluginsInfo : Loading Cached plugininfo for %s\n", tag->mFileName.get())); tag->mNext = mCachedPlugins; mCachedPlugins = tag; } // On Android we always want to try to load a plugin again (Flash). Bug 935676. #ifndef MOZ_WIDGET_ANDROID if (hasInvalidPlugins) { if (!ReadSectionHeader(reader, "INVALID")) { return rv; } while (reader.NextLine()) { const char *fullpath = reader.LinePtr(); if (!reader.NextLine()) { return rv; } const char *lastModifiedTimeStamp = reader.LinePtr(); int64_t lastmod = (vdiff == 0) ? nsCRT::atoll(lastModifiedTimeStamp) : -1; nsRefPtr invalidTag = new nsInvalidPluginTag(fullpath, lastmod); invalidTag->mNext = mInvalidPlugins; if (mInvalidPlugins) { mInvalidPlugins->mPrev = invalidTag; } mInvalidPlugins = invalidTag; } } #endif // flip the pref so we don't import the legacy flags again Preferences::SetBool("plugin.importedState", true); return NS_OK; } void nsPluginHost::RemoveCachedPluginsInfo(const char *filePath, nsPluginTag **result) { nsRefPtr prev; nsRefPtr tag = mCachedPlugins; while (tag) { if (tag->mFullPath.Equals(filePath)) { // Found it. Remove it from our list if (prev) prev->mNext = tag->mNext; else mCachedPlugins = tag->mNext; tag->mNext = nullptr; *result = tag; NS_ADDREF(*result); break; } prev = tag; tag = tag->mNext; } } #ifdef XP_WIN nsresult nsPluginHost::EnsurePrivateDirServiceProvider() { if (!mPrivateDirServiceProvider) { nsresult rv; mPrivateDirServiceProvider = new nsPluginDirServiceProvider(); if (!mPrivateDirServiceProvider) return NS_ERROR_OUT_OF_MEMORY; nsCOMPtr dirService(do_GetService(kDirectoryServiceContractID, &rv)); if (NS_FAILED(rv)) return rv; rv = dirService->RegisterProvider(mPrivateDirServiceProvider); if (NS_FAILED(rv)) return rv; } return NS_OK; } #endif /* XP_WIN */ nsresult nsPluginHost::NewPluginURLStream(const nsString& aURL, nsNPAPIPluginInstance *aInstance, nsNPAPIPluginStreamListener* aListener, nsIInputStream *aPostStream, const char *aHeadersData, uint32_t aHeadersDataLen) { nsCOMPtr url; nsAutoString absUrl; nsresult rv; if (aURL.Length() <= 0) return NS_OK; // get the base URI for the plugin to create an absolute url // in case aURL is relative nsRefPtr owner = aInstance->GetOwner(); if (owner) { rv = NS_MakeAbsoluteURI(absUrl, aURL, nsCOMPtr(owner->GetBaseURI())); } if (absUrl.IsEmpty()) absUrl.Assign(aURL); rv = NS_NewURI(getter_AddRefs(url), absUrl); if (NS_FAILED(rv)) return rv; nsCOMPtr element; nsCOMPtr doc; if (owner) { owner->GetDOMElement(getter_AddRefs(element)); owner->GetDocument(getter_AddRefs(doc)); } int16_t shouldLoad = nsIContentPolicy::ACCEPT; rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_OBJECT_SUBREQUEST, url, (doc ? doc->NodePrincipal() : nullptr), element, EmptyCString(), //mime guess nullptr, //extra &shouldLoad); if (NS_FAILED(rv)) return rv; if (NS_CP_REJECTED(shouldLoad)) { // Disallowed by content policy return NS_ERROR_CONTENT_BLOCKED; } nsRefPtr listenerPeer = new nsPluginStreamListenerPeer(); if (!listenerPeer) return NS_ERROR_OUT_OF_MEMORY; rv = listenerPeer->Initialize(url, aInstance, aListener); if (NS_FAILED(rv)) return rv; nsCOMPtr channel; rv = NS_NewChannel(getter_AddRefs(channel), url, nullptr, nullptr, /* do not add this internal plugin's channel on the load group otherwise this channel could be canceled form |nsDocShell::OnLinkClickSync| bug 166613 */ listenerPeer); if (NS_FAILED(rv)) return rv; if (doc) { // Set the owner of channel to the document principal... channel->SetOwner(doc->NodePrincipal()); // And if it's a script allow it to execute against the // document's script context. nsCOMPtr scriptChannel(do_QueryInterface(channel)); if (scriptChannel) { scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL); // Plug-ins seem to depend on javascript: URIs running synchronously scriptChannel->SetExecuteAsync(false); } } // deal with headers and post data nsCOMPtr httpChannel(do_QueryInterface(channel)); if (httpChannel) { if (!aPostStream) { // Only set the Referer header for GET requests because IIS throws // errors about malformed requests if we include it in POSTs. See // bug 724465. nsCOMPtr referer; nsCOMPtr olc = do_QueryInterface(element); if (olc) olc->GetSrcURI(getter_AddRefs(referer)); if (!referer) { if (!doc) { return NS_ERROR_FAILURE; } referer = doc->GetDocumentURI(); } rv = httpChannel->SetReferrer(referer); NS_ENSURE_SUCCESS(rv,rv); } if (aPostStream) { // XXX it's a bit of a hack to rewind the postdata stream // here but it has to be done in case the post data is // being reused multiple times. nsCOMPtr postDataSeekable(do_QueryInterface(aPostStream)); if (postDataSeekable) postDataSeekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); nsCOMPtr uploadChannel(do_QueryInterface(httpChannel)); NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel"); uploadChannel->SetUploadStream(aPostStream, EmptyCString(), -1); } if (aHeadersData) { rv = AddHeadersToChannel(aHeadersData, aHeadersDataLen, httpChannel); NS_ENSURE_SUCCESS(rv,rv); } } rv = channel->AsyncOpen(listenerPeer, nullptr); if (NS_SUCCEEDED(rv)) listenerPeer->TrackRequest(channel); return rv; } // Called by GetURL and PostURL nsresult nsPluginHost::DoURLLoadSecurityCheck(nsNPAPIPluginInstance *aInstance, const char* aURL) { if (!aURL || *aURL == '\0') return NS_OK; // get the base URI for the plugin element nsRefPtr owner = aInstance->GetOwner(); if (!owner) return NS_ERROR_FAILURE; nsCOMPtr baseURI = owner->GetBaseURI(); if (!baseURI) return NS_ERROR_FAILURE; // Create an absolute URL for the target in case the target is relative nsCOMPtr targetURL; NS_NewURI(getter_AddRefs(targetURL), aURL, baseURI); if (!targetURL) return NS_ERROR_FAILURE; nsCOMPtr doc; owner->GetDocument(getter_AddRefs(doc)); if (!doc) return NS_ERROR_FAILURE; nsresult rv; nsCOMPtr secMan( do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv)); if (NS_FAILED(rv)) return rv; return secMan->CheckLoadURIWithPrincipal(doc->NodePrincipal(), targetURL, nsIScriptSecurityManager::STANDARD); } nsresult nsPluginHost::AddHeadersToChannel(const char *aHeadersData, uint32_t aHeadersDataLen, nsIChannel *aGenericChannel) { nsresult rv = NS_OK; nsCOMPtr aChannel = do_QueryInterface(aGenericChannel); if (!aChannel) { return NS_ERROR_NULL_POINTER; } // used during the manipulation of the String from the aHeadersData nsAutoCString headersString; nsAutoCString oneHeader; nsAutoCString headerName; nsAutoCString headerValue; int32_t crlf = 0; int32_t colon = 0; // Turn the char * buffer into an nsString. headersString = aHeadersData; // Iterate over the nsString: for each "\r\n" delimited chunk, // add the value as a header to the nsIHTTPChannel while (true) { crlf = headersString.Find("\r\n", true); if (-1 == crlf) { rv = NS_OK; return rv; } headersString.Mid(oneHeader, 0, crlf); headersString.Cut(0, crlf + 2); oneHeader.StripWhitespace(); colon = oneHeader.Find(":"); if (-1 == colon) { rv = NS_ERROR_NULL_POINTER; return rv; } oneHeader.Left(headerName, colon); colon++; oneHeader.Mid(headerValue, colon, oneHeader.Length() - colon); // FINALLY: we can set the header! rv = aChannel->SetRequestHeader(headerName, headerValue, true); if (NS_FAILED(rv)) { rv = NS_ERROR_NULL_POINTER; return rv; } } return rv; } nsresult nsPluginHost::StopPluginInstance(nsNPAPIPluginInstance* aInstance) { if (PluginDestructionGuard::DelayDestroy(aInstance)) { return NS_OK; } PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsPluginHost::StopPluginInstance called instance=%p\n",aInstance)); if (aInstance->HasStartedDestroying()) { return NS_OK; } Telemetry::AutoTimer timer; aInstance->Stop(); // if the instance does not want to be 'cached' just remove it bool doCache = aInstance->ShouldCache(); if (doCache) { // try to get the max cached instances from a pref or use default uint32_t cachedInstanceLimit = Preferences::GetUint(NS_PREF_MAX_NUM_CACHED_INSTANCES, DEFAULT_NUMBER_OF_STOPPED_INSTANCES); if (StoppedInstanceCount() >= cachedInstanceLimit) { nsNPAPIPluginInstance *oldestInstance = FindOldestStoppedInstance(); if (oldestInstance) { nsPluginTag* pluginTag = TagForPlugin(oldestInstance->GetPlugin()); oldestInstance->Destroy(); mInstances.RemoveElement(oldestInstance); // TODO: Remove this check once bug 752422 was investigated if (pluginTag) { OnPluginInstanceDestroyed(pluginTag); } } } } else { nsPluginTag* pluginTag = TagForPlugin(aInstance->GetPlugin()); aInstance->Destroy(); mInstances.RemoveElement(aInstance); // TODO: Remove this check once bug 752422 was investigated if (pluginTag) { OnPluginInstanceDestroyed(pluginTag); } } return NS_OK; } nsresult nsPluginHost::NewPluginStreamListener(nsIURI* aURI, nsNPAPIPluginInstance* aInstance, nsIStreamListener **aStreamListener) { NS_ENSURE_ARG_POINTER(aURI); NS_ENSURE_ARG_POINTER(aStreamListener); nsRefPtr listener = new nsPluginStreamListenerPeer(); nsresult rv = listener->Initialize(aURI, aInstance, nullptr); if (NS_FAILED(rv)) { return rv; } listener.forget(aStreamListener); return NS_OK; } NS_IMETHODIMP nsPluginHost::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData) { if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) { OnShutdown(); UnloadPlugins(); sInst->Release(); } if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic)) { mPluginsDisabled = Preferences::GetBool("plugin.disable", false); mPluginsClickToPlay = Preferences::GetBool("plugins.click_to_play", false); // Unload or load plugins as needed if (mPluginsDisabled) { UnloadPlugins(); } else { LoadPlugins(); } } if (!strcmp("blocklist-updated", aTopic)) { nsPluginTag* plugin = mPlugins; while (plugin) { plugin->InvalidateBlocklistState(); plugin = plugin->mNext; } } #ifdef MOZ_WIDGET_ANDROID if (!strcmp("application-background", aTopic)) { for(uint32_t i = 0; i < mInstances.Length(); i++) { mInstances[i]->NotifyForeground(false); } } if (!strcmp("application-foreground", aTopic)) { for(uint32_t i = 0; i < mInstances.Length(); i++) { if (mInstances[i]->IsOnScreen()) mInstances[i]->NotifyForeground(true); } } if (!strcmp("memory-pressure", aTopic)) { for(uint32_t i = 0; i < mInstances.Length(); i++) { mInstances[i]->MemoryPressure(); } } #endif return NS_OK; } nsresult nsPluginHost::ParsePostBufferToFixHeaders(const char *inPostData, uint32_t inPostDataLen, char **outPostData, uint32_t *outPostDataLen) { if (!inPostData || !outPostData || !outPostDataLen) return NS_ERROR_NULL_POINTER; *outPostData = 0; *outPostDataLen = 0; const char CR = '\r'; const char LF = '\n'; const char CRLFCRLF[] = {CR,LF,CR,LF,'\0'}; // C string"\r\n\r\n" const char ContentLenHeader[] = "Content-length"; nsAutoTArray singleLF; const char *pSCntlh = 0;// pointer to start of ContentLenHeader in inPostData const char *pSod = 0; // pointer to start of data in inPostData const char *pEoh = 0; // pointer to end of headers in inPostData const char *pEod = inPostData + inPostDataLen; // pointer to end of inPostData if (*inPostData == LF) { // If no custom headers are required, simply add a blank // line ('\n') to the beginning of the file or buffer. // so *inPostData == '\n' is valid pSod = inPostData + 1; } else { const char *s = inPostData; //tmp pointer to sourse inPostData while (s < pEod) { if (!pSCntlh && (*s == 'C' || *s == 'c') && (s + sizeof(ContentLenHeader) - 1 < pEod) && (!PL_strncasecmp(s, ContentLenHeader, sizeof(ContentLenHeader) - 1))) { // lets assume this is ContentLenHeader for now const char *p = pSCntlh = s; p += sizeof(ContentLenHeader) - 1; // search for first CR or LF == end of ContentLenHeader for (; p < pEod; p++) { if (*p == CR || *p == LF) { // got delimiter, // one more check; if previous char is a digit // most likely pSCntlh points to the start of ContentLenHeader if (*(p-1) >= '0' && *(p-1) <= '9') { s = p; } break; //for loop } } if (pSCntlh == s) { // curret ptr is the same pSCntlh = 0; // that was not ContentLenHeader break; // there is nothing to parse, break *WHILE LOOP* here } } if (*s == CR) { if (pSCntlh && // only if ContentLenHeader is found we are looking for end of headers ((s + sizeof(CRLFCRLF)-1) <= pEod) && !memcmp(s, CRLFCRLF, sizeof(CRLFCRLF)-1)) { s += sizeof(CRLFCRLF)-1; pEoh = pSod = s; // data stars here break; } } else if (*s == LF) { if (*(s-1) != CR) { singleLF.AppendElement(s); } if (pSCntlh && (s+1 < pEod) && (*(s+1) == LF)) { s++; singleLF.AppendElement(s); s++; pEoh = pSod = s; // data stars here break; } } s++; } } // deal with output buffer if (!pSod) { // lets assume whole buffer is a data pSod = inPostData; } uint32_t newBufferLen = 0; uint32_t dataLen = pEod - pSod; uint32_t headersLen = pEoh ? pSod - inPostData : 0; char *p; // tmp ptr into new output buf if (headersLen) { // we got a headers // this function does not make any assumption on correctness // of ContentLenHeader value in this case. newBufferLen = dataLen + headersLen; // in case there were single LFs in headers // reserve an extra space for CR will be added before each single LF int cntSingleLF = singleLF.Length(); newBufferLen += cntSingleLF; if (!(*outPostData = p = (char*)nsMemory::Alloc(newBufferLen))) return NS_ERROR_OUT_OF_MEMORY; // deal with single LF const char *s = inPostData; if (cntSingleLF) { for (int i=0; i inFile; rv = NS_GetFileFromURLSpec(nsDependentCString(aPostDataURL), getter_AddRefs(inFile)); if (NS_FAILED(rv)) { nsCOMPtr localFile; rv = NS_NewNativeLocalFile(nsDependentCString(aPostDataURL), false, getter_AddRefs(localFile)); if (NS_FAILED(rv)) return rv; inFile = localFile; } rv = inFile->GetFileSize(&fileSize); if (NS_FAILED(rv)) return rv; rv = inFile->GetNativePath(filename); if (NS_FAILED(rv)) return rv; if (fileSize != 0) { nsCOMPtr inStream; rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream), inFile); if (NS_FAILED(rv)) return rv; // Create a temporary file to write the http Content-length: // %ld\r\n\" header and "\r\n" == end of headers for post data to nsCOMPtr tempFile; rv = GetPluginTempDir(getter_AddRefs(tempFile)); if (NS_FAILED(rv)) return rv; nsAutoCString inFileName; inFile->GetNativeLeafName(inFileName); // XXX hack around bug 70083 inFileName.Insert(NS_LITERAL_CSTRING("post-"), 0); rv = tempFile->AppendNative(inFileName); if (NS_FAILED(rv)) return rv; // make it unique, and mode == 0600, not world-readable rv = tempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); if (NS_FAILED(rv)) return rv; nsCOMPtr outStream; if (NS_SUCCEEDED(rv)) { rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), tempFile, (PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE), 0600); // 600 so others can't read our form data } NS_ASSERTION(NS_SUCCEEDED(rv), "Post data file couldn't be created!"); if (NS_FAILED(rv)) return rv; char buf[1024]; uint32_t br, bw; bool firstRead = true; while (1) { // Read() mallocs if buffer is null rv = inStream->Read(buf, 1024, &br); if (NS_FAILED(rv) || (int32_t)br <= 0) break; if (firstRead) { //"For protocols in which the headers must be distinguished from the body, // such as HTTP, the buffer or file should contain the headers, followed by // a blank line, then the body. If no custom headers are required, simply // add a blank line ('\n') to the beginning of the file or buffer. char *parsedBuf; // assuming first 1K (or what we got) has all headers in, // lets parse it through nsPluginHost::ParsePostBufferToFixHeaders() ParsePostBufferToFixHeaders((const char *)buf, br, &parsedBuf, &bw); rv = outStream->Write(parsedBuf, bw, &br); nsMemory::Free(parsedBuf); if (NS_FAILED(rv) || (bw != br)) break; firstRead = false; continue; } bw = br; rv = outStream->Write(buf, bw, &br); if (NS_FAILED(rv) || (bw != br)) break; } inStream->Close(); outStream->Close(); if (NS_SUCCEEDED(rv)) tempFile.forget(aTmpFile); } return rv; } nsresult nsPluginHost::NewPluginNativeWindow(nsPluginNativeWindow ** aPluginNativeWindow) { return PLUG_NewPluginNativeWindow(aPluginNativeWindow); } nsresult nsPluginHost::GetPluginName(nsNPAPIPluginInstance *aPluginInstance, const char** aPluginName) { nsNPAPIPluginInstance *instance = static_cast(aPluginInstance); if (!instance) return NS_ERROR_FAILURE; nsNPAPIPlugin* plugin = instance->GetPlugin(); if (!plugin) return NS_ERROR_FAILURE; *aPluginName = TagForPlugin(plugin)->mName.get(); return NS_OK; } nsresult nsPluginHost::GetPluginTagForInstance(nsNPAPIPluginInstance *aPluginInstance, nsIPluginTag **aPluginTag) { NS_ENSURE_ARG_POINTER(aPluginInstance); NS_ENSURE_ARG_POINTER(aPluginTag); nsNPAPIPlugin *plugin = aPluginInstance->GetPlugin(); if (!plugin) return NS_ERROR_FAILURE; *aPluginTag = TagForPlugin(plugin); NS_ADDREF(*aPluginTag); return NS_OK; } NS_IMETHODIMP nsPluginHost::Notify(nsITimer* timer) { nsRefPtr pluginTag = mPlugins; while (pluginTag) { if (pluginTag->mUnloadTimer == timer) { if (!IsRunningPlugin(pluginTag)) { pluginTag->TryUnloadPlugin(false); } return NS_OK; } pluginTag = pluginTag->mNext; } return NS_ERROR_FAILURE; } #ifdef XP_WIN // Re-enable any top level browser windows that were disabled by modal dialogs // displayed by the crashed plugin. static void CheckForDisabledWindows() { nsCOMPtr wm(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); if (!wm) return; nsCOMPtr windowList; wm->GetXULWindowEnumerator(nullptr, getter_AddRefs(windowList)); if (!windowList) return; bool haveWindows; do { windowList->HasMoreElements(&haveWindows); if (!haveWindows) return; nsCOMPtr supportsWindow; windowList->GetNext(getter_AddRefs(supportsWindow)); nsCOMPtr baseWin(do_QueryInterface(supportsWindow)); if (baseWin) { nsCOMPtr widget; baseWin->GetMainWidget(getter_AddRefs(widget)); if (widget && !widget->GetParent() && widget->IsVisible() && !widget->IsEnabled()) { nsIWidget* child = widget->GetFirstChild(); bool enable = true; while (child) { if (child->WindowType() == eWindowType_dialog) { enable = false; break; } child = child->GetNextSibling(); } if (enable) { widget->Enable(true); } } } } while (haveWindows); } #endif void nsPluginHost::PluginCrashed(nsNPAPIPlugin* aPlugin, const nsAString& pluginDumpID, const nsAString& browserDumpID) { nsPluginTag* crashedPluginTag = TagForPlugin(aPlugin); // Notify the app's observer that a plugin crashed so it can submit // a crashreport. bool submittedCrashReport = false; nsCOMPtr obsService = mozilla::services::GetObserverService(); nsCOMPtr propbag = do_CreateInstance("@mozilla.org/hash-property-bag;1"); if (obsService && propbag) { propbag->SetPropertyAsAString(NS_LITERAL_STRING("pluginDumpID"), pluginDumpID); propbag->SetPropertyAsAString(NS_LITERAL_STRING("browserDumpID"), browserDumpID); propbag->SetPropertyAsBool(NS_LITERAL_STRING("submittedCrashReport"), submittedCrashReport); obsService->NotifyObservers(propbag, "plugin-crashed", nullptr); // see if an observer submitted a crash report. propbag->GetPropertyAsBool(NS_LITERAL_STRING("submittedCrashReport"), &submittedCrashReport); } // Invalidate each nsPluginInstanceTag for the crashed plugin for (uint32_t i = mInstances.Length(); i > 0; i--) { nsNPAPIPluginInstance* instance = mInstances[i - 1]; if (instance->GetPlugin() == aPlugin) { // notify the content node (nsIObjectLoadingContent) that the // plugin has crashed nsCOMPtr domElement; instance->GetDOMElement(getter_AddRefs(domElement)); nsCOMPtr objectContent(do_QueryInterface(domElement)); if (objectContent) { objectContent->PluginCrashed(crashedPluginTag, pluginDumpID, browserDumpID, submittedCrashReport); } instance->Destroy(); mInstances.RemoveElement(instance); OnPluginInstanceDestroyed(crashedPluginTag); } } // Only after all instances have been invalidated is it safe to null // out nsPluginTag.mPlugin. The next time we try to create an // instance of this plugin we reload it (launch a new plugin process). crashedPluginTag->mPlugin = nullptr; #ifdef XP_WIN CheckForDisabledWindows(); #endif } nsNPAPIPluginInstance* nsPluginHost::FindInstance(const char *mimetype) { for (uint32_t i = 0; i < mInstances.Length(); i++) { nsNPAPIPluginInstance* instance = mInstances[i]; const char* mt; nsresult rv = instance->GetMIMEType(&mt); if (NS_FAILED(rv)) continue; if (PL_strcasecmp(mt, mimetype) == 0) return instance; } return nullptr; } nsNPAPIPluginInstance* nsPluginHost::FindOldestStoppedInstance() { nsNPAPIPluginInstance *oldestInstance = nullptr; TimeStamp oldestTime = TimeStamp::Now(); for (uint32_t i = 0; i < mInstances.Length(); i++) { nsNPAPIPluginInstance *instance = mInstances[i]; if (instance->IsRunning()) continue; TimeStamp time = instance->StopTime(); if (time < oldestTime) { oldestTime = time; oldestInstance = instance; } } return oldestInstance; } uint32_t nsPluginHost::StoppedInstanceCount() { uint32_t stoppedCount = 0; for (uint32_t i = 0; i < mInstances.Length(); i++) { nsNPAPIPluginInstance *instance = mInstances[i]; if (!instance->IsRunning()) stoppedCount++; } return stoppedCount; } nsTArray< nsRefPtr >* nsPluginHost::InstanceArray() { return &mInstances; } void nsPluginHost::DestroyRunningInstances(nsPluginTag* aPluginTag) { for (int32_t i = mInstances.Length(); i > 0; i--) { nsNPAPIPluginInstance *instance = mInstances[i - 1]; if (instance->IsRunning() && (!aPluginTag || aPluginTag == TagForPlugin(instance->GetPlugin()))) { instance->SetWindow(nullptr); instance->Stop(); // Get rid of all the instances without the possibility of caching. nsPluginTag* pluginTag = TagForPlugin(instance->GetPlugin()); instance->SetWindow(nullptr); nsCOMPtr domElement; instance->GetDOMElement(getter_AddRefs(domElement)); nsCOMPtr objectContent = do_QueryInterface(domElement); instance->Destroy(); mInstances.RemoveElement(instance); OnPluginInstanceDestroyed(pluginTag); // Notify owning content that we destroyed its plugin out from under it if (objectContent) { objectContent->PluginDestroyed(); } } } } // Runnable that does an async destroy of a plugin. class nsPluginDestroyRunnable : public nsRunnable, public PRCList { public: nsPluginDestroyRunnable(nsNPAPIPluginInstance *aInstance) : mInstance(aInstance) { PR_INIT_CLIST(this); PR_APPEND_LINK(this, &sRunnableListHead); } virtual ~nsPluginDestroyRunnable() { PR_REMOVE_LINK(this); } NS_IMETHOD Run() { nsRefPtr instance; // Null out mInstance to make sure this code in another runnable // will do the right thing even if someone was holding on to this // runnable longer than we expect. instance.swap(mInstance); if (PluginDestructionGuard::DelayDestroy(instance)) { // It's still not safe to destroy the plugin, it's now up to the // outermost guard on the stack to take care of the destruction. return NS_OK; } nsPluginDestroyRunnable *r = static_cast(PR_NEXT_LINK(&sRunnableListHead)); while (r != &sRunnableListHead) { if (r != this && r->mInstance == instance) { // There's another runnable scheduled to tear down // instance. Let it do the job. return NS_OK; } r = static_cast(PR_NEXT_LINK(r)); } PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("Doing delayed destroy of instance %p\n", instance.get())); nsRefPtr host = nsPluginHost::GetInst(); if (host) host->StopPluginInstance(instance); PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("Done with delayed destroy of instance %p\n", instance.get())); return NS_OK; } protected: nsRefPtr mInstance; static PRCList sRunnableListHead; }; PRCList nsPluginDestroyRunnable::sRunnableListHead = PR_INIT_STATIC_CLIST(&nsPluginDestroyRunnable::sRunnableListHead); PRCList PluginDestructionGuard::sListHead = PR_INIT_STATIC_CLIST(&PluginDestructionGuard::sListHead); PluginDestructionGuard::PluginDestructionGuard(nsNPAPIPluginInstance *aInstance) : mInstance(aInstance) { Init(); } PluginDestructionGuard::PluginDestructionGuard(NPP npp) : mInstance(npp ? static_cast(npp->ndata) : nullptr) { Init(); } PluginDestructionGuard::~PluginDestructionGuard() { NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread"); PR_REMOVE_LINK(this); if (mDelayedDestroy) { // We've attempted to destroy the plugin instance we're holding on // to while we were guarding it. Do the actual destroy now, off of // a runnable. nsRefPtr evt = new nsPluginDestroyRunnable(mInstance); NS_DispatchToMainThread(evt); } } // static bool PluginDestructionGuard::DelayDestroy(nsNPAPIPluginInstance *aInstance) { NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread"); NS_ASSERTION(aInstance, "Uh, I need an instance!"); // Find the first guard on the stack and make it do a delayed // destroy upon destruction. PluginDestructionGuard *g = static_cast(PR_LIST_HEAD(&sListHead)); while (g != &sListHead) { if (g->mInstance == aInstance) { g->mDelayedDestroy = true; return true; } g = static_cast(PR_NEXT_LINK(g)); } return false; }