HTML Templating With Heist - snapframework/snap GitHub Wiki

Heist is a template system for HTML 5.

Compiled Splices

Interpreted Splices

Interpreted splices can be divided into two broad categorizations: substitutions and filters. Substitution splices are where the spliced tag disappears in the output. If the spliced tag is preserved in the output, then it's a filter splice.

Substitution splices

Filter splices

One of my (Doug's) blog posts on splice subtleties said that you must call stopRecursion in filter splices otherwise you'll get infinite recursion. This is true, but it's not the whole story. To see what I'm talking about let's look at a real world example.

Application: CSRF Protection

Our application is protection against cross-site request forgery (CSRF) attacks. To protect against CSRF, we want to add a hidden field to every form on our whole website. The value of this field (call it "_csrf") is a secure token unique to every session. Every time we get a POST request, we want to check the value of the _csrf field and make sure it matches the one that we created for the session. We can do this very nicely by creating a splice to bind to the form tag. Here's a naive approach:

secureForm1 :: MonadIO m
            => m Text
            -- ^ A computation in the runtime monad that gets the CSRF
            -- protection token.
            -> Splice m
secureForm1 csrfToken = do
    stopRecursion
    n <- getParamNode
    token <- lift csrfToken
    let input = X.Element "input"
          [("type", "hidden"), ("name", "_csrf"), ("value", token)] []
    case n of
      X.Element nm as cs -> return [X.Element nm as (input : cs)]
      _ -> return [n] -- "impossible"

But this has a problem. If you try it, you'll see that your form tags are getting multiple _csrf fields added. Calling stopRecursion does not prevent your splice from getting executed more than once, it only prevents it from recursively calling itself more than once. We need to add a check to see if we've already added the field.

secureForm2 :: MonadIO m
            => m Text
            -- ^ A computation in the runtime monad that gets the CSRF
            -- protection token.
            -> Splice m
secureForm2 csrfToken = do
    stopRecursion
    n <- getParamNode
    token <- lift csrfToken
    let input = X.Element "input"
          [("type", "hidden"), ("name", "_csrf"), ("value", token)] []
    case n of
      X.Element nm as cs -> do
        let newCs = if take 1 cs == [input] then cs else (input : cs)
        return [X.Element nm as newCs]
      _ -> return [n] -- "impossible"

But that still doesn't work properly.

secureForm3 :: MonadIO m
            => m Text
            -- ^ A computation in the runtime monad that gets the CSRF
            -- protection token.
            -> Splice m
secureForm3 csrfToken = do
    stopRecursion
    n <- getParamNode
    token <- lift csrfToken
    let input = X.Element "input"
          [("type", "hidden"), ("name", "_csrf"), ("value", token)] []
    case n of
      X.Element nm as cs -> do
        cs' <- runNodeList cs
        let newCs = if take 1 cs' == [input] then cs' else (input : cs')
        return [X.Element nm as newCs]
      _ -> return [n] -- "impossible"

Attribute Splices