New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dangerouslySetInnerHTML should NOT remove script tag #8838

Closed
rajaraodv opened this Issue Jan 21, 2017 · 9 comments

Comments

Projects
None yet
8 participants
@rajaraodv
Copy link

rajaraodv commented Jan 21, 2017

Do you want to request a feature or report a bug?
Bug
What is the current behavior?
There are many cases in the real-world where we need to inject external 3rd party script. For example, adding google-analytics code, adding stripe button and so on. Currently dangerouslySetInnerHTML removes the script tag making it hard to simply inject JS code to the page.
Because of this issue, people are writing bloated components like react-ga (google analytics) that's 12KB(minimized) and all it does it to add a script tag! There are other similar libs like: react-scripts and stripe-checkout (8KB minimized to add the script).

I believe that at least when using dangerouslySetInnerHTML, we should not remove script tag and instead run the script.

For example, adding stripe's button is as follows.
https://stripe.com/docs/checkout/tutorial#embedding

<form action="/your-server-side-code" method="POST">
  <script
    src="https://checkout.stripe.com/checkout.js" class="stripe-button"
    data-key="pk_test_5qV78InO5XtnYvFRZ2VKnIjy"
    data-amount="999"
    data-name="Demo Site"
    data-description="Widget"
    data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
    data-locale="auto">
  </script>
