Closed aranw closed 10 months ago
Might not be the proper solution, but you can explicitly cast it to a string to resolve the error:
templ SomePage(h template.HTML) {
<div>
{ string(h) }
</div>
}
This doesn't appear to be an issue with templ, but just how Go's type system works.
You get the same error with just Go:
func foo(h template.HTML) {
var s string = h
}
Might not be the proper solution, but you can explicitly cast it to a string to resolve the error:
templ SomePage(h template.HTML) { <div> { string(h) } </div> }
This doesn't appear to be an issue with templ, but just how Go's type system works.
You get the same error with just Go:
func foo(h template.HTML) { var s string = h }
The problem with this is it escapes the html and puts it into the HTML as a string
The gorilla/csrf
package is giving me a HTML input element that I want to embed into my template
Would be handy to have something similar to templ.SafeURL
or templ.SafeClass
e.g. templ.SafeHTML
where it does not escape the given string or template.HTML in this case
This might be a good feature to add.
In terms of how this could work with templ, then I think it's just a templ component that renders the template.HTML
value. Here's how it would outside of the templ module (in your own code).
func GoTemplate(h template.HTML) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
_, err := io.WriteString(w, string(h))
return err
})
}
You could then use this in your templ code as:
<form>
@GoTemplate(csrf)
</form>
The only difference if it was built into templ would be that it would be a call to:
<form>
@templ.GoTemplate(csrf)
</form>
So far, I've resisted adding something built-in to templ to render unsafely since I don't want people to shoot themselves in the foot, but a more general version would bypass templ's output escaping for all strings:
func DangerouslyIncludeHTML(s string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
_, err := io.WriteString(w, s)
return err
})
}
The question is whether to provide a "Dangerous..." component, and if so, whether to bother with a specific function for Go HTML templates OR, to provide one for Go HTML template values only.
And for both, the classic problem of what they should be called. 😁
I would find a SafeHTML function, similar to SafeURL, that strips script tags etc very useful to handle untrusted html. A builtin UnsafeHTML escape hatch would be similarly useful when there are other trusted generators of html (could be in an unsafe sub package to keep people on their guard).
I would find a SafeHTML function, similar to SafeURL, that strips script tags etc very useful to handle untrusted html. A builtin UnsafeHTML escape hatch would be similarly useful when there are other trusted generators of html (could be in an unsafe sub package to keep people on their guard).
Are you thinking something along the lines of:
safe.HTML
/safe.URL
/safe.CSS
/etc
and
unsafe.HTML
/unsafe.URL
/unsafe.CSS
/etc
In terms of how this could work with templ, then I think it's just a templ component that renders the
template.HTML
value. Here's how it would outside of the templ module (in your own code).func GoTemplate(h template.HTML) templ.Component { return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error { _, err := io.WriteString(w, string(h)) return err }) }
You could then use this in your templ code as:
<form> @GoTemplate(csrf) </form>
This suggested solution worked for me and I switched to it for now until a feautre in templ itself lands
Are you thinking something along the lines of:
safe.HTML
/safe.URL
/safe.CSS
/etc andunsafe.HTML
/unsafe.URL
/unsafe.CSS
/etc
The safe variants already live in the templ package.
The choice between templ.UnsafeHTML
and putting it in another package depends mainly on how explicit the authors want to make unsafe behaviour? One of the main attractions for me is the default safety of templ.
The concept of safety that we're talking about is whether the content is fully under control of the developer. Dynamic content from external sources (user input, 3rd party libraries etc.) isn't under our control, so we shouldn't trust it.
It's fairly well explained at https://gohugo.io/functions/safe/url/ and https://gohugo.io/functions/safe/html/ docs in short form, and in the linked OWASP video at https://github.com/google/safehtml
The SafeHTML
function from Hugo and similar libraries isn't making the HTML safe. It is a declaration by the developer. By using the function, the developer "Declares a provided string as a “safe” HTML document, and Hugo then bypasses content sanitization and escaping.
The idea is that all content goes through the sanitization and escaping process, unless it is a Safe*
type.
In the case of templ, adding a SafeHTML
type and a component to render it would allow bypassing of sanitization. It's basically the same code as what I called DangerouslyIncludeHTML
:
// SafeHTML is HTML that has come from a trusted source, or has been sanitized.
type SafeHTML string
// Include HTML from another trusted source.
func Include(s SafeHTML) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
_, err := io.WriteString(w, string(s))
return err
})
}
Then, it would be a case of:
<form>
@templ.Include(templ.SafeHTML(csrf))
</form>
Could also echo Google's SafeHTML approach which prevents type casting.
// SafeHTML is HTML that has come from a trusted source, or has been sanitized.
type SafeHTML struct {
content string
}
func DeclareHTMLSafe(s string) SafeHTML {
return SafeHTML{ content: s }
}
// Include HTML from another trusted source.
func Include(s SafeHTML) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
_, err := io.WriteString(w, s.content)
return err
})
}
<form>
@templ.Include(templ.DeclareHTMLSafe(csrf))
</form>
Hi @a-h you're right, I got confused, ignore my remarks above.
If we focus back on CSRF token handling, then we don't need a Safe HTML thing at all.
Note the csrfToken
argument, and that it's applied to the hidden gorilla.csrf.Token
input element.
templ form(csrfToken string) {
<form action="/" method="POST">
<input type="hidden" name="gorilla.csrf.Token" value={ csrfToken }/>
<div><button type="submit" name="global" value="global">Global</button></div>
<div><button type="submit" name="user" value="user">User</button></div>
</form>
}
Then, in the HTTP handler, it's a case of passing the csrf.Token(r)
to the template instead of csrf.TemplateField
.
func getHandler(w http.ResponseWriter, r *http.Request) {
userCount := sessionManager.GetInt(r.Context(), "count")
component := page(csrf.Token(r), global.Count, userCount)
component.Render(r.Context(), w)
}
So. I think we have two things in this issue.
I think the answer to #1 is no. Not having it makes accidental XSS much less likely, and I think that anyone who knows they need it won't mind writing the few lines of code. It could be documented though.
On 2, I think a docs update would be a good idea. I think a new section of "Using with other tools", and having "Gorilla", "HTMX" etc. under that would make sense.
So... actions are:
Include
and SafeHTML
concept above in "Syntax and usage" > "Including raw HTML".Right now, I have created a helper function I use for this
func Raw(s string, errs ...error) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
_, err := fmt.Fprint(w, s)
return errors.Join(append(errs, err)...)
})
}
I really think the ability to render raw HTML should be part of the package itself. It makes sense to give the user a convenient way to opt-out of HTML escaping.
The ability to render raw HTML should at least be mentioned in the documentation if its not included. I don't mind adding in DangerouslyIncludeHTML
to get the functionality, but it took me a while to find this issue to get an answer about it.
It's totally understandable if you don't want to include it, but its also one of those things that is required unfortunately, hence appearing in every template language out there.
BTW thanks so much for making this. I absolutely adore it and plan on porting everything I maintain to it over time.
So far, I've resisted adding something built-in to templ to render unsafely since I don't want people to shoot themselves in the foot, but a more general version would bypass templ's output escaping for all strings:
func DangerouslyIncludeHTML(s string) templ.Component { return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error { _, err := io.WriteString(w, s) return err }) }
Thanks for this repo and the helper func. However, I agree with @boyter: this really should be included in the docs if it's not available and point to this issue.
I had never had problem with raw HTML for frontend before until using templ
. This is a valid use case.
Makes sense, I propose that we add the following...
// Raw renders the input HTML to the output without applying HTML escaping.
//
// Use of this component presents a security risk - the HTML should come from
// a trusted source, because it will be included as-is in the output.
func Raw[T ~string](html T, errs ...error) Component {
return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
if err = errors.Join(errs...); err != nil {
return err
}
_, err = io.WriteString(w, string(html))
return err
})
}
// GoHTMLTemplate renders the Go html/template to the output.
func GoHTMLTemplate(t *template.Template, data any) Component {
return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
return t.Execute(w, data)
})
}
I've got tests for these components ready, and will add some documentation alongside.
OK folks, PR is in place. Comments welcome.
I updated it to add bi-directional support - with these helper functions you can use templ in Go templates, and Go templates in templ.
If we focus back on CSRF token handling, then we don't need a Safe HTML thing at all.
Note the
csrfToken
argument, and that it's applied to the hiddengorilla.csrf.Token
input element.templ form(csrfToken string) { <form action="/" method="POST"> <input type="hidden" name="gorilla.csrf.Token" value={ csrfToken }/> <div><button type="submit" name="global" value="global">Global</button></div> <div><button type="submit" name="user" value="user">User</button></div> </form> }
Then, in the HTTP handler, it's a case of passing the
csrf.Token(r)
to the template instead ofcsrf.TemplateField
.func getHandler(w http.ResponseWriter, r *http.Request) { userCount := sessionManager.GetInt(r.Context(), "count") component := page(csrf.Token(r), global.Count, userCount) component.Render(r.Context(), w) }
So. I think we have two things in this issue.
- Do we want a raw "Include" component built into templ?
- Do we need to document how to do CSRF with templ?
I think the answer to #1 is no. Not having it makes accidental XSS much less likely, and I think that anyone who knows they need it won't mind writing the few lines of code. It could be documented though.
On 2, I think a docs update would be a good idea. I think a new section of "Using with other tools", and having "Gorilla", "HTMX" etc. under that would make sense.
So... actions are:
- Document the
Include
andSafeHTML
concept above in "Syntax and usage" > "Including raw HTML".- Add "Using with other tools" as a section, add Gorilla, and include a section on how to use Gorilla CSRF with templ.
I just wanted to leave an update on using Gorilla CSRF with templ for anyone that got here for the same reason.
I believe we can achieve the same things a bit easier.
All csrf.TemplateField and csrf.Token does is look for "gorilla.csrf.Token" key on the request context.
So you can just do this instead:
templ Csrf() {
<input type="hidden" name="gorilla.csrf.Token" value={ ctx.Value("gorilla.csrf.Token").(string) }/>
}
and use that template inside any form. You wont need to drill the token from the handler. In my case is working without an issue just make sure to have added the csrf middleware on your http Handler.
@a-h if its ok, i'm happy to make a pr adding a page to the docs about working with Gorilla CSRF including your examples
Thanks @xV0lk - if you have time for a PR, that would be great!
@a-h I ran into this issue today, Thanks for your support and @xV0lk also. I have made a PR to add this to the docs here https://github.com/a-h/templ/pull/622
I'm using the https://github.com/gorilla/csrf library to get a csrf token for forms and it provides you with a https://pkg.go.dev/html/template#HTML when you use the https://pkg.go.dev/github.com/gorilla/csrf#TemplateField func
This all works fine as
HTML
is just a string but right now the compiler does give a warning or something?A really simple reproducible example is
Which generates the following
Templ Version
I would try do this but I'm still getting to know the library and how it all works so I thought I'd just log it here in case anyone else with more experience can quickly solve this