File indexing completed on 2024-04-28 04:57:32

0001 /* This file is part of the KDE project
0002 
0003    Copyright (C) 2004 Dario Massarin <nekkar@libero.it>
0004    Copyright (C) 2007 Manolo Valdes <nolis71cu@gmail.com>
0005    Copyright (C) 2009 Matthias Fuchs <mat69@gmx.net>
0006    Copyright (C) 2012 Aish Raj Dahal <dahalaishraj@gmail.com>
0007 
0008    This program is free software; you can redistribute it and/or
0009    modify it under the terms of the GNU General Public
0010    License as published by the Free Software Foundation; either
0011    version 2 of the License, or (at your option) any later version.
0012 */
0013 
0014 #include "metalinkhttp.h"
0015 #include "metalinksettings.h"
0016 #include "metalinkxml.h"
0017 
0018 #include "core/download.h"
0019 #include "core/filemodel.h"
0020 #include "core/kget.h"
0021 #include "core/signature.h"
0022 #include "core/transferdatasource.h"
0023 #include "core/transfergroup.h"
0024 #include "core/urlchecker.h"
0025 #include "core/verifier.h"
0026 
0027 #include "kget_debug.h"
0028 
0029 #include <KIO/DeleteJob>
0030 #include <KIO/RenameDialog>
0031 #include <KLocalizedString>
0032 #include <KMessageBox>
0033 #include <QDialog>
0034 
0035 #include <KConfigGroup>
0036 #include <QDir>
0037 #include <QDomElement>
0038 #include <QFile>
0039 #include <QStandardPaths>
0040 
0041 /**
0042  * @return Hex value from a base64 value
0043  * @note needed for hex based signature verification
0044  */
0045 QString base64ToHex(const QString &b64)
0046 {
0047     return QString(QByteArray::fromBase64(b64.toLatin1()).toHex());
0048 }
0049 
0050 MetalinkHttp::MetalinkHttp(TransferGroup *parent,
0051                            TransferFactory *factory,
0052                            Scheduler *scheduler,
0053                            const QUrl &source,
0054                            const QUrl &dest,
0055                            KGetMetalink::MetalinkHttpParser *httpParser,
0056                            const QDomElement *e)
0057     : AbstractMetalink(parent, factory, scheduler, source, dest, e)
0058     , m_signatureUrl(QUrl())
0059     , m_httpparser(httpParser)
0060 
0061 {
0062     m_httpparser->setParent(this);
0063 }
0064 
0065 MetalinkHttp::~MetalinkHttp()
0066 {
0067 }
0068 
0069 void MetalinkHttp::load(const QDomElement *element)
0070 {
0071     qCDebug(KGET_DEBUG);
0072     Transfer::load(element);
0073     auto *fac = new DataSourceFactory(this, m_dest);
0074     m_dataSourceFactory.insert(m_dest, fac);
0075 
0076     connect(fac, &DataSourceFactory::capabilitiesChanged, this, &MetalinkHttp::slotUpdateCapabilities);
0077     connect(fac, &DataSourceFactory::dataSourceFactoryChange, this, &MetalinkHttp::slotDataSourceFactoryChange);
0078     connect(fac->verifier(), &Verifier::verified, this, &MetalinkHttp::slotVerified);
0079     connect(fac->signature(), SIGNAL(verified(int)), this, SLOT(slotSignatureVerified()));
0080     connect(fac, &DataSourceFactory::log, this, &Transfer::setLog);
0081 
0082     fac->load(element);
0083 
0084     if (fac->mirrors().isEmpty()) {
0085         return;
0086     }
0087 
0088     m_ready = true;
0089 }
0090 
0091 void MetalinkHttp::save(const QDomElement &element)
0092 {
0093     qCDebug(KGET_DEBUG);
0094     Transfer::save(element);
0095     m_dataSourceFactory.begin().value()->save(element);
0096 }
0097 
0098 void MetalinkHttp::startMetalink()
0099 {
0100     if (m_ready) {
0101         foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0102             // specified number of files is downloaded simultaneously
0103             if (m_currentFiles < MetalinkSettings::simultaneousFiles()) {
0104                 const Job::Status status = factory->status();
0105 
0106                 // only start factories that should be downloaded
0107                 if (factory->doDownload() && (status != Job::Finished) && (status != Job::FinishedKeepAlive) && (status != Job::Running)) {
0108                     ++m_currentFiles;
0109                     factory->start();
0110                 }
0111             } else {
0112                 break;
0113             }
0114         }
0115     }
0116 }
0117 
0118 void MetalinkHttp::start()
0119 {
0120     qDebug() << "metalinkhttp::start";
0121 
0122     if (!m_ready) {
0123         setLinks();
0124         setDigests();
0125         if (metalinkHttpInit()) {
0126             startMetalink();
0127         }
0128     } else {
0129         startMetalink();
0130     }
0131 }
0132 
0133 void MetalinkHttp::setSignature(QUrl &dest, QByteArray &data, DataSourceFactory *dataFactory)
0134 {
0135     Q_UNUSED(dest);
0136     dataFactory->signature()->setSignature(data, Signature::AsciiDetached);
0137 }
0138 
0139 void MetalinkHttp::slotSignatureVerified()
0140 {
0141     if (status() == Job::Finished) {
0142         // see if some files are NotVerified
0143         QStringList brokenFiles;
0144         foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0145             if (m_fileModel) {
0146                 QModelIndex signatureVerified = m_fileModel->index(factory->dest(), FileItem::SignatureVerified);
0147                 m_fileModel->setData(signatureVerified, factory->signature()->status());
0148             }
0149             if (factory->doDownload() && (factory->verifier()->status() == Verifier::NotVerified)) {
0150                 brokenFiles.append(factory->dest().toString());
0151             }
0152         }
0153 
0154         if (brokenFiles.count()) {
0155             if (KMessageBox::warningTwoActionsList(nullptr,
0156                                                    i18n("The download could not be verified, try to repair it?"),
0157                                                    brokenFiles,
0158                                                    QString(),
0159                                                    KGuiItem(i18nc("@action:button", "Repair")),
0160                                                    KGuiItem(i18nc("@action:button", "Ignore"), QStringLiteral("dialog-cancel")))
0161                 == KMessageBox::PrimaryAction) {
0162                 if (repair()) {
0163                     KGet::addTransfer(m_metalinkxmlUrl);
0164                     // TODO Use a Notification instead. Check kget.h for how to use it.
0165                 }
0166             }
0167         }
0168     }
0169 }
0170 
0171 bool MetalinkHttp::metalinkHttpInit()
0172 {
0173     qDebug() << "m_dest = " << m_dest;
0174     const QUrl tempDest = QUrl(m_dest.adjusted(QUrl::RemoveFilename));
0175     QUrl dest = QUrl(tempDest.toString() + m_dest.fileName());
0176     qDebug() << "dest = " << dest;
0177 
0178     // sort the urls according to their priority (highest first)
0179     std::stable_sort(m_linkheaderList.begin(), m_linkheaderList.end());
0180 
0181     auto *dataFactory = new DataSourceFactory(this, dest);
0182     dataFactory->setMaxMirrorsUsed(MetalinkSettings::mirrorsPerFile());
0183 
0184     connect(dataFactory, &DataSourceFactory::capabilitiesChanged, this, &MetalinkHttp::slotUpdateCapabilities);
0185     connect(dataFactory, &DataSourceFactory::dataSourceFactoryChange, this, &MetalinkHttp::slotDataSourceFactoryChange);
0186     connect(dataFactory->verifier(), &Verifier::verified, this, &MetalinkHttp::slotVerified);
0187     connect(dataFactory->signature(), SIGNAL(verified(int)), this, SLOT(slotSignatureVerified()));
0188     connect(dataFactory, &DataSourceFactory::log, this, &Transfer::setLog);
0189 
0190     // add the Mirrors Sources
0191 
0192     for (int i = 0; i < m_linkheaderList.size(); ++i) {
0193         const QUrl url = m_linkheaderList[i].url;
0194         if (url.isValid()) {
0195             if (m_linkheaderList[i].pref) {
0196                 qDebug() << "found etag in a mirror";
0197                 auto *eTagCher = new KGetMetalink::MetalinkHttpParser(url);
0198                 if (eTagCher->getEtag() != m_httpparser->getEtag()) { // There is an ETag mismatch
0199                     continue;
0200                 }
0201             }
0202 
0203             dataFactory->addMirror(url, MetalinkSettings::connectionsPerUrl());
0204         }
0205     }
0206 
0207     // no datasource has been created, so remove the datasource factory
0208     if (dataFactory->mirrors().isEmpty()) {
0209         qDebug() << "data source factory being deleted";
0210         delete dataFactory;
0211     } else {
0212         QMultiHashIterator<QString, QString> itr(m_DigestList);
0213         while (itr.hasNext()) {
0214             itr.next();
0215             qDebug() << itr.key() << ":" << itr.value();
0216         }
0217 
0218         dataFactory->verifier()->addChecksums(m_DigestList);
0219 
0220         // Add OpenPGP signatures
0221         if (m_signatureUrl != QUrl()) {
0222             // make sure that the DataLocation directory exists (earlier this used to be handled by KStandardDirs)
0223             if (!QFileInfo::exists(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))) {
0224                 QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
0225             }
0226             const QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/metalinks/") + m_source.fileName();
0227             auto *signat_download = new Download(m_signatureUrl, QUrl::fromLocalFile(path));
0228             connect(signat_download, SIGNAL(finishedSuccessfully(QUrl, QByteArray)), SLOT(setSignature(QUrl, QByteArray)));
0229         }
0230         m_dataSourceFactory[dataFactory->dest()] = dataFactory;
0231     }
0232 
0233     if (m_dataSourceFactory.size()) {
0234         m_dest = dest;
0235     }
0236 
0237     if (!m_dataSourceFactory.size()) {
0238         // TODO make this via log in the future + do not display the KMessageBox
0239         qCWarning(KGET_DEBUG) << "Download of" << m_source << "failed, no working URLs were found.";
0240         KMessageBox::error(nullptr, i18n("Download failed, no working URLs were found."), i18n("Error"));
0241         setStatus(Job::Aborted);
0242         setTransferChange(Tc_Status, true);
0243         return false;
0244     }
0245 
0246     m_ready = !m_dataSourceFactory.isEmpty();
0247     slotUpdateCapabilities();
0248 
0249     return true;
0250 }
0251 
0252 void MetalinkHttp::setLinks()
0253 {
0254     const QMultiMap<QString, QString> *headerInf = m_httpparser->getHeaderInfo();
0255     const QList<QString> linkVals = headerInf->values("link");
0256 
0257     foreach (const QString link, linkVals) {
0258         const KGetMetalink::HttpLinkHeader linkheader(link);
0259 
0260         if (linkheader.reltype == "duplicate") {
0261             m_linkheaderList.append(linkheader);
0262         } else if (linkheader.reltype == "application/pgp-signature") {
0263             m_signatureUrl = linkheader.url; // There will only be one signature
0264         } else if (linkheader.reltype == "application/metalink4+xml") {
0265             m_metalinkxmlUrl = linkheader.url; // There will only be one metalink xml (metainfo URL)
0266         }
0267     }
0268 }
0269 
0270 void MetalinkHttp::deinit(Transfer::DeleteOptions options)
0271 {
0272     foreach (DataSourceFactory *factory, m_dataSourceFactory) {
0273         if (options & Transfer::DeleteFiles) {
0274             factory->deinit();
0275         }
0276     }
0277 }
0278 
0279 void MetalinkHttp::setDigests()
0280 {
0281     const QMultiMap<QString, QString> *digestInfo = m_httpparser->getHeaderInfo();
0282     const QList<QString> digestList = digestInfo->values("digest");
0283 
0284     foreach (const QString digest, digestList) {
0285         const int eqDelimiter = digest.indexOf('=');
0286         const QString digestType = MetalinkHttp::adaptDigestType(digest.left(eqDelimiter).trimmed());
0287         const QString hexDigestValue = base64ToHex(digest.mid(eqDelimiter + 1).trimmed());
0288 
0289         m_DigestList.insert(digestType, hexDigestValue);
0290     }
0291 }
0292 
0293 QString MetalinkHttp::adaptDigestType(const QString &hashType)
0294 {
0295     if (hashType == QString("SHA")) {
0296         return QString("sha");
0297     } else if (hashType == QString("MD5")) {
0298         return QString("md5");
0299     } else if (hashType == QString("SHA-256")) {
0300         return QString("sha256");
0301     } else {
0302         return hashType;
0303     }
0304 }
0305 
0306 #include "moc_metalinkhttp.cpp"