mirror of
https://github.com/discourse/discourse.git
synced 2025-05-30 07:11:34 +08:00
FIX: Apply 'allowed_href_schemes' to all src/srcset attributes (#16860)
Previously we were only applying the restriction to `a[href]` and `img[src]`. This commit ensures we apply the same logic to all allowlisted media src attributes.
This commit is contained in:
@ -176,6 +176,7 @@ export const DEFAULT_LIST = [
|
|||||||
"img[title]",
|
"img[title]",
|
||||||
"img[width]",
|
"img[width]",
|
||||||
"img[data-thumbnail]",
|
"img[data-thumbnail]",
|
||||||
|
// img[src] handled by sanitizer.js
|
||||||
"ins",
|
"ins",
|
||||||
"kbd",
|
"kbd",
|
||||||
"li",
|
"li",
|
||||||
@ -203,14 +204,13 @@ export const DEFAULT_LIST = [
|
|||||||
"sub",
|
"sub",
|
||||||
"sup",
|
"sup",
|
||||||
"source[data-orig-src]",
|
"source[data-orig-src]",
|
||||||
"source[src]",
|
// source[src] and source[srcset] handled by sanitizer.js
|
||||||
"source[srcset]",
|
|
||||||
"source[type]",
|
"source[type]",
|
||||||
"track",
|
"track",
|
||||||
"track[default]",
|
"track[default]",
|
||||||
"track[label]",
|
"track[label]",
|
||||||
"track[kind]",
|
"track[kind]",
|
||||||
"track[src]",
|
// track[src] handled by sanitizer.js
|
||||||
"track[srclang]",
|
"track[srclang]",
|
||||||
"ul",
|
"ul",
|
||||||
"video",
|
"video",
|
||||||
|
@ -41,6 +41,38 @@ export function hrefAllowed(href, extraHrefMatchers) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizeMediaSrc(tag, attrName, value, extraHrefMatchers) {
|
||||||
|
const srcAttrs = {
|
||||||
|
img: ["src"],
|
||||||
|
source: ["src", "srcset"],
|
||||||
|
track: ["src"],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!srcAttrs[tag]?.includes(attrName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.startsWith("data:image")) {
|
||||||
|
return attr(attrName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attrName === "srcset") {
|
||||||
|
const srcset = value.split(",").map((v) => v.split(" ", 2));
|
||||||
|
const sanitizedValue = srcset
|
||||||
|
.map((src) => {
|
||||||
|
const allowedSrc = hrefAllowed(src[0], extraHrefMatchers);
|
||||||
|
if (allowedSrc) {
|
||||||
|
return src[1] ? `${allowedSrc} ${src[1]}` : allowedSrc;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join(",");
|
||||||
|
return attr(attrName, sanitizedValue);
|
||||||
|
} else {
|
||||||
|
const returnVal = hrefAllowed(value, extraHrefMatchers);
|
||||||
|
return attr(attrName, returnVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function testDataAttribute(forTag, name, value) {
|
function testDataAttribute(forTag, name, value) {
|
||||||
return Object.keys(forTag).find((k) => {
|
return Object.keys(forTag).find((k) => {
|
||||||
const nameWithMatcher = `^${k.replace(/\*$/, "\\w+?")}`;
|
const nameWithMatcher = `^${k.replace(/\*$/, "\\w+?")}`;
|
||||||
@ -94,10 +126,6 @@ export function sanitize(text, allowLister) {
|
|||||||
(tag === "a" &&
|
(tag === "a" &&
|
||||||
name === "href" &&
|
name === "href" &&
|
||||||
hrefAllowed(value, extraHrefMatchers)) ||
|
hrefAllowed(value, extraHrefMatchers)) ||
|
||||||
(tag === "img" &&
|
|
||||||
name === "src" &&
|
|
||||||
(/^data:image.*$/i.test(value) ||
|
|
||||||
hrefAllowed(value, extraHrefMatchers))) ||
|
|
||||||
(tag === "iframe" &&
|
(tag === "iframe" &&
|
||||||
name === "src" &&
|
name === "src" &&
|
||||||
allowedIframes.some((i) => {
|
allowedIframes.some((i) => {
|
||||||
@ -107,6 +135,16 @@ export function sanitize(text, allowLister) {
|
|||||||
return attr(name, value);
|
return attr(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sanitizedMediaSrc = sanitizeMediaSrc(
|
||||||
|
tag,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
extraHrefMatchers
|
||||||
|
);
|
||||||
|
if (sanitizedMediaSrc) {
|
||||||
|
return sanitizedMediaSrc;
|
||||||
|
}
|
||||||
|
|
||||||
if (tag === "iframe" && name === "src") {
|
if (tag === "iframe" && name === "src") {
|
||||||
return "-STRIP-";
|
return "-STRIP-";
|
||||||
}
|
}
|
||||||
|
@ -1371,6 +1371,37 @@ describe PrettyText do
|
|||||||
expect(cooked).to eq(n expected)
|
expect(cooked).to eq(n expected)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "applies scheme restrictions to img[src] attributes" do
|
||||||
|
SiteSetting.allowed_href_schemes = "steam"
|
||||||
|
cooked = cook " "
|
||||||
|
expected = '<p><img src="steam://store/452530" alt="Steam URL Image"> <img src="" alt="Other scheme image"></p>'
|
||||||
|
expect(cooked).to eq(n expected)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "applies scheme restrictions to track[src] and source[src]" do
|
||||||
|
SiteSetting.allowed_href_schemes = "steam"
|
||||||
|
cooked = cook <<~MD
|
||||||
|
<video>
|
||||||
|
<source src="steam://store/452530"><source src="itunes://store/452530"><track src="steam://store/452530"><track src="itunes://store/452530">
|
||||||
|
</video>
|
||||||
|
MD
|
||||||
|
expect(cooked).to include <<~HTML
|
||||||
|
<source src="steam://store/452530"><source src=""><track src="steam://store/452530"><track src="">
|
||||||
|
HTML
|
||||||
|
end
|
||||||
|
|
||||||
|
it "applies scheme restrictions to source[srcset]" do
|
||||||
|
SiteSetting.allowed_href_schemes = "steam"
|
||||||
|
cooked = cook <<~MD
|
||||||
|
<video>
|
||||||
|
<source srcset="steam://store/452530 1x,itunes://store/123 2x"><source srcset="steam://store/452530"><source srcset="itunes://store/452530">
|
||||||
|
</video>
|
||||||
|
MD
|
||||||
|
expect(cooked).to include <<~HTML
|
||||||
|
<source srcset="steam://store/452530 1x,"><source srcset="steam://store/452530"><source srcset="">
|
||||||
|
HTML
|
||||||
|
end
|
||||||
|
|
||||||
it 'allows only tel URL scheme to start with a plus character' do
|
it 'allows only tel URL scheme to start with a plus character' do
|
||||||
SiteSetting.allowed_href_schemes = "tel|steam"
|
SiteSetting.allowed_href_schemes = "tel|steam"
|
||||||
cooked = cook("[Tel URL Scheme](tel://+452530579785)")
|
cooked = cook("[Tel URL Scheme](tel://+452530579785)")
|
||||||
|
Reference in New Issue
Block a user