diff --git a/src/libs/browser/webbridge.cpp b/src/libs/browser/webbridge.cpp index 417f3d68..e174c62c 100644 --- a/src/libs/browser/webbridge.cpp +++ b/src/libs/browser/webbridge.cpp @@ -6,10 +6,20 @@ #include #include +#include #include namespace Zeal::Browser { +namespace { +const QSet AllowedShortUrlKeys = {QStringLiteral("discord"), + QStringLiteral("github"), + QStringLiteral("report-bug"), + QStringLiteral("telegram"), + QStringLiteral("website"), + QStringLiteral("x")}; +} // namespace + WebBridge::WebBridge(QObject *parent) : QObject(parent) { @@ -17,6 +27,10 @@ WebBridge::WebBridge(QObject *parent) void WebBridge::openShortUrl(const QString &key) { + if (!AllowedShortUrlKeys.contains(key)) { + return; + } + QDesktopServices::openUrl(QUrl(QStringLiteral("https://go.zealdocs.org/l/") + key)); } diff --git a/src/libs/core/application.cpp b/src/libs/core/application.cpp index 9039c210..e6c2d6e6 100644 --- a/src/libs/core/application.cpp +++ b/src/libs/core/application.cpp @@ -205,8 +205,23 @@ void Application::checkForUpdates(bool quiet) return; } - const QJsonObject versionInfo = jsonDoc.array().first().toObject(); // Latest is the first. + const QJsonArray releases = jsonDoc.array(); + if (releases.isEmpty() || !releases.first().isObject()) { + if (!quiet) { + emit updateCheckError(tr("Server returned an invalid release list.")); + } + return; + } + + const QJsonObject versionInfo = releases.first().toObject(); // Latest is the first. const auto latestVersion = QVersionNumber::fromString(versionInfo[QLatin1String("version")].toString()); + if (latestVersion.isNull()) { + if (!quiet) { + emit updateCheckError(tr("Server returned an invalid release list.")); + } + return; + } + if (latestVersion > version()) { emit updateCheckDone(latestVersion.toString()); } else if (!quiet) { diff --git a/src/libs/core/httpserver.cpp b/src/libs/core/httpserver.cpp index 6a9e287f..ca31ed25 100644 --- a/src/libs/core/httpserver.cpp +++ b/src/libs/core/httpserver.cpp @@ -69,7 +69,7 @@ HttpServer::HttpServer(quint16 port, QObject *parent) const QString html = QStringLiteral("ERROR %1
Request path: %2
") .arg(res.status) - .arg(QString::fromStdString(req.path)); + .arg(QString::fromStdString(req.path).toHtmlEscaped()); res.set_content(html.toUtf8().data(), "text/html"); }); diff --git a/src/libs/registry/docsetmetadata.cpp b/src/libs/registry/docsetmetadata.cpp index 15b3fc65..1a02aaa2 100644 --- a/src/libs/registry/docsetmetadata.cpp +++ b/src/libs/registry/docsetmetadata.cpp @@ -173,7 +173,19 @@ QUrl DocsetMetadata::feedUrl() const QUrl DocsetMetadata::url() const { - return m_urls.at(QRandomGenerator::global()->bounded(m_urls.size())); + QList validUrls; + validUrls.reserve(m_urls.size()); + for (const QUrl &url : m_urls) { + if (url.isValid() && !url.isEmpty()) { + validUrls.append(url); + } + } + + if (validUrls.isEmpty()) { + return {}; + } + + return validUrls.at(QRandomGenerator::global()->bounded(validUrls.size())); } QList DocsetMetadata::urls() const diff --git a/src/libs/ui/docsetsdialog.cpp b/src/libs/ui/docsetsdialog.cpp index e9e34298..3da4636e 100644 --- a/src/libs/ui/docsetsdialog.cpp +++ b/src/libs/ui/docsetsdialog.cpp @@ -197,7 +197,9 @@ void DocsetsDialog::removeSelectedDocsets() const QModelIndexList selectedIndexes = selectionModel->selectedRows(); if (selectedIndexes.size() == 1) { const QString docsetTitle = selectedIndexes.first().data().toString(); - rc = QMessageBox::question(this, QStringLiteral("Zeal"), tr("Remove %1 docset?").arg(docsetTitle)); + rc = QMessageBox::question(this, + QStringLiteral("Zeal"), + tr("Remove %1 docset?").arg(docsetTitle.toHtmlEscaped())); } else { rc = QMessageBox::question(this, QStringLiteral("Zeal"), @@ -308,7 +310,8 @@ void DocsetsDialog::downloadCompleted() if (reply->error() != QNetworkReply::OperationCanceledError) { const QString msg = tr("Download failed!

