@ecency/render-helper 2.5.11 → 2.5.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -133,6 +133,20 @@ declare function setCacheSize(size: number): void;
133
133
 
134
134
  declare const SECTION_LIST: string[];
135
135
 
136
+ /**
137
+ * True iff `value` is an absolute https:// URL whose host is one the renderer
138
+ * is permitted to embed AND (for hosts with a known embed-path shape) whose
139
+ * path matches that shape. Used to validate data-embed-src / data-video-href in
140
+ * the sanitizer and the iframe `src` the client video extensions assign.
141
+ *
142
+ * Defensive: never throws (a malformed URL returns false), rejects every
143
+ * non-https scheme (javascript:, data:, http:, protocol-relative //host),
144
+ * compares the parsed hostname so an attacker can't smuggle an allowed host as
145
+ * a path/query/userinfo/subdomain-suffix segment, and constrains the path so an
146
+ * allowed host can't be aimed at a non-embed route.
147
+ */
148
+ declare function isAllowedEmbedSrc(value?: string | null): boolean;
149
+
136
150
  declare function isValidPermlink(permlink: string): boolean;
137
151
 
138
152
  /**
@@ -145,4 +159,4 @@ declare function isValidPermlink(permlink: string): boolean;
145
159
  */
146
160
  declare function simpleMarkdownToHTML(input: string): string;
147
161
 
148
- export { type Entry, IMAGE_SIZES, type ProxifyOptions, type RenderOptions, SECTION_LIST, type SeoContext, buildPictureSources, buildSrcSet, buildSrcSetForFormat, catchPostImage, getEntryImageRawUrl, isPictureEligibleRawUrl, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase, setSlowRenderThresholdMs, simpleMarkdownToHTML };
162
+ export { type Entry, IMAGE_SIZES, type ProxifyOptions, type RenderOptions, SECTION_LIST, type SeoContext, buildPictureSources, buildSrcSet, buildSrcSetForFormat, catchPostImage, getEntryImageRawUrl, isAllowedEmbedSrc, isPictureEligibleRawUrl, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase, setSlowRenderThresholdMs, simpleMarkdownToHTML };
@@ -176,6 +176,96 @@ var ALLOWED_ATTRIBUTES = {
176
176
  "del": [],
177
177
  "ins": []
178
178
  };
