diff --git a/app/assets/javascripts/discourse/app/instance-initializers/video-placeholder.js b/app/assets/javascripts/discourse/app/instance-initializers/video-placeholder.js
index 9deb040a700..3d14304ac5e 100644
--- a/app/assets/javascripts/discourse/app/instance-initializers/video-placeholder.js
+++ b/app/assets/javascripts/discourse/app/instance-initializers/video-placeholder.js
@@ -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 = `
`;
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,
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/video-placeholder-test.js b/app/assets/javascripts/discourse/tests/acceptance/video-placeholder-test.js
index f518752444e..873af1ff884 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/video-placeholder-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/video-placeholder-test.js
@@ -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">'
+ );
+
+ 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");
+ });
});
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 320af174189..1080114a57a 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -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: ", "