Skip to content

Commit ff4fbcc

Browse files
author
Francois Chagnon
committed
add config options for escape_slash
1 parent fa28233 commit ff4fbcc

File tree

6 files changed

+93
-28
lines changed

6 files changed

+93
-28
lines changed

ext/json/ext/generator/generator.c

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ static ID i_to_s, i_to_json, i_new, i_indent, i_space, i_space_before,
1616
i_object_nl, i_array_nl, i_max_nesting, i_allow_nan, i_ascii_only,
1717
i_quirks_mode, i_pack, i_unpack, i_create_id, i_extend, i_key_p,
1818
i_aref, i_send, i_respond_to_p, i_match, i_keys, i_depth,
19-
i_buffer_initial_length, i_dup;
19+
i_buffer_initial_length, i_dup, i_escape_slash;
2020

2121
/*
2222
* Copyright 2001-2004 Unicode, Inc.
@@ -124,7 +124,7 @@ static void unicode_escape_to_buffer(FBuffer *buffer, char buf[6], UTF16
124124

125125
/* Converts string to a JSON string in FBuffer buffer, where all but the ASCII
126126
* and control characters are JSON escaped. */
127-
static void convert_UTF8_to_JSON_ASCII(FBuffer *buffer, VALUE string)
127+
static void convert_UTF8_to_JSON_ASCII(FBuffer *buffer, VALUE string, char escape_slash)
128128
{
129129
const UTF8 *source = (UTF8 *) RSTRING_PTR(string);
130130
const UTF8 *sourceEnd = source + RSTRING_LEN(string);
@@ -171,12 +171,14 @@ static void convert_UTF8_to_JSON_ASCII(FBuffer *buffer, VALUE string)
171171
case '\\':
172172
fbuffer_append(buffer, "\\\\", 2);
173173
break;
174-
case '/':
175-
fbuffer_append(buffer, "\\/", 2);
176-
break;
177174
case '"':
178175
fbuffer_append(buffer, "\\\"", 2);
179176
break;
177+
case '/':
178+
if(escape_slash) {
179+
fbuffer_append(buffer, "\\/", 2);
180+
break;
181+
}
180182
default:
181183
fbuffer_append_char(buffer, (char)ch);
182184
break;
@@ -225,7 +227,7 @@ static void convert_UTF8_to_JSON_ASCII(FBuffer *buffer, VALUE string)
225227
* characters required by the JSON standard are JSON escaped. The remaining
226228
* characters (should be UTF8) are just passed through and appended to the
227229
* result. */
228-
static void convert_UTF8_to_JSON(FBuffer *buffer, VALUE string)
230+
static void convert_UTF8_to_JSON(FBuffer *buffer, VALUE string, char escape_slash)
229231
{
230232
const char *ptr = RSTRING_PTR(string), *p;
231233
unsigned long len = RSTRING_LEN(string), start = 0, end = 0;
@@ -271,14 +273,16 @@ static void convert_UTF8_to_JSON(FBuffer *buffer, VALUE string)
271273
escape = "\\\\";
272274
escape_len = 2;
273275
break;
274-
case '/':
275-
escape = "\\/";
276-
escape_len = 2;
277-
break;
278276
case '"':
279277
escape = "\\\"";
280278
escape_len = 2;
281279
break;
280+
case '/':
281+
if(escape_slash) {
282+
escape = "\\/";
283+
escape_len = 2;
284+
break;
285+
}
282286
default:
283287
{
284288
unsigned short clen = trailingBytesForUTF8[c] + 1;
@@ -631,6 +635,8 @@ static VALUE cState_configure(VALUE self, VALUE opts)
631635
state->ascii_only = RTEST(tmp);
632636
tmp = rb_hash_aref(opts, ID2SYM(i_quirks_mode));
633637
state->quirks_mode = RTEST(tmp);
638+
tmp = rb_hash_aref(opts, ID2SYM(i_escape_slash));
639+
state->escape_slash = RTEST(tmp);
634640
return self;
635641
}
636642

@@ -666,6 +672,7 @@ static VALUE cState_to_h(VALUE self)
666672
rb_hash_aset(result, ID2SYM(i_ascii_only), state->ascii_only ? Qtrue : Qfalse);
667673
rb_hash_aset(result, ID2SYM(i_quirks_mode), state->quirks_mode ? Qtrue : Qfalse);
668674
rb_hash_aset(result, ID2SYM(i_max_nesting), LONG2FIX(state->max_nesting));
675+
rb_hash_aset(result, ID2SYM(i_escape_slash), state->escape_slash ? Qtrue : Qfalse);
669676
rb_hash_aset(result, ID2SYM(i_depth), LONG2FIX(state->depth));
670677
rb_hash_aset(result, ID2SYM(i_buffer_initial_length), LONG2FIX(state->buffer_initial_length));
671678
return result;
@@ -799,9 +806,9 @@ static void generate_json_string(FBuffer *buffer, VALUE Vstate, JSON_Generator_S
799806
obj = rb_funcall(obj, i_encode, 1, CEncoding_UTF_8);
800807
#endif
801808
if (state->ascii_only) {
802-
convert_UTF8_to_JSON_ASCII(buffer, obj);
809+
convert_UTF8_to_JSON_ASCII(buffer, obj, state->escape_slash);
803810
} else {
804-
convert_UTF8_to_JSON(buffer, obj);
811+
convert_UTF8_to_JSON(buffer, obj, state->escape_slash);
805812
}
806813
fbuffer_append_char(buffer, '"');
807814
}
@@ -1252,6 +1259,31 @@ static VALUE cState_max_nesting_set(VALUE self, VALUE depth)
12521259
return state->max_nesting = FIX2LONG(depth);
12531260
}
12541261

1262+
/*
1263+
* call-seq: escape_slash
1264+
*
1265+
* If this boolean is true, the forward slashes will be escaped in
1266+
* the json output.
1267+
*/
1268+
static VALUE cState_escape_slash(VALUE self)
1269+
{
1270+
GET_STATE(self);
1271+
return state->escape_slash ? Qtrue : Qfalse;
1272+
}
1273+
1274+
/*
1275+
* call-seq: escape_slash=(depth)
1276+
*
1277+
* This sets whether or not the forward slashes will be escaped in
1278+
* the json output.
1279+
*/
1280+
static VALUE cState_escape_slash_set(VALUE self, VALUE enable)
1281+
{
1282+
GET_STATE(self);
1283+
state->escape_slash = RTEST(enable);
1284+
return Qnil;
1285+
}
1286+
12551287
/*
12561288
* call-seq: allow_nan?
12571289
*
@@ -1384,6 +1416,9 @@ void Init_generator(void)
13841416
rb_define_method(cState, "array_nl=", cState_array_nl_set, 1);
13851417
rb_define_method(cState, "max_nesting", cState_max_nesting, 0);
13861418
rb_define_method(cState, "max_nesting=", cState_max_nesting_set, 1);
1419+
rb_define_method(cState, "escape_slash", cState_escape_slash, 0);
1420+
rb_define_method(cState, "escape_slash?", cState_escape_slash, 0);
1421+
rb_define_method(cState, "escape_slash=", cState_escape_slash_set, 1);
13871422
rb_define_method(cState, "check_circular?", cState_check_circular_p, 0);
13881423
rb_define_method(cState, "allow_nan?", cState_allow_nan_p, 0);
13891424
rb_define_method(cState, "ascii_only?", cState_ascii_only_p, 0);
@@ -1439,6 +1474,7 @@ void Init_generator(void)
14391474
i_object_nl = rb_intern("object_nl");
14401475
i_array_nl = rb_intern("array_nl");
14411476
i_max_nesting = rb_intern("max_nesting");
1477+
i_escape_slash = rb_intern("escape_slash");
14421478
i_allow_nan = rb_intern("allow_nan");
14431479
i_ascii_only = rb_intern("ascii_only");
14441480
i_quirks_mode = rb_intern("quirks_mode");

ext/json/ext/generator/generator.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ static const UTF32 halfMask = 0x3FFUL;
5050
static unsigned char isLegalUTF8(const UTF8 *source, unsigned long length);
5151
static void unicode_escape(char *buf, UTF16 character);
5252
static void unicode_escape_to_buffer(FBuffer *buffer, char buf[6], UTF16 character);
53-
static void convert_UTF8_to_JSON_ASCII(FBuffer *buffer, VALUE string);
54-
static void convert_UTF8_to_JSON(FBuffer *buffer, VALUE string);
53+
static void convert_UTF8_to_JSON_ASCII(FBuffer *buffer, VALUE string, char escape_slash);
54+
static void convert_UTF8_to_JSON(FBuffer *buffer, VALUE string, char escape_slash);
5555
static char *fstrndup(const char *ptr, unsigned long len);
5656

5757
/* ruby api and some helpers */
@@ -74,6 +74,7 @@ typedef struct JSON_Generator_StateStruct {
7474
char allow_nan;
7575
char ascii_only;
7676
char quirks_mode;
77+
char escape_slash;
7778
long depth;
7879
long buffer_initial_length;
7980
} JSON_Generator_State;
@@ -145,6 +146,8 @@ static VALUE cState_allow_nan_p(VALUE self);
145146
static VALUE cState_ascii_only_p(VALUE self);
146147
static VALUE cState_depth(VALUE self);
147148
static VALUE cState_depth_set(VALUE self, VALUE depth);
149+
static VALUE cState_escape_slash(VALUE self);
150+
static VALUE cState_escape_slash_set(VALUE self, VALUE depth);
148151
static FBuffer *cState_prepare_buffer(VALUE self);
149152
#ifndef ZALLOC
150153
#define ZALLOC(type) ((type *)ruby_zalloc(sizeof(type)))

lib/json/common.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,12 +358,14 @@ class << self
358358
# :max_nesting: false
359359
# :allow_nan: true
360360
# :quirks_mode: true
361+
# :escape_slash: true
361362
attr_accessor :dump_default_options
362363
end
363364
self.dump_default_options = {
364365
:max_nesting => false,
365366
:allow_nan => true,
366367
:quirks_mode => true,
368+
:escape_slash => true,
367369
}
368370

369371
# Dumps _obj_ as a JSON string, i.e. calls generate on the object and returns
@@ -443,7 +445,7 @@ module ::Kernel
443445
# one line.
444446
def j(*objs)
445447
objs.each do |obj|
446-
puts JSON::generate(obj, :allow_nan => true, :max_nesting => false)
448+
puts JSON::generate(obj, :allow_nan => true, :max_nesting => false, :escape_slash => true)
447449
end
448450
nil
449451
end
@@ -452,7 +454,7 @@ def j(*objs)
452454
# indentation and over many lines.
453455
def jj(*objs)
454456
objs.each do |obj|
455-
puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false)
457+
puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false, :escape_slash => true)
456458
end
457459
nil
458460
end