179
+
180
+ // src/consts/embed-hosts.const.ts
181
+ var ALLOWED_EMBED_HOSTS = /* @__PURE__ */ new Set([
182
+ // YouTube
183
+ "www.youtube.com",
184
+ "youtube.com",
185
+ "www.youtube-nocookie.com",
186
+ "youtube-nocookie.com",
187
+ // Vimeo
188
+ "player.vimeo.com",
189
+ // Twitch
190
+ "player.twitch.tv",
191
+ // DTube
192
+ "emb.d.tube",
193
+ // 3Speak (video + audio)
194
+ "play.3speak.tv",
195
+ "3speak.tv",
196
+ "audio.3speak.tv",
197
+ // Loom
198
+ "www.loom.com",
199
+ // Spotify
200
+ "open.spotify.com",
201
+ // SoundCloud
202
+ "w.soundcloud.com",
203
+ // BitChute
204
+ "www.bitchute.com",
205
+ "bitchute.com",
206
+ // Rumble
207
+ "www.rumble.com",
208
+ "rumble.com",
209
+ // Brighteon
210
+ "www.brighteon.com",
211
+ "brighteon.com",
212
+ // VIMM
213
+ "www.vimm.tv",
214
+ // BrandNewTube
215
+ "brandnewtube.com",
216
+ // LBRY / Odysee
217
+ "lbry.tv",
218
+ "odysee.com",
219
+ // Skatehive / Skatehype
220
+ "ipfs.skatehive.app",
221
+ "www.skatehype.com",
222
+ "skatehype.com",
223
+ // archive.org
224
+ "archive.org",
225
+ // Truvvl
226
+ "embed.truvvl.com",
227
+ // Aureal
228
+ "aureal-embed.web.app",
229
+ "www.aureal-embed.web.app"
230
+ // Dapplr (player.*.dapplr.in / *.dapplr.in) — host suffix, handled below
231
+ ]);
232
+ var ALLOWED_EMBED_HOST_SUFFIXES = [".dapplr.in"];
233
+ var EMBED_HOST_PATH_PATTERNS = {
234
+ "www.youtube.com": /^\/embed\//,
235
+ "youtube.com": /^\/embed\//,
236
+ "www.youtube-nocookie.com": /^\/embed\//,
237
+ "youtube-nocookie.com": /^\/embed\//,
238
+ "player.vimeo.com": /^\/video\//,
239
+ "player.twitch.tv": /^\/$/,
240
+ // channel/video carried in the query string
241
+ "emb.d.tube": /^\/$/,
242
+ // dtube carries the ref in the #! fragment
243
+ "play.3speak.tv": /^\/(watch|embed)/,
244
+ "open.spotify.com": /^\/embed\//,
245
+ "www.loom.com": /^\/embed\//,
246
+ "www.bitchute.com": /^\/embed\//,
247
+ "bitchute.com": /^\/embed\//,
248
+ "www.rumble.com": /^\/embed\//,
249
+ "rumble.com": /^\/embed\//,
250
+ "www.brighteon.com": /^\/embed\//,
251
+ "brighteon.com": /^\/embed\//
252
+ };
253
+ function isAllowedEmbedSrc(value) {
254
+ if (!value) return false;
255
+ let url;
256
+ try {
257
+ url = new URL(value.trim());
258
+ } catch {
259
+ return false;
260
+ }
261
+ if (url.protocol !== "https:") return false;
262
+ const host = url.hostname.toLowerCase();
263
+ const hostAllowed = ALLOWED_EMBED_HOSTS.has(host) || ALLOWED_EMBED_HOST_SUFFIXES.some((suffix) => host.endsWith(suffix));
264
+ if (!hostAllowed) return false;
265
+ const pathPattern = EMBED_HOST_PATH_PATTERNS[host];
266
+ if (pathPattern && !pathPattern.test(url.pathname)) return false;
267
+ return true;
268
+ }
179
269
  function createParser() {
180
270
  return new DOMParser$1({
181
271
  onError(level, msg) {
@@ -571,6 +661,14 @@ function buildPictureSources(rawUrl) {
571
661
  }
572
662
 
573
663
  // src/methods/sanitize-html.method.ts
664
+ var EMBED_SRC_DATA_ATTRS = /* @__PURE__ */ new Set(["data-embed-src", "data-video-href"]);
665
+ var isSafeNavValue = (value) => {
666
+ const trimmed = value.trim().replace(/[\t\n\r\f\v\0]/g, "").toLowerCase();
667
+ if (!trimmed) return false;
668
+ const isSafeScheme = /^(https?|mailto|hive|tel|web\+[a-z0-9.+-]+):/i.test(trimmed);
669
+ const isRelative = /^(\/\/|\/[^/]?|#|\?|[a-z0-9._\-]+(\/|$))/i.test(trimmed);
670
+ return isSafeScheme || isRelative;
671
+ };
574
672
  var decodeEntities = (input) => input.replace(/&#(\d+);?/g, (_, dec) => String.fromCodePoint(Number(dec))).replace(/&#x([0-9a-f]+);?/gi, (_, hex) => String.fromCodePoint(parseInt(hex, 16)));
575
673
  var isProxyPSrcset = (srcset) => {
576
674
  const base = trimTrailingSlash(getProxyBase());
@@ -598,6 +696,8 @@ function sanitizeHtml(html) {
598
696
  if (tag === "video" && ["src", "poster"].includes(name) && !/^https?:\/\//.test(decodedLower)) return "";
599
697
  if (tag === "img" && ["dynsrc", "lowsrc"].includes(name)) return "";
600
698
  if (tag === "span" && name === "class" && decoded.toLowerCase().trim() === "wr") return "";
699
+ if (EMBED_SRC_DATA_ATTRS.has(name) && !isAllowedEmbedSrc(decoded)) return "";
700
+ if (name === "data-href" && !isSafeNavValue(decoded)) return "";
601
701
  if (name === "id") {
602
702
  if (!ID_WHITELIST.test(decoded)) return "";
603
703
  }
@@ -2203,6 +2303,6 @@ function getPostBodySummary(obj, length, platform) {
2203
2303
  return res;
2204
2304
  }
2205
2305
 
2206
- export { IMAGE_SIZES, SECTION_LIST, buildPictureSources, buildSrcSet, buildSrcSetForFormat, catchPostImage, getEntryImageRawUrl, isPictureEligibleRawUrl, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase, setSlowRenderThresholdMs, simpleMarkdownToHTML };
2306
+ export { IMAGE_SIZES, SECTION_LIST, buildPictureSources, buildSrcSet, buildSrcSetForFormat, catchPostImage, getEntryImageRawUrl, isAllowedEmbedSrc, isPictureEligibleRawUrl, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase, setSlowRenderThresholdMs, simpleMarkdownToHTML };
2207
2307
  //# sourceMappingURL=index.js.map
2208
2308
  //# sourceMappingURL=index.js.map