|
|
b5cc75 |
diff --git a/plasma/generic/dataengines/notifications/CMakeLists.txt b/plasma/generic/dataengines/notifications/CMakeLists.txt
|
|
|
b5cc75 |
index cf34971..e1d567f 100644
|
|
|
b5cc75 |
--- a/plasma/generic/dataengines/notifications/CMakeLists.txt
|
|
|
b5cc75 |
+++ b/plasma/generic/dataengines/notifications/CMakeLists.txt
|
|
|
b5cc75 |
@@ -2,6 +2,7 @@ set(notifications_engine_SRCS
|
|
|
b5cc75 |
notificationsengine.cpp
|
|
|
b5cc75 |
notificationservice.cpp
|
|
|
b5cc75 |
notificationaction.cpp
|
|
|
b5cc75 |
+ notificationsanitizer.cpp
|
|
|
b5cc75 |
)
|
|
|
b5cc75 |
|
|
|
b5cc75 |
qt4_add_dbus_adaptor( notifications_engine_SRCS org.freedesktop.Notifications.xml notificationsengine.h NotificationsEngine )
|
|
|
b5cc75 |
@@ -13,3 +14,15 @@ target_link_libraries(plasma_engine_notifications ${KDE4_PLASMA_LIBS} ${KDE4_KDE
|
|
|
b5cc75 |
install(TARGETS plasma_engine_notifications DESTINATION ${PLUGIN_INSTALL_DIR})
|
|
|
b5cc75 |
install(FILES plasma-dataengine-notifications.desktop DESTINATION ${SERVICES_INSTALL_DIR} )
|
|
|
b5cc75 |
install(FILES notifications.operations DESTINATION ${DATA_INSTALL_DIR}/plasma/services)
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+set(notificationstest_SRCS notificationsanitizer.cpp notifications_test.cpp)
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+kde4_add_unit_test( notificationstest
|
|
|
b5cc75 |
+ TESTNAME notifications-notificationstest
|
|
|
b5cc75 |
+ ${notificationstest_SRCS}
|
|
|
b5cc75 |
+)
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+target_link_libraries(notificationstest
|
|
|
b5cc75 |
+ ${QT_QTTEST_LIBRARY}
|
|
|
b5cc75 |
+ ${KDE4_KDECORE_LIBS}
|
|
|
b5cc75 |
+)
|
|
|
b5cc75 |
\ No newline at end of file
|
|
|
b5cc75 |
diff --git a/plasma/generic/dataengines/notifications/notifications_test.cpp b/plasma/generic/dataengines/notifications/notifications_test.cpp
|
|
|
b5cc75 |
new file mode 100644
|
|
|
b5cc75 |
index 0000000..ffa5187
|
|
|
b5cc75 |
--- /dev/null
|
|
|
b5cc75 |
+++ b/plasma/generic/dataengines/notifications/notifications_test.cpp
|
|
|
b5cc75 |
@@ -0,0 +1,68 @@
|
|
|
b5cc75 |
+#include <QtTest>
|
|
|
b5cc75 |
+#include <QObject>
|
|
|
b5cc75 |
+#include <QDebug>
|
|
|
b5cc75 |
+#include "notificationsanitizer.h"
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+class NotificationTest : public QObject
|
|
|
b5cc75 |
+{
|
|
|
b5cc75 |
+ Q_OBJECT
|
|
|
b5cc75 |
+public:
|
|
|
b5cc75 |
+ NotificationTest() {}
|
|
|
b5cc75 |
+private Q_SLOTS:
|
|
|
b5cc75 |
+ void parse_data();
|
|
|
b5cc75 |
+ void parse();
|
|
|
b5cc75 |
+};
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+void NotificationTest::parse_data()
|
|
|
b5cc75 |
+{
|
|
|
b5cc75 |
+ QTest::addColumn<QString>("messageIn");
|
|
|
b5cc75 |
+ QTest::addColumn<QString>("expectedOut");
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ QTest::newRow("basic no HTML") << "I am a notification" << "I am a notification";
|
|
|
b5cc75 |
+ QTest::newRow("whitespace") << " I am a notification " << "I am a notification";
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ QTest::newRow("basic html") << "I am the notification" << "I am the notification";
|
|
|
b5cc75 |
+ QTest::newRow("nested html") << "I am the notification" << "I am the notification";
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ QTest::newRow("no extra tags") << "I am <blink>the</blink> notification" << "I am the notification";
|
|
|
b5cc75 |
+ QTest::newRow("no extra attrs") << "I am the notification" << "I am the notification";
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ QTest::newRow("newlines") << "I am\nthe\nnotification" << "I am the notification";
|
|
|
b5cc75 |
+ QTest::newRow("multinewlines") << "I am\n\nthe\n\n\nnotification" << "I am the notification";
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ QTest::newRow("amp") << "me&you" << "me&you";
|
|
|
b5cc75 |
+ QTest::newRow("double escape") << "foo & <bar>" << "foo & <bar>";
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ QTest::newRow("quotes") << "'foo'" << "'foo'";//as label can't handle this normally valid entity
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ QTest::newRow("image normal") << "This is and more text" << "This is and more text";
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ //this input is technically wrong, so the output is also wrong, but QTextHtmlParser does the "right" thing
|
|
|
b5cc75 |
+ QTest::newRow("image normal no close") << "This is and more text" << "This is and more text";
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ QTest::newRow("image remote URL") << "This is and more text" << "This is and more text";
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ //more bad formatted options. To some extent actual output doesn't matter. Garbage in, garbabe out.
|
|
|
b5cc75 |
+ //the important thing is that it doesn't contain anything that could be parsed as the remote URL
|
|
|
b5cc75 |
+ QTest::newRow("image remote URL no close") << "This is \" alt=\"cheese\"> and more text" << "This is and more text";
|
|
|
b5cc75 |
+ QTest::newRow("image remote URL double open") << "This is <\" and more text" << "This is ";
|
|
|
b5cc75 |
+ QTest::newRow("image remote URL no entitiy close") << "This is
|
|
|
b5cc75 |
+ QTest::newRow("image remote URL space in element name") << "This is < img src=\"http://foo.com/boo.png\" alt=\"cheese\" /> and more text" << "This is ";
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ QTest::newRow("link") << "This is a link and more text" << "This is a link and more text";
|
|
|
b5cc75 |
+}
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+void NotificationTest::parse()
|
|
|
b5cc75 |
+{
|
|
|
b5cc75 |
+ QFETCH(QString, messageIn);
|
|
|
b5cc75 |
+ QFETCH(QString, expectedOut);
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ const QString out = NotificationSanitizer::parse(messageIn);
|
|
|
b5cc75 |
+ expectedOut = "<html>" + expectedOut + "</html>\n";
|
|
|
b5cc75 |
+ QCOMPARE(out, expectedOut);
|
|
|
b5cc75 |
+}
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+QTEST_MAIN(NotificationTest)
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+#include "notificationtest.moc"
|
|
|
b5cc75 |
\ No newline at end of file
|
|
|
b5cc75 |
diff --git a/plasma/generic/dataengines/notifications/notificationsanitizer.cpp b/plasma/generic/dataengines/notifications/notificationsanitizer.cpp
|
|
|
b5cc75 |
new file mode 100644
|
|
|
b5cc75 |
index 0000000..8750958
|
|
|
b5cc75 |
--- /dev/null
|
|
|
b5cc75 |
+++ b/plasma/generic/dataengines/notifications/notificationsanitizer.cpp
|
|
|
b5cc75 |
@@ -0,0 +1,106 @@
|
|
|
b5cc75 |
+/*
|
|
|
b5cc75 |
+ * Copyright (C) 2017 David Edmundson <davidedmundson@kde.org>
|
|
|
b5cc75 |
+ *
|
|
|
b5cc75 |
+ * This program is free software you can redistribute it and/or
|
|
|
b5cc75 |
+ * modify it under the terms of the GNU Library General Public
|
|
|
b5cc75 |
+ * License as published by the Free Software Foundation; either
|
|
|
b5cc75 |
+ * version 2 of the License, or (at your option) any later version.
|
|
|
b5cc75 |
+ *
|
|
|
b5cc75 |
+ * This program is distributed in the hope that it will be useful,
|
|
|
b5cc75 |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
b5cc75 |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
b5cc75 |
+ * Library General Public License for more details.
|
|
|
b5cc75 |
+ *
|
|
|
b5cc75 |
+ * You should have received a copy of the GNU Library General Public License
|
|
|
b5cc75 |
+ * along with this library; see the file COPYING.LIB. If not, write to
|
|
|
b5cc75 |
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
|
b5cc75 |
+ * Boston, MA 02110-1301, USA.
|
|
|
b5cc75 |
+*/
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+#include "notificationsanitizer.h"
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+#include <QXmlStreamReader>
|
|
|
b5cc75 |
+#include <QXmlStreamWriter>
|
|
|
b5cc75 |
+#include <QRegExp>
|
|
|
b5cc75 |
+#include <QDebug>
|
|
|
b5cc75 |
+#include <QUrl>
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+QString NotificationSanitizer::parse(const QString &text)
|
|
|
b5cc75 |
+{
|
|
|
b5cc75 |
+ // replace all \ns with
|
|
|
b5cc75 |
+ QString t = text;
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ t.replace(QLatin1String("\n"), QLatin1String(" "));
|
|
|
b5cc75 |
+ // Now remove all inner whitespace (\ns are already s)
|
|
|
b5cc75 |
+ t = t.simplified();
|
|
|
b5cc75 |
+ // Finally, check if we don't have multiple s following,
|
|
|
b5cc75 |
+ // can happen for example when "\n \n" is sent, this replaces
|
|
|
b5cc75 |
+ // all s in succsession with just one
|
|
|
b5cc75 |
+ t.replace(QRegExp(QLatin1String(" \\s* (\\s| )*")), QLatin1String(" "));
|
|
|
b5cc75 |
+ // This fancy RegExp escapes every occurence of & since QtQuick Text will blatantly cut off
|
|
|
b5cc75 |
+ // text where it finds a stray ampersand.
|
|
|
b5cc75 |
+ // Only &{apos, quot, gt, lt, amp}; as well as { character references will be allowed
|
|
|
b5cc75 |
+ t.replace(QRegExp(QLatin1String("&(?!(?:apos|quot|[gl]t|amp);|#)")), QLatin1String("&"));
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ QXmlStreamReader r(QLatin1String("<html>") + t + QLatin1String("</html>"));
|
|
|
b5cc75 |
+ QString result;
|
|
|
b5cc75 |
+ QXmlStreamWriter out(&result);
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ QVector<QString> allowedTags;
|
|
|
b5cc75 |
+ allowedTags << "b" << "i" << "u" << "img" << "a" << "html"<< "br";
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ out.writeStartDocument();
|
|
|
b5cc75 |
+ while (!r.atEnd()) {
|
|
|
b5cc75 |
+ r.readNext();
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ if (r.tokenType() == QXmlStreamReader::StartElement) {
|
|
|
b5cc75 |
+ const QString name = r.name().toString();
|
|
|
b5cc75 |
+ if (!allowedTags.contains(name)) {
|
|
|
b5cc75 |
+ continue;
|
|
|
b5cc75 |
+ }
|
|
|
b5cc75 |
+ out.writeStartElement(name);
|
|
|
b5cc75 |
+ if (name == QLatin1String("img")) {
|
|
|
b5cc75 |
+ QString src = r.attributes().value("src").toString();
|
|
|
b5cc75 |
+ QString alt = r.attributes().value("alt").toString();
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ const QUrl url(src);
|
|
|
b5cc75 |
+ if (url.isLocalFile()) {
|
|
|
b5cc75 |
+ out.writeAttribute(QLatin1String("src"), src);
|
|
|
b5cc75 |
+ } else {
|
|
|
b5cc75 |
+ //image denied for security reasons! Do not copy the image src here!
|
|
|
b5cc75 |
+ }
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ out.writeAttribute(QLatin1String("alt"), alt);
|
|
|
b5cc75 |
+ }
|
|
|
b5cc75 |
+ if (name == QLatin1String("a")) {
|
|
|
b5cc75 |
+ out.writeAttribute(QLatin1String("href"), r.attributes().value("href").toString());
|
|
|
b5cc75 |
+ }
|
|
|
b5cc75 |
+ }
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ if (r.tokenType() == QXmlStreamReader::EndElement) {
|
|
|
b5cc75 |
+ const QString name = r.name().toString();
|
|
|
b5cc75 |
+ if (!allowedTags.contains(name)) {
|
|
|
b5cc75 |
+ continue;
|
|
|
b5cc75 |
+ }
|
|
|
b5cc75 |
+ out.writeEndElement();
|
|
|
b5cc75 |
+ }
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ if (r.tokenType() == QXmlStreamReader::Characters) {
|
|
|
b5cc75 |
+ const QString text = r.text().toString();
|
|
|
b5cc75 |
+ out.writeCharacters(text); //this auto escapes chars -> HTML entities
|
|
|
b5cc75 |
+ }
|
|
|
b5cc75 |
+ }
|
|
|
b5cc75 |
+ out.writeEndDocument();
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ if (r.hasError()) {
|
|
|
b5cc75 |
+ qWarning() << "Notification to send to backend contains invalid XML: "
|
|
|
b5cc75 |
+ << r.errorString() << "line" << r.lineNumber()
|
|
|
b5cc75 |
+ << "col" << r.columnNumber();
|
|
|
b5cc75 |
+ }
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ // The Text.StyledText format handles only html3.2 stuff and ' is html4 stuff
|
|
|
b5cc75 |
+ // so we need to replace it here otherwise it will not render at all.
|
|
|
b5cc75 |
+ result = result.replace(QLatin1String("'"), QChar('\''));
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+ return result;
|
|
|
b5cc75 |
+}
|
|
|
b5cc75 |
diff --git a/plasma/generic/dataengines/notifications/notificationsanitizer.h b/plasma/generic/dataengines/notifications/notificationsanitizer.h
|
|
|
b5cc75 |
new file mode 100644
|
|
|
b5cc75 |
index 0000000..b0c3ccd
|
|
|
b5cc75 |
--- /dev/null
|
|
|
b5cc75 |
+++ b/plasma/generic/dataengines/notifications/notificationsanitizer.h
|
|
|
b5cc75 |
@@ -0,0 +1,35 @@
|
|
|
b5cc75 |
+/*
|
|
|
b5cc75 |
+ * Copyright (C) 2017 David Edmundson <davidedmundson@kde.org>
|
|
|
b5cc75 |
+ *
|
|
|
b5cc75 |
+ * This program is free software you can redistribute it and/or
|
|
|
b5cc75 |
+ * modify it under the terms of the GNU Library General Public
|
|
|
b5cc75 |
+ * License as published by the Free Software Foundation; either
|
|
|
b5cc75 |
+ * version 2 of the License, or (at your option) any later version.
|
|
|
b5cc75 |
+ *
|
|
|
b5cc75 |
+ * This program is distributed in the hope that it will be useful,
|
|
|
b5cc75 |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
b5cc75 |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
b5cc75 |
+ * Library General Public License for more details.
|
|
|
b5cc75 |
+ *
|
|
|
b5cc75 |
+ * You should have received a copy of the GNU Library General Public License
|
|
|
b5cc75 |
+ * along with this library; see the file COPYING.LIB. If not, write to
|
|
|
b5cc75 |
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
|
b5cc75 |
+ * Boston, MA 02110-1301, USA.
|
|
|
b5cc75 |
+*/
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+#include <QString>
|
|
|
b5cc75 |
+
|
|
|
b5cc75 |
+namespace NotificationSanitizer
|
|
|
b5cc75 |
+{
|
|
|
b5cc75 |
+ /*
|
|
|
b5cc75 |
+ * This turns generic random text of either plain text of any degree of faux-HTML into HTML allowed
|
|
|
b5cc75 |
+ * in the notification spec namely:
|
|
|
b5cc75 |
+ * a, img, b, i, u and br
|
|
|
b5cc75 |
+ * All other tags and attributes are stripped
|
|
|
b5cc75 |
+ * Whitespace is stripped and converted to
|
|
|
b5cc75 |
+ * Double newlines are compressed
|
|
|
b5cc75 |
+ *
|
|
|
b5cc75 |
+ * Image src is only copied when referring to a local file
|
|
|
b5cc75 |
+ */
|
|
|
b5cc75 |
+ QString parse(const QString &in);
|
|
|
b5cc75 |
+}
|