You've already forked AstralRinth
forked from didirus/AstralRinth
feat: Improve iframe parameter handling (#1299)
* fix(utils): Fix TypeScript issues in `parse.ts` * feat: Improve iframe parameter handling (modrinth/knossos#362) - Remove not whitelisted parameters instead of not rendering whole iframe - Allowed `start` and `end` parameters in YouTube embeds
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
import MarkdownIt from 'markdown-it'
|
import MarkdownIt from 'markdown-it'
|
||||||
import xss from 'xss'
|
import { escapeAttrValue, FilterXSS, safeAttrValue, whiteList } from 'xss'
|
||||||
|
|
||||||
export const configuredXss = new xss.FilterXSS({
|
export const configuredXss = new FilterXSS({
|
||||||
whiteList: {
|
whiteList: {
|
||||||
...xss.whiteList,
|
...whiteList,
|
||||||
summary: [],
|
summary: [],
|
||||||
h1: ['id'],
|
h1: ['id'],
|
||||||
h2: ['id'],
|
h2: ['id'],
|
||||||
@@ -14,12 +14,12 @@ export const configuredXss = new xss.FilterXSS({
|
|||||||
kbd: ['id'],
|
kbd: ['id'],
|
||||||
input: ['checked', 'disabled', 'type'],
|
input: ['checked', 'disabled', 'type'],
|
||||||
iframe: ['width', 'height', 'allowfullscreen', 'frameborder', 'start', 'end'],
|
iframe: ['width', 'height', 'allowfullscreen', 'frameborder', 'start', 'end'],
|
||||||
img: [...xss.whiteList.img, 'usemap', 'style'],
|
img: [...(whiteList.img || []), 'usemap', 'style'],
|
||||||
map: ['name'],
|
map: ['name'],
|
||||||
area: [...xss.whiteList.a, 'coords'],
|
area: [...(whiteList.a || []), 'coords'],
|
||||||
a: [...xss.whiteList.a, 'rel'],
|
a: [...(whiteList.a || []), 'rel'],
|
||||||
td: [...xss.whiteList.td, 'style'],
|
td: [...(whiteList.td || []), 'style'],
|
||||||
th: [...xss.whiteList.th, 'style'],
|
th: [...(whiteList.th || []), 'style'],
|
||||||
picture: [],
|
picture: [],
|
||||||
source: ['media', 'sizes', 'src', 'srcset', 'type'],
|
source: ['media', 'sizes', 'src', 'srcset', 'type'],
|
||||||
},
|
},
|
||||||
@@ -35,35 +35,43 @@ export const configuredXss = new xss.FilterXSS({
|
|||||||
if (tag === 'iframe' && name === 'src') {
|
if (tag === 'iframe' && name === 'src') {
|
||||||
const allowedSources = [
|
const allowedSources = [
|
||||||
{
|
{
|
||||||
regex:
|
url: /^https?:\/\/(www\.)?youtube(-nocookie)?\.com\/embed\/[a-zA-Z0-9_-]{11}/,
|
||||||
/^https?:\/\/(www\.)?youtube(-nocookie)?\.com\/embed\/[a-zA-Z0-9_-]{11}(\?&autoplay=[0-1]{1})?$/,
|
allowedParameters: [/start=\d+/, /end=\d+/],
|
||||||
remove: ['&autoplay=1'], // Prevents autoplay
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
regex: /^https?:\/\/(www\.)?discord\.com\/widget\?id=\d{18,19}(&theme=\w+)?$/,
|
url: /^https?:\/\/(www\.)?discord\.com\/widget/,
|
||||||
remove: [/&theme=\w+/],
|
allowedParameters: [/id=\d{18,19}/],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const url = new URL(value)
|
||||||
|
|
||||||
for (const source of allowedSources) {
|
for (const source of allowedSources) {
|
||||||
if (source.regex.test(value)) {
|
if (!source.url.test(url.href)) {
|
||||||
for (const remove of source.remove) {
|
continue
|
||||||
value = value.replace(remove, '')
|
|
||||||
}
|
|
||||||
return `${name}="${xss.escapeAttrValue(value)}"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newSearchParams = new URLSearchParams()
|
||||||
|
url.searchParams.forEach((value, key) => {
|
||||||
|
if (!source.allowedParameters.some((param) => param.test(`${key}=${value}`))) {
|
||||||
|
newSearchParams.delete(key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
url.search = newSearchParams.toString()
|
||||||
|
return `${name}="${escapeAttrValue(url.toString())}"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For Highlight.JS
|
// For Highlight.JS
|
||||||
if (name === 'class' && ['pre', 'code', 'span'].includes(tag)) {
|
if (name === 'class' && ['pre', 'code', 'span'].includes(tag)) {
|
||||||
const allowedClasses = []
|
const allowedClasses: string[] = []
|
||||||
for (const className of value.split(/\s/g)) {
|
for (const className of value.split(/\s/g)) {
|
||||||
if (className.startsWith('hljs-') || className.startsWith('language-')) {
|
if (className.startsWith('hljs-') || className.startsWith('language-')) {
|
||||||
allowedClasses.push(className)
|
allowedClasses.push(className)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return `${name}="${xss.escapeAttrValue(allowedClasses.join(' '))}"`
|
return `${name}="${escapeAttrValue(allowedClasses.join(' '))}"`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
safeAttrValue(tag, name, value, cssFilter) {
|
safeAttrValue(tag, name, value, cssFilter) {
|
||||||
@@ -92,7 +100,7 @@ export const configuredXss = new xss.FilterXSS({
|
|||||||
]
|
]
|
||||||
|
|
||||||
if (!allowedHostnames.includes(url.hostname)) {
|
if (!allowedHostnames.includes(url.hostname)) {
|
||||||
return xss.safeAttrValue(
|
return safeAttrValue(
|
||||||
tag,
|
tag,
|
||||||
name,
|
name,
|
||||||
`https://wsrv.nl/?url=${encodeURIComponent(
|
`https://wsrv.nl/?url=${encodeURIComponent(
|
||||||
@@ -101,13 +109,13 @@ export const configuredXss = new xss.FilterXSS({
|
|||||||
cssFilter,
|
cssFilter,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return xss.safeAttrValue(tag, name, url.toString(), cssFilter)
|
return safeAttrValue(tag, name, url.toString(), cssFilter)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
/* empty */
|
/* empty */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return xss.safeAttrValue(tag, name, value, cssFilter)
|
return safeAttrValue(tag, name, value, cssFilter)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -129,7 +137,7 @@ export const md = (options = {}) => {
|
|||||||
const token = tokens[idx]
|
const token = tokens[idx]
|
||||||
const index = token.attrIndex('href')
|
const index = token.attrIndex('href')
|
||||||
|
|
||||||
if (index !== -1) {
|
if (token.attrs && index !== -1) {
|
||||||
const href = token.attrs[index][1]
|
const href = token.attrs[index][1]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -152,4 +160,4 @@ export const md = (options = {}) => {
|
|||||||
return md
|
return md
|
||||||
}
|
}
|
||||||
|
|
||||||
export const renderString = (string) => configuredXss.process(md().render(string))
|
export const renderString = (string: string) => configuredXss.process(md().render(string))
|
||||||
|
|||||||
Reference in New Issue
Block a user