1 /**************************************************************************
2 * Copyright (C) 2009-2011 Matthias Fuchs <mat69@gmx.net> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
18 ***************************************************************************/
19
20 #include "verifier_p.h"
21 #include "verificationmodel.h"
22 #include "../dbus/dbusverifierwrapper.h"
23 #include "verifieradaptor.h"
24 #include "settings.h"
25
26 #include <QFile>
27 #include <QDomElement>
28
29 #include "kget_debug.h"
30 #include <QDebug>
31
32 #ifdef HAVE_QCA2
33 #include <qca_basic.h>
34 #endif
35
36 //TODO use mutable to make some methods const?
37 const QStringList VerifierPrivate::SUPPORTED = (QStringList() << "sha512" << "sha384" << "sha256" << "ripmed160" << "sha1" << "md5" << "md4");
38 const QString VerifierPrivate::MD5 = QString("md5");
39 const int VerifierPrivate::DIGGESTLENGTH[] = {128, 96, 64, 40, 40, 32, 32};
40 const int VerifierPrivate::MD5LENGTH = 32;
41 const int VerifierPrivate::PARTSIZE = 500 * 1024;
42
~VerifierPrivate()43 VerifierPrivate::~VerifierPrivate()
44 {
45 delete model;
46 qDeleteAll(partialSums.begin(), partialSums.end());
47 }
48
calculatePartialChecksum(QFile * file,const QString & type,KIO::fileoffset_t startOffset,int pieceLength,KIO::filesize_t fileSize,bool * abortPtr)49 QString VerifierPrivate::calculatePartialChecksum(QFile *file, const QString &type, KIO::fileoffset_t startOffset, int pieceLength, KIO::filesize_t fileSize, bool *abortPtr)
50 {
51 if (!file)
52 {
53 return QString();
54 }
55
56 if (!fileSize)
57 {
58 fileSize = file->size();
59 }
60 //longer than the file, so adapt it
61 if (static_cast<KIO::fileoffset_t>(fileSize) < startOffset + pieceLength)
62 {
63 pieceLength = fileSize - startOffset;
64 }
65
66 #ifdef HAVE_QCA2
67 QCA::Hash hash(type);
68
69 //it can be that QCA2 does not support md5, e.g. when Qt is compiled locally
70 QCryptographicHash md5Hash(QCryptographicHash::Md5);
71 const bool useMd5 = (type == MD5);
72 #else //NO QCA2
73 if (type != MD5)
74 {
75 return QString();
76 }
77 QCryptographicHash hash(QCryptographicHash::Md5);
78 #endif //HAVE_QCA2
79
80 //we only read 512kb each time, to save RAM
81 int numData = pieceLength / PARTSIZE;
82 KIO::fileoffset_t dataRest = pieceLength % PARTSIZE;
83
84 if (!numData && !dataRest)
85 {
86 return QString();
87 }
88
89 int k = 0;
90 for (k = 0; k < numData; ++k)
91 {
92 if (!file->seek(startOffset + PARTSIZE * k))
93 {
94 return QString();
95 }
96
97 if (abortPtr && *abortPtr)
98 {
99 return QString();
100 }
101
102 QByteArray data = file->read(PARTSIZE);
103 #ifdef HAVE_QCA2
104 if (useMd5) {
105 md5Hash.addData(data);
106 } else {
107 hash.update(data);
108 }
109 #else //NO QCA2
110 hash.addData(data);
111 #endif //HAVE_QCA2
112 }
113
114 //now read the rest
115 if (dataRest)
116 {
117 if (!file->seek(startOffset + PARTSIZE * k))
118 {
119 return QString();
120 }
121
122 QByteArray data = file->read(dataRest);
123 #ifdef HAVE_QCA2
124 if (useMd5) {
125 md5Hash.addData(data);
126 } else {
127 hash.update(data);
128 }
129 #else //NO QCA2
130 hash.addData(data);
131 #endif //HAVE_QCA2
132 }
133
134 #ifdef HAVE_QCA2
135 return (useMd5 ? QString(md5Hash.result()) : QString(QCA::arrayToHex(hash.final().toByteArray())));
136 #else //NO QCA2
137 return QString(hash.result());
138 #endif //HAVE_QCA2
139 }
140
orderChecksumTypes(Verifier::ChecksumStrength strength) const141 QStringList VerifierPrivate::orderChecksumTypes(Verifier::ChecksumStrength strength) const
142 {
143 QStringList checksumTypes;
144 if (strength == Verifier::Weak) {
145 for (int i = SUPPORTED.count() - 1; i >= 0; --i) {
146 checksumTypes.append(SUPPORTED.at(i));
147 }
148 checksumTypes.move(0, 1); //md4 second position
149 } else if (strength == Verifier::Strong) {
150 for (int i = SUPPORTED.count() - 1; i >= 0; --i) {
151 checksumTypes.append(SUPPORTED.at(i));
152 }
153 checksumTypes.move(1, checksumTypes.count() - 1); //md5 second last position
154 checksumTypes.move(0, checksumTypes.count() - 1); //md4 last position
155 } else if (strength == Verifier::Strongest) {
156 checksumTypes = SUPPORTED;
157 }
158
159 return checksumTypes;
160 }
161
Verifier(const QUrl & dest,QObject * parent)162 Verifier::Verifier(const QUrl &dest, QObject *parent)
163 : QObject(parent),
164 d(new VerifierPrivate(this))
165 {
166 d->dest = dest;
167 d->status = NoResult;
168
169 static int dBusObjIdx = 0;
170 d->dBusObjectPath = "/KGet/Verifiers/" + QString::number(dBusObjIdx++);
171
172 auto *wrapper = new DBusVerifierWrapper(this);
173 new VerifierAdaptor(wrapper);
174 QDBusConnection::sessionBus().registerObject(d->dBusObjectPath, wrapper);
175
176 qRegisterMetaType<KIO::filesize_t>("KIO::filesize_t");
177 qRegisterMetaType<KIO::fileoffset_t>("KIO::fileoffset_t");
178 qRegisterMetaType<QList<KIO::fileoffset_t> >("QList<KIO::fileoffset_t>");
179
180 d->model = new VerificationModel();
181 connect(&d->thread, SIGNAL(verified(QString,bool,QUrl)), this, SLOT(changeStatus(QString,bool)));
182 connect(&d->thread, SIGNAL(brokenPieces(QList<KIO::fileoffset_t>,KIO::filesize_t)), this, SIGNAL(brokenPieces(QList<KIO::fileoffset_t>,KIO::filesize_t)));
183 }
184
~Verifier()185 Verifier::~Verifier()
186 {
187 delete d;
188 }
189
dBusObjectPath() const190 QString Verifier::dBusObjectPath() const
191 {
192 return d->dBusObjectPath;
193 }
194
destination() const195 QUrl Verifier::destination() const
196 {
197 return d->dest;
198 }
199
setDestination(const QUrl & destination)200 void Verifier::setDestination(const QUrl &destination)
201 {
202 d->dest = destination;
203 }
204
status() const205 Verifier::VerificationStatus Verifier::status() const
206 {
207 return d->status;
208 }
209
model()210 VerificationModel *Verifier::model()
211 {
212 return d->model;
213 }
214
supportedVerficationTypes()215 QStringList Verifier::supportedVerficationTypes()
216 {
217 QStringList supported;
218 #ifdef HAVE_QCA2
219 QStringList supportedTypes = QCA::Hash::supportedTypes();
220 for (int i = 0; i < VerifierPrivate::SUPPORTED.count(); ++i)
221 {
222 if (supportedTypes.contains(VerifierPrivate::SUPPORTED.at(i)))
223 {
224 supported << VerifierPrivate::SUPPORTED.at(i);
225 }
226 }
227 #endif //HAVE_QCA2
228
229 if (!supported.contains(VerifierPrivate::MD5))
230 {
231 supported << VerifierPrivate::MD5;
232 }
233
234 return supported;
235
236 }
237
diggestLength(const QString & type)238 int Verifier::diggestLength(const QString &type)
239 {
240 if (type == VerifierPrivate::MD5)
241 {
242 return VerifierPrivate::MD5LENGTH;
243 }
244
245 #ifdef HAVE_QCA2
246 if (QCA::isSupported(type.toLatin1()))
247 {
248 return VerifierPrivate::DIGGESTLENGTH[VerifierPrivate::SUPPORTED.indexOf(type)];
249 }
250 #endif //HAVE_QCA2
251
252 return 0;
253 }
254
isChecksum(const QString & type,const QString & checksum)255 bool Verifier::isChecksum(const QString &type, const QString &checksum)
256 {
257 const int length = diggestLength(type);
258 const QString pattern = QString("[0-9a-z]{%1}").arg(length);
259 //needs correct length and only word characters
260 if (length && (checksum.length() == length) && checksum.toLower().contains(QRegExp(pattern)))
261 {
262 return true;
263 }
264
265 return false;
266 }
267
cleanChecksumType(const QString & type)268 QString Verifier::cleanChecksumType(const QString &type)
269 {
270 QString hashType = type.toUpper();
271 if (hashType.contains(QRegExp("^SHA\\d+"))) {
272 hashType.insert(3, '-');
273 }
274
275 return hashType;
276 }
277
isVerifyable() const278 bool Verifier::isVerifyable() const
279 {
280 return QFile::exists(d->dest.toString()) && d->model->rowCount();
281 }
282
isVerifyable(const QModelIndex & index) const283 bool Verifier::isVerifyable(const QModelIndex &index) const
284 {
285 int row = -1;
286 if (index.isValid())
287 {
288 row = index.row();
289 }
290 if (QFile::exists(d->dest.toString()) && (row >= 0) && (row < d->model->rowCount()))
291 {
292 return true;
293 }
294 return false;
295 }
296
availableChecksum(Verifier::ChecksumStrength strength) const297 Checksum Verifier::availableChecksum(Verifier::ChecksumStrength strength) const
298 {
299 Checksum pair;
300
301 //check if there is at least one entry
302 QModelIndex index = d->model->index(0, 0);
303 if (!index.isValid())
304 {
305 return pair;
306 }
307
308 const QStringList available = supportedVerficationTypes();
309 const QStringList supported = d->orderChecksumTypes(strength);
310 for (int i = 0; i < supported.count(); ++i) {
311 QModelIndexList indexList = d->model->match(index, Qt::DisplayRole, supported.at(i));
312 if (!indexList.isEmpty() && available.contains(supported.at(i))) {
313 QModelIndex match = d->model->index(indexList.first().row(), VerificationModel::Checksum);
314 pair.first = supported.at(i);
315 pair.second = match.data().toString();
316 break;
317 }
318 }
319
320 return pair;
321 }
322
availableChecksums() const323 QList<Checksum> Verifier::availableChecksums() const
324 {
325 QList<Checksum> checksums;
326
327 for (int i = 0; i < d->model->rowCount(); ++i) {
328 const QString type = d->model->index(i, VerificationModel::Type).data().toString();
329 const QString hash = d->model->index(i, VerificationModel::Checksum).data().toString();
330 checksums << qMakePair(type, hash);
331 }
332
333 return checksums;
334 }
335
availablePartialChecksum(Verifier::ChecksumStrength strength) const336 QPair<QString, PartialChecksums*> Verifier::availablePartialChecksum(Verifier::ChecksumStrength strength) const
337 {
338 QPair<QString, PartialChecksums*> pair;
339 QString type;
340 PartialChecksums *checksum = nullptr;
341
342 const QStringList available = supportedVerficationTypes();
343 const QStringList supported = d->orderChecksumTypes(strength);
344 for (int i = 0; i < supported.size(); ++i) {
345 if (d->partialSums.contains(supported.at(i)) && available.contains(supported.at(i))) {
346 type = supported.at(i);
347 checksum = d->partialSums[type];
348 break;
349 }
350 }
351
352 return QPair<QString, PartialChecksums*>(type, checksum);
353 }
354
changeStatus(const QString & type,bool isVerified)355 void Verifier::changeStatus(const QString &type, bool isVerified)
356 {
357 qCDebug(KGET_DEBUG) << "Verified:" << isVerified;
358 d->status = isVerified ? Verifier::Verified : Verifier::NotVerified;
359 d->model->setVerificationStatus(type, d->status);
360 Q_EMIT verified(isVerified);
361 }
362
verify(const QModelIndex & index)363 void Verifier::verify(const QModelIndex &index)
364 {
365 int row = -1;
366 if (index.isValid()) {
367 row = index.row();
368 }
369
370 QString type;
371 QString checksum;
372
373 if (row == -1) {
374 Checksum pair = availableChecksum(static_cast<Verifier::ChecksumStrength>(Settings::checksumStrength()));
375 type = pair.first;
376 checksum = pair.second;
377 } else if ((row >= 0) && (row < d->model->rowCount())) {
378 type = d->model->index(row, VerificationModel::Type).data().toString();
379 checksum = d->model->index(row, VerificationModel::Checksum).data().toString();
380 }
381
382 d->thread.verify(type, checksum, d->dest);
383 }
384
brokenPieces() const385 void Verifier::brokenPieces() const
386 {
387 QPair<QString, PartialChecksums*> pair = availablePartialChecksum(static_cast<Verifier::ChecksumStrength>(Settings::checksumStrength()));
388 QList<QString> checksums;
389 KIO::filesize_t length = 0;
390 if (pair.second) {
391 checksums = pair.second->checksums();
392 length = pair.second->length();
393 }
394 d->thread.findBrokenPieces(pair.first, checksums, length, d->dest);
395 }
396
checksum(const QUrl & dest,const QString & type,bool * abortPtr)397 QString Verifier::checksum(const QUrl &dest, const QString &type, bool *abortPtr)
398 {
399 QStringList supported = supportedVerficationTypes();
400 if (!supported.contains(type))
401 {
402 return QString();
403 }
404
405 QFile file(dest.toString());
406 if (!file.open(QIODevice::ReadOnly))
407 {
408 return QString();
409 }
410
411 if (type == VerifierPrivate::MD5) {
412 QCryptographicHash hash(QCryptographicHash::Md5);
413 hash.addData(&file);
414 QString final = QString(hash.result());
415 file.close();
416 return final;
417 }
418
419
420 #ifdef HAVE_QCA2
421 QCA::Hash hash(type);
422
423 //BEGIN taken from qca_basic.h and slightly adopted to allow abort
424 char buffer[1024];
425 int len;
426
427 while ((len=file.read(reinterpret_cast<char*>(buffer), sizeof(buffer))) > 0)
428 {
429 hash.update(buffer, len);
430 if (abortPtr && *abortPtr)
431 {
432 hash.final();
433 file.close();
434 return QString();
435 }
436 }
437 //END
438
439 QString final = QString(QCA::arrayToHex(hash.final().toByteArray()));
440 file.close();
441 return final;
442 #endif //HAVE_QCA2
443
444 return QString();
445 }
446
partialChecksums(const QUrl & dest,const QString & type,KIO::filesize_t length,bool * abortPtr)447 PartialChecksums Verifier::partialChecksums(const QUrl &dest, const QString &type, KIO::filesize_t length, bool *abortPtr)
448 {
449 QStringList checksums;
450
451 QStringList supported = supportedVerficationTypes();
452 if (!supported.contains(type))
453 {
454 return PartialChecksums();
455 }
456
457 QFile file(dest.toString());
458 if (!file.open(QIODevice::ReadOnly))
459 {
460 return PartialChecksums();
461 }
462
463 const KIO::filesize_t fileSize = file.size();
464 if (!fileSize)
465 {
466 return PartialChecksums();
467 }
468
469 int numPieces = 0;
470
471 //the piece length has been defined
472 if (length)
473 {
474 numPieces = fileSize / length;
475 }
476 else
477 {
478 length = VerifierPrivate::PARTSIZE;
479 numPieces = fileSize / length;
480 if (numPieces > 100)
481 {
482 numPieces = 100;
483 length = fileSize / numPieces;
484 }
485 }
486
487 //there is a rest, so increase numPieces by one
488 if (fileSize % length)
489 {
490 ++numPieces;
491 }
492
493 PartialChecksums partialChecksums;
494
495 //create all the checksums for the pieces
496 for (int i = 0; i < numPieces; ++i)
497 {
498 QString hash = VerifierPrivate::calculatePartialChecksum(&file, type, length * i, length, fileSize, abortPtr);
499 if (hash.isEmpty())
500 {
501 file.close();
502 return PartialChecksums();
503 }
504 checksums.append(hash);
505 }
506
507 partialChecksums.setLength(length);
508 partialChecksums.setChecksums(checksums);
509 file.close();
510 return partialChecksums;
511 }
512
addChecksum(const QString & type,const QString & checksum,int verified)513 void Verifier::addChecksum(const QString &type, const QString &checksum, int verified)
514 {
515 d->model->addChecksum(type, checksum, verified);
516 }
517
addChecksums(const QHash<QString,QString> & checksums)518 void Verifier::addChecksums(const QHash<QString, QString> &checksums)
519 {
520 d->model->addChecksums(checksums);
521 }
522
addPartialChecksums(const QString & type,KIO::filesize_t length,const QStringList & checksums)523 void Verifier::addPartialChecksums(const QString &type, KIO::filesize_t length, const QStringList &checksums)
524 {
525 if (!d->partialSums.contains(type) && length && !checksums.isEmpty())
526 {
527 d->partialSums[type] = new PartialChecksums(length, checksums);
528 }
529 }
530
partialChunkLength() const531 KIO::filesize_t Verifier::partialChunkLength() const
532 {
533 QStringList::const_iterator it;
534 QStringList::const_iterator itEnd = VerifierPrivate::SUPPORTED.constEnd();
535 for (it = VerifierPrivate::SUPPORTED.constBegin(); it != itEnd; ++it)
536 {
537 if (d->partialSums.contains(*it))
538 {
539 return d->partialSums[*it]->length();
540 }
541 }
542
543 return 0;
544 }
545
save(const QDomElement & element)546 void Verifier::save(const QDomElement &element)
547 {
548 QDomElement e = element;
549 e.setAttribute("verificationStatus", d->status);
550
551 QDomElement verification = e.ownerDocument().createElement("verification");
552 for (int i = 0; i < d->model->rowCount(); ++i)
553 {
554 QDomElement hash = e.ownerDocument().createElement("hash");
555 hash.setAttribute("type", d->model->index(i, VerificationModel::Type).data().toString());
556 hash.setAttribute("verified", d->model->index(i, VerificationModel::Verified).data(Qt::EditRole).toInt());
557 QDomText value = e.ownerDocument().createTextNode(d->model->index(i, VerificationModel::Checksum).data().toString());
558 hash.appendChild(value);
559 verification.appendChild(hash);
560 }
561
562 QHash<QString, PartialChecksums*>::const_iterator it;
563 QHash<QString, PartialChecksums*>::const_iterator itEnd = d->partialSums.constEnd();
564 for (it = d->partialSums.constBegin(); it != itEnd; ++it)
565 {
566 QDomElement pieces = e.ownerDocument().createElement("pieces");
567 pieces.setAttribute("type", it.key());
568 pieces.setAttribute("length", (*it)->length());
569 QList<QString> checksums = (*it)->checksums();
570 for (int i = 0; i < checksums.size(); ++i)
571 {
572 QDomElement hash = e.ownerDocument().createElement("hash");
573 hash.setAttribute("piece", i);
574 QDomText value = e.ownerDocument().createTextNode(checksums[i]);
575 hash.appendChild(value);
576 pieces.appendChild(hash);
577 }
578 verification.appendChild(pieces);
579 }
580 e.appendChild(verification);
581 }
582
load(const QDomElement & e)583 void Verifier::load(const QDomElement &e)
584 {
585 if (e.hasAttribute("verificationStatus"))
586 {
587 const int status = e.attribute("verificationStatus").toInt();
588 switch (status)
589 {
590 case NoResult:
591 d->status = NoResult;
592 break;
593 case NotVerified:
594 d->status = NotVerified;
595 break;
596 case Verified:
597 d->status = Verified;
598 break;
599 default:
600 d->status = NotVerified;
601 break;
602 }
603 }
604
605 QDomElement verification = e.firstChildElement("verification");
606 QDomNodeList const hashList = verification.elementsByTagName("hash");
607
608 for (int i = 0; i < hashList.length(); ++i)
609 {
610 const QDomElement hash = hashList.item(i).toElement();
611 const QString value = hash.text();
612 const QString type = hash.attribute("type");
613 const int verificationStatus = hash.attribute("verified").toInt();
614 if (!type.isEmpty() && !value.isEmpty())
615 {
616 d->model->addChecksum(type, value, verificationStatus);
617 }
618 }
619
620 QDomNodeList const piecesList = verification.elementsByTagName("pieces");
621
622 for (int i = 0; i < piecesList.length(); ++i)
623 {
624 QDomElement pieces = piecesList.at(i).toElement();
625
626 const QString type = pieces.attribute("type");
627 const KIO::filesize_t length = pieces.attribute("length").toULongLong();
628 QStringList partialChecksums;
629
630 const QDomNodeList partialHashList = pieces.elementsByTagName("hash");
631 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 additional check, do that check in addPartialChecksums?!
632 {
633 const QString hash = partialHashList.at(j).toElement().text();
634 if (hash.isEmpty())
635 {
636 break;
637 }
638 partialChecksums.append(hash);
639 }
640
641 addPartialChecksums(type, length, partialChecksums);
642 }
643 }
644
645
646