Skip to content

Commit c7b3644

Browse files
committed
sanitizeHtml to avoid breaking layout when displaying rendered html
1 parent b1c42a3 commit c7b3644

3 files changed

Lines changed: 54 additions & 4 deletions

File tree

llms/ui/ctx.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
import { reactive, markRaw } from 'vue'
33
import { EventBus, humanize, combinePaths } from "@servicestack/client"
4-
import { storageObject, isHtml } from './utils.mjs'
4+
import { storageObject, isHtml, sanitizeHtml } from './utils.mjs'
55

66
export class ExtensionScope {
77
constructor(ctx, id) {
@@ -397,7 +397,8 @@ export class AppContext {
397397
const header = content.substring(3, headerEnd).trim()
398398
content = '<div class="frontmatter">' + header + '</div>\n' + content.substring(headerEnd + 3)
399399
}
400-
return this.marked.parse(content || '')
400+
const html = this.marked.parse(content || '')
401+
return sanitizeHtml(html)
401402
}
402403

403404
renderContent(content) {

llms/ui/modules/chat/ChatBody.mjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ function hasJsonStructure(str) {
1515
function isEmpty(v) {
1616
return !v || v === '{}' || v === '[]' || v === 'null' || v === 'undefined' || v === '""' || v === "''" || v === "``"
1717
}
18+
1819
function embedHtml(html) {
1920
const resizeScript = `<script>
2021
let lastH = 0;
@@ -512,7 +513,7 @@ export const ToolArguments = {
512513
<TextViewer prefsName="toolArgs" :text="v" />
513514
</td>
514515
<td data-arg="value" v-else class="align-top py-2 px-4 text-sm whitespace-pre-wrap">
515-
<HtmlFormat :value="v" :classes="$utils.htmlFormatClasses" />
516+
<HtmlFormat :value="v" :classes="$utils.htmlFormatClasses" :formatText="$utils.sanitizeHtml" />
516517
</td>
517518
</tr>
518519
</table>
@@ -527,6 +528,7 @@ export const ToolArguments = {
527528
value: String,
528529
},
529530
setup(props) {
531+
const ctx = inject('ctx')
530532
const refArgs = ref()
531533
const maximized = ref({})
532534
const dict = computed(() => {

llms/ui/utils.mjs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ const htmlEntities = {
220220
}
221221

222222
export function encodeHtml(str) {
223-
if (!str) return ''
223+
if (typeof str !== 'string' || !str) return ''
224224
return str.replace(/[&<>"']/g, m => htmlEntities[m]);
225225
}
226226

@@ -242,6 +242,52 @@ function htmlFormatClasses(type, tag, depth, cls, index) {
242242
return cls
243243
}
244244

245+
const dangerousTags = [
246+
'script',
247+
'iframe',
248+
'object',
249+
'embed',
250+
'link',
251+
'style',
252+
'meta',
253+
'base',
254+
'frame',
255+
'frameset',
256+
'applet',
257+
'noscript',
258+
'template'
259+
]
260+
const anyDangerousTag = new RegExp(`<(${dangerousTags.join('|')})`, 'i')
261+
262+
export function sanitizeHtml(html) {
263+
if (!html || typeof html !== 'string') return html
264+
265+
let result = html
266+
let lowerResult = result.toLowerCase()
267+
function updateResult(r) {
268+
result = r
269+
lowerResult = result.toLowerCase()
270+
}
271+
272+
if (anyDangerousTag.test(lowerResult)) {
273+
for (const tag of dangerousTags) {
274+
const tagOpen = `<${tag}`
275+
276+
if (lowerResult.indexOf(tagOpen) === -1) continue
277+
278+
const regex = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, 'gi')
279+
updateResult(result.replace(regex, ''))
280+
281+
if (lowerResult.indexOf(tagOpen) !== -1) {
282+
const selfClosingRegex = new RegExp(`<${tag}[^>]*\\/?>`, 'gi')
283+
updateResult(result.replace(selfClosingRegex, ''))
284+
}
285+
}
286+
}
287+
288+
return result
289+
}
290+
245291
/**
246292
* Returns an ever-increasing unique integer id.
247293
*/
@@ -280,6 +326,7 @@ export function utilsFunctions() {
280326
isHtml,
281327
htmlFormatClasses,
282328
encodeHtml,
329+
sanitizeHtml,
283330
hashString,
284331
}
285332
}

0 commit comments

Comments
 (0)