Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions shell/plugins/notifications/NotificationLogic.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,24 @@ function styledBody(body, app, appIcon) {
return stripImageTags(sanitizeBody(body, app, appIcon).replace(/\r\n|\r|\n/g, "<br/>"))
}

// KDE Connect escapes the title and body it relays from a phone before handing
// them to KNotification, and they reach this server escaped a second time, so
// a quotation mark arrives as &amp;quot; and StyledText shows &quot; on the
// card. Undo that one layer for it. Only &amp; is rewritten: it is the sole
// entity the double escape manufactures, and the rewrite can never produce a
// `<`, so no tag stripImageTags did not see can come out of it.
function isKdeConnect(app, appIcon) {
var source = (String(app || "") + "\n" + String(appIcon || "")).toLowerCase()
return source.indexOf("kdeconnect") >= 0 || source.indexOf("kde connect") >= 0
}

function undoDoubleEscape(text) {
return text.replace(/&amp;(?=(?:[a-z]+|#\d+|#x[0-9a-f]+);)/gi, "&")
}

function sanitizeBody(body, app, appIcon) {
var text = stripImageTags(String(body || ""))
if (isKdeConnect(app, appIcon)) text = undoDoubleEscape(text)
if (!isChromiumDerived(app, appIcon)) return text

return text
Expand Down Expand Up @@ -450,6 +466,7 @@ if (typeof module !== "undefined") {
module.exports = {
isChromiumDerived: isChromiumDerived,
sanitizeBody: sanitizeBody,
isKdeConnect: isKdeConnect,
styledBody: styledBody,
summaryStartsWithGlyph: summaryStartsWithGlyph,
shouldBypassDnd: shouldBypassDnd,
Expand Down
27 changes: 27 additions & 0 deletions test/shell.d/notifications-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,33 @@ assertEqual(
'notifications strip inline image tags'
)

assert(notifications.isKdeConnect('KDE Connect', ''), 'notifications detect KDE Connect by name')
assert(notifications.isKdeConnect('', 'kdeconnect'), 'notifications detect KDE Connect by icon')
assert(!notifications.isKdeConnect('Slack', ''), 'notifications do not treat unrelated apps as KDE Connect')

// KDE Connect relays phone notifications escaped twice over, so the card
// would otherwise show the entity names themselves.
assertEqual(
notifications.sanitizeBody('Someone: Reacted \u{1F913} to &amp;quot;\u{1F917}&amp;quot;', 'KDE Connect', ''),
'Someone: Reacted \u{1F913} to &quot;\u{1F917}&quot;',
'notifications undo one layer of escaping on KDE Connect bodies'
)
assertEqual(
notifications.sanitizeBody('a &amp;lt;b&amp;gt; c &amp;#39;d&amp;#39; &amp;#x27;e&amp;#x27;', 'KDE Connect', ''),
'a &lt;b&gt; c &#39;d&#39; &#x27;e&#x27;',
'the undone layer leaves the text escaped once, never as live markup'
)
assertEqual(
notifications.sanitizeBody('Tom &amp; Jerry &amp; friends', 'KDE Connect', ''),
'Tom &amp; Jerry &amp; friends',
'a bare &amp; that escapes nothing further is left alone'
)
assertEqual(
notifications.sanitizeBody('Someone: &amp;quot;hi&amp;quot;', 'Slack', ''),
'Someone: &amp;quot;hi&amp;quot;',
'other apps keep their entities as sent'
)

// The body renders as StyledText, which fetches <img src> over the network. The
// invariant that matters is not a particular output string but that no tag Qt
// would honour as an image survives, so assert that directly. Tags are bounded
Expand Down