Skip to content

Commit 26d1810

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

File tree

9 files changed

+132
-32
lines changed

9 files changed

+132
-32
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)))

java/src/json/ext/Generator.java

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

137137
public StringEncoder getStringEncoder() {
138138
if (stringEncoder == null) {
139-
stringEncoder = new StringEncoder(context, getState().asciiOnly());
139+
stringEncoder = new StringEncoder(context, getState().asciiOnly(), getState().escapeSlash());
140140
}
141141
return stringEncoder;
142142
}

java/src/json/ext/GeneratorState.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ public class GeneratorState extends RubyObject {
8383
*/
8484
private boolean quirksMode = DEFAULT_QUIRKS_MODE;
8585
static final boolean DEFAULT_QUIRKS_MODE = false;
86+
/**
87+
* If set to <code>true</code> the forward slash will be escaped in
88+
* json output.
89+
*/
90+
private boolean escapeSlash = DEFAULT_ESCAPE_SLASH;
91+
static final boolean DEFAULT_ESCAPE_SLASH = false;
8692
/**
8793
* The initial buffer length of this state. (This isn't really used on all
8894
* non-C implementations.)
@@ -172,6 +178,9 @@ static GeneratorState fromState(ThreadContext context, RuntimeInfo info,
172178
* <code>-Infinity</code> should be generated, otherwise an exception is
173179
* thrown if these values are encountered.
174180
* This options defaults to <code>false</code>.
181+
* <dt><code>:escape_slash</code>
182+
* <dd>set to <code>true</code> if the forward slashes should be escaped
183+
* in the json output (default: <code>false</code>)
175184
*/
176185
@JRubyMethod(optional=1, visibility=Visibility.PRIVATE)
177186
public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
@@ -195,6 +204,7 @@ public IRubyObject initialize_copy(ThreadContext context, IRubyObject vOrig) {
195204
this.allowNaN = orig.allowNaN;
196205
this.asciiOnly = orig.asciiOnly;
197206
this.quirksMode = orig.quirksMode;
207+
this.escapeSlash = orig.escapeSlash;
198208
this.bufferInitialLength = orig.bufferInitialLength;
199209
this.depth = orig.depth;
200210
return this;
@@ -381,6 +391,24 @@ public IRubyObject max_nesting_set(IRubyObject max_nesting) {
381391
return max_nesting;
382392
}
383393

394+
/**
395+
* Returns true if forward slashes are escaped in the json output.
396+
*/
397+
public boolean escapeSlash() {
398+
return escapeSlash;
399+
}
400+
401+
@JRubyMethod(name="escape_slash")
402+
public RubyBoolean escape_slash_get(ThreadContext context) {
403+
return context.getRuntime().newBoolean(escapeSlash);
404+
}
405+
406+
@JRubyMethod(name="escape_slash=")
407+
public IRubyObject escape_slash_set(IRubyObject escape_slash) {
408+
escapeSlash = escape_slash.isTrue();
409+
return escape_slash.getRuntime().newBoolean(escapeSlash);
410+
}
411+
384412
public boolean allowNaN() {
385413
return allowNaN;
386414
}
@@ -482,6 +510,7 @@ public IRubyObject configure(ThreadContext context, IRubyObject vOpts) {
482510
allowNaN = opts.getBool("allow_nan", DEFAULT_ALLOW_NAN);
483511
asciiOnly = opts.getBool("ascii_only", DEFAULT_ASCII_ONLY);
484512
quirksMode = opts.getBool("quirks_mode", DEFAULT_QUIRKS_MODE);
513+
escapeSlash = opts.getBool("escape_slash", DEFAULT_ESCAPE_SLASH);
485514
bufferInitialLength = opts.getInt("buffer_initial_length", DEFAULT_BUFFER_INITIAL_LENGTH);
486515

487516
depth = opts.getInt("depth", 0);
@@ -510,6 +539,7 @@ public RubyHash to_h(ThreadContext context) {
510539
result.op_aset(context, runtime.newSymbol("ascii_only"), ascii_only_p(context));
511540
result.op_aset(context, runtime.newSymbol("quirks_mode"), quirks_mode_p(context));
512541
result.op_aset(context, runtime.newSymbol("max_nesting"), max_nesting_get(context));
542+
result.op_aset(context, runtime.newSymbol("escape_slash"), escape_slash_get(context));
513543
result.op_aset(context, runtime.newSymbol("depth"), depth_get(context));
514544
result.op_aset(context, runtime.newSymbol("buffer_initial_length"), buffer_initial_length_get(context));
515545
for (String name: getInstanceVariableNameList()) {

java/src/json/ext/StringEncoder.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* and throws a GeneratorError if any problem is found.
1111
*/
1212
final class StringEncoder extends ByteListTranscoder {
13-
private final boolean asciiOnly;
13+
private final boolean asciiOnly, escapeSlash;
1414

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

35-
StringEncoder(ThreadContext context, boolean asciiOnly) {
35+
StringEncoder(ThreadContext context, boolean asciiOnly, boolean escapeSlash) {
3636
super(context);
3737
this.asciiOnly = asciiOnly;
38+
this.escapeSlash = escapeSlash;
3839
}
3940

4041
void encode(ByteList src, ByteList out) {
@@ -50,7 +51,6 @@ void encode(ByteList src, ByteList out) {
5051
private void handleChar(int c) {
5152
switch (c) {
5253
case '"':
53-
case '/':
5454
case '\\':
5555
escapeChar((char)c);
5656
break;
@@ -69,6 +69,11 @@ private void handleChar(int c) {
6969
case '\b':
7070
escapeChar('b');
7171
break;
72+
case '/':
73+
if(escapeSlash) {
74+
escapeChar((char)c);
75+
break;
76+
}
7277
default:
7378
if (c >= 0x20 && c <= 0x7f ||
7479
(c >= 0x80 && !asciiOnly)) {

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

0 commit comments

Comments
 (0)