File indexing completed on 2024-04-21 04:57:07

0001 /**************************************************************************
0002  *   Copyright (C) 2009-2011 Matthias Fuchs <mat69@gmx.net>                *
0003  *                                                                         *
0004  *   This program is free software; you can redistribute it and/or modify  *
0005  *   it under the terms of the GNU General Public License as published by  *
0006  *   the Free Software Foundation; either version 2 of the License, or     *
0007  *   (at your option) any later version.                                   *
0008  *                                                                         *
0009  *   This program is distributed in the hope that it will be useful,       *
0010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0012  *   GNU General Public License for more details.                          *
0013  *                                                                         *
0014  *   You should have received a copy of the GNU General Public License     *
0015  *   along with this program; if not, write to the                         *
0016  *   Free Software Foundation, Inc.,                                       *
0017  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
0018  ***************************************************************************/
0019 
0020 #include "../dbus/dbusverifierwrapper.h"
0021 #include "settings.h"
0022 #include "verificationmodel.h"
0023 #include "verifier_p.h"
0024 #include "verifieradaptor.h"
0025 
0026 #include <QDomElement>
0027 #include <QFile>
0028 #include <QRegularExpression>
0029 
0030 #include "kget_debug.h"
0031 #include <QDebug>
0032 
0033 #include <vector>
0034 
0035 struct VerifierAlgo {
0036     QString type;
0037     QCryptographicHash::Algorithm qtType;
0038     int diggestLength;
0039 };
0040 
0041 const std::vector<VerifierAlgo> SUPPORTED_ALGOS = {{"sha512", QCryptographicHash::Sha512, 128},
0042                                                    {"sha384", QCryptographicHash::Sha384, 96},
0043                                                    {"sha256", QCryptographicHash::Sha256, 64},
0044                                                    {"sha1", QCryptographicHash::Sha1, 40},
0045                                                    {"md5", QCryptographicHash::Md5, 32},
0046                                                    {"md4", QCryptographicHash::Md4, 32}};
0047 
0048 const int VerifierPrivate::PARTSIZE = 500 * 1024;
0049 
0050 VerifierPrivate::~VerifierPrivate()
0051 {
0052     delete model;
0053     qDeleteAll(partialSums.begin(), partialSums.end());
0054 }
0055 
0056 static QCryptographicHash::Algorithm qtAlgorithmForType(const QString &type)
0057 {
0058     for (const VerifierAlgo &alg : SUPPORTED_ALGOS) {
0059         if (type == alg.type) {
0060             return alg.qtType;
0061         }
0062     }
0063 
0064     return QCryptographicHash::Md5;
0065 }
0066 
0067 QString VerifierPrivate::calculatePartialChecksum(QFile *file,
0068                                                   const QString &type,
0069                                                   KIO::fileoffset_t startOffset,
0070                                                   int pieceLength,
0071                                                   KIO::filesize_t fileSize,
0072                                                   bool *abortPtr)
0073 {
0074     if (!file) {
0075         return QString();
0076     }
0077 
0078     if (!fileSize) {
0079         fileSize = file->size();
0080     }
0081     // longer than the file, so adapt it
0082     if (static_cast<KIO::fileoffset_t>(fileSize) < startOffset + pieceLength) {
0083         pieceLength = fileSize - startOffset;
0084     }
0085 
0086     QCryptographicHash hash(qtAlgorithmForType(type));
0087 
0088     // we only read 512kb each time, to save RAM
0089     int numData = pieceLength / PARTSIZE;
0090     KIO::fileoffset_t dataRest = pieceLength % PARTSIZE;
0091 
0092     if (!numData && !dataRest) {
0093         return QString();
0094     }
0095 
0096     int k = 0;
0097     for (k = 0; k < numData; ++k) {
0098         if (!file->seek(startOffset + PARTSIZE * k)) {
0099             return QString();
0100         }
0101 
0102         if (abortPtr && *abortPtr) {
0103             return QString();
0104         }
0105 
0106         QByteArray data = file->read(PARTSIZE);
0107         hash.addData(data);
0108     }
0109 
0110     // now read the rest
0111     if (dataRest) {
0112         if (!file->seek(startOffset + PARTSIZE * k)) {
0113             return QString();
0114         }
0115 
0116         QByteArray data = file->read(dataRest);
0117         hash.addData(data);
0118     }
0119 
0120     return hash.result().toHex();
0121 }
0122 
0123 QStringList VerifierPrivate::orderChecksumTypes(Verifier::ChecksumStrength strength) const
0124 {
0125     QStringList checksumTypes = q->supportedVerficationTypes();
0126     if (strength == Verifier::Weak) {
0127         std::reverse(checksumTypes.begin(), checksumTypes.end());
0128         checksumTypes.move(0, 1); // md4 second position
0129     } else if (strength == Verifier::Strong) {
0130         std::reverse(checksumTypes.begin(), checksumTypes.end());
0131         checksumTypes.move(1, checksumTypes.count() - 1); // md5 second last position
0132         checksumTypes.move(0, checksumTypes.count() - 1); // md4 last position
0133     } else if (strength == Verifier::Strongest) {
0134         // nothing
0135     }
0136 
0137     return checksumTypes;
0138 }
0139 
0140 Verifier::Verifier(const QUrl &dest, QObject *parent)
0141     : QObject(parent)
0142     , d(new VerifierPrivate(this))
0143 {
0144     d->dest = dest;
0145     d->status = NoResult;
0146 
0147     static int dBusObjIdx = 0;
0148     d->dBusObjectPath = "/KGet/Verifiers/" + QString::number(dBusObjIdx++);
0149 
0150     auto *wrapper = new DBusVerifierWrapper(this);
0151     new VerifierAdaptor(wrapper);
0152     QDBusConnection::sessionBus().registerObject(d->dBusObjectPath, wrapper);
0153 
0154     qRegisterMetaType<KIO::filesize_t>("KIO::filesize_t");
0155     qRegisterMetaType<KIO::fileoffset_t>("KIO::fileoffset_t");
0156     qRegisterMetaType<QList<KIO::fileoffset_t>>("QList<KIO::fileoffset_t>");
0157 
0158     d->model = new VerificationModel();
0159     connect(&d->thread, SIGNAL(verified(QString, bool, QUrl)), this, SLOT(changeStatus(QString, bool)));
0160     connect(&d->thread, SIGNAL(brokenPieces(QList<KIO::fileoffset_t>, KIO::filesize_t)), this, SIGNAL(brokenPieces(QList<KIO::fileoffset_t>, KIO::filesize_t)));
0161 }
0162 
0163 Verifier::~Verifier()
0164 {
0165     delete d;
0166 }
0167 
0168 QString Verifier::dBusObjectPath() const
0169 {
0170     return d->dBusObjectPath;
0171 }
0172 
0173 QUrl Verifier::destination() const
0174 {
0175     return d->dest;
0176 }
0177 
0178 void Verifier::setDestination(const QUrl &destination)
0179 {
0180     d->dest = destination;
0181 }
0182 
0183 Verifier::VerificationStatus Verifier::status() const
0184 {
0185     return d->status;
0186 }
0187 
0188 VerificationModel *Verifier::model()
0189 {
0190     return d->model;
0191 }
0192 
0193 QStringList Verifier::supportedVerficationTypes()
0194 {
0195     static QStringList list;
0196     if (list.isEmpty()) {
0197         for (const VerifierAlgo &alg : SUPPORTED_ALGOS) {
0198             list << alg.type;
0199         }
0200     }
0201     return list;
0202 }
0203 
0204 int Verifier::diggestLength(const QString &type)
0205 {
0206     for (const VerifierAlgo &alg : SUPPORTED_ALGOS) {
0207         if (type == alg.type) {
0208             return alg.diggestLength;
0209         }
0210     }
0211     return 0;
0212 }
0213 
0214 bool Verifier::isChecksum(const QString &type, const QString &checksum)
0215 {
0216     const int length = diggestLength(type);
0217     const QString pattern = QString("[0-9a-z]{%1}").arg(length);
0218     // needs correct length and only word characters
0219     if (length && (checksum.length() == length) && checksum.toLower().contains(QRegularExpression(pattern))) {
0220         return true;
0221     }
0222 
0223     return false;
0224 }
0225 
0226 QString Verifier::cleanChecksumType(const QString &type)
0227 {
0228     QString hashType = type.toUpper();
0229     if (hashType.contains(QRegularExpression("^SHA\\d+"))) {
0230         hashType.insert(3, '-');
0231     }
0232 
0233     return hashType;
0234 }
0235 
0236 bool Verifier::isVerifyable() const
0237 {
0238     return QFile::exists(d->dest.toLocalFile()) && d->model->rowCount();
0239 }
0240 
0241 bool Verifier::isVerifyable(const QModelIndex &index) const
0242 {
0243     int row = -1;
0244     if (index.isValid()) {
0245         row = index.row();
0246     }
0247     if (QFile::exists(d->dest.toLocalFile()) && (row >= 0) && (row < d->model->rowCount())) {
0248         return true;
0249     }
0250     return false;
0251 }
0252 
0253 Checksum Verifier::availableChecksum(Verifier::ChecksumStrength strength) const
0254 {
0255     Checksum pair;
0256 
0257     // check if there is at least one entry
0258     QModelIndex index = d->model->index(0, 0);
0259     if (!index.isValid()) {
0260         return pair;
0261     }
0262 
0263     const QStringList available = supportedVerficationTypes();
0264     const QStringList supported = d->orderChecksumTypes(strength);
0265     for (int i = 0; i < supported.count(); ++i) {
0266         QModelIndexList indexList = d->model->match(index, Qt::DisplayRole, supported.at(i));
0267         if (!indexList.isEmpty() && available.contains(supported.at(i))) {
0268             QModelIndex match = d->model->index(indexList.first().row(), VerificationModel::Checksum);
0269             pair.first = supported.at(i);
0270             pair.second = match.data().toString();
0271             break;
0272         }
0273     }
0274 
0275     return pair;
0276 }
0277 
0278 QList<Checksum> Verifier::availableChecksums() const
0279 {
0280     QList<Checksum> checksums;
0281 
0282     for (int i = 0; i < d->model->rowCount(); ++i) {
0283         const QString type = d->model->index(i, VerificationModel::Type).data().toString();
0284         const QString hash = d->model->index(i, VerificationModel::Checksum).data().toString();
0285         checksums << qMakePair(type, hash);
0286     }
0287 
0288     return checksums;
0289 }
0290 
0291 QPair<QString, PartialChecksums *> Verifier::availablePartialChecksum(Verifier::ChecksumStrength strength) const
0292 {
0293     QPair<QString, PartialChecksums *> pair;
0294     QString type;
0295     PartialChecksums *checksum = nullptr;
0296 
0297     const QStringList available = supportedVerficationTypes();
0298     const QStringList supported = d->orderChecksumTypes(strength);
0299     for (int i = 0; i < supported.size(); ++i) {
0300         if (d->partialSums.contains(supported.at(i)) && available.contains(supported.at(i))) {
0301             type = supported.at(i);
0302             checksum = d->partialSums[type];
0303             break;
0304         }
0305     }
0306 
0307     return QPair<QString, PartialChecksums *>(type, checksum);
0308 }
0309 
0310 void Verifier::changeStatus(const QString &type, bool isVerified)
0311 {
0312     qCDebug(KGET_DEBUG) << "Verified:" << isVerified;
0313     d->status = isVerified ? Verifier::Verified : Verifier::NotVerified;
0314     d->model->setVerificationStatus(type, d->status);
0315     Q_EMIT verified(isVerified);
0316 }
0317 
0318 void Verifier::verify(const QModelIndex &index)
0319 {
0320     int row = -1;
0321     if (index.isValid()) {
0322         row = index.row();
0323     }
0324 
0325     QString type;
0326     QString checksum;
0327 
0328     if (row == -1) {
0329         Checksum pair = availableChecksum(static_cast<Verifier::ChecksumStrength>(Settings::checksumStrength()));
0330         type = pair.first;
0331         checksum = pair.second;
0332     } else if ((row >= 0) && (row < d->model->rowCount())) {
0333         type = d->model->index(row, VerificationModel::Type).data().toString();
0334         checksum = d->model->index(row, VerificationModel::Checksum).data().toString();
0335     }
0336 
0337     d->thread.verify(type, checksum, d->dest);
0338 }
0339 
0340 void Verifier::brokenPieces() const
0341 {
0342     QPair<QString, PartialChecksums *> pair = availablePartialChecksum(static_cast<Verifier::ChecksumStrength>(Settings::checksumStrength()));
0343     QList<QString> checksums;
0344     KIO::filesize_t length = 0;
0345     if (pair.second) {
0346         checksums = pair.second->checksums();
0347         length = pair.second->length();
0348     }
0349     d->thread.findBrokenPieces(pair.first, checksums, length, d->dest);
0350 }
0351 
0352 QString Verifier::checksum(const QUrl &dest, const QString &type, bool *abortPtr)
0353 {
0354     QStringList supported = supportedVerficationTypes();
0355     if (!supported.contains(type)) {
0356         return QString();
0357     }
0358 
0359     QFile file(dest.toLocalFile());
0360     if (!file.open(QIODevice::ReadOnly)) {
0361         return QString();
0362     }
0363 
0364     QCryptographicHash hash(qtAlgorithmForType(type));
0365 
0366     char buffer[1024];
0367     int len;
0368 
0369     while ((len = file.read(reinterpret_cast<char *>(buffer), sizeof(buffer))) > 0) {
0370         hash.addData(buffer, len);
0371         if (abortPtr && *abortPtr) {
0372             file.close();
0373             return QString();
0374         }
0375     }
0376     QString final = hash.result().toHex();
0377     file.close();
0378     return final;
0379 }
0380 
0381 PartialChecksums Verifier::partialChecksums(const QUrl &dest, const QString &type, KIO::filesize_t length, bool *abortPtr)
0382 {
0383     QStringList checksums;
0384 
0385     QStringList supported = supportedVerficationTypes();
0386     if (!supported.contains(type)) {
0387         return PartialChecksums();
0388     }
0389 
0390     QFile file(dest.toLocalFile());
0391     if (!file.open(QIODevice::ReadOnly)) {
0392         return PartialChecksums();
0393     }
0394 
0395     const KIO::filesize_t fileSize = file.size();
0396     if (!fileSize) {
0397         return PartialChecksums();
0398     }
0399 
0400     int numPieces = 0;
0401 
0402     // the piece length has been defined
0403     if (length) {
0404         numPieces = fileSize / length;
0405     } else {
0406         length = VerifierPrivate::PARTSIZE;
0407         numPieces = fileSize / length;
0408         if (numPieces > 100) {
0409             numPieces = 100;
0410             length = fileSize / numPieces;
0411         }
0412     }
0413 
0414     // there is a rest, so increase numPieces by one
0415     if (fileSize % length) {
0416         ++numPieces;
0417     }
0418 
0419     PartialChecksums partialChecksums;
0420 
0421     // create all the checksums for the pieces
0422     for (int i = 0; i < numPieces; ++i) {
0423         QString hash = VerifierPrivate::calculatePartialChecksum(&file, type, length * i, length, fileSize, abortPtr);
0424         if (hash.isEmpty()) {
0425             file.close();
0426             return PartialChecksums();
0427         }
0428         checksums.append(hash);
0429     }
0430 
0431     partialChecksums.setLength(length);
0432     partialChecksums.setChecksums(checksums);
0433     file.close();
0434     return partialChecksums;
0435 }
0436 
0437 void Verifier::addChecksum(const QString &type, const QString &checksum, int verified)
0438 {
0439     d->model->addChecksum(type, checksum, verified);
0440 }
0441 
0442 void Verifier::addChecksums(const QMultiHash<QString, QString> &checksums)
0443 {
0444     d->model->addChecksums(checksums);
0445 }
0446 
0447 void Verifier::addPartialChecksums(const QString &type, KIO::filesize_t length, const QStringList &checksums)
0448 {
0449     if (!d->partialSums.contains(type) && length && !checksums.isEmpty()) {
0450         d->partialSums[type] = new PartialChecksums(length, checksums);
0451     }
0452 }
0453 
0454 KIO::filesize_t Verifier::partialChunkLength() const
0455 {
0456     for (const VerifierAlgo &alg : SUPPORTED_ALGOS) {
0457         if (d->partialSums.contains(alg.type)) {
0458             return d->partialSums[alg.type]->length();
0459         }
0460     }
0461 
0462     return 0;
0463 }
0464 
0465 void Verifier::save(const QDomElement &element)
0466 {
0467     QDomElement e = element;
0468     e.setAttribute("verificationStatus", d->status);
0469 
0470     QDomElement verification = e.ownerDocument().createElement("verification");
0471     for (int i = 0; i < d->model->rowCount(); ++i) {
0472         QDomElement hash = e.ownerDocument().createElement("hash");
0473         hash.setAttribute("type", d->model->index(i, VerificationModel::Type).data().toString());
0474         hash.setAttribute("verified", d->model->index(i, VerificationModel::Verified).data(Qt::EditRole).toInt());
0475         QDomText value = e.ownerDocument().createTextNode(d->model->index(i, VerificationModel::Checksum).data().toString());
0476         hash.appendChild(value);
0477         verification.appendChild(hash);
0478     }
0479 
0480     QHash<QString, PartialChecksums *>::const_iterator it;
0481     QHash<QString, PartialChecksums *>::const_iterator itEnd = d->partialSums.constEnd();
0482     for (it = d->partialSums.constBegin(); it != itEnd; ++it) {
0483         QDomElement pieces = e.ownerDocument().createElement("pieces");
0484         pieces.setAttribute("type", it.key());
0485         pieces.setAttribute("length", (*it)->length());
0486         QList<QString> checksums = (*it)->checksums();
0487         for (int i = 0; i < checksums.size(); ++i) {
0488             QDomElement hash = e.ownerDocument().createElement("hash");
0489             hash.setAttribute("piece", i);
0490             QDomText value = e.ownerDocument().createTextNode(checksums[i]);
0491             hash.appendChild(value);
0492             pieces.appendChild(hash);
0493         }
0494         verification.appendChild(pieces);
0495     }
0496     e.appendChild(verification);
0497 }
0498 
0499 void Verifier::load(const QDomElement &e)
0500 {
0501     if (e.hasAttribute("verificationStatus")) {
0502         const int status = e.attribute("verificationStatus").toInt();
0503         switch (status) {
0504         case NoResult:
0505             d->status = NoResult;
0506             break;
0507         case NotVerified:
0508             d->status = NotVerified;
0509             break;
0510         case Verified:
0511             d->status = Verified;
0512             break;
0513         default:
0514             d->status = NotVerified;
0515             break;
0516         }
0517     }
0518 
0519     QDomElement verification = e.firstChildElement("verification");
0520     QDomNodeList const hashList = verification.elementsByTagName("hash");
0521 
0522     for (int i = 0; i < hashList.length(); ++i) {
0523         const QDomElement hash = hashList.item(i).toElement();
0524         const QString value = hash.text();
0525         const QString type = hash.attribute("type");
0526         const int verificationStatus = hash.attribute("verified").toInt();
0527         if (!type.isEmpty() && !value.isEmpty()) {
0528             d->model->addChecksum(type, value, verificationStatus);
0529         }
0530     }
0531 
0532     QDomNodeList const piecesList = verification.elementsByTagName("pieces");
0533 
0534     for (int i = 0; i < piecesList.length(); ++i) {
0535         QDomElement pieces = piecesList.at(i).toElement();
0536 
0537         const QString type = pieces.attribute("type");
0538         const KIO::filesize_t length = pieces.attribute("length").toULongLong();
0539         QStringList partialChecksums;
0540 
0541         const QDomNodeList partialHashList = pieces.elementsByTagName("hash");
0542         for (int j = 0; j < partialHashList.size(); ++j) // TODO give this function the size of the file, to calculate how many hashs are needed as an
0543                                                          // additional check, do that check in addPartialChecksums?!
0544         {
0545             const QString hash = partialHashList.at(j).toElement().text();
0546             if (hash.isEmpty()) {
0547                 break;
0548             }
0549             partialChecksums.append(hash);
0550         }
0551 
0552         addPartialChecksums(type, length, partialChecksums);
0553     }
0554 }
0555 
0556 #include "moc_verifier.cpp"