@ecency/render-helper 2.3.11 → 2.3.13
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.
- package/lib/consts/allowed-attributes.const.js +1 -0
- package/lib/consts/allowed-attributes.const.js.map +1 -1
- package/lib/methods/a.method.d.ts +1 -1
- package/lib/methods/a.method.js +3 -0
- package/lib/methods/a.method.js.map +1 -1
- package/lib/methods/iframe.method.d.ts +1 -1
- package/lib/methods/iframe.method.js +7 -2
- package/lib/methods/iframe.method.js.map +1 -1
- package/lib/methods/sanitize-html.method.js +3 -0
- package/lib/methods/sanitize-html.method.js.map +1 -1
- package/lib/methods/text.method.d.ts +1 -1
- package/lib/methods/text.method.js +13 -9
- package/lib/methods/text.method.js.map +1 -1
- package/lib/render-helper.js +1 -1
- package/lib/types/xss-white-list.interface.d.ts +1 -0
- package/package.json +1 -1
- package/src/consts/allowed-attributes.const.ts +1 -0
- package/src/methods/a.method.ts +4 -1
- package/src/methods/iframe.method.ts +10 -5
- package/src/methods/sanitize-html.method.ts +4 -0
- package/src/methods/text.method.ts +15 -10
- package/src/sanitize-html.spec.ts +15 -0
- package/src/types/xss-white-list.interface.ts +1 -0
package/package.json
CHANGED
|
@@ -33,6 +33,7 @@ export const ALLOWED_ATTRIBUTES: XSSWhiteList = {
|
|
|
33
33
|
],
|
|
34
34
|
'span': ['class', 'id'],
|
|
35
35
|
'iframe': ['src', 'class', 'frameborder', 'allowfullscreen', 'webkitallowfullscreen', 'mozallowfullscreen', 'sandbox'],
|
|
36
|
+
'video': ['src', 'controls', 'poster'],
|
|
36
37
|
'div': ['class', 'id'],
|
|
37
38
|
'strong': [],
|
|
38
39
|
'b': [],
|
package/src/methods/a.method.ts
CHANGED
|
@@ -34,7 +34,10 @@ import { extractYtStartTime, isValidPermlink, isValidUsername } from '../helper'
|
|
|
34
34
|
import { createImageHTML } from "./img.method";
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
export function a(el: HTMLElement, forApp: boolean, webp: boolean): void {
|
|
37
|
+
export function a(el: HTMLElement | null, forApp: boolean, webp: boolean): void {
|
|
38
|
+
if (!el || !el.parentNode) {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
38
41
|
let href = el.getAttribute('href')
|
|
39
42
|
|
|
40
43
|
// Continue if href has no value
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { ARCH_REGEX, DAPPLR_REGEX, LBRY_REGEX, TRUVVL_REGEX, ODYSEE_REGEX, SKATEHIVE_IPFS_REGEX, BITCHUTE_REGEX, RUMBLE_REGEX, BRIGHTEON_REGEX, VIMEO_EMBED_REGEX, SPEAK_EMBED_REGEX, VIMM_EMBED_REGEX, D_TUBE_EMBED_REGEX, SPOTIFY_EMBED_REGEX, SOUNDCLOUD_EMBED_REGEX, TWITCH_EMBED_REGEX, YOUTUBE_EMBED_REGEX, BRAND_NEW_TUBE_REGEX, LOOM_EMBED_REGEX, AUREAL_EMBED_REGEX } from '../consts'
|
|
2
2
|
|
|
3
|
-
export function iframe(el: HTMLElement): void {
|
|
3
|
+
export function iframe(el: HTMLElement | null): void {
|
|
4
|
+
if (!el || !el.parentNode) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
4
7
|
const src = el.getAttribute('src');
|
|
5
8
|
if (!src) {
|
|
6
9
|
el.parentNode.removeChild(el);
|
|
@@ -18,7 +21,7 @@ export function iframe(el: HTMLElement): void {
|
|
|
18
21
|
if (src.match(BITCHUTE_REGEX)) {
|
|
19
22
|
return;
|
|
20
23
|
}
|
|
21
|
-
|
|
24
|
+
|
|
22
25
|
// Vimeo
|
|
23
26
|
const m = src.match(VIMEO_EMBED_REGEX);
|
|
24
27
|
if (m && m.length === 2) {
|
|
@@ -86,7 +89,7 @@ export function iframe(el: HTMLElement): void {
|
|
|
86
89
|
el.setAttribute('allowfullscreen', 'true');
|
|
87
90
|
return;
|
|
88
91
|
}
|
|
89
|
-
|
|
92
|
+
|
|
90
93
|
// Truvvl
|
|
91
94
|
if (src.match(TRUVVL_REGEX)) {
|
|
92
95
|
el.setAttribute('src', src);
|
|
@@ -162,6 +165,8 @@ export function iframe(el: HTMLElement): void {
|
|
|
162
165
|
const replaceNode = el.ownerDocument.createElement('div');
|
|
163
166
|
replaceNode.setAttribute('class', 'unsupported-iframe');
|
|
164
167
|
replaceNode.textContent = `(Unsupported ${src})`;
|
|
165
|
-
el.parentNode
|
|
166
|
-
|
|
168
|
+
if (el.parentNode) {
|
|
169
|
+
el.parentNode.insertBefore(replaceNode, el);
|
|
170
|
+
el.parentNode.removeChild(el);
|
|
171
|
+
}
|
|
167
172
|
}
|
|
@@ -17,6 +17,10 @@ export function sanitizeHtml(html: string): string {
|
|
|
17
17
|
|
|
18
18
|
if (name.startsWith('on')) return ''; // 🛡 event handlers
|
|
19
19
|
if (tag === 'img' && name === 'src' && (!/^https?:\/\//.test(decoded) || decoded.startsWith('javascript:'))) return '';
|
|
20
|
+
if (
|
|
21
|
+
tag === 'video' && ['src', 'poster'].includes(name) &&
|
|
22
|
+
(!/^https?:\/\//.test(decoded) || decoded.startsWith('javascript:'))
|
|
23
|
+
) return '';
|
|
20
24
|
if (tag === 'img' && ['dynsrc', 'lowsrc'].includes(name)) return '';
|
|
21
25
|
if (tag === 'span' && name === 'class' && value === 'wr') return '';
|
|
22
26
|
if (name === 'id') {
|
|
@@ -4,13 +4,18 @@ import { proxifyImageSrc } from '../proxify-image-src'
|
|
|
4
4
|
import { linkify } from './linkify.method'
|
|
5
5
|
import {createImageHTML} from "./img.method";
|
|
6
6
|
|
|
7
|
-
export function text(node: HTMLElement, forApp: boolean, webp: boolean): void {
|
|
7
|
+
export function text(node: HTMLElement | null, forApp: boolean, webp: boolean): void {
|
|
8
|
+
if (!node || !node.parentNode) {
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
if (['a', 'code'].includes(node.parentNode.nodeName)) {
|
|
9
13
|
return
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
const
|
|
13
|
-
|
|
16
|
+
const nodeValue = node.nodeValue || ''
|
|
17
|
+
const linkified = linkify(nodeValue, forApp, webp)
|
|
18
|
+
if (linkified !== nodeValue) {
|
|
14
19
|
const replaceNode = DOMParser.parseFromString(
|
|
15
20
|
`<span class="wr">${linkified}</span>`
|
|
16
21
|
)
|
|
@@ -20,15 +25,15 @@ export function text(node: HTMLElement, forApp: boolean, webp: boolean): void {
|
|
|
20
25
|
return
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
if (
|
|
28
|
+
if (nodeValue.match(IMG_REGEX)) {
|
|
24
29
|
const isLCP = false; // Traverse handles LCP; no need to double-count
|
|
25
|
-
const imageHTML = createImageHTML(
|
|
30
|
+
const imageHTML = createImageHTML(nodeValue, isLCP, webp);
|
|
26
31
|
const replaceNode = DOMParser.parseFromString(imageHTML);
|
|
27
32
|
node.parentNode.replaceChild(replaceNode, node);
|
|
28
33
|
}
|
|
29
34
|
// If a youtube video
|
|
30
|
-
if (
|
|
31
|
-
const e = YOUTUBE_REGEX.exec(
|
|
35
|
+
if (nodeValue.match(YOUTUBE_REGEX)) {
|
|
36
|
+
const e = YOUTUBE_REGEX.exec(nodeValue)
|
|
32
37
|
if (e[1]) {
|
|
33
38
|
const vid = e[1]
|
|
34
39
|
const thumbnail = proxifyImageSrc(`https://img.youtube.com/vi/${vid.split('?')[0]}/hqdefault.jpg`, 0, 0, webp ? 'webp' : 'match')
|
|
@@ -36,7 +41,7 @@ export function text(node: HTMLElement, forApp: boolean, webp: boolean): void {
|
|
|
36
41
|
|
|
37
42
|
let attrs = `class="markdown-video-link markdown-video-link-youtube" data-embed-src="${embedSrc}" data-youtube="${vid}"`
|
|
38
43
|
//extract start time if available
|
|
39
|
-
const startTime = extractYtStartTime(
|
|
44
|
+
const startTime = extractYtStartTime(nodeValue);
|
|
40
45
|
if(startTime){
|
|
41
46
|
attrs = attrs.concat(` data-start-time="${startTime}"`);
|
|
42
47
|
}
|
|
@@ -52,8 +57,8 @@ export function text(node: HTMLElement, forApp: boolean, webp: boolean): void {
|
|
|
52
57
|
node.parentNode.replaceChild(replaceNode, node)
|
|
53
58
|
}
|
|
54
59
|
}
|
|
55
|
-
if (
|
|
56
|
-
const postMatch =
|
|
60
|
+
if (nodeValue && typeof nodeValue === 'string') {
|
|
61
|
+
const postMatch = nodeValue.trim().match(POST_REGEX)
|
|
57
62
|
if (postMatch && WHITE_LIST.includes(postMatch[1].replace(/www./,''))) {
|
|
58
63
|
const tag = postMatch[2]
|
|
59
64
|
const author = postMatch[3].replace('@', '')
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { sanitizeHtml } from './methods/sanitize-html.method'
|
|
2
|
+
|
|
3
|
+
describe('sanitizeHtml', () => {
|
|
4
|
+
it('1- should allow video tag with src and controls', () => {
|
|
5
|
+
const input = '<video src="https://example.com/video.mp4" controls></video>'
|
|
6
|
+
const expected = '<video src="https://example.com/video.mp4" controls></video>'
|
|
7
|
+
expect(sanitizeHtml(input)).toBe(expected)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('2- should strip dangerous video src', () => {
|
|
11
|
+
const input = "<video src=\"javascript:alert('XSS')\" controls></video>"
|
|
12
|
+
const expected = '<video controls></video>'
|
|
13
|
+
expect(sanitizeHtml(input)).toBe(expected)
|
|
14
|
+
})
|
|
15
|
+
})
|