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 50 namespace 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. 54 class 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(). 78 class 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 96 static 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 112 bool PluginPackage::isPluginBlacklisted() 113 { 114 // No blacklisted Android plugins... yet! 115 return false; 116 } 117 118 void 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 128 static 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 141 static 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 193 bool 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 265 bool 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 373 unsigned 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 384 bool 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 409 uint16_t PluginPackage::NPVersion() const 410 { 411 return NP_VERSION_MINOR; 412 } 413 414 } // namespace WebCore 415 416 #endif 417