我试图设置从我的服务器发送的html,在React中使用dangerouslySetInnerHTML属性显示div内部。我里面也有脚本标签,并使用html中定义的函数。我在JSFiddle here中提出了错误示例。
这是测试代码:
var x = '<html><scr'+'ipt>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</scr'+'ipt><body><p onClick="pClicked()">Hello</p></body></html>';
var Hello = React.createClass({
displayName: 'Hello',
render: function() {
return (<div dangerouslySetInnerHTML={{__html: x}} />);
}
});
我检查过脚本标记已添加到DOM,但无法调用该脚本标记中定义的函数。如果这不是正确的方法,我可以通过其他方式注入脚本标记的内容。
答案 0 :(得分:11)
这是完成它的一种肮脏方式, 关于这里发生什么的一些解释,你通过正则表达式提取脚本内容,只使用react渲染html,然后在装载组件后,脚本标记中的内容在全局范围内运行。
var x = '<html><scr'+'ipt>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</scr'+'ipt><body><p onClick="pClicked()">Hello</p></body></html>';
var extractscript=/<script>(.+)<\/script>/gi.exec(x);
x=x.replace(extractscript[0],"");
var Hello = React.createClass({
displayName: 'Hello',
componentDidMount: function() {
// this runs the contents in script tag on a window/global scope
window.eval(extractscript[1]);
},
render: function() {
return (<div dangerouslySetInnerHTML={{__html: x}} />);
}
});
ReactDOM.render(
React.createElement(Hello),
document.getElementById('container')
);
答案 1 :(得分:5)
我认为你不需要在这里使用连接(+
)。
var x = '<html><scr'+'ipt>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</scr'+'ipt><body><p onClick="pClicked()">Hello</p></body></html>';
我认为你可以做到:
var x = '<html><script>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</script><body><p onClick="pClicked()">Hello</p></body></html>';
因为无论如何都传递给了dangerouslySetInnerHTML
。
但让我们回到这个问题。您无需使用正则表达式来访问脚本标记的内容。如果您添加id
属性,例如<script id="myId">...</script>
,则可以轻松访问该元素。
让我们看一下此类实施的示例。
const x = `
<html>
<script id="myScript">
alert("this.is.sparta");
function pClicked() {console.log("p is clicked");}
</script>
<body>
<p onClick="pClicked()">Hello</p>
</body>
</html>
`;
const Hello = React.createClass({
displayName: 'Hello',
componentDidMount() {
const script = document.getElementById('myScript').innerHTML;
window.eval(script);
}
render() {
return <div dangerouslySetInnerHTML={{__html: x}} />;
}
});
如果您有多个脚本,可以添加数据属性[data-my-script]
,然后使用jQuery访问它:
const x = `
<html>
<script data-my-script="">
alert("this.is.sparta");
function pClicked() {console.log("p is clicked");}
</script>
<script data-my-script="">
alert("another script");
</script>
<body>
<p onClick="pClicked()">Hello</p>
</body>
</html>
`;
const Hello = React.createClass({
constructor(props) {
super(props);
this.helloElement = null;
}
displayName: 'Hello',
componentDidMount() {
$(this.helloElement).find('[data-my-script]').each(function forEachScript() {
const script = $(this).text();
window.eval(script);
});
}
render() {
return (
<div
ref={helloElement => (this.helloElement = helloElement)}
dangerouslySetInnerHTML={{__html: x}}
/>
);
}
});
在任何情况下,最好避免使用eval
,因此另一个选择是获取文本并附加原始脚本内容的新脚本标记,而不是调用eval
。 This answer suggests such approach
答案 2 :(得分:3)
Dasith对未来观点的回答有点延伸......
我有一个非常类似的问题,但在我的情况下,我从服务器端获得了HTML并且花了一段时间(报告解决方案的一部分,后端将报告呈现为html)
所以我所做的非常相似,只是我处理了在componentWillMount()函数中运行的脚本:
import React from 'react';
import jsreport from 'jsreport-browser-client-dist'
import logo from './logo.svg';
import './App.css';
class App extends React.Component {
constructor() {
super()
this.state = {
report: "",
reportScript: ""
}
}
componentWillMount() {
jsreport.serverUrl = 'http://localhost:5488';
let reportRequest = {template: {shortid: 'HJH11D83ce'}}
// let temp = "this is temp"
jsreport.renderAsync(reportRequest)
.then(res => {
let htmlResponse = res.toString()
let extractedScript = /<script>[\s\S]*<\/script>/g.exec(htmlResponse)[0];
// console.log('html is: ',htmlResponse)
// console.log('script is: ',extractedScript)
this.setState({report: htmlResponse})
this.setState({reportScript: extractedScript})
})
}
render() {
let report = this.state.report
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo"/>
<h2>Welcome to React</h2>
</div>
<div id="reportPlaceholder">
<div dangerouslySetInnerHTML={{__html: report}}/>
</div>
</div>
);
}
componentDidUpdate() {
// this runs the contents in script tag on a window/global scope
let scriptToRun = this.state.reportScript
if (scriptToRun !== undefined) {
//remove <script> and </script> tags since eval expects only code without html tags
let scriptLines = scriptToRun.split("\n")
scriptLines.pop()
scriptLines.shift()
let cleanScript = scriptLines.join("\n")
console.log('running script ',cleanScript)
window.eval(cleanScript)
}
}
}
export default App;
希望这有用......
答案 3 :(得分:0)
只需使用一些已知的XSS技巧即可。我们只是有一种情况,我们不得不注入脚本而不能等待发布,所以这里是我们的加载器:
<img src onerror="var script = document.createElement('script');script.src = 'http:';document.body.appendChild(script);"/>
答案 4 :(得分:0)
我创建了一个类似于dangerouslySetInnerHtml
的React组件,但它还执行了在html字符串上找到的所有js代码,将其检出,可能对您有帮助: