/*
 * 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 "CString.h"
#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>

// 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;
}

} // namespace WebCore

#endif
