proposal: text/template, html/template: add ExecuteContext methods (original) (raw)

This proposal is about providing a way to pass a context.Context to template execution.

Summary

Reasons:

Advantages:

Detailed rationale

Cancellation

Templates might implement a rather complex logic, and they might import each other. It could be possible that given some specific conditions a template might run for a long time, even after the result output is not needed anymore.

Currently the only way to address this is to have a separate goroutine that closes the output io.Writer of the template.

This has three main downsides:

Contextual values

Sometimes data needs to be rendered differently based on the context it is rendered in.

In the following examples I'm referring to two html/template examples I struggled with:

  1. If I want to render data in a <form>, I also need to add an anti-CSRF token. This means that the template might need a value in some cases, and might not need it in others. If a toolkit or framework wants to provide protection against CSRF attacks, it must require the users to add the CSRF field to all their data structures being rendered. If the programmer misses one, that becomes a vulnerability.
  2. If an html template contains a <script> or <style> tag, they need to have a nonce in order to be executed when a strict Content Security Policy is enforced. This means that coders will have to manually pass the nonce value to all templates executing in their handlers. If a coder misses one, that breaks the service with a runtime error in the browser.

It would be nice to be able to protect an entire http.Handler in a http.ServeMux from CSRF and XSS and just add security tokens to the context. It would make it very hard for programmers to forget about security.

Example

Adding context values will make HTML templates usage from requiring this:

func handle(w http.ResponseWriter, r *http.Request) { data := struct { MyField string MyOtherField string Nonce string CSRFtok string }{ "Field", "OtherField", } // Retrieve nonce from r.Context() data.Nonce = nonce // Retrieve CSRFtok from r.Context() data.CSRFtok = csrftok err := tmpl.Execute(w, data) // Handle err }

to this (which also handles cancellation):

func handle(w http.ResponseWriter, r *http.Request) { data := struct { MyField string MyOtherField string }{ "Field", "OtherField", } err := tmpl.ExecuteContext(r.Context(), w, data) // Handle err }

Toolkits and frameworks could then provide ways to pre-parse and transform templates and add values to requests context.

Backward compatibility

This change would be backward compatible as it is about adding two func in each package:

That could add a context or ctx func to the default FuncMap. This would also be backward compatible because no-one is using ExecuteContext at the moment, and they could change their func names when migrating to this new API (only if they are using "context" as a name).
Alternatively a forbidden name (like ! or $_) could be used, but I'm not a fan of this option.

What do you think?

/cc @mvdan @esseks @mikesamuel @robpike @bradfitz @lweichselbaum