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

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