From 5df79d012919891ab0d71876ca765c682e49e792 Mon Sep 17 00:00:00 2001
From: Eden Yemini <eden881@gmail.com>
Date: Mon, 27 Jan 2025 18:26:46 +0200
Subject: [PATCH] Pass description through sanitize(marked())

---
 src/components/SafeLinks.vue | 65 ------------------------------------
 src/pages/Details.vue        | 19 +++++++----
 2 files changed, 13 insertions(+), 71 deletions(-)
 delete mode 100644 src/components/SafeLinks.vue

diff --git a/src/components/SafeLinks.vue b/src/components/SafeLinks.vue
deleted file mode 100644
index 19d95cbd7..000000000
--- a/src/components/SafeLinks.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<template>
-    <span>
-        <template v-for="(part, index) in parts" :key="index">
-            <a
-                v-if="part.type === 'link'"
-                :href="part.content"
-                target="_blank"
-                rel="noopener noreferrer"
-            >{{ part.content }}</a>
-            <span v-else>{{ part.content }}</span>
-        </template>
-    </span>
-</template>
-
-<script>
-export default {
-    name: "SafeLinks",
-    props: {
-        text: {
-            type: String,
-            required: true
-        }
-    },
-
-    computed: {
-        parts() {
-            if (!this.text) {
-                return [];
-            }
-
-            const urlPattern = /(\b(?:https?|ftp|file|smb|ssh|telnet|ldap|git):\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi;
-            const parts = [];
-            let lastIndex = 0;
-
-            this.text.replace(urlPattern, (match, url, offset) => {
-                // Add text before the link
-                if (offset > lastIndex) {
-                    parts.push({
-                        type: "text",
-                        content: this.text.slice(lastIndex, offset)
-                    });
-                }
-
-                // Add the link
-                parts.push({
-                    type: "link",
-                    content: url
-                });
-
-                lastIndex = offset + match.length;
-            });
-
-            // Add remaining text after last link
-            if (lastIndex < this.text.length) {
-                parts.push({
-                    type: "text",
-                    content: this.text.slice(lastIndex)
-                });
-            }
-
-            return parts;
-        }
-    }
-};
-</script>
diff --git a/src/pages/Details.vue b/src/pages/Details.vue
index 1a4f78b6e..1d068b92e 100644
--- a/src/pages/Details.vue
+++ b/src/pages/Details.vue
@@ -9,9 +9,8 @@
                     <div>{{ monitor.id }}</div>
                 </div>
             </h1>
-            <p v-if="monitor.description">
-                <SafeLinks :text="monitor.description" />
-            </p>
+            <!-- eslint-disable-next-line vue/no-v-html-->
+            <p v-if="monitor.description" v-html="descriptionHTML"></p>
             <div class="d-flex">
                 <div class="tags">
                     <Tag v-for="tag in monitor.tags" :key="tag.id" :item="tag" :size="'sm'" />
@@ -281,13 +280,14 @@ import Status from "../components/Status.vue";
 import Datetime from "../components/Datetime.vue";
 import CountUp from "../components/CountUp.vue";
 import Uptime from "../components/Uptime.vue";
-import SafeLinks from "../components/SafeLinks.vue";
 import Pagination from "v-pagination-3";
 const PingChart = defineAsyncComponent(() => import("../components/PingChart.vue"));
 import Tag from "../components/Tag.vue";
 import CertificateInfo from "../components/CertificateInfo.vue";
 import { getMonitorRelativeURL } from "../util.ts";
 import { URL } from "whatwg-url";
+import DOMPurify from "dompurify";
+import { marked } from "marked";
 import { getResBaseURL } from "../util-frontend";
 import { highlight, languages } from "prismjs/components/prism-core";
 import "prismjs/components/prism-clike";
@@ -310,8 +310,7 @@ export default {
         Tag,
         CertificateInfo,
         PrismEditor,
-        ScreenshotDialog,
-        SafeLinks,
+        ScreenshotDialog
     },
     data() {
         return {
@@ -403,6 +402,14 @@ export default {
 
         screenshotURL() {
             return getResBaseURL() + this.monitor.screenshot + "?time=" + this.cacheTime;
+        },
+
+        descriptionHTML() {
+            if (this.monitor.description != null) {
+                return DOMPurify.sanitize(marked(this.monitor.description));
+            } else {
+                return "";
+            }
         }
     },