diff --git a/ext/oj/dump.c b/ext/oj/dump.c index 83f4add5..ed7340cd 100644 --- a/ext/oj/dump.c +++ b/ext/oj/dump.c @@ -32,6 +32,7 @@ static const char nan_val[] = NAN_VAL; typedef unsigned long ulong; static size_t hibit_friendly_size(const uint8_t *str, size_t len); +static size_t slash_friendly_size(const uint8_t *str, size_t len); static size_t xss_friendly_size(const uint8_t *str, size_t len); static size_t ascii_friendly_size(const uint8_t *str, size_t len); @@ -59,6 +60,17 @@ static char hibit_friendly_chars[256] = "\ 11111111111111111111111111111111\ 11111111111111111111111111111111"; +// JSON standard but escape forward slashes `/` +static char slash_friendly_chars[256] = "\ +66666666222622666666666666666666\ +11211111111111121111111111111111\ +11111111111111111111111111112111\ +11111111111111111111111111111111\ +11111111111111111111111111111111\ +11111111111111111111111111111111\ +11111111111111111111111111111111\ +11111111111111111111111111111111"; + // High bit set characters are always encoded as unicode. Worse case is 3 // bytes per character in the output. That makes this conservative. static char ascii_friendly_chars[256] = "\ @@ -143,6 +155,10 @@ inline static size_t hibit_friendly_size(const uint8_t *str, size_t len) { return calculate_string_size(str, len, hibit_friendly_chars); } +inline static size_t slash_friendly_size(const uint8_t *str, size_t len) { + return calculate_string_size(str, len, slash_friendly_chars); +} + inline static size_t ascii_friendly_size(const uint8_t *str, size_t len) { return calculate_string_size(str, len, ascii_friendly_chars); } @@ -756,6 +772,11 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou cmap = ascii_friendly_chars; size = ascii_friendly_size((uint8_t *)str, cnt); break; + case SlashEsc: + has_hi = true; + cmap = slash_friendly_chars; + size = slash_friendly_size((uint8_t *)str, cnt); + break; case XSSEsc: cmap = xss_friendly_chars; size = xss_friendly_size((uint8_t *)str, cnt); diff --git a/ext/oj/oj.c b/ext/oj/oj.c index 0df10104..b9634d27 100644 --- a/ext/oj/oj.c +++ b/ext/oj/oj.c @@ -137,6 +137,7 @@ static VALUE rails_sym; static VALUE raise_sym; static VALUE ruby_sym; static VALUE sec_prec_sym; +static VALUE slash_sym; static VALUE strict_sym; static VALUE symbol_keys_sym; static VALUE time_format_sym; @@ -405,6 +406,7 @@ static VALUE get_def_opts(VALUE self) { switch (oj_default_options.escape_mode) { case NLEsc: rb_hash_aset(opts, escape_mode_sym, newline_sym); break; case JSONEsc: rb_hash_aset(opts, escape_mode_sym, json_sym); break; + case SlashEsc: rb_hash_aset(opts, escape_mode_sym, slash_sym); break; case XSSEsc: rb_hash_aset(opts, escape_mode_sym, xss_safe_sym); break; case ASCIIEsc: rb_hash_aset(opts, escape_mode_sym, ascii_sym); break; case JXEsc: rb_hash_aset(opts, escape_mode_sym, unicode_xss_sym); break; @@ -734,6 +736,8 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) { copts->escape_mode = NLEsc; } else if (json_sym == v) { copts->escape_mode = JSONEsc; + } else if (slash_sym == v) { + copts->escape_mode = SlashEsc; } else if (xss_safe_sym == v) { copts->escape_mode = XSSEsc; } else if (ascii_sym == v) { @@ -1987,6 +1991,8 @@ void Init_oj(void) { rb_gc_register_address(&ruby_sym); sec_prec_sym = ID2SYM(rb_intern("second_precision")); rb_gc_register_address(&sec_prec_sym); + slash_sym = ID2SYM(rb_intern("slash")); + rb_gc_register_address(&slash_sym); strict_sym = ID2SYM(rb_intern("strict")); rb_gc_register_address(&strict_sym); symbol_keys_sym = ID2SYM(rb_intern("symbol_keys")); diff --git a/ext/oj/oj.h b/ext/oj/oj.h index 3895e828..9eb8b97f 100644 --- a/ext/oj/oj.h +++ b/ext/oj/oj.h @@ -66,6 +66,7 @@ typedef enum { UnixTime = 'u', UnixZTime = 'z', XmlTime = 'x', RubyTime = 'r' } typedef enum { NLEsc = 'n', JSONEsc = 'j', + SlashEsc = 's', XSSEsc = 'x', ASCIIEsc = 'a', JXEsc = 'g', // json gem diff --git a/test/test_various.rb b/test/test_various.rb index d1a92e75..d6bea7b6 100755 --- a/test/test_various.rb +++ b/test/test_various.rb @@ -345,6 +345,12 @@ def test_escapes_entities_by_default_when_configured_to_do_so out = Oj.dump hash assert_equal(%{{"key":"I \\u003c3 this"}}, out) end + def test_escapes_slashes_by_default_when_configured_to_do_so + hash = {'key' => "I <3 this "} + Oj.default_options = {:escape_mode => :slash} + out = Oj.dump hash + assert_equal(%{{"key":"I <3 this <\\/script>"}}, out) + end def test_escapes_entities_when_asked_to hash = {'key' => "I <3 this"} out = Oj.dump(hash, :escape_mode => :xss_safe)