</form>

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar (template: https://jsfiddle.net/reactjs/69z2wepo/).

var StripeCheckout = React.createClass({
  createMarkup: function() {
    return {__html: 
      `Below script should create a Stripe button:
      <form action="/your-server-side-code" method="POST">
        <script
          src="https://checkout.stripe.com/checkout.js" class="stripe-button"
          data-key="pk_test_5qV78InO5XtnYvFRZ2VKnIjy"
          data-amount="999"
          data-name="Demo Site"
          data-description="Widget"
          data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
          data-locale="auto">
        </script>
      </form>`
    };
  },
  render: function() {
    return  <span dangerouslySetInnerHTML={this.createMarkup()} />;
  }
})

ReactDOM.render(
  <StripeCheckout />,
  document.getElementById('container')
);

What is the expected behavior?
It should display a "Stripe" button button.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
15.4.2. I believe that this is how React worked for a long time.

@niole

This comment has been minimized.

Copy link
Contributor

niole commented Jan 22, 2017

A workaround could be to make a script DOM element and append it to the appropriate parent. This stack overflow question also seems to have some answers:
http://stackoverflow.com/questions/35614809/react-script-tag-not-working-when-inserted-using-dangerouslysetinnerhtml

@rajaraodv

This comment has been minimized.

Copy link

rajaraodv commented Jan 24, 2017

I think that dangerouslySetInnerHTML should NOT strip script tag. When we are using dangerouslySetInnerHTML, we know that it could be 'dangerous'. Because of this issue, people are writing bloated components like react-ga (google analytics) that's 10KB(minimized) and all it does it to add a script tag! There are other similar libs like: react-scripts andstripe-checkout
CC @gaearon

@rajaraodv rajaraodv changed the title "script" tag in the renderer is gets removed (or doesnt work even when in dangerouslySetInnerHTML) "script" tag in the renderer is gets removed (aka dangerouslySetInnerHTML should NOT strip script tag) Jan 24, 2017

@rajaraodv rajaraodv changed the title "script" tag in the renderer is gets removed (aka dangerouslySetInnerHTML should NOT strip script tag) dangerouslySetInnerHTML should NOT remove script tag Jan 24, 2017

@abhirathore2006

This comment has been minimized.

Copy link

abhirathore2006 commented Jan 27, 2017

I use a very basic component to load scripts, you can customize it to load any script you wish for.
it gives you a callback, so you can continue doing stuff once script get loaded.

    export interface mapWrapperProps{
      asyncScriptOnLoad?:()=>void;
      libraries?:string;
    }

    class MapWrapper extends React.Component<mapWrapperProps,{}>{

      componentWillMount(){
         let scriptlibraries = this.props.libraries?"libraries="+this.props.libraries:"";
         this.loadScript("https://maps.googleapis.com/maps/api/js?key=apikey&" + scriptlibraries, this.props.asyncScriptOnLoad);
      }

      loadScript(src,callback){
        let len = $('script').filter(function () {
                    return ($(this).attr('src') == src);
                  }).length;
        if(len){
          if(callback)callback();
        }else { 
          var script = document.createElement("script");
          script.type = "text/javascript";
          if(callback)script.onload=callback;
          document.getElementsByTagName("head")[0].appendChild(script);
          script.src = src;
        }
      }
      render(){
        return (<div></div>);
      }
    }
    export default MapWrapper;
@brigand

This comment has been minimized.

Copy link
Contributor

brigand commented Apr 8, 2017

I don't think this is a React issue, it's just how setting innerHTML works.

@nhunzaker

This comment has been minimized.

Copy link
Collaborator

nhunzaker commented Jul 12, 2017

scripts injected via innerHTML will not execute:

Without React:

https://codepen.io/nhunzaker/pen/GEPBJz

The same is true for React:

https://codepen.io/nhunzaker/pen/VWqdJx

Why:

This is because script tags injected via innerHTML are not executed for security reasons:

https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML

Although this may look like a cross-site scripting attack, the result is harmless. HTML5 specifies that a <script> tag inserted via innerHTML should not execute.

What to do

I think a solution like @abhirathore2006's answer is spot on. As a far as bloat is concerned, one developer's bloat is often another's response to edge cases. I would encourage you to reach out to the projects to learn their reasoning, or possibly fork the project for your specific requirements.

@nhunzaker nhunzaker closed this Jul 12, 2017

@jakearchibald

This comment has been minimized.

Copy link

jakearchibald commented Dec 4, 2017

createContextualFragment will run scripts http://jsbin.com/pajubuz/edit?js,output.

I don't know how many people want this, but React could add dangerouslySetHtmlContent which used createContextualFragment rather than innerHTML.

@nhunzaker

This comment has been minimized.

Copy link
Collaborator

nhunzaker commented Dec 19, 2017

Nice! Sorry, I kept meaning to respond here. DOM parsing/serialization is really neat stuff!

Still, I'm not sure how much steam there would be behind adding something like that, but I don't want to kill anyone's enthusiasm! If anyone is particularly interested in making a case for it, React now has a formal RFC repo. Personally, I'd want to know:

  1. What advantages exist for a dedicated property over using createContextualFragment in a lifecycle hook?
  2. Is this safe? How do we help users avoid injecting "live" script tags unintentionally?
  3. Does this behave consistently across every browser, is the behavior intentional, and is IE11+ support okay?
@webdeveloperpr

This comment has been minimized.

Copy link

webdeveloperpr commented Mar 1, 2018

Running into issues with draft-js and dangerouslySetInnerHTML property. I need to embed tweets and script tags need to run in order to do that. A way around make it work is to use an image's onload property to run scripts.

<img
  style="display: none;"
  class="load-script-img"
  src="http://placehold.it/1x1"
  onload="(function() {
    var script = document.createElement('script');
      script.setAttribute('src', 'https://platform.twitter.com/widgets.js');
      script.setAttribute('charset', 'utf-8');
      script.setAttribute('async', 'true');
      document.body.append(script);
  })()"
>
 <!--  Tweeter embed  -->
${html}
/>

I know it's not the best solution but is a way to go around the limitations.

@rickschott

This comment has been minimized.

Copy link

rickschott commented Apr 10, 2018

This feature is needed. If you have encapsulated html code with script you have to separate it out. In a pure React environment it's not normally needed, but in a large enterprise setting you don't always have control over the remote content you want to use. JQuery does this easily and I can't believe more people don't complain about this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment