1/*
2 * Copyright (C) 2006, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Collabora Ltd.  All rights reserved.
4 * Copyright (C) 2009 Holger Hans Peter Freyther
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "config.h"
29#include "PluginPackage.h"
30
31#include "MIMETypeRegistry.h"
32#include "PluginDatabase.h"
33#include "PluginDebug.h"
34#include "Timer.h"
35#include "npruntime_impl.h"
36#include <string.h>
37#include <wtf/OwnArrayPtr.h>
38#include <wtf/text/CString.h>
39
40namespace WebCore {
41
42PluginPackage::~PluginPackage()
43{
44    // This destructor gets called during refresh() if PluginDatabase's
45    // PluginSet hash is already populated, as it removes items from
46    // the hash table. Calling the destructor on a loaded plug-in of
47    // course would cause a crash, so we check to call unload before we
48    // ASSERT.
49    // FIXME: There is probably a better way to fix this.
50    if (!m_loadCount)
51        unloadWithoutShutdown();
52    else
53        unload();
54
55    ASSERT(!m_isLoaded);
56}
57
58void PluginPackage::freeLibrarySoon()
59{
60    ASSERT(!m_freeLibraryTimer.isActive());
61    ASSERT(m_module);
62    ASSERT(!m_loadCount);
63
64    m_freeLibraryTimer.startOneShot(0);
65}
66
67void PluginPackage::freeLibraryTimerFired(Timer<PluginPackage>*)
68{
69    ASSERT(m_module);
70    // Do nothing if the module got loaded again meanwhile
71    if (!m_loadCount) {
72        unloadModule(m_module);
73        m_module = 0;
74    }
75}
76
77
78int PluginPackage::compare(const PluginPackage& compareTo) const
79{
80    // Sort plug-ins that allow multiple instances first.
81    bool AallowsMultipleInstances = !quirks().contains(PluginQuirkDontAllowMultipleInstances);
82    bool BallowsMultipleInstances = !compareTo.quirks().contains(PluginQuirkDontAllowMultipleInstances);
83    if (AallowsMultipleInstances != BallowsMultipleInstances)
84        return AallowsMultipleInstances ? -1 : 1;
85
86    // Sort plug-ins in a preferred path first.
87    bool AisInPreferredDirectory = PluginDatabase::isPreferredPluginDirectory(parentDirectory());
88    bool BisInPreferredDirectory = PluginDatabase::isPreferredPluginDirectory(compareTo.parentDirectory());
89    if (AisInPreferredDirectory != BisInPreferredDirectory)
90        return AisInPreferredDirectory ? -1 : 1;
91
92    int diff = strcmp(name().utf8().data(), compareTo.name().utf8().data());
93    if (diff)
94        return diff;
95
96    diff = compareFileVersion(compareTo.version());
97    if (diff)
98        return diff;
99
100    return strcmp(parentDirectory().utf8().data(), compareTo.parentDirectory().utf8().data());
101}
102
103PluginPackage::PluginPackage(const String& path, const time_t& lastModified)
104    : m_isEnabled(true)
105    , m_isLoaded(false)
106    , m_loadCount(0)
107    , m_path(path)
108    , m_moduleVersion(0)
109    , m_module(0)
110    , m_lastModified(lastModified)
111    , m_freeLibraryTimer(this, &PluginPackage::freeLibraryTimerFired)
112#if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
113    , m_infoIsFromCache(true)
114#endif
115{
116    m_fileName = pathGetFileName(m_path);
117    m_parentDirectory = m_path.left(m_path.length() - m_fileName.length() - 1);
118}
119
120void PluginPackage::unload()
121{
122    if (!m_isLoaded)
123        return;
124
125    if (--m_loadCount > 0)
126        return;
127
128    m_NPP_Shutdown();
129
130    unloadWithoutShutdown();
131}
132
133void PluginPackage::unloadWithoutShutdown()
134{
135    if (!m_isLoaded)
136        return;
137
138    ASSERT(!m_loadCount);
139    ASSERT(m_module);
140
141    // <rdar://5530519>: Crash when closing tab with pdf file (Reader 7 only)
142    // If the plugin has subclassed its parent window, as with Reader 7, we may have
143    // gotten here by way of the plugin's internal window proc forwarding a message to our
144    // original window proc. If we free the plugin library from here, we will jump back
145    // to code we just freed when we return, so delay calling FreeLibrary at least until
146    // the next message loop
147    freeLibrarySoon();
148
149    m_isLoaded = false;
150}
151
152void PluginPackage::setEnabled(bool enabled)
153{
154    m_isEnabled = enabled;
155}
156
157PassRefPtr<PluginPackage> PluginPackage::createPackage(const String& path, const time_t& lastModified)
158{
159    RefPtr<PluginPackage> package = adoptRef(new PluginPackage(path, lastModified));
160
161    if (!package->fetchInfo())
162        return 0;
163
164    return package.release();
165}
166
167#if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
168PassRefPtr<PluginPackage> PluginPackage::createPackageFromCache(const String& path, const time_t& lastModified, const String& name, const String& description, const String& mimeDescription)
169{
170    RefPtr<PluginPackage> package = adoptRef(new PluginPackage(path, lastModified));
171    package->m_name = name;
172    package->m_description = description;
173    package->determineModuleVersionFromDescription();
174    package->setMIMEDescription(mimeDescription);
175    package->m_infoIsFromCache = true;
176    return package.release();
177}
178#endif
179
180#if defined(XP_UNIX)
181void PluginPackage::determineQuirks(const String& mimeType)
182{
183    if (MIMETypeRegistry::isJavaAppletMIMEType(mimeType)) {
184        // Because a single process cannot create multiple VMs, and we cannot reliably unload a
185        // Java VM, we cannot unload the Java plugin, or we'll lose reference to our only VM
186        m_quirks.add(PluginQuirkDontUnloadPlugin);
187
188        // Setting the window region to an empty region causes bad scrolling repaint problems
189        // with the Java plug-in.
190        m_quirks.add(PluginQuirkDontClipToZeroRectWhenScrolling);
191        return;
192    }
193
194    if (mimeType == "application/x-shockwave-flash") {
195        static const PlatformModuleVersion flashTenVersion(0x0a000000);
196
197        if (compareFileVersion(flashTenVersion) >= 0) {
198            // Flash 10.0 b218 doesn't like having a NULL window handle
199            m_quirks.add(PluginQuirkDontSetNullWindowHandleOnDestroy);
200#if PLATFORM(QT)
201            m_quirks.add(PluginQuirkRequiresGtkToolKit);
202#endif
203        } else {
204            // Flash 9 and older requests windowless plugins if we return a mozilla user agent
205            m_quirks.add(PluginQuirkWantsMozillaUserAgent);
206        }
207
208#if PLATFORM(QT)
209        // Flash will crash on repeated calls to SetWindow in windowed mode
210        m_quirks.add(PluginQuirkDontCallSetWindowMoreThanOnce);
211#endif
212
213#if CPU(X86_64)
214        // 64-bit Flash freezes if right-click is sent in windowless mode
215        m_quirks.add(PluginQuirkIgnoreRightClickInWindowlessMode);
216#endif
217
218        m_quirks.add(PluginQuirkRequiresDefaultScreenDepth);
219        m_quirks.add(PluginQuirkThrottleInvalidate);
220        m_quirks.add(PluginQuirkThrottleWMUserPlusOneMessages);
221        m_quirks.add(PluginQuirkFlashURLNotifyBug);
222    }
223}
224#endif
225
226#if !OS(WINDOWS)
227void PluginPackage::determineModuleVersionFromDescription()
228{
229    // It's a bit lame to detect the plugin version by parsing it
230    // from the plugin description string, but it doesn't seem that
231    // version information is available in any standardized way at
232    // the module level, like in Windows
233
234    if (m_description.isEmpty())
235        return;
236
237    if (m_description.startsWith("Shockwave Flash") && m_description.length() >= 19) {
238        // The flash version as a PlatformModuleVersion differs on Unix from Windows
239        // since the revision can be larger than a 8 bits, so we allow it 16 here and
240        // push the major/minor up 8 bits. Thus on Unix, Flash's version may be
241        // 0x0a000000 instead of 0x000a0000.
242
243        Vector<String> versionParts;
244        m_description.substring(16).split(' ', /*allowEmptyEntries =*/ false, versionParts);
245        if (versionParts.isEmpty())
246            return;
247
248        if (versionParts.size() >= 1) {
249            Vector<String> majorMinorParts;
250            versionParts[0].split('.', majorMinorParts);
251            if (majorMinorParts.size() >= 1) {
252                bool converted = false;
253                unsigned major = majorMinorParts[0].toUInt(&converted);
254                if (converted)
255                    m_moduleVersion = (major & 0xff) << 24;
256            }
257            if (majorMinorParts.size() == 2) {
258                bool converted = false;
259                unsigned minor = majorMinorParts[1].toUInt(&converted);
260                if (converted)
261                    m_moduleVersion |= (minor & 0xff) << 16;
262            }
263        }
264
265        if (versionParts.size() >= 2) {
266            String revision = versionParts[1];
267            if (revision.length() > 1 && (revision[0] == 'r' || revision[0] == 'b')) {
268                revision.remove(0, 1);
269                m_moduleVersion |= revision.toInt() & 0xffff;
270            }
271        }
272    }
273}
274#endif
275
276#if ENABLE(NETSCAPE_PLUGIN_API)
277void PluginPackage::initializeBrowserFuncs()
278{
279    memset(&m_browserFuncs, 0, sizeof(m_browserFuncs));
280    m_browserFuncs.size = sizeof(m_browserFuncs);
281    m_browserFuncs.version = NPVersion();
282
283    m_browserFuncs.geturl = NPN_GetURL;
284    m_browserFuncs.posturl = NPN_PostURL;
285    m_browserFuncs.requestread = NPN_RequestRead;
286    m_browserFuncs.newstream = NPN_NewStream;
287    m_browserFuncs.write = NPN_Write;
288    m_browserFuncs.destroystream = NPN_DestroyStream;
289    m_browserFuncs.status = NPN_Status;
290    m_browserFuncs.uagent = NPN_UserAgent;
291    m_browserFuncs.memalloc = NPN_MemAlloc;
292    m_browserFuncs.memfree = NPN_MemFree;
293    m_browserFuncs.memflush = NPN_MemFlush;
294    m_browserFuncs.reloadplugins = NPN_ReloadPlugins;
295    m_browserFuncs.geturlnotify = NPN_GetURLNotify;
296    m_browserFuncs.posturlnotify = NPN_PostURLNotify;
297    m_browserFuncs.getvalue = NPN_GetValue;
298    m_browserFuncs.setvalue = NPN_SetValue;
299    m_browserFuncs.invalidaterect = NPN_InvalidateRect;
300    m_browserFuncs.invalidateregion = NPN_InvalidateRegion;
301    m_browserFuncs.forceredraw = NPN_ForceRedraw;
302    m_browserFuncs.getJavaEnv = NPN_GetJavaEnv;
303    m_browserFuncs.getJavaPeer = NPN_GetJavaPeer;
304    m_browserFuncs.pushpopupsenabledstate = NPN_PushPopupsEnabledState;
305    m_browserFuncs.poppopupsenabledstate = NPN_PopPopupsEnabledState;
306    m_browserFuncs.pluginthreadasynccall = NPN_PluginThreadAsyncCall;
307
308    m_browserFuncs.releasevariantvalue = _NPN_ReleaseVariantValue;
309    m_browserFuncs.getstringidentifier = _NPN_GetStringIdentifier;
310    m_browserFuncs.getstringidentifiers = _NPN_GetStringIdentifiers;
311    m_browserFuncs.getintidentifier = _NPN_GetIntIdentifier;
312    m_browserFuncs.identifierisstring = _NPN_IdentifierIsString;
313    m_browserFuncs.utf8fromidentifier = _NPN_UTF8FromIdentifier;
314    m_browserFuncs.intfromidentifier = _NPN_IntFromIdentifier;
315    m_browserFuncs.createobject = _NPN_CreateObject;
316    m_browserFuncs.retainobject = _NPN_RetainObject;
317    m_browserFuncs.releaseobject = _NPN_ReleaseObject;
318    m_browserFuncs.invoke = _NPN_Invoke;
319    m_browserFuncs.invokeDefault = _NPN_InvokeDefault;
320    m_browserFuncs.evaluate = _NPN_Evaluate;
321    m_browserFuncs.getproperty = _NPN_GetProperty;
322    m_browserFuncs.setproperty = _NPN_SetProperty;
323    m_browserFuncs.removeproperty = _NPN_RemoveProperty;
324    m_browserFuncs.hasproperty = _NPN_HasProperty;
325    m_browserFuncs.hasmethod = _NPN_HasMethod;
326    m_browserFuncs.setexception = _NPN_SetException;
327    m_browserFuncs.enumerate = _NPN_Enumerate;
328    m_browserFuncs.construct = _NPN_Construct;
329    m_browserFuncs.getvalueforurl = NPN_GetValueForURL;
330    m_browserFuncs.setvalueforurl = NPN_SetValueForURL;
331    m_browserFuncs.getauthenticationinfo = NPN_GetAuthenticationInfo;
332
333    m_browserFuncs.popupcontextmenu = NPN_PopUpContextMenu;
334}
335#endif // ENABLE(NETSCAPE_PLUGIN_API)
336
337#if ENABLE(PLUGIN_PACKAGE_SIMPLE_HASH)
338unsigned PluginPackage::hash() const
339{
340    struct HashCodes {
341        unsigned hash;
342        time_t modifiedDate;
343    } hashCodes;
344
345    hashCodes.hash = m_path.impl()->hash();
346    hashCodes.modifiedDate = m_lastModified;
347
348    return StringHasher::hashMemory<sizeof(hashCodes)>(&hashCodes);
349}
350
351bool PluginPackage::equal(const PluginPackage& a, const PluginPackage& b)
352{
353    return a.m_description == b.m_description;
354}
355#endif
356
357int PluginPackage::compareFileVersion(const PlatformModuleVersion& compareVersion) const
358{
359    // return -1, 0, or 1 if plug-in version is less than, equal to, or greater than
360    // the passed version
361
362#if OS(WINDOWS)
363    if (m_moduleVersion.mostSig != compareVersion.mostSig)
364        return m_moduleVersion.mostSig > compareVersion.mostSig ? 1 : -1;
365    if (m_moduleVersion.leastSig != compareVersion.leastSig)
366        return m_moduleVersion.leastSig > compareVersion.leastSig ? 1 : -1;
367#else
368    if (m_moduleVersion != compareVersion)
369        return m_moduleVersion > compareVersion ? 1 : -1;
370#endif
371
372    return 0;
373}
374
375#if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
376bool PluginPackage::ensurePluginLoaded()
377{
378    if (!m_infoIsFromCache)
379        return m_isLoaded;
380
381    m_quirks = PluginQuirkSet();
382    m_name = String();
383    m_description = String();
384    m_fullMIMEDescription = String();
385    m_moduleVersion = 0;
386
387    return fetchInfo();
388}
389#endif
390
391}
392