2
votes

I have this javascript which fetches a html part from the server. I'm able to return the html in an alert message and it's the correct code.

ajax: function(url){
        var request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                          .createInstance(Components.interfaces.nsIXMLHttpRequest);
        request.onreadystatechange = function() {
            if (request.readyState == 4) {
                // only if "OK"
                if (request.status == 200) {
                    var popup = document.getElementById("ajax"); // a <menupopup> element
                    var txt = request.responseText;
                    alert(txt);
                        popup.appendChild(txt);
                    } else {
                        alert("There was a problem retrieving the XML data:\n" +
                            request.statusText);
                }
            }
        };
        request.open("GET", url, true);
        request.send(null);
}

the xul/html returned from the server:

     <menuitem>
        <html:h2><html:a href="http://google.com">Google</html:a></html:h2>
        <html:p><html:table><html:tr><html:td>xxxx</html:td></html:tr></html:table></html:p>
     </menuitem>
     <menuitem>
        <html:h2><html:a href="http://yahoo.com">Yahoo</html:a></html:h2>
        <html:p><html:table><html:tr><html:td>yyyy</html:td></html:tr></html:table></html:p>
     </menuitem>

My xul page:

... some more code

<menu class="menu" label="test">
   <menupopup id="ajax" width="450" height="700" onpopupshowing="myextension.ajax('http://www.myserver.com/phpscript.php'>
  </menupopup>
</menu>

Now if I attach the html directly to the menupopup with id ajax it works as expected. When I attach it using appendChild it doesn't. I know I can't use appendChild but there seems to be no equivalent to innerHTML in XUL. I have no control over the xul/html returned from the server so I can't use the DOM methods to add the content and bypass the html.

I tried using HTMLParser https://developer.mozilla.org/en/Code_snippets/HTML_to_DOM to convert it into a DOM object but that doesn't seem to work, I believe because it cuts of the <menuitem> tag.

Any ideas how to attach the HTML to a menupopup so I can display them as menuitems.

Ok I tried the iframe approach:

        ajax: function(url){
            var request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                              .createInstance(Components.interfaces.nsIXMLHttpRequest);
            request.onreadystatechange = function() {
                if (request.readyState == 4) {
                    // only if "OK"
                    if (request.status == 200) {
                       var frame = document.getElementById("frame");
                       frame.setAttribute("src", "data:text/html," + encodeURIComponent(request.responseText));
                        } else {
                            alert("There was a problem retrieving the XML data:\n" + request.statusText);
                    }
                }
            };
            request.open("GET", url, true);
            request.send(null);
    }

with

<iframe id="frame" type="content" src="" />

This doesn't seem to work. However if I do an alert of the encodeURIComponent(request.responseText) it's correctly encoded. Evenmore if I directly add it to the iframe like this:

<iframe type="content" src="data:text/html,%3Chtml%3E%0A%3Cbody%3E%0A%3Cp%3EHello%20everyone%3C%2Fp%3E%0A%09%3C%2Fbody%3E%0A%3C%2Fhtml%3E%0A%0A"/>

it does not work because to display the html it needs to be within tags but the html contains many different items which should be each in their own tag. Simply adding to the html is not working.

2
If you control the server, you might be better off having the AJAX call return some JSON with the data you want to display, and then building the XUL/HTML structures on the client side, using either DOM methods or XBL.Tyler
I can't do that. Actually I'm receiving an RSS feed that contains some HTML in the description. I already parse the RSS feed to get that HMTL part out of it. But it's hard to parse the html as well as I don't know what is going to be in it. So I prefer to use the html as is.Stofke
Sorry, I didn't notice you already said you can't control the server.Tyler

2 Answers

3
votes

First the correct answer, which isn't really what you're looking for, but to important to omit:

Do not append/inject/whatever remotely retrieved HTML or javascript code into XUL or chrome code scopes!

Stuff like this always is a security vulnerability. JS is executable, and HTML may contain executable bits as well (e.g. javascript: protocol). Any injected code will run with full browser privileges, which translates to OS user privileges (which on Windows XP translates to Administrator privileges).

You need to either escape the html, or parse it and only leave secure bits in.

You cannot trust remote code, even if it originates from your own servers:

  • Man in the middle attacks (http)
  • Compromised server
  • Rough server admin (not you, hopefully ;))

BTW: Appending/injecting/whatever remotely retrieved code or HTML into chrome space will lead to rejection of the affected versions of your add-on on addons.mozilla.org for the reason stated above.

Now the technically correct answer, but do not use with remotely retrieved and sanitized HTML:

  1. You need to make sure the correct HTML namespace will be used (xmlns:html="http://www.w3.org/1999/xhtml")
  2. You cannot actually appendElement() text, but only real DOM elements. Hence you must parse any text into a DOM before. Easiest with valid XML (DOMParser); possible for tag soup stuff via a hidden iframe.
  3. You should adoptNode any Elements from different DOMs
  4. Append the elements one by one then (each menuitem and subtree).
1
votes

Your code is indeed a security vulnerability, you should never do it like that. There is a relatively easy way to do it securely however (important: this is assuming that your XUL isn't running in the browser's content area). You put an iframe into your menupopup:

<menu class="menu" label="test">
    <menupopup id="ajax" onpopupshowing="myextension.ajax('http://www.myserver.com/phpscript.php'>
        <menuitem>
            <iframe id="frame" type="content" width="450" height="700"></iframe>
        </menuitem>
    </menupopup>
</menu>

You can then load your data into that frame using a data: URL. The important part here is type="content" here, this creates a security boundary between your code (chrome) and the code you loaded (content). Which is why it is important that your XUL document isn't in the browser's content area - then you are already on the "content" side of the security boundary, you cannot establish another one.

Actually putting the data into the frame works like this:

var frame = document.getElementById("frame"); // <iframe> element
var txt = request.responseText;
frame.setAttribute("src", "data:text/html;charset=utf-8," + encodeURIComponent(txt));

For more information see https://developer.mozilla.org/En/Displaying_web_content_in_an_extension_without_security_issues (this article was written specifically with RSS readers in mind).