1/*
2 * Copyright 2009, The Android Open Source Project
3 * Copyright (C) 2006, 2007 Apple Inc.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *  * Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 *  * Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "PluginPackage.h"
29
30#ifdef ANDROID_PLUGINS
31
32#include "JNIUtility.h"
33#include "PlatformString.h"
34#include "PluginDatabase.h"
35#include "PluginMainThreadScheduler.h"
36#include "Timer.h"
37#include "npfunctions.h"
38#include "npruntime_impl.h"
39#include <dlfcn.h>
40#include <errno.h>
41#include <wtf/text/CString.h>
42
43// un-comment this to enable logging
44//#define PLUGIN_DEBUG_LOCAL
45#define LOG_TAG "WebKit"
46#include "NotImplemented.h"
47#include "PluginDebug.h"
48#include "PluginDebugAndroid.h"
49
50namespace WebCore {
51
52// Simple class which calls dlclose() on a dynamic library when going
53// out of scope. Call ok() if the handle should stay open.
54class DynamicLibraryCloser
55{
56  public:
57    DynamicLibraryCloser(PlatformModule *module) : m_module(module) { }
58    ~DynamicLibraryCloser()
59    {
60        // Close the library if non-NULL reference and open.
61        if (m_module && *m_module)
62        {
63            dlclose(*m_module);
64            *m_module = 0;
65        }
66    }
67    void ok() { m_module = NULL; }
68
69  private:
70    PlatformModule *m_module;
71};
72
73// A container for a dummy npp instance. This is used to allow
74// NPN_PluginThreadAsyncCall() to be used with NULL passed as the npp
75// instance. This is substituted instead, and is shared between all
76// plugins which behave in this way. This will be lazily created in
77// the first call to NPN_PluginThreadAsyncCall().
78class DummyNpp {
79  public:
80    DummyNpp() {
81        m_npp = new NPP_t();
82        m_npp->pdata = NULL;
83        m_npp->ndata = NULL;
84        PluginMainThreadScheduler::scheduler().registerPlugin(m_npp);
85    }
86    ~DummyNpp() {
87        PluginMainThreadScheduler::scheduler().unregisterPlugin(m_npp);
88        delete m_npp;
89    }
90    NPP_t *getInstance() { return m_npp; }
91
92  private:
93    NPP_t *m_npp;
94};
95
96static bool getEntryPoint(PlatformModule module,
97                          const char *name,
98                          void **entry_point)
99{
100    dlerror();
101    *entry_point = dlsym(module, name);
102    const char *error = dlerror();
103    if(error == NULL && *entry_point != NULL) {
104        return true;
105    } else {
106        PLUGIN_LOG("Couldn't get entry point \"%s\": %s\n",
107                   name, error);
108        return false;
109    }
110}
111
112bool PluginPackage::isPluginBlacklisted()
113{
114    // No blacklisted Android plugins... yet!
115    return false;
116}
117
118void PluginPackage::determineQuirks(const String& mimeType)
119{
120    // The Gears implementation relies on it being loaded *all the time*,
121    // so check to see if this package represents the Gears plugin and
122    // load it.
123    if (mimeType == "application/x-googlegears") {
124        m_quirks.add(PluginQuirkDontUnloadPlugin);
125    }
126}
127
128static void Android_NPN_PluginThreadAsyncCall(NPP instance,
129                                              void (*func) (void *),
130                                              void *userData)
131{
132    // Translate all instance == NULL to a dummy actual npp.
133    static DummyNpp dummyNpp;
134    if (instance == NULL) {
135        instance = dummyNpp.getInstance();
136    }
137    // Call through to the wrapped function.
138    NPN_PluginThreadAsyncCall(instance, func, userData);
139}
140
141static void initializeExtraBrowserFuncs(NPNetscapeFuncs *funcs)
142{
143    funcs->version = NP_VERSION_MINOR;
144    funcs->geturl = NPN_GetURL;
145    funcs->posturl = NPN_PostURL;
146    funcs->requestread = NPN_RequestRead;
147    funcs->newstream = NPN_NewStream;
148    funcs->write = NPN_Write;
149    funcs->destroystream = NPN_DestroyStream;
150    funcs->status = NPN_Status;
151    funcs->uagent = NPN_UserAgent;
152    funcs->memalloc = NPN_MemAlloc;
153    funcs->memfree = NPN_MemFree;
154    funcs->memflush = NPN_MemFlush;
155    funcs->reloadplugins = NPN_ReloadPlugins;
156    funcs->geturlnotify = NPN_GetURLNotify;
157    funcs->posturlnotify = NPN_PostURLNotify;
158    funcs->getvalue = NPN_GetValue;
159    funcs->setvalue = NPN_SetValue;
160    funcs->invalidaterect = NPN_InvalidateRect;
161    funcs->invalidateregion = NPN_InvalidateRegion;
162    funcs->forceredraw = NPN_ForceRedraw;
163    funcs->getJavaEnv = NPN_GetJavaEnv;
164    funcs->getJavaPeer = NPN_GetJavaPeer;
165    funcs->pushpopupsenabledstate = NPN_PushPopupsEnabledState;
166    funcs->poppopupsenabledstate = NPN_PopPopupsEnabledState;
167    funcs->pluginthreadasynccall = Android_NPN_PluginThreadAsyncCall;
168    funcs->scheduletimer = NPN_ScheduleTimer;
169    funcs->unscheduletimer = NPN_UnscheduleTimer;
170
171    funcs->releasevariantvalue = _NPN_ReleaseVariantValue;
172    funcs->getstringidentifier = _NPN_GetStringIdentifier;
173    funcs->getstringidentifiers = _NPN_GetStringIdentifiers;
174    funcs->getintidentifier = _NPN_GetIntIdentifier;
175    funcs->identifierisstring = _NPN_IdentifierIsString;
176    funcs->utf8fromidentifier = _NPN_UTF8FromIdentifier;
177    funcs->intfromidentifier = _NPN_IntFromIdentifier;
178    funcs->createobject = _NPN_CreateObject;
179    funcs->retainobject = _NPN_RetainObject;
180    funcs->releaseobject = _NPN_ReleaseObject;
181    funcs->invoke = _NPN_Invoke;
182    funcs->invokeDefault = _NPN_InvokeDefault;
183    funcs->evaluate = _NPN_Evaluate;
184    funcs->getproperty = _NPN_GetProperty;
185    funcs->setproperty = _NPN_SetProperty;
186    funcs->removeproperty = _NPN_RemoveProperty;
187    funcs->hasproperty = _NPN_HasProperty;
188    funcs->hasmethod = _NPN_HasMethod;
189    funcs->setexception = _NPN_SetException;
190    funcs->enumerate = _NPN_Enumerate;
191}
192
193bool PluginPackage::load()
194{
195    PLUGIN_LOG("tid:%d isActive:%d isLoaded:%d loadCount:%d\n",
196               gettid(),
197               m_freeLibraryTimer.isActive(),
198               m_isLoaded,
199               m_loadCount);
200    if (m_freeLibraryTimer.isActive()) {
201        ASSERT(m_module);
202        m_freeLibraryTimer.stop();
203    } else if (m_isLoaded) {
204        if (m_quirks.contains(PluginQuirkDontAllowMultipleInstances))
205            return false;
206        m_loadCount++;
207        PLUGIN_LOG("Already loaded, count now %d\n", m_loadCount);
208        return true;
209    } else {
210        ASSERT(m_loadCount == 0);
211        ASSERT(m_module == NULL);
212
213        PLUGIN_LOG("Loading \"%s\"\n", m_path.utf8().data());
214
215        // Open the library
216        void *handle = dlopen(m_path.utf8().data(), RTLD_NOW);
217        if(!handle) {
218            PLUGIN_LOG("Couldn't load plugin library \"%s\": %s\n",
219                       m_path.utf8().data(), dlerror());
220            return false;
221        }
222        m_module = handle;
223        PLUGIN_LOG("Fetch Info Loaded %p\n", m_module);
224    }
225
226    // This object will call dlclose() and set m_module to NULL
227    // when going out of scope.
228    DynamicLibraryCloser dlCloser(&m_module);
229
230
231    NP_InitializeFuncPtr NP_Initialize;
232    if(!getEntryPoint(m_module, "NP_Initialize", (void **) &NP_Initialize) ||
233            !getEntryPoint(m_module, "NP_Shutdown", (void **) &m_NPP_Shutdown)) {
234        PLUGIN_LOG("Couldn't find Initialize function\n");
235        return false;
236    }
237
238    // Provide the plugin with our browser function table and grab its
239    // plugin table. Provide the Java environment and the Plugin which
240    // can be used to override the defaults if the plugin wants.
241    initializeBrowserFuncs();
242    // call this afterwards, which may re-initialize some methods, but ensures
243    // that any additional (or changed) procs are set. There is no real attempt
244    // to have this step be minimal (i.e. only what we add/override), since the
245    // core version (initializeBrowserFuncs) can change in the future.
246    initializeExtraBrowserFuncs(&m_browserFuncs);
247
248    memset(&m_pluginFuncs, 0, sizeof(m_pluginFuncs));
249    m_pluginFuncs.size = sizeof(m_pluginFuncs);
250    if(NP_Initialize(&m_browserFuncs,
251                     &m_pluginFuncs,
252                     JSC::Bindings::getJNIEnv()) != NPERR_NO_ERROR) {
253        PLUGIN_LOG("Couldn't initialize plugin\n");
254        return false;
255    }
256
257    // Don't close the library - loaded OK.
258    dlCloser.ok();
259    m_isLoaded = true;
260    ++m_loadCount;
261    PLUGIN_LOG("Initial load ok, count now %d\n", m_loadCount);
262    return true;
263}
264
265bool PluginPackage::fetchInfo()
266{
267    PLUGIN_LOG("Fetch Info Loading \"%s\"\n", m_path.utf8().data());
268
269    // Open the library
270    void *handle = dlopen(m_path.utf8().data(), RTLD_NOW);
271    if(!handle) {
272        PLUGIN_LOG("Couldn't load plugin library \"%s\": %s\n",
273                   m_path.utf8().data(), dlerror());
274        return false;
275    }
276    PLUGIN_LOG("Fetch Info Loaded %p\n", handle);
277
278    // This object will call dlclose() and set m_module to NULL
279    // when going out of scope.
280    DynamicLibraryCloser dlCloser(&handle);
281
282    // Get the three entry points we need for Linux Netscape Plug-ins
283    NP_GetMIMEDescriptionFuncPtr NP_GetMIMEDescription;
284    NPP_GetValueProcPtr NP_GetValue;
285    if(!getEntryPoint(handle, "NP_GetMIMEDescription",
286            (void **) &NP_GetMIMEDescription) ||
287            !getEntryPoint(handle, "NP_GetValue", (void **) &NP_GetValue)) {
288        // If any of those failed to resolve, fail the entire load
289        return false;
290    }
291
292    // Get the plugin name and description using NP_GetValue
293    const char *name;
294    const char *description;
295    if(NP_GetValue(NULL, NPPVpluginNameString, &name) != NPERR_NO_ERROR ||
296            NP_GetValue(NULL, NPPVpluginDescriptionString, &description) !=
297                NPERR_NO_ERROR) {
298        PLUGIN_LOG("Couldn't get name/description using NP_GetValue\n");
299        return false;
300    }
301
302    PLUGIN_LOG("Plugin name: \"%s\"\n", name);
303    PLUGIN_LOG("Plugin description: \"%s\"\n", description);
304    m_name = name;
305    m_description = description;
306
307    // fileName is just the trailing part of the path
308    int last_slash = m_path.reverseFind('/');
309    if(last_slash < 0)
310        m_fileName = m_path;
311    else
312        m_fileName = m_path.substring(last_slash + 1);
313
314    // Grab the MIME description. This is in the format, e.g:
315    // application/x-somescriptformat:ssf:Some Script Format
316    String mimeDescription(NP_GetMIMEDescription());
317    PLUGIN_LOG("MIME description: \"%s\"\n", mimeDescription.utf8().data());
318    // Clear out the current mappings.
319    m_mimeToDescriptions.clear();
320    m_mimeToExtensions.clear();
321    // Split the description into its component entries, separated by
322    // semicolons.
323    Vector<String> mimeEntries;
324    mimeDescription.split(';', true, mimeEntries);
325    // Iterate through the entries, adding them to the MIME mappings.
326    for(Vector<String>::const_iterator it = mimeEntries.begin();
327            it != mimeEntries.end(); ++it) {
328        // Each part is split into 3 fields separated by colons
329        // Field 1 is the MIME type (e.g "application/x-shockwave-flash").
330        // Field 2 is a comma separated list of file extensions.
331        // Field 3 is a human readable short description.
332        const String &mimeEntry = *it;
333        Vector<String> fields;
334        mimeEntry.split(':', true, fields);
335        if(fields.size() != 3) {
336            PLUGIN_LOG("Bad MIME entry \"%s\"\n", mimeEntry.utf8().data());
337            return false;
338        }
339
340        const String& mimeType = fields[0];
341        Vector<String> extensions;
342        fields[1].split(',', true, extensions);
343        const String& description = fields[2];
344
345        determineQuirks(mimeType);
346
347        PLUGIN_LOG("mime_type: \"%s\"\n", mimeType.utf8().data());
348        PLUGIN_LOG("extensions: \"%s\"\n", fields[1].utf8().data());
349        PLUGIN_LOG("description: \"%s\"\n", description.utf8().data());
350
351        // Map the mime type to the vector of extensions and the description
352        if(!extensions.isEmpty())
353            m_mimeToExtensions.set(mimeType, extensions);
354        if(!description.isEmpty())
355            m_mimeToDescriptions.set(mimeType, description);
356    }
357
358    PLUGIN_LOG("Fetch Info Loaded plugin details ok \"%s\"\n",
359            m_path.utf8().data());
360
361    // If this plugin needs to be kept in memory, unload the module now
362    // and load it permanently.
363    if (m_quirks.contains(PluginQuirkDontUnloadPlugin)) {
364        dlCloser.ok();
365        dlclose(handle);
366        load();
367    }
368
369    // dlCloser will unload the plugin if required.
370    return true;
371}
372
373unsigned PluginPackage::hash() const
374{
375    const unsigned hashCodes[] = {
376        m_name.impl()->hash(),
377        m_description.impl()->hash(),
378        m_mimeToExtensions.size(),
379    };
380
381    return StringHasher::computeHash(reinterpret_cast<const UChar*>(hashCodes), sizeof(hashCodes) / sizeof(UChar));
382}
383
384bool PluginPackage::equal(const PluginPackage& a, const PluginPackage& b)
385{
386    if (a.m_name != b.m_name)
387        return false;
388
389    if (a.m_description != b.m_description)
390        return false;
391
392    if (a.m_mimeToExtensions.size() != b.m_mimeToExtensions.size())
393        return false;
394
395    MIMEToExtensionsMap::const_iterator::Keys end =
396            a.m_mimeToExtensions.end().keys();
397    for (MIMEToExtensionsMap::const_iterator::Keys it =
398                 a.m_mimeToExtensions.begin().keys();
399         it != end;
400         ++it) {
401        if (!b.m_mimeToExtensions.contains(*it)) {
402            return false;
403        }
404    }
405
406    return true;
407}
408
409uint16_t PluginPackage::NPVersion() const
410{
411    return NP_VERSION_MINOR;
412}
413
414} // namespace WebCore
415
416#endif
417