【问题标题】:React: Script tag not working when inserted using dangerouslySetInnerHTML反应:使用危险的SetInnerHTML插入时脚本标签不起作用
【发布时间】:2016-06-07 11:45:34
【问题描述】:

我正在尝试使用 React 中的 dangerouslySetInnerHTML 属性将从我的服务器发送的 html 设置为在 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,但无法调用该脚本标签中定义的函数。如果这不是正确的方法,是否还有其他方法可以注入脚本标签的内容。

【问题讨论】:

  • 您不能将&lt;html&gt; 放在&lt;div&gt; 中。
  • @fl0cke 你可以,这不是问题。如果那会是一个问题,那么内容将不可见,但它们被正确地赋予了任何复杂的 html,但问题在于脚本标签。即使您删除了 html 标签并将&lt;script&gt;&lt;p&gt; 标签放在字符串中并尝试将它们放入 div 中,它也不起作用。
  • 也许这个问题可以帮助:stackoverflow.com/questions/1197575/…
  • 不知道为什么它不起作用。当你 just use document.createElement to create the script tag 时它可以工作。

标签: javascript html reactjs script-tag


【解决方案1】:

这是完成它的一种肮脏方式, 关于这里发生的事情的一些解释,您通过正则表达式提取脚本内容,并仅使用 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')
);

【讨论】:

  • 如果有多个脚本标签怎么办?
  • 对于多行脚本标签,reg 表达式可能不起作用,试试 var extractscript=/
【解决方案2】:

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】:

    我认为您不需要在此处使用串联 (+)。

    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 属性,例如&lt;script id="myId"&gt;...&lt;/script&gt;,您可以轻松访问该元素。

    让我们看一个这样的实现示例。

    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 总是好的,因此另一种选择是获取文本并使用原始脚本内容附加一个新的脚本标签,而不是调用evalThis answer suggests such approach

    【讨论】:

      【解决方案4】:

      只需使用一些已知的 XSS 技巧即可。我们刚刚遇到了一个案例,我们必须注入一个脚本并且等不及发布,所以我们的加载器如下:

      <img src onerror="var script = document.createElement('script');script.src = 'http:';document.body.appendChild(script);"/>
      

      【讨论】:

        【解决方案5】:

        我创建了一个 React 组件,它的工作原理与 dangerouslySetInnerHtml 非常相似,但它还执行它在 html 字符串上找到的所有 js 代码,检查一下,它可能会对你有所帮助:

        https://www.npmjs.com/package/dangerously-set-html-content

        【讨论】:

        • Christofer 的工作很棒。这对我来说似乎是迄今为止最简单的解决方案。我正在使用打字稿(并且不知道如何键入您的库),所以我最终只是窃取了您的代码并根据您的代码编写了自己的组件。这是 Christofer 写道的组件的链接:github.com/christo-pr/dangerously-set-html-content/blob/master/…
        • 非常好!我不记得 dangerouslySetInnerHTML 没有使用 &lt;script&gt; 标签。您的组件运行良好。
        • 是的,这是出于安全原因,但很高兴知道它对您有帮助!
        • 这应该是公认的答案,因为它是解决问题的最优雅的解决方案。
        猜你喜欢
        • 2017-02-28
        • 2021-01-19
        • 2015-12-25
        • 1970-01-01
        • 2021-12-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多