|
1 /* |
|
2 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. |
|
3 * Copyright (C) 2008 Collabora, Ltd. 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 * 1. Redistributions of source code must retain the above copyright |
|
9 * notice, this list of conditions and the following disclaimer. |
|
10 * 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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 "PluginDatabase.h" |
|
29 |
|
30 #include "Frame.h" |
|
31 #include "KURL.h" |
|
32 #include "PluginPackage.h" |
|
33 #include <stdlib.h> |
|
34 |
|
35 namespace WebCore { |
|
36 |
|
37 typedef HashMap<String, RefPtr<PluginPackage> > PluginPackageByNameMap; |
|
38 |
|
39 PluginDatabase::PluginDatabase() |
|
40 { |
|
41 } |
|
42 |
|
43 PluginDatabase* PluginDatabase::installedPlugins(bool populate) |
|
44 { |
|
45 static PluginDatabase* plugins = 0; |
|
46 |
|
47 if (!plugins) { |
|
48 plugins = new PluginDatabase; |
|
49 |
|
50 if (populate) { |
|
51 plugins->setPluginDirectories(PluginDatabase::defaultPluginDirectories()); |
|
52 plugins->refresh(); |
|
53 } |
|
54 } |
|
55 |
|
56 return plugins; |
|
57 } |
|
58 |
|
59 bool PluginDatabase::isMIMETypeRegistered(const String& mimeType) |
|
60 { |
|
61 if (mimeType.isNull()) |
|
62 return false; |
|
63 if (m_registeredMIMETypes.contains(mimeType)) |
|
64 return true; |
|
65 // No plugin was found, try refreshing the database and searching again |
|
66 return (refresh() && m_registeredMIMETypes.contains(mimeType)); |
|
67 } |
|
68 |
|
69 void PluginDatabase::addExtraPluginDirectory(const String& directory) |
|
70 { |
|
71 m_pluginDirectories.append(directory); |
|
72 refresh(); |
|
73 } |
|
74 |
|
75 bool PluginDatabase::refresh() |
|
76 { |
|
77 bool pluginSetChanged = false; |
|
78 |
|
79 if (!m_plugins.isEmpty()) { |
|
80 PluginSet pluginsToUnload; |
|
81 getDeletedPlugins(pluginsToUnload); |
|
82 |
|
83 // Unload plugins |
|
84 PluginSet::const_iterator end = pluginsToUnload.end(); |
|
85 for (PluginSet::const_iterator it = pluginsToUnload.begin(); it != end; ++it) |
|
86 remove(it->get()); |
|
87 |
|
88 pluginSetChanged = !pluginsToUnload.isEmpty(); |
|
89 } |
|
90 |
|
91 HashSet<String> paths; |
|
92 getPluginPathsInDirectories(paths); |
|
93 |
|
94 HashMap<String, time_t> pathsWithTimes; |
|
95 |
|
96 // We should only skip unchanged files if we didn't remove any plugins above. If we did remove |
|
97 // any plugins, we need to look at every plugin file so that, e.g., if the user has two versions |
|
98 // of RealPlayer installed and just removed the newer one, we'll pick up the older one. |
|
99 bool shouldSkipUnchangedFiles = !pluginSetChanged; |
|
100 |
|
101 HashSet<String>::const_iterator pathsEnd = paths.end(); |
|
102 for (HashSet<String>::const_iterator it = paths.begin(); it != pathsEnd; ++it) { |
|
103 time_t lastModified; |
|
104 if (!getFileModificationTime(*it, lastModified)) |
|
105 continue; |
|
106 |
|
107 pathsWithTimes.add(*it, lastModified); |
|
108 |
|
109 // If the path's timestamp hasn't changed since the last time we ran refresh(), we don't have to do anything. |
|
110 if (shouldSkipUnchangedFiles && m_pluginPathsWithTimes.get(*it) == lastModified) |
|
111 continue; |
|
112 |
|
113 if (RefPtr<PluginPackage> oldPackage = m_pluginsByPath.get(*it)) { |
|
114 ASSERT(!shouldSkipUnchangedFiles || oldPackage->lastModified() != lastModified); |
|
115 remove(oldPackage.get()); |
|
116 } |
|
117 |
|
118 RefPtr<PluginPackage> package = PluginPackage::createPackage(*it, lastModified); |
|
119 if (package && add(package.release())) |
|
120 pluginSetChanged = true; |
|
121 } |
|
122 |
|
123 // Cache all the paths we found with their timestamps for next time. |
|
124 pathsWithTimes.swap(m_pluginPathsWithTimes); |
|
125 |
|
126 if (!pluginSetChanged) |
|
127 return false; |
|
128 |
|
129 m_registeredMIMETypes.clear(); |
|
130 |
|
131 // Register plug-in MIME types |
|
132 PluginSet::const_iterator end = m_plugins.end(); |
|
133 for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) { |
|
134 // Get MIME types |
|
135 MIMEToDescriptionsMap::const_iterator map_it = (*it)->mimeToDescriptions().begin(); |
|
136 MIMEToDescriptionsMap::const_iterator map_end = (*it)->mimeToDescriptions().end(); |
|
137 for (; map_it != map_end; ++map_it) |
|
138 m_registeredMIMETypes.add(map_it->first); |
|
139 } |
|
140 |
|
141 return true; |
|
142 } |
|
143 |
|
144 Vector<PluginPackage*> PluginDatabase::plugins() const |
|
145 { |
|
146 Vector<PluginPackage*> result; |
|
147 |
|
148 PluginSet::const_iterator end = m_plugins.end(); |
|
149 for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) |
|
150 result.append((*it).get()); |
|
151 |
|
152 return result; |
|
153 } |
|
154 |
|
155 int PluginDatabase::preferredPluginCompare(const void* a, const void* b) |
|
156 { |
|
157 PluginPackage* pluginA = *static_cast<PluginPackage* const*>(a); |
|
158 PluginPackage* pluginB = *static_cast<PluginPackage* const*>(b); |
|
159 |
|
160 return pluginA->compare(*pluginB); |
|
161 } |
|
162 |
|
163 PluginPackage* PluginDatabase::pluginForMIMEType(const String& mimeType) |
|
164 { |
|
165 if (mimeType.isEmpty()) |
|
166 return 0; |
|
167 |
|
168 String key = mimeType.lower(); |
|
169 PluginSet::const_iterator end = m_plugins.end(); |
|
170 PluginPackage* preferredPlugin = m_preferredPlugins.get(key).get(); |
|
171 if (preferredPlugin |
|
172 && preferredPlugin->isEnabled() |
|
173 && preferredPlugin->mimeToDescriptions().contains(key)) { |
|
174 return preferredPlugin; |
|
175 } |
|
176 |
|
177 Vector<PluginPackage*, 2> pluginChoices; |
|
178 |
|
179 for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) { |
|
180 PluginPackage* plugin = (*it).get(); |
|
181 |
|
182 if (!plugin->isEnabled()) |
|
183 continue; |
|
184 |
|
185 if (plugin->mimeToDescriptions().contains(key)) |
|
186 pluginChoices.append(plugin); |
|
187 } |
|
188 |
|
189 if (pluginChoices.isEmpty()) |
|
190 return 0; |
|
191 |
|
192 qsort(pluginChoices.data(), pluginChoices.size(), sizeof(PluginPackage*), PluginDatabase::preferredPluginCompare); |
|
193 |
|
194 return pluginChoices[0]; |
|
195 } |
|
196 |
|
197 String PluginDatabase::MIMETypeForExtension(const String& extension) const |
|
198 { |
|
199 if (extension.isEmpty()) |
|
200 return String(); |
|
201 |
|
202 PluginSet::const_iterator end = m_plugins.end(); |
|
203 String mimeType; |
|
204 Vector<PluginPackage*, 2> pluginChoices; |
|
205 HashMap<PluginPackage*, String> mimeTypeForPlugin; |
|
206 |
|
207 for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) { |
|
208 if (!(*it)->isEnabled()) |
|
209 continue; |
|
210 |
|
211 MIMEToExtensionsMap::const_iterator mime_end = (*it)->mimeToExtensions().end(); |
|
212 |
|
213 for (MIMEToExtensionsMap::const_iterator mime_it = (*it)->mimeToExtensions().begin(); mime_it != mime_end; ++mime_it) { |
|
214 mimeType = mime_it->first; |
|
215 PluginPackage* preferredPlugin = m_preferredPlugins.get(mimeType).get(); |
|
216 const Vector<String>& extensions = mime_it->second; |
|
217 bool foundMapping = false; |
|
218 for (unsigned i = 0; i < extensions.size(); i++) { |
|
219 if (equalIgnoringCase(extensions[i], extension)) { |
|
220 PluginPackage* plugin = (*it).get(); |
|
221 |
|
222 if (preferredPlugin && PluginPackage::equal(*plugin, *preferredPlugin)) |
|
223 return mimeType; |
|
224 |
|
225 pluginChoices.append(plugin); |
|
226 mimeTypeForPlugin.add(plugin, mimeType); |
|
227 foundMapping = true; |
|
228 break; |
|
229 } |
|
230 } |
|
231 if (foundMapping) |
|
232 break; |
|
233 } |
|
234 } |
|
235 |
|
236 if (pluginChoices.isEmpty()) |
|
237 return String(); |
|
238 |
|
239 qsort(pluginChoices.data(), pluginChoices.size(), sizeof(PluginPackage*), PluginDatabase::preferredPluginCompare); |
|
240 |
|
241 return mimeTypeForPlugin.get(pluginChoices[0]); |
|
242 } |
|
243 |
|
244 PluginPackage* PluginDatabase::findPlugin(const KURL& url, String& mimeType) |
|
245 { |
|
246 PluginPackage* plugin = pluginForMIMEType(mimeType); |
|
247 String filename = url.string(); |
|
248 |
|
249 if (!plugin) { |
|
250 String filename = url.lastPathComponent(); |
|
251 if (!filename.endsWith("/")) { |
|
252 int extensionPos = filename.reverseFind('.'); |
|
253 if (extensionPos != -1) { |
|
254 String extension = filename.substring(extensionPos + 1); |
|
255 |
|
256 String mimeTypeForExtension = MIMETypeForExtension(extension); |
|
257 if ((plugin = pluginForMIMEType(mimeTypeForExtension))) |
|
258 mimeType = mimeTypeForExtension; |
|
259 } |
|
260 } |
|
261 } |
|
262 |
|
263 // FIXME: if no plugin could be found, query Windows for the mime type |
|
264 // corresponding to the extension. |
|
265 |
|
266 return plugin; |
|
267 } |
|
268 |
|
269 void PluginDatabase::setPreferredPluginForMIMEType(const String& mimeType, PluginPackage* plugin) |
|
270 { |
|
271 if (!plugin || plugin->mimeToExtensions().contains(mimeType)) |
|
272 m_preferredPlugins.set(mimeType.lower(), plugin); |
|
273 } |
|
274 |
|
275 void PluginDatabase::getDeletedPlugins(PluginSet& plugins) const |
|
276 { |
|
277 PluginSet::const_iterator end = m_plugins.end(); |
|
278 for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) { |
|
279 if (!fileExists((*it)->path())) |
|
280 plugins.add(*it); |
|
281 } |
|
282 } |
|
283 |
|
284 bool PluginDatabase::add(PassRefPtr<PluginPackage> prpPackage) |
|
285 { |
|
286 ASSERT_ARG(prpPackage, prpPackage); |
|
287 |
|
288 RefPtr<PluginPackage> package = prpPackage; |
|
289 |
|
290 if (!m_plugins.add(package).second) |
|
291 return false; |
|
292 |
|
293 m_pluginsByPath.add(package->path(), package); |
|
294 return true; |
|
295 } |
|
296 |
|
297 void PluginDatabase::remove(PluginPackage* package) |
|
298 { |
|
299 MIMEToExtensionsMap::const_iterator it = package->mimeToExtensions().begin(); |
|
300 MIMEToExtensionsMap::const_iterator end = package->mimeToExtensions().end(); |
|
301 for ( ; it != end; ++it) { |
|
302 PluginPackageByNameMap::iterator packageInMap = m_preferredPlugins.find(it->first); |
|
303 if (packageInMap != m_preferredPlugins.end() && packageInMap->second == package) |
|
304 m_preferredPlugins.remove(packageInMap); |
|
305 } |
|
306 |
|
307 m_plugins.remove(package); |
|
308 m_pluginsByPath.remove(package->path()); |
|
309 } |
|
310 |
|
311 void PluginDatabase::clear() |
|
312 { |
|
313 m_plugins.clear(); |
|
314 m_pluginsByPath.clear(); |
|
315 m_pluginPathsWithTimes.clear(); |
|
316 m_registeredMIMETypes.clear(); |
|
317 m_preferredPlugins.clear(); |
|
318 } |
|
319 |
|
320 #if (!OS(WINCE)) && (!OS(SYMBIAN)) && (!OS(WINDOWS) || !ENABLE(NETSCAPE_PLUGIN_API)) |
|
321 // For Safari/Win the following three methods are implemented |
|
322 // in PluginDatabaseWin.cpp, but if we can use WebCore constructs |
|
323 // for the logic we should perhaps move it here under XP_WIN? |
|
324 |
|
325 Vector<String> PluginDatabase::defaultPluginDirectories() |
|
326 { |
|
327 Vector<String> paths; |
|
328 |
|
329 // Add paths specific to each platform |
|
330 #if defined(XP_UNIX) |
|
331 String userPluginPath = homeDirectoryPath(); |
|
332 userPluginPath.append(String("/.mozilla/plugins")); |
|
333 paths.append(userPluginPath); |
|
334 |
|
335 userPluginPath = homeDirectoryPath(); |
|
336 userPluginPath.append(String("/.netscape/plugins")); |
|
337 paths.append(userPluginPath); |
|
338 |
|
339 paths.append("/usr/lib/browser/plugins"); |
|
340 paths.append("/usr/local/lib/mozilla/plugins"); |
|
341 paths.append("/usr/lib/firefox/plugins"); |
|
342 paths.append("/usr/lib64/browser-plugins"); |
|
343 paths.append("/usr/lib/browser-plugins"); |
|
344 paths.append("/usr/lib/mozilla/plugins"); |
|
345 paths.append("/usr/local/netscape/plugins"); |
|
346 paths.append("/opt/mozilla/plugins"); |
|
347 paths.append("/opt/mozilla/lib/plugins"); |
|
348 paths.append("/opt/netscape/plugins"); |
|
349 paths.append("/opt/netscape/communicator/plugins"); |
|
350 paths.append("/usr/lib/netscape/plugins"); |
|
351 paths.append("/usr/lib/netscape/plugins-libc5"); |
|
352 paths.append("/usr/lib/netscape/plugins-libc6"); |
|
353 paths.append("/usr/lib64/netscape/plugins"); |
|
354 paths.append("/usr/lib64/mozilla/plugins"); |
|
355 paths.append("/usr/lib/nsbrowser/plugins"); |
|
356 paths.append("/usr/lib64/nsbrowser/plugins"); |
|
357 |
|
358 String mozHome(getenv("MOZILLA_HOME")); |
|
359 mozHome.append("/plugins"); |
|
360 paths.append(mozHome); |
|
361 |
|
362 Vector<String> mozPaths; |
|
363 String mozPath(getenv("MOZ_PLUGIN_PATH")); |
|
364 mozPath.split(UChar(':'), /* allowEmptyEntries */ false, mozPaths); |
|
365 paths.append(mozPaths); |
|
366 #elif defined(XP_MACOSX) |
|
367 String userPluginPath = homeDirectoryPath(); |
|
368 userPluginPath.append(String("/Library/Internet Plug-Ins")); |
|
369 paths.append(userPluginPath); |
|
370 paths.append("/Library/Internet Plug-Ins"); |
|
371 #elif defined(XP_WIN) |
|
372 String userPluginPath = homeDirectoryPath(); |
|
373 userPluginPath.append(String("\\Application Data\\Mozilla\\plugins")); |
|
374 paths.append(userPluginPath); |
|
375 #endif |
|
376 |
|
377 // Add paths specific to each port |
|
378 #if PLATFORM(QT) |
|
379 Vector<String> qtPaths; |
|
380 String qtPath(qgetenv("QTWEBKIT_PLUGIN_PATH").constData()); |
|
381 qtPath.split(UChar(':'), /* allowEmptyEntries */ false, qtPaths); |
|
382 paths.append(qtPaths); |
|
383 #endif |
|
384 |
|
385 return paths; |
|
386 } |
|
387 |
|
388 bool PluginDatabase::isPreferredPluginDirectory(const String& path) |
|
389 { |
|
390 String preferredPath = homeDirectoryPath(); |
|
391 |
|
392 #if defined(XP_UNIX) |
|
393 preferredPath.append(String("/.mozilla/plugins")); |
|
394 #elif defined(XP_MACOSX) |
|
395 preferredPath.append(String("/Library/Internet Plug-Ins")); |
|
396 #elif defined(XP_WIN) |
|
397 preferredPath.append(String("\\Application Data\\Mozilla\\plugins")); |
|
398 #endif |
|
399 |
|
400 // TODO: We should normalize the path before doing a comparison. |
|
401 return path == preferredPath; |
|
402 } |
|
403 |
|
404 void PluginDatabase::getPluginPathsInDirectories(HashSet<String>& paths) const |
|
405 { |
|
406 // FIXME: This should be a case insensitive set. |
|
407 HashSet<String> uniqueFilenames; |
|
408 |
|
409 #if defined(XP_UNIX) |
|
410 String fileNameFilter("*.so"); |
|
411 #else |
|
412 String fileNameFilter(""); |
|
413 #endif |
|
414 |
|
415 Vector<String>::const_iterator dirsEnd = m_pluginDirectories.end(); |
|
416 for (Vector<String>::const_iterator dIt = m_pluginDirectories.begin(); dIt != dirsEnd; ++dIt) { |
|
417 Vector<String> pluginPaths = listDirectory(*dIt, fileNameFilter); |
|
418 Vector<String>::const_iterator pluginsEnd = pluginPaths.end(); |
|
419 for (Vector<String>::const_iterator pIt = pluginPaths.begin(); pIt != pluginsEnd; ++pIt) { |
|
420 if (!fileExists(*pIt)) |
|
421 continue; |
|
422 |
|
423 paths.add(*pIt); |
|
424 } |
|
425 } |
|
426 } |
|
427 |
|
428 #endif // !OS(SYMBIAN) && !OS(WINDOWS) |
|
429 |
|
430 } |