Error: %1
URL: %2") - .arg(reply->errorString(), reply->request().url().toString()); + .arg(reply->errorString().toHtmlEscaped(), + reply->request().url().toString().toHtmlEscaped()); const int ret = QMessageBox::warning(this, QStringLiteral("Zeal"), msg, @@ -393,7 +396,8 @@ void DocsetsDialog::downloadCompleted() } QMessageBox::warning(this, QStringLiteral("Zeal"), - tr("Cannot create a temporary file to install %1.").arg(docsetName)); + tr("Cannot create a temporary file to install %1.") + .arg(docsetName.toHtmlEscaped())); break; } @@ -530,7 +534,8 @@ void DocsetsDialog::extractionError(const QString &filePath, const QString &erro QMessageBox::warning(this, QStringLiteral("Zeal"), - tr("Cannot extract docset %1: %2").arg(docsetName, errorString)); + tr("Cannot extract docset %1: %2") + .arg(docsetName.toHtmlEscaped(), errorString.toHtmlEscaped())); QListWidgetItem *listItem = findDocsetListItem(docsetName); if (listItem != nullptr) { @@ -911,6 +916,10 @@ void DocsetsDialog::processDocsetList(const QJsonArray &list) const QJsonObject docsetJson = v.toObject(); const Registry::DocsetMetadata metadata(docsetJson); + if (metadata.name().isEmpty() || metadata.urls().isEmpty()) { + qCWarning(log, "Skipping invalid docset metadata entry."); + continue; + } m_availableDocsets.insert({metadata.name(), metadata}); } @@ -1002,6 +1011,14 @@ void DocsetsDialog::downloadDashDocset(const QModelIndex &index) url = m_userFeeds[name].url(); } + if (!url.isValid() || url.isEmpty()) { + qCWarning(log, "Cannot download docset '%s': no valid URL.", qPrintable(name)); + if (QListWidgetItem *listItem = findDocsetListItem(name); listItem != nullptr) { + listItem->setData(DocsetListItemDelegate::ShowProgressRole, false); + } + return; + } + QNetworkReply *reply = download(url); reply->setProperty(DocsetNameProperty, name); setDownloadType(reply, DownloadType::Docset); @@ -1032,7 +1049,7 @@ void DocsetsDialog::onTarixIndexFailed(QNetworkReply *reply) QStringLiteral("Zeal"), tr("Could not download the compact index for %1. Installing without it will be " "slower and use considerably more disk space.") - .arg(docsetName), + .arg(docsetName.toHtmlEscaped()), QMessageBox::NoButton, this); QPushButton *retryButton = box.addButton(QMessageBox::Retry); @@ -1077,7 +1094,7 @@ void DocsetsDialog::removeDocset(const QString &name) if (!Core::FileManager::removeRecursively(docsetPath)) { const QString error = tr("Cannot remove directory %1! It might be in use" " by another process.") - .arg(docsetPath); + .arg(docsetPath.toHtmlEscaped()); QMessageBox::warning(this, QStringLiteral("Zeal"), error); return; } diff --git a/src/libs/ui/windowmanager.cpp b/src/libs/ui/windowmanager.cpp index 74033f54..16029c6a 100644 --- a/src/libs/ui/windowmanager.cpp +++ b/src/libs/ui/windowmanager.cpp @@ -27,7 +27,7 @@ WindowManager::WindowManager(Core::Application *application, QObject *parent) { // Update-check dialogs are session-level, not per-window. connect(m_application, &Core::Application::updateCheckError, this, [this](const QString &message) { - QMessageBox::warning(activeWindow(), QStringLiteral("Zeal"), message); + QMessageBox::warning(activeWindow(), QStringLiteral("Zeal"), message.toHtmlEscaped()); }); connect(m_application, &Core::Application::updateCheckDone, this, [this](const QString &version) { @@ -40,7 +40,8 @@ WindowManager::WindowManager(Core::Application *application, QObject *parent) qApp->setQuitOnLastWindowClosed(false); const int ret = QMessageBox::information(activeWindow(), QStringLiteral("Zeal"), - tr("Zeal %1 is available. Open download page?").arg(version), + tr("Zeal %1 is available. Open download page?") + .arg(version.toHtmlEscaped()), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); qApp->setQuitOnLastWindowClosed(true);