@@ -29,32 +29,79 @@ export async function ProjectPreview({url, title, renderUrl}: ProjectPreviewProp
2929 let html = await response . text ( )
3030
3131 // Remove script tags to make it static (no JavaScript execution)
32- html = html . replace ( / < s c r i p t \b [ ^ < ] * (?: (? ! < \/ s c r i p t > ) < [ ^ < ] * ) * < \/ s c r i p t > / gi, '' )
32+ // Use iterative replacement to handle nested or overlapping patterns
33+ let previousHtml = ''
34+ while ( previousHtml !== html ) {
35+ previousHtml = html
36+ html = html . replace ( / < s c r i p t [ \s \S ] * ?< \/ s c r i p t > / gi, '' )
37+ html = html . replace ( / < s c r i p t [ ^ > ] * > / gi, '' )
38+ }
3339
3440 // Remove noscript tags (they're not needed in a static preview)
35- html = html . replace ( / < n o s c r i p t \b [ ^ < ] * (?: (? ! < \/ n o s c r i p t > ) < [ ^ < ] * ) * < \/ n o s c r i p t > / gi, '' )
41+ previousHtml = ''
42+ while ( previousHtml !== html ) {
43+ previousHtml = html
44+ html = html . replace ( / < n o s c r i p t [ \s \S ] * ?< \/ n o s c r i p t > / gi, '' )
45+ }
3646
3747 // Remove preload links for scripts (since we removed the scripts)
38- html = html . replace ( / < l i n k [ ^ > ] * r e l = [ " ' ] ? p r e l o a d [ " ' ] [ ^ > ] * a s = [ " ' ] ? s c r i p t [ " ' ] [ ^ > ] * > / gi, '' )
39- html = html . replace ( / < l i n k [ ^ > ] * a s = [ " ' ] ? s c r i p t [ " ' ] [ ^ > ] * r e l = [ " ' ] ? p r e l o a d [ " ' ] [ ^ > ] * > / gi, '' )
48+ html = html . replace ( / < l i n k [ ^ > ] * r e l \s * = \s * [ " ' ] ? p r e l o a d [ " ' ] [ ^ > ] * a s \s * = \s * [ " ' ] ? s c r i p t [ " ' ] [ ^ > ] * > / gi, '' )
49+ html = html . replace ( / < l i n k [ ^ > ] * a s \s * = \s * [ " ' ] ? s c r i p t [ " ' ] [ ^ > ] * r e l \s * = \s * [ " ' ] ? p r e l o a d [ " ' ] [ ^ > ] * > / gi, '' )
4050
4151 // Remove modulepreload links as well
42- html = html . replace ( / < l i n k [ ^ > ] * r e l = [ " ' ] ? m o d u l e p r e l o a d [ " ' ] [ ^ > ] * > / gi, '' )
52+ html = html . replace ( / < l i n k [ ^ > ] * r e l \s * = \s * [ " ' ] ? m o d u l e p r e l o a d [ " ' ] [ ^ > ] * > / gi, '' )
4353
4454 // Remove inline event handlers (onclick, onload, onerror, etc.)
45- html = html . replace ( / \s + o n \w + \s * = \s * [ " ' ] [ ^ " ' ] * [ " ' ] / gi, '' )
46- html = html . replace ( / \s + o n \w + \s * = \s * [ ^ \s > ] * / gi, '' )
55+ // Use iterative replacement to handle overlapping patterns like "ononclick"
56+ previousHtml = ''
57+ while ( previousHtml !== html ) {
58+ previousHtml = html
59+ html = html . replace ( / \s o n \w + \s * = \s * [ " ' ] [ ^ " ' ] * [ " ' ] / gi, ' ' )
60+ html = html . replace ( / \s o n \w + \s * = \s * [ ^ \s > ] + / gi, ' ' )
61+ }
62+
63+ // Remove javascript: URIs
64+ previousHtml = ''
65+ while ( previousHtml !== html ) {
66+ previousHtml = html
67+ html = html . replace ( / \s ( h r e f | s r c | a c t i o n | f o r m a c t i o n | d a t a ) \s * = \s * [ " ' ] ? \s * j a v a s c r i p t : / gi, ' data-blocked-$1="javascript:' )
68+ }
69+
70+ // Remove data: URIs that could contain HTML/SVG with scripts
71+ previousHtml = ''
72+ while ( previousHtml !== html ) {
73+ previousHtml = html
74+ html = html . replace ( / \s ( h r e f | s r c | a c t i o n | f o r m a c t i o n ) \s * = \s * [ " ' ] ? \s * d a t a : t e x t \/ h t m l / gi, ' data-blocked-$1="data:text/html' )
75+ }
76+
77+ // Remove potentially dangerous tags (object, embed, applet)
78+ previousHtml = ''
79+ while ( previousHtml !== html ) {
80+ previousHtml = html
81+ html = html . replace ( / < ( o b j e c t | e m b e d | a p p l e t ) [ \s \S ] * ?< \/ \1> / gi, '' )
82+ html = html . replace ( / < ( o b j e c t | e m b e d | a p p l e t ) [ ^ > ] * > / gi, '' )
83+ }
4784
4885 // Remove existing CSP meta tags that might conflict
49- html = html . replace ( / < m e t a [ ^ > ] * h t t p - e q u i v = [ " ' ] ? C o n t e n t - S e c u r i t y - P o l i c y [ " ' ] ? [ ^ > ] * > / gi, '' )
86+ html = html . replace ( / < m e t a [ ^ > ] * h t t p - e q u i v \s * = \s * [ " ' ] ? C o n t e n t - S e c u r i t y - P o l i c y [ " ' ] ? [ ^ > ] * > / gi, '' )
5087
5188 // Get the base URL (in case of redirects, use the final URL)
5289 const baseUrl = new URL ( response . url )
5390 // Use the full origin with trailing slash to ensure all resources load correctly
5491 const baseHref = baseUrl . origin + '/'
5592
93+ // Helper function to escape HTML attributes
94+ const escapeHtmlAttr = ( str : string ) => {
95+ return str
96+ . replace ( / & / g, '&' )
97+ . replace ( / " / g, '"' )
98+ . replace ( / ' / g, ''' )
99+ . replace ( / < / g, '<' )
100+ . replace ( / > / g, '>' )
101+ }
102+
56103 // Inject base tag, CSP meta tag (blocking all scripts), and no-scroll style in the head
57- const baseTag = `<base href="${ baseHref } ">`
104+ const baseTag = `<base href="${ escapeHtmlAttr ( baseHref ) } ">`
58105 const cspMeta = `<meta http-equiv="Content-Security-Policy" content="default-src *; script-src 'none'; style-src * 'unsafe-inline'; img-src * data: blob:; font-src * data:; connect-src * data: blob:;">`
59106 const noScrollStyle = `<style>html, body { overflow: hidden !important; }</style>`
60107
0 commit comments