a-h / templ

A language for writing HTML user interfaces in Go.
https://templ.guide/
MIT License
8.26k stars 272 forks source link

generator: `templ.URL` sanitization for non-standard attributes #422

Closed bshore closed 9 months ago

bshore commented 9 months ago

I'm working through the HTMX book and I noticed the templ generator only generates sanitized URLs for:

Generator code reference

I believe this is where the issue is - https://github.com/a-h/templ/blob/v0.2.513/generator/generator.go#L1084

Working as expected

On a <form action=""></form> or <a href=""></a> the generator defines the SafeURL on a separate line, and then casts it with string() when writing.

<form action={ templ.URL(fmt.Sprintf("/contacts/%s/edit", contact.ID)) } method="post">
  ...
</form>

Generates

_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form action=\"")
if templ_7745c5c3_Err != nil {
    return templ_7745c5c3_Err
}
// declares on a separate line
var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(fmt.Sprintf("/contacts/%s/edit", contact.ID))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
//                                                                           ^ and casts as string()

Generator produces error

On anything other than an <a href=""></a> or <form action=""></form> is written as a single line and causes an error.

<input
  name="email"
  id="email"
  type="email"
  hx-get={ templ.URL(fmt.Sprintf("/contacts/%s/email", contact.ID)) }
  ...
/>

Generates

_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</label> <input name=\"email\" id=\"email\" type=\"email\" hx-get=\"")
if templ_7745c5c3_Err != nil {
    return templ_7745c5c3_Err
}
// writes as a one-liner
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(
  templ.EscapeString(templ.URL(fmt.Sprintf("/contacts/%s/email", contact.ID))),
//     ^ cannot use templ.URL (value of type templ.SafeURL) as string value in argument to templ.EscapeString
)

Workaround

I do have a workaround and it's totally fine, I just skip doing templ.URL on hx-* attributes:

hx-get={ fmt.Sprintf("/contacts/%s/email", contact.ID) }

Contribute

I'd like to contribute a fix for this if possible, maybe checking for these attributes?

Or maybe is there a way to make an allow list? Any ideas?

bshore commented 9 months ago

Repo: https://github.com/bshore/htmx-contact-app

File where I noticed the behavior: https://github.com/bshore/htmx-contact-app/blob/7140c9d5332c03a361ebac6503dc9d633a1af353/internal/views/edit.templ

joerdav commented 9 months ago

@bshore These are the only attributes at the moment which require sanitizing URLs, however you can still leverage the sensitization function, with some slightly verbose code, in your case it would be:

<input
  name="email"
  id="email"
  type="email"
  hx-get={ string(templ.URL(fmt.Sprintf("/contacts/%s/email", contact.ID))) }
  ...
/>

An option to reduce this would be for a new function templ.SanitizeURL(string) string, which encapsulates the functionality of templ.URL(string) templ.SafeURL, but returns a string. But as you can see it doesn't involve any less typing:

string(templ.URL(fmt.Sprintf("/contacts/%s/email", contact.ID))) templ.SanitizeURL(fmt.Sprintf("/contacts/%s/email", contact.ID))

bshore commented 9 months ago

@joerdav So maybe instead what I can do is contribute a docs update for others that may encounter this.

A note here about non-standard HTML attributes that contain URLs ( i.e. HTMX )

https://templ.guide/syntax-and-usage/attributes/#url-attributes

joerdav commented 9 months ago

I agree, a small note on the fact that this function can be used independent of href/action would be useful I think.