File indexing completed on 2024-12-08 07:31:58
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"