diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/plugins/base/nsPluginHost.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/plugins/base/nsPluginHost.cpp')
-rw-r--r-- | dom/plugins/base/nsPluginHost.cpp | 4239 |
1 files changed, 4239 insertions, 0 deletions
diff --git a/dom/plugins/base/nsPluginHost.cpp b/dom/plugins/base/nsPluginHost.cpp new file mode 100644 index 000000000..6ee23f38b --- /dev/null +++ b/dom/plugins/base/nsPluginHost.cpp @@ -0,0 +1,4239 @@ +/* -*- 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 <cstdlib> +#include <stdio.h> +#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 "nsProtocolProxyService.h" +#include "nsIStreamConverterService.h" +#include "nsIFile.h" +#if defined(XP_MACOSX) +#include "nsILocalFileMac.h" +#endif +#include "nsISeekableStream.h" +#include "nsNetUtil.h" +#include "nsIFileStreams.h" +#include "nsISimpleEnumerator.h" +#include "nsIStringStream.h" +#include "nsIProgressEventSink.h" +#include "nsIDocument.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/dom/ContentChild.h" +#include "mozilla/dom/FakePluginTagInitBinding.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/plugins/PluginAsyncSurrogate.h" +#include "mozilla/plugins/PluginBridge.h" +#include "mozilla/plugins/PluginTypes.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 "mozilla/Sprintf.h" +#include "nsThreadUtils.h" +#include "nsIInputStreamTee.h" +#include "nsQueryObject.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 "nsIContentPolicy.h" +#include "nsContentPolicyUtils.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Telemetry.h" +#include "nsIImageLoadingContent.h" +#include "mozilla/Preferences.h" +#include "nsVersionComparator.h" +#include "nsNullPrincipal.h" + +#if defined(XP_WIN) +#include "nsIWindowMediator.h" +#include "nsIBaseWindow.h" +#include "windows.h" +#include "winbase.h" +#endif + +#ifdef ANDROID +#include <android/log.h> +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) +#endif + +#if MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif + +#include "npapi.h" + +using namespace mozilla; +using mozilla::TimeStamp; +using mozilla::plugins::PluginTag; +using mozilla::plugins::PluginAsyncSurrogate; +using mozilla::dom::FakePluginTagInit; + +// 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 *kPrefLoadInParentPrefix = "plugin.load_in_parent_process."; +static const char *kPrefDisableFullPage = "plugin.disable_full_page_plugin_for_types"; +static const char *kPrefJavaMIME = "plugin.java.mime"; + +// How long we wait before unloading an idle plugin process. +// Defaults to 30 seconds. +static const char *kPrefUnloadPluginTimeoutSecs = "dom.ipc.plugins.unloadTimeoutSecs"; +static const uint32_t kDefaultPluginUnloadingTimeout = 30; + +static const char *kPluginRegistryVersion = "0.18"; + +static const char kDirectoryServiceContractID[] = "@mozilla.org/file/directory_service;1"; + +#define kPluginRegistryFilename NS_LITERAL_CSTRING("pluginreg.dat") + +#ifdef PLUGIN_LOGGING +LazyLogModule nsPluginLogging::gNPNLog(NPN_LOG_NAME); +LazyLogModule nsPluginLogging::gNPPLog(NPP_LOG_NAME); +LazyLogModule nsPluginLogging::gPluginLog(PLUGIN_LOG_NAME); +#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; + +/* to cope with short read */ +/* we should probably put this into a global library now that this is the second + time we need this. */ +static +int32_t +busy_beaver_PR_Read(PRFileDesc *fd, void * start, int32_t len) +{ + int n; + int32_t remaining = len; + + while (remaining > 0) + { + n = PR_Read(fd, start, remaining); + if (n < 0) + { + /* may want to repeat if errno == EINTR */ + if( (len - remaining) == 0 ) // no octet is ever read + return -1; + break; + } + else + { + remaining -= n; + char *cp = (char *) start; + cp += n; + start = cp; + } + } + return len - remaining; +} + +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(const 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(','); + + // Lower-case the search string and MIME type to properly handle a mixed-case + // type, as MIME types are case insensitive. + ToLowerCase(searchStr); + ToLowerCase(commaSeparated); + + 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::GetUint(kPrefUnloadPluginTimeoutSecs, kDefaultPluginUnloadingTimeout) == 0); +} + +nsPluginHost::nsPluginHost() + // No need to initialize members to nullptr, false etc because this class + // has a zeroing operator new. +{ + // Bump the pluginchanged epoch on startup. This insures content gets a + // good plugin list the first time it requests it. Normally we'd just + // init this to 1, but due to the unique nature of our ctor we need to do + // this manually. + if (XRE_IsParentProcess()) { + IncrementChromeEpoch(); + } else { + // When NPAPI requests the proxy setting by calling |FindProxyForURL|, + // the service is requested and initialized asynchronously, but + // |FindProxyForURL| is synchronous, so we should initialize this earlier. + nsCOMPtr<nsIProtocolProxyService> proxyService = + do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID); + } + + // 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); + + Preferences::AddStrongObserver(this, "plugin.disable"); + + nsCOMPtr<nsIObserverService> 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 + MOZ_LOG(nsPluginLogging::gNPNLog, PLUGIN_LOG_ALWAYS,("NPN Logging Active!\n")); + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_ALWAYS,("General Plugin Logging Active! (nsPluginHost::ctor)\n")); + MOZ_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_ISUPPORTS(nsPluginHost, + nsIPluginHost, + nsIObserver, + nsITimerCallback, + nsISupportsWeakReference) + +already_AddRefed<nsPluginHost> +nsPluginHost::GetInst() +{ + if (!sInst) { + sInst = new nsPluginHost(); + if (!sInst) + return nullptr; + NS_ADDREF(sInst); + } + + RefPtr<nsPluginHost> inst = sInst; + return inst.forget(); +} + +bool nsPluginHost::IsRunningPlugin(nsPluginTag * aPluginTag) +{ + if (!aPluginTag || !aPluginTag->mPlugin) { + return false; + } + + if (aPluginTag->mContentProcessRunningCount) { + return true; + } + + 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 + RefPtr<nsPluginTag> prev; + RefPtr<nsPluginTag> next; + + for (RefPtr<nsPluginTag> 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<nsIHttpProtocolHandler> 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<nsNPAPIPluginInstance*>(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 = NS_OK; + + if (target) { + RefPtr<nsPluginInstanceOwner> owner = pluginInst->GetOwner(); + if (owner) { + rv = owner->GetURL(url, target, nullptr, nullptr, 0, true); + } + } + + 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<nsNPAPIPluginInstance*>(pluginInst); + + nsCOMPtr<nsIInputStream> postStream; + if (isFile) { + nsCOMPtr<nsIFile> file; + rv = CreateTempFileToPost(postData, getter_AddRefs(file)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIInputStream> 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<nsIStringInputStream> sis = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); + if (!sis) { + 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) { + RefPtr<nsPluginInstanceOwner> owner = instance->GetOwner(); + if (owner) { + rv = owner->GetURL(url, target, postStream, + (void*)postHeaders, postHeadersLength, true); + } + } + + // if we don't have a target, just create a stream. + 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<nsIProtocolProxyService> proxyService = + do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &res); + if (NS_FAILED(res) || !proxyService) + return res; + + RefPtr<nsProtocolProxyService> rawProxyService = do_QueryObject(proxyService); + if (!rawProxyService) { + return NS_ERROR_FAILURE; + } + + // make a temporary channel from the argument url + nsCOMPtr<nsIURI> uri; + res = NS_NewURI(getter_AddRefs(uri), nsDependentCString(url)); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr<nsIPrincipal> nullPrincipal = nsNullPrincipal::Create(); + // The following channel is never openend, so it does not matter what + // securityFlags we pass; let's follow the principle of least privilege. + nsCOMPtr<nsIChannel> tempChannel; + res = NS_NewChannel(getter_AddRefs(tempChannel), uri, nullPrincipal, + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED, + nsIContentPolicy::TYPE_OTHER); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr<nsIProxyInfo> pi; + + // Remove this deprecated call in the future (see Bug 778201): + res = rawProxyService->DeprecatedBlockingResolve(tempChannel, 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::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(RefPtr<nsPluginTag>, mPlugins, mNext); + NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext); + NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, 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<nsIDirectoryService> 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 after a pref-controlled + // delay once 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); + } + uint32_t unloadTimeout = Preferences::GetUint(kPrefUnloadPluginTimeoutSecs, + kDefaultPluginUnloadingTimeout); + aPluginTag->mUnloadTimer->InitWithCallback(this, + 1000 * unloadTimeout, + nsITimer::TYPE_ONE_SHOT); + } + } +} + +nsresult +nsPluginHost::GetPluginTempDir(nsIFile **aDir) +{ + if (!sPluginTempDir) { + nsCOMPtr<nsIFile> 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 nsACString& aMimeType, nsIURI* aURL, + nsObjectLoadingContent *aContent, + nsPluginInstanceOwner** aOwner) +{ + NS_ENSURE_ARG_POINTER(aOwner); + +#ifdef PLUGIN_LOGGING + nsAutoCString urlSpec; + if (aURL) + aURL->GetAsciiSpec(urlSpec); + + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, + ("nsPluginHost::InstantiatePlugin Begin mime=%s, url=%s\n", + PromiseFlatCString(aMimeType).get(), urlSpec.get())); + + PR_LogFlush(); +#endif + + if (aMimeType.IsEmpty()) { + NS_NOTREACHED("Attempting to spawn a plugin with no mime type"); + return NS_ERROR_FAILURE; + } + + RefPtr<nsPluginInstanceOwner> instanceOwner = new nsPluginInstanceOwner(); + if (!instanceOwner) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCOMPtr<nsIContent> ourContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(aContent)); + nsresult rv = instanceOwner->Init(ourContent); + if (NS_FAILED(rv)) { + return rv; + } + + nsPluginTagType tagType; + rv = instanceOwner->GetTagType(&tagType); + if (NS_FAILED(rv)) { + instanceOwner->Destroy(); + return rv; + } + + if (tagType != nsPluginTagType_Embed && + tagType != nsPluginTagType_Applet && + tagType != nsPluginTagType_Object) { + instanceOwner->Destroy(); + return NS_ERROR_FAILURE; + } + + rv = SetUpPluginInstance(aMimeType, aURL, instanceOwner); + if (NS_FAILED(rv)) { + instanceOwner->Destroy(); + return NS_ERROR_FAILURE; + } + const bool isAsyncInit = (rv == NS_PLUGIN_INIT_PENDING); + + RefPtr<nsNPAPIPluginInstance> instance; + rv = instanceOwner->GetInstance(getter_AddRefs(instance)); + if (NS_FAILED(rv)) { + instanceOwner->Destroy(); + return rv; + } + + // Async init plugins will initiate their own widget creation. + if (!isAsyncInit && instance) { + CreateWidget(instanceOwner); + } + + // 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); + + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, + ("nsPluginHost::InstantiatePlugin Finished mime=%s, rv=%d, url=%s\n", + PromiseFlatCString(aMimeType).get(), 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 nsACString &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<nsIDocument> document; + aOwner->GetDocument(getter_AddRefs(document)); + + nsCOMPtr<nsIDocument> 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 nsACString &aMimeType, + nsIURI *aURL, + nsPluginInstanceOwner *aOwner) +{ +#ifdef PLUGIN_LOGGING + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, + ("nsPluginHost::TrySetupPluginInstance Begin mime=%s, owner=%p, url=%s\n", + PromiseFlatCString(aMimeType).get(), aOwner, + aURL ? aURL->GetSpecOrDefault().get() : "")); + + PR_LogFlush(); +#endif + +#ifdef XP_WIN + bool changed; + if ((mRegKeyHKLM && NS_SUCCEEDED(mRegKeyHKLM->HasChanged(&changed)) && changed) || + (mRegKeyHKCU && NS_SUCCEEDED(mRegKeyHKCU->HasChanged(&changed)) && changed)) { + ReloadPlugins(); + } +#endif + + RefPtr<nsNPAPIPlugin> plugin; + GetPlugin(aMimeType, getter_AddRefs(plugin)); + if (!plugin) { + return NS_ERROR_FAILURE; + } + + nsPluginTag* pluginTag = FindNativePluginForType(aMimeType, true); + + NS_ASSERTION(pluginTag, "Must have plugin tag here!"); + + plugin->GetLibrary()->SetHasLocalInstance(); + +#if defined(MOZ_WIDGET_ANDROID) && defined(MOZ_CRASHREPORTER) + if (pluginTag->mIsFlashPlugin) { + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("FlashVersion"), pluginTag->Version()); + } +#endif + + RefPtr<nsNPAPIPluginInstance> 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 + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_BASIC, + ("nsPluginHost::TrySetupPluginInstance Finished mime=%s, rv=%d, owner=%p, url=%s\n", + PromiseFlatCString(aMimeType).get(), rv, aOwner, + aURL ? aURL->GetSpecOrDefault().get() : "")); + + PR_LogFlush(); +#endif + + return rv; +} + +bool +nsPluginHost::HavePluginForType(const nsACString & aMimeType, + PluginFilter aFilter) +{ + bool checkEnabled = aFilter & eExcludeDisabled; + bool allowFake = !(aFilter & eExcludeFake); + return FindPluginForType(aMimeType, allowFake, checkEnabled); +} + +nsIInternalPluginTag* +nsPluginHost::FindPluginForType(const nsACString& aMimeType, + bool aIncludeFake, bool aCheckEnabled) +{ + if (aIncludeFake) { + nsFakePluginTag* fakeTag = FindFakePluginForType(aMimeType, aCheckEnabled); + if (fakeTag) { + return fakeTag; + } + } + + return FindNativePluginForType(aMimeType, aCheckEnabled); +} + +NS_IMETHODIMP +nsPluginHost::GetPluginTagForType(const nsACString& aMimeType, + uint32_t aExcludeFlags, + nsIPluginTag** aResult) +{ + bool includeFake = !(aExcludeFlags & eExcludeFake); + bool includeDisabled = !(aExcludeFlags & eExcludeDisabled); + + // First look for an enabled plugin. + RefPtr<nsIInternalPluginTag> tag = FindPluginForType(aMimeType, includeFake, + true); + if (!tag && includeDisabled) { + tag = FindPluginForType(aMimeType, includeFake, false); + } + + if (tag) { + tag.forget(aResult); + return NS_OK; + } + + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsPluginHost::GetStateForType(const nsACString &aMimeType, + uint32_t aExcludeFlags, + uint32_t* aResult) +{ + nsCOMPtr<nsIPluginTag> tag; + nsresult rv = GetPluginTagForType(aMimeType, aExcludeFlags, + getter_AddRefs(tag)); + NS_ENSURE_SUCCESS(rv, rv); + + return tag->GetEnabledState(aResult); +} + +NS_IMETHODIMP +nsPluginHost::GetBlocklistStateForType(const nsACString &aMimeType, + uint32_t aExcludeFlags, + uint32_t *aState) +{ + nsCOMPtr<nsIPluginTag> tag; + nsresult rv = GetPluginTagForType(aMimeType, + aExcludeFlags, + getter_AddRefs(tag)); + NS_ENSURE_SUCCESS(rv, rv); + return tag->GetBlocklistState(aState); +} + +NS_IMETHODIMP +nsPluginHost::GetPermissionStringForType(const nsACString &aMimeType, + uint32_t aExcludeFlags, + nsACString &aPermissionString) +{ + nsCOMPtr<nsIPluginTag> tag; + nsresult rv = GetPluginTagForType(aMimeType, aExcludeFlags, + getter_AddRefs(tag)); + NS_ENSURE_SUCCESS(rv, rv); + return GetPermissionStringForTag(tag, aExcludeFlags, aPermissionString); +} + +NS_IMETHODIMP +nsPluginHost::GetPermissionStringForTag(nsIPluginTag* aTag, + uint32_t aExcludeFlags, + nsACString &aPermissionString) +{ + NS_ENSURE_TRUE(aTag, NS_ERROR_FAILURE); + + aPermissionString.Truncate(); + uint32_t blocklistState; + nsresult rv = aTag->GetBlocklistState(&blocklistState); + NS_ENSURE_SUCCESS(rv, rv); + + if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE || + blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) { + aPermissionString.AssignLiteral("plugin-vulnerable:"); + } + else { + aPermissionString.AssignLiteral("plugin:"); + } + + nsCString niceName; + rv = aTag->GetNiceName(niceName); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(!niceName.IsEmpty(), NS_ERROR_FAILURE); + + aPermissionString.Append(niceName); + + return NS_OK; +} + +bool +nsPluginHost::HavePluginForExtension(const nsACString & aExtension, + /* out */ nsACString & aMimeType, + PluginFilter aFilter) +{ + bool checkEnabled = aFilter & eExcludeDisabled; + bool allowFake = !(aFilter & eExcludeFake); + return FindNativePluginForExtension(aExtension, aMimeType, checkEnabled) || + (allowFake && + FindFakePluginForExtension(aExtension, aMimeType, checkEnabled)); +} + +void +nsPluginHost::GetPlugins(nsTArray<nsCOMPtr<nsIInternalPluginTag>>& aPluginArray, + bool aIncludeDisabled) +{ + aPluginArray.Clear(); + + LoadPlugins(); + + // Append fake plugins, then normal plugins. + + uint32_t numFake = mFakePlugins.Length(); + for (uint32_t i = 0; i < numFake; i++) { + aPluginArray.AppendElement(mFakePlugins[i]); + } + + // Regular plugins + nsPluginTag* plugin = mPlugins; + while (plugin != nullptr) { + if (plugin->IsEnabled() || aIncludeDisabled) { + aPluginArray.AppendElement(plugin); + } + plugin = plugin->mNext; + } +} + +// FIXME-jsplugins Check users for order of fake v non-fake +NS_IMETHODIMP +nsPluginHost::GetPluginTags(uint32_t* aPluginCount, nsIPluginTag*** aResults) +{ + LoadPlugins(); + + uint32_t count = 0; + uint32_t fakeCount = mFakePlugins.Length(); + RefPtr<nsPluginTag> plugin = mPlugins; + while (plugin != nullptr) { + count++; + plugin = plugin->mNext; + } + + *aResults = static_cast<nsIPluginTag**> + (moz_xmalloc((fakeCount + count) * sizeof(**aResults))); + if (!*aResults) + return NS_ERROR_OUT_OF_MEMORY; + + *aPluginCount = count + fakeCount; + + plugin = mPlugins; + for (uint32_t i = 0; i < count; i++) { + (*aResults)[i] = plugin; + NS_ADDREF((*aResults)[i]); + plugin = plugin->mNext; + } + + for (uint32_t i = 0; i < fakeCount; i++) { + (*aResults)[i + count] = static_cast<nsIInternalPluginTag*>(mFakePlugins[i]); + NS_ADDREF((*aResults)[i + count]); + } + + return NS_OK; +} + +nsPluginTag* +nsPluginHost::FindPreferredPlugin(const InfallibleTArray<nsPluginTag*>& 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]->Version().get()) > preferredPlugin->Version().get()) { + preferredPlugin = matches[i]; + } + } + + return preferredPlugin; +} + +nsFakePluginTag* +nsPluginHost::FindFakePluginForExtension(const nsACString & aExtension, + /* out */ nsACString & aMimeType, + bool aCheckEnabled) +{ + if (aExtension.IsEmpty()) { + return nullptr; + } + + int32_t numFakePlugins = mFakePlugins.Length(); + for (int32_t i = 0; i < numFakePlugins; i++) { + nsFakePluginTag *plugin = mFakePlugins[i]; + bool active; + if ((!aCheckEnabled || + (NS_SUCCEEDED(plugin->GetActive(&active)) && active)) && + plugin->HasExtension(aExtension, aMimeType)) { + return plugin; + } + } + + return nullptr; +} + +nsFakePluginTag* +nsPluginHost::FindFakePluginForType(const nsACString & aMimeType, + bool aCheckEnabled) +{ + int32_t numFakePlugins = mFakePlugins.Length(); + for (int32_t i = 0; i < numFakePlugins; i++) { + nsFakePluginTag *plugin = mFakePlugins[i]; + bool active; + if ((!aCheckEnabled || + (NS_SUCCEEDED(plugin->GetActive(&active)) && active)) && + plugin->HasMimeType(aMimeType)) { + return plugin; + } + } + + return nullptr; +} + +nsPluginTag* +nsPluginHost::FindNativePluginForType(const nsACString & aMimeType, + bool aCheckEnabled) +{ + if (aMimeType.IsEmpty()) { + return nullptr; + } + + LoadPlugins(); + + InfallibleTArray<nsPluginTag*> matchingPlugins; + + nsPluginTag *plugin = mPlugins; + while (plugin) { + if ((!aCheckEnabled || plugin->IsActive()) && + plugin->HasMimeType(aMimeType)) { + matchingPlugins.AppendElement(plugin); + } + plugin = plugin->mNext; + } + + return FindPreferredPlugin(matchingPlugins); +} + +nsPluginTag* +nsPluginHost::FindNativePluginForExtension(const nsACString & aExtension, + /* out */ nsACString & aMimeType, + bool aCheckEnabled) +{ + if (aExtension.IsEmpty()) { + return nullptr; + } + + LoadPlugins(); + + InfallibleTArray<nsPluginTag*> matchingPlugins; + nsCString matchingMime; // Don't mutate aMimeType unless returning a match + nsPluginTag *plugin = mPlugins; + + while (plugin) { + if (!aCheckEnabled || plugin->IsActive()) { + if (plugin->HasExtension(aExtension, matchingMime)) { + matchingPlugins.AppendElement(plugin); + } + } + plugin = plugin->mNext; + } + + nsPluginTag *preferredPlugin = FindPreferredPlugin(matchingPlugins); + if (!preferredPlugin) { + return nullptr; + } + + // Re-fetch the matching type because of how FindPreferredPlugin works... + preferredPlugin->HasExtension(aExtension, aMimeType); + 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<nsIFile> 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) +{ + RefPtr<nsNPAPIPlugin> 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::GetPluginForContentProcess(uint32_t aPluginId, nsNPAPIPlugin** aPlugin) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + MOZ_ASSERT(XRE_IsParentProcess()); + + // If plugins haven't been scanned yet, do so now + LoadPlugins(); + + nsPluginTag* pluginTag = PluginWithId(aPluginId); + if (pluginTag) { + // When setting up a bridge, double check with chrome to see if this plugin + // is blocked hard. Note this does not protect against vulnerable plugins + // that the user has explicitly allowed. :( + if (pluginTag->IsBlocklisted()) { + return NS_ERROR_PLUGIN_BLOCKLISTED; + } + + nsresult rv = EnsurePluginLoaded(pluginTag); + if (NS_FAILED(rv)) { + return rv; + } + + // We only get here if a content process doesn't have a PluginModuleParent + // for the given plugin already. Therefore, this counter is counting the + // number of outstanding PluginModuleParents for the plugin, excluding the + // one from the chrome process. + pluginTag->mContentProcessRunningCount++; + NS_ADDREF(*aPlugin = pluginTag->mPlugin); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +class nsPluginUnloadRunnable : public Runnable +{ +public: + explicit nsPluginUnloadRunnable(uint32_t aPluginId) : mPluginId(aPluginId) {} + + NS_IMETHOD Run() override + { + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + if (!host) { + return NS_OK; + } + nsPluginTag* pluginTag = host->PluginWithId(mPluginId); + if (!pluginTag) { + return NS_OK; + } + + MOZ_ASSERT(pluginTag->mContentProcessRunningCount > 0); + pluginTag->mContentProcessRunningCount--; + + if (!pluginTag->mContentProcessRunningCount) { + if (!host->IsRunningPlugin(pluginTag)) { + pluginTag->TryUnloadPlugin(false); + } + } + return NS_OK; + } + +protected: + uint32_t mPluginId; +}; + +void +nsPluginHost::NotifyContentModuleDestroyed(uint32_t aPluginId) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + // This is called in response to a message from the plugin. Don't unload the + // plugin until the message handler is off the stack. + RefPtr<nsPluginUnloadRunnable> runnable = + new nsPluginUnloadRunnable(aPluginId); + NS_DispatchToMainThread(runnable); +} + +nsresult nsPluginHost::GetPlugin(const nsACString &aMimeType, + nsNPAPIPlugin** aPlugin) +{ + nsresult rv = NS_ERROR_FAILURE; + *aPlugin = nullptr; + + // If plugins haven't been scanned yet, do so now + LoadPlugins(); + + nsPluginTag* pluginTag = FindNativePluginForType(aMimeType, true); + if (pluginTag) { + rv = NS_OK; + PLUGIN_LOG(PLUGIN_LOG_BASIC, + ("nsPluginHost::GetPlugin Begin mime=%s, plugin=%s\n", + PromiseFlatCString(aMimeType).get(), pluginTag->FileName().get())); + +#ifdef DEBUG + if (!pluginTag->FileName().IsEmpty()) + printf("For %s found plugin %s\n", + PromiseFlatCString(aMimeType).get(), pluginTag->FileName().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", + PromiseFlatCString(aMimeType).get(), rv, *aPlugin, + (pluginTag ? pluginTag->FileName().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<nsCString>& sites, + InfallibleTArray<nsCString>& 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::RegisterFakePlugin(JS::Handle<JS::Value> aInitDictionary, + JSContext* aCx, + nsIFakePluginTag **aResult) +{ + FakePluginTagInit initDictionary; + if (!initDictionary.Init(aCx, aInitDictionary)) { + return NS_ERROR_FAILURE; + } + + RefPtr<nsFakePluginTag> newTag; + nsresult rv = nsFakePluginTag::Create(initDictionary, getter_AddRefs(newTag)); + NS_ENSURE_SUCCESS(rv, rv); + + for (auto existingTag : mFakePlugins) { + if (newTag->HandlerURIMatches(existingTag->HandlerURI())) { + return NS_ERROR_UNEXPECTED; + } + } + + mFakePlugins.AppendElement(newTag); + // FIXME-jsplugins do we need to register with the category manager here? For + // shumway, for now, probably not. + newTag.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsPluginHost::UnregisterFakePlugin(const nsACString& aHandlerURI) +{ + nsCOMPtr<nsIURI> handlerURI; + nsresult rv = NS_NewURI(getter_AddRefs(handlerURI), aHandlerURI); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < mFakePlugins.Length(); ++i) { + if (mFakePlugins[i]->HandlerURIMatches(handlerURI)) { + mFakePlugins.RemoveElementAt(i); + return NS_OK; + } + } + + return NS_OK; +} + +// FIXME-jsplugins Is this method actually needed? +NS_IMETHODIMP +nsPluginHost::GetFakePlugin(const nsACString & aMimeType, + nsIFakePluginTag** aResult) +{ + RefPtr<nsFakePluginTag> result = FindFakePluginForType(aMimeType, false); + if (result) { + result.forget(aResult); + return NS_OK; + } + + *aResult = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +#define ClearDataFromSitesClosure_CID {0x9fb21761, 0x2403, 0x41ad, {0x9e, 0xfd, 0x36, 0x7e, 0xc4, 0x4f, 0xa4, 0x5e}} + + +// Class to hold all the data we need need for IterateMatchesAndClear and ClearDataFromSites +class ClearDataFromSitesClosure : public nsIClearSiteDataCallback, public nsIGetSitesWithDataCallback { +public: + ClearDataFromSitesClosure(nsIPluginTag* plugin, const nsACString& domain, uint64_t flags, + int64_t maxAge, nsCOMPtr<nsIClearSiteDataCallback> callback, + nsPluginHost* host) : + domain(domain), callback(callback), tag(plugin), flags(flags), maxAge(maxAge), host(host) {} + NS_DECL_ISUPPORTS + + // Callback from NPP_ClearSiteData, continue to iterate the matches and clear + NS_IMETHOD Callback(nsresult rv) override { + if (NS_FAILED(rv)) { + callback->Callback(rv); + return NS_OK; + } + if (!matches.Length()) { + callback->Callback(NS_OK); + return NS_OK; + } + + const nsCString match(matches[0]); + matches.RemoveElement(match); + PluginLibrary* library = static_cast<nsPluginTag*>(tag)->mPlugin->GetLibrary(); + rv = library->NPP_ClearSiteData(match.get(), flags, maxAge, this); + if (NS_FAILED(rv)) { + callback->Callback(rv); + return NS_OK; + } + return NS_OK; + } + + // Callback from NPP_GetSitesWithData, kick the iteration off to clear the data + NS_IMETHOD SitesWithData(InfallibleTArray<nsCString>& sites) override + { + // Enumerate the sites and build a list of matches. + nsresult rv = host->EnumerateSiteData(domain, sites, matches, false); + Callback(rv); + return NS_OK; + } + + nsCString domain; + nsCOMPtr<nsIClearSiteDataCallback> callback; + InfallibleTArray<nsCString> matches; + nsIPluginTag* tag; + uint64_t flags; + int64_t maxAge; + nsPluginHost* host; + NS_DECLARE_STATIC_IID_ACCESSOR(ClearDataFromSitesClosure_CID) + private: + virtual ~ClearDataFromSitesClosure() {} +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(ClearDataFromSitesClosure, ClearDataFromSitesClosure_CID) + +NS_IMPL_ADDREF(ClearDataFromSitesClosure) +NS_IMPL_RELEASE(ClearDataFromSitesClosure) + +NS_INTERFACE_MAP_BEGIN(ClearDataFromSitesClosure) + NS_INTERFACE_MAP_ENTRY(nsIClearSiteDataCallback) + NS_INTERFACE_MAP_ENTRY(nsIGetSitesWithDataCallback) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIClearSiteDataCallback) +NS_INTERFACE_MAP_END + +// FIXME-jsplugins what should this do for fake plugins? +NS_IMETHODIMP +nsPluginHost::ClearSiteData(nsIPluginTag* plugin, const nsACString& domain, + uint64_t flags, int64_t maxAge, nsIClearSiteDataCallback* callbackFunc) +{ + nsCOMPtr<nsIClearSiteDataCallback> callback(callbackFunc); + // 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<nsPluginTag*>(plugin); + + if (!tag->IsEnabled()) { + return NS_ERROR_NOT_AVAILABLE; + } + + // 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, callback); + } + nsCOMPtr<nsIGetSitesWithDataCallback> closure(new ClearDataFromSitesClosure(plugin, domain, flags, + maxAge, callback, this)); + rv = library->NPP_GetSitesWithData(closure); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +#define GetSitesClosure_CID {0x4c9268ac, 0x2fd1, 0x4f2a, {0x9a, 0x10, 0x7a, 0x09, 0xf1, 0xb7, 0x60, 0x3a}} + +// Closure to contain the data needed to handle the callback from NPP_GetSitesWithData +class GetSitesClosure : public nsIGetSitesWithDataCallback { +public: + NS_DECL_ISUPPORTS + GetSitesClosure(const nsACString& domain, nsPluginHost* host) + : domain(domain), host(host), keepWaiting(true) + { + } + NS_IMETHOD SitesWithData(InfallibleTArray<nsCString>& sites) override { + retVal = HandleGetSites(sites); + keepWaiting = false; + return NS_OK; + } + + nsresult HandleGetSites(InfallibleTArray<nsCString>& sites) { + // 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<nsCString> matches; + nsresult rv = host->EnumerateSiteData(domain, sites, matches, true); + NS_ENSURE_SUCCESS(rv, rv); + + result = !matches.IsEmpty(); + return NS_OK; + } + + nsCString domain; + RefPtr<nsPluginHost> host; + bool result; + bool keepWaiting; + nsresult retVal; + NS_DECLARE_STATIC_IID_ACCESSOR(GetSitesClosure_CID) + private: + virtual ~GetSitesClosure() { + } +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(GetSitesClosure, GetSitesClosure_CID) + +NS_IMPL_ISUPPORTS(GetSitesClosure, GetSitesClosure, nsIGetSitesWithDataCallback) + +// This will spin the event loop while waiting on an async +// call to GetSitesWithData +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; + } + + // FIXME-jsplugins audit casts + nsPluginTag* tag = static_cast<nsPluginTag*>(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 + nsCOMPtr<GetSitesClosure> closure(new GetSitesClosure(domain, this)); + rv = library->NPP_GetSitesWithData(nsCOMPtr<nsIGetSitesWithDataCallback>(do_QueryInterface(closure))); + NS_ENSURE_SUCCESS(rv, rv); + // Spin the event loop while we wait for the async call to GetSitesWithData + while (closure->keepWaiting) { + NS_ProcessNextEvent(nullptr, true); + } + *result = closure->result; + return closure->retVal; +} + +nsPluginHost::SpecialType +nsPluginHost::GetSpecialType(const nsACString & aMIMEType) +{ + if (aMIMEType.LowerCaseEqualsASCII("application/x-test")) { + return eSpecialType_Test; + } + + if (aMIMEType.LowerCaseEqualsASCII("application/x-shockwave-flash") || + aMIMEType.LowerCaseEqualsASCII("application/futuresplash") || + aMIMEType.LowerCaseEqualsASCII("application/x-shockwave-flash-test")) { + return eSpecialType_Flash; + } + + if (aMIMEType.LowerCaseEqualsASCII("application/x-silverlight") || + aMIMEType.LowerCaseEqualsASCII("application/x-silverlight-2") || + aMIMEType.LowerCaseEqualsASCII("application/x-silverlight-test")) { + return eSpecialType_Silverlight; + } + + if (aMIMEType.LowerCaseEqualsASCII("audio/x-pn-realaudio-plugin")) { + NS_WARNING("You are loading RealPlayer"); + return eSpecialType_RealPlayer; + } + + if (aMIMEType.LowerCaseEqualsASCII("application/pdf")) { + return eSpecialType_PDF; + } + + if (aMIMEType.LowerCaseEqualsASCII("application/vnd.unity")) { + return eSpecialType_Unity; + } + + // Java registers variants of its MIME with parameters, e.g. + // application/x-java-vm;version=1.3 + const nsACString &noParam = Substring(aMIMEType, 0, aMIMEType.FindChar(';')); + + // 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); + if ((!javaMIME.IsEmpty() && noParam.LowerCaseEqualsASCII(javaMIME)) || + noParam.LowerCaseEqualsASCII("application/x-java-vm") || + noParam.LowerCaseEqualsASCII("application/x-java-applet") || + noParam.LowerCaseEqualsASCII("application/x-java-bean")) { + return eSpecialType_Java; + } + + return eSpecialType_None; +} + +// Check whether or not a tag is a live, valid tag, and that it's loaded. +bool +nsPluginHost::IsLiveTag(nsIPluginTag* aPluginTag) +{ + nsCOMPtr<nsIInternalPluginTag> internalTag(do_QueryInterface(aPluginTag)); + uint32_t fakeCount = mFakePlugins.Length(); + for (uint32_t i = 0; i < fakeCount; i++) { + if (mFakePlugins[i] == internalTag) { + return true; + } + } + + nsPluginTag* tag; + for (tag = mPlugins; tag; tag = tag->mNext) { + if (tag == internalTag) { + return true; + } + } + return false; +} + +// FIXME-jsplugins what should happen with jsplugins here, if anything? +nsPluginTag* +nsPluginHost::HaveSamePlugin(const nsPluginTag* aPluginTag) +{ + for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) { + if (tag->HasSameNameAndMimes(aPluginTag)) { + return tag; + } + } + return nullptr; +} + +// Don't have to worry about fake plugins here, since this is only used during +// the plugin directory scan, which doesn't pick up fake plugins. +nsPluginTag* +nsPluginHost::FirstPluginWithPath(const nsCString& path) +{ + for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) { + if (tag->mFullPath.Equals(path)) { + return tag; + } + } + return nullptr; +} + +nsPluginTag* +nsPluginHost::PluginWithId(uint32_t aId) +{ + for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) { + if (tag->mId == aId) { + return tag; + } + } + return nullptr; +} + +namespace { + +int64_t GetPluginLastModifiedTime(const nsCOMPtr<nsIFile>& 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<nsILocalFileMac> localFileMac = do_QueryInterface(localfile); + if (localFileMac) { + localFileMac->GetBundleContentsLastModifiedTime(&fileModTime); + } else { + localfile->GetLastModifiedTime(&fileModTime); + } +#else + localfile->GetLastModifiedTime(&fileModTime); +#endif + + return fileModTime; +} + +bool +GetPluginIsFromExtension(const nsCOMPtr<nsIFile>& pluginFile, + const nsCOMArray<nsIFile>& extensionDirs) +{ + for (uint32_t i = 0; i < extensionDirs.Length(); ++i) { + bool contains; + if (NS_FAILED(extensionDirs[i]->Contains(pluginFile, &contains)) || !contains) { + continue; + } + + return true; + } + + return false; +} + +void +GetExtensionDirectories(nsCOMArray<nsIFile>& dirs) +{ + nsCOMPtr<nsIProperties> dirService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); + if (!dirService) { + return; + } + + nsCOMPtr<nsISimpleEnumerator> list; + nsresult rv = dirService->Get(XRE_EXTENSIONS_DIR_LIST, + NS_GET_IID(nsISimpleEnumerator), + getter_AddRefs(list)); + if (NS_FAILED(rv)) { + return; + } + + bool more; + while (NS_SUCCEEDED(list->HasMoreElements(&more)) && more) { + nsCOMPtr<nsISupports> next; + if (NS_FAILED(list->GetNext(getter_AddRefs(next)))) { + break; + } + nsCOMPtr<nsIFile> file = do_QueryInterface(next); + if (file) { + file->Normalize(); + dirs.AppendElement(file.forget()); + } + } +} + +struct CompareFilesByTime +{ + bool + LessThan(const nsCOMPtr<nsIFile>& a, const nsCOMPtr<nsIFile>& b) const + { + return GetPluginLastModifiedTime(a) < GetPluginLastModifiedTime(b); + } + + bool + Equals(const nsCOMPtr<nsIFile>& a, const nsCOMPtr<nsIFile>& b) const + { + return GetPluginLastModifiedTime(a) == GetPluginLastModifiedTime(b); + } +}; + +} // namespace + +bool +nsPluginHost::ShouldAddPlugin(nsPluginTag* aPluginTag) +{ +#if defined(XP_WIN) && (defined(__x86_64__) || defined(_M_X64)) + // On 64-bit windows, the only plugins we should load are flash and + // silverlight. Use library filename and MIME type to check. + if (StringBeginsWith(aPluginTag->FileName(), NS_LITERAL_CSTRING("NPSWF"), nsCaseInsensitiveCStringComparator()) && + (aPluginTag->HasMimeType(NS_LITERAL_CSTRING("application/x-shockwave-flash")) || + aPluginTag->HasMimeType(NS_LITERAL_CSTRING("application/x-shockwave-flash-test")))) { + return true; + } + if (StringBeginsWith(aPluginTag->FileName(), NS_LITERAL_CSTRING("npctrl"), nsCaseInsensitiveCStringComparator()) && + (aPluginTag->HasMimeType(NS_LITERAL_CSTRING("application/x-silverlight-test")) || + aPluginTag->HasMimeType(NS_LITERAL_CSTRING("application/x-silverlight-2")) || + aPluginTag->HasMimeType(NS_LITERAL_CSTRING("application/x-silverlight")))) { + return true; + } + // Accept the test plugin MIME types, so mochitests still work. + if (aPluginTag->HasMimeType(NS_LITERAL_CSTRING("application/x-test")) || + aPluginTag->HasMimeType(NS_LITERAL_CSTRING("application/x-Second-Test")) || + aPluginTag->HasMimeType(NS_LITERAL_CSTRING("application/x-java-test"))) { + return true; + } +#ifdef PLUGIN_LOGGING + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("ShouldAddPlugin : Ignoring non-flash plugin library %s\n", aPluginTag->FileName().get())); +#endif // PLUGIN_LOGGING + return false; +#else + return true; +#endif // defined(XP_WIN) && (defined(__x86_64__) || defined(_M_X64)) +} + +void +nsPluginHost::AddPluginTag(nsPluginTag* aPluginTag) +{ + if (!ShouldAddPlugin(aPluginTag)) { + return; + } + aPluginTag->mNext = mPlugins; + mPlugins = aPluginTag; + + if (aPluginTag->IsActive()) { + nsAdoptingCString disableFullPage = + Preferences::GetCString(kPrefDisableFullPage); + for (uint32_t i = 0; i < aPluginTag->MimeTypes().Length(); i++) { + if (!IsTypeInList(aPluginTag->MimeTypes()[i], disableFullPage)) { + RegisterWithCategoryManager(aPluginTag->MimeTypes()[i], + ePluginRegister); + } + } + } +} + +static bool +PluginInfoIsFlash(const nsPluginInfo& info) +{ + if (!info.fName || strcmp(info.fName, "Shockwave Flash") != 0) { + return false; + } + for (uint32_t i = 0; i < info.fVariantCount; ++i) { + if (info.fMimeTypeArray[i] && + (!strcmp(info.fMimeTypeArray[i], "application/x-shockwave-flash") || + !strcmp(info.fMimeTypeArray[i], "application/x-shockwave-flash-test"))) { + return true; + } + } + return false; +} + +typedef NS_NPAPIPLUGIN_CALLBACK(char *, NP_GETMIMEDESCRIPTION)(void); + +nsresult nsPluginHost::ScanPluginsDirectory(nsIFile *pluginsDir, + bool aCreatePluginList, + bool *aPluginsChanged) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + 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 + + bool flashOnly = Preferences::GetBool("plugin.load_flash_only", true); + + nsCOMPtr<nsISimpleEnumerator> iter; + rv = pluginsDir->GetDirectoryEntries(getter_AddRefs(iter)); + if (NS_FAILED(rv)) + return rv; + + AutoTArray<nsCOMPtr<nsIFile>, 6> pluginFiles; + + bool hasMore; + while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsISupports> supports; + rv = iter->GetNext(getter_AddRefs(supports)); + if (NS_FAILED(rv)) + continue; + nsCOMPtr<nsIFile> 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()); + + nsCOMArray<nsIFile> extensionDirs; + GetExtensionDirectories(extensionDirs); + + for (int32_t i = (pluginFiles.Length() - 1); i >= 0; i--) { + nsCOMPtr<nsIFile>& localfile = pluginFiles[i]; + + nsString utf16FilePath; + rv = localfile->GetPath(utf16FilePath); + if (NS_FAILED(rv)) + continue; + + const int64_t fileModTime = GetPluginLastModifiedTime(localfile); + const bool fromExtension = GetPluginIsFromExtension(localfile, extensionDirs); + + // Look for it in our cache + NS_ConvertUTF16toUTF8 filePath(utf16FilePath); + RefPtr<nsPluginTag> 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 (RefPtr<nsInvalidPluginTag> 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::PLUGIN_LOAD_METADATA> 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 || + (flashOnly && !PluginInfoIsFlash(info))) { + RefPtr<nsInvalidPluginTag> 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, fromExtension); + pluginFile.FreePluginInfo(info); + pluginTag->mLibrary = library; + uint32_t state; + rv = pluginTag->GetBlocklistState(&state); + NS_ENSURE_SUCCESS(rv, rv); + + // If the blocklist says it is risky and we have never seen this + // plugin before, then disable it. + if (state == nsIBlocklistService::STATE_SOFTBLOCKED && !seenBefore) { + pluginTag->SetEnabledState(nsIPluginTag::STATE_DISABLED); + } + + // 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; + } + + AddPluginTag(pluginTag); + } + + return NS_OK; +} + +nsresult nsPluginHost::ScanPluginsDirectoryList(nsISimpleEnumerator *dirEnum, + bool aCreatePluginList, + bool *aPluginsChanged) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + bool hasMore; + while (NS_SUCCEEDED(dirEnum->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsISupports> supports; + nsresult rv = dirEnum->GetNext(getter_AddRefs(supports)); + if (NS_FAILED(rv)) + continue; + nsCOMPtr<nsIFile> 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; +} + +void +nsPluginHost::IncrementChromeEpoch() +{ + MOZ_ASSERT(XRE_IsParentProcess()); + mPluginEpoch++; +} + +uint32_t +nsPluginHost::ChromeEpoch() +{ + MOZ_ASSERT(XRE_IsParentProcess()); + return mPluginEpoch; +} + +uint32_t +nsPluginHost::ChromeEpochForContent() +{ + MOZ_ASSERT(XRE_IsContentProcess()); + return mPluginEpoch; +} + +void +nsPluginHost::SetChromeEpochForContent(uint32_t aEpoch) +{ + MOZ_ASSERT(XRE_IsContentProcess()); + mPluginEpoch = aEpoch; +} + +#ifdef XP_WIN +static void +WatchRegKey(uint32_t aRoot, nsCOMPtr<nsIWindowsRegKey>& aKey) +{ + if (aKey) { + return; + } + + aKey = do_CreateInstance("@mozilla.org/windows-registry-key;1"); + if (!aKey) { + return; + } + nsresult rv = aKey->Open(aRoot, + NS_LITERAL_STRING("Software\\MozillaPlugins"), + nsIWindowsRegKey::ACCESS_READ | nsIWindowsRegKey::ACCESS_NOTIFY); + if (NS_FAILED(rv)) { + aKey = nullptr; + return; + } + aKey->StartWatching(true); +} +#endif + +nsresult nsPluginHost::LoadPlugins() +{ +#ifdef ANDROID + if (XRE_IsContentProcess()) { + 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; + +#ifdef XP_WIN + WatchRegKey(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, mRegKeyHKLM); + WatchRegKey(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, mRegKeyHKCU); +#endif + + 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) { + if (XRE_IsParentProcess()) { + IncrementChromeEpoch(); + } + + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (obsService) + obsService->NotifyObservers(nullptr, "plugins-list-updated", nullptr); + } + + return NS_OK; +} + +nsresult +nsPluginHost::FindPluginsInContent(bool aCreatePluginList, bool* aPluginsChanged) +{ + MOZ_ASSERT(XRE_IsContentProcess()); + + dom::ContentChild* cp = dom::ContentChild::GetSingleton(); + nsresult rv; + nsTArray<PluginTag> plugins; + uint32_t parentEpoch; + if (!cp->SendFindPlugins(ChromeEpochForContent(), &rv, &plugins, &parentEpoch) || + NS_FAILED(rv)) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (parentEpoch != ChromeEpochForContent()) { + *aPluginsChanged = true; + if (!aCreatePluginList) { + return NS_OK; + } + + // Don't do this if aCreatePluginList is false. Otherwise, when we actually + // want to create the list, we'll come back here and do nothing. + SetChromeEpochForContent(parentEpoch); + + for (size_t i = 0; i < plugins.Length(); i++) { + PluginTag& tag = plugins[i]; + + // Don't add the same plugin again. + if (nsPluginTag* existing = PluginWithId(tag.id())) { + UpdateInMemoryPluginInfo(existing); + continue; + } + + nsPluginTag *pluginTag = new nsPluginTag(tag.id(), + tag.name().get(), + tag.description().get(), + tag.filename().get(), + "", // aFullPath + tag.version().get(), + nsTArray<nsCString>(tag.mimeTypes()), + nsTArray<nsCString>(tag.mimeDescriptions()), + nsTArray<nsCString>(tag.extensions()), + tag.isJavaPlugin(), + tag.isFlashPlugin(), + tag.supportsAsyncInit(), + tag.supportsAsyncRender(), + tag.lastModifiedTime(), + tag.isFromExtension(), + tag.sandboxLevel()); + AddPluginTag(pluginTag); + } + } + + mPluginsLoaded = true; + 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::FIND_PLUGINS> telemetry; + + NS_ENSURE_ARG_POINTER(aPluginsChanged); + + *aPluginsChanged = false; + + if (XRE_IsContentProcess()) { + return FindPluginsInContent(aCreatePluginList, aPluginsChanged); + } + + 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<nsIProperties> dirService(do_GetService(kDirectoryServiceContractID, &rv)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsISimpleEnumerator> 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(RefPtr<nsPluginTag>, mCachedPlugins, mNext); + NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, 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(RefPtr<nsPluginTag>, mCachedPlugins, mNext); + NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, 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<nsIFile> 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(RefPtr<nsPluginTag>, mCachedPlugins, mNext); + NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, 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 + RefPtr<nsInvalidPluginTag> invalidPlugins = mInvalidPlugins; + while (invalidPlugins) { + if (!invalidPlugins->mSeen) { + RefPtr<nsInvalidPluginTag> 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(RefPtr<nsPluginTag>, mCachedPlugins, mNext); + NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, 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(RefPtr<nsPluginTag>, mCachedPlugins, mNext); + NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext); + + return NS_OK; +} + +nsresult +mozilla::plugins::FindPluginsForContent(uint32_t aPluginEpoch, + nsTArray<PluginTag>* aPlugins, + uint32_t* aNewPluginEpoch) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + return host->FindPluginsForContent(aPluginEpoch, aPlugins, aNewPluginEpoch); +} + +nsresult +nsPluginHost::FindPluginsForContent(uint32_t aPluginEpoch, + nsTArray<PluginTag>* aPlugins, + uint32_t* aNewPluginEpoch) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + // Load plugins so that the epoch is correct. + nsresult rv = LoadPlugins(); + if (NS_FAILED(rv)) { + return rv; + } + + *aNewPluginEpoch = ChromeEpoch(); + if (aPluginEpoch == ChromeEpoch()) { + return NS_OK; + } + + nsTArray<nsCOMPtr<nsIInternalPluginTag>> plugins; + GetPlugins(plugins, true); + + for (size_t i = 0; i < plugins.Length(); i++) { + nsCOMPtr<nsIInternalPluginTag> basetag = plugins[i]; + + nsCOMPtr<nsIFakePluginTag> faketag = do_QueryInterface(basetag); + if (faketag) { + /// FIXME-jsplugins - We need to make content processes properly + /// aware of jsplugins (and add a nsIInternalPluginTag->AsNative() to + /// avoid this hacky static cast) + continue; + } + + /// FIXME-jsplugins - We need to cleanup the various plugintag classes + /// to be more sane and avoid this dance + nsPluginTag *tag = static_cast<nsPluginTag *>(basetag.get()); + + aPlugins->AppendElement(PluginTag(tag->mId, + tag->Name(), + tag->Description(), + tag->MimeTypes(), + tag->MimeDescriptions(), + tag->Extensions(), + tag->mIsJavaPlugin, + tag->mIsFlashPlugin, + tag->mSupportsAsyncInit, + tag->mSupportsAsyncRender, + tag->FileName(), + tag->Version(), + tag->mLastModifiedTime, + tag->IsFromExtension(), + tag->mSandboxLevel)); + } + return NS_OK; +} + +void +nsPluginHost::UpdateInMemoryPluginInfo(nsPluginTag* aPluginTag) +{ + NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext); + NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext); + + if (!aPluginTag) { + return; + } + + // Update types with category manager + nsAdoptingCString disableFullPage = + Preferences::GetCString(kPrefDisableFullPage); + for (uint32_t i = 0; i < aPluginTag->MimeTypes().Length(); i++) { + nsRegisterType shouldRegister; + + if (IsTypeInList(aPluginTag->MimeTypes()[i], disableFullPage)) { + shouldRegister = ePluginUnregister; + } else { + nsPluginTag *plugin = FindNativePluginForType(aPluginTag->MimeTypes()[i], + true); + shouldRegister = plugin ? ePluginRegister : ePluginUnregister; + } + + RegisterWithCategoryManager(aPluginTag->MimeTypes()[i], shouldRegister); + } + + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (obsService) + obsService->NotifyObservers(nullptr, "plugin-info-updated", nullptr); +} + +// This function is not relevant for fake plugins. +void +nsPluginHost::UpdatePluginInfo(nsPluginTag* aPluginTag) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + ReadPluginInfo(); + WritePluginInfo(); + + IncrementChromeEpoch(); + + UpdateInMemoryPluginInfo(aPluginTag); +} + +/* static */ bool +nsPluginHost::IsTypeWhitelisted(const char *aMimeType) +{ + nsAdoptingCString whitelist = Preferences::GetCString(kPrefWhitelist); + if (!whitelist.Length()) { + return true; + } + nsDependentCString wrap(aMimeType); + return IsTypeInList(wrap, whitelist); +} + +/* static */ bool +nsPluginHost::ShouldLoadTypeInParent(const nsACString& aMimeType) +{ + nsCString prefName(kPrefLoadInParentPrefix); + prefName += aMimeType; + return Preferences::GetBool(prefName.get(), false); +} + +void +nsPluginHost::RegisterWithCategoryManager(const nsCString& aMimeType, + nsRegisterType aType) +{ + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("nsPluginTag::RegisterWithCategoryManager type = %s, removing = %s\n", + aMimeType.get(), aType == ePluginUnregister ? "yes" : "no")); + + nsCOMPtr<nsICategoryManager> 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 { + if (aType == ePluginMaybeUnregister) { + // Bail out if this type is still used by an enabled plugin + if (HavePluginForType(aMimeType)) { + return; + } + } else { + MOZ_ASSERT(aType == ePluginUnregister, "Unknown nsRegisterType"); + } + + // 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() +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + nsresult rv = NS_OK; + nsCOMPtr<nsIProperties> 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<nsIFile> pluginReg; + + rv = mPluginRegFile->Clone(getter_AddRefs(pluginReg)); + if (NS_FAILED(rv)) + return rv; + + nsAutoCString filename(kPluginRegistryFilename); + filename.AppendLiteral(".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<nsIXULRuntime> 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; + } + + bool flashOnly = Preferences::GetBool("plugin.load_flash_only", true); + + PR_fprintf(fd, "Generated File. Do not edit.\n"); + + PR_fprintf(fd, "\n[HEADER]\nVersion%c%s%c%c%c\nArch%c%s%c%c\n", + PLUGIN_REGISTRY_FIELD_DELIMITER, + kPluginRegistryVersion, + flashOnly ? 't' : 'f', + 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->FileName().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->Version().get()), + PLUGIN_REGISTRY_FIELD_DELIMITER, + PLUGIN_REGISTRY_END_OF_LINE_MARKER); + + // lastModifiedTimeStamp|canUnload|tag->mFlags|fromExtension + PR_fprintf(fd, "%lld%c%d%c%lu%c%d%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, + tag->IsFromExtension(), + 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->Description().get()), + PLUGIN_REGISTRY_FIELD_DELIMITER, + PLUGIN_REGISTRY_END_OF_LINE_MARKER, + (tag->Name().get()), + PLUGIN_REGISTRY_FIELD_DELIMITER, + PLUGIN_REGISTRY_END_OF_LINE_MARKER, + tag->MimeTypes().Length()); + + // Add in each mimetype this plugin supports + for (uint32_t i = 0; i < tag->MimeTypes().Length(); i++) { + PR_fprintf(fd, "%d%c%s%c%s%c%s%c%c\n", + i,PLUGIN_REGISTRY_FIELD_DELIMITER, + (tag->MimeTypes()[i].get()), + PLUGIN_REGISTRY_FIELD_DELIMITER, + (tag->MimeDescriptions()[i].get()), + PLUGIN_REGISTRY_FIELD_DELIMITER, + (tag->Extensions()[i].get()), + PLUGIN_REGISTRY_FIELD_DELIMITER, + PLUGIN_REGISTRY_END_OF_LINE_MARKER); + } + } + + PR_fprintf(fd, "\n[INVALID]\n"); + + RefPtr<nsInvalidPluginTag> 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<nsIFile> parent; + rv = pluginReg->GetParent(getter_AddRefs(parent)); + NS_ENSURE_SUCCESS(rv, rv); + rv = pluginReg->MoveToNative(parent, kPluginRegistryFilename); + return rv; +} + +nsresult +nsPluginHost::ReadPluginInfo() +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + const long PLUGIN_REG_MIMETYPES_ARRAY_SIZE = 12; + const long PLUGIN_REG_MAX_MIMETYPES = 1000; + + nsresult rv; + + nsCOMPtr<nsIProperties> 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<nsIFile> 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; + + // We know how many octes we are supposed to read. + // So let use the busy_beaver_PR_Read version. + int32_t bread = busy_beaver_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; + } + + // short read error, so to speak. + 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; + + // If we're reading an old registry, ignore it + // If we flipped the flash-only pref, ignore it + bool flashOnly = Preferences::GetBool("plugin.load_flash_only", true); + nsAutoCString expectedVersion(kPluginRegistryVersion); + expectedVersion.Append(flashOnly ? 't' : 'f'); + + if (!expectedVersion.Equals(values[1])) { + return rv; + } + + 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<nsIXULRuntime> 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; + } + + if (!ReadSectionHeader(reader, "PLUGINS")) + return rv; + + while (reader.NextLine()) { + if (*reader.LinePtr() == '[') { + break; + } + + const char* filename = reader.LinePtr(); + if (!reader.NextLine()) + return rv; + + const char* fullpath = reader.LinePtr(); + if (!reader.NextLine()) + return rv; + + const char *version; + version = reader.LinePtr(); + if (!reader.NextLine()) + return rv; + + // lastModifiedTimeStamp|canUnload|tag.mFlag|fromExtension + if (4 != reader.ParseLine(values, 4)) + return rv; + + int64_t lastmod = nsCRT::atoll(values[0]); + bool fromExtension = atoi(values[3]); + 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; + } + + RefPtr<nsPluginTag> tag = new nsPluginTag(name, + description, + filename, + fullpath, + version, + (const char* const*)mimetypes, + (const char* const*)mimedescriptions, + (const char* const*)extensions, + mimetypecount, lastmod, fromExtension, true); + if (heapalloced) + delete [] heapalloced; + + // Import flags from registry into prefs for old registry versions + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_BASIC, + ("LoadCachedPluginsInfo : Loading Cached plugininfo for %s\n", tag->FileName().get())); + + if (!ShouldAddPlugin(tag)) { + continue; + } + + 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 (!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 = nsCRT::atoll(lastModifiedTimeStamp); + + RefPtr<nsInvalidPluginTag> invalidTag = new nsInvalidPluginTag(fullpath, lastmod); + + invalidTag->mNext = mInvalidPlugins; + if (mInvalidPlugins) { + mInvalidPlugins->mPrev = invalidTag; + } + mInvalidPlugins = invalidTag; + } +#endif + + return NS_OK; +} + +void +nsPluginHost::RemoveCachedPluginsInfo(const char *filePath, nsPluginTag **result) +{ + RefPtr<nsPluginTag> prev; + RefPtr<nsPluginTag> 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(); + nsCOMPtr<nsIDirectoryService> 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<nsIURI> 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 + RefPtr<nsPluginInstanceOwner> owner = aInstance->GetOwner(); + if (owner) { + nsCOMPtr<nsIURI> baseURI = owner->GetBaseURI(); + rv = NS_MakeAbsoluteURI(absUrl, aURL, baseURI); + } + + if (absUrl.IsEmpty()) + absUrl.Assign(aURL); + + rv = NS_NewURI(getter_AddRefs(url), absUrl); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<nsPluginStreamListenerPeer> listenerPeer = new nsPluginStreamListenerPeer(); + NS_ENSURE_TRUE(listenerPeer, NS_ERROR_OUT_OF_MEMORY); + + rv = listenerPeer->Initialize(url, aInstance, aListener); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDOMElement> element; + nsCOMPtr<nsIDocument> doc; + if (owner) { + owner->GetDOMElement(getter_AddRefs(element)); + owner->GetDocument(getter_AddRefs(doc)); + } + + nsCOMPtr<nsINode> requestingNode(do_QueryInterface(element)); + NS_ENSURE_TRUE(requestingNode, NS_ERROR_FAILURE); + + nsCOMPtr<nsIChannel> channel; + // @arg loadgroup: + // do not add this internal plugin's channel on the + // load group otherwise this channel could be canceled + // form |nsDocShell::OnLinkClickSync| bug 166613 + rv = NS_NewChannel(getter_AddRefs(channel), + url, + requestingNode, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS | + nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL, + nsIContentPolicy::TYPE_OBJECT_SUBREQUEST, + nullptr, // aLoadGroup + listenerPeer, + nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI | + nsIChannel::LOAD_BYPASS_SERVICE_WORKER); + NS_ENSURE_SUCCESS(rv, rv); + + if (doc) { + // And if it's a script allow it to execute against the + // document's script context. + nsCOMPtr<nsIScriptChannel> 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<nsIHttpChannel> 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<nsIURI> referer; + net::ReferrerPolicy referrerPolicy = net::RP_Default; + + nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(element); + if (olc) + olc->GetSrcURI(getter_AddRefs(referer)); + + + if (!referer) { + if (!doc) { + return NS_ERROR_FAILURE; + } + referer = doc->GetDocumentURI(); + referrerPolicy = doc->GetReferrerPolicy(); + } + + rv = httpChannel->SetReferrerWithPolicy(referer, referrerPolicy); + 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<nsISeekableStream> + postDataSeekable(do_QueryInterface(aPostStream)); + if (postDataSeekable) + postDataSeekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); + + nsCOMPtr<nsIUploadChannel> 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->AsyncOpen2(listenerPeer); + if (NS_SUCCEEDED(rv)) + listenerPeer->TrackRequest(channel); + return rv; +} + +nsresult +nsPluginHost::AddHeadersToChannel(const char *aHeadersData, + uint32_t aHeadersDataLen, + nsIChannel *aGenericChannel) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsIHttpChannel> 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; + } + } +} + +nsresult +nsPluginHost::StopPluginInstance(nsNPAPIPluginInstance* aInstance) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + 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<Telemetry::PLUGIN_SHUTDOWN_MS> 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); + + RefPtr<nsPluginStreamListenerPeer> listener = new nsPluginStreamListenerPeer(); + nsresult rv = listener->Initialize(aURI, aInstance, nullptr); + if (NS_FAILED(rv)) { + return rv; + } + + listener.forget(aStreamListener); + + return NS_OK; +} + +void nsPluginHost::CreateWidget(nsPluginInstanceOwner* aOwner) +{ + aOwner->CreateWidget(); + + // If we've got a native window, the let the plugin know about it. + aOwner->CallSetWindow(); +} + +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); + // 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"; + + AutoTArray<const char*, 8> 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*)moz_xmalloc(newBufferLen))) + return NS_ERROR_OUT_OF_MEMORY; + + // deal with single LF + const char *s = inPostData; + if (cntSingleLF) { + for (int i=0; i<cntSingleLF; i++) { + const char *plf = singleLF.ElementAt(i); // ptr to single LF in headers + int n = plf - s; // bytes to copy + if (n) { // for '\n\n' there is nothing to memcpy + memcpy(p, s, n); + p += n; + } + *p++ = CR; + s = plf; + *p++ = *s++; + } + } + // are we done with headers? + headersLen = pEoh - s; + if (headersLen) { // not yet + memcpy(p, s, headersLen); // copy the rest + p += headersLen; + } + } else if (dataLen) { // no ContentLenHeader is found but there is a data + // make new output buffer big enough + // to keep ContentLenHeader+value followed by data + uint32_t l = sizeof(ContentLenHeader) + sizeof(CRLFCRLF) + 32; + newBufferLen = dataLen + l; + if (!(*outPostData = p = (char*)moz_xmalloc(newBufferLen))) + return NS_ERROR_OUT_OF_MEMORY; + headersLen = snprintf(p, l,"%s: %u%s", ContentLenHeader, dataLen, CRLFCRLF); + if (headersLen == l) { // if snprintf has ate all extra space consider this as an error + free(p); + *outPostData = 0; + return NS_ERROR_FAILURE; + } + p += headersLen; + newBufferLen = headersLen + dataLen; + } + // at this point we've done with headers. + // there is a possibility that input buffer has only headers info in it + // which already parsed and copied into output buffer. + // copy the data + if (dataLen) { + memcpy(p, pSod, dataLen); + } + + *outPostDataLen = newBufferLen; + + return NS_OK; +} + +nsresult +nsPluginHost::CreateTempFileToPost(const char *aPostDataURL, nsIFile **aTmpFile) +{ + nsresult rv; + int64_t fileSize; + nsAutoCString filename; + + // stat file == get size & convert file:///c:/ to c: if needed + nsCOMPtr<nsIFile> inFile; + rv = NS_GetFileFromURLSpec(nsDependentCString(aPostDataURL), + getter_AddRefs(inFile)); + if (NS_FAILED(rv)) { + nsCOMPtr<nsIFile> 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<nsIInputStream> 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<nsIFile> 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<nsIOutputStream> 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); + 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<nsNPAPIPluginInstance*>(aPluginInstance); + if (!instance) + return NS_ERROR_FAILURE; + + nsNPAPIPlugin* plugin = instance->GetPlugin(); + if (!plugin) + return NS_ERROR_FAILURE; + + *aPluginName = TagForPlugin(plugin)->Name().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) +{ + RefPtr<nsPluginTag> 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<nsIWindowMediator> wm(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (!wm) + return; + + nsCOMPtr<nsISimpleEnumerator> windowList; + wm->GetXULWindowEnumerator(nullptr, getter_AddRefs(windowList)); + if (!windowList) + return; + + bool haveWindows; + do { + windowList->HasMoreElements(&haveWindows); + if (!haveWindows) + return; + + nsCOMPtr<nsISupports> supportsWindow; + windowList->GetNext(getter_AddRefs(supportsWindow)); + nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(supportsWindow)); + if (baseWin) { + nsCOMPtr<nsIWidget> 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); + MOZ_ASSERT(crashedPluginTag); + + // Notify the app's observer that a plugin crashed so it can submit + // a crashreport. + bool submittedCrashReport = false; + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + nsCOMPtr<nsIWritablePropertyBag2> propbag = + do_CreateInstance("@mozilla.org/hash-property-bag;1"); + if (obsService && propbag) { + uint32_t runID = 0; + PluginLibrary* library = aPlugin->GetLibrary(); + + if (!NS_WARN_IF(!library)) { + library->GetRunID(&runID); + } + propbag->SetPropertyAsUint32(NS_LITERAL_STRING("runID"), runID); + + nsCString pluginName; + crashedPluginTag->GetName(pluginName); + propbag->SetPropertyAsAString(NS_LITERAL_STRING("pluginName"), + NS_ConvertUTF8toUTF16(pluginName)); + 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<nsIDOMElement> domElement; + instance->GetDOMElement(getter_AddRefs(domElement)); + nsCOMPtr<nsIObjectLoadingContent> 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; + crashedPluginTag->mContentProcessRunningCount = 0; + +#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< RefPtr<nsNPAPIPluginInstance> >* +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<nsIDOMElement> domElement; + instance->GetDOMElement(getter_AddRefs(domElement)); + nsCOMPtr<nsIObjectLoadingContent> 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 Runnable, + public PRCList +{ +public: + explicit nsPluginDestroyRunnable(nsNPAPIPluginInstance *aInstance) + : mInstance(aInstance) + { + PR_INIT_CLIST(this); + PR_APPEND_LINK(this, &sRunnableListHead); + } + + virtual ~nsPluginDestroyRunnable() + { + PR_REMOVE_LINK(this); + } + + NS_IMETHOD Run() override + { + RefPtr<nsNPAPIPluginInstance> 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<nsPluginDestroyRunnable*>(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<nsPluginDestroyRunnable*>(PR_NEXT_LINK(r)); + } + + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("Doing delayed destroy of instance %p\n", instance.get())); + + RefPtr<nsPluginHost> 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: + RefPtr<nsNPAPIPluginInstance> 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(PluginAsyncSurrogate *aSurrogate) + : mInstance(static_cast<nsNPAPIPluginInstance*>(aSurrogate->GetNPP()->ndata)) +{ + InitAsync(); +} + +PluginDestructionGuard::PluginDestructionGuard(NPP npp) + : mInstance(npp ? static_cast<nsNPAPIPluginInstance*>(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. + RefPtr<nsPluginDestroyRunnable> 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<PluginDestructionGuard*>(PR_LIST_HEAD(&sListHead)); + + while (g != &sListHead) { + if (g->mInstance == aInstance) { + g->mDelayedDestroy = true; + + return true; + } + g = static_cast<PluginDestructionGuard*>(PR_NEXT_LINK(g)); + } + + return false; +} |