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"