diff --git a/config-plasma.h.cmake b/config-plasma.h.cmake index 577a8cd5c..198885792 100644 --- a/config-plasma.h.cmake +++ b/config-plasma.h.cmake @@ -1,2 +1,3 @@ #cmakedefine ENABLE_REMOTE_WIDGETS +#cmakedefine QCA2_FOUND diff --git a/package.cpp b/package.cpp index 7b80f15d1..ba08b0732 100644 --- a/package.cpp +++ b/package.cpp @@ -19,11 +19,16 @@ *******************************************************************************/ #include "package.h" +#include "config-plasma.h" #include #include #include +#ifdef QCA2_FOUND +#include +#endif + #include #include #include @@ -165,6 +170,85 @@ const PackageStructure::Ptr Package::structure() const return d->structure; } +#ifdef QCA2_FOUND +void PackagePrivate::updateHash(const QString &basePath, const QString &subPath, const QDir &dir, QCA::Hash &hash) +{ + // hash is calculated as a function of: + // * files ordered alphabetically by name, with each file's: + // * path relative to the content root + // * file data + // * directories ordered alphabetically by name, with each dir's: + // * path relative to the content root + // * file listing (recursing) + // symlinks (in both the file and dir case) are handled by adding + // the name of the symlink itself and the abs path of what it points to + + const QDir::SortFlags sorting = QDir::Name | QDir::IgnoreCase; + const QDir::Filters filters = QDir::Hidden | QDir::System | QDir::NoDotAndDotDot; + foreach (const QString &file, dir.entryList(QDir::Files | filters, sorting)) { + if (!subPath.isEmpty()) { + hash.update(subPath.toUtf8()); + } + + hash.update(file.toUtf8()); + + QFileInfo info(dir.path() + '/' + file); + if (info.isSymLink()) { + hash.update(info.symLinkTarget().toUtf8()); + } else { + QFile f(info.filePath()); + if (f.open(QIODevice::ReadOnly)) { + while (!f.atEnd()) { + hash.update(f.read(1024)); + } + } else { + kWarning() << "permissions fail?" << info.permissions() << info.isFile(); + kWarning() << "could not add" << f.fileName() << "to the hash; file could not be opened for reading"; + } + } + } + + foreach (const QString &subDirPath, dir.entryList(QDir::Dirs | filters, sorting)) { + const QString relativePath = subPath + subDirPath + '/'; + hash.update(relativePath.toUtf8()); + + QDir subDir(dir.path()); + subDir.cd(subDirPath); + + if (subDir.path() != subDir.canonicalPath()) { + hash.update(subDir.canonicalPath().toUtf8()); + } else { + updateHash(basePath, relativePath, subDir, hash); + } + } +} +#endif + +QString Package::hash() const +{ +#ifdef QCA2_FOUND + if (!QCA::isSupported("sha1")) { + kWarning() << "can not create hash for" << path() << "due to no SHA1 support in QCA2"; + return QString(); + } + + const QString basePath = d->structure->path() + d->structure->contentsPrefix(); + QDir dir(basePath); + + if (!dir.exists()) { + return QString(); + } + + QCA::Hash hash("sha1"); + d->updateHash(basePath, QString(), dir, hash); + return QCA::arrayToHex(hash.final().toByteArray()); +#else + // no QCA2! + kWarning() << "can not create hash for" << path() << "due to no cryptographic support (QCA2)"; + return QString(); +#endif +} + //TODO: provide a version of this that allows one to ask for certain types of packages, etc? // should we be using KService here instead/as well? QStringList Package::listInstalled(const QString &packageRoot) // static diff --git a/package.h b/package.h index f85aa8531..e785c9cca 100644 --- a/package.h +++ b/package.h @@ -135,6 +135,11 @@ class PLASMA_EXPORT Package */ const PackageStructure::Ptr structure() const; + /** + * @return a SHA1 hash digest of the contents of the package in hexadecimal form + */ + QString hash() const; + /** * Returns a list of all installed packages by name * diff --git a/private/package_p.h b/private/package_p.h index b45a1bdf2..9a8ed5d28 100644 --- a/private/package_p.h +++ b/private/package_p.h @@ -35,6 +35,10 @@ public: void unpublish(); bool isPublished() const; +#ifdef QCA2_FOUND + void updateHash(const QString &basePath, const QString &subPath, const QDir &dir, QCA::Hash &hash); +#endif + PackageStructure::Ptr structure; Service *service; bool valid; diff --git a/tests/plasmoidpackagetest.cpp b/tests/plasmoidpackagetest.cpp index 3caf8ed68..3131d0d88 100644 --- a/tests/plasmoidpackagetest.cpp +++ b/tests/plasmoidpackagetest.cpp @@ -18,6 +18,7 @@ *******************************************************************************/ #include "plasmoidpackagetest.h" +#include "../config-plasma.h" #include #include @@ -26,8 +27,15 @@ #include "plasma/applet.h" #include "plasma/packagemetadata.h" +#ifdef QCA2_FOUND +#include +#endif + void PlasmoidPackageTest::init() { +#ifdef QCA2_FOUND + QCA::Initializer *cryptoInit = new QCA::Initializer; +#endif mPackage = QString("Package"); mPackageRoot = QDir::homePath() + "/.kde-unit-test/packageRoot"; ps = Plasma::Applet::packageStructure(); @@ -42,7 +50,7 @@ void PlasmoidPackageTest::cleanup() // Clean things up. QDir local = QDir::homePath() + QLatin1String("/.kde-unit-test/packageRoot"); - foreach(const QString &dir, local.entryList(QDir::Dirs)) { + foreach (const QString &dir, local.entryList(QDir::Dirs)) { removeDir(QLatin1String("packageRoot/" + dir.toLatin1() + "/contents/code")); removeDir(QLatin1String("packageRoot/" + dir.toLatin1() + "/contents/images")); removeDir(QLatin1String("packageRoot/" + dir.toLatin1() + "/contents")); @@ -70,8 +78,7 @@ void PlasmoidPackageTest::createTestPackage(const QString &packageName) { QDir pRoot(mPackageRoot); // Create the root and package dir. - if(!pRoot.exists()) - { + if (!pRoot.exists()) { QVERIFY(QDir().mkpath(mPackageRoot)); } @@ -166,14 +173,18 @@ void PlasmoidPackageTest::isValid() QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); out.setDevice(&file); - out << "THIS IS A PLASMOID SCRIPT....."; + out << "THIS IS A PLASMOID SCRIPT.....\n"; file.flush(); file.close(); + file.setPermissions(QFile::ReadUser | QFile::WriteUser); // Main file exists so should be valid now. delete p; p = new Plasma::Package(mPackageRoot, mPackage, ps); QVERIFY(p->isValid()); +#ifdef QCA2_FOUND + QCOMPARE(QString("0b8c7de4bee1ac6f373276ac2b5776c9194b2c56"), p->hash()); +#endif } void PlasmoidPackageTest::filePath()