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"