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