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