Skip to content
Permalink
Browse files

Merge PR #3168: Log: remove support for external images.

This change remove support for loading external images.
That is, this change ensures Mumble doesn't load images from  tags in text messages,
comments and channel descriptions via HTTP or HTTPS.

With this change in place, Mumble only supports images that are embedded
in the message via data URLs.
  • Loading branch information...
mkrautz committed Nov 26, 2017
2 parents 24e437c + 7578e85 commit 312543974048d832e5d430aa9b49cd32984188e8
Showing with 29 additions and 118 deletions.
  1. +26 −76 src/mumble/Log.cpp
  2. +1 −7 src/mumble/Log.h
  3. +0 −7 src/mumble/NetworkConfig.cpp
  4. +0 −23 src/mumble/NetworkConfig.ui
  5. +2 −3 src/mumble/Settings.cpp
  6. +0 −2 src/mumble/Settings.h
@@ -362,13 +362,10 @@ QString Log::imageToImg(QImage img) {
return QString();
}

QString Log::validHtml(const QString &html, bool allowReplacement, QTextCursor *tc) {
QString Log::validHtml(const QString &html, QTextCursor *tc) {
QDesktopWidget dw;
LogDocument qtd;

qtd.setAllowHTTPResources(allowReplacement);
qtd.setOnlyLoadDataURLs(true);

QRectF qr = dw.availableGeometry(dw.screenNumber(g.mw));
qtd.setTextWidth(qr.width() / 2);
qtd.setDefaultStyleSheet(qApp->styleSheet());
@@ -478,7 +475,7 @@ void Log::log(MsgType mt, const QString &console, const QString &terse, bool own
tc.insertBlock();
}
tc.insertHtml(Log::msgColor(QString::fromLatin1("[%1] ").arg(Qt::escape(dt.time().toString())), Log::Time));
validHtml(console, true, &tc);
validHtml(console, &tc);
tc.movePosition(QTextCursor::End);
g.mw->qteLog->setTextCursor(tc);

@@ -574,9 +571,7 @@ void Log::postQtNotification(MsgType mt, const QString &plain) {
}

LogDocument::LogDocument(QObject *p)
: QTextDocument(p)
, m_allowHTTPResources(true)
, m_onlyLoadDataURLs(false) {
: QTextDocument(p) {
}

QVariant LogDocument::loadResource(int type, const QUrl &url) {
@@ -590,64 +585,25 @@ QVariant LogDocument::loadResource(int type, const QUrl &url) {
QImage qi(1, 1, QImage::Format_Mono);
addResource(type, url, qi);

if (url.scheme() != QLatin1String("data") && g.s.iMaxImageSize <= 0) {
if (! url.isValid()) {
return qi;
}

if (! url.isValid() || url.isRelative()) {
return qi;
}

QStringList allowedSchemes;
allowedSchemes << QLatin1String("data");
if (m_allowHTTPResources) {
allowedSchemes << QLatin1String("http");
allowedSchemes << QLatin1String("https");
}

if (!allowedSchemes.contains(url.scheme())) {
if (url.scheme() != QLatin1String("data")) {
return qi;
}

bool shouldLoad = true;
if (m_onlyLoadDataURLs && url.scheme() != QLatin1String("data")) {
shouldLoad = false;
}

if (shouldLoad) {
QNetworkReply *rep = Network::get(url);
connect(rep, SIGNAL(metaDataChanged()), this, SLOT(receivedHead()));
connect(rep, SIGNAL(finished()), this, SLOT(finished()));
QNetworkReply *rep = Network::get(url);
connect(rep, SIGNAL(finished()), this, SLOT(finished()));

// Handle data URLs immediately without a roundtrip to the event loop.
// We need this to perform proper validation for data URL images when
// a LogDocument is used inside Log::validHtml().
if (url.scheme() == QLatin1String("data")) {
QCoreApplication::sendPostedEvents(rep, 0);
}
}
// Handle data URLs immediately without a roundtrip to the event loop.
// We need this to perform proper validation for data URL images when
// a LogDocument is used inside Log::validHtml().
QCoreApplication::sendPostedEvents(rep, 0);

return qi;
}

void LogDocument::setAllowHTTPResources(bool allowHTTPResources) {
m_allowHTTPResources = allowHTTPResources;
}

void LogDocument::setOnlyLoadDataURLs(bool onlyLoadDataURLs) {
m_onlyLoadDataURLs = onlyLoadDataURLs;
}

void LogDocument::receivedHead() {
QNetworkReply *rep = qobject_cast<QNetworkReply *>(sender());
if (rep->url().scheme() != QLatin1String("data")) {
QVariant length = rep->header(QNetworkRequest::ContentLengthHeader);
if (length == QVariant::Invalid || length.toInt() > g.s.iMaxImageSize) {
rep->abort();
}
}
}

void LogDocument::finished() {
QNetworkReply *rep = qobject_cast<QNetworkReply *>(sender());

@@ -662,27 +618,21 @@ void LogDocument::finished() {
// instead of strictly requiring a correct Content-Type.
if (RichTextImage::isValidImage(ba, fmt)) {
if (qi.loadFromData(ba, fmt)) {
bool ok = true;
if (rep->url().scheme() != QLatin1String("data")) {
ok = (qi.width() <= g.s.iMaxImageWidth && qi.height() <= g.s.iMaxImageHeight);
}
if (ok) {
addResource(QTextDocument::ImageResource, rep->request().url(), qi);

// Force a re-layout of the QTextEdit the next
// time we enter the event loop.
// We must not trigger a re-layout immediately.
// Doing so can trigger crashes deep inside Qt
// if the QTextDocument has just been set on the
// text edit widget.
QTextEdit *qte = qobject_cast<QTextEdit *>(parent());
if (qte != NULL) {
QEvent *e = new QEvent(QEvent::FontChange);
QApplication::postEvent(qte, e);

e = new LogDocumentResourceAddedEvent();
QApplication::postEvent(qte, e);
}
addResource(QTextDocument::ImageResource, rep->request().url(), qi);

// Force a re-layout of the QTextEdit the next
// time we enter the event loop.
// We must not trigger a re-layout immediately.
// Doing so can trigger crashes deep inside Qt
// if the QTextDocument has just been set on the
// text edit widget.
QTextEdit *qte = qobject_cast<QTextEdit *>(parent());
if (qte != NULL) {
QEvent *e = new QEvent(QEvent::FontChange);
QApplication::postEvent(qte, e);

e = new LogDocumentResourceAddedEvent();
QApplication::postEvent(qte, e);
}
}
}
@@ -67,7 +67,7 @@ class Log : public QObject {
QString msgName(MsgType t) const;
void setIgnore(MsgType t, int ignore = 1 << 30);
void clearIgnore();
static QString validHtml(const QString &html, bool allowReplacement = false, QTextCursor *tc = NULL);
static QString validHtml(const QString &html, QTextCursor *tc = NULL);
static QString imageToImg(const QByteArray &format, const QByteArray &image);
static QString imageToImg(QImage img);
static QString msgColor(const QString &text, LogColorType t);
@@ -84,14 +84,8 @@ class LogDocument : public QTextDocument {
public:
LogDocument(QObject *p = NULL);
QVariant loadResource(int, const QUrl &) Q_DECL_OVERRIDE;
void setAllowHTTPResources(bool allowHttpResources);
void setOnlyLoadDataURLs(bool onlyLoadDataURLs);
public slots:
void receivedHead();
void finished();
private:
bool m_allowHTTPResources;
bool m_onlyLoadDataURLs;
};

class LogDocumentResourceAddedEvent : public QEvent {
@@ -54,7 +54,6 @@ void NetworkConfig::load(const Settings &r) {
qleUsername->setText(r.qsProxyUsername);
qlePassword->setText(r.qsProxyPassword);

loadCheckBox(qcbImageDownload, r.iMaxImageSize <= 0);
loadCheckBox(qcbHideOS, s.bHideOS);

loadCheckBox(qcbAutoUpdate, r.bUpdateCheck);
@@ -81,12 +80,6 @@ void NetworkConfig::save() const {
s.qsProxyUsername = qleUsername->text();
s.qsProxyPassword = qlePassword->text();

if (qcbImageDownload->isChecked()) {
s.iMaxImageSize = 0;
} else if (s.iMaxImageSize <= 0) {
s.iMaxImageSize = s.ciDefaultMaxImageSize;
}

s.bUpdateCheck=qcbAutoUpdate->isChecked();
s.bPluginCheck=qcbPluginUpdate->isChecked();
s.bUsage=qcbUsage->isChecked();
@@ -272,29 +272,6 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="qgbMisc">
<property name="title">
<string>Misc</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QCheckBox" name="qcbImageDownload">
<property name="toolTip">
<string>Prevent log from downloading images</string>
</property>
<property name="whatsThis">
<string>&lt;b&gt;Disable image download&lt;/b&gt;&lt;br/&gt;
Prevents the client from downloading images embedded into chat messages with the img tag.</string>
</property>
<property name="text">
<string>Disable image download</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="qgbPrivacy">
<property name="title">
@@ -352,7 +352,6 @@ Settings::Settings() {
iMaxInFlightTCPPings = 2;
bUdpForceTcpAddr = true;

iMaxImageSize = ciDefaultMaxImageSize;
iMaxImageWidth = 1024; // Allow 1024x1024 resolution
iMaxImageHeight = 1024;
bSuppressIdentity = false;
@@ -683,7 +682,7 @@ void Settings::load(QSettings* settings_ptr) {
SAVELOAD(usProxyPort, "net/proxyport");
SAVELOAD(qsProxyUsername, "net/proxyusername");
SAVELOAD(qsProxyPassword, "net/proxypassword");
SAVELOAD(iMaxImageSize, "net/maximagesize");
DEPRECATED("net/maximagesize");
SAVELOAD(iMaxImageWidth, "net/maximagewidth");
SAVELOAD(iMaxImageHeight, "net/maximageheight");
SAVELOAD(qsServicePrefix, "net/serviceprefix");
@@ -1016,7 +1015,7 @@ void Settings::save() {
SAVELOAD(usProxyPort, "net/proxyport");
SAVELOAD(qsProxyUsername, "net/proxyusername");
SAVELOAD(qsProxyPassword, "net/proxypassword");
SAVELOAD(iMaxImageSize, "net/maximagesize");
DEPRECATED("net/maximagesize");
SAVELOAD(iMaxImageWidth, "net/maximagewidth");
SAVELOAD(iMaxImageHeight, "net/maximageheight");
SAVELOAD(qsServicePrefix, "net/serviceprefix");
@@ -365,8 +365,6 @@ struct Settings {
// Privacy settings
bool bHideOS;

static const int ciDefaultMaxImageSize = 50 * 1024; // Restrict to 50KiB as a default
int iMaxImageSize;
int iMaxImageWidth;
int iMaxImageHeight;
KeyPair kpCertificate;

0 comments on commit 3125439

Please sign in to comment.
You can’t perform that action at this time.