REGRESSION (r141051): Broke plugin support on non-Mac WebKit2 Ports
[WebKit-https.git] / Source / WebKit2 / UIProcess / Plugins / qt / PluginProcessProxyQt.cpp
1 /*
2  * Copyright (C) 2011 Nokia Inc. All rights reserved.
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 "PluginProcessProxy.h"
28
29 #if ENABLE(PLUGIN_PROCESS)
30
31 #include "ProcessExecutablePath.h"
32 #include "QtDefaultDataLocation.h"
33 #include <QByteArray>
34 #include <QCoreApplication>
35 #include <QDateTime>
36 #include <QDir>
37 #include <QEventLoop>
38 #include <QFile>
39 #include <QJsonArray>
40 #include <QJsonDocument>
41 #include <QJsonObject>
42 #include <QMap>
43 #include <QProcess>
44 #include <QStringBuilder>
45 #include <QVariant>
46 #include <WebCore/FileSystem.h>
47 #include <wtf/OwnPtr.h>
48 #include <wtf/PassOwnPtr.h>
49
50 namespace WebKit {
51
52 class PluginProcessCreationParameters;
53
54 void PluginProcessProxy::platformInitializeLaunchOptions(ProcessLauncher::LaunchOptions& launchOptions, const PluginModuleInfo& pluginInfo)
55 {
56     launchOptions.extraInitializationData.add("plugin-path", pluginInfo.path);
57 }
58
59 void PluginProcessProxy::platformInitializePluginProcess(PluginProcessCreationParameters&)
60 {
61 }
62
63 static PassOwnPtr<QFile> cacheFile()
64 {
65     static QString cacheFilePath = defaultDataLocation() % QStringLiteral("plugin_meta_data.json");
66     return adoptPtr(new QFile(cacheFilePath));
67 }
68
69 struct ReadResult {
70     enum Tag {
71         Empty,
72         Error,
73         Success
74     };
75 };
76
77 static ReadResult::Tag readMetaDataFromCacheFile(QJsonDocument& result)
78 {
79     OwnPtr<QFile> file = cacheFile();
80     if (!file->open(QFile::ReadOnly))
81         return ReadResult::Empty;
82     QByteArray data = file->readAll();
83     if (data.isEmpty())
84         return ReadResult::Empty;
85
86     QJsonParseError error;
87     result = QJsonDocument::fromJson(data, &error);
88     if (error.error != QJsonParseError::NoError || !result.isArray()) {
89         // Corrupted file.
90         file->remove();
91         return ReadResult::Error;
92     }
93
94     return ReadResult::Success;
95 }
96
97 static void writeToCacheFile(const QJsonArray& array)
98 {
99     OwnPtr<QFile> file = cacheFile();
100     if (!file->open(QFile::WriteOnly | QFile::Truncate)) {
101         // It should never but let's be pessimistic, it is the file system after all.
102         qWarning("Cannot write into plugin meta data cache file: %s\n", qPrintable(file->fileName()));
103         return;
104     }
105
106     // Don't care about write error here. We will detect it later.
107     file->write(QJsonDocument(array).toJson());
108 }
109
110 static void appendToCacheFile(const QJsonObject& object)
111 {
112     QJsonDocument jsonDocument;
113     ReadResult::Tag result = readMetaDataFromCacheFile(jsonDocument);
114     if (result == ReadResult::Error)
115         return;
116     if (result == ReadResult::Empty)
117         jsonDocument.setArray(QJsonArray());
118
119     QJsonArray array = jsonDocument.array();
120     array.append(object);
121     writeToCacheFile(array);
122 }
123
124 struct MetaDataResult {
125     enum Tag {
126         NotAvailable,
127         Unloadable,
128         Available
129     };
130 };
131
132 static MetaDataResult::Tag tryReadPluginMetaDataFromCacheFile(const QString& canonicalPluginPath, RawPluginMetaData& result)
133 {
134     QJsonDocument jsonDocument;
135     if (readMetaDataFromCacheFile(jsonDocument) != ReadResult::Success)
136         return MetaDataResult::NotAvailable;
137
138     QJsonArray array = jsonDocument.array();
139     QDateTime pluginLastModified = QFileInfo(canonicalPluginPath).lastModified();
140     for (QJsonArray::const_iterator i = array.constBegin(); i != array.constEnd(); ++i) {
141         QJsonValue item = *i;
142         if (!item.isObject()) {
143             cacheFile()->remove();
144             return MetaDataResult::NotAvailable;
145         }
146
147         QJsonObject object = item.toObject();
148         if (object.value(QStringLiteral("path")).toString() == canonicalPluginPath) {
149             QString timestampString = object.value(QStringLiteral("timestamp")).toString();
150             if (timestampString.isEmpty()) {
151                 cacheFile()->remove();
152                 return MetaDataResult::NotAvailable;
153             }
154             QDateTime timestamp = QDateTime::fromString(timestampString);
155             if (timestamp < pluginLastModified) {
156                 // Out of date data for this plugin => remove it from the file.
157                 array.removeAt(i.i);
158                 writeToCacheFile(array);
159                 return MetaDataResult::NotAvailable;
160             }
161
162             if (object.contains(QLatin1String("unloadable")))
163                 return MetaDataResult::Unloadable;
164
165             // Match.
166             result.name = object.value(QStringLiteral("name")).toString();
167             result.description = object.value(QStringLiteral("description")).toString();
168             result.mimeDescription = object.value(QStringLiteral("mimeDescription")).toString();
169             if (result.mimeDescription.isEmpty()) {
170                 // Only the mime description is mandatory.
171                 // Don't trust in the cache file if it is empty.
172                 cacheFile()->remove();
173                 return MetaDataResult::NotAvailable;
174             }
175
176             return MetaDataResult::Available;
177         }
178     }
179
180     return MetaDataResult::NotAvailable;
181 }
182
183 bool PluginProcessProxy::scanPlugin(const String& pluginPath, RawPluginMetaData& result)
184 {
185     QFileInfo pluginFileInfo(pluginPath);
186     if (!pluginFileInfo.exists())
187         return false;
188
189     MetaDataResult::Tag metaDataResult = tryReadPluginMetaDataFromCacheFile(pluginFileInfo.canonicalFilePath(), result);
190     if (metaDataResult == MetaDataResult::Available)
191         return true;
192     if (metaDataResult == MetaDataResult::Unloadable)
193         return false;
194
195     // Scan the plugin via the plugin process.
196     QString commandLine = QString(executablePathOfPluginProcess()) % QLatin1Char(' ')
197                           % QStringLiteral("-scanPlugin") % QLatin1Char(' ') % pluginFileInfo.canonicalFilePath();
198     QProcess process;
199     process.setReadChannel(QProcess::StandardOutput);
200     process.start(commandLine);
201
202     bool ranSuccessfully = process.waitForFinished()
203                            && process.exitStatus() == QProcess::NormalExit
204                            && process.exitCode() == EXIT_SUCCESS;
205     if (ranSuccessfully) {
206         QByteArray outputBytes = process.readAll();
207         ASSERT(!(outputBytes.size() % sizeof(UChar)));
208
209         String output(reinterpret_cast<const UChar*>(outputBytes.constData()), outputBytes.size() / sizeof(UChar));
210         Vector<String> lines;
211         output.split(UChar('\n'), true, lines);
212         ASSERT(lines.size() == 4 && lines.last().isEmpty());
213
214         result.name.swap(lines[0]);
215         result.description.swap(lines[1]);
216         result.mimeDescription.swap(lines[2]);
217     } else
218         process.kill();
219
220     QVariantMap map;
221     map[QStringLiteral("path")] = QString(pluginFileInfo.canonicalFilePath());
222     map[QStringLiteral("timestamp")] = QDateTime::currentDateTime().toString();
223
224     if (!ranSuccessfully || result.mimeDescription.isEmpty()) {
225         // We failed getting the meta data in some way. Cache this information, so we don't
226         // need to rescan such plugins every time. We will retry it once the plugin is updated.
227
228         map[QStringLiteral("unloadable")] = QStringLiteral("true");
229         appendToCacheFile(QJsonObject::fromVariantMap(map));
230         return false;
231     }
232
233     map[QStringLiteral("name")] = QString(result.name);
234     map[QStringLiteral("description")] = QString(result.description);
235     map[QStringLiteral("mimeDescription")] = QString(result.mimeDescription);
236     appendToCacheFile(QJsonObject::fromVariantMap(map));
237     return true;
238 }
239
240 } // namespace WebKit
241
242 #endif // ENABLE(PLUGIN_PROCESS)