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