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"