Реагируйте: тег Script не работает при вставке с использованием опасно SetInnerHTML
Я пытаюсь установить html, отправленный с моего сервера, чтобы показать внутри div, используя свойство dangerouslySetInnerHTML в React. У меня также есть тег script внутри него, и я использую функции, определенные в этом html. Я сделал пример ошибки в JSFiddle здесь.
Это тестовый код:
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, но не может вызвать функции, определенные в этом теге сценария. Если это не правильный путь, есть ли другой способ, которым я могу вставить содержимое тега скрипта.
3 ответа
Вот немного грязный способ сделать это, Немного объяснения того, что здесь происходит, вы извлекаете содержимое скрипта через регулярное выражение и визуализируете 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')
);
Я создал компонент React, который работает примерно так же, как dangerouslySetInnerHtml
но, кроме того, он выполняет весь js-код, который он находит в строке 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 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
, Этот ответ предполагает такой подход
Небольшое расширение для ответа Дасит для будущих взглядов...
У меня была очень похожая проблема, но в моем случае я получил 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;
надеюсь, что это полезно...
Просто воспользуйтесь некоторыми известными приемами XSS. У нас был случай, когда нам пришлось внедрить скрипт и мы не могли дождаться релиза, так что вот наш загрузчик:
<img src onerror="var script = document.createElement('script');script.src = 'http:';document.body.appendChild(script);"/>