-
Notifications
You must be signed in to change notification settings - Fork 17.9k
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
html/template: template.HTML strings escaped (Go 1.9 regression) #21844
Comments
https://play.golang.org/p/dWGICIIIbn As far as I can see, template.HTML works as intented. I'm not sure, is this |
@mattn I will have a look on this later; your conclusion is, however, a little simplistic. |
Tentatively marking as 1.9.1 issue. Let's see what the problem and fix are. |
A template parse tree example in this situation is:
Which fits good with the description in #20842. I may find time to extract a repro tomorrow. |
This looks similar to #20842, where executing one template within another template confusing the autoescaper. Perhaps there was some edge case that I didn't catch? I'm not too familiar with the internals of Hugo, so if you could come up with a repro, I would appreciate it. |
I have added a fix (or a workaround) in Hugo: gohugoio/hugo@2d613dd#diff-640125fcf55eaedde41a99d7d2f3ad96R308 clone := texttemplate.Must(templ.Clone())
tt.AddParseTree(withoutExt, clone.Tree) Don't ask why we use |
I'm not sure, I don't read code of hugo well.
If template is re-used to Parse() after Execute(), (AFAIK), it should be cloned. |
@mattn you are reading the code wrong. But let us not spend time on the Hugo source code, that could take a while. |
https://play.golang.org/p/JpKWWNv_mu This is minimal code to reproduce. |
diff --git a/src/html/template/template.go b/src/html/template/template.go
index 6a661bf..1be8869 100644
--- a/src/html/template/template.go
+++ b/src/html/template/template.go
@@ -217,6 +217,10 @@ func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error
return nil, err
}
+ if t.Tree == tree {
+ t.set[name] = t
+ return t, nil
+ }
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
text, err := t.text.AddParseTree(name, tree) fixes this. But I can't bet this is best. |
Thanks for the repro, @mattn. The problem here is that two different templates ("A" and "B") that are associated in the same set share the same underlying parse tree, while having different escaper states. When A is escaped, it modifies that underlying parse tree, then marks itself as being escaped. When we get to B, we don't realize that the underlying parse tree has already been modified since its escaper state is not "escaped". B proceeds to modify the parse tree again, leading to over-escaping. I'm not sure if this is valid use of AddParseTree. The function was added to html/template to mirror text/template functionality, where it was added to allow a template to be addressed from multiple associations:
I think we should probably disallow aliasing of the same underlying text/template/parse.Tree via the html/template API. The way to do this would be to add a check in AddParseTree that ensures that the new tree pointer does not already exist in the template set, i.e. something like: func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
for _, tmpl := range t.set {
if tmpl.Tree == tree {
return nil, fmt.Errorf("tree already exists in template set")
}
}
...
} |
@stjj89 That sounds reasonable to me -- it would still be a potentially breaking change vs 1.8.3, but it will be a loud one, which is always good. |
godoc says:
So I wonder it is breaking change. |
The above sentence does imply that the
I spent 5 hours debugging and fixing one application that got hit by this (because something broke). I'm in general very happy and impressed by Go and its quality, but let us call a spade for a spade: This is a breaking change. You may fix it in the documentation, but please don't close it with no action. |
As @stjj89 mentioned, we can not make sure if this use of AddParseTree is valid. We can change to return error here, but I think everyone doesn't want to make text/template slow. Also I understand your frustration, and it is regrettable that it happened. So I suggest to fix doc to avoid another people will take many time to debug again. |
That copying in the quoted statement seems to refer to "*Template values", not parse Trees, so I think it is actually accurate.
My proposed change will only affect html/template, not text/template. I don't expect AddParseTree to be used very often, so maybe the performance cost of searching through all associated templates won't be that bad. +@robpike who originally wrote Add/AddParseTree, added the documentation above, and approved the html/template version of AddParseTree. Rob, do you have an opinion on this? Should we prevent html/template users from shooting themselves in the foot like this with AddParseTree? |
It sounds like a bug to me, and easy to fix, but I am not an expert on html/template. I work on text/template and do what's needed in html/template to get the tests to pass. If someone can create a simple test for html/template that catches the problem, we can see if the proposed fix works. |
Change https://golang.org/cl/64770 mentions this issue: |
I've come up with a minimal proof-of-concept at https://play.golang.org/p/j6NtnrQSPq. I have uploaded one possible fix at https://golang.org/cl/64770. I discussed this with @mikesamuel, the author of html/template, and we think that AddParseTree doesn't really make sense in html/template's API. This method was originally introduce specifically to allow html/template modify the internals of the underlying parse tree. It's not clear why a html/template user would need to directly modify the underlying parse Tree, given that html/template was designed to auto-magically transform it. My guess is that AddParseTree is used to merge template sets, or to allow a single template to be addressable from multiple sets. Since we're considering breaking changes, perhaps we could completely replace AddParseTree with: // AddTemplate associates the given template with t.
//
// It returns an error if t, the given template, or any associated
// template has already been executed.
func (t *Template) AddTemplate(tmpl *Template) (*Template, error) While we're at it, we could also consider un-exporting Template's Tree field. These changes will ensure that only html/template, not the user, gets to modify the underlying parse trees, while still allowing users to add templates to different sets. @bep, does this work for your current use case? |
Yes. |
@bep, the CL just reports an error where there wasn't one before. Why does that make your Hugo users happy? |
Now I see that @bep has a workaround already. I don't understand why this would be considered a "fix" for Go 1.9.2:
The fix is not restoring the old Go 1.8 behavior, so I don't see a reason to put it in Go 1.9.2, especially since apparently Hugo may have been the only program doing this, and Hugo has a different workaround. |
@rsc Hugo is fine. We don't need this fix. As I understand the fix, we would still need to apply the current workaround. Which is fine. Whether this qualifies as a 1.9.2 fix is a judgement you have to take: There may be unknown bugs in the wild from the Go 1.9 release because of this which would get an |
As I understand it, which I don't, the argument in favor of putting this into 1.9.2 is that code that used to do something relatively harmless now does something horrible. The fix (CL 64770) would change it to return an error rather than do something horrible, so that might overall be worth doing. |
I'm not sure about the "horrible" part, but in general you are right. And this is in an area likely not covered by tests. Hugo didn't know before end users started to shout. |
I'm sorry, but I am still a bit confused. Why did we decide to disallow the old uses of AddParseTree? If they were working before, wouldn't the right fix (especially for Go 1.9.2) to be to keep them working? So far I've heard basically "we didn't mean to allow that", but in general that's been true of many things about the Go libraries, and our default position is to accept what we did and preserve the behavior. Is the old behavior somehow unsafe? |
I'm going to kick this back into Go 1.10. I think we still need to discuss whether the new error is correct or if we should make the old behavior keep working. |
Ping @stjj89 |
CL 64770 really just stops html/template users from shooting themselves in the foot by aliasing parse trees. Since the existing html/template docs do not say anything about the relationship between parse trees and html/template Templates, the existing behavior is technically correct, albeit unintuitive. I'm ok with reverting the change and leaving things as they are. |
@stjj89 yes, I understand what CL 64770 does. My question is why the behavior changed from Go 1.8 to Go 1.9 and whether we should be working to move back to the Go 1.8 behavior. (See top of shell transcript in #21844 (comment).) Was the change in Go 1.9 intentional? If not, do we know what caused it and how to revert to the old Go 1.8 behavior? |
@rsc sorry, I missed that part of your earlier comment. It turns out that this change in behavior from 1.8 to 1.9 is a side effect of CL 37880. Specifically, it removed a section of ensurePipelineContains that prevents duplicate escaping commands from being inserted into a pipeline. This change was made with the assumption that duplicate escapers only ever occur when a predefined escapers (e.g. html, urlquery) are present at the end of a pipeline. This assumption seems to hold, since html/template prevents already-escaped templates in a template set from being escaped again. This particular usage of AddParseTree violates this assumption by bypassing the mechanisms that html/template uses to prevent its parse trees from being re-escaped. I can restore the 1.8 behavior by changing the logic in ensurePipelineContains to account for duplicate escapers anywhere in the pipeline. Does that sound like the right thing to do? |
@stjj89 Can html/template track whether a particular piece of syntax has been escaped or not? It seems like that would just be an extra field in the Template struct or a map[something]bool on the side. In any event, then AddParseTree should be able to tell whether a particular piece of syntax needs escaping (because it's new) or not (because it came out of an existing html/template's tree). P.S. Thanks for linkifying CL references, but note that you have to put https:// at the beginning of URLs in markdown. :-) |
By "piece of syntax", I assume you mean a template action (i.e. something bound by delimiters, like Per-action book-keeping might be overkill, though. Each Template and its parse tree are either completely escaped or not. We could just key the map with parse tree pointers and use the value of that entry to decide whether or not to escape the parse tree.
Ah, thanks. I didn't realize. I've fixed the URLs in my two previous posts. |
It just occurred to me that any kind of template-level book-keeping will not work if the underlying parse tree is aliased by two different Template values. See https://play.golang.org/p/uHNbVnTa7T. It seems to me that the only way to solve this problem in general is to attach a flag to the parse tree itself that indicates whether that tree has already been escaped. However, such a html/template-specific flag doesn't seem to belong to the text/template/parse API. Perhaps we could add an exported interface{} field to the parse Tree that html/template can use to store this flag? |
I'm pretty frustrated that we didn't manage to understand this well enough to restore the Go 1.8 behavior, but at this point I'm willing to just cut our losses and move on. We need to find a real owner for this package at some point. |
Change https://golang.org/cl/83920 mentions this issue: |
Sorry, this issue got swept under the rug. I'm giving this one more shot by reverting my AddParseTree change and adding a new fix that restores Go1.8 behavior by checking for duplicate escapers when rewriting pipelines. I hope it's not too late to consider this fix. |
Thanks very much. |
This has been reported by several users in the wild after we released Hugo 0.27 built on Go 1.9.
I have tried to make a simple standalone and failing test case, not yet successful.
I have, however, a failing test inside Hugo:
https://travis-ci.org/gohugoio/hugo/builds/274496313?utm_source=github_status&utm_medium=notification
It passes fine on Go 1.8.3, fails on Go 1.9 and tip.
See gohugoio/hugo#3878
The gist of it seems to be:
template.HTML
types to mark them as safe.Note that we have had plenty of similar and passing tests in Hugo, so there is a corner case here that I don't fully understand.
I will go back to using Go 1.8.3 (where I can).
The text was updated successfully, but these errors were encountered: