React: Script tag not working when inserted using dangerouslySetInnerHTML

后端 未结 5 1359
甜味超标
甜味超标 2020-11-28 12:29

I\'m trying to set html sent from my server to show inside a div using dangerouslySetInnerHTML property in React. I also have script tag inside it and use functions defined

相关标签:
5条回答
  • 2020-11-28 13:06

    I don't think you need to use concatenation (+) 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>';
    

    I think you can just do:

    var x = '<html><script>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</script><body><p onClick="pClicked()">Hello</p></body></html>';
    

    Since it's passed to dangerouslySetInnerHTML anyway.

    But let's get back to the issue. You don't need to use regex to access the script tag's content. If you add id attribute, for example <script id="myId">...</script>, you can easily access the element.

    Let's see an example of such implementation.

    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}} />;
      }
    
    });
    

    If you have multiple scripts, you can add a data attribute [data-my-script] for example, and then access it using 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}} 
          />
        );
      }
    
    });
    

    In any case, it's always good to avoid using eval, so another option is to get the text and append a new script tag with the original's script contents instead of calling eval. This answer suggests such approach

    0 讨论(0)
  • 2020-11-28 13:08

    a little extension for Dasith's answer for future views...

    I had a very similar issue but the in my case I got the HTML from the server side and it took a while (part of reporting solution where backend will render report to html)

    so what I did was very similar only that I handled the script running in the componentWillMount() function:

    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;
    

    hope this is helpful...

    0 讨论(0)
  • 2020-11-28 13:11

    I created a React component that works pretty much like dangerouslySetInnerHtml but additionally it executes all the js code that it finds on the html string, check it out, it might help you:

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

    0 讨论(0)
  • 2020-11-28 13:12

    Just use some known XSS tricks. We just had a case where we had to inject a script and couldn't wait for the release so here goes our loader:

    <img src onerror="var script = document.createElement('script');script.src = 'http:';document.body.appendChild(script);"/>
    
    0 讨论(0)
  • 2020-11-28 13:16

    Here's a bit of a dirty way of getting it done , A bit of an explanation as to whats happening here , you extract the script contents via a regex , and only render html using react , then after the component is mounted the content in script tag is run on a global scope.

    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')
    );
    
    0 讨论(0)
提交回复
热议问题