Skip to content

Commit 2e28828

Browse files
committed
Add an option to escape forward slash character
Squashed commit of the following: commit 26d1810 Author: Francois Chagnon <francois.chagnon@jadedpixel.com> Date: Tue Sep 15 21:17:34 2015 +0000 add config options for escape_slash commit fa28233 Author: Francois Chagnon <francois.chagnon@jadedpixel.com> Date: Mon Feb 9 21:09:33 2015 +0000 add forward slash to escape character
1 parent ee70abf commit 2e28828

File tree

9 files changed

+132
-18
lines changed

9 files changed

+132
-18
lines changed

ext/json/ext/generator/generator.c

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ static ID i_to_s, i_to_json, i_new, i_indent, i_space, i_space_before,
2222
i_object_nl, i_array_nl, i_max_nesting, i_allow_nan, i_ascii_only,
2323
i_pack, i_unpack, i_create_id, i_extend, i_key_p,
2424
i_aref, i_send, i_respond_to_p, i_match, i_keys, i_depth,
25-
i_buffer_initial_length, i_dup;
25+
i_buffer_initial_length, i_dup, i_escape_slash;
2626

2727
/*
2828
* Copyright 2001-2004 Unicode, Inc.
@@ -130,7 +130,7 @@ static void unicode_escape_to_buffer(FBuffer *buffer, char buf[6], UTF16
130130

131131
/* Converts string to a JSON string in FBuffer buffer, where all but the ASCII
132132
* and control characters are JSON escaped. */
133-
static void convert_UTF8_to_JSON_ASCII(FBuffer *buffer, VALUE string)
133+
static void convert_UTF8_to_JSON_ASCII(FBuffer *buffer, VALUE string, char escape_slash)
134134
{
135135
const UTF8 *source = (UTF8 *) RSTRING_PTR(string);
136136
const UTF8 *sourceEnd = source + RSTRING_LEN(string);
@@ -180,6 +180,11 @@ static void convert_UTF8_to_JSON_ASCII(FBuffer *buffer, VALUE string)
180180
case '"':
181181
fbuffer_append(buffer, "\\\"", 2);
182182
break;
183+
case '/':
184+
if(escape_slash) {
185+
fbuffer_append(buffer, "\\/", 2);
186+
break;
187+
}
183188
default:
184189
fbuffer_append_char(buffer, (char)ch);
185190
break;
@@ -229,7 +234,7 @@ static void convert_UTF8_to_JSON_ASCII(FBuffer *buffer, VALUE string)
229234
* characters required by the JSON standard are JSON escaped. The remaining
230235
* characters (should be UTF8) are just passed through and appended to the
231236
* result. */
232-
static void convert_UTF8_to_JSON(FBuffer *buffer, VALUE string)
237+
static void convert_UTF8_to_JSON(FBuffer *buffer, VALUE string, char escape_slash)
233238
{
234239
const char *ptr = RSTRING_PTR(string), *p;
235240
unsigned long len = RSTRING_LEN(string), start = 0, end = 0;
@@ -280,6 +285,12 @@ static void convert_UTF8_to_JSON(FBuffer *buffer, VALUE string)
280285
escape = "\\\"";
281286
escape_len = 2;
282287
break;
288+
case '/':
289+
if(escape_slash) {
290+
escape = "\\/";
291+
escape_len = 2;
292+
break;
293+
}
283294
default:
284295
{
285296
unsigned short clen = 1;
@@ -716,6 +727,8 @@ static VALUE cState_configure(VALUE self, VALUE opts)
716727
state->allow_nan = RTEST(tmp);
717728
tmp = rb_hash_aref(opts, ID2SYM(i_ascii_only));
718729
state->ascii_only = RTEST(tmp);
730+
tmp = rb_hash_aref(opts, ID2SYM(i_escape_slash));
731+
state->escape_slash = RTEST(tmp);
719732
return self;
720733
}
721734

@@ -750,6 +763,7 @@ static VALUE cState_to_h(VALUE self)
750763
rb_hash_aset(result, ID2SYM(i_allow_nan), state->allow_nan ? Qtrue : Qfalse);
751764
rb_hash_aset(result, ID2SYM(i_ascii_only), state->ascii_only ? Qtrue : Qfalse);
752765
rb_hash_aset(result, ID2SYM(i_max_nesting), LONG2FIX(state->max_nesting));
766+
rb_hash_aset(result, ID2SYM(i_escape_slash), state->escape_slash ? Qtrue : Qfalse);
753767
rb_hash_aset(result, ID2SYM(i_depth), LONG2FIX(state->depth));
754768
rb_hash_aset(result, ID2SYM(i_buffer_initial_length), LONG2FIX(state->buffer_initial_length));
755769
return result;
@@ -934,9 +948,9 @@ static void generate_json_string(FBuffer *buffer, VALUE Vstate, JSON_Generator_S
934948
}
935949
#endif
936950
if (state->ascii_only) {
937-
convert_UTF8_to_JSON_ASCII(buffer, obj);
951+
convert_UTF8_to_JSON_ASCII(buffer, obj, state->escape_slash);
938952
} else {
939-
convert_UTF8_to_JSON(buffer, obj);
953+
convert_UTF8_to_JSON(buffer, obj, state->escape_slash);
940954
}
941955
fbuffer_append_char(buffer, '"');
942956
}
@@ -1377,6 +1391,31 @@ static VALUE cState_max_nesting_set(VALUE self, VALUE depth)
13771391
return state->max_nesting = FIX2LONG(depth);
13781392
}
13791393

1394+
/*
1395+
* call-seq: escape_slash
1396+
*
1397+
* If this boolean is true, the forward slashes will be escaped in
1398+
* the json output.
1399+
*/
1400+
static VALUE cState_escape_slash(VALUE self)
1401+
{
1402+
GET_STATE(self);
1403+
return state->escape_slash ? Qtrue : Qfalse;
1404+
}
1405+
1406+
/*
1407+
* call-seq: escape_slash=(depth)
1408+
*
1409+
* This sets whether or not the forward slashes will be escaped in
1410+
* the json output.
1411+
*/
1412+
static VALUE cState_escape_slash_set(VALUE self, VALUE enable)
1413+
{
1414+
GET_STATE(self);
1415+
state->escape_slash = RTEST(enable);
1416+
return Qnil;
1417+
}
1418+
13801419
/*
13811420
* call-seq: allow_nan?
13821421
*
@@ -1489,6 +1528,9 @@ void Init_generator(void)
14891528
rb_define_method(cState, "array_nl=", cState_array_nl_set, 1);
14901529
rb_define_method(cState, "max_nesting", cState_max_nesting, 0);
14911530
rb_define_method(cState, "max_nesting=", cState_max_nesting_set, 1);
1531+
rb_define_method(cState, "escape_slash", cState_escape_slash, 0);
1532+
rb_define_method(cState, "escape_slash?", cState_escape_slash, 0);
1533+
rb_define_method(cState, "escape_slash=", cState_escape_slash_set, 1);
14921534
rb_define_method(cState, "check_circular?", cState_check_circular_p, 0);
14931535
rb_define_method(cState, "allow_nan?", cState_allow_nan_p, 0);
14941536
rb_define_method(cState, "ascii_only?", cState_ascii_only_p, 0);
@@ -1545,6 +1587,7 @@ void Init_generator(void)
15451587
i_object_nl = rb_intern("object_nl");
15461588
i_array_nl = rb_intern("array_nl");
15471589
i_max_nesting = rb_intern("max_nesting");
1590+
i_escape_slash = rb_intern("escape_slash");
15481591
i_allow_nan = rb_intern("allow_nan");
15491592
i_ascii_only = rb_intern("ascii_only");
15501593
i_depth = rb_intern("depth");

ext/json/ext/generator/generator.h

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

5656
/* ruby api and some helpers */
@@ -72,6 +72,7 @@ typedef struct JSON_Generator_StateStruct {
7272
long max_nesting;
7373
char allow_nan;
7474
char ascii_only;
75+
char escape_slash;
7576
long depth;
7677
long buffer_initial_length;
7778
} JSON_Generator_State;
@@ -150,6 +151,8 @@ static VALUE cState_allow_nan_p(VALUE self);
150151
static VALUE cState_ascii_only_p(VALUE self);
151152
static VALUE cState_depth(VALUE self);
152153
static VALUE cState_depth_set(VALUE self, VALUE depth);
154+
static VALUE cState_escape_slash(VALUE self);
155+
static VALUE cState_escape_slash_set(VALUE self, VALUE depth);
153156
static FBuffer *cState_prepare_buffer(VALUE self);
154157
#ifndef ZALLOC
155158
#define ZALLOC(type) ((type *)ruby_zalloc(sizeof(type)))

java/src/json/ext/Generator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ public RuntimeInfo getInfo() {
158158

159159
public StringEncoder getStringEncoder() {
160160
if (stringEncoder == null) {
161-
stringEncoder = new StringEncoder(context, getState().asciiOnly());
161+
stringEncoder = new StringEncoder(context, getState().asciiOnly(), getState().escapeSlash());
162162
}
163163
return stringEncoder;
164164
}

java/src/json/ext/GeneratorState.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ public class GeneratorState extends RubyObject {
8282
*/
8383
private boolean quirksMode = DEFAULT_QUIRKS_MODE;
8484
static final boolean DEFAULT_QUIRKS_MODE = false;
85+
/**
86+
* If set to <code>true</code> the forward slash will be escaped in
87+
* json output.
88+
*/
89+
private boolean escapeSlash = DEFAULT_ESCAPE_SLASH;
90+
static final boolean DEFAULT_ESCAPE_SLASH = false;
8591
/**
8692
* The initial buffer length of this state. (This isn't really used on all
8793
* non-C implementations.)
@@ -171,6 +177,9 @@ static GeneratorState fromState(ThreadContext context, RuntimeInfo info,
171177
* <code>-Infinity</code> should be generated, otherwise an exception is
172178
* thrown if these values are encountered.
173179
* This options defaults to <code>false</code>.
180+
* <dt><code>:escape_slash</code>
181+
* <dd>set to <code>true</code> if the forward slashes should be escaped
182+
* in the json output (default: <code>false</code>)
174183
*/
175184
@JRubyMethod(optional=1, visibility=Visibility.PRIVATE)
176185
public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
@@ -194,6 +203,7 @@ public IRubyObject initialize_copy(ThreadContext context, IRubyObject vOrig) {
194203
this.allowNaN = orig.allowNaN;
195204
this.asciiOnly = orig.asciiOnly;
196205
this.quirksMode = orig.quirksMode;
206+
this.escapeSlash = orig.escapeSlash;
197207
this.bufferInitialLength = orig.bufferInitialLength;
198208
this.depth = orig.depth;
199209
return this;
@@ -346,6 +356,24 @@ public IRubyObject max_nesting_set(IRubyObject max_nesting) {
346356
return max_nesting;
347357
}
348358

359+
/**
360+
* Returns true if forward slashes are escaped in the json output.
361+
*/
362+
public boolean escapeSlash() {
363+
return escapeSlash;
364+
}
365+
366+
@JRubyMethod(name="escape_slash")
367+
public RubyBoolean escape_slash_get(ThreadContext context) {
368+
return context.getRuntime().newBoolean(escapeSlash);
369+
}
370+
371+
@JRubyMethod(name="escape_slash=")
372+
public IRubyObject escape_slash_set(IRubyObject escape_slash) {
373+
escapeSlash = escape_slash.isTrue();
374+
return escape_slash.getRuntime().newBoolean(escapeSlash);
375+
}
376+
349377
public boolean allowNaN() {
350378
return allowNaN;
351379
}
@@ -430,6 +458,7 @@ public IRubyObject configure(ThreadContext context, IRubyObject vOpts) {
430458
maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING);
431459
allowNaN = opts.getBool("allow_nan", DEFAULT_ALLOW_NAN);
432460
asciiOnly = opts.getBool("ascii_only", DEFAULT_ASCII_ONLY);
461+
escapeSlash = opts.getBool("escape_slash", DEFAULT_ESCAPE_SLASH);
433462
bufferInitialLength = opts.getInt("buffer_initial_length", DEFAULT_BUFFER_INITIAL_LENGTH);
434463

435464
depth = opts.getInt("depth", 0);
@@ -457,6 +486,7 @@ public RubyHash to_h(ThreadContext context) {
457486
result.op_aset(context, runtime.newSymbol("allow_nan"), allow_nan_p(context));
458487
result.op_aset(context, runtime.newSymbol("ascii_only"), ascii_only_p(context));
459488
result.op_aset(context, runtime.newSymbol("max_nesting"), max_nesting_get(context));
489+
result.op_aset(context, runtime.newSymbol("escape_slash"), escape_slash_get(context));
460490
result.op_aset(context, runtime.newSymbol("depth"), depth_get(context));
461491
result.op_aset(context, runtime.newSymbol("buffer_initial_length"), buffer_initial_length_get(context));
462492
for (String name: getInstanceVariableNameList()) {

java/src/json/ext/StringEncoder.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* and throws a GeneratorError if any problem is found.
1616
*/
1717
final class StringEncoder extends ByteListTranscoder {
18-
private final boolean asciiOnly;
18+
private final boolean asciiOnly, escapeSlash;
1919

2020
// Escaped characters will reuse this array, to avoid new allocations
2121
// or appending them byte-by-byte
@@ -37,9 +37,10 @@ final class StringEncoder extends ByteListTranscoder {
3737
new byte[] {'0', '1', '2', '3', '4', '5', '6', '7',
3838
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
3939

40-
StringEncoder(ThreadContext context, boolean asciiOnly) {
40+
StringEncoder(ThreadContext context, boolean asciiOnly, boolean escapeSlash) {
4141
super(context);
4242
this.asciiOnly = asciiOnly;
43+
this.escapeSlash = escapeSlash;
4344
}
4445

4546
void encode(ByteList src, ByteList out) {
@@ -73,6 +74,11 @@ private void handleChar(int c) {
7374
case '\b':
7475
escapeChar('b');
7576
break;
77+
case '/':
78+
if(escapeSlash) {
79+
escapeChar((char)c);
80+
break;
81+
}
7682
default:
7783
if (c >= 0x20 && c <= 0x7f ||
7884
(c >= 0x80 && !asciiOnly)) {

lib/json/common.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ def parse!(source, opts = {})
198198
# * *space_before*: a string that is put before a <tt>:</tt> pair delimiter (default: <tt>''</tt>),
199199
# * *object_nl*: a string that is put at the end of a JSON object (default: <tt>''</tt>),
200200
# * *array_nl*: a string that is put at the end of a JSON array (default: <tt>''</tt>),
201+
# * *escape_slash*: true if forward slash (/) should be escaped (default: <tt>false</tt>)
201202
# * *allow_nan*: true if NaN, Infinity, and -Infinity should be
202203
# generated, otherwise an exception is thrown if these values are
203204
# encountered. This options defaults to false.
@@ -362,11 +363,13 @@ class << self
362363
# :max_nesting: false
363364
# :allow_nan: true
364365
# :allow_blank: true
366+
# :escape_slash: true
365367
attr_accessor :dump_default_options
366368
end
367369
self.dump_default_options = {
368370
:max_nesting => false,
369371
:allow_nan => true,
372+
:escape_slash => true,
370373
}
371374

372375
# Dumps _obj_ as a JSON string, i.e. calls generate on the object and returns
@@ -418,7 +421,7 @@ module ::Kernel
418421
# one line.
419422
def j(*objs)
420423
objs.each do |obj|
421-
puts JSON::generate(obj, :allow_nan => true, :max_nesting => false)
424+
puts JSON::generate(obj, :allow_nan => true, :max_nesting => false, :escape_slash => true)
422425
end
423426
nil
424427
end
@@ -427,7 +430,7 @@ def j(*objs)
427430
# indentation and over many lines.
428431
def jj(*objs)
429432
objs.each do |obj|
430-
puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false)
433+
puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false, :escape_slash => true)
431434
end
432435
nil
433436
end

0 commit comments

Comments
 (0)