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