Improve channel banner sanitization

This commit is contained in:
thePR0M3TH3AN committed 2025-10-17 10:03:50 -04:00
1 parent ab9ff9b318
commit c6500d6b7a
4 files changed
+108 -60

No files matched your search

+11 -57
View File
@@ -15,6 +15,7 @@ import createPopover from "./ui/overlay/popoverEngine.js";
import { VideoCard } from "./ui/components/VideoCard.js";
import { createChannelProfileMenuPanel } from "./ui/components/videoMenuRenderers.js";
import { ALLOW_NSFW_CONTENT } from "./config.js";
import { sanitizeProfileMediaUrl } from "./utils/profileMedia.js";
import {
calculateZapShares,
describeShareType,
@@ -2097,73 +2098,26 @@ function trimProfileString(value) {
return value.trim();
}
function normalizeProfileMediaUrl(value) {
const trimmed = trimProfileString(value);
if (!trimmed) {
return "";
}
if (/^data:image\//i.test(trimmed)) {
return trimmed;
}
if (/^blob:/i.test(trimmed)) {
return trimmed;
}
if (/^https?:\/\//i.test(trimmed)) {
return trimmed;
}
if (trimmed.startsWith("//")) {
return `https:${trimmed}`;
}
if (/^ipfs:\/\//i.test(trimmed)) {
const ipfsPath = trimmed
.replace(/^ipfs:\/\//i, "")
.replace(/^ipfs\//i, "");
if (!ipfsPath) {
return "";
}
return `https://ipfs.io/ipfs/${encodeURI(ipfsPath)}`;
}
if (/^(?:\.\.\/|\.\/|\/)/.test(trimmed)) {
return trimmed;
}
if (/^assets\//i.test(trimmed)) {
return trimmed;
}
const localhostPattern = /^(?:localhost|(?:\d{1,3}\.){3}\d{1,3})(?::\d+)?(?:[/?#].*)?$/i;
if (localhostPattern.test(trimmed)) {
return `http://${trimmed}`;
}
const domainPattern =
/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9-]{2,}(?::\d+)?(?:[/?#].*)?$/i;
if (domainPattern.test(trimmed)) {
return `https://${trimmed}`;
}
return "";
}
function normalizeChannelProfileMetadata(raw = {}) {
const name =
trimProfileString(raw.display_name) ||
trimProfileString(raw.name) ||
"Unknown User";
const picture =
normalizeProfileMediaUrl(raw.picture) ||
normalizeProfileMediaUrl(raw.image) ||
sanitizeProfileMediaUrl(raw.picture) ||
sanitizeProfileMediaUrl(raw.image) ||
FALLBACK_CHANNEL_AVATAR;
const about = trimProfileString(raw.about || raw.bio);
const website = trimProfileString(raw.website || raw.url);
const banner =
normalizeProfileMediaUrl(raw.banner || raw.header || raw.background) || "";
sanitizeProfileMediaUrl(
raw.banner ||
raw.header ||
raw.background ||
raw.cover ||
raw.cover_image ||
raw.coverImage
) || "";
const lud16 = trimProfileString(raw.lud16);
const lud06 = trimProfileString(raw.lud06);
const lightningAddress =
+12 -2
View File
@@ -1,4 +1,5 @@
import { userLogger } from "../utils/logger.js";
import { sanitizeProfileMediaUrl } from "../utils/profileMedia.js";
import {
readUrlHealthFromStorage,
removeUrlHealthFromStorage,
@@ -577,7 +578,9 @@ export function setProfileCacheEntry(pubkey, profile, { persist = true } = {}) {
const normalized = {
name: profile.name || profile.display_name || "Unknown",
picture: profile.picture || "assets/svg/default-profile.svg",
picture:
sanitizeProfileMediaUrl(profile.picture || profile.image) ||
"assets/svg/default-profile.svg",
};
const about = sanitizeProfileString(profile.about || profile.aboutMe);
@@ -590,7 +593,14 @@ export function setProfileCacheEntry(pubkey, profile, { persist = true } = {}) {
normalized.website = website;
}
const banner = sanitizeProfileString(profile.banner || profile.header);
const banner = sanitizeProfileMediaUrl(
profile.banner ||
profile.header ||
profile.background ||
profile.cover ||
profile.cover_image ||
profile.coverImage
);
if (banner) {
normalized.banner = banner;
}
+80
View File
@@ -0,0 +1,80 @@
import { devLogger } from "./logger.js";
const LOCALHOST_PATTERN = /^(?:localhost|(?:\d{1,3}\.){3}\d{1,3})(?::\d+)?(?:[/?#].*)?$/i;
const DOMAIN_PATTERN = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9-]{2,}(?::\d+)?(?:[/?#].*)?$/i;
function normalizeIpfsGatewayUrl(trimmed) {
const ipfsPath = trimmed.replace(/^ipfs:\/\//i, "").replace(/^ipfs\//i, "");
if (!ipfsPath) {
return "";
}
return `https://ipfs.io/ipfs/${encodeURI(ipfsPath)}`;
}
function coerceHttpToHttps(url) {
if (!/^http:\/\//i.test(url)) {
return url;
}
if (LOCALHOST_PATTERN.test(url.replace(/^http:\/\//i, ""))) {
return url;
}
return `https://${url.slice(7)}`;
}
export function sanitizeProfileMediaUrl(value) {
if (typeof value !== "string") {
return "";
}
let trimmed = value.trim();
if (!trimmed) {
return "";
}
trimmed = trimmed.replace(/^['"]+|['"]+$/g, "");
if (!trimmed) {
return "";
}
if (/^data:image\//i.test(trimmed)) {
return trimmed;
}
if (/^blob:/i.test(trimmed)) {
return trimmed;
}
if (/^ipfs:\/\//i.test(trimmed)) {
return normalizeIpfsGatewayUrl(trimmed);
}
if (trimmed.startsWith("//")) {
trimmed = `https:${trimmed}`;
}
if (/^(?:\.\.\/|\.\/|\/)/.test(trimmed)) {
return trimmed;
}
if (/^assets\//i.test(trimmed)) {
return trimmed;
}
if (!/^https?:\/\//i.test(trimmed)) {
if (LOCALHOST_PATTERN.test(trimmed)) {
trimmed = `http://${trimmed}`;
} else if (DOMAIN_PATTERN.test(trimmed)) {
trimmed = `https://${trimmed}`;
} else {
devLogger?.warn?.(
"[profileMedia.sanitizeProfileMediaUrl] Unsupported media URL",
trimmed
);
return "";
}
}
return coerceHttpToHttps(trimmed);
}
+5 -1
View File
@@ -5,8 +5,12 @@
<img
id="channelBanner"
class="h-48 w-full rounded-lg object-cover md:h-64"
src=""
src="assets/jpg/bitvid.jpg"
alt="Banner"
loading="lazy"
referrerpolicy="no-referrer"
crossorigin="anonymous"
data-fallback-src="assets/jpg/bitvid.jpg"
/>
<div class="absolute inset-0 rounded-lg bg-black bg-opacity-50"></div>