mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-02-22 11:35:56 +00:00
Avoid rendering HTML, detect links explicitly
This commit is contained in:
parent
1502632433
commit
1a3150351d
2 changed files with 71 additions and 31 deletions
65
src/components/SafeLinks.vue
Normal file
65
src/components/SafeLinks.vue
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<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>
|
|
@ -9,9 +9,9 @@
|
||||||
<div>{{ monitor.id }}</div>
|
<div>{{ monitor.id }}</div>
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<p v-if="monitor.description">
|
||||||
<p v-if="monitor.description" v-html="processedDescription"></p>
|
<SafeLinks :text="monitor.description" />
|
||||||
<!--eslint-enable-->
|
</p>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<div class="tags">
|
<div class="tags">
|
||||||
<Tag v-for="tag in monitor.tags" :key="tag.id" :item="tag" :size="'sm'" />
|
<Tag v-for="tag in monitor.tags" :key="tag.id" :item="tag" :size="'sm'" />
|
||||||
|
@ -281,6 +281,7 @@ import Status from "../components/Status.vue";
|
||||||
import Datetime from "../components/Datetime.vue";
|
import Datetime from "../components/Datetime.vue";
|
||||||
import CountUp from "../components/CountUp.vue";
|
import CountUp from "../components/CountUp.vue";
|
||||||
import Uptime from "../components/Uptime.vue";
|
import Uptime from "../components/Uptime.vue";
|
||||||
|
import SafeLinks from "../components/SafeLinks.vue";
|
||||||
import Pagination from "v-pagination-3";
|
import Pagination from "v-pagination-3";
|
||||||
const PingChart = defineAsyncComponent(() => import("../components/PingChart.vue"));
|
const PingChart = defineAsyncComponent(() => import("../components/PingChart.vue"));
|
||||||
import Tag from "../components/Tag.vue";
|
import Tag from "../components/Tag.vue";
|
||||||
|
@ -309,7 +310,8 @@ export default {
|
||||||
Tag,
|
Tag,
|
||||||
CertificateInfo,
|
CertificateInfo,
|
||||||
PrismEditor,
|
PrismEditor,
|
||||||
ScreenshotDialog
|
ScreenshotDialog,
|
||||||
|
SafeLinks,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -401,19 +403,6 @@ export default {
|
||||||
|
|
||||||
screenshotURL() {
|
screenshotURL() {
|
||||||
return getResBaseURL() + this.monitor.screenshot + "?time=" + this.cacheTime;
|
return getResBaseURL() + this.monitor.screenshot + "?time=" + this.cacheTime;
|
||||||
},
|
|
||||||
|
|
||||||
processedDescription() {
|
|
||||||
if (!this.monitor.description) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const urlPattern = /(\b(?:https?|ftp|file|smb|ssh|telnet|ldap|git):\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi;
|
|
||||||
const processed = this.monitor.description.replace(
|
|
||||||
urlPattern,
|
|
||||||
url => `<a href="${this.escapeHtml(url)}" target="_blank" rel="noopener noreferrer">${this.escapeHtml(url)}</a>`
|
|
||||||
);
|
|
||||||
return processed;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -671,20 +660,6 @@ export default {
|
||||||
.replace("https://example.com/api/push/key?status=up&msg=OK&ping=", this.pushURL);
|
.replace("https://example.com/api/push/key?status=up&msg=OK&ping=", this.pushURL);
|
||||||
this.pushMonitor.code = code;
|
this.pushMonitor.code = code;
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escape HTML
|
|
||||||
* @param {string} unsafe Unsafe string
|
|
||||||
* @returns {string} Safe string
|
|
||||||
*/
|
|
||||||
escapeHtml(unsafe) {
|
|
||||||
return unsafe
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue