- From: Adam Barth <w3c@adambarth.com>
- Date: Thu, 1 Nov 2012 09:14:32 -0700
- To: Hajime Morrita <morrita@google.com>
- Cc: Maciej Stachowiak <mjs@apple.com>, Anne van Kesteren <annevk@annevk.nl>, "public-webapps@w3.org WG" <public-webapps@w3.org>
- Message-ID: <CAJE5ia82g+hk_k=ZHHqsm9q-kJ+pCz=R=eZ3z4q-1iRJAgyj=Q@mail.gmail.com>
On Thu, Nov 1, 2012 at 8:42 AM, Hajime Morrita <morrita@google.com> wrote: > A naive proposal: Can we introduce an alias of <script> element, like > <scr>, and ask template authors to use <scr> instead of <script> inside > <script template>? Since <scr> is just an alias of <script>, authors can > use it even outside <script template>. > The problem is then that you can't have doubly nested templates because <scr> wouldn't be able to nest inside itself. Adam > I guess it won't confuse neither the tokenizer nor existing parser, and it > will be polyfill-friendly. It isn't as clean as <template>, but not as ugly > as <+script> IMO. Obviously "scr" sounds ugly so it is great if it has some > unused sensible name. > > > > > On Thu, Nov 1, 2012 at 3:14 PM, Adam Barth <w3c@adambarth.com> wrote: > >> >> >> >> On Thu, Nov 1, 2012 at 6:33 AM, Maciej Stachowiak <mjs@apple.com> wrote: >> >>> >>> On Nov 1, 2012, at 1:57 PM, Adam Barth <w3c@adambarth.com> wrote: >>> >>> >>>> >>> (5) The nested template fragment parser operates like the template >>>> fragment parser, but with the following additional difference: >>>> (a) When a close tag named "+script" is encountered which does not >>>> match any currently open script tag: >>>> >>> >>> Let me try to understand what you've written here concretely: >>> >>> 1) We need to change the "end tag open" state to somehow recognize >>> "</+script>" as an end tag rather than as a bogus comment. >>> 2) When the tree builder encounter such an end tag in the ???? state(s), >>> we execute the substeps you've outlined below. >>> >>> The problem with this approach is that nested templates parse >>> differently than top-level templates. Consider the following example: >>> >>> <script type=template> >>> <b >>> </script> >>> >>> In this case, none of the nested template parser modifications apply and >>> we'll parse this as normal for HTML. That means the contents of the >>> template will be "<b" (let's ignore whitespace for simplicity). >>> >>> <script type=template> >>> <h1>Inbox</h1> >>> <script type=template> >>> <b >>> </+script> >>> </script> >>> >>> Unfortunately, the nested template in this example parses differently >>> than it did when it was a top-level template. The problem is that the >>> characters "</+script>" are not recognized by the tokenizer as an end tag >>> because they are encountered by the nested template fragment parser in the >>> "before attribute name" state. That means they get treated as some sort of >>> bogus attributes of the <b> tag rather than as an end tag. >>> >>> >>> OK. Do you believe this to be a serious problem? I feel like >>> inconsistency in the case of a malformed tag is not a very important >>> problem, but perhaps there are cases that would be more obviously >>> problematic, or reasons not obvious to me to be very concerned about cases >>> exactly like this one. >>> >> >> It's going to lead to subtle parsing bugs in web sites, which usually >> means security vulnerabilities. :( >> >> Also: can you think of a way to fix this problem? Or alternately, do you >>> believe it's fundamentally not fixable? I've only spent a short amount of >>> time thinking about this approach, and I am not nearly as much an expert on >>> HTML parsing as you are. >>> >> >> I definitely see the appeal of trying to re-use <script> for templates. >> Unfortunately, I couldn't figure out how to make it work sensibly with >> nested templates, which is why I ended up recommending that we use the >> <template> element. >> >> Another approach we considered was to separate out the "hide from legacy >> user agents" and the "define a template" operations. That approach pushes >> you towards a design like >> >> <xmp> >> <template> >> <h1>Inbox</h1> >> <template> >> <h2>Folder</h2> >> </template> >> </template> >> </xmp> >> >> You could do the same thing with <script type=something>, but <xmp> is >> shorter (and currently unused). This approach has a bunch of >> disadvantages, including being verbose and having some unexpected parsing: >> >> <xmp> >> <template> >> <div data-foo="<xmp>bar</xmp>"> >> This text is actually outside the template! >> </div> >> </template> >> </xmp> >> >> The <script type=template> has similar problems, of course: >> >> <script type=template> >> <div data-foo="<script>bar</script>"> >> This text is actually outside the template! >> </div> >> </script> >> >> Perhaps developers have a clearer understanding of such problems from >> having to escape </script> in JavaScript? >> >> All this goofiness eventually convinced me that if we want to support >> nested templates, we ought to use the usual nesting mechanics of HTML, >> which leads to a design like <template> that nests like a normal tag. >> >> (a.i) Consume the token for the close tag named "+script". >>>> (a.ii) Crate a DocumentFragment containing that parsed >>>> contents of the fragment. >>>> (a.iii) [return to the parent template fragment parser] with >>>> the result of step (a.ii) with the parent parser to resume after the >>>> "+script" close tag. >>>> >>>> >>>> This is pretty rough and I'm sure I got some details wrong. But I >>>> believe it demonstrates the following properties: >>>> (B) Allows for perfect fidelity polyfills, because it will manifestly >>>> end the template in the same place that an unaware browser would close the >>>> <script> element. >>>> (C) Does not require multiple levels of escaping. >>>> (A) Can be implemented without changes to the core HTML parser (though >>>> you'd need to introduce a new fragment parsing mode). >>>> >>> >>> I suspect we're quibbling over "no true Scotsman" semantics here, but >>> you obviously need to modify both the HTML tokenizer and tree builder for >>> this approach to work. >>> >>> >>> In principle you could create a whole separate tokenizer and tree >>> builder. But obviously that would probably be a poor choice for a native >>> implementation compared to adding some flags and variable behavior. I'm not >>> even necessarily claiming that all the above properties are advantages, I >>> just wanted to show that there need not be a multi-escapting problem nor >>> necessarily scary complicated changes to the tokenizer states for <script>. >>> >>> I think the biggest advantage to this kind of approach is that it can be >>> polyfilled with full fidelity. But I am in no way wedded to this solution >>> and I am intrigued at the mention of other approaches with this property. >>> The others I know of (external source only, srcdoc like on iframe) seem >>> clearly worse, but there might be other bigger ones. >>> >> >> The xmp-like wrapper also can be polyfilled, as can approaches based on >> HTML comments or attributes. There's a trade-off, however. In the long >> view, it's not clear to me how important polyfillability is for a feature. >> It certainly makes adoption easier in the short term, but if we constrain >> ourselves to designing only features that can be polyfilled at each step, >> we'll end up with a contorted platform. >> >> >>> (D) Can be implemented with near-identical behavior for XHTML, except >>>> that you'd need an XML fragment parser. >>>> >>> >>> The downside is that nested templates don't parse the same as top-level >>> templates. >>> >>> >>> Indeed. That is in addition to the previously conceded downsides that >>> the syntax is somewhat less congenial. >>> >>> Another issue is that you've also introduced the following security risk: >>> >>> Today, the following line of JavaScript is safe to include in an inline >>> script tag: >>> >>> var x = "</+script><img onerror=alert(1)>"; >>> >>> Because that line does not contain "</script>", the string "alert(1)" >>> will be treated as the contents of a string. However, if that line is >>> included in an inline script inside of a template, the modifications of to >>> the parser above will mean that alert(1) will execute as JavaScript rather >>> than being treated as a string, introducing an XSS vector. >>> >>> >>> I don't follow. Can you give a full example of how this would be >>> included in a template and therefore be executed? >>> >> >> <script> >> x = "</+script><img onerror=alert(1)>"; // This is safe, there is no >> script execution. >> </script> >> >> <script type=template id=a> >> <script> >> x = "</+script><img onerror=alert(1)>"; // This is not safe, the >> alert(1) executes as script. >> </+script> >> </script> >> >> <script> >> document.body.appendChild(document.getTemplateById("a").instantiate()); >> </script> >> >> You should imagine, of course, the string not being written literally by >> the developer but instead generated on the server side by some code that >> knows how to escape strings for use in inline script tags (e.g., by >> escaping "\" and "</script"). It's certainly possible to defend against >> this XSS vector on the server, but it's one more XSS vector to worry about. >> >> I hope this clarifies the proposal. >>>> >>>> Notes: >>>> - Just because it's described this way doesn't mean it has to be >>>> implemented this way - implementations could do template parsing in a >>>> single pass with HTML parsing if desired. I wrote it this way mainly to >>>> demonstrate the desired properties/ >>>> >>> >>> I'm not sure how we'd be able to that without running multiple copies of >>> the tokenizer state machine in parallel. The tokenizer states for the >>> template fragment parser aren't going to line up in any meaningful way with >>> the top-level tokenizer's search for an appropriate end tag to escape from >>> the script data states. >>> >>> >>> I'm pretty confident it's *possible* to do a one-pass version of this >>> algorithm, but I am not sure if it is easy, or if it is desirable. >>> >>> What I actually imagined (knowing much less about HTML parsing than you) >>> was that you'd enter a different tokenizer state after encountering a >>> <script template> than a <script>. But defining that state would be >>> challenging. >>> >> >> Sure, but you're going to need a 17x bigger tokenizer state machine >> because you'll need to track all 17 script data states for the top-level >> tokenizer at the same time as you're tracking all the states for the >> template fragment tokenzier. >> >> Adam >> >> > > > -- > morrita > >
Received on Thursday, 1 November 2012 16:15:37 UTC