Class: Iodine::Mustache

Inherits:
Data
  • Object
show all
Defined in:
lib/iodine/mustache.rb,
ext/iodine/iodine_mustache.c

Overview

Iodine includes a strict and extra safe Mustache templating engine.

The Iodine Mustache templating engine provides increased XSS protection through agressive HTML escaping. It’s also faster than the original (Ruby based) Mustache templating engine.

Another difference is that the Iodine Mustache templating engine always loads the templates from the disk, allowing for patial template path resolution.

There’s no monkey-patch for ‘mustache` Ruby gem since the API is incompatible.

You can benchmark the Iodine Mustache performance and decide if you wish to switch from the Ruby implementation.

require 'benchmark/ips'
require 'mustache'
require 'iodine'

def benchmark_mustache
  # benchmark code was copied, in part, from:
  #   https://github.com/mustache/mustache/blob/master/benchmarks/render_collection_benchmark.rb
  template = """
  {{#products}}
    <div class='product_brick'>
      <div class='container'>
        <div class='element'>
          <img src='images/{{image}}' class='product_miniature' />
        </div>
        <div class='element description'>
          <a href={{url}} class='product_name block bold'>
            {{external_index}}
          </a>
        </div>
      </div>
    </div>
  {{/products}}
  """

  IO.write "test_template.mustache", template

  data_1 = {
    products: [ {
      :external_index=>"This <product> should've been \"properly\" escaped.",
      :url=>"/products/7",
      :image=>"products/product.jpg"
    } ]
  }

  data_10 = {
    products: []
  }

  10.times do
    data_10[:products] << {
      :external_index=>"product",
      :url=>"/products/7",
      :image=>"products/product.jpg"
    }
  end

  data_100 = {
    products: []
  }

  100.times do
    data_100[:products] << {
      :external_index=>"product",
      :url=>"/products/7",
      :image=>"products/product.jpg"
    }
  end

  data_1000 = {
    products: []
  }

  1000.times do
    data_1000[:products] << {
      :external_index=>"product",
      :url=>"/products/7",
      :image=>"products/product.jpg"
    }
  end

  data_1000_escaped = {
    products: []
  }

  1000.times do
    data_1000_escaped[:products] << {
      :external_index=>"This <product> should've been \"properly\" escaped.",
      :url=>"/products/7",
      :image=>"products/product.jpg"
    }
  end

  view = Mustache.new
  view.template = template
  view.render # Call render once so the template will be compiled
  iodine_view = Iodine::Mustache.new("test_template")

  puts "Ruby Mustache rendering (and HTML escaping) results in:",
       view.render(data_1), "",
       "Notice that Iodine::Mustache rendering (and HTML escaping) results in agressive escaping:",
       iodine_view.render(data_1), "", "----"

  # return;

  Benchmark.ips do |x|
    x.report("Ruby Mustache render list of 10") do |times|
      view.render(data_10)
    end
    x.report("Iodine::Mustache render list of 10") do |times|
      iodine_view.render(data_10)
    end

    x.report("Ruby Mustache render list of 100") do |times|
      view.render(data_100)
    end
    x.report("Iodine::Mustache render list of 100") do |times|
      iodine_view.render(data_100)
    end

    x.report("Ruby Mustache render list of 1000") do |times|
      view.render(data_1000)
    end
    x.report("Iodine::Mustache render list of 1000") do |times|
      iodine_view.render(data_1000)
    end

    x.report("Ruby Mustache render list of 1000 with escaped data") do |times|
      view.render(data_1000_escaped)
    end
    x.report("Iodine::Mustache render list of 1000 with escaped data") do |times|
      iodine_view.render(data_1000_escaped)
    end
  end
end

benchmark_mustache

Instance Method Summary collapse

Constructor Details

#initialize(filename) ⇒ Object

Loads a mustache template (and any partials).

Once a template was loaded, it could be rendered using #render.



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'ext/iodine/iodine_mustache.c', line 287

static VALUE iodine_mustache_new(VALUE self, VALUE filename) {
  mustache_s **m = NULL;
  TypedData_Get_Struct(self, mustache_s *, &iodine_mustache_data_type, m);
  if (!m) {
    rb_raise(rb_eRuntimeError, "Iodine::Mustache allocation error.");
  }
  Check_Type(filename, T_STRING);
  mustache_error_en err;
  *m = mustache_load(.filename = RSTRING_PTR(filename),
                     .filename_len = RSTRING_LEN(filename), .err = &err);
  if (!*m)
    goto error;
  return self;
error:
  switch (err) {
  case MUSTACHE_OK:
    rb_raise(rb_eRuntimeError, "Iodine::Mustache template ok, unknown error.");
    break;
  case MUSTACHE_ERR_TOO_DEEP:
    rb_raise(rb_eRuntimeError, "Iodine::Mustache element nesting too deep.");
    break;
  case MUSTACHE_ERR_CLOSURE_MISMATCH:
    rb_raise(rb_eRuntimeError,
             "Iodine::Mustache template error, closure mismatch.");
    break;
  case MUSTACHE_ERR_FILE_NOT_FOUND:
    rb_raise(rb_eRuntimeError, "Iodine::Mustache template not found.");
    break;
  case MUSTACHE_ERR_FILE_TOO_BIG:
    rb_raise(rb_eRuntimeError, "Iodine::Mustache template too big.");
    break;
  case MUSTACHE_ERR_FILE_NAME_TOO_LONG:
    rb_raise(rb_eRuntimeError, "Iodine::Mustache template name too long.");
    break;
  case MUSTACHE_ERR_EMPTY_TEMPLATE:
    rb_raise(rb_eRuntimeError, "Iodine::Mustache template is empty.");
    break;
  case MUSTACHE_ERR_UNKNOWN:
    rb_raise(rb_eRuntimeError, "Iodine::Mustache unknown error.");
    break;
  case MUSTACHE_ERR_USER_ERROR:
    rb_raise(rb_eRuntimeError, "Iodine::Mustache internal error.");
    break;
  }
  return self;
}

Instance Method Details

#render(data) ⇒ Object

Renders the mustache template using the data provided in the ‘data` argument.

Returns a String.

Raises an exception on error.

NOTE:

As one might notice, no binding is provided. Instead, a ‘data` Hash is assumed. Iodine will search the Hash for any data while protecting against code execution.



351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'ext/iodine/iodine_mustache.c', line 351

static VALUE iodine_mustache_render(VALUE self, VALUE data) {
  fio_str_s str = FIO_STR_INIT;
  mustache_s **m = NULL;
  TypedData_Get_Struct(self, mustache_s *, &iodine_mustache_data_type, m);
  if (!m) {
    rb_raise(rb_eRuntimeError, "Iodine::Mustache allocation error.");
  }
  if (mustache_build(*m, .udata1 = &str, .udata2 = (void *)data))
    goto error;
  fio_str_info_s i = fio_str_info(&str);
  VALUE ret = rb_str_new(i.data, i.len);
  fio_str_free(&str);
  return ret;

error:
  fio_str_free(&str);
  rb_raise(rb_eRuntimeError, "Couldn't build template frome data.");
}