diff --git a/ext/json/ext/generator/generator.c b/ext/json/ext/generator/generator.c
index 6300c64f3..cb9b87ca9 100644
--- a/ext/json/ext/generator/generator.c
+++ b/ext/json/ext/generator/generator.c
@@ -16,7 +16,7 @@ static ID i_to_s, i_to_json, i_new, i_indent, i_space, i_space_before,
i_object_nl, i_array_nl, i_max_nesting, i_allow_nan, i_ascii_only,
i_quirks_mode, i_pack, i_unpack, i_create_id, i_extend, i_key_p,
i_aref, i_send, i_respond_to_p, i_match, i_keys, i_depth,
- i_buffer_initial_length, i_dup;
+ i_buffer_initial_length, i_dup, i_escape_slash;
/*
* Copyright 2001-2004 Unicode, Inc.
@@ -124,7 +124,7 @@ static void unicode_escape_to_buffer(FBuffer *buffer, char buf[6], UTF16
/* Converts string to a JSON string in FBuffer buffer, where all but the ASCII
* and control characters are JSON escaped. */
-static void convert_UTF8_to_JSON_ASCII(FBuffer *buffer, VALUE string)
+static void convert_UTF8_to_JSON_ASCII(FBuffer *buffer, VALUE string, char escape_slash)
{
const UTF8 *source = (UTF8 *) RSTRING_PTR(string);
const UTF8 *sourceEnd = source + RSTRING_LEN(string);
@@ -174,6 +174,11 @@ static void convert_UTF8_to_JSON_ASCII(FBuffer *buffer, VALUE string)
case '"':
fbuffer_append(buffer, "\\\"", 2);
break;
+ case '/':
+ if(escape_slash) {
+ fbuffer_append(buffer, "\\/", 2);
+ break;
+ }
default:
fbuffer_append_char(buffer, (char)ch);
break;
@@ -222,7 +227,7 @@ static void convert_UTF8_to_JSON_ASCII(FBuffer *buffer, VALUE string)
* characters required by the JSON standard are JSON escaped. The remaining
* characters (should be UTF8) are just passed through and appended to the
* result. */
-static void convert_UTF8_to_JSON(FBuffer *buffer, VALUE string)
+static void convert_UTF8_to_JSON(FBuffer *buffer, VALUE string, char escape_slash)
{
const char *ptr = RSTRING_PTR(string), *p;
unsigned long len = RSTRING_LEN(string), start = 0, end = 0;
@@ -272,6 +277,12 @@ static void convert_UTF8_to_JSON(FBuffer *buffer, VALUE string)
escape = "\\\"";
escape_len = 2;
break;
+ case '/':
+ if(escape_slash) {
+ escape = "\\/";
+ escape_len = 2;
+ break;
+ }
default:
{
unsigned short clen = trailingBytesForUTF8[c] + 1;
@@ -624,6 +635,8 @@ static VALUE cState_configure(VALUE self, VALUE opts)
state->ascii_only = RTEST(tmp);
tmp = rb_hash_aref(opts, ID2SYM(i_quirks_mode));
state->quirks_mode = RTEST(tmp);
+ tmp = rb_hash_aref(opts, ID2SYM(i_escape_slash));
+ state->escape_slash = RTEST(tmp);
return self;
}
@@ -659,6 +672,7 @@ static VALUE cState_to_h(VALUE self)
rb_hash_aset(result, ID2SYM(i_ascii_only), state->ascii_only ? Qtrue : Qfalse);
rb_hash_aset(result, ID2SYM(i_quirks_mode), state->quirks_mode ? Qtrue : Qfalse);
rb_hash_aset(result, ID2SYM(i_max_nesting), LONG2FIX(state->max_nesting));
+ rb_hash_aset(result, ID2SYM(i_escape_slash), state->escape_slash ? Qtrue : Qfalse);
rb_hash_aset(result, ID2SYM(i_depth), LONG2FIX(state->depth));
rb_hash_aset(result, ID2SYM(i_buffer_initial_length), LONG2FIX(state->buffer_initial_length));
return result;
@@ -792,9 +806,9 @@ static void generate_json_string(FBuffer *buffer, VALUE Vstate, JSON_Generator_S
obj = rb_funcall(obj, i_encode, 1, CEncoding_UTF_8);
#endif
if (state->ascii_only) {
- convert_UTF8_to_JSON_ASCII(buffer, obj);
+ convert_UTF8_to_JSON_ASCII(buffer, obj, state->escape_slash);
} else {
- convert_UTF8_to_JSON(buffer, obj);
+ convert_UTF8_to_JSON(buffer, obj, state->escape_slash);
}
fbuffer_append_char(buffer, '"');
}
@@ -1245,6 +1259,31 @@ static VALUE cState_max_nesting_set(VALUE self, VALUE depth)
return state->max_nesting = FIX2LONG(depth);
}
+/*
+ * call-seq: escape_slash
+ *
+ * If this boolean is true, the forward slashes will be escaped in
+ * the json output.
+ */
+static VALUE cState_escape_slash(VALUE self)
+{
+ GET_STATE(self);
+ return state->escape_slash ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq: escape_slash=(depth)
+ *
+ * This sets whether or not the forward slashes will be escaped in
+ * the json output.
+ */
+static VALUE cState_escape_slash_set(VALUE self, VALUE enable)
+{
+ GET_STATE(self);
+ state->escape_slash = RTEST(enable);
+ return Qnil;
+}
+
/*
* call-seq: allow_nan?
*
@@ -1377,6 +1416,9 @@ void Init_generator(void)
rb_define_method(cState, "array_nl=", cState_array_nl_set, 1);
rb_define_method(cState, "max_nesting", cState_max_nesting, 0);
rb_define_method(cState, "max_nesting=", cState_max_nesting_set, 1);
+ rb_define_method(cState, "escape_slash", cState_escape_slash, 0);
+ rb_define_method(cState, "escape_slash?", cState_escape_slash, 0);
+ rb_define_method(cState, "escape_slash=", cState_escape_slash_set, 1);
rb_define_method(cState, "check_circular?", cState_check_circular_p, 0);
rb_define_method(cState, "allow_nan?", cState_allow_nan_p, 0);
rb_define_method(cState, "ascii_only?", cState_ascii_only_p, 0);
@@ -1432,6 +1474,7 @@ void Init_generator(void)
i_object_nl = rb_intern("object_nl");
i_array_nl = rb_intern("array_nl");
i_max_nesting = rb_intern("max_nesting");
+ i_escape_slash = rb_intern("escape_slash");
i_allow_nan = rb_intern("allow_nan");
i_ascii_only = rb_intern("ascii_only");
i_quirks_mode = rb_intern("quirks_mode");
diff --git a/ext/json/ext/generator/generator.h b/ext/json/ext/generator/generator.h
index 298c0a496..ea68e0e6d 100644
--- a/ext/json/ext/generator/generator.h
+++ b/ext/json/ext/generator/generator.h
@@ -50,8 +50,8 @@ static const UTF32 halfMask = 0x3FFUL;
static unsigned char isLegalUTF8(const UTF8 *source, unsigned long length);
static void unicode_escape(char *buf, UTF16 character);
static void unicode_escape_to_buffer(FBuffer *buffer, char buf[6], UTF16 character);
-static void convert_UTF8_to_JSON_ASCII(FBuffer *buffer, VALUE string);
-static void convert_UTF8_to_JSON(FBuffer *buffer, VALUE string);
+static void convert_UTF8_to_JSON_ASCII(FBuffer *buffer, VALUE string, char escape_slash);
+static void convert_UTF8_to_JSON(FBuffer *buffer, VALUE string, char escape_slash);
static char *fstrndup(const char *ptr, unsigned long len);
/* ruby api and some helpers */
@@ -74,6 +74,7 @@ typedef struct JSON_Generator_StateStruct {
char allow_nan;
char ascii_only;
char quirks_mode;
+ char escape_slash;
long depth;
long buffer_initial_length;
} JSON_Generator_State;
@@ -145,6 +146,8 @@ static VALUE cState_allow_nan_p(VALUE self);
static VALUE cState_ascii_only_p(VALUE self);
static VALUE cState_depth(VALUE self);
static VALUE cState_depth_set(VALUE self, VALUE depth);
+static VALUE cState_escape_slash(VALUE self);
+static VALUE cState_escape_slash_set(VALUE self, VALUE depth);
static FBuffer *cState_prepare_buffer(VALUE self);
#ifndef ZALLOC
#define ZALLOC(type) ((type *)ruby_zalloc(sizeof(type)))
diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java
index ecceb2709..e6c8c28d6 100644
--- a/java/src/json/ext/Generator.java
+++ b/java/src/json/ext/Generator.java
@@ -136,7 +136,7 @@ public RuntimeInfo getInfo() {
public StringEncoder getStringEncoder() {
if (stringEncoder == null) {
- stringEncoder = new StringEncoder(context, getState().asciiOnly());
+ stringEncoder = new StringEncoder(context, getState().asciiOnly(), getState().escapeSlash());
}
return stringEncoder;
}
diff --git a/java/src/json/ext/GeneratorState.java b/java/src/json/ext/GeneratorState.java
index 30653074c..ecb73f565 100644
--- a/java/src/json/ext/GeneratorState.java
+++ b/java/src/json/ext/GeneratorState.java
@@ -83,6 +83,12 @@ public class GeneratorState extends RubyObject {
*/
private boolean quirksMode = DEFAULT_QUIRKS_MODE;
static final boolean DEFAULT_QUIRKS_MODE = false;
+ /**
+ * If set to true the forward slash will be escaped in
+ * json output.
+ */
+ private boolean escapeSlash = DEFAULT_ESCAPE_SLASH;
+ static final boolean DEFAULT_ESCAPE_SLASH = false;
/**
* The initial buffer length of this state. (This isn't really used on all
* non-C implementations.)
@@ -172,6 +178,9 @@ static GeneratorState fromState(ThreadContext context, RuntimeInfo info,
* -Infinity should be generated, otherwise an exception is
* thrown if these values are encountered.
* This options defaults to false.
+ *
:escape_slash
+ * true if the forward slashes should be escaped
+ * in the json output (default: false)
*/
@JRubyMethod(optional=1, visibility=Visibility.PRIVATE)
public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
@@ -195,6 +204,7 @@ public IRubyObject initialize_copy(ThreadContext context, IRubyObject vOrig) {
this.allowNaN = orig.allowNaN;
this.asciiOnly = orig.asciiOnly;
this.quirksMode = orig.quirksMode;
+ this.escapeSlash = orig.escapeSlash;
this.bufferInitialLength = orig.bufferInitialLength;
this.depth = orig.depth;
return this;
@@ -381,6 +391,24 @@ public IRubyObject max_nesting_set(IRubyObject max_nesting) {
return max_nesting;
}
+ /**
+ * Returns true if forward slashes are escaped in the json output.
+ */
+ public boolean escapeSlash() {
+ return escapeSlash;
+ }
+
+ @JRubyMethod(name="escape_slash")
+ public RubyBoolean escape_slash_get(ThreadContext context) {
+ return context.getRuntime().newBoolean(escapeSlash);
+ }
+
+ @JRubyMethod(name="escape_slash=")
+ public IRubyObject escape_slash_set(IRubyObject escape_slash) {
+ escapeSlash = escape_slash.isTrue();
+ return escape_slash.getRuntime().newBoolean(escapeSlash);
+ }
+
public boolean allowNaN() {
return allowNaN;
}
@@ -482,6 +510,7 @@ public IRubyObject configure(ThreadContext context, IRubyObject vOpts) {
allowNaN = opts.getBool("allow_nan", DEFAULT_ALLOW_NAN);
asciiOnly = opts.getBool("ascii_only", DEFAULT_ASCII_ONLY);
quirksMode = opts.getBool("quirks_mode", DEFAULT_QUIRKS_MODE);
+ escapeSlash = opts.getBool("escape_slash", DEFAULT_ESCAPE_SLASH);
bufferInitialLength = opts.getInt("buffer_initial_length", DEFAULT_BUFFER_INITIAL_LENGTH);
depth = opts.getInt("depth", 0);
@@ -510,6 +539,7 @@ public RubyHash to_h(ThreadContext context) {
result.op_aset(context, runtime.newSymbol("ascii_only"), ascii_only_p(context));
result.op_aset(context, runtime.newSymbol("quirks_mode"), quirks_mode_p(context));
result.op_aset(context, runtime.newSymbol("max_nesting"), max_nesting_get(context));
+ result.op_aset(context, runtime.newSymbol("escape_slash"), escape_slash_get(context));
result.op_aset(context, runtime.newSymbol("depth"), depth_get(context));
result.op_aset(context, runtime.newSymbol("buffer_initial_length"), buffer_initial_length_get(context));
for (String name: getInstanceVariableNameList()) {
diff --git a/java/src/json/ext/StringEncoder.java b/java/src/json/ext/StringEncoder.java
index 57bd19bce..44e69d10e 100644
--- a/java/src/json/ext/StringEncoder.java
+++ b/java/src/json/ext/StringEncoder.java
@@ -10,7 +10,7 @@
* and throws a GeneratorError if any problem is found.
*/
final class StringEncoder extends ByteListTranscoder {
- private final boolean asciiOnly;
+ private final boolean asciiOnly, escapeSlash;
// Escaped characters will reuse this array, to avoid new allocations
// or appending them byte-by-byte
@@ -32,9 +32,10 @@ final class StringEncoder extends ByteListTranscoder {
new byte[] {'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
- StringEncoder(ThreadContext context, boolean asciiOnly) {
+ StringEncoder(ThreadContext context, boolean asciiOnly, boolean escapeSlash) {
super(context);
this.asciiOnly = asciiOnly;
+ this.escapeSlash = escapeSlash;
}
void encode(ByteList src, ByteList out) {
@@ -68,6 +69,11 @@ private void handleChar(int c) {
case '\b':
escapeChar('b');
break;
+ case '/':
+ if(escapeSlash) {
+ escapeChar((char)c);
+ break;
+ }
default:
if (c >= 0x20 && c <= 0x7f ||
(c >= 0x80 && !asciiOnly)) {
diff --git a/lib/json/common.rb b/lib/json/common.rb
index f44184e13..c16de2b41 100644
--- a/lib/json/common.rb
+++ b/lib/json/common.rb
@@ -358,12 +358,14 @@ class << self
# :max_nesting: false
# :allow_nan: true
# :quirks_mode: true
+ # :escape_slash: true
attr_accessor :dump_default_options
end
self.dump_default_options = {
:max_nesting => false,
:allow_nan => true,
:quirks_mode => true,
+ :escape_slash => true,
}
# Dumps _obj_ as a JSON string, i.e. calls generate on the object and returns
@@ -443,7 +445,7 @@ module ::Kernel
# one line.
def j(*objs)
objs.each do |obj|
- puts JSON::generate(obj, :allow_nan => true, :max_nesting => false)
+ puts JSON::generate(obj, :allow_nan => true, :max_nesting => false, :escape_slash => true)
end
nil
end
@@ -452,7 +454,7 @@ def j(*objs)
# indentation and over many lines.
def jj(*objs)
objs.each do |obj|
- puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false)
+ puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false, :escape_slash => true)
end
nil
end
diff --git a/lib/json/pure/generator.rb b/lib/json/pure/generator.rb
index 9056a5d7f..e26760955 100644
--- a/lib/json/pure/generator.rb
+++ b/lib/json/pure/generator.rb
@@ -34,23 +34,26 @@ module JSON
"\x1f" => '\u001f',
'"' => '\"',
'\\' => '\\\\',
+ '/' => '\\/',
} # :nodoc:
# Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
# UTF16 big endian characters as \u????, and return it.
if defined?(::Encoding)
- def utf8_to_json(string) # :nodoc:
+ def utf8_to_json(string, escape_slash = true) # :nodoc:
string = string.dup
string.force_encoding(::Encoding::ASCII_8BIT)
string.gsub!(/["\\\x0-\x1f]/) { MAP[$&] }
+ string.gsub!('/') { MAP[$&] } if escape_slash
string.force_encoding(::Encoding::UTF_8)
string
end
- def utf8_to_json_ascii(string) # :nodoc:
+ def utf8_to_json_ascii(string, escape_slash = true) # :nodoc:
string = string.dup
string.force_encoding(::Encoding::ASCII_8BIT)
string.gsub!(/["\\\x0-\x1f]/n) { MAP[$&] }
+ string.gsub!('/') { MAP[$&] } if escape_slash
string.gsub!(/(
(?:
[\xc2-\xdf][\x80-\xbf] |
@@ -78,12 +81,15 @@ def valid_utf8?(string)
end
module_function :valid_utf8?
else
- def utf8_to_json(string) # :nodoc:
- string.gsub(/["\\\x0-\x1f]/n) { MAP[$&] }
+ def utf8_to_json(string, escape_slash = true) # :nodoc:
+ string = string.gsub(/["\\\x0-\x1f]/n) { MAP[$&] }
+ string.gsub!('/') { MAP[$&] } if escape_slash
+ string
end
- def utf8_to_json_ascii(string) # :nodoc:
- string = string.gsub(/["\\\x0-\x1f]/) { MAP[$&] }
+ def utf8_to_json_ascii(string, escape_slash = true) # :nodoc:
+ string = string.gsub(/[\/"\\\x0-\x1f]/) { MAP[$&] }
+ string.gsub!('/') { MAP[$&] } if escape_slash
string.gsub!(/(
(?:
[\xc2-\xdf][\x80-\xbf] |
@@ -166,6 +172,7 @@ def initialize(opts = {})
@ascii_only = false
@quirks_mode = false
@buffer_initial_length = 1024
+ @escape_slash = false
configure opts
end
@@ -197,6 +204,10 @@ def initialize(opts = {})
# :stopdoc:
attr_reader :buffer_initial_length
+ # If this attribute is set to true, forward slashes will be escaped in
+ # all json strings.
+ attr_accessor :escape_slash
+
def buffer_initial_length=(length)
if length > 0
@buffer_initial_length = length
@@ -238,6 +249,11 @@ def quirks_mode?
@quirks_mode
end
+ # Returns true, if forward slashes are escaped. Otherwise returns false.
+ def escape_slash?
+ @escape_slash
+ end
+
# Configure this State instance with the Hash _opts_, and return
# itself.
def configure(opts)
@@ -261,6 +277,7 @@ def configure(opts)
@depth = opts[:depth] || 0
@quirks_mode = opts[:quirks_mode] if opts.key?(:quirks_mode)
@buffer_initial_length ||= opts[:buffer_initial_length]
+ @escape_slash = !!opts[:escape_slash] if opts.key?(:escape_slash)
if !opts.key?(:max_nesting) # defaults to 100
@max_nesting = 100
@@ -449,9 +466,9 @@ def to_json(state = nil, *args)
string = encode(::Encoding::UTF_8)
end
if state.ascii_only?
- '"' << JSON.utf8_to_json_ascii(string) << '"'
+ '"' << JSON.utf8_to_json_ascii(string, state.escape_slash) << '"'
else
- '"' << JSON.utf8_to_json(string) << '"'
+ '"' << JSON.utf8_to_json(string, state.escape_slash) << '"'
end
end
else
@@ -461,9 +478,9 @@ def to_json(state = nil, *args)
def to_json(state = nil, *args)
state = State.from_state(state)
if state.ascii_only?
- '"' << JSON.utf8_to_json_ascii(self) << '"'
+ '"' << JSON.utf8_to_json_ascii(self, state.escape_slash) << '"'
else
- '"' << JSON.utf8_to_json(self) << '"'
+ '"' << JSON.utf8_to_json(self, state.escape_slash) << '"'
end
end
end
diff --git a/tests/test_json.rb b/tests/test_json.rb
index 7957773f2..8e6196067 100755
--- a/tests/test_json.rb
+++ b/tests/test_json.rb
@@ -409,10 +409,15 @@ def test_backslash
assert_equal json, JSON.generate(data)
assert_equal data, JSON.parse(json)
#
+ data = [ '/' ]
json = '["/"]'
- data = JSON.parse(json)
- assert_equal ['/'], data
assert_equal json, JSON.generate(data)
+ assert_equal data, JSON.parse(json)
+ #
+ data = [ '/' ]
+ json = '["\/"]'
+ assert_equal json, JSON.generate(data, :escape_slash => true)
+ assert_equal data, JSON.parse(json)
#
json = '["\""]'
data = JSON.parse(json)
diff --git a/tests/test_json_generate.rb b/tests/test_json_generate.rb
index 8db0b7890..1e0ec9315 100755
--- a/tests/test_json_generate.rb
+++ b/tests/test_json_generate.rb
@@ -142,6 +142,7 @@ def test_pretty_state
:buffer_initial_length => 1024,
:quirks_mode => false,
:depth => 0,
+ :escape_slash => false,
:indent => " ",
:max_nesting => 100,
:object_nl => "\n",
@@ -159,6 +160,7 @@ def test_safe_state
:buffer_initial_length => 1024,
:quirks_mode => false,
:depth => 0,
+ :escape_slash => false,
:indent => "",
:max_nesting => 100,
:object_nl => "",
@@ -176,6 +178,7 @@ def test_fast_state
:buffer_initial_length => 1024,
:quirks_mode => false,
:depth => 0,
+ :escape_slash => false,
:indent => "",
:max_nesting => 0,
:object_nl => "",