fix missing HTML escape in RSS feed description and accidental diff in titles default tip

Mon, 22 Sep 2025 20:00:59 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 22 Sep 2025 20:00:59 +0200
changeset 391
49f68aeb1dd2
parent 390
b2f8cce6a160

fix missing HTML escape in RSS feed description and accidental diff in titles

fixes #724 fixes #729

src/main/kotlin/de/uapcore/lightpit/Constants.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/changelogs/changelog-de.jspf file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/changelogs/changelog.jspf file | annotate | diff | comparison | revisions
--- a/src/main/kotlin/de/uapcore/lightpit/Constants.kt	Mon Sep 15 20:13:22 2025 +0200
+++ b/src/main/kotlin/de/uapcore/lightpit/Constants.kt	Mon Sep 22 20:00:59 2025 +0200
@@ -29,7 +29,7 @@
     /**
      * A data in yyyy-mm-dd format to identify the release.
      */
-    const val VERSION_DATE = "2025-09-14"
+    const val VERSION_DATE = "2025-09-22"
 
     /**
      * The path where the JSP files reside.
--- a/src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt	Mon Sep 15 20:13:22 2025 +0200
+++ b/src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt	Mon Sep 22 20:00:59 2025 +0200
@@ -54,11 +54,14 @@
         .mergeOriginalRevised(true)
         .inlineDiffByWord(true)
         .reportLinesUnchanged(true)
+        .lineNormalizer { it } // do not normalize - we escape the HTML ourselves
         .oldTag { start -> if (start) "<strike style=\"color:red\">" else "</strike>" }
         .newTag { start -> if (start) "<i style=\"color: green\">" else "</i>" }
         .build()
     )
 
+    fun escapeHtml(s: String): String = s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
+
     fun calculateDiff(earlier: String, later: String): String =
         diffGenerator.generateDiffRows(
             earlier.replace("\r", "").split("\n"),
@@ -76,40 +79,50 @@
         CommentDiff(
             issueid = data.issueid,
             id = data.commentid,
-            project = data.project,
-            currentSubject = data.subject,
-            comment = data.comment.replace("\r", "")
+            project = escapeHtml(data.project),
+            currentSubject = escapeHtml(data.subject),
+            comment = escapeHtml(data.comment.replace("\r", ""))
         )
 
     private fun diffContent(cur: IssueCommentHistoryEntry, next: IssueCommentHistoryEntry) =
         CommentDiff(
             issueid = cur.issueid,
             id = cur.commentid,
-            project = cur.project,
-            currentSubject = cur.subject,
-            comment = calculateDiff(next.comment, cur.comment)
+            project = escapeHtml(cur.project),
+            currentSubject = escapeHtml(cur.subject),
+            comment = calculateDiff(escapeHtml(next.comment), escapeHtml(cur.comment))
         )
 
-    private fun fullContent(http: HttpRequest, issue: IssueHistoryEntry) = IssueDiff(
-        id = issue.issueid,
-        project = issue.project,
-        component = issue.component,
-        status = http.i18n("issue.status."+issue.status.name),
-        category = http.i18n("issue.category."+issue.category.name),
-        subject = issue.subject,
-        description = issue.description,
-        assignee = issue.assignee,
-        eta = issue.eta?.let { SimpleDateFormat("dd.MM.yyyy").format(it) } ?: "",
-        affected = issue.affected,
-        resolved = issue.resolved
-    )
+    private fun fullContent(http: HttpRequest, issue: IssueHistoryEntry): IssueDiff {
+        val status = http.i18n("issue.status."+issue.status.name)
+        val category = http.i18n("issue.category."+issue.category.name)
+        val subject = escapeHtml(issue.subject)
+        return IssueDiff(
+            id = issue.issueid,
+            project = escapeHtml(issue.project),
+            currentSubject = subject,
+            currentCategory = category,
+            component = escapeHtml(issue.component),
+            status = status,
+            category = category,
+            subject = escapeHtml(issue.subject),
+            description = escapeHtml(issue.description),
+            assignee = escapeHtml(issue.assignee),
+            eta = issue.eta?.let { SimpleDateFormat("dd.MM.yyyy").format(it) } ?: "",
+            affected = escapeHtml(issue.affected),
+            resolved = escapeHtml(issue.resolved)
+        )
+    }
 
     private fun diffContent(http: HttpRequest, cur: IssueHistoryEntry, next: IssueHistoryEntry): IssueDiff {
         val earlier = fullContent(http, next)
         val later = fullContent(http, cur)
+        // no need to escape HTML again, because fullContent already did it
         return IssueDiff(
             id = later.id,
             project = later.project,
+            currentSubject = later.subject,
+            currentCategory = later.category,
             subject = calculateDiff(earlier.subject, later.subject),
             component = calculateDiff(earlier.component, later.component),
             status = calculateDiff(earlier.status, later.status),
--- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt	Mon Sep 15 20:13:22 2025 +0200
+++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt	Mon Sep 22 20:00:59 2025 +0200
@@ -34,19 +34,18 @@
 class IssueDiff(
     val id: Int,
     val project: String,
-    var component: String,
-    var status: String,
-    var category: String,
-    var subject: String,
-    var description: String,
-    var assignee: String,
-    var eta: String,
-    var affected: String,
-    var resolved: String,
-) {
-    var currentCategory: String = category
-    val currentSubject: String = subject
-}
+    val currentSubject: String,
+    val currentCategory: String,
+    val component: String,
+    val status: String,
+    val category: String,
+    val subject: String,
+    val description: String,
+    val assignee: String,
+    val eta: String,
+    val affected: String,
+    val resolved: String,
+)
 
 class CommentDiff(
     val issueid: Int,
--- a/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf	Mon Sep 15 20:13:22 2025 +0200
+++ b/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf	Mon Sep 22 20:00:59 2025 +0200
@@ -36,6 +36,7 @@
     <li>Die Vorschläge in den Suchfeldern für Vorgänge sind nun absteigend nach Vorgangsnummer sortiert.</li>
     <li>Die Standardkategorie für neue Vorgänge in veröffentlichten Versionen ist nun "Fehler" anstelle von "Feature".</li>
     <li>Fehlerhafte Delta-Anzeige in RSS-Feeds behoben.</li>
+    <li>Ungefiltertes HTML aus Vorgangsbeschreibungen in RSS-Feeds behoben.</li>
     <li>Vorgänge können nicht länger mit sich selbst verlinkt werden.</li>
     <li>Fehler in der Deutschen Übersetzung behoben.</li>
 </ul>
--- a/src/main/webapp/WEB-INF/changelogs/changelog.jspf	Mon Sep 15 20:13:22 2025 +0200
+++ b/src/main/webapp/WEB-INF/changelogs/changelog.jspf	Mon Sep 22 20:00:59 2025 +0200
@@ -36,6 +36,7 @@
     <li>Change that issues suggested by the search boxes are now sorted by ID in descending order.</li>
     <li>Change that the default category for new issues in released versions is Bug instead of Feature.</li>
     <li>Fix wrong diffs in RSS feed.</li>
+    <li>Fix unescaped HTML in RSS feed.</li>
     <li>Fix that issues could relate to themselves.</li>
     <li>Fix errors in the German translation.</li>
 </ul>

mercurial