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 ""; + } } },