mirror of
https://github.com/discourse/discourse.git
synced 2025-03-11 00:15:38 +08:00
SECURITY: Sanitize video placeholder urls
Make sure video placeholder urls are valid. An error message is displayed instead of an infinite loading spinner after clicking play.
This commit is contained in:
parent
2c5dbdc23f
commit
8192aedd69
@ -1,5 +1,6 @@
|
||||
import { spinnerHTML } from "discourse/helpers/loading-spinner";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import { sanitize } from "discourse/lib/text";
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import discourseLater from "discourse-common/lib/later";
|
||||
import I18n from "discourse-i18n";
|
||||
@ -15,10 +16,39 @@ export default {
|
||||
parentDiv.style.cursor = "";
|
||||
overlay.innerHTML = spinnerHTML;
|
||||
|
||||
const videoSrc = sanitizeUrl(parentDiv.dataset.videoSrc);
|
||||
const origSrc = sanitizeUrl(parentDiv.dataset.origSrc);
|
||||
const dataOrigSrcAttr =
|
||||
origSrc !== null ? `data-orig-src="${origSrc}"` : "";
|
||||
|
||||
if (videoSrc === null) {
|
||||
const existingNotice = wrapper.querySelector(".notice.error");
|
||||
if (existingNotice) {
|
||||
existingNotice.remove();
|
||||
}
|
||||
|
||||
const notice = document.createElement("div");
|
||||
notice.className = "notice error";
|
||||
notice.innerHTML =
|
||||
iconHTML("triangle-exclamation") +
|
||||
" " +
|
||||
I18n.t("invalid_video_url");
|
||||
wrapper.appendChild(notice);
|
||||
overlay.innerHTML = iconHTML("play");
|
||||
|
||||
parentDiv.style.cursor = "pointer";
|
||||
parentDiv.addEventListener(
|
||||
"click",
|
||||
(e) => handleVideoPlaceholderClick(helper, e),
|
||||
{ once: true }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const videoHTML = `
|
||||
<video width="100%" height="100%" preload="metadata" controls style="display:none">
|
||||
<source src="${parentDiv.dataset.videoSrc}" ${parentDiv.dataset.origSrc}>
|
||||
<a href="${parentDiv.dataset.videoSrc}">${parentDiv.dataset.videoSrc}</a>
|
||||
<source src="${videoSrc}" ${dataOrigSrcAttr}>
|
||||
<a href="${videoSrc}">${videoSrc}</a>
|
||||
</video>`;
|
||||
parentDiv.insertAdjacentHTML("beforeend", videoHTML);
|
||||
parentDiv.classList.add("video-container");
|
||||
@ -108,6 +138,33 @@ export default {
|
||||
});
|
||||
}
|
||||
|
||||
function sanitizeUrl(url) {
|
||||
try {
|
||||
const parsedUrl = new URL(url, window.location.origin);
|
||||
|
||||
if (
|
||||
["http:", "https:"].includes(parsedUrl.protocol) ||
|
||||
url.startsWith("/")
|
||||
) {
|
||||
const sanitized = sanitize(url);
|
||||
|
||||
if (
|
||||
sanitized &&
|
||||
sanitized.trim() !== "" &&
|
||||
!sanitized.includes(">") &&
|
||||
!sanitized.includes("<")
|
||||
) {
|
||||
return sanitized;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("Invalid URL encountered:", url, e.message);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
api.decorateCookedElement(applyVideoPlaceholder, {
|
||||
onlyStream: true,
|
||||
});
|
||||
|
@ -50,4 +50,30 @@ acceptance("Video Placeholder Test", function () {
|
||||
.hasStyle({ display: "block" }, "The video is no longer hidden");
|
||||
assert.dom(".video-placeholder-wrapper").doesNotExist();
|
||||
});
|
||||
|
||||
test("displays an error for invalid video URL and allows retry", async function (assert) {
|
||||
await visit("/t/54081");
|
||||
|
||||
const placeholder = document.querySelector(".video-placeholder-container");
|
||||
placeholder.setAttribute(
|
||||
"data-video-src",
|
||||
'http://example.com/video.mp4"><script>alert(1)</script>'
|
||||
);
|
||||
|
||||
await click(".video-placeholder-overlay");
|
||||
|
||||
assert
|
||||
.dom(".video-placeholder-wrapper .notice.error")
|
||||
.exists("An error message is displayed for an invalid URL");
|
||||
assert
|
||||
.dom(".video-placeholder-wrapper .notice.error")
|
||||
.hasText(
|
||||
"This video cannot be played because the URL is invalid or unavailable.",
|
||||
"Error message is correct"
|
||||
);
|
||||
|
||||
assert
|
||||
.dom("video")
|
||||
.doesNotExist("No video element is created for invalid URL");
|
||||
});
|
||||
});
|
||||
|
@ -4360,6 +4360,7 @@ en:
|
||||
retry: "Retry loading the image"
|
||||
|
||||
cannot_render_video: This video cannot be rendered because your browser does not support the codec.
|
||||
invalid_video_url: This video cannot be played because the URL is invalid or unavailable.
|
||||
|
||||
keyboard_shortcuts_help:
|
||||
shortcut_key_delimiter_comma: ", "
|
||||
|
Loading…
x
Reference in New Issue
Block a user