mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-30 17:32:26 +00:00
Improve channel banner sanitization
This commit is contained in:
1 parent
ab9ff9b318
commit
c6500d6b7a
4 files changed
+108
-60
No files matched your search
+11
-57
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
|
||||
Reference in new issue
Block a user