mirror of
https://github.com/zealdocs/zeal.git
synced 2026-08-29 08:34:50 +08:00
fix: address security related edge cases (#1919)
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user