File indexing completed on 2023-12-03 10:58:38
0001 /* 0002 SPDX-FileCopyrightText: 2007 Aaron Seigo <aseigo@kde.org> 0003 SPDX-FileCopyrightText: 2010 Marco Martin <notmart@gmail.com> 0004 SPDX-FileCopyrightText: 2010 Kevin Ottens <ervin@kde.org> 0005 SPDX-FileCopyrightText: 2009 Rob Scheepmaker 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "package.h" 0011 0012 #include <QResource> 0013 #include <qtemporarydir.h> 0014 0015 #include "kpackage_debug.h" 0016 #include <KArchive> 0017 #include <KLocalizedString> 0018 #include <KTar> 0019 #include <kzip.h> 0020 0021 #include "config-package.h" 0022 0023 #include <QMimeDatabase> 0024 #include <QStandardPaths> 0025 0026 #include "packageloader.h" 0027 #include "packagestructure.h" 0028 #include "private/package_p.h" 0029 #include "private/packageloader_p.h" 0030 0031 namespace KPackage 0032 { 0033 Package::Package(PackageStructure *structure) 0034 : d(new PackagePrivate()) 0035 { 0036 d->structure = structure; 0037 0038 if (d->structure) { 0039 d->structure.data()->initPackage(this); 0040 addFileDefinition("metadata", QStringLiteral("metadata.json")); 0041 } 0042 } 0043 0044 Package::Package(const Package &other) 0045 : d(other.d) 0046 { 0047 } 0048 0049 Package::~Package() = default; 0050 0051 Package &Package::operator=(const Package &rhs) 0052 { 0053 if (&rhs != this) { 0054 d = rhs.d; 0055 } 0056 0057 return *this; 0058 } 0059 0060 bool Package::hasValidStructure() const 0061 { 0062 return d->structure; 0063 } 0064 0065 bool Package::isValid() const 0066 { 0067 if (!d->structure) { 0068 return false; 0069 } 0070 0071 // Minimal packages with no metadata *are* supposed to be possible 0072 // so if !metadata().isValid() go ahead 0073 if (metadata().isValid() && metadata().value(QStringLiteral("isHidden"), QStringLiteral("false")) == QLatin1String("true")) { 0074 return false; 0075 } 0076 0077 if (d->checkedValid) { 0078 return d->valid; 0079 } 0080 0081 const QString rootPath = d->tempRoot.isEmpty() ? d->path : d->tempRoot; 0082 if (rootPath.isEmpty()) { 0083 return false; 0084 } 0085 0086 d->valid = true; 0087 0088 // search for the file in all prefixes and in all possible paths for each prefix 0089 // even if it's a big nested loop, usually there is one prefix and one location 0090 // so shouldn't cause too much disk access 0091 QHashIterator<QByteArray, ContentStructure> it(d->contents); 0092 0093 while (it.hasNext()) { 0094 it.next(); 0095 if (!it.value().required) { 0096 continue; 0097 } 0098 0099 const QString foundPath = filePath(it.key(), {}); 0100 if (foundPath.isEmpty()) { 0101 qCWarning(KPACKAGE_LOG) << "Could not find required" << (it.value().directory ? "directory" : "file") << it.key() << "for package" << path() 0102 << "should be" << it.value().paths; 0103 d->valid = false; 0104 break; 0105 } 0106 } 0107 0108 return d->valid; 0109 } 0110 0111 bool Package::isRequired(const QByteArray &key) const 0112 { 0113 QHash<QByteArray, ContentStructure>::const_iterator it = d->contents.constFind(key); 0114 if (it == d->contents.constEnd()) { 0115 return false; 0116 } 0117 0118 return it.value().required; 0119 } 0120 0121 QStringList Package::mimeTypes(const QByteArray &key) const 0122 { 0123 QHash<QByteArray, ContentStructure>::const_iterator it = d->contents.constFind(key); 0124 if (it == d->contents.constEnd()) { 0125 return QStringList(); 0126 } 0127 0128 if (it.value().mimeTypes.isEmpty()) { 0129 return d->mimeTypes; 0130 } 0131 0132 return it.value().mimeTypes; 0133 } 0134 0135 QString Package::defaultPackageRoot() const 0136 { 0137 return d->defaultPackageRoot; 0138 } 0139 0140 void Package::setDefaultPackageRoot(const QString &packageRoot) 0141 { 0142 d.detach(); 0143 d->defaultPackageRoot = packageRoot; 0144 if (!d->defaultPackageRoot.isEmpty() && !d->defaultPackageRoot.endsWith(QLatin1Char('/'))) { 0145 d->defaultPackageRoot.append(QLatin1Char('/')); 0146 } 0147 } 0148 0149 void Package::setFallbackPackage(const KPackage::Package &package) 0150 { 0151 if ((d->fallbackPackage && d->fallbackPackage->path() == package.path() && d->fallbackPackage->metadata() == package.metadata()) || 0152 // can't be fallback of itself 0153 (package.path() == path() && package.metadata() == metadata()) || d->hasCycle(package)) { 0154 return; 0155 } 0156 0157 d->fallbackPackage = std::make_unique<Package>(package); 0158 } 0159 0160 KPackage::Package Package::fallbackPackage() const 0161 { 0162 if (d->fallbackPackage) { 0163 return (*d->fallbackPackage); 0164 } else { 0165 return Package(); 0166 } 0167 } 0168 0169 bool Package::allowExternalPaths() const 0170 { 0171 return d->externalPaths; 0172 } 0173 0174 void Package::setMetadata(const KPluginMetaData &data) 0175 { 0176 Q_ASSERT(data.isValid()); 0177 d->metadata = data; 0178 } 0179 0180 void Package::setAllowExternalPaths(bool allow) 0181 { 0182 d.detach(); 0183 d->externalPaths = allow; 0184 } 0185 0186 KPluginMetaData Package::metadata() const 0187 { 0188 // qCDebug(KPACKAGE_LOG) << "metadata: " << d->path << filePath("metadata"); 0189 if (!d->metadata && !d->path.isEmpty()) { 0190 const QString metadataPath = filePath("metadata"); 0191 0192 if (!metadataPath.isEmpty()) { 0193 d->createPackageMetadata(metadataPath); 0194 } else { 0195 // d->path might still be a file, if its path has a trailing /, 0196 // the fileInfo lookup will fail, so remove it. 0197 QString p = d->path; 0198 if (p.endsWith(QLatin1Char('/'))) { 0199 p.chop(1); 0200 } 0201 QFileInfo fileInfo(p); 0202 0203 if (fileInfo.isDir()) { 0204 d->createPackageMetadata(d->path); 0205 } else if (fileInfo.exists()) { 0206 d->path = fileInfo.canonicalFilePath(); 0207 d->tempRoot = d->unpack(p); 0208 } 0209 } 0210 } 0211 0212 // Set a dummy KPluginMetaData object, this way we don't try to do the expensive 0213 // search for the metadata again if none of the paths have changed 0214 if (!d->metadata) { 0215 d->metadata = KPluginMetaData(); 0216 } 0217 0218 return d->metadata.value(); 0219 } 0220 0221 QString PackagePrivate::unpack(const QString &filePath) 0222 { 0223 KArchive *archive = nullptr; 0224 QMimeDatabase db; 0225 QMimeType mimeType = db.mimeTypeForFile(filePath); 0226 0227 if (mimeType.inherits(QStringLiteral("application/zip"))) { 0228 archive = new KZip(filePath); 0229 } else if (mimeType.inherits(QStringLiteral("application/x-compressed-tar")) || // 0230 mimeType.inherits(QStringLiteral("application/x-gzip")) || // 0231 mimeType.inherits(QStringLiteral("application/x-tar")) || // 0232 mimeType.inherits(QStringLiteral("application/x-bzip-compressed-tar")) || // 0233 mimeType.inherits(QStringLiteral("application/x-xz")) || // 0234 mimeType.inherits(QStringLiteral("application/x-lzma"))) { 0235 archive = new KTar(filePath); 0236 } else { 0237 // qCWarning(KPACKAGE_LOG) << "Could not open package file, unsupported archive format:" << filePath << mimeType.name(); 0238 } 0239 QString tempRoot; 0240 if (archive && archive->open(QIODevice::ReadOnly)) { 0241 const KArchiveDirectory *source = archive->directory(); 0242 QTemporaryDir tempdir; 0243 tempdir.setAutoRemove(false); 0244 tempRoot = tempdir.path() + QLatin1Char('/'); 0245 source->copyTo(tempRoot); 0246 0247 if (!QFile::exists(tempdir.path() + QLatin1String("/metadata.json"))) { 0248 // search metadata.json, the zip file might have the package contents in a subdirectory 0249 QDir unpackedPath(tempdir.path()); 0250 const auto entries = unpackedPath.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); 0251 for (const auto &pack : entries) { 0252 if (QFile::exists(pack.filePath() + QLatin1String("/metadata.json"))) { 0253 tempRoot = pack.filePath() + QLatin1Char('/'); 0254 } 0255 } 0256 } 0257 0258 createPackageMetadata(tempRoot); 0259 } else { 0260 // qCWarning(KPACKAGE_LOG) << "Could not open package file:" << path; 0261 } 0262 0263 delete archive; 0264 return tempRoot; 0265 } 0266 0267 bool PackagePrivate::isInsidePackageDir(const QString &canonicalPath) const 0268 { 0269 // make sure that the target file is actually inside the package dir to prevent 0270 // path traversal using symlinks or "../" path segments 0271 0272 // make sure we got passed a valid path 0273 Q_ASSERT(QFileInfo::exists(canonicalPath)); 0274 Q_ASSERT(QFileInfo(canonicalPath).canonicalFilePath() == canonicalPath); 0275 // make sure that the base path is also canonical 0276 // this was not the case until 5.8, making this check fail e.g. if /home is a symlink 0277 // which in turn would make plasmashell not find the .qml files 0278 // installed package 0279 if (tempRoot.isEmpty()) { 0280 Q_ASSERT(QDir(path).exists()); 0281 Q_ASSERT(path == QStringLiteral("/") || QDir(path).canonicalPath() + QLatin1Char('/') == path); 0282 0283 if (canonicalPath.startsWith(path)) { 0284 return true; 0285 } 0286 // temporary compressed package 0287 } else { 0288 Q_ASSERT(QDir(tempRoot).exists()); 0289 Q_ASSERT(tempRoot == QStringLiteral("/") || QDir(tempRoot).canonicalPath() + QLatin1Char('/') == tempRoot); 0290 0291 if (canonicalPath.startsWith(tempRoot)) { 0292 return true; 0293 } 0294 } 0295 qCWarning(KPACKAGE_LOG) << "Path traversal attempt detected:" << canonicalPath << "is not inside" << path; 0296 return false; 0297 } 0298 0299 QString Package::filePath(const QByteArray &fileType, const QString &filename) const 0300 { 0301 if (!d->valid) { 0302 QString result = d->fallbackFilePath(fileType, filename); 0303 if (result.isEmpty()) { 0304 // qCDebug(KPACKAGE_LOG) << fileType << "file with name" << filename 0305 // << "was not found in package with path" << d->path; 0306 } 0307 return result; 0308 } 0309 0310 const QString discoveryKey(QString::fromUtf8(fileType) + filename); 0311 const auto path = d->discoveries.value(discoveryKey); 0312 if (!path.isEmpty()) { 0313 return path; 0314 } 0315 0316 QStringList paths; 0317 0318 if (!fileType.isEmpty()) { 0319 const auto contents = d->contents.constFind(fileType); 0320 if (contents == d->contents.constEnd()) { 0321 // qCDebug(KPACKAGE_LOG) << "package does not contain" << fileType << filename; 0322 return d->fallbackFilePath(fileType, filename); 0323 } 0324 0325 paths = contents->paths; 0326 0327 if (paths.isEmpty()) { 0328 // qCDebug(KPACKAGE_LOG) << "no matching path came of it, while looking for" << fileType << filename; 0329 d->discoveries.insert(discoveryKey, QString()); 0330 return d->fallbackFilePath(fileType, filename); 0331 } 0332 } else { 0333 // when filetype is empty paths is always empty, so try with an empty string 0334 paths << QString(); 0335 } 0336 0337 // Nested loop, but in the medium case resolves to just one iteration 0338 // qCDebug(KPACKAGE_LOG) << "prefixes:" << d->contentsPrefixPaths.count() << d->contentsPrefixPaths; 0339 for (const QString &contentsPrefix : std::as_const(d->contentsPrefixPaths)) { 0340 QString prefix; 0341 // We are an installed package 0342 if (d->tempRoot.isEmpty()) { 0343 prefix = fileType == "metadata" ? d->path : (d->path + contentsPrefix); 0344 // We are a compressed package temporarily uncompressed in /tmp 0345 } else { 0346 prefix = fileType == "metadata" ? d->tempRoot : (d->tempRoot + contentsPrefix); 0347 } 0348 0349 for (const QString &path : std::as_const(paths)) { 0350 QString file = prefix + path; 0351 0352 if (!filename.isEmpty()) { 0353 file.append(QLatin1Char('/') + filename); 0354 } 0355 0356 QFileInfo fi(file); 0357 if (fi.exists()) { 0358 if (d->externalPaths) { 0359 // qCDebug(KPACKAGE_LOG) << "found" << file; 0360 d->discoveries.insert(discoveryKey, file); 0361 return file; 0362 } 0363 0364 // ensure that we don't return files outside of our base path 0365 // due to symlink or ../ games 0366 if (d->isInsidePackageDir(fi.canonicalFilePath())) { 0367 // qCDebug(KPACKAGE_LOG) << "found" << file; 0368 d->discoveries.insert(discoveryKey, file); 0369 return file; 0370 } 0371 } 0372 } 0373 } 0374 0375 // qCDebug(KPACKAGE_LOG) << fileType << filename << "does not exist in" << prefixes << "at root" << d->path; 0376 return d->fallbackFilePath(fileType, filename); 0377 } 0378 0379 QUrl Package::fileUrl(const QByteArray &fileType, const QString &filename) const 0380 { 0381 QString path = filePath(fileType, filename); 0382 // construct a qrc:/ url or a file:/ url, the only two protocols supported 0383 if (path.startsWith(QStringLiteral(":"))) { 0384 return QUrl(QStringLiteral("qrc") + path); 0385 } else { 0386 return QUrl::fromLocalFile(path); 0387 } 0388 } 0389 0390 QStringList Package::entryList(const QByteArray &key) const 0391 { 0392 if (!d->valid) { 0393 return QStringList(); 0394 } 0395 0396 QHash<QByteArray, ContentStructure>::const_iterator it = d->contents.constFind(key); 0397 if (it == d->contents.constEnd()) { 0398 // qCDebug(KPACKAGE_LOG) << "couldn't find" << key; 0399 return QStringList(); 0400 } 0401 0402 // qCDebug(KPACKAGE_LOG) << "going to list" << key; 0403 QStringList list; 0404 for (const QString &prefix : std::as_const(d->contentsPrefixPaths)) { 0405 // qCDebug(KPACKAGE_LOG) << " looking in" << prefix; 0406 const QStringList paths = it.value().paths; 0407 for (const QString &path : paths) { 0408 // qCDebug(KPACKAGE_LOG) << " looking in" << path; 0409 if (it.value().directory) { 0410 // qCDebug(KPACKAGE_LOG) << "it's a directory, so trying out" << d->path + prefix + path; 0411 QDir dir(d->path + prefix + path); 0412 0413 if (d->externalPaths) { 0414 list += dir.entryList(QDir::Files | QDir::Readable); 0415 } else { 0416 // ensure that we don't return files outside of our base path 0417 // due to symlink or ../ games 0418 QString canonicalized = dir.canonicalPath(); 0419 if (canonicalized.startsWith(d->path)) { 0420 list += dir.entryList(QDir::Files | QDir::Readable); 0421 } 0422 } 0423 } else { 0424 const QString fullPath = d->path + prefix + path; 0425 // qCDebug(KPACKAGE_LOG) << "it's a file at" << fullPath << QFile::exists(fullPath); 0426 if (!QFile::exists(fullPath)) { 0427 continue; 0428 } 0429 0430 if (d->externalPaths) { 0431 list += fullPath; 0432 } else { 0433 QDir dir(fullPath); 0434 QString canonicalized = dir.canonicalPath() + QDir::separator(); 0435 0436 // qCDebug(KPACKAGE_LOG) << "testing that" << canonicalized << "is in" << d->path; 0437 if (canonicalized.startsWith(d->path)) { 0438 list += fullPath; 0439 } 0440 } 0441 } 0442 } 0443 } 0444 0445 return list; 0446 } 0447 0448 void Package::setPath(const QString &path) 0449 { 0450 // if the path is already what we have, don't bother 0451 if (path == d->path) { 0452 return; 0453 } 0454 0455 // our dptr is shared, and it is almost certainly going to change. 0456 // hold onto the old pointer just in case it does not, however! 0457 QExplicitlySharedDataPointer<PackagePrivate> oldD(d); 0458 d.detach(); 0459 d->metadata = std::nullopt; 0460 0461 // without structure we're doomed 0462 if (!d->structure) { 0463 d->path.clear(); 0464 d->discoveries.clear(); 0465 d->valid = false; 0466 d->checkedValid = true; 0467 qCWarning(KPACKAGE_LOG) << "Cannot set a path in a package without structure" << path; 0468 return; 0469 } 0470 0471 // empty path => nothing to do 0472 if (path.isEmpty()) { 0473 d->path.clear(); 0474 d->discoveries.clear(); 0475 d->valid = false; 0476 d->structure.data()->pathChanged(this); 0477 return; 0478 } 0479 0480 // now we look for all possible paths, including resolving 0481 // relative paths against the system search paths 0482 QStringList paths; 0483 if (QDir::isRelativePath(path)) { 0484 QString p; 0485 0486 if (d->defaultPackageRoot.isEmpty()) { 0487 p = path % QLatin1Char('/'); 0488 } else { 0489 p = d->defaultPackageRoot % path % QLatin1Char('/'); 0490 } 0491 0492 if (QDir::isRelativePath(p)) { 0493 // FIXME: can searching of the qrc be better? 0494 paths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, p, QStandardPaths::LocateDirectory); 0495 } else { 0496 const QDir dir(p); 0497 if (QFile::exists(dir.canonicalPath())) { 0498 paths << p; 0499 } 0500 } 0501 0502 // qCDebug(KPACKAGE_LOG) << "paths:" << p << paths << d->defaultPackageRoot; 0503 } else { 0504 const QDir dir(path); 0505 if (QFile::exists(dir.canonicalPath())) { 0506 paths << path; 0507 } 0508 } 0509 0510 QFileInfo fileInfo(path); 0511 if (fileInfo.isFile() && d->tempRoot.isEmpty()) { 0512 d->path = fileInfo.canonicalFilePath(); 0513 d->tempRoot = d->unpack(path); 0514 } 0515 0516 // now we search each path found, caching our previous path to know if 0517 // anything actually really changed 0518 const QString previousPath = d->path; 0519 for (const QString &p : std::as_const(paths)) { 0520 d->checkedValid = false; 0521 QDir dir(p); 0522 0523 Q_ASSERT(QFile::exists(dir.canonicalPath())); 0524 0525 d->path = dir.canonicalPath(); 0526 // canonicalPath() does not include a trailing slash (unless it is the root dir) 0527 if (!d->path.endsWith(QLatin1Char('/'))) { 0528 d->path.append(QLatin1Char('/')); 0529 } 0530 0531 const QString fallbackPath = metadata().value(QStringLiteral("X-Plasma-RootPath")); 0532 if (!fallbackPath.isEmpty()) { 0533 const KPackage::Package fp = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"), fallbackPath); 0534 setFallbackPackage(fp); 0535 } 0536 0537 // we need to tell the structure we're changing paths ... 0538 d->structure.data()->pathChanged(this); 0539 // ... and then testing the results for validity 0540 if (isValid()) { 0541 break; 0542 } 0543 } 0544 0545 // if nothing did change, then we go back to the old dptr 0546 if (d->path == previousPath) { 0547 d = oldD; 0548 return; 0549 } 0550 0551 // .. but something did change, so we get rid of our discovery cache 0552 d->discoveries.clear(); 0553 d->metadata = std::nullopt; 0554 0555 // uh-oh, but we didn't end up with anything valid, so we sadly reset ourselves 0556 // to futility. 0557 if (!d->valid) { 0558 d->path.clear(); 0559 d->structure.data()->pathChanged(this); 0560 } 0561 } 0562 0563 const QString Package::path() const 0564 { 0565 return d->path; 0566 } 0567 0568 QStringList Package::contentsPrefixPaths() const 0569 { 0570 return d->contentsPrefixPaths; 0571 } 0572 0573 void Package::setContentsPrefixPaths(const QStringList &prefixPaths) 0574 { 0575 d.detach(); 0576 d->contentsPrefixPaths = prefixPaths; 0577 if (d->contentsPrefixPaths.isEmpty()) { 0578 d->contentsPrefixPaths << QString(); 0579 } else { 0580 // the code assumes that the prefixes have a trailing slash 0581 // so let's make that true here 0582 QMutableStringListIterator it(d->contentsPrefixPaths); 0583 while (it.hasNext()) { 0584 it.next(); 0585 0586 if (!it.value().endsWith(QLatin1Char('/'))) { 0587 it.setValue(it.value() % QLatin1Char('/')); 0588 } 0589 } 0590 } 0591 } 0592 0593 QByteArray Package::cryptographicHash(QCryptographicHash::Algorithm algorithm) const 0594 { 0595 if (!d->valid) { 0596 qCWarning(KPACKAGE_LOG) << "can not create hash due to Package being invalid"; 0597 return QByteArray(); 0598 } 0599 0600 QCryptographicHash hash(algorithm); 0601 const QString guessedMetaDataJson = d->path + QLatin1String("metadata.json"); 0602 const QString metadataPath = QFile::exists(guessedMetaDataJson) ? guessedMetaDataJson : QString(); 0603 if (!metadataPath.isEmpty()) { 0604 QFile f(metadataPath); 0605 if (f.open(QIODevice::ReadOnly)) { 0606 while (!f.atEnd()) { 0607 hash.addData(f.read(1024)); 0608 } 0609 } else { 0610 qCWarning(KPACKAGE_LOG) << "could not add" << f.fileName() << "to the hash; file could not be opened for reading."; 0611 } 0612 } else { 0613 qCWarning(KPACKAGE_LOG) << "no metadata at" << metadataPath; 0614 } 0615 0616 for (const QString &prefix : std::as_const(d->contentsPrefixPaths)) { 0617 const QString basePath = d->path + prefix; 0618 QDir dir(basePath); 0619 0620 if (!dir.exists()) { 0621 return QByteArray(); 0622 } 0623 0624 d->updateHash(basePath, QString(), dir, hash); 0625 } 0626 0627 return hash.result().toHex(); 0628 } 0629 0630 void Package::addDirectoryDefinition(const QByteArray &key, const QString &path) 0631 { 0632 const auto contentsIt = d->contents.constFind(key); 0633 ContentStructure s; 0634 0635 if (contentsIt != d->contents.constEnd()) { 0636 if (contentsIt->paths.contains(path) && contentsIt->directory == true) { 0637 return; 0638 } 0639 s = *contentsIt; 0640 } 0641 0642 d.detach(); 0643 0644 s.paths.append(path); 0645 s.directory = true; 0646 0647 d->contents[key] = s; 0648 } 0649 0650 void Package::addFileDefinition(const QByteArray &key, const QString &path) 0651 { 0652 const auto contentsIt = d->contents.constFind(key); 0653 ContentStructure s; 0654 0655 if (contentsIt != d->contents.constEnd()) { 0656 if (contentsIt->paths.contains(path) && contentsIt->directory == true) { 0657 return; 0658 } 0659 s = *contentsIt; 0660 } 0661 0662 d.detach(); 0663 0664 s.paths.append(path); 0665 s.directory = false; 0666 0667 d->contents[key] = s; 0668 } 0669 0670 void Package::removeDefinition(const QByteArray &key) 0671 { 0672 if (d->contents.contains(key)) { 0673 d.detach(); 0674 d->contents.remove(key); 0675 } 0676 0677 if (d->discoveries.contains(QString::fromLatin1(key))) { 0678 d.detach(); 0679 d->discoveries.remove(QString::fromLatin1(key)); 0680 } 0681 } 0682 0683 void Package::setRequired(const QByteArray &key, bool required) 0684 { 0685 QHash<QByteArray, ContentStructure>::iterator it = d->contents.find(key); 0686 if (it == d->contents.end()) { 0687 return; 0688 } 0689 0690 d.detach(); 0691 // have to find the item again after detaching: d->contents is a different object now 0692 it = d->contents.find(key); 0693 it.value().required = required; 0694 } 0695 0696 void Package::setDefaultMimeTypes(const QStringList &mimeTypes) 0697 { 0698 d.detach(); 0699 d->mimeTypes = mimeTypes; 0700 } 0701 0702 void Package::setMimeTypes(const QByteArray &key, const QStringList &mimeTypes) 0703 { 0704 QHash<QByteArray, ContentStructure>::iterator it = d->contents.find(key); 0705 if (it == d->contents.end()) { 0706 return; 0707 } 0708 0709 d.detach(); 0710 // have to find the item again after detaching: d->contents is a different object now 0711 it = d->contents.find(key); 0712 it.value().mimeTypes = mimeTypes; 0713 } 0714 0715 QList<QByteArray> Package::directories() const 0716 { 0717 QList<QByteArray> dirs; 0718 QHash<QByteArray, ContentStructure>::const_iterator it = d->contents.constBegin(); 0719 while (it != d->contents.constEnd()) { 0720 if (it.value().directory) { 0721 dirs << it.key(); 0722 } 0723 ++it; 0724 } 0725 return dirs; 0726 } 0727 0728 QList<QByteArray> Package::requiredDirectories() const 0729 { 0730 QList<QByteArray> dirs; 0731 QHash<QByteArray, ContentStructure>::const_iterator it = d->contents.constBegin(); 0732 while (it != d->contents.constEnd()) { 0733 if (it.value().directory && it.value().required) { 0734 dirs << it.key(); 0735 } 0736 ++it; 0737 } 0738 return dirs; 0739 } 0740 0741 QList<QByteArray> Package::files() const 0742 { 0743 QList<QByteArray> files; 0744 QHash<QByteArray, ContentStructure>::const_iterator it = d->contents.constBegin(); 0745 while (it != d->contents.constEnd()) { 0746 if (!it.value().directory) { 0747 files << it.key(); 0748 } 0749 ++it; 0750 } 0751 return files; 0752 } 0753 0754 QList<QByteArray> Package::requiredFiles() const 0755 { 0756 QList<QByteArray> files; 0757 QHash<QByteArray, ContentStructure>::const_iterator it = d->contents.constBegin(); 0758 while (it != d->contents.constEnd()) { 0759 if (!it.value().directory && it.value().required) { 0760 files << it.key(); 0761 } 0762 ++it; 0763 } 0764 0765 return files; 0766 } 0767 0768 PackagePrivate::PackagePrivate() 0769 : QSharedData() 0770 , fallbackPackage(nullptr) 0771 , externalPaths(false) 0772 , valid(false) 0773 , checkedValid(false) 0774 { 0775 contentsPrefixPaths << QStringLiteral("contents/"); 0776 } 0777 0778 PackagePrivate::PackagePrivate(const PackagePrivate &other) 0779 : QSharedData() 0780 { 0781 *this = other; 0782 if (other.metadata && other.metadata.value().isValid()) { 0783 metadata = other.metadata; 0784 } 0785 } 0786 0787 PackagePrivate::~PackagePrivate() 0788 { 0789 if (!tempRoot.isEmpty()) { 0790 QDir dir(tempRoot); 0791 dir.removeRecursively(); 0792 } 0793 } 0794 0795 PackagePrivate &PackagePrivate::operator=(const PackagePrivate &rhs) 0796 { 0797 if (&rhs == this) { 0798 return *this; 0799 } 0800 0801 structure = rhs.structure; 0802 if (rhs.fallbackPackage) { 0803 fallbackPackage = std::make_unique<Package>(*rhs.fallbackPackage); 0804 } else { 0805 fallbackPackage = nullptr; 0806 } 0807 if (rhs.metadata && rhs.metadata.value().isValid()) { 0808 metadata = rhs.metadata; 0809 } 0810 path = rhs.path; 0811 contentsPrefixPaths = rhs.contentsPrefixPaths; 0812 contents = rhs.contents; 0813 mimeTypes = rhs.mimeTypes; 0814 defaultPackageRoot = rhs.defaultPackageRoot; 0815 externalPaths = rhs.externalPaths; 0816 valid = rhs.valid; 0817 return *this; 0818 } 0819 0820 void PackagePrivate::updateHash(const QString &basePath, const QString &subPath, const QDir &dir, QCryptographicHash &hash) 0821 { 0822 // hash is calculated as a function of: 0823 // * files ordered alphabetically by name, with each file's: 0824 // * path relative to the content root 0825 // * file data 0826 // * directories ordered alphabetically by name, with each dir's: 0827 // * path relative to the content root 0828 // * file listing (recursing) 0829 // symlinks (in both the file and dir case) are handled by adding 0830 // the name of the symlink itself and the abs path of what it points to 0831 0832 const QDir::SortFlags sorting = QDir::Name | QDir::IgnoreCase; 0833 const QDir::Filters filters = QDir::Hidden | QDir::System | QDir::NoDotAndDotDot; 0834 const auto lstEntries = dir.entryList(QDir::Files | filters, sorting); 0835 for (const QString &file : lstEntries) { 0836 if (!subPath.isEmpty()) { 0837 hash.addData(subPath.toUtf8()); 0838 } 0839 0840 hash.addData(file.toUtf8()); 0841 0842 QFileInfo info(dir.path() + QLatin1Char('/') + file); 0843 if (info.isSymLink()) { 0844 hash.addData(info.symLinkTarget().toUtf8()); 0845 } else { 0846 QFile f(info.filePath()); 0847 if (f.open(QIODevice::ReadOnly)) { 0848 while (!f.atEnd()) { 0849 hash.addData(f.read(1024)); 0850 } 0851 } else { 0852 qCWarning(KPACKAGE_LOG) << "could not add" << f.fileName() << "to the hash; file could not be opened for reading. " 0853 << "permissions fail?" << info.permissions() << info.isFile(); 0854 } 0855 } 0856 } 0857 0858 const auto lstEntries2 = dir.entryList(QDir::Dirs | filters, sorting); 0859 for (const QString &subDirPath : lstEntries2) { 0860 const QString relativePath = subPath + subDirPath + QLatin1Char('/'); 0861 hash.addData(relativePath.toUtf8()); 0862 0863 QDir subDir(dir.path()); 0864 subDir.cd(subDirPath); 0865 0866 if (subDir.path() != subDir.canonicalPath()) { 0867 hash.addData(subDir.canonicalPath().toUtf8()); 0868 } else { 0869 updateHash(basePath, relativePath, subDir, hash); 0870 } 0871 } 0872 } 0873 0874 void PackagePrivate::createPackageMetadata(const QString &path) 0875 { 0876 const bool isDir = QFileInfo(path).isDir(); 0877 0878 if (isDir && QFile::exists(path + QStringLiteral("/metadata.json"))) { 0879 metadata = KPluginMetaData::fromJsonFile(path + QStringLiteral("/metadata.json")); 0880 } else { 0881 if (isDir) { 0882 qCDebug(KPACKAGE_LOG) << "No metadata file in the package, expected it at:" << path; 0883 } else { 0884 metadata = KPluginMetaData::fromJsonFile(path); 0885 } 0886 } 0887 } 0888 0889 QString PackagePrivate::fallbackFilePath(const QByteArray &key, const QString &filename) const 0890 { 0891 // don't fallback if the package isn't valid and never fallback the metadata file 0892 if (key != "metadata" && fallbackPackage && fallbackPackage->isValid()) { 0893 return fallbackPackage->filePath(key, filename); 0894 } else { 0895 return QString(); 0896 } 0897 } 0898 0899 bool PackagePrivate::hasCycle(const KPackage::Package &package) 0900 { 0901 if (!package.d->fallbackPackage) { 0902 return false; 0903 } 0904 0905 // This is the Floyd cycle detection algorithm 0906 // http://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare 0907 const KPackage::Package *slowPackage = &package; 0908 const KPackage::Package *fastPackage = &package; 0909 0910 while (fastPackage && fastPackage->d->fallbackPackage) { 0911 // consider two packages the same if they have the same metadata 0912 if ((fastPackage->d->fallbackPackage->metadata().isValid() && fastPackage->d->fallbackPackage->metadata() == slowPackage->metadata()) 0913 || (fastPackage->d->fallbackPackage->d->fallbackPackage && fastPackage->d->fallbackPackage->d->fallbackPackage->metadata().isValid() 0914 && fastPackage->d->fallbackPackage->d->fallbackPackage->metadata() == slowPackage->metadata())) { 0915 qCWarning(KPACKAGE_LOG) << "Warning: the fallback chain of " << package.metadata().pluginId() << "contains a cyclical dependency."; 0916 return true; 0917 } 0918 fastPackage = fastPackage->d->fallbackPackage->d->fallbackPackage.get(); 0919 slowPackage = slowPackage->d->fallbackPackage.get(); 0920 } 0921 return false; 0922 } 0923 0924 } // Namespace