1/*
2 * Copyright (C) 2014 Igalia S.L.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "PluginInfoCache.h"
28
29#if ENABLE(NETSCAPE_PLUGIN_API)
30
31#include "NetscapePluginModule.h"
32#include <WebCore/PlatformDisplay.h>
33#include <wtf/FileSystem.h>
34#include <wtf/text/CString.h>
35
36namespace WebKit {
37
38static const unsigned gSchemaVersion = 3;
39
40PluginInfoCache& PluginInfoCache::singleton()
41{
42 static NeverDestroyed<PluginInfoCache> pluginInfoCache;
43 return pluginInfoCache;
44}
45
46static inline const char* cacheFilenameForCurrentDisplay()
47{
48#if PLATFORM(X11)
49 if (WebCore::PlatformDisplay::sharedDisplay().type() == WebCore::PlatformDisplay::Type::X11)
50 return "plugins-x11";
51#endif
52#if PLATFORM(WAYLAND)
53 if (WebCore::PlatformDisplay::sharedDisplay().type() == WebCore::PlatformDisplay::Type::Wayland)
54 return "plugins-wayland";
55#endif
56
57 ASSERT_NOT_REACHED();
58 return "plugins";
59}
60
61PluginInfoCache::PluginInfoCache()
62 : m_cacheFile(g_key_file_new())
63 , m_saveToFileIdle(RunLoop::main(), this, &PluginInfoCache::saveToFile)
64 , m_readOnlyMode(false)
65{
66 m_saveToFileIdle.setPriority(G_PRIORITY_DEFAULT_IDLE);
67
68 GUniquePtr<char> cacheDirectory(g_build_filename(g_get_user_cache_dir(), "webkitgtk", nullptr));
69 if (FileSystem::makeAllDirectories(cacheDirectory.get())) {
70 // Delete old cache file.
71 GUniquePtr<char> oldCachePath(g_build_filename(cacheDirectory.get(), "plugins", nullptr));
72 FileSystem::deleteFile(FileSystem::stringFromFileSystemRepresentation(oldCachePath.get()));
73
74 m_cachePath.reset(g_build_filename(cacheDirectory.get(), cacheFilenameForCurrentDisplay(), nullptr));
75 g_key_file_load_from_file(m_cacheFile.get(), m_cachePath.get(), G_KEY_FILE_NONE, nullptr);
76 }
77
78 if (g_key_file_has_group(m_cacheFile.get(), "schema")) {
79 unsigned schemaVersion = static_cast<unsigned>(g_key_file_get_integer(m_cacheFile.get(), "schema", "version", nullptr));
80 if (schemaVersion < gSchemaVersion) {
81 // Cache file using an old schema, create a new empty file.
82 m_cacheFile.reset(g_key_file_new());
83 } else if (schemaVersion > gSchemaVersion) {
84 // Cache file using a newer schema, use the cache in read only mode.
85 m_readOnlyMode = true;
86 } else {
87 // Same schema version, we don't need to update it.
88 return;
89 }
90 }
91
92 g_key_file_set_integer(m_cacheFile.get(), "schema", "version", static_cast<unsigned>(gSchemaVersion));
93}
94
95PluginInfoCache::~PluginInfoCache()
96{
97}
98
99void PluginInfoCache::saveToFile()
100{
101 gsize dataLength;
102 GUniquePtr<char> data(g_key_file_to_data(m_cacheFile.get(), &dataLength, nullptr));
103 if (!data)
104 return;
105
106 g_file_set_contents(m_cachePath.get(), data.get(), dataLength, nullptr);
107}
108
109bool PluginInfoCache::getPluginInfo(const String& pluginPath, PluginModuleInfo& plugin)
110{
111 CString pluginGroup = pluginPath.utf8();
112 if (!g_key_file_has_group(m_cacheFile.get(), pluginGroup.data()))
113 return false;
114
115 auto lastModifiedTime = FileSystem::getFileModificationTime(pluginPath);
116 if (!lastModifiedTime)
117 return false;
118 time_t cachedLastModified = static_cast<time_t>(g_key_file_get_uint64(m_cacheFile.get(), pluginGroup.data(), "mtime", nullptr));
119 if (lastModifiedTime->secondsSinceEpoch().secondsAs<time_t>() != cachedLastModified)
120 return false;
121
122 plugin.path = pluginPath;
123 plugin.info.file = FileSystem::pathGetFileName(pluginPath);
124
125 GUniquePtr<char> stringValue(g_key_file_get_string(m_cacheFile.get(), pluginGroup.data(), "name", nullptr));
126 plugin.info.name = String::fromUTF8(stringValue.get());
127
128 stringValue.reset(g_key_file_get_string(m_cacheFile.get(), pluginGroup.data(), "description", nullptr));
129 plugin.info.desc = String::fromUTF8(stringValue.get());
130
131#if PLUGIN_ARCHITECTURE(UNIX)
132 stringValue.reset(g_key_file_get_string(m_cacheFile.get(), pluginGroup.data(), "mime-description", nullptr));
133 NetscapePluginModule::parseMIMEDescription(String::fromUTF8(stringValue.get()), plugin.info.mimes);
134#endif
135
136 plugin.requiresGtk2 = g_key_file_get_boolean(m_cacheFile.get(), pluginGroup.data(), "requires-gtk2", nullptr);
137
138 return true;
139}
140
141void PluginInfoCache::updatePluginInfo(const String& pluginPath, const PluginModuleInfo& plugin)
142{
143 auto lastModifiedTime = FileSystem::getFileModificationTime(pluginPath);
144 if (!lastModifiedTime)
145 return;
146
147 CString pluginGroup = pluginPath.utf8();
148 g_key_file_set_uint64(m_cacheFile.get(), pluginGroup.data(), "mtime", lastModifiedTime->secondsSinceEpoch().secondsAs<guint64>());
149 g_key_file_set_string(m_cacheFile.get(), pluginGroup.data(), "name", plugin.info.name.utf8().data());
150 g_key_file_set_string(m_cacheFile.get(), pluginGroup.data(), "description", plugin.info.desc.utf8().data());
151
152#if PLUGIN_ARCHITECTURE(UNIX)
153 String mimeDescription = NetscapePluginModule::buildMIMEDescription(plugin.info.mimes);
154 g_key_file_set_string(m_cacheFile.get(), pluginGroup.data(), "mime-description", mimeDescription.utf8().data());
155#endif
156
157 g_key_file_set_boolean(m_cacheFile.get(), pluginGroup.data(), "requires-gtk2", plugin.requiresGtk2);
158
159 if (m_cachePath && !m_readOnlyMode) {
160 // Save the cache file in an idle to make sure it happens in the main thread and
161 // it's done only once when this is called multiple times in a very short time.
162 if (m_saveToFileIdle.isActive())
163 return;
164
165 m_saveToFileIdle.startOneShot(0_s);
166 }
167}
168
169} // namespace WebKit
170
171#endif // ENABLE(NETSCAPE_PLUGIN_API)
172