Permalink
Browse files

Merge pull request #30 from brianmario/as_html_safe

EscapeUtils.escape_html_as_html_safe
  • Loading branch information...
2 parents 2a881be + 33aed9e commit c8130d4437a4ce5cbeded379c25aac8ace215d80 @brianmario committed Feb 28, 2013
Showing with 97 additions and 27 deletions.
  1. +53 −14 ext/escape_utils/escape_utils.c
  2. +0 −11 ext/escape_utils/extconf.rb
  3. +10 −1 lib/escape_utils.rb
  4. +34 −1 test/html/escape_test.rb
@@ -6,10 +6,6 @@
#include <ruby.h>
#include "houdini.h"
-#if RB_CVAR_SET_ARITY == 4
-# define rb_cvar_set(a,b,c) rb_cvar_set(a,b,c,0)
-#endif
-
#ifdef HAVE_RUBY_ENCODING_H
#include <ruby/encoding.h>
static VALUE rb_eEncodingCompatibilityError;
@@ -48,22 +44,34 @@ static void check_utf8_encoding(VALUE str) {}
typedef int (*houdini_cb)(gh_buf *, const uint8_t *, size_t);
static VALUE rb_mEscapeUtils;
+static ID ID_at_html_safe, ID_new;
/**
* html_secure instance variable
*/
-static ID rb_html_secure;
static int g_html_secure = 1;
-static VALUE rb_eu_get_html_secure(VALUE self)
+static VALUE rb_eu_set_html_secure(VALUE self, VALUE val)
{
- return rb_cvar_get(self, rb_html_secure);
+ g_html_secure = RTEST(val);
+ rb_ivar_set(self, rb_intern("@html_secure"), val);
+ return val;
}
-static VALUE rb_eu_set_html_secure(VALUE self, VALUE val)
+/**
+* html_safe_string_class instance variable
+*/
+static VALUE rb_html_safe_string_class;
+
+static VALUE rb_eu_set_html_safe_string_class(VALUE self, VALUE val)
{
- g_html_secure = RTEST(val);
- rb_cvar_set(self, rb_html_secure, val);
+ Check_Type(val, T_CLASS);
+
+ if (rb_funcall(val, rb_intern("<="), 1, rb_cString) == Qnil)
+ rb_raise(rb_eArgError, "%s must be a descendent of String", rb_class2name(val));
+
+ rb_html_safe_string_class = val;
+ rb_ivar_set(self, rb_intern("@html_safe_string_class"), val);
return val;
}
@@ -94,6 +102,37 @@ rb_eu__generic(VALUE str, houdini_cb do_escape)
/**
* HTML methods
*/
+static VALUE rb_eu_escape_html_as_html_safe(VALUE self, VALUE str)
+{
+ VALUE result;
+ int secure = g_html_secure;
+ gh_buf buf = GH_BUF_INIT;
+
+ Check_Type(str, T_STRING);
+ check_utf8_encoding(str);
+
+ if (houdini_escape_html0(&buf, (const uint8_t *)RSTRING_PTR(str), RSTRING_LEN(str), secure)) {
+ result = eu_new_str(buf.ptr, buf.size);
+ gh_buf_free(&buf);
+ } else {
+#ifdef RBASIC
+ result = rb_str_dup(str);
+#else
+ result = str;
+#endif
+ }
+
+#ifdef RBASIC
+ RBASIC(result)->klass = rb_html_safe_string_class;
+#else
+ result = rb_funcall(rb_html_safe_string_class, ID_new, 1, result);
+#endif
+
+ rb_ivar_set(result, ID_at_html_safe, Qtrue);
+
+ return result;
+}
+
static VALUE rb_eu_escape_html(int argc, VALUE *argv, VALUE self)
{
VALUE str, rb_secure;
@@ -181,12 +220,14 @@ static VALUE rb_eu_unescape_uri(VALUE self, VALUE str)
void Init_escape_utils()
{
#ifdef HAVE_RUBY_ENCODING_H
- VALUE rb_cEncoding = rb_const_get(rb_cObject, rb_intern("Encoding"));
rb_eEncodingCompatibilityError = rb_const_get(rb_cEncoding, rb_intern("CompatibilityError"));
#endif
+ ID_new = rb_intern("new");
+ ID_at_html_safe = rb_intern("@html_safe");
rb_mEscapeUtils = rb_define_module("EscapeUtils");
+ rb_define_method(rb_mEscapeUtils, "escape_html_as_html_safe", rb_eu_escape_html_as_html_safe, 1);
rb_define_method(rb_mEscapeUtils, "escape_html", rb_eu_escape_html, -1);
rb_define_method(rb_mEscapeUtils, "unescape_html", rb_eu_unescape_html, 1);
rb_define_method(rb_mEscapeUtils, "escape_xml", rb_eu_escape_xml, 1);
@@ -197,9 +238,7 @@ void Init_escape_utils()
rb_define_method(rb_mEscapeUtils, "escape_uri", rb_eu_escape_uri, 1);
rb_define_method(rb_mEscapeUtils, "unescape_uri", rb_eu_unescape_uri, 1);
- rb_define_singleton_method(rb_mEscapeUtils, "html_secure", rb_eu_get_html_secure, 0);
rb_define_singleton_method(rb_mEscapeUtils, "html_secure=", rb_eu_set_html_secure, 1);
-
- rb_html_secure = rb_intern("@@html_secure");
+ rb_define_singleton_method(rb_mEscapeUtils, "html_safe_string_class=", rb_eu_set_html_safe_string_class, 1);
}
@@ -1,17 +1,6 @@
-# encoding: UTF-8
require 'mkmf'
-require 'rbconfig'
$CFLAGS << ' -Wall -funroll-loops'
$CFLAGS << ' -Wextra -O0 -ggdb3' if ENV['DEBUG']
-if try_compile(<<SRC)
-#include <ruby.h>
-int main(void) { rb_cvar_set(Qnil, Qnil, Qnil); return 0; }
-SRC
- $CFLAGS << " -DRB_CVAR_SET_ARITY=3 "
-else
- $CFLAGS << " -DRB_CVAR_SET_ARITY=4 "
-end
-
create_makefile("escape_utils/escape_utils")
View
@@ -7,7 +7,16 @@ module EscapeUtils
# turn on/off the escaping of the '/' character during HTML escaping
# Escaping '/' is recommended by the OWASP - http://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content
# This is because quotes around HTML attributes are optional in most/all modern browsers at the time of writing (10/15/2010)
- @@html_secure = true
+ def self.html_secure
+ @html_secure
+ end
+ self.html_secure = true
+
+ # Default String class to return from HTML escaping
+ def self.html_safe_string_class
+ @html_safe_string_class
+ end
+ self.html_safe_string_class = String
autoload :HtmlSafety, 'escape_utils/html_safety'
end
@@ -1,5 +1,8 @@
require File.expand_path("../../helper", __FILE__)
+class MyCustomHtmlSafeString < String
+end
+
class HtmlEscapeTest < MiniTest::Unit::TestCase
def test_escape_basic_html_with_secure
assert_equal "&lt;some_tag&#47;&gt;", EscapeUtils.escape_html("<some_tag/>")
@@ -36,6 +39,36 @@ def test_returns_original_if_not_escaped
assert_equal str.object_id, EscapeUtils.escape_html(str).object_id
end
+ def test_html_safe_escape_default_works
+ str = EscapeUtils.escape_html_as_html_safe('foobar')
+ assert_equal 'foobar', str
+ end
+
+ def test_returns_custom_string_class
+ klass_before = EscapeUtils.html_safe_string_class
+ EscapeUtils.html_safe_string_class = MyCustomHtmlSafeString
+
+ str = EscapeUtils.escape_html_as_html_safe('foobar')
+ assert_equal 'foobar', str
+ assert_equal MyCustomHtmlSafeString, str.class
+ assert_equal true, str.instance_variable_get(:@html_safe)
+ ensure
+ EscapeUtils.html_safe_string_class = klass_before
+ end
+
+ def test_html_safe_string_class_descends_string
+ assert_raises ArgumentError do
+ EscapeUtils.html_safe_string_class = Hash
+ end
+
+ begin
+ EscapeUtils.html_safe_string_class = String
+ EscapeUtils.html_safe_string_class = MyCustomHtmlSafeString
+ rescue ArgumentError => e
+ assert_nil e, "#{e.class.name} raised, expected nothing"
+ end
+ end
+
if RUBY_VERSION =~ /^1.9/
def test_utf8_or_ascii_input_only
str = "<b>Bourbon & Branch</b>"
@@ -58,4 +91,4 @@ def test_return_value_is_tagged_as_utf8
assert_equal Encoding.find('UTF-8'), EscapeUtils.escape_html(str).encoding
end
end
-end
+end

0 comments on commit c8130d4

Please sign in to comment.