KPackage

package.cpp
1/*
2 SPDX-FileCopyrightText: 2007 Aaron Seigo <aseigo@kde.org>
3 SPDX-FileCopyrightText: 2010 Marco Martin <notmart@gmail.com>
4 SPDX-FileCopyrightText: 2010 Kevin Ottens <ervin@kde.org>
5 SPDX-FileCopyrightText: 2009 Rob Scheepmaker
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "package.h"
11
12#include <QResource>
13#include <qtemporarydir.h>
14
15#include "kpackage_debug.h"
16#include <KArchive>
17#include <KLocalizedString>
18#include <KTar>
19#include <kzip.h>
20
21#include "config-package.h"
22
23#include <QMimeDatabase>
24#include <QStandardPaths>
25
26#include "packageloader.h"
27#include "packagestructure.h"
28#include "private/package_p.h"
29#include "private/packageloader_p.h"
30
31namespace KPackage
32{
34 : d(new PackagePrivate())
35{
36 d->structure = structure;
37
38 if (d->structure) {
39 addFileDefinition("metadata", QStringLiteral("metadata.json"));
40 d->structure.data()->initPackage(this);
41 }
42}
43
45 : d(other.d)
46{
47}
48
49Package::~Package() = default;
50
52{
53 if (&rhs != this) {
54 d = rhs.d;
55 }
56
57 return *this;
58}
59
61{
62 qWarning() << d->structure << requiredFiles();
63 return d->structure;
64}
65
66bool Package::isValid() const
67{
68 if (!d->structure) {
69 return false;
70 }
71
72 // Minimal packages with no metadata *are* supposed to be possible
73 // so if !metadata().isValid() go ahead
74 if (metadata().isValid() && metadata().value(QStringLiteral("isHidden"), QStringLiteral("false")) == QLatin1String("true")) {
75 return false;
76 }
77
78 if (d->checkedValid) {
79 return d->valid;
80 }
81
82 const QString rootPath = d->tempRoot.isEmpty() ? d->path : d->tempRoot;
83 if (rootPath.isEmpty()) {
84 return false;
85 }
86
87 d->valid = true;
88
89 // search for the file in all prefixes and in all possible paths for each prefix
90 // even if it's a big nested loop, usually there is one prefix and one location
91 // so shouldn't cause too much disk access
92 for (auto it = d->contents.cbegin(), end = d->contents.cend(); it != end; ++it) {
93 if (it.value().required && filePath(it.key()).isEmpty()) {
94 qCWarning(KPACKAGE_LOG) << "Could not find required" << (it.value().directory ? "directory" : "file") << it.key() << "for package" << path()
95 << "should be" << it.value().paths;
96 d->valid = false;
97 break;
98 }
99 }
100
101 return d->valid;
102}
103
104bool Package::isRequired(const QByteArray &key) const
105{
106 auto it = d->contents.constFind(key);
107 if (it == d->contents.constEnd()) {
108 return false;
109 }
110
111 return it.value().required;
112}
113
115{
116 auto it = d->contents.constFind(key);
117 if (it == d->contents.constEnd()) {
118 return QStringList();
119 }
120
121 if (it.value().mimeTypes.isEmpty()) {
122 return d->mimeTypes;
123 }
124
125 return it.value().mimeTypes;
126}
127
129{
130 return d->defaultPackageRoot;
131}
132
134{
135 d.detach();
136 d->defaultPackageRoot = packageRoot;
137 if (!d->defaultPackageRoot.isEmpty() && !d->defaultPackageRoot.endsWith(QLatin1Char('/'))) {
138 d->defaultPackageRoot.append(QLatin1Char('/'));
139 }
140}
141
143{
144 if ((d->fallbackPackage && d->fallbackPackage->path() == package.path() && d->fallbackPackage->metadata() == package.metadata()) ||
145 // can't be fallback of itself
146 (package.path() == path() && package.metadata() == metadata()) || d->hasCycle(package)) {
147 return;
148 }
149
150 d->fallbackPackage = std::make_unique<Package>(package);
151}
152
154{
155 if (d->fallbackPackage) {
156 return (*d->fallbackPackage);
157 } else {
158 return Package();
159 }
160}
161
163{
164 return d->externalPaths;
165}
166
168{
169 Q_ASSERT(data.isValid());
170 d->metadata = data;
171}
172
174{
175 d.detach();
176 d->externalPaths = allow;
177}
178
180{
181 // qCDebug(KPACKAGE_LOG) << "metadata: " << d->path << filePath("metadata");
182 if (!d->metadata && !d->path.isEmpty()) {
183 const QString metadataPath = filePath("metadata", QStringLiteral("metadata.json"));
184
185 if (!metadataPath.isEmpty()) {
186 d->createPackageMetadata(metadataPath);
187 } else {
188 // d->path might still be a file, if its path has a trailing /,
189 // the fileInfo lookup will fail, so remove it.
190 QString p = d->path;
191 if (p.endsWith(QLatin1Char('/'))) {
192 p.chop(1);
193 }
194 QFileInfo fileInfo(p);
195
196 if (fileInfo.isDir()) {
197 d->createPackageMetadata(d->path);
198 } else if (fileInfo.exists()) {
199 d->path = fileInfo.canonicalFilePath();
200 d->tempRoot = d->unpack(p);
201 }
202 }
203 }
204
205 // Set a dummy KPluginMetaData object, this way we don't try to do the expensive
206 // search for the metadata again if none of the paths have changed
207 if (!d->metadata) {
208 d->metadata = KPluginMetaData();
209 }
210
211 return d->metadata.value();
212}
213
214QString PackagePrivate::unpack(const QString &filePath)
215{
216 KArchive *archive = nullptr;
217 QMimeDatabase db;
218 QMimeType mimeType = db.mimeTypeForFile(filePath);
219
220 if (mimeType.inherits(QStringLiteral("application/zip"))) {
221 archive = new KZip(filePath);
222 } else if (mimeType.inherits(QStringLiteral("application/x-compressed-tar")) || //
223 mimeType.inherits(QStringLiteral("application/x-gzip")) || //
224 mimeType.inherits(QStringLiteral("application/x-tar")) || //
225 mimeType.inherits(QStringLiteral("application/x-bzip-compressed-tar")) || //
226 mimeType.inherits(QStringLiteral("application/x-xz")) || //
227 mimeType.inherits(QStringLiteral("application/x-lzma"))) {
228 archive = new KTar(filePath);
229 } else {
230 // qCWarning(KPACKAGE_LOG) << "Could not open package file, unsupported archive format:" << filePath << mimeType.name();
231 }
232 QString tempRoot;
233 if (archive && archive->open(QIODevice::ReadOnly)) {
234 const KArchiveDirectory *source = archive->directory();
235 QTemporaryDir tempdir;
236 tempdir.setAutoRemove(false);
237 tempRoot = tempdir.path() + QLatin1Char('/');
238 source->copyTo(tempRoot);
239
240 if (!QFile::exists(tempdir.path() + QLatin1String("/metadata.json"))) {
241 // search metadata.json, the zip file might have the package contents in a subdirectory
242 QDir unpackedPath(tempdir.path());
243 const auto entries = unpackedPath.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
244 for (const auto &pack : entries) {
245 if (QFile::exists(pack.filePath() + QLatin1String("/metadata.json"))) {
246 tempRoot = pack.filePath() + QLatin1Char('/');
247 }
248 }
249 }
250
251 createPackageMetadata(tempRoot);
252 } else {
253 // qCWarning(KPACKAGE_LOG) << "Could not open package file:" << path;
254 }
255
256 delete archive;
257 return tempRoot;
258}
259
260bool PackagePrivate::isInsidePackageDir(const QString &canonicalPath) const
261{
262 // make sure that the target file is actually inside the package dir to prevent
263 // path traversal using symlinks or "../" path segments
264
265 // make sure we got passed a valid path
266 Q_ASSERT(QFileInfo::exists(canonicalPath));
267 Q_ASSERT(QFileInfo(canonicalPath).canonicalFilePath() == canonicalPath);
268 // make sure that the base path is also canonical
269 // this was not the case until 5.8, making this check fail e.g. if /home is a symlink
270 // which in turn would make plasmashell not find the .qml files
271 // installed package
272 if (tempRoot.isEmpty()) {
273 Q_ASSERT(QDir(path).exists());
274 Q_ASSERT(path == QStringLiteral("/") || QDir(path).canonicalPath() + QLatin1Char('/') == path);
275
276 if (canonicalPath.startsWith(path)) {
277 return true;
278 }
279 // temporary compressed package
280 } else {
281 Q_ASSERT(QDir(tempRoot).exists());
282 Q_ASSERT(tempRoot == QStringLiteral("/") || QDir(tempRoot).canonicalPath() + QLatin1Char('/') == tempRoot);
283
284 if (canonicalPath.startsWith(tempRoot)) {
285 return true;
286 }
287 }
288 qCWarning(KPACKAGE_LOG) << "Path traversal attempt detected:" << canonicalPath << "is not inside" << path;
289 return false;
290}
291
292QString Package::filePath(const QByteArray &fileType, const QString &filename) const
293{
294 if (!d->valid && d->checkedValid) { // Don't check the validity here, because we'd have infinite recursion
295 QString result = d->fallbackFilePath(fileType, filename);
296 if (result.isEmpty()) {
297 // qCDebug(KPACKAGE_LOG) << fileType << "file with name" << filename
298 // << "was not found in package with path" << d->path;
299 }
300 return result;
301 }
302
303 const QString discoveryKey(QString::fromUtf8(fileType) + filename);
304 const auto path = d->discoveries.value(discoveryKey);
305 if (!path.isEmpty()) {
306 return path;
307 }
308
309 QStringList paths;
310
311 if (!fileType.isEmpty()) {
312 const auto contents = d->contents.constFind(fileType);
313 if (contents == d->contents.constEnd()) {
314 // qCDebug(KPACKAGE_LOG) << "package does not contain" << fileType << filename;
315 return d->fallbackFilePath(fileType, filename);
316 }
317
318 paths = contents->paths;
319
320 if (paths.isEmpty()) {
321 // qCDebug(KPACKAGE_LOG) << "no matching path came of it, while looking for" << fileType << filename;
322 d->discoveries.insert(discoveryKey, QString());
323 return d->fallbackFilePath(fileType, filename);
324 }
325 } else {
326 // when filetype is empty paths is always empty, so try with an empty string
327 paths << QString();
328 }
329
330 // Nested loop, but in the medium case resolves to just one iteration
331 // qCDebug(KPACKAGE_LOG) << "prefixes:" << d->contentsPrefixPaths.count() << d->contentsPrefixPaths;
332 for (const QString &contentsPrefix : std::as_const(d->contentsPrefixPaths)) {
333 QString prefix;
334 // We are an installed package
335 if (d->tempRoot.isEmpty()) {
336 prefix = fileType == "metadata" ? d->path : (d->path + contentsPrefix);
337 // We are a compressed package temporarily uncompressed in /tmp
338 } else {
339 prefix = fileType == "metadata" ? d->tempRoot : (d->tempRoot + contentsPrefix);
340 }
341
342 for (const QString &path : std::as_const(paths)) {
343 QString file = prefix + path;
344
345 if (!filename.isEmpty()) {
346 file.append(QLatin1Char('/') + filename);
347 }
348
349 QFileInfo fi(file);
350 if (fi.exists()) {
351 if (d->externalPaths) {
352 // qCDebug(KPACKAGE_LOG) << "found" << file;
353 d->discoveries.insert(discoveryKey, file);
354 return file;
355 }
356
357 // ensure that we don't return files outside of our base path
358 // due to symlink or ../ games
359 if (d->isInsidePackageDir(fi.canonicalFilePath())) {
360 // qCDebug(KPACKAGE_LOG) << "found" << file;
361 d->discoveries.insert(discoveryKey, file);
362 return file;
363 }
364 }
365 }
366 }
367
368 // qCDebug(KPACKAGE_LOG) << fileType << filename << "does not exist in" << prefixes << "at root" << d->path;
369 return d->fallbackFilePath(fileType, filename);
370}
371
372QUrl Package::fileUrl(const QByteArray &fileType, const QString &filename) const
373{
374 QString path = filePath(fileType, filename);
375 // construct a qrc:/ url or a file:/ url, the only two protocols supported
376 if (path.startsWith(QStringLiteral(":"))) {
377 return QUrl(QStringLiteral("qrc") + path);
378 } else {
380 }
381}
382
384{
385 if (!d->valid) {
386 return QStringList();
387 }
388
389 const auto it = d->contents.constFind(key);
390 if (it == d->contents.constEnd()) {
391 qCWarning(KPACKAGE_LOG) << "couldn't find" << key << "when trying to list entries";
392 return QStringList();
393 }
394
395 QStringList list;
396 for (const QString &prefix : std::as_const(d->contentsPrefixPaths)) {
397 // qCDebug(KPACKAGE_LOG) << " looking in" << prefix;
398 const QStringList paths = it.value().paths;
399 for (const QString &path : paths) {
400 // qCDebug(KPACKAGE_LOG) << " looking in" << path;
401 if (it.value().directory) {
402 // qCDebug(KPACKAGE_LOG) << "it's a directory, so trying out" << d->path + prefix + path;
403 QDir dir(d->path + prefix + path);
404 if (d->externalPaths) {
405 list += dir.entryList(QDir::Files | QDir::Readable);
406 } else {
407 // ensure that we don't return files outside of our base path
408 // due to symlink or ../ games
409 QString canonicalized = dir.canonicalPath();
410 if (canonicalized.startsWith(d->path)) {
411 list += dir.entryList(QDir::Files | QDir::Readable);
412 }
413 }
414 } else {
415 const QString fullPath = d->path + prefix + path;
416 // qCDebug(KPACKAGE_LOG) << "it's a file at" << fullPath << QFile::exists(fullPath);
417 if (!QFile::exists(fullPath)) {
418 continue;
419 }
420
421 if (d->externalPaths) {
422 list += fullPath;
423 } else {
424 QDir dir(fullPath);
425 QString canonicalized = dir.canonicalPath() + QDir::separator();
426
427 // qCDebug(KPACKAGE_LOG) << "testing that" << canonicalized << "is in" << d->path;
428 if (canonicalized.startsWith(d->path)) {
429 list += fullPath;
430 }
431 }
432 }
433 }
434 }
435
436 return list;
437}
438
439void Package::setPath(const QString &path)
440{
441 // if the path is already what we have, don't bother
442 if (path == d->path) {
443 return;
444 }
445
446 // our dptr is shared, and it is almost certainly going to change.
447 // hold onto the old pointer just in case it does not, however!
449 d.detach();
450 d->metadata = std::nullopt;
451
452 // without structure we're doomed
453 if (!d->structure) {
454 d->path.clear();
455 d->discoveries.clear();
456 d->valid = false;
457 d->checkedValid = true;
458 qCWarning(KPACKAGE_LOG) << "Cannot set a path in a package without structure" << path;
459 return;
460 }
461
462 // empty path => nothing to do
463 if (path.isEmpty()) {
464 d->path.clear();
465 d->discoveries.clear();
466 d->valid = false;
467 d->structure.data()->pathChanged(this);
468 return;
469 }
470
471 // now we look for all possible paths, including resolving
472 // relative paths against the system search paths
473 QStringList paths;
475 QString p;
476
477 if (d->defaultPackageRoot.isEmpty()) {
478 p = path % QLatin1Char('/');
479 } else {
480 p = d->defaultPackageRoot % path % QLatin1Char('/');
481 }
482
483 if (QDir::isRelativePath(p)) {
484 // FIXME: can searching of the qrc be better?
486 } else {
487 const QDir dir(p);
488 if (QFile::exists(dir.canonicalPath())) {
489 paths << p;
490 }
491 }
492
493 // qCDebug(KPACKAGE_LOG) << "paths:" << p << paths << d->defaultPackageRoot;
494 } else {
495 const QDir dir(path);
496 if (QFile::exists(dir.canonicalPath())) {
497 paths << path;
498 }
499 }
500
501 QFileInfo fileInfo(path);
502 if (fileInfo.isFile() && d->tempRoot.isEmpty()) {
503 d->path = fileInfo.canonicalFilePath();
504 d->tempRoot = d->unpack(path);
505 }
506
507 // now we search each path found, caching our previous path to know if
508 // anything actually really changed
509 const QString previousPath = d->path;
510 for (const QString &p : std::as_const(paths)) {
511 d->checkedValid = false;
512 QDir dir(p);
513
514 Q_ASSERT(QFile::exists(dir.canonicalPath()));
515
516 d->path = dir.canonicalPath();
517 // canonicalPath() does not include a trailing slash (unless it is the root dir)
518 if (!d->path.endsWith(QLatin1Char('/'))) {
519 d->path.append(QLatin1Char('/'));
520 }
521
522 const QString fallbackPath = metadata().value(QStringLiteral("X-Plasma-RootPath"));
523 if (!fallbackPath.isEmpty()) {
524 const KPackage::Package fp = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"), fallbackPath);
526 }
527
528 // we need to tell the structure we're changing paths ...
529 d->structure.data()->pathChanged(this);
530 // ... and then testing the results for validity
531 if (isValid()) {
532 break;
533 }
534 }
535
536 // if nothing did change, then we go back to the old dptr
537 if (d->path == previousPath) {
538 d = oldD;
539 return;
540 }
541
542 // .. but something did change, so we get rid of our discovery cache
543 d->discoveries.clear();
544
545 // Do NOT override the metadata when the PackageStructure has set it
546 if (!previousPath.isEmpty()) {
547 d->metadata = std::nullopt;
548 }
549
550 // uh-oh, but we didn't end up with anything valid, so we sadly reset ourselves
551 // to futility.
552 if (!d->valid) {
553 d->path.clear();
554 d->structure.data()->pathChanged(this);
555 }
556}
557
559{
560 return d->path;
561}
562
564{
565 return d->contentsPrefixPaths;
566}
567
569{
570 d.detach();
571 d->contentsPrefixPaths = prefixPaths;
572 if (d->contentsPrefixPaths.isEmpty()) {
573 d->contentsPrefixPaths << QString();
574 } else {
575 // the code assumes that the prefixes have a trailing slash
576 // so let's make that true here
577 QMutableStringListIterator it(d->contentsPrefixPaths);
578 while (it.hasNext()) {
579 it.next();
580
581 if (!it.value().endsWith(QLatin1Char('/'))) {
582 it.setValue(it.value() % QLatin1Char('/'));
583 }
584 }
585 }
586}
587
589{
590 if (!d->valid) {
591 qCWarning(KPACKAGE_LOG) << "can not create hash due to Package being invalid";
592 return QByteArray();
593 }
594
595 QCryptographicHash hash(algorithm);
596 const QString guessedMetaDataJson = d->path + QLatin1String("metadata.json");
597 const QString metadataPath = QFile::exists(guessedMetaDataJson) ? guessedMetaDataJson : QString();
598 if (!metadataPath.isEmpty()) {
599 QFile f(metadataPath);
600 if (f.open(QIODevice::ReadOnly)) {
601 while (!f.atEnd()) {
602 hash.addData(f.read(1024));
603 }
604 } else {
605 qCWarning(KPACKAGE_LOG) << "could not add" << f.fileName() << "to the hash; file could not be opened for reading.";
606 }
607 } else {
608 qCWarning(KPACKAGE_LOG) << "no metadata at" << metadataPath;
609 }
610
611 for (const QString &prefix : std::as_const(d->contentsPrefixPaths)) {
612 const QString basePath = d->path + prefix;
613 QDir dir(basePath);
614
615 if (!dir.exists()) {
616 return QByteArray();
617 }
618
619 d->updateHash(basePath, QString(), dir, hash);
620 }
621
622 return hash.result().toHex();
623}
624
626{
627 const auto contentsIt = d->contents.constFind(key);
628 ContentStructure s;
629
630 if (contentsIt != d->contents.constEnd()) {
631 if (contentsIt->paths.contains(path) && contentsIt->directory == true) {
632 return;
633 }
634 s = *contentsIt;
635 }
636
637 d.detach();
638
639 s.paths.append(path);
640 s.directory = true;
641
642 d->contents[key] = s;
643}
644
645void Package::addFileDefinition(const QByteArray &key, const QString &path)
646{
647 const auto contentsIt = d->contents.constFind(key);
648 ContentStructure s;
649
650 if (contentsIt != d->contents.constEnd()) {
651 if (contentsIt->paths.contains(path) && contentsIt->directory == true) {
652 return;
653 }
654 s = *contentsIt;
655 }
656
657 d.detach();
658
659 s.paths.append(path);
660 s.directory = false;
661
662 d->contents[key] = s;
663}
664
666{
667 if (d->contents.contains(key)) {
668 d.detach();
669 d->contents.remove(key);
670 }
671
672 if (d->discoveries.contains(QString::fromLatin1(key))) {
673 d.detach();
674 d->discoveries.remove(QString::fromLatin1(key));
675 }
676}
677
678void Package::setRequired(const QByteArray &key, bool required)
679{
680 QHash<QByteArray, ContentStructure>::iterator it = d->contents.find(key);
681 if (it == d->contents.end()) {
682 qCWarning(KPACKAGE_LOG) << key << "is now a known key for the package. File is thus not set to being required";
683 return;
684 }
685
686 d.detach();
687 // have to find the item again after detaching: d->contents is a different object now
688 it = d->contents.find(key);
689 it.value().required = required;
690}
691
693{
694 d.detach();
695 d->mimeTypes = mimeTypes;
696}
697
698void Package::setMimeTypes(const QByteArray &key, const QStringList &mimeTypes)
699{
700 if (!d->contents.contains(key)) {
701 return;
702 }
703
704 d.detach();
705 d->contents[key].mimeTypes = mimeTypes;
706}
707
709{
711 for (auto it = d->contents.cbegin(); it != d->contents.cend(); ++it) {
712 if (it.value().directory) {
713 dirs << it.key();
714 }
715 }
716 return dirs;
717}
718
720{
722 for (auto it = d->contents.cbegin(); it != d->contents.cend(); ++it) {
723 if (it.value().directory && it.value().required) {
724 dirs << it.key();
725 }
726 }
727 return dirs;
728}
729
731{
733 for (auto it = d->contents.cbegin(); it != d->contents.cend(); ++it) {
734 if (!it.value().directory) {
735 files << it.key();
736 }
737 }
738 return files;
739}
740
742{
744 for (auto it = d->contents.cbegin(); it != d->contents.cend(); ++it) {
745 if (!it.value().directory && it.value().required) {
746 files << it.key();
747 }
748 }
749
750 return files;
751}
752
753PackagePrivate::PackagePrivate()
754 : QSharedData()
755{
756 contentsPrefixPaths << QStringLiteral("contents/");
757}
758
759PackagePrivate::PackagePrivate(const PackagePrivate &other)
760 : QSharedData()
761{
762 *this = other;
763 if (other.metadata && other.metadata.value().isValid()) {
764 metadata = other.metadata;
765 }
766}
767
768PackagePrivate::~PackagePrivate()
769{
770 if (!tempRoot.isEmpty()) {
771 QDir dir(tempRoot);
772 dir.removeRecursively();
773 }
774}
775
776PackagePrivate &PackagePrivate::operator=(const PackagePrivate &rhs)
777{
778 if (&rhs == this) {
779 return *this;
780 }
781
782 structure = rhs.structure;
783 if (rhs.fallbackPackage) {
784 fallbackPackage = std::make_unique<Package>(*rhs.fallbackPackage);
785 } else {
786 fallbackPackage = nullptr;
787 }
788 if (rhs.metadata && rhs.metadata.value().isValid()) {
789 metadata = rhs.metadata;
790 }
791 path = rhs.path;
792 contentsPrefixPaths = rhs.contentsPrefixPaths;
793 contents = rhs.contents;
794 mimeTypes = rhs.mimeTypes;
795 defaultPackageRoot = rhs.defaultPackageRoot;
796 externalPaths = rhs.externalPaths;
797 valid = rhs.valid;
798 return *this;
799}
800
801void PackagePrivate::updateHash(const QString &basePath, const QString &subPath, const QDir &dir, QCryptographicHash &hash)
802{
803 // hash is calculated as a function of:
804 // * files ordered alphabetically by name, with each file's:
805 // * path relative to the content root
806 // * file data
807 // * directories ordered alphabetically by name, with each dir's:
808 // * path relative to the content root
809 // * file listing (recursing)
810 // symlinks (in both the file and dir case) are handled by adding
811 // the name of the symlink itself and the abs path of what it points to
812
815 const auto lstEntries = dir.entryList(QDir::Files | filters, sorting);
816 for (const QString &file : lstEntries) {
817 if (!subPath.isEmpty()) {
818 hash.addData(subPath.toUtf8());
819 }
820
821 hash.addData(file.toUtf8());
822
823 QFileInfo info(dir.path() + QLatin1Char('/') + file);
824 if (info.isSymLink()) {
825 hash.addData(info.symLinkTarget().toUtf8());
826 } else {
827 QFile f(info.filePath());
828 if (f.open(QIODevice::ReadOnly)) {
829 while (!f.atEnd()) {
830 hash.addData(f.read(1024));
831 }
832 } else {
833 qCWarning(KPACKAGE_LOG) << "could not add" << f.fileName() << "to the hash; file could not be opened for reading. "
834 << "permissions fail?" << info.permissions() << info.isFile();
835 }
836 }
837 }
838
839 const auto lstEntries2 = dir.entryList(QDir::Dirs | filters, sorting);
840 for (const QString &subDirPath : lstEntries2) {
841 const QString relativePath = subPath + subDirPath + QLatin1Char('/');
842 hash.addData(relativePath.toUtf8());
843
844 QDir subDir(dir.path());
845 subDir.cd(subDirPath);
846
847 if (subDir.path() != subDir.canonicalPath()) {
848 hash.addData(subDir.canonicalPath().toUtf8());
849 } else {
850 updateHash(basePath, relativePath, subDir, hash);
851 }
852 }
853}
854
855void PackagePrivate::createPackageMetadata(const QString &path)
856{
857 if (QFileInfo(path).isDir()) {
858 if (const QString jsonPath = path + QLatin1String("/metadata.json"); QFileInfo::exists(jsonPath)) {
859 metadata = KPluginMetaData::fromJsonFile(jsonPath);
860 } else {
861 qCDebug(KPACKAGE_LOG) << "No metadata file in the package, expected it at:" << jsonPath;
862 }
863 } else {
864 metadata = KPluginMetaData::fromJsonFile(path);
865 }
866}
867
868QString PackagePrivate::fallbackFilePath(const QByteArray &key, const QString &filename) const
869{
870 // don't fallback if the package isn't valid and never fallback the metadata file
871 if (key != "metadata" && fallbackPackage && fallbackPackage->isValid()) {
872 return fallbackPackage->filePath(key, filename);
873 } else {
874 return QString();
875 }
876}
877
878bool PackagePrivate::hasCycle(const KPackage::Package &package)
879{
880 if (!package.d->fallbackPackage) {
881 return false;
882 }
883
884 // This is the Floyd cycle detection algorithm
885 // http://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare
886 const KPackage::Package *slowPackage = &package;
887 const KPackage::Package *fastPackage = &package;
888
889 while (fastPackage && fastPackage->d->fallbackPackage) {
890 // consider two packages the same if they have the same metadata
891 if ((fastPackage->d->fallbackPackage->metadata().isValid() && fastPackage->d->fallbackPackage->metadata() == slowPackage->metadata())
892 || (fastPackage->d->fallbackPackage->d->fallbackPackage && fastPackage->d->fallbackPackage->d->fallbackPackage->metadata().isValid()
893 && fastPackage->d->fallbackPackage->d->fallbackPackage->metadata() == slowPackage->metadata())) {
894 qCWarning(KPACKAGE_LOG) << "Warning: the fallback chain of " << package.metadata().pluginId() << "contains a cyclical dependency.";
895 return true;
896 }
897 fastPackage = fastPackage->d->fallbackPackage->d->fallbackPackage.get();
898 slowPackage = slowPackage->d->fallbackPackage.get();
899 }
900 return false;
901}
902
903} // Namespace
bool copyTo(const QString &dest, bool recursive=true) const
virtual bool open(QIODevice::OpenMode mode)
const KArchiveDirectory * directory() const
Package loadPackage(const QString &packageFormat, const QString &packagePath=QString())
Load a Package plugin.
static PackageLoader * self()
Return the active plugin loader.
This class is used to define the filesystem structure of a package type.
object representing an installed package
Definition package.h:63
void setContentsPrefixPaths(const QStringList &prefixPaths)
Sets the prefixes that all the contents in this package should appear under.
Definition package.cpp:568
QByteArray cryptographicHash(QCryptographicHash::Algorithm algorithm) const
Definition package.cpp:588
Package & operator=(const Package &rhs)
Assignment operator.
Definition package.cpp:51
void setRequired(const QByteArray &key, bool required)
Sets whether or not a given part of the structure is required or not.
Definition package.cpp:678
bool hasValidStructure() const
Definition package.cpp:60
void setMimeTypes(const QByteArray &key, const QStringList &mimeTypes)
Define mimeTypes for a given part of the structure The path must already have been added using addDir...
Definition package.cpp:698
void setPath(const QString &path)
Sets the path to the root of this package.
Definition package.cpp:439
QUrl fileUrl(const QByteArray &key, const QString &filename=QString()) const
Get the url to a given file based on the key and an optional filename, is the file:// or qrc:// forma...
Definition package.cpp:372
Package(PackageStructure *structure=nullptr)
Default constructor.
Definition package.cpp:33
QString defaultPackageRoot() const
Definition package.cpp:128
bool isValid() const
Definition package.cpp:66
void addDirectoryDefinition(const QByteArray &key, const QString &path)
Adds a directory to the structure of the package.
Definition package.cpp:625
QStringList entryList(const QByteArray &key) const
Get the list of files of a given type.
Definition package.cpp:383
QList< QByteArray > requiredDirectories() const
Definition package.cpp:719
bool isRequired(const QByteArray &key) const
Definition package.cpp:104
const QString path() const
Definition package.cpp:558
void setMetadata(const KPluginMetaData &data)
Sets the metadata for the KPackage.
Definition package.cpp:167
void addFileDefinition(const QByteArray &key, const QString &path)
Adds a file to the structure of the package.
Definition package.cpp:645
void setDefaultPackageRoot(const QString &packageRoot)
Sets preferred package root.
Definition package.cpp:133
QString filePath(const QByteArray &key, const QString &filename=QString()) const
Get the path to a given file based on the key and an optional filename.
Definition package.cpp:292
void setAllowExternalPaths(bool allow)
Sets whether or not external paths/symlinks can be followed by a package.
Definition package.cpp:173
KPluginMetaData metadata() const
Definition package.cpp:179
KPackage::Package fallbackPackage() const
Definition package.cpp:153
void removeDefinition(const QByteArray &key)
Removes a definition from the structure of the package.
Definition package.cpp:665
QStringList contentsPrefixPaths() const
Definition package.cpp:563
bool allowExternalPaths() const
Definition package.cpp:162
QList< QByteArray > files() const
Definition package.cpp:730
void setFallbackPackage(const KPackage::Package &package)
Sets the fallback package root path If a file won't be found in this package, it will search it in th...
Definition package.cpp:142
QList< QByteArray > directories() const
Definition package.cpp:708
QList< QByteArray > requiredFiles() const
Definition package.cpp:741
QStringList mimeTypes(const QByteArray &key) const
Definition package.cpp:114
void setDefaultMimeTypes(const QStringList &mimeTypes)
Defines the default mimeTypes for any definitions that do not have associated mimeTypes.
Definition package.cpp:692
QString pluginId() const
bool value(const QString &key, bool defaultValue) const
bool isValid() const
static KPluginMetaData fromJsonFile(const QString &jsonFile)
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
bool isEmpty() const const
QByteArray toHex(char separator) const const
bool addData(QIODevice *device)
QByteArray result() const const
typedef SortFlags
bool isRelativePath(const QString &path)
QChar separator()
bool exists() const const
virtual QString fileName() const const override
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
virtual bool atEnd() const const override
QString canonicalFilePath() const const
bool exists() const const
bool exists(const QString &path)
bool isDir() const const
bool isFile() const const
QByteArray read(qint64 maxSize)
bool isEmpty() const const
T value(qsizetype i) const const
QMimeType mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode) const const
QStringList locateAll(StandardLocation type, const QString &fileName, LocateOptions options)
QString & append(QChar ch)
void chop(qsizetype n)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toUtf8() const const
QString path() const const
void setAutoRemove(bool b)
QUrl fromLocalFile(const QString &localFile)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:16:48 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.