fix: address security related edge cases (#1919)

This commit is contained in:
Oleg Shparber
2026-06-30 21:59:03 +03:00
committed by GitHub
parent d9f74690a7
commit 19cad78b72
6 changed files with 70 additions and 11 deletions
+14
View File
@@ -6,10 +6,20 @@
#include <core/application.h>
#include <QDesktopServices>
#include <QSet>
#include <QUrl>
namespace Zeal::Browser {
namespace {
const QSet<QString> 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));
}
+16 -1
View File
@@ -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) {
+1 -1
View File
@@ -69,7 +69,7 @@ HttpServer::HttpServer(quint16 port, QObject *parent)
const QString html = QStringLiteral("<b>ERROR %1</b><br><pre>Request path: %2</pre>")
.arg(res.status)
.arg(QString::fromStdString(req.path));
.arg(QString::fromStdString(req.path).toHtmlEscaped());
res.set_content(html.toUtf8().data(), "text/html");
});
+13 -1
View File
@@ -173,7 +173,19 @@ QUrl DocsetMetadata::feedUrl() const
QUrl DocsetMetadata::url() const
{
return m_urls.at(QRandomGenerator::global()->bounded(m_urls.size()));
QList<QUrl> 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<QUrl> DocsetMetadata::urls() const
+23 -6
View File
@@ -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 <b>%1</b> docset?").arg(docsetTitle));
rc = QMessageBox::question(this,
QStringLiteral("Zeal"),
tr("Remove <b>%1</b> 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!<br><br><b>Error:</b> %1<br><b>URL:</b> %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 <b>%1</b>.").arg(docsetName));
tr("Cannot create a temporary file to install <b>%1</b>.")
.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 <b>%1</b>: %2").arg(docsetName, errorString));
tr("Cannot extract docset <b>%1</b>: %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 <b>%1</b>. 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 <b>%1</b>! It might be in use"
" by another process.")
.arg(docsetPath);
.arg(docsetPath.toHtmlEscaped());
QMessageBox::warning(this, QStringLiteral("Zeal"), error);
return;
}
+3 -2
View File
@@ -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 <b>%1</b> is available. Open download page?").arg(version),
tr("Zeal <b>%1</b> is available. Open download page?")
.arg(version.toHtmlEscaped()),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes);
qApp->setQuitOnLastWindowClosed(true);