Move URL from WebCore to WTF
[WebKit-https.git] / Source / WebKitLegacy / win / Plugins / PluginDatabase.cpp
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 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 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 "PluginDatabase.h"
28
29 #include "PluginPackage.h"
30 #include <WebCore/Frame.h>
31 #include <stdlib.h>
32 #include <wtf/URL.h>
33 #include <wtf/text/CString.h>
34
35 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
36 #include "FileSystem.h"
37 #endif
38
39 namespace WebCore {
40
41 typedef HashMap<String, RefPtr<PluginPackage> > PluginPackageByNameMap;
42
43 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
44
45 static const size_t maximumPersistentPluginMetadataCacheSize = 32768;
46
47 static bool gPersistentPluginMetadataCacheIsEnabled;
48
49 String& persistentPluginMetadataCachePath()
50 {
51     static NeverDestroyed<String> cachePath;
52     return cachePath;
53 }
54
55 #endif
56
57 PluginDatabase::PluginDatabase()
58 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
59     : m_persistentMetadataCacheIsLoaded(false)
60 #endif
61 {
62 }
63
64 PluginDatabase* PluginDatabase::installedPlugins(bool populate)
65 {
66     static PluginDatabase* plugins = 0;
67
68     if (!plugins) {
69         plugins = new PluginDatabase;
70
71         if (populate) {
72             plugins->setPluginDirectories(PluginDatabase::defaultPluginDirectories());
73             plugins->refresh();
74         }
75     }
76
77     return plugins;
78 }
79
80 bool PluginDatabase::isMIMETypeRegistered(const String& mimeType)
81 {
82     if (mimeType.isNull())
83         return false;
84     if (m_registeredMIMETypes.contains(mimeType))
85         return true;
86     // No plugin was found, try refreshing the database and searching again
87     return (refresh() && m_registeredMIMETypes.contains(mimeType));
88 }
89
90 void PluginDatabase::addExtraPluginDirectory(const String& directory)
91 {
92     m_pluginDirectories.append(directory);
93     refresh();
94 }
95
96 bool PluginDatabase::refresh()
97 {
98 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
99     if (!m_persistentMetadataCacheIsLoaded)
100         loadPersistentMetadataCache();
101 #endif
102     bool pluginSetChanged = false;
103
104     if (!m_plugins.isEmpty()) {
105         PluginSet pluginsToUnload;
106         getDeletedPlugins(pluginsToUnload);
107
108         // Unload plugins
109         auto end = pluginsToUnload.end();
110         for (auto it = pluginsToUnload.begin(); it != end; ++it)
111             remove(it->get());
112
113         pluginSetChanged = !pluginsToUnload.isEmpty();
114     }
115
116     HashSet<String> paths;
117     getPluginPathsInDirectories(paths);
118
119     HashMap<String, time_t> pathsWithTimes;
120
121     // We should only skip unchanged files if we didn't remove any plugins above. If we did remove
122     // any plugins, we need to look at every plugin file so that, e.g., if the user has two versions
123     // of RealPlayer installed and just removed the newer one, we'll pick up the older one.
124     bool shouldSkipUnchangedFiles = !pluginSetChanged;
125
126     auto pathsEnd = paths.end();
127     for (auto it = paths.begin(); it != pathsEnd; ++it) {
128         time_t lastModified;
129         if (!FileSystem::getFileModificationTime(*it, lastModified))
130             continue;
131
132         pathsWithTimes.add(*it, lastModified);
133
134         // If the path's timestamp hasn't changed since the last time we ran refresh(), we don't have to do anything.
135         if (shouldSkipUnchangedFiles && m_pluginPathsWithTimes.get(*it) == lastModified)
136             continue;
137
138         if (RefPtr<PluginPackage> oldPackage = m_pluginsByPath.get(*it)) {
139             ASSERT(!shouldSkipUnchangedFiles || oldPackage->lastModified() != lastModified);
140             remove(oldPackage.get());
141         }
142
143         RefPtr<PluginPackage> package = PluginPackage::createPackage(*it, lastModified);
144         if (package && add(package.releaseNonNull()))
145             pluginSetChanged = true;
146     }
147
148     // Cache all the paths we found with their timestamps for next time.
149     pathsWithTimes.swap(m_pluginPathsWithTimes);
150
151     if (!pluginSetChanged)
152         return false;
153
154 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
155     updatePersistentMetadataCache();
156 #endif
157
158     m_registeredMIMETypes.clear();
159
160     // Register plug-in MIME types
161     auto end = m_plugins.end();
162     for (auto it = m_plugins.begin(); it != end; ++it) {
163         // Get MIME types
164         auto map_it = (*it)->mimeToDescriptions().begin();
165         auto map_end = (*it)->mimeToDescriptions().end();
166         for (; map_it != map_end; ++map_it)
167             m_registeredMIMETypes.add(map_it->key);
168     }
169
170     return true;
171 }
172
173 Vector<PluginPackage*> PluginDatabase::plugins() const
174 {
175     Vector<PluginPackage*> result;
176
177     auto end = m_plugins.end();
178     for (auto it = m_plugins.begin(); it != end; ++it)
179         result.append((*it).get());
180
181     return result;
182 }
183
184 int PluginDatabase::preferredPluginCompare(const void* a, const void* b)
185 {
186     PluginPackage* pluginA = *static_cast<PluginPackage* const*>(a);
187     PluginPackage* pluginB = *static_cast<PluginPackage* const*>(b);
188
189     return pluginA->compare(*pluginB);
190 }
191
192 PluginPackage* PluginDatabase::pluginForMIMEType(const String& mimeType)
193 {
194     if (mimeType.isEmpty())
195         return nullptr;
196
197     PluginPackage* preferredPlugin = m_preferredPlugins.get(mimeType);
198     if (preferredPlugin
199         && preferredPlugin->isEnabled()
200         && preferredPlugin->mimeToDescriptions().contains(mimeType)) {
201         return preferredPlugin;
202     }
203
204     Vector<PluginPackage*, 2> pluginChoices;
205
206     auto end = m_plugins.end();
207     for (auto it = m_plugins.begin(); it != end; ++it) {
208         PluginPackage* plugin = (*it).get();
209
210         if (!plugin->isEnabled())
211             continue;
212
213         if (plugin->mimeToDescriptions().contains(mimeType)) {
214 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
215             if (!plugin->ensurePluginLoaded())
216                 continue;
217 #endif
218             pluginChoices.append(plugin);
219         }
220     }
221
222     if (pluginChoices.isEmpty())
223         return 0;
224
225     qsort(pluginChoices.data(), pluginChoices.size(), sizeof(PluginPackage*), PluginDatabase::preferredPluginCompare);
226
227     return pluginChoices[0];
228 }
229
230 String PluginDatabase::MIMETypeForExtension(const String& extension) const
231 {
232     if (extension.isEmpty())
233         return String();
234
235     auto end = m_plugins.end();
236     String mimeType;
237     Vector<PluginPackage*, 2> pluginChoices;
238     HashMap<PluginPackage*, String> mimeTypeForPlugin;
239
240     for (auto it = m_plugins.begin(); it != end; ++it) {
241         if (!(*it)->isEnabled())
242             continue;
243
244         auto mime_end = (*it)->mimeToExtensions().end();
245
246         for (auto mime_it = (*it)->mimeToExtensions().begin(); mime_it != mime_end; ++mime_it) {
247             mimeType = mime_it->key;
248             PluginPackage* preferredPlugin = m_preferredPlugins.get(mimeType);
249             const Vector<String>& extensions = mime_it->value;
250             bool foundMapping = false;
251             for (unsigned i = 0; i < extensions.size(); i++) {
252                 if (equalIgnoringASCIICase(extensions[i], extension)) {
253                     PluginPackage* plugin = (*it).get();
254
255                     if (preferredPlugin && PluginPackage::equal(*plugin, *preferredPlugin))
256                         return mimeType;
257
258 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
259                     if (!plugin->ensurePluginLoaded())
260                         continue;
261 #endif
262                     pluginChoices.append(plugin);
263                     mimeTypeForPlugin.add(plugin, mimeType);
264                     foundMapping = true;
265                     break;
266                 }
267             }
268             if (foundMapping)
269                 break;
270         }
271     }
272
273     if (pluginChoices.isEmpty())
274         return String();
275
276     qsort(pluginChoices.data(), pluginChoices.size(), sizeof(PluginPackage*), PluginDatabase::preferredPluginCompare);
277
278     return mimeTypeForPlugin.get(pluginChoices[0]);
279 }
280
281 PluginPackage* PluginDatabase::findPlugin(const URL& url, String& mimeType)
282 {
283     if (!mimeType.isEmpty())
284         return pluginForMIMEType(mimeType);
285     
286     String filename = url.lastPathComponent();
287     if (filename.endsWith('/'))
288         return 0;
289     
290     int extensionPos = filename.reverseFind('.');
291     if (extensionPos == -1)
292         return 0;
293     
294     String mimeTypeForExtension = MIMETypeForExtension(filename.substring(extensionPos + 1));
295     PluginPackage* plugin = pluginForMIMEType(mimeTypeForExtension);
296     if (!plugin) {
297         // FIXME: if no plugin could be found, query Windows for the mime type
298         // corresponding to the extension.
299         return 0;
300     }
301     
302     mimeType = mimeTypeForExtension;
303     return plugin;
304 }
305
306 void PluginDatabase::setPreferredPluginForMIMEType(const String& mimeType, PluginPackage* plugin)
307 {
308     if (!plugin || plugin->mimeToExtensions().contains(mimeType))
309         m_preferredPlugins.set(mimeType, plugin);
310 }
311
312 bool PluginDatabase::fileExistsAndIsNotDisabled(const String& filePath) const
313 {
314     // Skip plugin files that are disabled by filename.
315     if (m_disabledPluginFiles.contains(FileSystem::pathGetFileName(filePath)))
316         return false;
317
318     return FileSystem::fileExists(filePath);
319 }
320
321 void PluginDatabase::getDeletedPlugins(PluginSet& plugins) const
322 {
323     auto end = m_plugins.end();
324     for (auto it = m_plugins.begin(); it != end; ++it) {
325         if (!fileExistsAndIsNotDisabled((*it)->path()))
326             plugins.add(*it);
327     }
328 }
329
330 bool PluginDatabase::add(Ref<PluginPackage>&& package)
331 {
332     if (!m_plugins.add(package.copyRef()).isNewEntry)
333         return false;
334
335     m_pluginsByPath.add(package->path(), package.copyRef());
336     return true;
337 }
338
339 void PluginDatabase::remove(PluginPackage* package)
340 {
341     auto it = package->mimeToExtensions().begin();
342     auto end = package->mimeToExtensions().end();
343     for ( ; it != end; ++it) {
344         auto packageInMap = m_preferredPlugins.find(it->key);
345         if (packageInMap != m_preferredPlugins.end() && packageInMap->value == package)
346             m_preferredPlugins.remove(packageInMap);
347     }
348
349     m_plugins.remove(package);
350     m_pluginsByPath.remove(package->path());
351 }
352
353 void PluginDatabase::clear()
354 {
355     m_plugins.clear();
356     m_pluginsByPath.clear();
357     m_pluginPathsWithTimes.clear();
358     m_registeredMIMETypes.clear();
359     m_preferredPlugins.clear();
360 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
361     m_persistentMetadataCacheIsLoaded = false;
362 #endif
363 }
364
365 bool PluginDatabase::removeDisabledPluginFile(const String& fileName)
366 {
367     return m_disabledPluginFiles.remove(fileName);
368 }
369
370 bool PluginDatabase::addDisabledPluginFile(const String& fileName)
371 {
372     return m_disabledPluginFiles.add(fileName).isNewEntry;
373 }
374
375 #if !ENABLE(NETSCAPE_PLUGIN_API)
376 // For Safari/Win the following three methods are implemented
377 // in PluginDatabaseWin.cpp, but if we can use WebCore constructs
378 // for the logic we should perhaps move it here under XP_WIN?
379
380 Vector<String> PluginDatabase::defaultPluginDirectories()
381 {
382     Vector<String> paths;
383
384     String userPluginPath = FileSystem::homeDirectoryPath();
385     userPluginPath.append(String("\\Application Data\\Mozilla\\plugins"));
386     paths.append(userPluginPath);
387
388     return paths;
389 }
390
391 bool PluginDatabase::isPreferredPluginDirectory(const String& path)
392 {
393     String preferredPath = FileSystem::homeDirectoryPath();
394
395     preferredPath.append(String("\\Application Data\\Mozilla\\plugins"));
396
397     // TODO: We should normalize the path before doing a comparison.
398     return path == preferredPath;
399 }
400
401 void PluginDatabase::getPluginPathsInDirectories(HashSet<String>& paths) const
402 {
403     // FIXME: This should be a case insensitive set.
404     HashSet<String> uniqueFilenames;
405
406     String fileNameFilter("");
407
408     auto dirsEnd = m_pluginDirectories.end();
409     for (auto dIt = m_pluginDirectories.begin(); dIt != dirsEnd; ++dIt) {
410         Vector<String> pluginPaths = FileSystem::listDirectory(*dIt, fileNameFilter);
411         auto pluginsEnd = pluginPaths.end();
412         for (auto pIt = pluginPaths.begin(); pIt != pluginsEnd; ++pIt) {
413             if (!fileExistsAndIsNotDisabled(*pIt))
414                 continue;
415
416             paths.add(*pIt);
417         }
418     }
419 }
420
421 #endif // !ENABLE(NETSCAPE_PLUGIN_API)
422
423 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
424
425 static void fillBufferWithContentsOfFile(FileSystem::PlatformFileHandle file, Vector<char>& buffer)
426 {
427     size_t bufferSize = 0;
428     size_t bufferCapacity = 1024;
429     buffer.resize(bufferCapacity);
430
431     do {
432         bufferSize += FileSystem::readFromFile(file, buffer.data() + bufferSize, bufferCapacity - bufferSize);
433         if (bufferSize == bufferCapacity) {
434             if (bufferCapacity < maximumPersistentPluginMetadataCacheSize) {
435                 bufferCapacity *= 2;
436                 buffer.resize(bufferCapacity);
437             } else {
438                 buffer.clear();
439                 return;
440             }
441         } else
442             break;
443     } while (true);
444
445     buffer.shrink(bufferSize);
446 }
447
448 static bool readUTF8String(String& resultString, char*& start, const char* end)
449 {
450     if (start >= end)
451         return false;
452
453     int len = strlen(start);
454     resultString = String::fromUTF8(start, len);
455     start += len + 1;
456
457     return true;
458 }
459
460 static bool readTime(time_t& resultTime, char*& start, const char* end)
461 {
462     if (start + sizeof(time_t) >= end)
463         return false;
464
465     // The stream is not necessary aligned.
466     memcpy(&resultTime, start, sizeof(time_t));
467     start += sizeof(time_t);
468
469     return true;
470 }
471
472 static const char schemaVersion = '1';
473 static const char persistentPluginMetadataCacheFilename[] = "PluginMetadataCache.bin";
474
475 void PluginDatabase::loadPersistentMetadataCache()
476 {
477     if (!isPersistentMetadataCacheEnabled() || persistentMetadataCachePath().isEmpty())
478         return;
479
480     FileSystem::PlatformFileHandle file;
481     String absoluteCachePath = FileSystem::pathByAppendingComponent(persistentMetadataCachePath(), persistentPluginMetadataCacheFilename);
482     file = FileSystem::openFile(absoluteCachePath, FileSystem::FileOpenMode::Read);
483
484     if (!FileSystem::isHandleValid(file))
485         return;
486
487     // Mark cache as loaded regardless of success or failure. If
488     // there's error in the cache, we won't try to load it anymore.
489     m_persistentMetadataCacheIsLoaded = true;
490
491     Vector<char> fileContents;
492     fillBufferWithContentsOfFile(file, fileContents);
493     FileSystem::closeFile(file);
494
495     if (fileContents.size() < 2 || fileContents.first() != schemaVersion || fileContents.last() != '\0') {
496         LOG_ERROR("Unable to read plugin metadata cache: corrupt schema");
497         FileSystem::deleteFile(absoluteCachePath);
498         return;
499     }
500
501     char* bufferPos = fileContents.data() + 1;
502     char* end = fileContents.data() + fileContents.size();
503
504     PluginSet cachedPlugins;
505     HashMap<String, time_t> cachedPluginPathsWithTimes;
506     HashMap<String, RefPtr<PluginPackage> > cachedPluginsByPath;
507
508     while (bufferPos < end) {
509         String path;
510         time_t lastModified;
511         String name;
512         String desc;
513         String mimeDesc;
514         if (!(readUTF8String(path, bufferPos, end)
515               && readTime(lastModified, bufferPos, end)
516               && readUTF8String(name, bufferPos, end)
517               && readUTF8String(desc, bufferPos, end)
518               && readUTF8String(mimeDesc, bufferPos, end))) {
519             LOG_ERROR("Unable to read plugin metadata cache: corrupt data");
520             FileSystem::deleteFile(absoluteCachePath);
521             return;
522         }
523
524         // Skip metadata that points to plugins from directories that
525         // are not part of plugin directory list anymore.
526         String pluginDirectoryName = FileSystem::directoryName(path);
527         if (m_pluginDirectories.find(pluginDirectoryName) == WTF::notFound)
528             continue;
529
530         auto package = PluginPackage::createPackageFromCache(path, lastModified, name, desc, mimeDesc);
531
532         if (cachedPlugins.add(package.copyRef()).isNewEntry) {
533             cachedPluginPathsWithTimes.add(package->path(), package->lastModified());
534             cachedPluginsByPath.add(package->path(), package);
535         }
536     }
537
538     m_plugins.swap(cachedPlugins);
539     m_pluginsByPath.swap(cachedPluginsByPath);
540     m_pluginPathsWithTimes.swap(cachedPluginPathsWithTimes);
541 }
542
543 static bool writeUTF8String(FileSystem::PlatformFileHandle file, const String& string)
544 {
545     CString utf8String = string.utf8();
546     int length = utf8String.length() + 1;
547     return FileSystem::writeToFile(file, utf8String.data(), length) == length;
548 }
549
550 static bool writeTime(FileSystem::PlatformFileHandle file, const time_t& time)
551 {
552     return FileSystem::writeToFile(file, reinterpret_cast<const char*>(&time), sizeof(time_t)) == sizeof(time_t);
553 }
554
555 void PluginDatabase::updatePersistentMetadataCache()
556 {
557     if (!isPersistentMetadataCacheEnabled() || persistentMetadataCachePath().isEmpty())
558         return;
559
560     FileSystem::makeAllDirectories(persistentMetadataCachePath());
561     String absoluteCachePath = FileSystem::pathByAppendingComponent(persistentMetadataCachePath(), persistentPluginMetadataCacheFilename);
562     FileSystem::deleteFile(absoluteCachePath);
563
564     if (m_plugins.isEmpty())
565         return;
566
567     FileSystem::PlatformFileHandle file;
568     file = FileSystem::openFile(absoluteCachePath, FileSystem::FileOpenMode::Write);
569
570     if (!FileSystem::isHandleValid(file)) {
571         LOG_ERROR("Unable to open plugin metadata cache for saving");
572         return;
573     }
574
575     char localSchemaVersion = schemaVersion;
576     if (FileSystem::writeToFile(file, &localSchemaVersion, 1) != 1) {
577         LOG_ERROR("Unable to write plugin metadata cache schema");
578         FileSystem::closeFile(file);
579         FileSystem::deleteFile(absoluteCachePath);
580         return;
581     }
582
583     auto end = m_plugins.end();
584     for (auto it = m_plugins.begin(); it != end; ++it) {
585         if (!(writeUTF8String(file, (*it)->path())
586               && writeTime(file, (*it)->lastModified())
587               && writeUTF8String(file, (*it)->name())
588               && writeUTF8String(file, (*it)->description())
589               && writeUTF8String(file, (*it)->fullMIMEDescription()))) {
590             LOG_ERROR("Unable to write plugin metadata to cache");
591             FileSystem::closeFile(file);
592             FileSystem::deleteFile(absoluteCachePath);
593             return;
594         }
595     }
596
597     FileSystem::closeFile(file);
598 }
599
600 bool PluginDatabase::isPersistentMetadataCacheEnabled()
601 {
602     return gPersistentPluginMetadataCacheIsEnabled;
603 }
604
605 void PluginDatabase::setPersistentMetadataCacheEnabled(bool isEnabled)
606 {
607     gPersistentPluginMetadataCacheIsEnabled = isEnabled;
608 }
609
610 String PluginDatabase::persistentMetadataCachePath()
611 {
612     return WebCore::persistentPluginMetadataCachePath();
613 }
614
615 void PluginDatabase::setPersistentMetadataCachePath(const String& persistentMetadataCachePath)
616 {
617     WebCore::persistentPluginMetadataCachePath() = persistentMetadataCachePath;
618 }
619 #endif
620 }