lib/json/pure/generator.rb

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,20 @@ module JSON
4040
# Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
4141
# UTF16 big endian characters as \u????, and return it.
4242
if defined?(::Encoding)
43-
def utf8_to_json(string) # :nodoc:
43+
def utf8_to_json(string, escape_slash = true) # :nodoc:
4444
string = string.dup
4545
string.force_encoding(::Encoding::ASCII_8BIT)
46-
string.gsub!(/[\/"\\\x0-\x1f]/) { MAP[$&] }
46+
string.gsub!(/["\\\x0-\x1f]/) { MAP[$&] }
47+
string.gsub!('/') { MAP[$&] } if escape_slash
4748
string.force_encoding(::Encoding::UTF_8)
4849
string
4950
end
5051

51-
def utf8_to_json_ascii(string) # :nodoc:
52+
def utf8_to_json_ascii(string, escape_slash = true) # :nodoc:
5253
string = string.dup
5354
string.force_encoding(::Encoding::ASCII_8BIT)
54-
string.gsub!(/[\/"\\\x0-\x1f]/n) { MAP[$&] }
55+
string.gsub!(/["\\\x0-\x1f]/n) { MAP[$&] }
56+
string.gsub!('/') { MAP[$&] } if escape_slash
5557
string.gsub!(/(
5658
(?:
5759
[\xc2-\xdf][\x80-\xbf] |
@@ -79,12 +81,15 @@ def valid_utf8?(string)
7981
end
8082
module_function :valid_utf8?
8183
else
82-
def utf8_to_json(string) # :nodoc:
83-
string.gsub(/[\/"\\\x0-\x1f]/n) { MAP[$&] }
84+
def utf8_to_json(string, escape_slash = true) # :nodoc:
85+
string = string.gsub(/["\\\x0-\x1f]/n) { MAP[$&] }
86+
string.gsub!('/') { MAP[$&] } if escape_slash
87+
string
8488
end
8589

86-
def utf8_to_json_ascii(string) # :nodoc:
90+
def utf8_to_json_ascii(string, escape_slash = true) # :nodoc:
8791
string = string.gsub(/[\/"\\\x0-\x1f]/) { MAP[$&] }
92+
string.gsub!('/') { MAP[$&] } if escape_slash
8893
string.gsub!(/(
8994
(?:
9095
[\xc2-\xdf][\x80-\xbf] |
@@ -167,6 +172,7 @@ def initialize(opts = {})
167172
@ascii_only = false
168173
@quirks_mode = false
169174
@buffer_initial_length = 1024
175+
@escape_slash = false
170176
configure opts
171177
end
172178

@@ -198,6 +204,10 @@ def initialize(opts = {})
198204
# :stopdoc:
199205
attr_reader :buffer_initial_length
200206

207+
# If this attribute is set to true, forward slashes will be escaped in
208+
# all json strings.
209+
attr_accessor :escape_slash
210+
201211
def buffer_initial_length=(length)
202212
if length > 0
203213
@buffer_initial_length = length
@@ -239,6 +249,11 @@ def quirks_mode?
239249
@quirks_mode
240250
end
241251

252+
# Returns true, if forward slashes are escaped. Otherwise returns false.
253+
def escape_slash?
254+
@escape_slash
255+
end
256+
242257
# Configure this State instance with the Hash _opts_, and return
243258
# itself.
244259
def configure(opts)
@@ -262,6 +277,7 @@ def configure(opts)
262277
@depth = opts[:depth] || 0
263278
@quirks_mode = opts[:quirks_mode] if opts.key?(:quirks_mode)
264279
@buffer_initial_length ||= opts[:buffer_initial_length]
280+
@escape_slash = !!opts[:escape_slash] if opts.key?(:escape_slash)
265281

266282
if !opts.key?(:max_nesting) # defaults to 100
267283
@max_nesting = 100
@@ -450,9 +466,9 @@ def to_json(state = nil, *args)
450466
string = encode(::Encoding::UTF_8)
451467
end
452468
if state.ascii_only?
453-
'"' << JSON.utf8_to_json_ascii(string) << '"'
469+
'"' << JSON.utf8_to_json_ascii(string, state.escape_slash) << '"'
454470
else
455-
'"' << JSON.utf8_to_json(string) << '"'
471+
'"' << JSON.utf8_to_json(string, state.escape_slash) << '"'
456472
end
457473
end
458474
else
@@ -462,9 +478,9 @@ def to_json(state = nil, *args)
462478
def to_json(state = nil, *args)
463479
state = State.from_state(state)
464480
if state.ascii_only?
465-
'"' << JSON.utf8_to_json_ascii(self) << '"'
481+
'"' << JSON.utf8_to_json_ascii(self, state.escape_slash) << '"'
466482
else
467-
'"' << JSON.utf8_to_json(self) << '"'
483+
'"' << JSON.utf8_to_json(self, state.escape_slash) << '"'
468484
end
469485
end
470486
end

tests/test_json.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,10 +410,15 @@ def test_backslash
410410
assert_equal data, JSON.parse(json)
411411
#
412412
data = [ '/' ]
413-
json = '["\/"]'
413+
json = '["/"]'
414414
assert_equal json, JSON.generate(data)
415415
assert_equal data, JSON.parse(json)
416416
#
417+
data = [ '/' ]
418+
json = '["\/"]'
419+
assert_equal json, JSON.generate(data, :escape_slash => true)
420+
assert_equal data, JSON.parse(json)
421+
#
417422
json = '["\""]'
418423
data = JSON.parse(json)
419424
assert_equal ['"'], data

tests/test_json_generate.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ def test_pretty_state
142142
:buffer_initial_length => 1024,
143143
:quirks_mode => false,
144144
:depth => 0,
145+
:escape_slash => false,
145146
:indent => " ",
146147
:max_nesting => 100,
147148
:object_nl => "\n",
@@ -159,6 +160,7 @@ def test_safe_state
159160
:buffer_initial_length => 1024,
160161
:quirks_mode => false,
161162
:depth => 0,
163+
:escape_slash => false,
162164
:indent => "",
163165
:max_nesting => 100,
164166
:object_nl => "",
@@ -176,6 +178,7 @@ def test_fast_state
176178
:buffer_initial_length => 1024,
177179
:quirks_mode => false,
178180
:depth => 0,
181+
:escape_slash => false,
179182
:indent => "",
180183
:max_nesting => 0,
181184
:object_nl => "",

0 commit comments

Comments
 (0)