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