File indexing completed on 2023-12-03 08:26:48

0001 /***************************************************************************
0002  *   Copyright (C) 2009 Matthias Fuchs <mat69@gmx.net>                     *
0003  *   Copyright (C) 2012 Aish Raj Dahal <dahalaishraj@gmail.com>            *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program; if not, write to the                         *
0017  *   Free Software Foundation, Inc.,                                       *
0018  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
0019  ***************************************************************************/
0020 
0021 #include "metalinker.h"
0022 
0023 #include <QFile>
0024 #include <QTextStream>
0025 #include <QTimeZone>
0026 
0027 #include <KLocalizedString>
0028 
0029 #include "kget_debug.h"
0030 #include "kget_version.h"
0031 
0032 const QString KGetMetalink::Metalink::KGET_DESCRIPTION = QStringLiteral("KGet/" KGET_VERSION_STRING);
0033 const uint KGetMetalink::Metalink::MAX_URL_PRIORITY = 999999;
0034 const uint KGetMetalink::Metalink_v3::MAX_PREFERENCE = 100; // as defined in Metalink specification 3.0 2nd edition
0035 
0036 namespace KGetMetalink
0037 {
0038 QString addaptHashType(const QString &type, bool loaded);
0039 }
0040 
0041 /**
0042  * Adapts type to the way the hash is internally stored
0043  * @param type the hash-type
0044  * @param loaded @c true if the hash has been loaded, false if it should be saved
0045  * @note metalink wants sha1 in the form "sha-1", though
0046  * the metalinker uses it internally in the form "sha1", this function
0047  * transforms it to the correct form, it is only needed internally
0048  */
0049 QString KGetMetalink::addaptHashType(const QString &type, bool loaded)
0050 {
0051     QString t = type;
0052     if (loaded) {
0053         t.replace("sha-", "sha");
0054     } else {
0055         t.replace("sha", "sha-");
0056     }
0057 
0058     return t;
0059 }
0060 
0061 void KGetMetalink::DateConstruct::setData(const QDateTime &dateT, const QTime &timeZoneOff, bool negOff)
0062 {
0063     dateTime = dateT;
0064     timeZoneOffset = timeZoneOff;
0065     negativeOffset = negOff;
0066 }
0067 
0068 void KGetMetalink::DateConstruct::setData(const QString &dateConstruct)
0069 {
0070     if (dateConstruct.isEmpty()) {
0071         return;
0072     }
0073 
0074     const QString exp = "yyyy-MM-ddThh:mm:ss";
0075     const int length = exp.length();
0076 
0077     dateTime = QDateTime::fromString(dateConstruct.left(length), exp);
0078     if (dateTime.isValid()) {
0079         int index = dateConstruct.indexOf('+', length - 1);
0080         if (index > -1) {
0081             timeZoneOffset = QTime::fromString(dateConstruct.mid(index + 1), "hh:mm");
0082         } else {
0083             index = dateConstruct.indexOf('-', length - 1);
0084             if (index > -1) {
0085                 negativeOffset = true;
0086                 timeZoneOffset = QTime::fromString(dateConstruct.mid(index + 1), "hh:mm");
0087             }
0088         }
0089     }
0090 }
0091 
0092 bool KGetMetalink::DateConstruct::isNull() const
0093 {
0094     return dateTime.isNull();
0095 }
0096 
0097 bool KGetMetalink::DateConstruct::isValid() const
0098 {
0099     return dateTime.isValid();
0100 }
0101 
0102 QString KGetMetalink::DateConstruct::toString() const
0103 {
0104     QString string;
0105 
0106     if (dateTime.isValid()) {
0107         string += dateTime.toString(Qt::ISODate);
0108     }
0109 
0110     if (timeZoneOffset.isValid()) {
0111         string += (negativeOffset ? '-' : '+');
0112         string += timeZoneOffset.toString("hh:mm");
0113     } else if (!string.isEmpty()) {
0114         string += 'Z';
0115     }
0116 
0117     return string;
0118 }
0119 
0120 void KGetMetalink::DateConstruct::clear()
0121 {
0122     dateTime = QDateTime();
0123     timeZoneOffset = QTime();
0124 }
0125 
0126 void KGetMetalink::UrlText::clear()
0127 {
0128     name.clear();
0129     url.clear();
0130 }
0131 
0132 void KGetMetalink::CommonData::load(const QDomElement &e)
0133 {
0134     identity = e.firstChildElement("identity").text();
0135     version = e.firstChildElement("version").text();
0136     description = e.firstChildElement("description").text();
0137     logo = QUrl(e.firstChildElement("logo").text());
0138     copyright = e.firstChildElement("copyright").text();
0139 
0140     const QDomElement publisherElem = e.firstChildElement("publisher");
0141     publisher.name = publisherElem.attribute("name");
0142     publisher.url = QUrl(publisherElem.attribute("url"));
0143 
0144     for (QDomElement elemRes = e.firstChildElement("language"); !elemRes.isNull(); elemRes = elemRes.nextSiblingElement("language")) {
0145         languages << elemRes.text();
0146     }
0147 
0148     for (QDomElement elemRes = e.firstChildElement("os"); !elemRes.isNull(); elemRes = elemRes.nextSiblingElement("os")) {
0149         oses << elemRes.text();
0150     }
0151 }
0152 
0153 void KGetMetalink::CommonData::save(QDomElement &e) const
0154 {
0155     QDomDocument doc = e.ownerDocument();
0156 
0157     if (!copyright.isEmpty()) {
0158         QDomElement elem = doc.createElement("copyright");
0159         QDomText text = doc.createTextNode(copyright);
0160         elem.appendChild(text);
0161         e.appendChild(elem);
0162     }
0163     if (!description.isEmpty()) {
0164         QDomElement elem = doc.createElement("description");
0165         QDomText text = doc.createTextNode(description);
0166         elem.appendChild(text);
0167         e.appendChild(elem);
0168     }
0169     if (!identity.isEmpty()) {
0170         QDomElement elem = doc.createElement("identity");
0171         QDomText text = doc.createTextNode(identity);
0172         elem.appendChild(text);
0173         e.appendChild(elem);
0174     }
0175     if (!logo.isEmpty()) {
0176         QDomElement elem = doc.createElement("logo");
0177         QDomText text = doc.createTextNode(logo.url());
0178         elem.appendChild(text);
0179         e.appendChild(elem);
0180     }
0181     if (!publisher.isEmpty()) {
0182         QDomElement elem = doc.createElement("publisher");
0183         elem.setAttribute("url", publisher.url.url());
0184         elem.setAttribute("name", publisher.name);
0185 
0186         e.appendChild(elem);
0187     }
0188     if (!version.isEmpty()) {
0189         QDomElement elem = doc.createElement("version");
0190         QDomText text = doc.createTextNode(version);
0191         elem.appendChild(text);
0192         e.appendChild(elem);
0193     }
0194 
0195     foreach (const QString &language, languages) {
0196         QDomElement elem = doc.createElement("language");
0197         QDomText text = doc.createTextNode(language);
0198         elem.appendChild(text);
0199         e.appendChild(elem);
0200     }
0201 
0202     foreach (const QString &os, oses) {
0203         QDomElement elem = doc.createElement("os");
0204         QDomText text = doc.createTextNode(os);
0205         elem.appendChild(text);
0206         e.appendChild(elem);
0207     }
0208 }
0209 
0210 void KGetMetalink::CommonData::clear()
0211 {
0212     identity.clear();
0213     version.clear();
0214     description.clear();
0215     oses.clear();
0216     logo.clear();
0217     languages.clear();
0218     publisher.clear();
0219     copyright.clear();
0220 }
0221 
0222 bool KGetMetalink::Metaurl::operator<(const KGetMetalink::Metaurl &other) const
0223 {
0224     return (this->priority > other.priority) || (this->priority == 0);
0225 }
0226 
0227 void KGetMetalink::Metaurl::load(const QDomElement &e)
0228 {
0229     type = e.attribute("mediatype").toLower();
0230     priority = e.attribute("priority").toUInt();
0231     if (priority > Metalink::MAX_URL_PRIORITY) {
0232         priority = Metalink::MAX_URL_PRIORITY;
0233     }
0234     name = e.attribute("name");
0235     url = QUrl(e.text());
0236 }
0237 
0238 void KGetMetalink::Metaurl::save(QDomElement &e) const
0239 {
0240     QDomDocument doc = e.ownerDocument();
0241     QDomElement metaurl = doc.createElement("metaurl");
0242     if (priority) {
0243         metaurl.setAttribute("priority", priority);
0244     }
0245     if (!name.isEmpty()) {
0246         metaurl.setAttribute("name", name);
0247     }
0248     metaurl.setAttribute("mediatype", type);
0249 
0250     QDomText text = doc.createTextNode(url.url());
0251     metaurl.appendChild(text);
0252 
0253     e.appendChild(metaurl);
0254 }
0255 
0256 bool KGetMetalink::Metaurl::isValid()
0257 {
0258     return url.isValid() && !url.host().isEmpty() && !url.scheme().isEmpty() && !type.isEmpty();
0259 }
0260 
0261 void KGetMetalink::Metaurl::clear()
0262 {
0263     type.clear();
0264     priority = 0;
0265     name.clear();
0266     url.clear();
0267 }
0268 
0269 bool KGetMetalink::Url::operator<(const KGetMetalink::Url &other) const
0270 {
0271     bool smaller = (this->priority > other.priority) || ((this->priority == 0) && (other.priority != 0));
0272 
0273     if (!smaller && (this->priority == other.priority)) {
0274         QString countryCode; // = KLocale::global()->country();//TODO: Port
0275         if (!countryCode.isEmpty()) {
0276             smaller = (other.location.toLower() == countryCode.toLower());
0277         }
0278     }
0279     return smaller;
0280 }
0281 
0282 void KGetMetalink::Url::load(const QDomElement &e)
0283 {
0284     location = e.attribute("location").toLower();
0285     priority = e.attribute("priority").toUInt();
0286     if (priority > Metalink::MAX_URL_PRIORITY) {
0287         priority = Metalink::MAX_URL_PRIORITY;
0288     }
0289     url = QUrl(e.text());
0290 }
0291 
0292 void KGetMetalink::Url::save(QDomElement &e) const
0293 {
0294     QDomDocument doc = e.ownerDocument();
0295     QDomElement elem = doc.createElement("url");
0296     if (priority) {
0297         elem.setAttribute("priority", priority);
0298     }
0299     if (!location.isEmpty()) {
0300         elem.setAttribute("location", location);
0301     }
0302 
0303     QDomText text = doc.createTextNode(url.url());
0304     elem.appendChild(text);
0305 
0306     e.appendChild(elem);
0307 }
0308 
0309 bool KGetMetalink::Url::isValid()
0310 {
0311     return url.isValid() && !url.host().isEmpty() && !url.scheme().isEmpty();
0312 }
0313 
0314 void KGetMetalink::Url::clear()
0315 {
0316     priority = 0;
0317     location.clear();
0318     url.clear();
0319 }
0320 
0321 void KGetMetalink::Resources::load(const QDomElement &e)
0322 {
0323     for (QDomElement elem = e.firstChildElement("url"); !elem.isNull(); elem = elem.nextSiblingElement("url")) {
0324         Url url;
0325         url.load(elem);
0326         if (url.isValid()) {
0327             urls.append(url);
0328         }
0329     }
0330 
0331     for (QDomElement elem = e.firstChildElement("metaurl"); !elem.isNull(); elem = elem.nextSiblingElement("metaurl")) {
0332         Metaurl metaurl;
0333         metaurl.load(elem);
0334         if (metaurl.isValid()) {
0335             metaurls.append(metaurl);
0336         }
0337     }
0338 }
0339 
0340 void KGetMetalink::Resources::save(QDomElement &e) const
0341 {
0342     foreach (const Metaurl &metaurl, metaurls) {
0343         metaurl.save(e);
0344     }
0345 
0346     foreach (const Url &url, urls) {
0347         url.save(e);
0348     }
0349 }
0350 
0351 void KGetMetalink::Resources::clear()
0352 {
0353     urls.clear();
0354     metaurls.clear();
0355 }
0356 
0357 void KGetMetalink::Pieces::load(const QDomElement &e)
0358 {
0359     type = addaptHashType(e.attribute("type"), true);
0360     length = e.attribute("length").toULongLong();
0361 
0362     QDomNodeList hashesList = e.elementsByTagName("hash");
0363 
0364     for (int i = 0; i < hashesList.count(); ++i) {
0365         QDomElement element = hashesList.at(i).toElement();
0366         hashes.append(element.text());
0367     }
0368 }
0369 
0370 void KGetMetalink::Pieces::save(QDomElement &e) const
0371 {
0372     QDomDocument doc = e.ownerDocument();
0373     QDomElement pieces = doc.createElement("pieces");
0374     pieces.setAttribute("type", addaptHashType(type, false));
0375     pieces.setAttribute("length", length);
0376 
0377     for (int i = 0; i < hashes.size(); ++i) {
0378         QDomElement hash = doc.createElement("hash");
0379         QDomText text = doc.createTextNode(hashes.at(i));
0380         hash.appendChild(text);
0381         pieces.appendChild(hash);
0382     }
0383 
0384     e.appendChild(pieces);
0385 }
0386 
0387 void KGetMetalink::Pieces::clear()
0388 {
0389     type.clear();
0390     length = 0;
0391     hashes.clear();
0392 }
0393 
0394 void KGetMetalink::Verification::load(const QDomElement &e)
0395 {
0396     for (QDomElement elem = e.firstChildElement("hash"); !elem.isNull(); elem = elem.nextSiblingElement("hash")) {
0397         QString type = elem.attribute("type");
0398         const QString hash = elem.text();
0399         if (!type.isEmpty() && !hash.isEmpty()) {
0400             type = addaptHashType(type, true);
0401             hashes[type] = hash;
0402         }
0403     }
0404 
0405     for (QDomElement elem = e.firstChildElement("pieces"); !elem.isNull(); elem = elem.nextSiblingElement("pieces")) {
0406         Pieces piecesItem;
0407         piecesItem.load(elem);
0408         pieces.append(piecesItem);
0409     }
0410 
0411     for (QDomElement elem = e.firstChildElement("signature"); !elem.isNull(); elem = elem.nextSiblingElement("signature")) {
0412         QString type = elem.attribute("mediatype");
0413         if (type == "application/pgp-signature") { // FIXME with 4.5 make it handle signatures by default with mime-type
0414             type = "pgp";
0415         }
0416         const QString signature = elem.text();
0417         if (!type.isEmpty() && !signature.isEmpty()) {
0418             signatures[type] = signature;
0419         }
0420     }
0421 }
0422 
0423 void KGetMetalink::Verification::save(QDomElement &e) const
0424 {
0425     QDomDocument doc = e.ownerDocument();
0426 
0427     QHash<QString, QString>::const_iterator it;
0428     QHash<QString, QString>::const_iterator itEnd = hashes.constEnd();
0429     for (it = hashes.constBegin(); it != itEnd; ++it) {
0430         QDomElement hash = doc.createElement("hash");
0431         hash.setAttribute("type", addaptHashType(it.key(), false));
0432         QDomText text = doc.createTextNode(it.value());
0433         hash.appendChild(text);
0434         e.appendChild(hash);
0435     }
0436 
0437     foreach (const Pieces &item, pieces) {
0438         item.save(e);
0439     }
0440 
0441     itEnd = signatures.constEnd();
0442     for (it = signatures.constBegin(); it != itEnd; ++it) {
0443         QString type = it.key();
0444         if (type == "pgp") { // FIXME with 4.5 make it handle signatures by default with mime-type
0445             type = "application/pgp-signature";
0446         }
0447         QDomElement hash = doc.createElement("signature");
0448         hash.setAttribute("mediatype", type);
0449         QDomText text = doc.createTextNode(it.value());
0450         hash.appendChild(text);
0451         e.appendChild(hash);
0452     }
0453 }
0454 
0455 void KGetMetalink::Verification::clear()
0456 {
0457     hashes.clear();
0458     pieces.clear();
0459 }
0460 
0461 bool KGetMetalink::File::isValid() const
0462 {
0463     return isValidNameAttribute() && resources.isValid();
0464 }
0465 
0466 void KGetMetalink::File::load(const QDomElement &e)
0467 {
0468     data.load(e);
0469 
0470     name = QUrl::fromPercentEncoding(e.attribute("name").toLatin1());
0471     size = e.firstChildElement("size").text().toULongLong();
0472 
0473     verification.load(e);
0474     resources.load(e);
0475 }
0476 
0477 void KGetMetalink::File::save(QDomElement &e) const
0478 {
0479     if (isValid()) {
0480         QDomDocument doc = e.ownerDocument();
0481         QDomElement file = doc.createElement("file");
0482         file.setAttribute("name", name);
0483 
0484         if (size) {
0485             QDomElement elem = doc.createElement("size");
0486             QDomText text = doc.createTextNode(QString::number(size));
0487             elem.appendChild(text);
0488             file.appendChild(elem);
0489         }
0490 
0491         data.save(file);
0492         resources.save(file);
0493         verification.save(file);
0494 
0495         e.appendChild(file);
0496     }
0497 }
0498 
0499 void KGetMetalink::File::clear()
0500 {
0501     name.clear();
0502     verification.clear();
0503     size = 0;
0504     data.clear();
0505     resources.clear();
0506 }
0507 
0508 bool KGetMetalink::File::isValidNameAttribute() const
0509 {
0510     if (name.isEmpty()) {
0511         qCCritical(KGET_DEBUG) << "Name attribute of Metalink::File is empty.";
0512         return false;
0513     }
0514 
0515     if (name.endsWith('/')) {
0516         qCCritical(KGET_DEBUG) << "Name attribute of Metalink::File does not contain a file name:" << name;
0517         return false;
0518     }
0519 
0520     const QStringList components = name.split('/');
0521     if (name.startsWith('/') || components.contains("..") || components.contains(".")) {
0522         qCCritical(KGET_DEBUG) << "Name attribute of Metalink::File contains directory traversal directives:" << name;
0523         return false;
0524     }
0525 
0526     return true;
0527 }
0528 
0529 bool KGetMetalink::Files::isValid() const
0530 {
0531     if (files.isEmpty()) {
0532         return false;
0533     }
0534 
0535     QStringList fileNames;
0536     foreach (const File &file, files) {
0537         fileNames << file.name;
0538         if (!file.isValid()) {
0539             return false;
0540         }
0541     }
0542 
0543     // The value of name must be unique for each file
0544     while (!fileNames.isEmpty()) {
0545         const QString fileName = fileNames.takeFirst();
0546         if (fileNames.contains(fileName)) {
0547             qCCritical(KGET_DEBUG) << "Metalink::File name" << fileName << "exists multiple times.";
0548             return false;
0549         }
0550     }
0551 
0552     return true;
0553 }
0554 
0555 void KGetMetalink::Files::load(const QDomElement &e)
0556 {
0557     for (QDomElement elem = e.firstChildElement("file"); !elem.isNull(); elem = elem.nextSiblingElement("file")) {
0558         File file;
0559         file.load(elem);
0560         files.append(file);
0561     }
0562 }
0563 
0564 void KGetMetalink::Files::save(QDomElement &e) const
0565 {
0566     if (e.isNull()) {
0567         return;
0568     }
0569 
0570     foreach (const File &file, files) {
0571         file.save(e);
0572     }
0573 }
0574 
0575 void KGetMetalink::Files::clear()
0576 {
0577     files.clear();
0578 }
0579 
0580 bool KGetMetalink::Metalink::isValid() const
0581 {
0582     return files.isValid();
0583 }
0584 
0585 void KGetMetalink::Metalink::load(const QDomElement &e)
0586 {
0587     QDomDocument doc = e.ownerDocument();
0588     const QDomElement metalink = doc.firstChildElement("metalink");
0589 
0590     xmlns = metalink.attribute("xmlns");
0591     generator = metalink.firstChildElement("generator").text();
0592     updated.setData(metalink.firstChildElement("updated").text());
0593     published.setData(metalink.firstChildElement("published").text());
0594     updated.setData(metalink.firstChildElement("updated").text());
0595     const QDomElement originElem = metalink.firstChildElement("origin");
0596     origin = QUrl(metalink.firstChildElement("origin").text());
0597     if (originElem.hasAttribute("dynamic")) {
0598         bool worked = false;
0599         dynamic = originElem.attribute("dynamic").toInt(&worked);
0600         if (!worked) {
0601             dynamic = (originElem.attribute("dynamic") == "true");
0602         }
0603     }
0604 
0605     files.load(e);
0606 }
0607 
0608 QDomDocument KGetMetalink::Metalink::save() const
0609 {
0610     QDomDocument doc;
0611     QDomProcessingInstruction header = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"");
0612     doc.appendChild(header);
0613 
0614     QDomElement metalink = doc.createElement("metalink");
0615     metalink.setAttribute(
0616         "xmlns",
0617         "urn:ietf:params:xml:ns:metalink"); // the xmlns value is ignored, instead the data format described in the specification is always used
0618 
0619     QDomElement elem = doc.createElement("generator");
0620     QDomText text = doc.createTextNode(Metalink::KGET_DESCRIPTION); // the set generator is ignored, instead when saving KGET is always used
0621     elem.appendChild(text);
0622     metalink.appendChild(elem);
0623 
0624     if (!origin.isEmpty()) {
0625         QDomElement elem = doc.createElement("origin");
0626         QDomText text = doc.createTextNode(origin.url());
0627         elem.appendChild(text);
0628         if (dynamic) {
0629             elem.setAttribute("dynamic", "true");
0630         }
0631         metalink.appendChild(elem);
0632     }
0633     if (published.isValid()) {
0634         QDomElement elem = doc.createElement("published");
0635         QDomText text = doc.createTextNode(published.toString());
0636         elem.appendChild(text);
0637         metalink.appendChild(elem);
0638     }
0639     if (updated.isValid()) {
0640         QDomElement elem = doc.createElement("updated");
0641         QDomText text = doc.createTextNode(updated.toString());
0642         elem.appendChild(text);
0643         metalink.appendChild(elem);
0644     }
0645 
0646     files.save(metalink);
0647 
0648     doc.appendChild(metalink);
0649 
0650     return doc;
0651 }
0652 
0653 void KGetMetalink::Metalink::clear()
0654 {
0655     dynamic = false;
0656     xmlns.clear();
0657     published.clear();
0658     origin.clear();
0659     generator.clear();
0660     updated.clear();
0661     files.clear();
0662 }
0663 
0664 KGetMetalink::Metalink_v3::Metalink_v3()
0665 {
0666 }
0667 
0668 KGetMetalink::Metalink KGetMetalink::Metalink_v3::metalink()
0669 {
0670     return m_metalink;
0671 }
0672 
0673 void KGetMetalink::Metalink_v3::setMetalink(const KGetMetalink::Metalink &metalink)
0674 {
0675     m_metalink = metalink;
0676 }
0677 
0678 void KGetMetalink::Metalink_v3::load(const QDomElement &e)
0679 {
0680     QDomDocument doc = e.ownerDocument();
0681     const QDomElement metalinkDom = doc.firstChildElement("metalink");
0682 
0683     m_metalink.dynamic = (metalinkDom.attribute("type") == "dynamic");
0684     m_metalink.origin = QUrl(metalinkDom.attribute("origin"));
0685     m_metalink.generator = metalinkDom.attribute("generator");
0686     m_metalink.published = parseDateConstruct(metalinkDom.attribute("pubdate"));
0687     m_metalink.updated = parseDateConstruct(metalinkDom.attribute("refreshdate"));
0688 
0689     parseFiles(metalinkDom);
0690 }
0691 
0692 void KGetMetalink::Metalink_v3::parseFiles(const QDomElement &e)
0693 {
0694     // here we assume that the CommonData set in metalink is for every file in the metalink
0695     CommonData data;
0696     data = parseCommonData(e);
0697 
0698     const QDomElement filesElem = e.firstChildElement("files");
0699     CommonData filesData = parseCommonData(filesElem);
0700 
0701     inheritCommonData(data, &filesData);
0702 
0703     for (QDomElement elem = filesElem.firstChildElement("file"); !elem.isNull(); elem = elem.nextSiblingElement("file")) {
0704         File file;
0705         file.name = QUrl::fromPercentEncoding(elem.attribute("name").toLatin1());
0706         file.size = elem.firstChildElement("size").text().toULongLong();
0707 
0708         file.data = parseCommonData(elem);
0709         inheritCommonData(filesData, &file.data);
0710 
0711         file.resources = parseResources(elem);
0712 
0713         // load the verification information
0714         QDomElement veriE = elem.firstChildElement("verification");
0715 
0716         for (QDomElement elemVer = veriE.firstChildElement("hash"); !elemVer.isNull(); elemVer = elemVer.nextSiblingElement("hash")) {
0717             QString type = elemVer.attribute("type");
0718             QString hash = elemVer.text();
0719             if (!type.isEmpty() && !hash.isEmpty()) {
0720                 type = addaptHashType(type, true);
0721                 file.verification.hashes[type] = hash;
0722             }
0723         }
0724 
0725         for (QDomElement elemVer = veriE.firstChildElement("pieces"); !elemVer.isNull(); elemVer = elemVer.nextSiblingElement("pieces")) {
0726             Pieces piecesItem;
0727             piecesItem.load(elemVer);
0728             file.verification.pieces.append(piecesItem);
0729         }
0730 
0731         for (QDomElement elemVer = veriE.firstChildElement("signature"); !elemVer.isNull(); elemVer = elemVer.nextSiblingElement("signature")) {
0732             const QString type = elemVer.attribute("type");
0733             const QString signature = elemVer.text();
0734             if (!type.isEmpty() && !signature.isEmpty()) {
0735                 file.verification.signatures[type] = signature;
0736             }
0737         }
0738 
0739         m_metalink.files.files.append(file);
0740     }
0741 }
0742 
0743 KGetMetalink::CommonData KGetMetalink::Metalink_v3::parseCommonData(const QDomElement &e)
0744 {
0745     CommonData data;
0746 
0747     data.load(e);
0748 
0749     const QDomElement publisherElem = e.firstChildElement("publisher");
0750     data.publisher.name = publisherElem.firstChildElement("name").text();
0751     data.publisher.url = QUrl(publisherElem.firstChildElement("url").text());
0752 
0753     return data;
0754 }
0755 
0756 void KGetMetalink::Metalink_v3::inheritCommonData(const KGetMetalink::CommonData &ancestor, KGetMetalink::CommonData *inheritor)
0757 {
0758     if (!inheritor) {
0759         return;
0760     }
0761 
0762     // ensure that inheritance works
0763     if (inheritor->identity.isEmpty()) {
0764         inheritor->identity = ancestor.identity;
0765     }
0766     if (inheritor->version.isEmpty()) {
0767         inheritor->version = ancestor.version;
0768     }
0769     if (inheritor->description.isEmpty()) {
0770         inheritor->description = ancestor.description;
0771     }
0772     if (inheritor->oses.isEmpty()) {
0773         inheritor->oses = ancestor.oses;
0774     }
0775     if (inheritor->logo.isEmpty()) {
0776         inheritor->logo = ancestor.logo;
0777     }
0778     if (inheritor->languages.isEmpty()) {
0779         inheritor->languages = ancestor.languages;
0780     }
0781     if (inheritor->copyright.isEmpty()) {
0782         inheritor->copyright = ancestor.copyright;
0783     }
0784     if (inheritor->publisher.isEmpty()) {
0785         inheritor->publisher = ancestor.publisher;
0786     }
0787 }
0788 
0789 KGetMetalink::Resources KGetMetalink::Metalink_v3::parseResources(const QDomElement &e)
0790 {
0791     Resources resources;
0792 
0793     QDomElement res = e.firstChildElement("resources");
0794     for (QDomElement elemRes = res.firstChildElement("url"); !elemRes.isNull(); elemRes = elemRes.nextSiblingElement("url")) {
0795         const QString location = elemRes.attribute("location").toLower();
0796 
0797         uint preference = elemRes.attribute("preference").toUInt();
0798         // the maximum preference we use is MAX_PREFERENCE
0799         if (preference > MAX_PREFERENCE) {
0800             preference = MAX_PREFERENCE;
0801         }
0802         const int priority = MAX_PREFERENCE - preference + 1; // convert old preference to new priority
0803 
0804         const QUrl link = QUrl(elemRes.text());
0805         QString type;
0806 
0807         if (link.fileName().endsWith(QLatin1String(".torrent"))) {
0808             type = "torrent";
0809         }
0810 
0811         if (type.isEmpty()) {
0812             Url url;
0813             if (preference) {
0814                 url.priority = priority;
0815             }
0816             url.location = location;
0817             url.url = link;
0818             if (url.isValid()) {
0819                 resources.urls.append(url);
0820             }
0821         } else {
0822             // it might be a metaurl
0823             Metaurl metaurl;
0824             if (preference) {
0825                 metaurl.priority = priority;
0826             }
0827             metaurl.url = link;
0828             metaurl.type = type;
0829             if (metaurl.isValid()) {
0830                 resources.metaurls.append(metaurl);
0831             }
0832         }
0833     }
0834 
0835     return resources;
0836 }
0837 
0838 KGetMetalink::DateConstruct KGetMetalink::Metalink_v3::parseDateConstruct(const QString &data)
0839 {
0840     DateConstruct dateConstruct;
0841 
0842     if (data.isEmpty()) {
0843         return dateConstruct;
0844     }
0845 
0846     qCDebug(KGET_DEBUG) << "Parsing" << data;
0847 
0848     QString temp = data;
0849     QDateTime dateTime;
0850     QTime timeZoneOffset;
0851 
0852     // Date according to RFC 822, the year with four characters preferred
0853     // e.g.: "Mon, 15 May 2006 00:00:01 GMT", "Fri, 01 Apr 2009 00:00:01 +1030"
0854 
0855     // find the date
0856     const QString weekdayExp = "ddd, ";
0857     const bool weekdayIncluded = (temp.indexOf(',') == 3);
0858     int startPosition = (weekdayIncluded ? weekdayExp.length() : 0);
0859     const QString dayMonthExp = "dd MMM ";
0860     const QString yearExp = "yy";
0861 
0862     QString exp = dayMonthExp + yearExp + yearExp;
0863     int length = exp.length();
0864 
0865     QLocale locale = QLocale::c();
0866     QDate date = locale.toDate(temp.mid(startPosition, length), exp);
0867     if (!date.isValid()) {
0868         exp = dayMonthExp + yearExp;
0869         length = exp.length();
0870         date = locale.toDate(temp.mid(startPosition, length), exp);
0871         if (!date.isValid()) {
0872             return dateConstruct;
0873         }
0874     }
0875 
0876     // find the time
0877     dateTime.setDate(date);
0878     temp = temp.mid(startPosition);
0879     temp = temp.mid(length + 1); // also remove the space
0880 
0881     const QString hourExp = "hh";
0882     const QString minuteExp = "mm";
0883     const QString secondExp = "ss";
0884 
0885     exp = hourExp + ':' + minuteExp + ':' + secondExp;
0886     length = exp.length();
0887     QTime time = QTime::fromString(temp.left(length), exp);
0888     if (!time.isValid()) {
0889         exp = hourExp + ':' + minuteExp;
0890         length = exp.length();
0891         time = QTime::fromString(temp.left(length), exp);
0892         if (!time.isValid()) {
0893             return dateConstruct;
0894         }
0895     }
0896     dateTime.setTime(time);
0897 
0898     // find the offset
0899     temp = temp.mid(length + 1); // also remove the space
0900     bool negativeOffset = false;
0901 
0902     if (temp.length() == 3) { // e.g. GMT
0903         const auto timeZone = QTimeZone(temp.toUtf8());
0904         if (timeZone.isValid()) {
0905             QDateTime a = QDateTime::currentDateTime();
0906             a.setTimeZone(timeZone);
0907             int offset = a.offsetFromUtc();
0908             negativeOffset = (offset < 0);
0909             timeZoneOffset = QTime(0, 0, 0);
0910             timeZoneOffset = timeZoneOffset.addSecs(qAbs(offset));
0911         }
0912     } else if (temp.length() == 5) { // e.g. +1030
0913         negativeOffset = (temp[0] == '-');
0914         timeZoneOffset = QTime::fromString(temp.mid(1, 4), "hhmm");
0915     }
0916 
0917     dateConstruct.setData(dateTime, timeZoneOffset, negativeOffset);
0918 
0919     return dateConstruct;
0920 }
0921 
0922 QDomDocument KGetMetalink::Metalink_v3::save() const
0923 {
0924     QDomDocument doc;
0925     QDomProcessingInstruction header = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"");
0926     doc.appendChild(header);
0927 
0928     QDomElement metalink = doc.createElement("metalink");
0929     metalink.setAttribute("xmlns", "http://www.metalinker.org/");
0930     metalink.setAttribute("version", "3.0");
0931     metalink.setAttribute("type", (m_metalink.dynamic ? "dynamic" : "static"));
0932     metalink.setAttribute("generator", Metalink::KGET_DESCRIPTION); // the set generator is ignored, instead when saving KGET is always used
0933 
0934     if (m_metalink.published.isValid()) {
0935         metalink.setAttribute("pubdate", dateConstructToString(m_metalink.published));
0936     }
0937     if (m_metalink.updated.isValid()) {
0938         metalink.setAttribute("refreshdate", dateConstructToString(m_metalink.updated));
0939     }
0940     if (!m_metalink.origin.isEmpty()) {
0941         metalink.setAttribute("origin", m_metalink.origin.url());
0942     }
0943 
0944     saveFiles(metalink);
0945 
0946     doc.appendChild(metalink);
0947 
0948     return doc;
0949 }
0950 
0951 void KGetMetalink::Metalink_v3::saveFiles(QDomElement &e) const
0952 {
0953     QDomDocument doc = e.ownerDocument();
0954     QDomElement filesElem = doc.createElement("files");
0955 
0956     foreach (const File &file, m_metalink.files.files) {
0957         QDomElement elem = doc.createElement("file");
0958         elem.setAttribute("name", file.name);
0959 
0960         QDomElement size = doc.createElement("size");
0961         QDomText text = doc.createTextNode(QString::number(file.size));
0962         size.appendChild(text);
0963         elem.appendChild(size);
0964 
0965         saveCommonData(file.data, elem);
0966         saveResources(file.resources, elem);
0967         saveVerification(file.verification, elem);
0968 
0969         filesElem.appendChild(elem);
0970     }
0971 
0972     e.appendChild(filesElem);
0973 }
0974 
0975 void KGetMetalink::Metalink_v3::saveResources(const Resources &resources, QDomElement &e) const
0976 {
0977     QDomDocument doc = e.ownerDocument();
0978     QDomElement res = doc.createElement("resources");
0979 
0980     foreach (const Url &url, resources.urls) {
0981         QDomElement elem = doc.createElement("url");
0982         const uint priority = url.priority;
0983         if (priority) {
0984             int preference = MAX_PREFERENCE - priority + 1;
0985             if (preference <= 0) {
0986                 preference = 1; // HACK if priority is larger MAX_PREFERENCE makes it 1
0987             }
0988             elem.setAttribute("preference", preference);
0989         }
0990         if (!url.location.isEmpty()) {
0991             elem.setAttribute("location", url.location);
0992         }
0993 
0994         QDomText text = doc.createTextNode(url.url.url());
0995         elem.appendChild(text);
0996 
0997         res.appendChild(elem);
0998     }
0999 
1000     foreach (const Metaurl &metaurl, resources.metaurls) {
1001         if (metaurl.type == "torrent") {
1002             QDomElement elem = doc.createElement("url");
1003             elem.setAttribute("type", "bittorrent");
1004             const uint priority = metaurl.priority;
1005             if (priority) {
1006                 int preference = MAX_PREFERENCE - priority + 1;
1007                 if (preference <= 0) {
1008                     preference = 1; // HACK if priority is larger MAX_PREFERENCE makes it 1
1009                 }
1010                 elem.setAttribute("preference", preference);
1011             }
1012 
1013             QDomText text = doc.createTextNode(metaurl.url.url());
1014             elem.appendChild(text);
1015 
1016             res.appendChild(elem);
1017         }
1018     }
1019 
1020     e.appendChild(res);
1021 }
1022 
1023 void KGetMetalink::Metalink_v3::saveVerification(const KGetMetalink::Verification &verification, QDomElement &e) const
1024 {
1025     QDomDocument doc = e.ownerDocument();
1026     QDomElement veri = doc.createElement("verification");
1027 
1028     QHash<QString, QString>::const_iterator it;
1029     QHash<QString, QString>::const_iterator itEnd = verification.hashes.constEnd();
1030     for (it = verification.hashes.constBegin(); it != itEnd; ++it) {
1031         QDomElement elem = doc.createElement("hash");
1032         elem.setAttribute("type", it.key());
1033         QDomText text = doc.createTextNode(it.value());
1034         elem.appendChild(text);
1035 
1036         veri.appendChild(elem);
1037     }
1038 
1039     foreach (const Pieces &pieces, verification.pieces) {
1040         QDomElement elem = doc.createElement("pieces");
1041         elem.setAttribute("type", pieces.type);
1042         elem.setAttribute("length", QString::number(pieces.length));
1043 
1044         for (int i = 0; i < pieces.hashes.count(); ++i) {
1045             QDomElement hash = doc.createElement("hash");
1046             hash.setAttribute("piece", i);
1047             QDomText text = doc.createTextNode(pieces.hashes.at(i));
1048             hash.appendChild(text);
1049 
1050             elem.appendChild(hash);
1051         }
1052         veri.appendChild(elem);
1053     }
1054 
1055     itEnd = verification.signatures.constEnd();
1056     for (it = verification.signatures.constBegin(); it != itEnd; ++it) {
1057         QDomElement elem = doc.createElement("signature");
1058         elem.setAttribute("type", it.key());
1059         QDomText text = doc.createTextNode(it.value());
1060         elem.appendChild(text);
1061 
1062         veri.appendChild(elem);
1063     }
1064 
1065     e.appendChild(veri);
1066 }
1067 
1068 void KGetMetalink::Metalink_v3::saveCommonData(const KGetMetalink::CommonData &data, QDomElement &e) const
1069 {
1070     QDomDocument doc = e.ownerDocument();
1071 
1072     CommonData commonData = data;
1073 
1074     if (!commonData.publisher.isEmpty()) {
1075         QDomElement elem = doc.createElement("publisher");
1076         QDomElement elemName = doc.createElement("name");
1077         QDomElement elemUrl = doc.createElement("url");
1078 
1079         QDomText text = doc.createTextNode(commonData.publisher.name);
1080         elemName.appendChild(text);
1081         elem.appendChild(elemName);
1082 
1083         text = doc.createTextNode(commonData.publisher.url.url());
1084         elemUrl.appendChild(text);
1085         elem.appendChild(elemUrl);
1086 
1087         e.appendChild(elem);
1088 
1089         commonData.publisher.clear();
1090     }
1091 
1092     if (commonData.oses.count() > 1) { // only one OS can be set in 3.0
1093         commonData.oses.clear();
1094     }
1095 
1096     commonData.save(e);
1097 }
1098 
1099 QString KGetMetalink::Metalink_v3::dateConstructToString(const KGetMetalink::DateConstruct &date) const
1100 {
1101     QString dateString;
1102     if (!date.isValid()) {
1103         return dateString;
1104     }
1105 
1106     QLocale locale = QLocale::c();
1107 
1108     //"Fri, 01 Apr 2009 00:00:01 +1030"
1109     dateString += locale.toString(date.dateTime, "ddd, dd MMM yyyy hh:mm:ss ");
1110 
1111     if (date.timeZoneOffset.isValid()) {
1112         dateString += (date.negativeOffset ? '-' : '+');
1113         dateString += date.timeZoneOffset.toString("hhmm");
1114     } else {
1115         dateString += "+0000";
1116     }
1117 
1118     return dateString;
1119 }
1120 
1121 bool KGetMetalink::HandleMetalink::load(const QUrl &destination, KGetMetalink::Metalink *metalink)
1122 {
1123     QFile file(destination.toLocalFile());
1124     if (!file.open(QIODevice::ReadOnly)) {
1125         return false;
1126     }
1127 
1128     QDomDocument doc;
1129     if (!doc.setContent(&file)) {
1130         file.close();
1131         return false;
1132     }
1133     file.close();
1134 
1135     QDomElement root = doc.documentElement();
1136     if (root.attribute("xmlns") == "urn:ietf:params:xml:ns:metalink") {
1137         metalink->load(root);
1138         return true;
1139     } else if ((root.attribute("xmlns") == "http://www.metalinker.org/") || (root.attribute("version") == "3.0")) {
1140         Metalink_v3 metalink_v3;
1141         metalink_v3.load(root);
1142         *metalink = metalink_v3.metalink();
1143         return true;
1144     }
1145 
1146     return false;
1147 }
1148 
1149 bool KGetMetalink::HandleMetalink::load(const QByteArray &data, KGetMetalink::Metalink *metalink)
1150 {
1151     if (data.isNull()) {
1152         return false;
1153     }
1154 
1155     QDomDocument doc;
1156     if (!doc.setContent(data)) {
1157         return false;
1158     }
1159 
1160     metalink->clear();
1161     QDomElement root = doc.documentElement();
1162     if (root.attribute("xmlns") == "urn:ietf:params:xml:ns:metalink") {
1163         metalink->load(root);
1164         return true;
1165     } else if ((root.attribute("xmlns") == "http://www.metalinker.org/") || (root.attribute("version") == "3.0")) {
1166         Metalink_v3 metalink_v3;
1167         metalink_v3.load(root);
1168         *metalink = metalink_v3.metalink();
1169         return true;
1170     }
1171 
1172     return false;
1173 }
1174 
1175 bool KGetMetalink::HandleMetalink::save(const QUrl &destination, KGetMetalink::Metalink *metalink)
1176 {
1177     QFile file(destination.toLocalFile());
1178     if (!file.open(QIODevice::WriteOnly)) {
1179         return false;
1180     }
1181 
1182     QDomDocument doc;
1183     QString fileName = destination.fileName();
1184     if (fileName.endsWith(QLatin1String("meta4"))) {
1185         doc = metalink->save();
1186     } else if (fileName.endsWith(QLatin1String("metalink"))) {
1187         Metalink_v3 metalink_v3;
1188         metalink_v3.setMetalink(*metalink);
1189         doc = metalink_v3.save();
1190     } else {
1191         file.close();
1192         return false;
1193     }
1194 
1195     QTextStream stream(&file);
1196     doc.save(stream, 2);
1197     file.close();
1198 
1199     return true;
1200 }
1201 
1202 KGetMetalink::MetalinkHttpParser::~MetalinkHttpParser()
1203 {
1204 }
1205 
1206 QString *KGetMetalink::MetalinkHttpParser::getEtag()
1207 {
1208     return &m_EtagValue;
1209 }
1210 
1211 void KGetMetalink::MetalinkHttpParser::checkMetalinkHttp()
1212 {
1213     if (!m_Url.isValid()) {
1214         qDebug() << "Url not valid";
1215         return;
1216     }
1217 
1218     KIO::TransferJob *job;
1219     job = KIO::get(m_Url, KIO::NoReload, KIO::HideProgressInfo);
1220     job->addMetaData("PropagateHttpHeader", "true");
1221     job->setRedirectionHandlingEnabled(false);
1222     connect(job, &KJob::result, this, &MetalinkHttpParser::slotHeaderResult); // Finished
1223     connect(job, &KIO::TransferJob::redirection, this, &MetalinkHttpParser::slotRedirection); // Redirection
1224     connect(job, SIGNAL(mimetype(KIO::Job *, QString)), this, SLOT(detectMime(KIO::Job *, QString))); // Mime detection.
1225     qDebug() << " Verifying Metalink/HTTP Status";
1226     m_loop.exec();
1227 }
1228 
1229 void KGetMetalink::MetalinkHttpParser::detectMime(KIO::Job *job, const QString &type)
1230 {
1231     qDebug() << "Mime Type: " << type;
1232     job->kill();
1233     m_loop.exit();
1234 }
1235 
1236 void KGetMetalink::MetalinkHttpParser::slotHeaderResult(KJob *kjob)
1237 {
1238     auto *job = qobject_cast<KIO::Job *>(kjob);
1239     const QString httpHeaders = job ? job->queryMetaData("HTTP-Headers") : QString();
1240     parseHeaders(httpHeaders);
1241     setMetalinkHSatus();
1242 
1243     // Handle the redirection... (Comment out if not desired)
1244     if (m_redirectionUrl.isValid()) {
1245         m_Url = m_redirectionUrl;
1246         m_redirectionUrl = QUrl();
1247         checkMetalinkHttp();
1248     }
1249 
1250     if (m_loop.isRunning())
1251         m_loop.exit();
1252 }
1253 
1254 void KGetMetalink::MetalinkHttpParser::slotRedirection(KIO::Job *job, const QUrl &url)
1255 {
1256     Q_UNUSED(job)
1257     m_redirectionUrl = url;
1258 }
1259 
1260 bool KGetMetalink::MetalinkHttpParser::isMetalinkHttp()
1261 {
1262     if (m_MetalinkHSatus) {
1263         qDebug() << "Metalink Http detected";
1264     } else {
1265         qDebug() << "No Metalink HTTP response found";
1266     }
1267     return m_MetalinkHSatus;
1268 }
1269 
1270 void KGetMetalink::MetalinkHttpParser::parseHeaders(const QString &httpHeader)
1271 {
1272     QString trimedHeader = httpHeader.mid(httpHeader.indexOf('\n') + 1).trimmed();
1273 
1274     foreach (QString line, trimedHeader.split('\n')) {
1275         int colon = line.indexOf(':');
1276         QString headerName = line.left(colon).trimmed();
1277         QString headerValue = line.mid(colon + 1).trimmed();
1278         m_headerInfo.insertMulti(headerName, headerValue);
1279     }
1280 
1281     m_EtagValue = m_headerInfo.value("ETag");
1282 }
1283 
1284 void KGetMetalink::MetalinkHttpParser::setMetalinkHSatus()
1285 {
1286     bool linkStatus, digestStatus;
1287     linkStatus = digestStatus = false;
1288     if (m_headerInfo.contains("link")) {
1289         QList<QString> linkValues = m_headerInfo.values("link");
1290 
1291         foreach (QString linkVal, linkValues) {
1292             if (linkVal.contains("rel=duplicate")) {
1293                 linkStatus = true;
1294                 break;
1295             }
1296         }
1297     }
1298 
1299     if (m_headerInfo.contains("digest")) {
1300         QList<QString> digestValues = m_headerInfo.values("digest");
1301 
1302         foreach (QString digestVal, digestValues) {
1303             if (digestVal.contains("sha-256", Qt::CaseInsensitive)) {
1304                 digestStatus = true;
1305                 break;
1306             }
1307         }
1308     }
1309 
1310     if ((linkStatus) && (digestStatus)) {
1311         m_MetalinkHSatus = true;
1312     }
1313 }
1314 
1315 QUrl KGetMetalink::MetalinkHttpParser::getUrl()
1316 {
1317     return m_Url;
1318 }
1319 
1320 QMultiMap<QString, QString> *KGetMetalink::MetalinkHttpParser::getHeaderInfo()
1321 {
1322     return &m_headerInfo;
1323 }
1324 
1325 KGetMetalink::HttpLinkHeader::HttpLinkHeader(const QString &headerLine)
1326     : pref(false)
1327 {
1328     parseHeaderLine(headerLine);
1329 }
1330 
1331 bool KGetMetalink::HttpLinkHeader::operator<(const HttpLinkHeader &other) const
1332 {
1333     return depth < other.depth;
1334 }
1335 
1336 void KGetMetalink::HttpLinkHeader::parseHeaderLine(const QString &line)
1337 {
1338     url = QUrl(line.mid(line.indexOf("<") + 1, line.indexOf(">") - 1).trimmed());
1339     const QList<QString> attribList = line.split(";");
1340 
1341     foreach (const QString str, attribList) {
1342         const QString attribId = str.mid(0, str.indexOf("=")).trimmed();
1343         const QString attribValue = str.mid(str.indexOf("=") + 1).trimmed();
1344 
1345         if (attribId == "rel") {
1346             reltype = attribValue;
1347         } else if (attribId == "depth") {
1348             depth = attribValue.toInt();
1349         } else if (attribId == "geo") {
1350             geo = attribValue;
1351         } else if (attribId == "pref") {
1352             pref = true;
1353         } else if (attribId == "pri") {
1354             priority = attribValue.toUInt();
1355         } else if (attribId == "type") {
1356             type = attribValue;
1357         } else if (attribId == "name") {
1358             name = attribValue;
1359         }
1360     }
1361 }
1362 
1363 #include "moc_metalinker.cpp"