| /* |
| * Copyright 2009, The Android Open Source Project |
| * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "PluginPackage.h" |
| |
| #ifdef ANDROID_PLUGINS |
| |
| #include "JNIUtility.h" |
| #include "PlatformString.h" |
| #include "PluginDatabase.h" |
| #include "PluginMainThreadScheduler.h" |
| #include "Timer.h" |
| #include "npfunctions.h" |
| #include "npruntime_impl.h" |
| #include <dlfcn.h> |
| #include <errno.h> |
| #include <wtf/text/CString.h> |
| |
| // un-comment this to enable logging |
| //#define PLUGIN_DEBUG_LOCAL |
| #define LOG_TAG "WebKit" |
| #include "NotImplemented.h" |
| #include "PluginDebug.h" |
| #include "PluginDebugAndroid.h" |
| |
| namespace WebCore { |
| |
| // Simple class which calls dlclose() on a dynamic library when going |
| // out of scope. Call ok() if the handle should stay open. |
| class DynamicLibraryCloser |
| { |
| public: |
| DynamicLibraryCloser(PlatformModule *module) : m_module(module) { } |
| ~DynamicLibraryCloser() |
| { |
| // Close the library if non-NULL reference and open. |
| if (m_module && *m_module) |
| { |
| dlclose(*m_module); |
| *m_module = 0; |
| } |
| } |
| void ok() { m_module = NULL; } |
| |
| private: |
| PlatformModule *m_module; |
| }; |
| |
| // A container for a dummy npp instance. This is used to allow |
| // NPN_PluginThreadAsyncCall() to be used with NULL passed as the npp |
| // instance. This is substituted instead, and is shared between all |
| // plugins which behave in this way. This will be lazily created in |
| // the first call to NPN_PluginThreadAsyncCall(). |
| class DummyNpp { |
| public: |
| DummyNpp() { |
| m_npp = new NPP_t(); |
| m_npp->pdata = NULL; |
| m_npp->ndata = NULL; |
| PluginMainThreadScheduler::scheduler().registerPlugin(m_npp); |
| } |
| ~DummyNpp() { |
| PluginMainThreadScheduler::scheduler().unregisterPlugin(m_npp); |
| delete m_npp; |
| } |
| NPP_t *getInstance() { return m_npp; } |
| |
| private: |
| NPP_t *m_npp; |
| }; |
| |
| static bool getEntryPoint(PlatformModule module, |
| const char *name, |
| void **entry_point) |
| { |
| dlerror(); |
| *entry_point = dlsym(module, name); |
| const char *error = dlerror(); |
| if(error == NULL && *entry_point != NULL) { |
| return true; |
| } else { |
| PLUGIN_LOG("Couldn't get entry point \"%s\": %s\n", |
| name, error); |
| return false; |
| } |
| } |
| |
| bool PluginPackage::isPluginBlacklisted() |
| { |
| // No blacklisted Android plugins... yet! |
| return false; |
| } |
| |
| void PluginPackage::determineQuirks(const String& mimeType) |
| { |
| // The Gears implementation relies on it being loaded *all the time*, |
| // so check to see if this package represents the Gears plugin and |
| // load it. |
| if (mimeType == "application/x-googlegears") { |
| m_quirks.add(PluginQuirkDontUnloadPlugin); |
| } |
| } |
| |
| static void Android_NPN_PluginThreadAsyncCall(NPP instance, |
| void (*func) (void *), |
| void *userData) |
| { |
| // Translate all instance == NULL to a dummy actual npp. |
| static DummyNpp dummyNpp; |
| if (instance == NULL) { |
| instance = dummyNpp.getInstance(); |
| } |
| // Call through to the wrapped function. |
| NPN_PluginThreadAsyncCall(instance, func, userData); |
| } |
| |
| static void initializeExtraBrowserFuncs(NPNetscapeFuncs *funcs) |
| { |
| funcs->version = NP_VERSION_MINOR; |
| funcs->geturl = NPN_GetURL; |
| funcs->posturl = NPN_PostURL; |
| funcs->requestread = NPN_RequestRead; |
| funcs->newstream = NPN_NewStream; |
| funcs->write = NPN_Write; |
| funcs->destroystream = NPN_DestroyStream; |
| funcs->status = NPN_Status; |
| funcs->uagent = NPN_UserAgent; |
| funcs->memalloc = NPN_MemAlloc; |
| funcs->memfree = NPN_MemFree; |
| funcs->memflush = NPN_MemFlush; |
| funcs->reloadplugins = NPN_ReloadPlugins; |
| funcs->geturlnotify = NPN_GetURLNotify; |
| funcs->posturlnotify = NPN_PostURLNotify; |
| funcs->getvalue = NPN_GetValue; |
| funcs->setvalue = NPN_SetValue; |
| funcs->invalidaterect = NPN_InvalidateRect; |
| funcs->invalidateregion = NPN_InvalidateRegion; |
| funcs->forceredraw = NPN_ForceRedraw; |
| funcs->getJavaEnv = NPN_GetJavaEnv; |
| funcs->getJavaPeer = NPN_GetJavaPeer; |
| funcs->pushpopupsenabledstate = NPN_PushPopupsEnabledState; |
| funcs->poppopupsenabledstate = NPN_PopPopupsEnabledState; |
| funcs->pluginthreadasynccall = Android_NPN_PluginThreadAsyncCall; |
| funcs->scheduletimer = NPN_ScheduleTimer; |
| funcs->unscheduletimer = NPN_UnscheduleTimer; |
| |
| funcs->releasevariantvalue = _NPN_ReleaseVariantValue; |
| funcs->getstringidentifier = _NPN_GetStringIdentifier; |
| funcs->getstringidentifiers = _NPN_GetStringIdentifiers; |
| funcs->getintidentifier = _NPN_GetIntIdentifier; |
| funcs->identifierisstring = _NPN_IdentifierIsString; |
| funcs->utf8fromidentifier = _NPN_UTF8FromIdentifier; |
| funcs->intfromidentifier = _NPN_IntFromIdentifier; |
| funcs->createobject = _NPN_CreateObject; |
| funcs->retainobject = _NPN_RetainObject; |
| funcs->releaseobject = _NPN_ReleaseObject; |
| funcs->invoke = _NPN_Invoke; |
| funcs->invokeDefault = _NPN_InvokeDefault; |
| funcs->evaluate = _NPN_Evaluate; |
| funcs->getproperty = _NPN_GetProperty; |
| funcs->setproperty = _NPN_SetProperty; |
| funcs->removeproperty = _NPN_RemoveProperty; |
| funcs->hasproperty = _NPN_HasProperty; |
| funcs->hasmethod = _NPN_HasMethod; |
| funcs->setexception = _NPN_SetException; |
| funcs->enumerate = _NPN_Enumerate; |
| } |
| |
| bool PluginPackage::load() |
| { |
| PLUGIN_LOG("tid:%d isActive:%d isLoaded:%d loadCount:%d\n", |
| gettid(), |
| m_freeLibraryTimer.isActive(), |
| m_isLoaded, |
| m_loadCount); |
| if (m_freeLibraryTimer.isActive()) { |
| ASSERT(m_module); |
| m_freeLibraryTimer.stop(); |
| } else if (m_isLoaded) { |
| if (m_quirks.contains(PluginQuirkDontAllowMultipleInstances)) |
| return false; |
| m_loadCount++; |
| PLUGIN_LOG("Already loaded, count now %d\n", m_loadCount); |
| return true; |
| } |
| ASSERT(m_loadCount == 0); |
| ASSERT(m_module == NULL); |
| |
| PLUGIN_LOG("Loading \"%s\"\n", m_path.utf8().data()); |
| |
| // Open the library |
| void *handle = dlopen(m_path.utf8().data(), RTLD_NOW); |
| if(!handle) { |
| PLUGIN_LOG("Couldn't load plugin library \"%s\": %s\n", |
| m_path.utf8().data(), dlerror()); |
| return false; |
| } |
| m_module = handle; |
| PLUGIN_LOG("Fetch Info Loaded %p\n", m_module); |
| // This object will call dlclose() and set m_module to NULL |
| // when going out of scope. |
| DynamicLibraryCloser dlCloser(&m_module); |
| |
| |
| NP_InitializeFuncPtr NP_Initialize; |
| if(!getEntryPoint(m_module, "NP_Initialize", (void **) &NP_Initialize) || |
| !getEntryPoint(handle, "NP_Shutdown", (void **) &m_NPP_Shutdown)) { |
| PLUGIN_LOG("Couldn't find Initialize function\n"); |
| return false; |
| } |
| |
| // Provide the plugin with our browser function table and grab its |
| // plugin table. Provide the Java environment and the Plugin which |
| // can be used to override the defaults if the plugin wants. |
| initializeBrowserFuncs(); |
| // call this afterwards, which may re-initialize some methods, but ensures |
| // that any additional (or changed) procs are set. There is no real attempt |
| // to have this step be minimal (i.e. only what we add/override), since the |
| // core version (initializeBrowserFuncs) can change in the future. |
| initializeExtraBrowserFuncs(&m_browserFuncs); |
| |
| memset(&m_pluginFuncs, 0, sizeof(m_pluginFuncs)); |
| m_pluginFuncs.size = sizeof(m_pluginFuncs); |
| if(NP_Initialize(&m_browserFuncs, |
| &m_pluginFuncs, |
| JSC::Bindings::getJNIEnv()) != NPERR_NO_ERROR) { |
| PLUGIN_LOG("Couldn't initialize plugin\n"); |
| return false; |
| } |
| |
| // Don't close the library - loaded OK. |
| dlCloser.ok(); |
| // Retain the handle so we can close it in the future. |
| m_module = handle; |
| m_isLoaded = true; |
| ++m_loadCount; |
| PLUGIN_LOG("Initial load ok, count now %d\n", m_loadCount); |
| return true; |
| } |
| |
| bool PluginPackage::fetchInfo() |
| { |
| PLUGIN_LOG("Fetch Info Loading \"%s\"\n", m_path.utf8().data()); |
| |
| // Open the library |
| void *handle = dlopen(m_path.utf8().data(), RTLD_NOW); |
| if(!handle) { |
| PLUGIN_LOG("Couldn't load plugin library \"%s\": %s\n", |
| m_path.utf8().data(), dlerror()); |
| return false; |
| } |
| PLUGIN_LOG("Fetch Info Loaded %p\n", handle); |
| |
| // This object will call dlclose() and set m_module to NULL |
| // when going out of scope. |
| DynamicLibraryCloser dlCloser(&handle); |
| |
| // Get the three entry points we need for Linux Netscape Plug-ins |
| NP_GetMIMEDescriptionFuncPtr NP_GetMIMEDescription; |
| NPP_GetValueProcPtr NP_GetValue; |
| if(!getEntryPoint(handle, "NP_GetMIMEDescription", |
| (void **) &NP_GetMIMEDescription) || |
| !getEntryPoint(handle, "NP_GetValue", (void **) &NP_GetValue)) { |
| // If any of those failed to resolve, fail the entire load |
| return false; |
| } |
| |
| // Get the plugin name and description using NP_GetValue |
| const char *name; |
| const char *description; |
| if(NP_GetValue(NULL, NPPVpluginNameString, &name) != NPERR_NO_ERROR || |
| NP_GetValue(NULL, NPPVpluginDescriptionString, &description) != |
| NPERR_NO_ERROR) { |
| PLUGIN_LOG("Couldn't get name/description using NP_GetValue\n"); |
| return false; |
| } |
| |
| PLUGIN_LOG("Plugin name: \"%s\"\n", name); |
| PLUGIN_LOG("Plugin description: \"%s\"\n", description); |
| m_name = name; |
| m_description = description; |
| |
| // fileName is just the trailing part of the path |
| int last_slash = m_path.reverseFind('/'); |
| if(last_slash < 0) |
| m_fileName = m_path; |
| else |
| m_fileName = m_path.substring(last_slash + 1); |
| |
| // Grab the MIME description. This is in the format, e.g: |
| // application/x-somescriptformat:ssf:Some Script Format |
| String mimeDescription(NP_GetMIMEDescription()); |
| PLUGIN_LOG("MIME description: \"%s\"\n", mimeDescription.utf8().data()); |
| // Clear out the current mappings. |
| m_mimeToDescriptions.clear(); |
| m_mimeToExtensions.clear(); |
| // Split the description into its component entries, separated by |
| // semicolons. |
| Vector<String> mimeEntries; |
| mimeDescription.split(';', true, mimeEntries); |
| // Iterate through the entries, adding them to the MIME mappings. |
| for(Vector<String>::const_iterator it = mimeEntries.begin(); |
| it != mimeEntries.end(); ++it) { |
| // Each part is split into 3 fields separated by colons |
| // Field 1 is the MIME type (e.g "application/x-shockwave-flash"). |
| // Field 2 is a comma separated list of file extensions. |
| // Field 3 is a human readable short description. |
| const String &mimeEntry = *it; |
| Vector<String> fields; |
| mimeEntry.split(':', true, fields); |
| if(fields.size() != 3) { |
| PLUGIN_LOG("Bad MIME entry \"%s\"\n", mimeEntry.utf8().data()); |
| return false; |
| } |
| |
| const String& mimeType = fields[0]; |
| Vector<String> extensions; |
| fields[1].split(',', true, extensions); |
| const String& description = fields[2]; |
| |
| determineQuirks(mimeType); |
| |
| PLUGIN_LOG("mime_type: \"%s\"\n", mimeType.utf8().data()); |
| PLUGIN_LOG("extensions: \"%s\"\n", fields[1].utf8().data()); |
| PLUGIN_LOG("description: \"%s\"\n", description.utf8().data()); |
| |
| // Map the mime type to the vector of extensions and the description |
| if(!extensions.isEmpty()) |
| m_mimeToExtensions.set(mimeType, extensions); |
| if(!description.isEmpty()) |
| m_mimeToDescriptions.set(mimeType, description); |
| } |
| |
| PLUGIN_LOG("Fetch Info Loaded plugin details ok \"%s\"\n", |
| m_path.utf8().data()); |
| |
| // If this plugin needs to be kept in memory, unload the module now |
| // and load it permanently. |
| if (m_quirks.contains(PluginQuirkDontUnloadPlugin)) { |
| dlCloser.ok(); |
| dlclose(handle); |
| load(); |
| } |
| |
| // dlCloser will unload the plugin if required. |
| return true; |
| } |
| |
| unsigned PluginPackage::hash() const |
| { |
| const unsigned hashCodes[] = { |
| m_name.impl()->hash(), |
| m_description.impl()->hash(), |
| m_mimeToExtensions.size(), |
| }; |
| |
| return StringImpl::computeHash(reinterpret_cast<const UChar*>(hashCodes), |
| sizeof(hashCodes) / sizeof(UChar)); |
| } |
| |
| bool PluginPackage::equal(const PluginPackage& a, const PluginPackage& b) |
| { |
| if (a.m_name != b.m_name) |
| return false; |
| |
| if (a.m_description != b.m_description) |
| return false; |
| |
| if (a.m_mimeToExtensions.size() != b.m_mimeToExtensions.size()) |
| return false; |
| |
| MIMEToExtensionsMap::const_iterator::Keys end = |
| a.m_mimeToExtensions.end().keys(); |
| for (MIMEToExtensionsMap::const_iterator::Keys it = |
| a.m_mimeToExtensions.begin().keys(); |
| it != end; |
| ++it) { |
| if (!b.m_mimeToExtensions.contains(*it)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| uint16_t PluginPackage::NPVersion() const |
| { |
| return NP_VERSION_MINOR; |
| } |
| |
| } // namespace WebCore |
| |
| #endif |