Skip to content

Commit

Permalink
Implement :escape_mode => :slash
Browse files Browse the repository at this point in the history
Similar to flori/json#405
It's a cheap way to make JSON safe to interpolate in a `<script>`
tag.
  • Loading branch information
byroot committed Jun 20, 2022
1 parent c1fb777 commit 9b3d1f8
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 0 deletions.
21 changes: 21 additions & 0 deletions ext/oj/dump.c
Expand Up @@ -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);

Expand Down Expand Up @@ -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] = "\
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions ext/oj/oj.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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"));
Expand Down
1 change: 1 addition & 0 deletions ext/oj/oj.h
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions test/test_various.rb
Expand Up @@ -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 </script>"}
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)
Expand Down

0 comments on commit 9b3d1f8

Please sign in to comment.