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"