22import PropTypes from 'prop-types'
33import React , { PureComponent } from 'react'
44import serialize from 'serialize-javascript'
5+ import DOMPurify from 'dompurify'
56
67// @twreporter
78import webfonts from '@twreporter/react-components/lib/text/utils/webfonts'
@@ -21,6 +22,13 @@ export default class Html extends PureComponent {
2122 scripts : PropTypes . array . isRequired ,
2223 scriptElement : PropTypes . arrayOf ( PropTypes . element ) . isRequired ,
2324 styles : PropTypes . array . isRequired ,
25+ /**
26+ * Server-side rendered markup from React
27+ * This content is sanitized using DOMPurify before being rendered to:
28+ * 1. Ensure XSS protection even if server-side rendering is compromised
29+ * 2. Allow hydration to work correctly while maintaining security
30+ * 3. Remove any potentially malicious content while preserving valid HTML
31+ */
2432 contentMarkup : PropTypes . string . isRequired ,
2533 store : PropTypes . object . isRequired ,
2634 styleElement : PropTypes . arrayOf ( PropTypes . element ) . isRequired ,
@@ -110,34 +118,32 @@ export default class Html extends PureComponent {
110118 { styleElement }
111119 </ head >
112120 < body >
113- < div id = "root" dangerouslySetInnerHTML = { { __html : contentMarkup } } />
121+ < div id = "root" dangerouslySetInnerHTML = { { __html : DOMPurify . sanitize ( contentMarkup ) } } />
114122 < script
115123 defer
116124 src = "https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.zh-Hant-TW"
117125 />
118126 < script
119- dangerouslySetInnerHTML = { {
120- __html : `window.__REDUX_STATE__=${ serialize ( store . getState ( ) ) } ;` ,
121- } }
127+ id = "redux-state"
128+ type = "application/json"
122129 charSet = "UTF-8"
123- />
130+ >
131+ { JSON . stringify ( store . getState ( ) ) }
132+ </ script >
124133 { _ . map ( scripts , ( script , key ) => (
125134 < script src = { script } key = { 'scripts' + key } charSet = "UTF-8" />
126135 ) ) }
127136 { scriptElement }
128- < script
129- dangerouslySetInnerHTML = { {
130- __html : `(function(d) {
131- var config = {
132- kitId: 'vlk1qbe',
133- scriptTimeout: 3000,
134- async: true
135- },
136- h=d.documentElement,t=setTimeout(function(){h.className=h.className.replace(/\bwf-loading\b/g,"")+" wf-inactive";},config.scriptTimeout),tk=d.createElement("script"),f=false,s=d.getElementsByTagName("script")[0],a;h.className+=" wf-loading";tk.src='https://use.typekit.net/'+config.kitId+'.js';tk.async=true;tk.onload=tk.onreadystatechange=function(){a=this.readyState;if(f||a&&a!="complete"&&a!="loaded")return;f=true;clearTimeout(t);try{Typekit.load(config)}catch(e){}};s.parentNode.insertBefore(tk,s)
137- })(document);
138- ` ,
139- } }
140- />
137+ < script src = "https://use.typekit.net/vlk1qbe.js" async />
138+ < script > { `
139+ try {
140+ Typekit.load({
141+ kitId: 'vlk1qbe',
142+ scriptTimeout: 3000,
143+ async: true
144+ });
145+ } catch(e) {}
146+ ` } </ script >
141147 </ body >
142148 </ html >
143149 )
0 commit comments