Escape blacklisted html tags

This commit is contained in:
Nicolas Werner 2020-01-14 17:47:30 +01:00
parent 62f17dffbd
commit 3f02b0bf56
3 changed files with 49 additions and 15 deletions

View File

@ -314,37 +314,67 @@ utils::linkifyMessage(const QString &body)
return doc; return doc;
} }
QByteArray QString
escapeRawHtml(const QByteArray &data) utils::escapeBlacklistedHtml(const QString &rawStr)
{ {
static const std::vector<std::string_view> allowedTags = {
"font", "/font", "del", "/del", "h1", "/h1", "h2", "/h2",
"h3", "/h3", "h4", "/h4", "h5", "/h5", "h6", "/h6",
"blockquote", "/blockquote", "p", "/p", "a", "/a", "ul", "/ul",
"ol", "/ol", "sup", "/sup", "sub", "/sub", "li", "/li",
"b", "/b", "i", "/i", "u", "/u", "strong", "/strong",
"em", "/em", "strike", "/strike", "code", "/code", "hr", "/hr",
"br", "br/", "div", "/div", "table", "/table", "thead", "/thead",
"tbody", "/tbody", "tr", "/tr", "th", "/th", "td", "/td",
"caption", "/caption", "pre", "/pre", "span", "/span", "img", "/img"};
QByteArray data = rawStr.toUtf8();
QByteArray buffer; QByteArray buffer;
const size_t length = data.size(); const size_t length = data.size();
buffer.reserve(length); buffer.reserve(length);
bool escapingTag = false;
for (size_t pos = 0; pos != length; ++pos) { for (size_t pos = 0; pos != length; ++pos) {
switch (data.at(pos)) { switch (data.at(pos)) {
case '&': case '<': {
buffer.append("&amp;"); bool oneTagMatched = false;
break; size_t endPos = std::min(static_cast<size_t>(data.indexOf('>', pos)),
case '<': static_cast<size_t>(data.indexOf(' ', pos)));
buffer.append("&lt;");
auto mid = data.mid(pos + 1, endPos - pos - 1);
for (const auto &tag : allowedTags) {
// TODO: Check src and href attribute
if (mid.compare(tag.data(), Qt::CaseInsensitive) == 0) {
oneTagMatched = true;
}
}
if (oneTagMatched)
buffer.append('<');
else {
escapingTag = true;
buffer.append("&lt;");
}
break; break;
}
case '>': case '>':
buffer.append("&gt;"); if (escapingTag)
buffer.append("&gt;");
else {
escapingTag = false;
buffer.append('>');
}
break; break;
default: default:
buffer.append(data.at(pos)); buffer.append(data.at(pos));
break; break;
} }
} }
return buffer; return QString::fromUtf8(buffer);
} }
QString QString
utils::markdownToHtml(const QString &text) utils::markdownToHtml(const QString &text)
{ {
const auto str = escapeRawHtml(text.toUtf8()); const auto str = text.toUtf8();
const char *tmp_buf = const char *tmp_buf = cmark_markdown_to_html(str.constData(), str.size(), CMARK_OPT_UNSAFE);
cmark_markdown_to_html(str.constData(), str.size(), CMARK_OPT_DEFAULT);
// Copy the null terminated output buffer. // Copy the null terminated output buffer.
std::string html(tmp_buf); std::string html(tmp_buf);
@ -352,7 +382,7 @@ utils::markdownToHtml(const QString &text)
// The buffer is no longer needed. // The buffer is no longer needed.
free((char *)tmp_buf); free((char *)tmp_buf);
auto result = QString::fromStdString(html).trimmed(); auto result = escapeBlacklistedHtml(QString::fromStdString(html)).trimmed();
return result; return result;
} }

View File

@ -286,6 +286,10 @@ linkifyMessage(const QString &body);
QString QString
markdownToHtml(const QString &text); markdownToHtml(const QString &text);
//! Escape every html tag, that was not whitelisted
QString
escapeBlacklistedHtml(const QString &data);
//! Generate a Rich Reply quote message //! Generate a Rich Reply quote message
QString QString
getFormattedQuoteBody(const RelatedInfo &related, const QString &html); getFormattedQuoteBody(const RelatedInfo &related, const QString &html);

View File

@ -260,8 +260,8 @@ TimelineModel::data(const QString &id, int role) const
const static QRegularExpression replyFallback( const static QRegularExpression replyFallback(
"<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption); "<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption);
return QVariant( return QVariant(
utils::replaceEmoji(utils::linkifyMessage(formattedBodyWithFallback(event))) utils::replaceEmoji(utils::linkifyMessage(utils::escapeBlacklistedHtml(
.remove(replyFallback)); formattedBodyWithFallback(event).remove(replyFallback)))));
} }
case Url: case Url:
return QVariant(QString::fromStdString(url(event))); return QVariant(QString::fromStdString(url(event)));