File indexing completed on 2023-10-01 08:39:32

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 
0029 #include "kget_debug.h"
0030 #include <QDebug>
0031 
0032 #ifdef HAVE_QCA2
0033 #include <qca_basic.h>
0034 #endif
0035 
0036 // TODO use mutable to make some methods const?
0037 const QStringList VerifierPrivate::SUPPORTED = (QStringList() << "sha512"
0038                                                               << "sha384"
0039                                                               << "sha256"
0040                                                               << "ripmed160"
0041                                                               << "sha1"
0042                                                               << "md5"
0043                                                               << "md4");
0044 const QString VerifierPrivate::MD5 = QString("md5");
0045 const int VerifierPrivate::DIGGESTLENGTH[] = {128, 96, 64, 40, 40, 32, 32};
0046 const int VerifierPrivate::MD5LENGTH = 32;
0047 const int VerifierPrivate::PARTSIZE = 500 * 1024;
0048 
0049 VerifierPrivate::~VerifierPrivate()
0050 {
0051     delete model;
0052     qDeleteAll(partialSums.begin(), partialSums.end());
0053 }
0054 
0055 QString VerifierPrivate::calculatePartialChecksum(QFile *file,
0056                                                   const QString &type,
0057                                                   KIO::fileoffset_t startOffset,
0058                                                   int pieceLength,
0059                                                   KIO::filesize_t fileSize,
0060                                                   bool *abortPtr)
0061 {
0062     if (!file) {
0063         return QString();
0064     }
0065 
0066     if (!fileSize) {
0067         fileSize = file->size();
0068     }
0069     // longer than the file, so adapt it
0070     if (static_cast<KIO::fileoffset_t>(fileSize) < startOffset + pieceLength) {
0071         pieceLength = fileSize - startOffset;
0072     }
0073 
0074 #ifdef HAVE_QCA2
0075     QCA::Hash hash(type);
0076 
0077     // it can be that QCA2 does not support md5, e.g. when Qt is compiled locally
0078     QCryptographicHash md5Hash(QCryptographicHash::Md5);
0079     const bool useMd5 = (type == MD5);
0080 #else // NO QCA2
0081     if (type != MD5) {
0082         return QString();
0083     }
0084     QCryptographicHash hash(QCryptographicHash::Md5);
0085 #endif // HAVE_QCA2
0086 
0087     // we only read 512kb each time, to save RAM
0088     int numData = pieceLength / PARTSIZE;
0089     KIO::fileoffset_t dataRest = pieceLength % PARTSIZE;
0090 
0091     if (!numData && !dataRest) {
0092         return QString();
0093     }
0094 
0095     int k = 0;
0096     for (k = 0; k < numData; ++k) {
0097         if (!file->seek(startOffset + PARTSIZE * k)) {
0098             return QString();
0099         }
0100 
0101         if (abortPtr && *abortPtr) {
0102             return QString();
0103         }
0104 
0105         QByteArray data = file->read(PARTSIZE);
0106 #ifdef HAVE_QCA2
0107         if (useMd5) {
0108             md5Hash.addData(data);
0109         } else {
0110             hash.update(data);
0111         }
0112 #else // NO QCA2
0113         hash.addData(data);
0114 #endif // HAVE_QCA2
0115     }
0116 
0117     // now read the rest
0118     if (dataRest) {
0119         if (!file->seek(startOffset + PARTSIZE * k)) {
0120             return QString();
0121         }
0122 
0123         QByteArray data = file->read(dataRest);
0124 #ifdef HAVE_QCA2
0125         if (useMd5) {
0126             md5Hash.addData(data);
0127         } else {
0128             hash.update(data);
0129         }
0130 #else // NO QCA2
0131         hash.addData(data);
0132 #endif // HAVE_QCA2
0133     }
0134 
0135 #ifdef HAVE_QCA2
0136     return (useMd5 ? md5Hash.result().toHex() : QString(QCA::arrayToHex(hash.final().toByteArray())));
0137 #else // NO QCA2
0138     return hash.result().toHex();
0139 #endif // HAVE_QCA2
0140 }
0141 
0142 QStringList VerifierPrivate::orderChecksumTypes(Verifier::ChecksumStrength strength) const
0143 {
0144     QStringList checksumTypes;
0145     if (strength == Verifier::Weak) {
0146         for (int i = SUPPORTED.count() - 1; i >= 0; --i) {
0147             checksumTypes.append(SUPPORTED.at(i));
0148         }
0149         checksumTypes.move(0, 1); // md4 second position
0150     } else if (strength == Verifier::Strong) {
0151         for (int i = SUPPORTED.count() - 1; i >= 0; --i) {
0152             checksumTypes.append(SUPPORTED.at(i));
0153         }
0154         checksumTypes.move(1, checksumTypes.count() - 1); // md5 second last position
0155         checksumTypes.move(0, checksumTypes.count() - 1); // md4 last position
0156     } else if (strength == Verifier::Strongest) {
0157         checksumTypes = SUPPORTED;
0158     }
0159 
0160     return checksumTypes;
0161 }
0162 
0163 Verifier::Verifier(const QUrl &dest, QObject *parent)
0164     : QObject(parent)
0165     , d(new VerifierPrivate(this))
0166 {
0167     d->dest = dest;
0168     d->status = NoResult;
0169 
0170     static int dBusObjIdx = 0;
0171     d->dBusObjectPath = "/KGet/Verifiers/" + QString::number(dBusObjIdx++);
0172 
0173     auto *wrapper = new DBusVerifierWrapper(this);
0174     new VerifierAdaptor(wrapper);
0175     QDBusConnection::sessionBus().registerObject(d->dBusObjectPath, wrapper);
0176 
0177     qRegisterMetaType<KIO::filesize_t>("KIO::filesize_t");
0178     qRegisterMetaType<KIO::fileoffset_t>("KIO::fileoffset_t");
0179     qRegisterMetaType<QList<KIO::fileoffset_t>>("QList<KIO::fileoffset_t>");
0180 
0181     d->model = new VerificationModel();
0182     connect(&d->thread, SIGNAL(verified(QString, bool, QUrl)), this, SLOT(changeStatus(QString, bool)));
0183     connect(&d->thread, SIGNAL(brokenPieces(QList<KIO::fileoffset_t>, KIO::filesize_t)), this, SIGNAL(brokenPieces(QList<KIO::fileoffset_t>, KIO::filesize_t)));
0184 }
0185 
0186 Verifier::~Verifier()
0187 {
0188     delete d;
0189 }
0190 
0191 QString Verifier::dBusObjectPath() const
0192 {
0193     return d->dBusObjectPath;
0194 }
0195 
0196 QUrl Verifier::destination() const
0197 {
0198     return d->dest;
0199 }
0200 
0201 void Verifier::setDestination(const QUrl &destination)
0202 {
0203     d->dest = destination;
0204 }
0205 
0206 Verifier::VerificationStatus Verifier::status() const
0207 {
0208     return d->status;
0209 }
0210 
0211 VerificationModel *Verifier::model()
0212 {
0213     return d->model;
0214 }
0215 
0216 QStringList Verifier::supportedVerficationTypes()
0217 {
0218     QStringList supported;
0219 #ifdef HAVE_QCA2
0220     QStringList supportedTypes = QCA::Hash::supportedTypes();
0221     for (int i = 0; i < VerifierPrivate::SUPPORTED.count(); ++i) {
0222         if (supportedTypes.contains(VerifierPrivate::SUPPORTED.at(i))) {
0223             supported << VerifierPrivate::SUPPORTED.at(i);
0224         }
0225     }
0226 #endif // HAVE_QCA2
0227 
0228     if (!supported.contains(VerifierPrivate::MD5)) {
0229         supported << VerifierPrivate::MD5;
0230     }
0231 
0232     return supported;
0233 }
0234 
0235 int Verifier::diggestLength(const QString &type)
0236 {
0237     if (type == VerifierPrivate::MD5) {
0238         return VerifierPrivate::MD5LENGTH;
0239     }
0240 
0241 #ifdef HAVE_QCA2
0242     if (QCA::isSupported(type.toLatin1())) {
0243         return VerifierPrivate::DIGGESTLENGTH[VerifierPrivate::SUPPORTED.indexOf(type)];
0244     }
0245 #endif // HAVE_QCA2
0246 
0247     return 0;
0248 }
0249 
0250 bool Verifier::isChecksum(const QString &type, const QString &checksum)
0251 {
0252     const int length = diggestLength(type);
0253     const QString pattern = QString("[0-9a-z]{%1}").arg(length);
0254     // needs correct length and only word characters
0255     if (length && (checksum.length() == length) && checksum.toLower().contains(QRegExp(pattern))) {
0256         return true;
0257     }
0258 
0259     return false;
0260 }
0261 
0262 QString Verifier::cleanChecksumType(const QString &type)
0263 {
0264     QString hashType = type.toUpper();
0265     if (hashType.contains(QRegExp("^SHA\\d+"))) {
0266         hashType.insert(3, '-');
0267     }
0268 
0269     return hashType;
0270 }
0271 
0272 bool Verifier::isVerifyable() const
0273 {
0274     return QFile::exists(d->dest.toLocalFile()) && d->model->rowCount();
0275 }
0276 
0277 bool Verifier::isVerifyable(const QModelIndex &index) const
0278 {
0279     int row = -1;
0280     if (index.isValid()) {
0281         row = index.row();
0282     }
0283     if (QFile::exists(d->dest.toLocalFile()) && (row >= 0) && (row < d->model->rowCount())) {
0284         return true;
0285     }
0286     return false;
0287 }
0288 
0289 Checksum Verifier::availableChecksum(Verifier::ChecksumStrength strength) const
0290 {
0291     Checksum pair;
0292 
0293     // check if there is at least one entry
0294     QModelIndex index = d->model->index(0, 0);
0295     if (!index.isValid()) {
0296         return pair;
0297     }
0298 
0299     const QStringList available = supportedVerficationTypes();
0300     const QStringList supported = d->orderChecksumTypes(strength);
0301     for (int i = 0; i < supported.count(); ++i) {
0302         QModelIndexList indexList = d->model->match(index, Qt::DisplayRole, supported.at(i));
0303         if (!indexList.isEmpty() && available.contains(supported.at(i))) {
0304             QModelIndex match = d->model->index(indexList.first().row(), VerificationModel::Checksum);
0305             pair.first = supported.at(i);
0306             pair.second = match.data().toString();
0307             break;
0308         }
0309     }
0310 
0311     return pair;
0312 }
0313 
0314 QList<Checksum> Verifier::availableChecksums() const
0315 {
0316     QList<Checksum> checksums;
0317 
0318     for (int i = 0; i < d->model->rowCount(); ++i) {
0319         const QString type = d->model->index(i, VerificationModel::Type).data().toString();
0320         const QString hash = d->model->index(i, VerificationModel::Checksum).data().toString();
0321         checksums << qMakePair(type, hash);
0322     }
0323 
0324     return checksums;
0325 }
0326 
0327 QPair<QString, PartialChecksums *> Verifier::availablePartialChecksum(Verifier::ChecksumStrength strength) const
0328 {
0329     QPair<QString, PartialChecksums *> pair;
0330     QString type;
0331     PartialChecksums *checksum = nullptr;
0332 
0333     const QStringList available = supportedVerficationTypes();
0334     const QStringList supported = d->orderChecksumTypes(strength);
0335     for (int i = 0; i < supported.size(); ++i) {
0336         if (d->partialSums.contains(supported.at(i)) && available.contains(supported.at(i))) {
0337             type = supported.at(i);
0338             checksum = d->partialSums[type];
0339             break;
0340         }
0341     }
0342 
0343     return QPair<QString, PartialChecksums *>(type, checksum);
0344 }
0345 
0346 void Verifier::changeStatus(const QString &type, bool isVerified)
0347 {
0348     qCDebug(KGET_DEBUG) << "Verified:" << isVerified;
0349     d->status = isVerified ? Verifier::Verified : Verifier::NotVerified;
0350     d->model->setVerificationStatus(type, d->status);
0351     Q_EMIT verified(isVerified);
0352 }
0353 
0354 void Verifier::verify(const QModelIndex &index)
0355 {
0356     int row = -1;
0357     if (index.isValid()) {
0358         row = index.row();
0359     }
0360 
0361     QString type;
0362     QString checksum;
0363 
0364     if (row == -1) {
0365         Checksum pair = availableChecksum(static_cast<Verifier::ChecksumStrength>(Settings::checksumStrength()));
0366         type = pair.first;
0367         checksum = pair.second;
0368     } else if ((row >= 0) && (row < d->model->rowCount())) {
0369         type = d->model->index(row, VerificationModel::Type).data().toString();
0370         checksum = d->model->index(row, VerificationModel::Checksum).data().toString();
0371     }
0372 
0373     d->thread.verify(type, checksum, d->dest);
0374 }
0375 
0376 void Verifier::brokenPieces() const
0377 {
0378     QPair<QString, PartialChecksums *> pair = availablePartialChecksum(static_cast<Verifier::ChecksumStrength>(Settings::checksumStrength()));
0379     QList<QString> checksums;
0380     KIO::filesize_t length = 0;
0381     if (pair.second) {
0382         checksums = pair.second->checksums();
0383         length = pair.second->length();
0384     }
0385     d->thread.findBrokenPieces(pair.first, checksums, length, d->dest);
0386 }
0387 
0388 QString Verifier::checksum(const QUrl &dest, const QString &type, bool *abortPtr)
0389 {
0390     QStringList supported = supportedVerficationTypes();
0391     if (!supported.contains(type)) {
0392         return QString();
0393     }
0394 
0395     QFile file(dest.toLocalFile());
0396     if (!file.open(QIODevice::ReadOnly)) {
0397         return QString();
0398     }
0399 
0400     if (type == VerifierPrivate::MD5) {
0401         QCryptographicHash hash(QCryptographicHash::Md5);
0402         hash.addData(&file);
0403         QString final = hash.result().toHex();
0404         file.close();
0405         return final;
0406     }
0407 
0408 #ifdef HAVE_QCA2
0409     QCA::Hash hash(type);
0410 
0411     // BEGIN taken from qca_basic.h and slightly adopted to allow abort
0412     char buffer[1024];
0413     int len;
0414 
0415     while ((len = file.read(reinterpret_cast<char *>(buffer), sizeof(buffer))) > 0) {
0416         hash.update(buffer, len);
0417         if (abortPtr && *abortPtr) {
0418             hash.final();
0419             file.close();
0420             return QString();
0421         }
0422     }
0423     // END
0424 
0425     QString final = QString(QCA::arrayToHex(hash.final().toByteArray()));
0426     file.close();
0427     return final;
0428 #endif // HAVE_QCA2
0429 
0430     return QString();
0431 }
0432 
0433 PartialChecksums Verifier::partialChecksums(const QUrl &dest, const QString &type, KIO::filesize_t length, bool *abortPtr)
0434 {
0435     QStringList checksums;
0436 
0437     QStringList supported = supportedVerficationTypes();
0438     if (!supported.contains(type)) {
0439         return PartialChecksums();
0440     }
0441 
0442     QFile file(dest.toLocalFile());
0443     if (!file.open(QIODevice::ReadOnly)) {
0444         return PartialChecksums();
0445     }
0446 
0447     const KIO::filesize_t fileSize = file.size();
0448     if (!fileSize) {
0449         return PartialChecksums();
0450     }
0451 
0452     int numPieces = 0;
0453 
0454     // the piece length has been defined
0455     if (length) {
0456         numPieces = fileSize / length;
0457     } else {
0458         length = VerifierPrivate::PARTSIZE;
0459         numPieces = fileSize / length;
0460         if (numPieces > 100) {
0461             numPieces = 100;
0462             length = fileSize / numPieces;
0463         }
0464     }
0465 
0466     // there is a rest, so increase numPieces by one
0467     if (fileSize % length) {
0468         ++numPieces;
0469     }
0470 
0471     PartialChecksums partialChecksums;
0472 
0473     // create all the checksums for the pieces
0474     for (int i = 0; i < numPieces; ++i) {
0475         QString hash = VerifierPrivate::calculatePartialChecksum(&file, type, length * i, length, fileSize, abortPtr);
0476         if (hash.isEmpty()) {
0477             file.close();
0478             return PartialChecksums();
0479         }
0480         checksums.append(hash);
0481     }
0482 
0483     partialChecksums.setLength(length);
0484     partialChecksums.setChecksums(checksums);
0485     file.close();
0486     return partialChecksums;
0487 }
0488 
0489 void Verifier::addChecksum(const QString &type, const QString &checksum, int verified)
0490 {
0491     d->model->addChecksum(type, checksum, verified);
0492 }
0493 
0494 void Verifier::addChecksums(const QHash<QString, QString> &checksums)
0495 {
0496     d->model->addChecksums(checksums);
0497 }
0498 
0499 void Verifier::addPartialChecksums(const QString &type, KIO::filesize_t length, const QStringList &checksums)
0500 {
0501     if (!d->partialSums.contains(type) && length && !checksums.isEmpty()) {
0502         d->partialSums[type] = new PartialChecksums(length, checksums);
0503     }
0504 }
0505 
0506 KIO::filesize_t Verifier::partialChunkLength() const
0507 {
0508     QStringList::const_iterator it;
0509     QStringList::const_iterator itEnd = VerifierPrivate::SUPPORTED.constEnd();
0510     for (it = VerifierPrivate::SUPPORTED.constBegin(); it != itEnd; ++it) {
0511         if (d->partialSums.contains(*it)) {
0512             return d->partialSums[*it]->length();
0513         }
0514     }
0515 
0516     return 0;
0517 }
0518 
0519 void Verifier::save(const QDomElement &element)
0520 {
0521     QDomElement e = element;
0522     e.setAttribute("verificationStatus", d->status);
0523 
0524     QDomElement verification = e.ownerDocument().createElement("verification");
0525     for (int i = 0; i < d->model->rowCount(); ++i) {
0526         QDomElement hash = e.ownerDocument().createElement("hash");
0527         hash.setAttribute("type", d->model->index(i, VerificationModel::Type).data().toString());
0528         hash.setAttribute("verified", d->model->index(i, VerificationModel::Verified).data(Qt::EditRole).toInt());
0529         QDomText value = e.ownerDocument().createTextNode(d->model->index(i, VerificationModel::Checksum).data().toString());
0530         hash.appendChild(value);
0531         verification.appendChild(hash);
0532     }
0533 
0534     QHash<QString, PartialChecksums *>::const_iterator it;
0535     QHash<QString, PartialChecksums *>::const_iterator itEnd = d->partialSums.constEnd();
0536     for (it = d->partialSums.constBegin(); it != itEnd; ++it) {
0537         QDomElement pieces = e.ownerDocument().createElement("pieces");
0538         pieces.setAttribute("type", it.key());
0539         pieces.setAttribute("length", (*it)->length());
0540         QList<QString> checksums = (*it)->checksums();
0541         for (int i = 0; i < checksums.size(); ++i) {
0542             QDomElement hash = e.ownerDocument().createElement("hash");
0543             hash.setAttribute("piece", i);
0544             QDomText value = e.ownerDocument().createTextNode(checksums[i]);
0545             hash.appendChild(value);
0546             pieces.appendChild(hash);
0547         }
0548         verification.appendChild(pieces);
0549     }
0550     e.appendChild(verification);
0551 }
0552 
0553 void Verifier::load(const QDomElement &e)
0554 {
0555     if (e.hasAttribute("verificationStatus")) {
0556         const int status = e.attribute("verificationStatus").toInt();
0557         switch (status) {
0558         case NoResult:
0559             d->status = NoResult;
0560             break;
0561         case NotVerified:
0562             d->status = NotVerified;
0563             break;
0564         case Verified:
0565             d->status = Verified;
0566             break;
0567         default:
0568             d->status = NotVerified;
0569             break;
0570         }
0571     }
0572 
0573     QDomElement verification = e.firstChildElement("verification");
0574     QDomNodeList const hashList = verification.elementsByTagName("hash");
0575 
0576     for (int i = 0; i < hashList.length(); ++i) {
0577         const QDomElement hash = hashList.item(i).toElement();
0578         const QString value = hash.text();
0579         const QString type = hash.attribute("type");
0580         const int verificationStatus = hash.attribute("verified").toInt();
0581         if (!type.isEmpty() && !value.isEmpty()) {
0582             d->model->addChecksum(type, value, verificationStatus);
0583         }
0584     }
0585 
0586     QDomNodeList const piecesList = verification.elementsByTagName("pieces");
0587 
0588     for (int i = 0; i < piecesList.length(); ++i) {
0589         QDomElement pieces = piecesList.at(i).toElement();
0590 
0591         const QString type = pieces.attribute("type");
0592         const KIO::filesize_t length = pieces.attribute("length").toULongLong();
0593         QStringList partialChecksums;
0594 
0595         const QDomNodeList partialHashList = pieces.elementsByTagName("hash");
0596         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
0597                                                          // additional check, do that check in addPartialChecksums?!
0598         {
0599             const QString hash = partialHashList.at(j).toElement().text();
0600             if (hash.isEmpty()) {
0601                 break;
0602             }
0603             partialChecksums.append(hash);
0604         }
0605 
0606         addPartialChecksums(type, length, partialChecksums);
0607     }
0608 }
0609 
0610 #include "moc_verifier.cpp"