2

I'm trying to make a web app that refreshes its content, its styles and scripts using AJAX, so the site updates everything without reloading the page.

So what happens is that I get the page first of all and when it loads I use ajax to make a request to the server and get the html content then another request to get the styles and finally another request to get the script and put it inside a script tag

<script> //The script goes here </script>

then, I put the script tag at the end of the html content and update the body completely with the content

<body>
  The HTML content goes here
  <script> //and here is the script </script>
</body>

The requests are successful and the content loads and so the styles and when I use the browser inspector tool I can see the script loaded inside the script tag but the script doesn't execute.

This was an overview of the question, you can see the code here https://codepen.io/Yousef-Essam/project/editor/ZJGxea

app.js serves the index.html file and then the index file gets the script.js file which sends two requests with ajax to get the content.html file and content.js file and sets the href attribute of the link tag to the style file. The content.js file then is put inside a script tag with the new content. Although the content.html loaded and the style file as well and the script loads in the script tag, the script isn't executed.

Why did that happen and how can I fix it?

--Update--

The problem may be really in using the innerHTML but why doesn't it really work.

Also, I think that eval may be a solution but I want a better solution as using eval is discouraged

Yousef Essam
  • 151
  • 1
  • 11
  • not looking at the codepen, but betting you are using innerHTML and scripts are not executed in innerHTML and there are plenty of dupes on that question. – epascarello Jul 18 '18 at 01:20
  • `document.body.innerHTML = content` bingo – Sam Holmes Jul 18 '18 at 01:21
  • that's right but why are scripts not executed in innerHTML – Yousef Essam Jul 18 '18 at 01:22
  • Possible duplicate of [Calling a JavaScript function returned from an Ajax response](https://stackoverflow.com/questions/510779/calling-a-javascript-function-returned-from-an-ajax-response) – Sam Holmes Jul 18 '18 at 01:22
  • Check the duplicate I just linked, but essentially you gotta use [`eval()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval) instead. – Sam Holmes Jul 18 '18 at 01:22
  • I don't think it helps, and still why innerHTML is the problem – Yousef Essam Jul 18 '18 at 01:27
  • additionally using eval is discouraged and I don't think it's the real solution – Yousef Essam Jul 18 '18 at 01:28
  • I'm not really an expert, but I think `eval()` is discouraged in general because it executes the code that you pass it, so if the user enters malicious code, that can be dangerous and pose a security risk. However, if you *want* to run the code, `eval()` will do that, so the fact that it runs the code isn't a problem, it's the desired behavior. – Keara Jul 18 '18 at 02:00

2 Answers2

2

This is too long to post as a comment so I'm posting as an answer.

You correctly note that:

I think that eval may be a solution but I want a better solution as using eval is discouraged

Indeed eval is problematic in any language because it essentially allows the running program to treat a string as code. The big problem with this is that this open the door to code injection (in layman's term: hacking). But this advice is more of a theoretical advice instead of an advice of what functions to use.

So what does the advice mean by eval?

Eval is any function or mechanism that allows strings to be interpreted as code. Different languages have different functions or features that allows eval to happen. Some languages for example allows recursive string interpolation. Some languages have a function called interp() that spawns a sub-interpreter. Some languages literally have a function called eval().

Javascript has four eval mechanisms:

  1. Using the eval() function.

    • Any string passed to eval() is treated as code.
  2. Setting the src attribute of script tags

    • The value of the src attribute is assumed to be a javascript file to be downloaded
    • This works in both literal code (actual code in HTML itself) or dynamically generated script tag (creating script element in javascript and set it's src property)
  3. The body of script tags

    • Any text inside a script tag is treated as code
    • This only works in literal code. As you found out, setting script body using innerHTML is banned (and has been since Netscape 4 - the first browser with javascript)
  4. The javascript: URI protocol

    • Everything after the : is treated as code
    • This only works if the user clicks the link

So, what you are trying to do is to perform an eval. It does not matter weather you use the eval function or not, you are still trying to do an eval using innerHTML (though it doesn't work)

So, is there a safe way to run javascript on the page if everything is an eval?

There is - at least for modern browsers.

Modern browsers implement a feature called subresource integrity. Unfortunately, at the time this answer is written, it is not supported on Edge. Basically you can calculate the hash of the js file (such as sha1) and declare it in the script tag so the browser can confirm that the js file has not been tampered with. The following is an example:

<script src="https://cdn.example.com/script.js"
 integrity="sha384-+/M6kredJcxdsqkczBUjMLvqyHb1K/JThDXWsBVxMEeZHEaMKEOEct339VItX1zB"></script>

Are you this paranoid?

It depends. The eval issue might really be important to you. If so, loading javascript from an external source with subresource integrity set up is the ONLY safe way to execute javascript.

But not everybody is that paranoid. We have been generally doing OK so far without this feature. Here are a few rules of thumb that can mitigate evaling code regardless if you use the script tag or call eval():

  1. Always make sure you ONLY execute static javascript code. Do not try to construct code from strings.

  2. If you must construct code from strings (for example using a templating language like Handlebars or using a bundler like Webpack or Browserify) then never include any user generated content in the code. You can still load content by making ajax requests instead of including them in the code.

  3. If you must have user generated content in your code than make sure you sanitise the content. There are several generally accepted strategies such as banning the use of special characters like < and > and " or escaping special characters. There are even libraries that will do this for you.

Basically what we want to avoid is situations like users entering their name as John"; console.log("gotcha");" and somehow being able to execute code.

slebetman
  • 109,858
  • 19
  • 140
  • 171
  • This answer is good. But there's still an issue "Setting the src attribute of script tag: This works in both literal code (actual code in HTML itself) or dynamically generated script tag (creating script element in javascript and set it's src property)", so when I try modifying the src attribute of a script tag that already exists, the attribute is modified but no script is executed! – Yousef Essam Jul 18 '18 at 03:33
  • Moving one step further, when I try to add a script tag using innerHTML and not using createElement and add a src attribute like that also nothing happens – Yousef Essam Jul 18 '18 at 03:36
  • turns out that using your second way of 'eval' is only valid when I create an element with createElement and setting its src attribute. Can you explain why the previous two issues I mentioned were invalid!? – Yousef Essam Jul 18 '18 at 03:38
  • 1
    For the same reason. The script tag is completely disabled when inserted with innerHTML. innerHTML has a lot of other limitations such as the inability to dynamically construct tables (you need to insert the entire table in one go) – slebetman Jul 18 '18 at 04:04
  • 1
    One really funny thing about `innerHTML` is that there is no standard that specifies how it should behave. When it came time to write the standard API for javascript to interact with elements (a.k.a. the DOM standard) they deliberately chose not to include innerHTML – slebetman Jul 18 '18 at 04:06
  • so what about setting the src attribute of an already-existing script tag, when I try and set the src attribute of a script tag which already exists nothing happens as if I didn't modify the attribute! – Yousef Essam Jul 18 '18 at 04:08
  • Sth src attribute is executed when the tag is first inserted in the page. If a pre-existing script tag created by `createElement` has not yet been inserted into the page you can still change the `src` attribute. After you've inserted it into the page it will no longer be processed – slebetman Jul 19 '18 at 12:24
1

The HTML specification dis-allows parsing SCRIPT elements dynamically added as HTML tags after page load using innerHTML (as per the note under the text property description in the living standard).

SO answers to similar questions without using eval to parse the script were not apparent in a simple search - while they may exist, I've only seen alternative techniques presented off-site.

Here's a dynamic loader that doesn't use eval. Because loading the script is asynchronous it uses a callback function of the (err, data) type to signal when the script can be called.

function loadScript( url, callback) {
    var el = document.createElement("SCRIPT");
    el.type = "text/javascript";
    function finish( err) {
        callback( err, err ? false : true);
    }
    el.onerror = finish;
    if( el.readyState){  // <= IE 10
        el.onreadystatechange = function(){
            if( this.readyState == "complete"){
                finish( null);
            }
        };
    }
    else { 
        el.onload = function() { finish(null) };
    }
    el.setAttribute("async", true);
    el.src = url;
    document.getElementsByTagName("HEAD")[0].appendChild( el);
} 

// example call to load jQuery 3.2.1         

loadScript( "https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js",
   function (err, ok) {
       if( err) {
           console.log( "loading failed: " + err);
        }
        else {
         console.log( "loading success");
        }
    }
);
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
traktor
  • 17,588
  • 4
  • 32
  • 53