1/*
2 * Copyright (C) 2006, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Collabora, Ltd.  All rights reserved.
4 * Copyright (C) 2009 Torch Mobile, Inc.  All rights reserved.
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
29#include "config.h"
30#include "PluginPackage.h"
31
32#include "MIMETypeRegistry.h"
33#include "PluginDatabase.h"
34#include "PluginDebug.h"
35#include "Timer.h"
36#include "npruntime_impl.h"
37#include <string.h>
38#include <wtf/OwnArrayPtr.h>
39#include <wtf/text/CString.h>
40#include <shlwapi.h>
41
42namespace WebCore {
43
44static String getVersionInfo(const LPVOID versionInfoData, const String& info)
45{
46    LPVOID buffer;
47    UINT bufferLength;
48    String subInfo = "\\StringfileInfo\\040904E4\\" + info;
49    bool retval = VerQueryValueW(versionInfoData,
50        const_cast<UChar*>(subInfo.charactersWithNullTermination()),
51        &buffer, &bufferLength);
52    if (!retval || bufferLength == 0)
53        return String();
54
55    // Subtract 1 from the length; we don't want the trailing null character.
56    return String(reinterpret_cast<UChar*>(buffer), bufferLength - 1);
57}
58
59bool PluginPackage::isPluginBlacklisted()
60{
61    if (name() == "Citrix ICA Client") {
62        // The Citrix ICA Client plug-in requires a Mozilla-based browser; see <rdar://6418681>.
63        return true;
64    }
65
66    if (name() == "Silverlight Plug-In") {
67        // workaround for <rdar://5557379> Crash in Silverlight when opening microsoft.com.
68        // the latest 1.0 version of Silverlight does not reproduce this crash, so allow it
69        // and any newer versions
70        static const PlatformModuleVersion slPluginMinRequired(0x51BE0000, 0x00010000);
71
72        if (compareFileVersion(slPluginMinRequired) < 0)
73            return true;
74    } else if (fileName() == "npmozax.dll") {
75        // Bug 15217: Mozilla ActiveX control complains about missing xpcom_core.dll
76        return true;
77    } else if (fileName() == "npwpf.dll") {
78        // Bug 57119: Microsoft Windows Presentation Foundation (WPF) plug-in complains about missing xpcom.dll
79        return true;
80    } else if (name() == "Yahoo Application State Plugin") {
81        // https://bugs.webkit.org/show_bug.cgi?id=26860
82        // Bug in Yahoo Application State plug-in earlier than 1.0.0.6 leads to heap corruption.
83        static const PlatformModuleVersion yahooAppStatePluginMinRequired(0x00000006, 0x00010000);
84        if (compareFileVersion(yahooAppStatePluginMinRequired) < 0)
85            return true;
86    }
87
88    return false;
89}
90
91void PluginPackage::determineQuirks(const String& mimeType)
92{
93    if (mimeType == "application/x-shockwave-flash") {
94        static const PlatformModuleVersion flashTenVersion(0x00000000, 0x000a0000);
95
96        // Pre 10 Flash only requests windowless plugins if we return a mozilla user agent
97        if (compareFileVersion(flashTenVersion) < 0)
98            m_quirks.add(PluginQuirkWantsMozillaUserAgent);
99
100        m_quirks.add(PluginQuirkThrottleInvalidate);
101        m_quirks.add(PluginQuirkThrottleWMUserPlusOneMessages);
102        m_quirks.add(PluginQuirkFlashURLNotifyBug);
103    }
104
105    if (name().contains("Microsoft") && name().contains("Windows Media")) {
106        // The WMP plugin sets its size on the first NPP_SetWindow call and never updates its size, so
107        // call SetWindow when the plugin view has a correct size
108        m_quirks.add(PluginQuirkDeferFirstSetWindowCall);
109
110        // Windowless mode does not work at all with the WMP plugin so just remove that parameter
111        // and don't pass it to the plug-in.
112        m_quirks.add(PluginQuirkRemoveWindowlessVideoParam);
113
114        // WMP has a modal message loop that it enters whenever we call it or
115        // ask it to paint. This modal loop can deliver messages to other
116        // windows in WebKit at times when they are not expecting them (for
117        // example, delivering a WM_PAINT message during a layout), and these
118        // can cause crashes.
119        m_quirks.add(PluginQuirkHasModalMessageLoop);
120    }
121
122    if (name() == "VLC Multimedia Plugin" || name() == "VLC Multimedia Plug-in") {
123        // VLC hangs on NPP_Destroy if we call NPP_SetWindow with a null window handle
124        m_quirks.add(PluginQuirkDontSetNullWindowHandleOnDestroy);
125
126        // VLC 0.8.6d and 0.8.6e crash if multiple instances are created.
127        // <rdar://problem/5773070> tracks allowing multiple instances when this
128        // bug is fixed.
129        m_quirks.add(PluginQuirkDontAllowMultipleInstances);
130    }
131
132    // The DivX plugin sets its size on the first NPP_SetWindow call and never updates its size, so
133    // call SetWindow when the plugin view has a correct size
134    if (mimeType == "video/divx")
135        m_quirks.add(PluginQuirkDeferFirstSetWindowCall);
136
137    // FIXME: This is a workaround for a problem in our NPRuntime bindings; if a plug-in creates an
138    // NPObject and passes it to a function it's not possible to see what root object that NPObject belongs to.
139    // Thus, we don't know that the object should be invalidated when the plug-in instance goes away.
140    // See <rdar://problem/5487742>.
141    if (mimeType == "application/x-silverlight")
142        m_quirks.add(PluginQuirkDontUnloadPlugin);
143
144    if (MIMETypeRegistry::isJavaAppletMIMEType(mimeType)) {
145        // Because a single process cannot create multiple VMs, and we cannot reliably unload a
146        // Java VM, we cannot unload the Java plugin, or we'll lose reference to our only VM
147        m_quirks.add(PluginQuirkDontUnloadPlugin);
148
149        // Setting the window region to an empty region causes bad scrolling repaint problems
150        // with the Java plug-in.
151        m_quirks.add(PluginQuirkDontClipToZeroRectWhenScrolling);
152    }
153
154    if (mimeType == "audio/x-pn-realaudio-plugin") {
155        // Prevent the Real plugin from calling the Window Proc recursively, causing the stack to overflow.
156        m_quirks.add(PluginQuirkDontCallWndProcForSameMessageRecursively);
157
158        static const PlatformModuleVersion lastKnownUnloadableRealPlayerVersion(0x000B0B24, 0x00060000);
159
160        // Unloading RealPlayer versions newer than 10.5 can cause a hang; see rdar://5669317.
161        // FIXME: Resume unloading when this bug in the RealPlayer Plug-In is fixed (rdar://5713147)
162        if (compareFileVersion(lastKnownUnloadableRealPlayerVersion) > 0)
163            m_quirks.add(PluginQuirkDontUnloadPlugin);
164    }
165}
166
167bool PluginPackage::fetchInfo()
168{
169    DWORD versionInfoSize, zeroHandle;
170    versionInfoSize = GetFileVersionInfoSizeW(const_cast<UChar*>(m_path.charactersWithNullTermination()), &zeroHandle);
171    if (versionInfoSize == 0)
172        return false;
173
174    OwnArrayPtr<char> versionInfoData = adoptArrayPtr(new char[versionInfoSize]);
175
176    if (!GetFileVersionInfoW(const_cast<UChar*>(m_path.charactersWithNullTermination()),
177            0, versionInfoSize, versionInfoData.get()))
178        return false;
179
180    m_name = getVersionInfo(versionInfoData.get(), "ProductName");
181    m_description = getVersionInfo(versionInfoData.get(), "FileDescription");
182    if (m_name.isNull() || m_description.isNull())
183        return false;
184
185    VS_FIXEDFILEINFO* info;
186    UINT infoSize;
187    if (!VerQueryValueW(versionInfoData.get(), L"\\", (LPVOID*) &info, &infoSize) || infoSize < sizeof(VS_FIXEDFILEINFO))
188        return false;
189    m_moduleVersion.leastSig = info->dwFileVersionLS;
190    m_moduleVersion.mostSig = info->dwFileVersionMS;
191
192    if (isPluginBlacklisted())
193        return false;
194
195    Vector<String> types;
196    getVersionInfo(versionInfoData.get(), "MIMEType").split('|', types);
197    Vector<String> extensionLists;
198    getVersionInfo(versionInfoData.get(), "FileExtents").split('|', extensionLists);
199    Vector<String> descriptions;
200    getVersionInfo(versionInfoData.get(), "FileOpenName").split('|', descriptions);
201
202    for (unsigned i = 0; i < types.size(); i++) {
203        String type = types[i].lower();
204        String description = i < descriptions.size() ? descriptions[i] : "";
205        String extensionList = i < extensionLists.size() ? extensionLists[i] : "";
206
207        Vector<String> extensionsVector;
208        extensionList.split(',', extensionsVector);
209
210        // Get rid of the extension list that may be at the end of the description string.
211        int pos = description.find("(*");
212        if (pos != -1) {
213            // There might be a space that we need to get rid of.
214            if (pos > 1 && description[pos - 1] == ' ')
215                pos--;
216            description = description.left(pos);
217        }
218
219        // Determine the quirks for the MIME types this plug-in supports
220        determineQuirks(type);
221
222        m_mimeToExtensions.add(type, extensionsVector);
223        m_mimeToDescriptions.add(type, description);
224    }
225
226    return true;
227}
228
229bool PluginPackage::load()
230{
231    if (m_freeLibraryTimer.isActive()) {
232        ASSERT(m_module);
233        m_freeLibraryTimer.stop();
234    } else if (m_isLoaded) {
235        if (m_quirks.contains(PluginQuirkDontAllowMultipleInstances))
236            return false;
237        m_loadCount++;
238        return true;
239    } else {
240#if OS(WINCE)
241        m_module = ::LoadLibraryW(m_path.charactersWithNullTermination());
242#else
243        WCHAR currentPath[MAX_PATH];
244
245        if (!::GetCurrentDirectoryW(MAX_PATH, currentPath))
246            return false;
247
248        String path = m_path.substring(0, m_path.reverseFind('\\'));
249
250        if (!::SetCurrentDirectoryW(path.charactersWithNullTermination()))
251            return false;
252
253        // Load the library
254        m_module = ::LoadLibraryExW(m_path.charactersWithNullTermination(), 0, LOAD_WITH_ALTERED_SEARCH_PATH);
255
256        if (!::SetCurrentDirectoryW(currentPath)) {
257            if (m_module)
258                ::FreeLibrary(m_module);
259            return false;
260        }
261#endif
262    }
263
264    if (!m_module)
265        return false;
266
267    m_isLoaded = true;
268
269    NP_GetEntryPointsFuncPtr NP_GetEntryPoints = 0;
270    NP_InitializeFuncPtr NP_Initialize = 0;
271    NPError npErr;
272
273#if OS(WINCE)
274    NP_Initialize = (NP_InitializeFuncPtr)GetProcAddress(m_module, L"NP_Initialize");
275    NP_GetEntryPoints = (NP_GetEntryPointsFuncPtr)GetProcAddress(m_module, L"NP_GetEntryPoints");
276    m_NPP_Shutdown = (NPP_ShutdownProcPtr)GetProcAddress(m_module, L"NP_Shutdown");
277#else
278    NP_Initialize = (NP_InitializeFuncPtr)GetProcAddress(m_module, "NP_Initialize");
279    NP_GetEntryPoints = (NP_GetEntryPointsFuncPtr)GetProcAddress(m_module, "NP_GetEntryPoints");
280    m_NPP_Shutdown = (NPP_ShutdownProcPtr)GetProcAddress(m_module, "NP_Shutdown");
281#endif
282
283    if (!NP_Initialize || !NP_GetEntryPoints || !m_NPP_Shutdown)
284        goto abort;
285
286    memset(&m_pluginFuncs, 0, sizeof(m_pluginFuncs));
287    m_pluginFuncs.size = sizeof(m_pluginFuncs);
288
289    npErr = NP_GetEntryPoints(&m_pluginFuncs);
290    LOG_NPERROR(npErr);
291    if (npErr != NPERR_NO_ERROR)
292        goto abort;
293
294    initializeBrowserFuncs();
295
296    npErr = NP_Initialize(&m_browserFuncs);
297    LOG_NPERROR(npErr);
298
299    if (npErr != NPERR_NO_ERROR)
300        goto abort;
301
302    m_loadCount++;
303    return true;
304
305abort:
306    unloadWithoutShutdown();
307    return false;
308}
309
310unsigned PluginPackage::hash() const
311{
312    const unsigned hashCodes[] = {
313        m_name.impl()->hash(),
314        m_description.impl()->hash(),
315        m_mimeToExtensions.size()
316    };
317
318    return StringHasher::hashMemory<sizeof(hashCodes)>(hashCodes);
319}
320
321bool PluginPackage::equal(const PluginPackage& a, const PluginPackage& b)
322{
323    if (a.m_name != b.m_name)
324        return false;
325
326    if (a.m_description != b.m_description)
327        return false;
328
329    if (a.m_mimeToExtensions.size() != b.m_mimeToExtensions.size())
330        return false;
331
332    MIMEToExtensionsMap::const_iterator::Keys end = a.m_mimeToExtensions.end().keys();
333    for (MIMEToExtensionsMap::const_iterator::Keys it = a.m_mimeToExtensions.begin().keys(); it != end; ++it) {
334        if (!b.m_mimeToExtensions.contains(*it))
335            return false;
336    }
337
338    return true;
339}
340
341uint16_t PluginPackage::NPVersion() const
342{
343    return NP_VERSION_MINOR;
344}
345}
346