From c50c9cd1b40dfea9c480ffec8d3178470f49cbe6 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 24 Oct 2024 16:51:25 +0200 Subject: [PATCH] Deprecate unsafe default options of `JSON.load` [Feature #19528] Ref: https://bugs.ruby-lang.org/issues/19528 `load` is understood as the default method for serializer kind of libraries, and the default options of `JSON.load` has caused many security vulnerabilities over the years. The plan is to do like YAML/Psych, deprecate these default options and direct users toward using `JSON.unsafe_load` so at least it's obvious it should be used against untrusted data. --- ext/json/ext/parser/parser.c | 135 +++++++++-------- ext/json/ext/parser/parser.h | 1 + ext/json/ext/parser/parser.rl | 15 +- java/src/json/ext/Parser.java | 164 ++++++++++++--------- java/src/json/ext/Parser.rl | 22 ++- lib/json/common.rb | 185 ++++++++++++++++++++++-- lib/json/pure/parser.rb | 25 +++- test/json/json_addition_test.rb | 6 + test/json/json_common_interface_test.rb | 5 - test/json/json_generator_test.rb | 2 +- 10 files changed, 393 insertions(+), 167 deletions(-) diff --git a/ext/json/ext/parser/parser.c b/ext/json/ext/parser/parser.c index cf0b3cef..37f92fcb 100644 --- a/ext/json/ext/parser/parser.c +++ b/ext/json/ext/parser/parser.c @@ -474,6 +474,9 @@ case 26: if (!NIL_P(klassname)) { VALUE klass = rb_funcall(mJSON, i_deep_const_get, 1, klassname); if (RTEST(rb_funcall(klass, i_json_creatable_p, 0))) { + if (json->deprecated_create_additions) { + rb_warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); + } *result = rb_funcall(klass, i_json_create, 1, *result); } } @@ -486,7 +489,7 @@ case 26: -#line 490 "parser.c" +#line 493 "parser.c" enum {JSON_value_start = 1}; enum {JSON_value_first_final = 29}; enum {JSON_value_error = 0}; @@ -494,7 +497,7 @@ enum {JSON_value_error = 0}; enum {JSON_value_en_main = 1}; -#line 287 "parser.rl" +#line 290 "parser.rl" static char *JSON_parse_value(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting) @@ -502,14 +505,14 @@ static char *JSON_parse_value(JSON_Parser *json, char *p, char *pe, VALUE *resul int cs = EVIL; -#line 506 "parser.c" +#line 509 "parser.c" { cs = JSON_value_start; } -#line 294 "parser.rl" +#line 297 "parser.rl" -#line 513 "parser.c" +#line 516 "parser.c" { if ( p == pe ) goto _test_eof; @@ -543,14 +546,14 @@ case 1: cs = 0; goto _out; tr2: -#line 239 "parser.rl" +#line 242 "parser.rl" { char *np = JSON_parse_string(json, p, pe, result); if (np == NULL) { p--; {p++; cs = 29; goto _out;} } else {p = (( np))-1;} } goto st29; tr3: -#line 244 "parser.rl" +#line 247 "parser.rl" { char *np; if(pe > p + 8 && !strncmp(MinusInfinity, p, 9)) { @@ -570,7 +573,7 @@ cs = 0; } goto st29; tr7: -#line 262 "parser.rl" +#line 265 "parser.rl" { char *np; np = JSON_parse_array(json, p, pe, result, current_nesting + 1); @@ -578,7 +581,7 @@ cs = 0; } goto st29; tr11: -#line 268 "parser.rl" +#line 271 "parser.rl" { char *np; np = JSON_parse_object(json, p, pe, result, current_nesting + 1); @@ -586,7 +589,7 @@ cs = 0; } goto st29; tr25: -#line 232 "parser.rl" +#line 235 "parser.rl" { if (json->allow_nan) { *result = CInfinity; @@ -596,7 +599,7 @@ cs = 0; } goto st29; tr27: -#line 225 "parser.rl" +#line 228 "parser.rl" { if (json->allow_nan) { *result = CNaN; @@ -606,19 +609,19 @@ cs = 0; } goto st29; tr31: -#line 219 "parser.rl" +#line 222 "parser.rl" { *result = Qfalse; } goto st29; tr34: -#line 216 "parser.rl" +#line 219 "parser.rl" { *result = Qnil; } goto st29; tr37: -#line 222 "parser.rl" +#line 225 "parser.rl" { *result = Qtrue; } @@ -627,9 +630,9 @@ cs = 0; if ( ++p == pe ) goto _test_eof29; case 29: -#line 274 "parser.rl" +#line 277 "parser.rl" { p--; {p++; cs = 29; goto _out;} } -#line 633 "parser.c" +#line 636 "parser.c" switch( (*p) ) { case 13: goto st29; case 32: goto st29; @@ -870,7 +873,7 @@ case 28: _out: {} } -#line 295 "parser.rl" +#line 298 "parser.rl" if (json->freeze) { OBJ_FREEZE(*result); @@ -884,7 +887,7 @@ case 28: } -#line 888 "parser.c" +#line 891 "parser.c" enum {JSON_integer_start = 1}; enum {JSON_integer_first_final = 3}; enum {JSON_integer_error = 0}; @@ -892,7 +895,7 @@ enum {JSON_integer_error = 0}; enum {JSON_integer_en_main = 1}; -#line 315 "parser.rl" +#line 318 "parser.rl" static char *JSON_parse_integer(JSON_Parser *json, char *p, char *pe, VALUE *result) @@ -900,15 +903,15 @@ static char *JSON_parse_integer(JSON_Parser *json, char *p, char *pe, VALUE *res int cs = EVIL; -#line 904 "parser.c" +#line 907 "parser.c" { cs = JSON_integer_start; } -#line 322 "parser.rl" +#line 325 "parser.rl" json->memo = p; -#line 912 "parser.c" +#line 915 "parser.c" { if ( p == pe ) goto _test_eof; @@ -942,14 +945,14 @@ case 3: goto st0; goto tr4; tr4: -#line 312 "parser.rl" +#line 315 "parser.rl" { p--; {p++; cs = 4; goto _out;} } goto st4; st4: if ( ++p == pe ) goto _test_eof4; case 4: -#line 953 "parser.c" +#line 956 "parser.c" goto st0; st5: if ( ++p == pe ) @@ -968,7 +971,7 @@ case 5: _out: {} } -#line 324 "parser.rl" +#line 327 "parser.rl" if (cs >= JSON_integer_first_final) { long len = p - json->memo; @@ -983,7 +986,7 @@ case 5: } -#line 987 "parser.c" +#line 990 "parser.c" enum {JSON_float_start = 1}; enum {JSON_float_first_final = 8}; enum {JSON_float_error = 0}; @@ -991,7 +994,7 @@ enum {JSON_float_error = 0}; enum {JSON_float_en_main = 1}; -#line 349 "parser.rl" +#line 352 "parser.rl" static char *JSON_parse_float(JSON_Parser *json, char *p, char *pe, VALUE *result) @@ -999,15 +1002,15 @@ static char *JSON_parse_float(JSON_Parser *json, char *p, char *pe, VALUE *resul int cs = EVIL; -#line 1003 "parser.c" +#line 1006 "parser.c" { cs = JSON_float_start; } -#line 356 "parser.rl" +#line 359 "parser.rl" json->memo = p; -#line 1011 "parser.c" +#line 1014 "parser.c" { if ( p == pe ) goto _test_eof; @@ -1065,14 +1068,14 @@ case 8: goto st0; goto tr9; tr9: -#line 343 "parser.rl" +#line 346 "parser.rl" { p--; {p++; cs = 9; goto _out;} } goto st9; st9: if ( ++p == pe ) goto _test_eof9; case 9: -#line 1076 "parser.c" +#line 1079 "parser.c" goto st0; st5: if ( ++p == pe ) @@ -1133,7 +1136,7 @@ case 7: _out: {} } -#line 358 "parser.rl" +#line 361 "parser.rl" if (cs >= JSON_float_first_final) { VALUE mod = Qnil; @@ -1186,7 +1189,7 @@ case 7: -#line 1190 "parser.c" +#line 1193 "parser.c" enum {JSON_array_start = 1}; enum {JSON_array_first_final = 17}; enum {JSON_array_error = 0}; @@ -1194,7 +1197,7 @@ enum {JSON_array_error = 0}; enum {JSON_array_en_main = 1}; -#line 438 "parser.rl" +#line 441 "parser.rl" static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *result, int current_nesting) @@ -1208,14 +1211,14 @@ static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *resul *result = NIL_P(array_class) ? rb_ary_new() : rb_class_new_instance(0, 0, array_class); -#line 1212 "parser.c" +#line 1215 "parser.c" { cs = JSON_array_start; } -#line 451 "parser.rl" +#line 454 "parser.rl" -#line 1219 "parser.c" +#line 1222 "parser.c" { if ( p == pe ) goto _test_eof; @@ -1254,7 +1257,7 @@ case 2: goto st2; goto st0; tr2: -#line 415 "parser.rl" +#line 418 "parser.rl" { VALUE v = Qnil; char *np = JSON_parse_value(json, p, pe, &v, current_nesting); @@ -1274,7 +1277,7 @@ case 2: if ( ++p == pe ) goto _test_eof3; case 3: -#line 1278 "parser.c" +#line 1281 "parser.c" switch( (*p) ) { case 13: goto st3; case 32: goto st3; @@ -1374,14 +1377,14 @@ case 12: goto st3; goto st12; tr4: -#line 430 "parser.rl" +#line 433 "parser.rl" { p--; {p++; cs = 17; goto _out;} } goto st17; st17: if ( ++p == pe ) goto _test_eof17; case 17: -#line 1385 "parser.c" +#line 1388 "parser.c" goto st0; st13: if ( ++p == pe ) @@ -1437,7 +1440,7 @@ case 16: _out: {} } -#line 452 "parser.rl" +#line 455 "parser.rl" if(cs >= JSON_array_first_final) { return p + 1; @@ -1598,7 +1601,7 @@ static VALUE json_string_unescape(char *string, char *stringEnd, int intern, int } -#line 1602 "parser.c" +#line 1605 "parser.c" enum {JSON_string_start = 1}; enum {JSON_string_first_final = 8}; enum {JSON_string_error = 0}; @@ -1606,7 +1609,7 @@ enum {JSON_string_error = 0}; enum {JSON_string_en_main = 1}; -#line 630 "parser.rl" +#line 633 "parser.rl" static int @@ -1627,15 +1630,15 @@ static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *resu VALUE match_string; -#line 1631 "parser.c" +#line 1634 "parser.c" { cs = JSON_string_start; } -#line 650 "parser.rl" +#line 653 "parser.rl" json->memo = p; -#line 1639 "parser.c" +#line 1642 "parser.c" { if ( p == pe ) goto _test_eof; @@ -1660,7 +1663,7 @@ case 2: goto st0; goto st2; tr2: -#line 617 "parser.rl" +#line 620 "parser.rl" { *result = json_string_unescape(json->memo + 1, p, json->parsing_name || json-> freeze, json->parsing_name && json->symbolize_names); if (NIL_P(*result)) { @@ -1670,14 +1673,14 @@ case 2: {p = (( p + 1))-1;} } } -#line 627 "parser.rl" +#line 630 "parser.rl" { p--; {p++; cs = 8; goto _out;} } goto st8; st8: if ( ++p == pe ) goto _test_eof8; case 8: -#line 1681 "parser.c" +#line 1684 "parser.c" goto st0; st3: if ( ++p == pe ) @@ -1753,7 +1756,7 @@ case 7: _out: {} } -#line 652 "parser.rl" +#line 655 "parser.rl" if (json->create_additions && RTEST(match_string = json->match_string)) { VALUE klass; @@ -1888,10 +1891,16 @@ static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self) } tmp = ID2SYM(i_create_additions); if (option_given_p(opts, tmp)) { - json->create_additions = RTEST(rb_hash_aref(opts, tmp)); - } else { - json->create_additions = 0; + tmp = rb_hash_aref(opts, tmp); + if (NIL_P(tmp)) { + json->create_additions = true; + json->deprecated_create_additions = true; + } else { + json->create_additions = RTEST(tmp); + json->deprecated_create_additions = false; + } } + if (json->symbolize_names && json->create_additions) { rb_raise(rb_eArgError, "options :symbolize_names and :create_additions cannot be " @@ -1946,7 +1955,7 @@ static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self) } -#line 1950 "parser.c" +#line 1959 "parser.c" enum {JSON_start = 1}; enum {JSON_first_final = 10}; enum {JSON_error = 0}; @@ -1954,7 +1963,7 @@ enum {JSON_error = 0}; enum {JSON_en_main = 1}; -#line 858 "parser.rl" +#line 867 "parser.rl" /* @@ -1972,16 +1981,16 @@ static VALUE cParser_parse(VALUE self) GET_PARSER; -#line 1976 "parser.c" +#line 1985 "parser.c" { cs = JSON_start; } -#line 875 "parser.rl" +#line 884 "parser.rl" p = json->source; pe = p + json->len; -#line 1985 "parser.c" +#line 1994 "parser.c" { if ( p == pe ) goto _test_eof; @@ -2015,7 +2024,7 @@ case 1: cs = 0; goto _out; tr2: -#line 850 "parser.rl" +#line 859 "parser.rl" { char *np = JSON_parse_value(json, p, pe, &result, 0); if (np == NULL) { p--; {p++; cs = 10; goto _out;} } else {p = (( np))-1;} @@ -2025,7 +2034,7 @@ cs = 0; if ( ++p == pe ) goto _test_eof10; case 10: -#line 2029 "parser.c" +#line 2038 "parser.c" switch( (*p) ) { case 13: goto st10; case 32: goto st10; @@ -2114,7 +2123,7 @@ case 9: _out: {} } -#line 878 "parser.rl" +#line 887 "parser.rl" if (cs >= JSON_first_final && p == pe) { return result; diff --git a/ext/json/ext/parser/parser.h b/ext/json/ext/parser/parser.h index 81ea6a85..179d1118 100644 --- a/ext/json/ext/parser/parser.h +++ b/ext/json/ext/parser/parser.h @@ -26,6 +26,7 @@ typedef struct JSON_ParserStruct { bool symbolize_names; bool freeze; bool create_additions; + bool deprecated_create_additions; } JSON_Parser; #define GET_PARSER \ diff --git a/ext/json/ext/parser/parser.rl b/ext/json/ext/parser/parser.rl index 73f81341..c11d547b 100644 --- a/ext/json/ext/parser/parser.rl +++ b/ext/json/ext/parser/parser.rl @@ -196,6 +196,9 @@ static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *resu if (!NIL_P(klassname)) { VALUE klass = rb_funcall(mJSON, i_deep_const_get, 1, klassname); if (RTEST(rb_funcall(klass, i_json_creatable_p, 0))) { + if (json->deprecated_create_additions) { + rb_warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); + } *result = rb_funcall(klass, i_json_create, 1, *result); } } @@ -783,10 +786,16 @@ static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self) } tmp = ID2SYM(i_create_additions); if (option_given_p(opts, tmp)) { - json->create_additions = RTEST(rb_hash_aref(opts, tmp)); - } else { - json->create_additions = 0; + tmp = rb_hash_aref(opts, tmp); + if (NIL_P(tmp)) { + json->create_additions = true; + json->deprecated_create_additions = true; + } else { + json->create_additions = RTEST(tmp); + json->deprecated_create_additions = false; + } } + if (json->symbolize_names && json->create_additions) { rb_raise(rb_eArgError, "options :symbolize_names and :create_additions cannot be " diff --git a/java/src/json/ext/Parser.java b/java/src/json/ext/Parser.java index 4234fd21..92001b3e 100644 --- a/java/src/json/ext/Parser.java +++ b/java/src/json/ext/Parser.java @@ -51,6 +51,7 @@ public class Parser extends RubyObject { private RubyString vSource; private RubyString createId; private boolean createAdditions; + private boolean deprecatedCreateAdditions; private int maxNesting; private boolean allowNaN; private boolean symbolizeNames; @@ -171,7 +172,20 @@ public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { this.symbolizeNames = opts.getBool("symbolize_names", false); this.freeze = opts.getBool("freeze", false); this.createId = opts.getString("create_id", getCreateId(context)); - this.createAdditions = opts.getBool("create_additions", false); + + IRubyObject additions = opts.get("create_additions"); + this.createAdditions = false; + this.deprecatedCreateAdditions = false; + + if (additions != null) { + if (additions.isNil()) { + this.createAdditions = true; + this.deprecatedCreateAdditions = true; + } else { + this.createAdditions = opts.getBool("create_additions", false); + } + } + this.objectClass = opts.getClass("object_class", runtime.getHash()); this.arrayClass = opts.getClass("array_class", runtime.getArray()); this.decimalClass = opts.getClass("decimal_class", null); @@ -342,11 +356,11 @@ private Ruby getRuntime() { } -// line 368 "Parser.rl" +// line 382 "Parser.rl" -// line 350 "Parser.java" +// line 364 "Parser.java" private static byte[] init__JSON_value_actions_0() { return new byte [] { @@ -460,7 +474,7 @@ private static byte[] init__JSON_value_from_state_actions_0() static final int JSON_value_en_main = 1; -// line 474 "Parser.rl" +// line 488 "Parser.rl" void parseValue(ParserResult res, int p, int pe) { @@ -468,14 +482,14 @@ void parseValue(ParserResult res, int p, int pe) { IRubyObject result = null; -// line 472 "Parser.java" +// line 486 "Parser.java" { cs = JSON_value_start; } -// line 481 "Parser.rl" +// line 495 "Parser.rl" -// line 479 "Parser.java" +// line 493 "Parser.java" { int _klen; int _trans = 0; @@ -501,13 +515,13 @@ void parseValue(ParserResult res, int p, int pe) { while ( _nacts-- > 0 ) { switch ( _JSON_value_actions[_acts++] ) { case 9: -// line 459 "Parser.rl" +// line 473 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 511 "Parser.java" +// line 525 "Parser.java" } } @@ -570,25 +584,25 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) switch ( _JSON_value_actions[_acts++] ) { case 0: -// line 376 "Parser.rl" +// line 390 "Parser.rl" { result = getRuntime().getNil(); } break; case 1: -// line 379 "Parser.rl" +// line 393 "Parser.rl" { result = getRuntime().getFalse(); } break; case 2: -// line 382 "Parser.rl" +// line 396 "Parser.rl" { result = getRuntime().getTrue(); } break; case 3: -// line 385 "Parser.rl" +// line 399 "Parser.rl" { if (parser.allowNaN) { result = getConstant(CONST_NAN); @@ -598,7 +612,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 4: -// line 392 "Parser.rl" +// line 406 "Parser.rl" { if (parser.allowNaN) { result = getConstant(CONST_INFINITY); @@ -608,7 +622,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 5: -// line 399 "Parser.rl" +// line 413 "Parser.rl" { if (pe > p + 8 && absSubSequence(p, p + 9).equals(JSON_MINUS_INFINITY)) { @@ -637,7 +651,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 6: -// line 425 "Parser.rl" +// line 439 "Parser.rl" { parseString(res, p, pe); if (res.result == null) { @@ -650,7 +664,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 7: -// line 435 "Parser.rl" +// line 449 "Parser.rl" { currentNesting++; parseArray(res, p, pe); @@ -665,7 +679,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } break; case 8: -// line 447 "Parser.rl" +// line 461 "Parser.rl" { currentNesting++; parseObject(res, p, pe); @@ -679,7 +693,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } } break; -// line 683 "Parser.java" +// line 697 "Parser.java" } } } @@ -699,7 +713,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) break; } } -// line 482 "Parser.rl" +// line 496 "Parser.rl" if (cs >= JSON_value_first_final && result != null) { if (parser.freeze) { @@ -712,7 +726,7 @@ else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) } -// line 716 "Parser.java" +// line 730 "Parser.java" private static byte[] init__JSON_integer_actions_0() { return new byte [] { @@ -811,7 +825,7 @@ private static byte[] init__JSON_integer_trans_actions_0() static final int JSON_integer_en_main = 1; -// line 504 "Parser.rl" +// line 518 "Parser.rl" void parseInteger(ParserResult res, int p, int pe) { @@ -829,15 +843,15 @@ int parseIntegerInternal(int p, int pe) { int cs = EVIL; -// line 833 "Parser.java" +// line 847 "Parser.java" { cs = JSON_integer_start; } -// line 521 "Parser.rl" +// line 535 "Parser.rl" int memo = p; -// line 841 "Parser.java" +// line 855 "Parser.java" { int _klen; int _trans = 0; @@ -918,13 +932,13 @@ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) switch ( _JSON_integer_actions[_acts++] ) { case 0: -// line 498 "Parser.rl" +// line 512 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 928 "Parser.java" +// line 942 "Parser.java" } } } @@ -944,7 +958,7 @@ else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) break; } } -// line 523 "Parser.rl" +// line 537 "Parser.rl" if (cs < JSON_integer_first_final) { return -1; @@ -964,7 +978,7 @@ RubyInteger bytesToInum(Ruby runtime, ByteList num) { } -// line 968 "Parser.java" +// line 982 "Parser.java" private static byte[] init__JSON_float_actions_0() { return new byte [] { @@ -1066,7 +1080,7 @@ private static byte[] init__JSON_float_trans_actions_0() static final int JSON_float_en_main = 1; -// line 556 "Parser.rl" +// line 570 "Parser.rl" void parseFloat(ParserResult res, int p, int pe) { @@ -1085,15 +1099,15 @@ int parseFloatInternal(int p, int pe) { int cs = EVIL; -// line 1089 "Parser.java" +// line 1103 "Parser.java" { cs = JSON_float_start; } -// line 574 "Parser.rl" +// line 588 "Parser.rl" int memo = p; -// line 1097 "Parser.java" +// line 1111 "Parser.java" { int _klen; int _trans = 0; @@ -1174,13 +1188,13 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) switch ( _JSON_float_actions[_acts++] ) { case 0: -// line 547 "Parser.rl" +// line 561 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1184 "Parser.java" +// line 1198 "Parser.java" } } } @@ -1200,7 +1214,7 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) break; } } -// line 576 "Parser.rl" +// line 590 "Parser.rl" if (cs < JSON_float_first_final) { return -1; @@ -1210,7 +1224,7 @@ else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) } -// line 1214 "Parser.java" +// line 1228 "Parser.java" private static byte[] init__JSON_string_actions_0() { return new byte [] { @@ -1312,7 +1326,7 @@ private static byte[] init__JSON_string_trans_actions_0() static final int JSON_string_en_main = 1; -// line 615 "Parser.rl" +// line 629 "Parser.rl" void parseString(ParserResult res, int p, int pe) { @@ -1320,15 +1334,15 @@ void parseString(ParserResult res, int p, int pe) { IRubyObject result = null; -// line 1324 "Parser.java" +// line 1338 "Parser.java" { cs = JSON_string_start; } -// line 622 "Parser.rl" +// line 636 "Parser.rl" int memo = p; -// line 1332 "Parser.java" +// line 1346 "Parser.java" { int _klen; int _trans = 0; @@ -1409,7 +1423,7 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) switch ( _JSON_string_actions[_acts++] ) { case 0: -// line 590 "Parser.rl" +// line 604 "Parser.rl" { int offset = byteList.begin(); ByteList decoded = decoder.decode(byteList, memo + 1 - offset, @@ -1424,13 +1438,13 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) } break; case 1: -// line 603 "Parser.rl" +// line 617 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1434 "Parser.java" +// line 1448 "Parser.java" } } } @@ -1450,7 +1464,7 @@ else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) break; } } -// line 624 "Parser.rl" +// line 638 "Parser.rl" if (parser.createAdditions) { RubyHash matchString = parser.match_string; @@ -1471,6 +1485,9 @@ public void visit(IRubyObject pattern, IRubyObject klass) { RubyClass klass = (RubyClass) memoArray[1]; if (klass.respondsTo("json_creatable?") && klass.callMethod(context, "json_creatable?").isTrue()) { + if (parser.deprecatedCreateAdditions) { + klass.getRuntime().getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); + } result = klass.callMethod(context, "json_create", result); } } @@ -1495,7 +1512,7 @@ public void visit(IRubyObject pattern, IRubyObject klass) { } -// line 1499 "Parser.java" +// line 1516 "Parser.java" private static byte[] init__JSON_array_actions_0() { return new byte [] { @@ -1608,7 +1625,7 @@ private static byte[] init__JSON_array_trans_actions_0() static final int JSON_array_en_main = 1; -// line 704 "Parser.rl" +// line 721 "Parser.rl" void parseArray(ParserResult res, int p, int pe) { @@ -1628,14 +1645,14 @@ void parseArray(ParserResult res, int p, int pe) { } -// line 1632 "Parser.java" +// line 1649 "Parser.java" { cs = JSON_array_start; } -// line 723 "Parser.rl" +// line 740 "Parser.rl" -// line 1639 "Parser.java" +// line 1656 "Parser.java" { int _klen; int _trans = 0; @@ -1716,7 +1733,7 @@ else if ( data[p] > _JSON_array_trans_keys[_mid+1] ) switch ( _JSON_array_actions[_acts++] ) { case 0: -// line 673 "Parser.rl" +// line 690 "Parser.rl" { parseValue(res, p, pe); if (res.result == null) { @@ -1733,13 +1750,13 @@ else if ( data[p] > _JSON_array_trans_keys[_mid+1] ) } break; case 1: -// line 688 "Parser.rl" +// line 705 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1743 "Parser.java" +// line 1760 "Parser.java" } } } @@ -1759,7 +1776,7 @@ else if ( data[p] > _JSON_array_trans_keys[_mid+1] ) break; } } -// line 724 "Parser.rl" +// line 741 "Parser.rl" if (cs >= JSON_array_first_final) { res.update(result, p + 1); @@ -1769,7 +1786,7 @@ else if ( data[p] > _JSON_array_trans_keys[_mid+1] ) } -// line 1773 "Parser.java" +// line 1790 "Parser.java" private static byte[] init__JSON_object_actions_0() { return new byte [] { @@ -1892,7 +1909,7 @@ private static byte[] init__JSON_object_trans_actions_0() static final int JSON_object_en_main = 1; -// line 781 "Parser.rl" +// line 798 "Parser.rl" void parseObject(ParserResult res, int p, int pe) { @@ -1917,14 +1934,14 @@ void parseObject(ParserResult res, int p, int pe) { } -// line 1921 "Parser.java" +// line 1938 "Parser.java" { cs = JSON_object_start; } -// line 805 "Parser.rl" +// line 822 "Parser.rl" -// line 1928 "Parser.java" +// line 1945 "Parser.java" { int _klen; int _trans = 0; @@ -2005,7 +2022,7 @@ else if ( data[p] > _JSON_object_trans_keys[_mid+1] ) switch ( _JSON_object_actions[_acts++] ) { case 0: -// line 738 "Parser.rl" +// line 755 "Parser.rl" { parseValue(res, p, pe); if (res.result == null) { @@ -2022,7 +2039,7 @@ else if ( data[p] > _JSON_object_trans_keys[_mid+1] ) } break; case 1: -// line 753 "Parser.rl" +// line 770 "Parser.rl" { parseString(res, p, pe); if (res.result == null) { @@ -2040,13 +2057,13 @@ else if ( data[p] > _JSON_object_trans_keys[_mid+1] ) } break; case 2: -// line 769 "Parser.rl" +// line 786 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 2050 "Parser.java" +// line 2067 "Parser.java" } } } @@ -2066,7 +2083,7 @@ else if ( data[p] > _JSON_object_trans_keys[_mid+1] ) break; } } -// line 806 "Parser.rl" +// line 823 "Parser.rl" if (cs < JSON_object_first_final) { res.update(null, p + 1); @@ -2090,6 +2107,9 @@ else if ( data[p] > _JSON_object_trans_keys[_mid+1] ) callMethod(context, "deep_const_get", vKlassName); if (klass.respondsTo("json_creatable?") && klass.callMethod(context, "json_creatable?").isTrue()) { + if (parser.deprecatedCreateAdditions) { + klass.getRuntime().getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); + } returnedResult = klass.callMethod(context, "json_create", result); } @@ -2099,7 +2119,7 @@ else if ( data[p] > _JSON_object_trans_keys[_mid+1] ) } -// line 2103 "Parser.java" +// line 2123 "Parser.java" private static byte[] init__JSON_actions_0() { return new byte [] { @@ -2202,7 +2222,7 @@ private static byte[] init__JSON_trans_actions_0() static final int JSON_en_main = 1; -// line 857 "Parser.rl" +// line 877 "Parser.rl" public IRubyObject parseImplemetation() { @@ -2212,16 +2232,16 @@ public IRubyObject parseImplemetation() { ParserResult res = new ParserResult(); -// line 2216 "Parser.java" +// line 2236 "Parser.java" { cs = JSON_start; } -// line 866 "Parser.rl" +// line 886 "Parser.rl" p = byteList.begin(); pe = p + byteList.length(); -// line 2225 "Parser.java" +// line 2245 "Parser.java" { int _klen; int _trans = 0; @@ -2302,7 +2322,7 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) switch ( _JSON_actions[_acts++] ) { case 0: -// line 843 "Parser.rl" +// line 863 "Parser.rl" { parseValue(res, p, pe); if (res.result == null) { @@ -2314,7 +2334,7 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) } } break; -// line 2318 "Parser.java" +// line 2338 "Parser.java" } } } @@ -2334,7 +2354,7 @@ else if ( data[p] > _JSON_trans_keys[_mid+1] ) break; } } -// line 869 "Parser.rl" +// line 889 "Parser.rl" if (cs >= JSON_first_final && p == pe) { return result; diff --git a/java/src/json/ext/Parser.rl b/java/src/json/ext/Parser.rl index 0fd6d796..2173105f 100644 --- a/java/src/json/ext/Parser.rl +++ b/java/src/json/ext/Parser.rl @@ -49,6 +49,7 @@ public class Parser extends RubyObject { private RubyString vSource; private RubyString createId; private boolean createAdditions; + private boolean deprecatedCreateAdditions; private int maxNesting; private boolean allowNaN; private boolean symbolizeNames; @@ -169,7 +170,20 @@ public class Parser extends RubyObject { this.symbolizeNames = opts.getBool("symbolize_names", false); this.freeze = opts.getBool("freeze", false); this.createId = opts.getString("create_id", getCreateId(context)); - this.createAdditions = opts.getBool("create_additions", false); + + IRubyObject additions = opts.get("create_additions"); + this.createAdditions = false; + this.deprecatedCreateAdditions = false; + + if (additions != null) { + if (additions.isNil()) { + this.createAdditions = true; + this.deprecatedCreateAdditions = true; + } else { + this.createAdditions = opts.getBool("create_additions", false); + } + } + this.objectClass = opts.getClass("object_class", runtime.getHash()); this.arrayClass = opts.getClass("array_class", runtime.getArray()); this.decimalClass = opts.getClass("decimal_class", null); @@ -641,6 +655,9 @@ public class Parser extends RubyObject { RubyClass klass = (RubyClass) memoArray[1]; if (klass.respondsTo("json_creatable?") && klass.callMethod(context, "json_creatable?").isTrue()) { + if (parser.deprecatedCreateAdditions) { + klass.getRuntime().getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); + } result = klass.callMethod(context, "json_create", result); } } @@ -826,6 +843,9 @@ public class Parser extends RubyObject { callMethod(context, "deep_const_get", vKlassName); if (klass.respondsTo("json_creatable?") && klass.callMethod(context, "json_creatable?").isTrue()) { + if (parser.deprecatedCreateAdditions) { + klass.getRuntime().getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); + } returnedResult = klass.callMethod(context, "json_create", result); } diff --git a/lib/json/common.rb b/lib/json/common.rb index 182bb1c0..d35d5033 100644 --- a/lib/json/common.rb +++ b/lib/json/common.rb @@ -49,18 +49,7 @@ def parser=(parser) # :nodoc: # level (absolute namespace path?). If there doesn't exist a constant at # the given path, an ArgumentError is raised. def deep_const_get(path) # :nodoc: - path.to_s.split(/::/).inject(Object) do |p, c| - case - when c.empty? then p - when p.const_defined?(c, true) then p.const_get(c) - else - begin - p.const_missing(c) - rescue NameError => e - raise ArgumentError, "can't get const #{path}: #{e}" - end - end - end + Object.const_get(path) end # Set the module _generator_ to be used by JSON. @@ -69,7 +58,7 @@ def generator=(generator) # :nodoc: @generator = generator generator_methods = generator::GeneratorMethods for const in generator_methods.constants - klass = deep_const_get(const) + klass = const_get(const) modul = generator_methods.const_get(const) klass.class_eval do instance_methods(false).each do |m| @@ -399,6 +388,20 @@ def pretty_generate(obj, opts = nil) module_function :pretty_unparse # :startdoc: + class << self + # Sets or returns default options for the JSON.unsafe_load method. + # Initially: + # opts = JSON.load_default_options + # opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true} + attr_accessor :unsafe_load_default_options + end + self.unsafe_load_default_options = { + :max_nesting => false, + :allow_nan => true, + :allow_blank => true, + :create_additions => true, + } + class << self # Sets or returns default options for the JSON.load method. # Initially: @@ -407,11 +410,162 @@ class << self attr_accessor :load_default_options end self.load_default_options = { - :max_nesting => false, :allow_nan => true, :allow_blank => true, - :create_additions => true, + :create_additions => nil, } + # :call-seq: + # JSON.unsafe_load(source, proc = nil, options = {}) -> object + # + # Returns the Ruby objects created by parsing the given +source+. + # + # - Argument +source+ must be, or be convertible to, a \String: + # - If +source+ responds to instance method +to_str+, + # source.to_str becomes the source. + # - If +source+ responds to instance method +to_io+, + # source.to_io.read becomes the source. + # - If +source+ responds to instance method +read+, + # source.read becomes the source. + # - If both of the following are true, source becomes the \String 'null': + # - Option +allow_blank+ specifies a truthy value. + # - The source, as defined above, is +nil+ or the empty \String ''. + # - Otherwise, +source+ remains the source. + # - Argument +proc+, if given, must be a \Proc that accepts one argument. + # It will be called recursively with each result (depth-first order). + # See details below. + # BEWARE: This method is meant to serialise data from trusted user input, + # like from your own database server or clients under your control, it could + # be dangerous to allow untrusted users to pass JSON sources into it. + # - Argument +opts+, if given, contains a \Hash of options for the parsing. + # See {Parsing Options}[#module-JSON-label-Parsing+Options]. + # The default options can be changed via method JSON.unsafe_load_default_options=. + # + # --- + # + # When no +proc+ is given, modifies +source+ as above and returns the result of + # parse(source, opts); see #parse. + # + # Source for following examples: + # source = <<~JSON + # { + # "name": "Dave", + # "age" :40, + # "hats": [ + # "Cattleman's", + # "Panama", + # "Tophat" + # ] + # } + # JSON + # + # Load a \String: + # ruby = JSON.unsafe_load(source) + # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]} + # + # Load an \IO object: + # require 'stringio' + # object = JSON.unsafe_load(StringIO.new(source)) + # object # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]} + # + # Load a \File object: + # path = 't.json' + # File.write(path, source) + # File.open(path) do |file| + # JSON.unsafe_load(file) + # end # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]} + # + # --- + # + # When +proc+ is given: + # - Modifies +source+ as above. + # - Gets the +result+ from calling parse(source, opts). + # - Recursively calls proc(result). + # - Returns the final result. + # + # Example: + # require 'json' + # + # # Some classes for the example. + # class Base + # def initialize(attributes) + # @attributes = attributes + # end + # end + # class User < Base; end + # class Account < Base; end + # class Admin < Base; end + # # The JSON source. + # json = <<-EOF + # { + # "users": [ + # {"type": "User", "username": "jane", "email": "jane@example.com"}, + # {"type": "User", "username": "john", "email": "john@example.com"} + # ], + # "accounts": [ + # {"account": {"type": "Account", "paid": true, "account_id": "1234"}}, + # {"account": {"type": "Account", "paid": false, "account_id": "1235"}} + # ], + # "admins": {"type": "Admin", "password": "0wn3d"} + # } + # EOF + # # Deserializer method. + # def deserialize_obj(obj, safe_types = %w(User Account Admin)) + # type = obj.is_a?(Hash) && obj["type"] + # safe_types.include?(type) ? Object.const_get(type).new(obj) : obj + # end + # # Call to JSON.unsafe_load + # ruby = JSON.unsafe_load(json, proc {|obj| + # case obj + # when Hash + # obj.each {|k, v| obj[k] = deserialize_obj v } + # when Array + # obj.map! {|v| deserialize_obj v } + # end + # }) + # pp ruby + # Output: + # {"users"=> + # [#"User", "username"=>"jane", "email"=>"jane@example.com"}>, + # #"User", "username"=>"john", "email"=>"john@example.com"}>], + # "accounts"=> + # [{"account"=> + # #"Account", "paid"=>true, "account_id"=>"1234"}>}, + # {"account"=> + # #"Account", "paid"=>false, "account_id"=>"1235"}>}], + # "admins"=> + # #"Admin", "password"=>"0wn3d"}>} + # + def unsafe_load(source, proc = nil, options = nil) + opts = if options.nil? + unsafe_load_default_options + else + unsafe_load_default_options.merge(options) + end + + unless source.is_a?(String) + if source.respond_to? :to_str + source = source.to_str + elsif source.respond_to? :to_io + source = source.to_io.read + elsif source.respond_to?(:read) + source = source.read + end + end + + if opts[:allow_blank] && (source.nil? || source.empty?) + source = 'null' + end + result = parse(source, opts) + recurse_proc(result, &proc) if proc + result + end # :call-seq: # JSON.load(source, proc = nil, options = {}) -> object @@ -435,6 +589,7 @@ class << self # BEWARE: This method is meant to serialise data from trusted user input, # like from your own database server or clients under your control, it could # be dangerous to allow untrusted users to pass JSON sources into it. + # If you must use it, use JSON.unsafe_load instead to make it clear. # - Argument +opts+, if given, contains a \Hash of options for the parsing. # See {Parsing Options}[#module-JSON-label-Parsing+Options]. # The default options can be changed via method JSON.load_default_options=. diff --git a/lib/json/pure/parser.rb b/lib/json/pure/parser.rb index 1afd8e46..49472bd7 100644 --- a/lib/json/pure/parser.rb +++ b/lib/json/pure/parser.rb @@ -90,11 +90,14 @@ def initialize(source, opts = nil) @allow_nan = !!opts[:allow_nan] @symbolize_names = !!opts[:symbolize_names] @freeze = !!opts[:freeze] - if opts.key?(:create_additions) - @create_additions = !!opts[:create_additions] - else - @create_additions = false + + @deprecated_create_additions = false + @create_additions = opts.fetch(:create_additions, false) + if @create_additions.nil? + @create_additions = true + @deprecated_create_additions = true end + @symbolize_names && @create_additions and raise ArgumentError, 'options :symbolize_names and :create_additions cannot be used '\ 'in conjunction' @@ -185,8 +188,13 @@ def parse_string if @create_additions and @match_string for (regexp, klass) in @match_string - klass.json_creatable? or next - string =~ regexp and return klass.json_create(string) + if klass.json_creatable? and string.match?(regexp) + if @deprecated_create_additions + warn "JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`" + end + + return klass.json_create(string) + end end end string @@ -308,8 +316,11 @@ def parse_object raise ParserError, "expected next name, value pair in object at '#{peek(20)}'!" end if @create_additions and klassname = result[@create_id] - klass = JSON.deep_const_get klassname + klass = Object.const_get(klassname) break unless klass and klass.json_creatable? + if @deprecated_create_additions + warn "JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`" + end result = klass.json_create(result) end break diff --git a/test/json/json_addition_test.rb b/test/json/json_addition_test.rb index 8c3fbda5..d78ae55c 100644 --- a/test/json/json_addition_test.rb +++ b/test/json/json_addition_test.rb @@ -162,6 +162,12 @@ def test_core assert_equal(/foo/i, JSON(JSON(/foo/i), :create_additions => true)) end + def test_deprecated_load_create_additions + assert_warning(/use JSON\.unsafe_load/) do + JSON.load(JSON.dump(Time.now)) + end + end + def test_utc_datetime now = Time.now d = DateTime.parse(now.to_s) # usual case diff --git a/test/json/json_common_interface_test.rb b/test/json/json_common_interface_test.rb index e552412b..6c2f4bfc 100644 --- a/test/json/json_common_interface_test.rb +++ b/test/json/json_common_interface_test.rb @@ -67,11 +67,6 @@ def test_create_id JSON.create_id = 'json_class' end - def test_deep_const_get - assert_raise(ArgumentError) { JSON.deep_const_get('Nix::Da') } - assert_equal File::SEPARATOR, JSON.deep_const_get('File::SEPARATOR') - end - def test_parse assert_equal [ 1, 2, 3, ], JSON.parse('[ 1, 2, 3 ]') end diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 01e412c9..68d3e970 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -280,7 +280,7 @@ def test_gc end EOS end - end if GC.respond_to?(:stress=) + end if GC.respond_to?(:stress=) && RUBY_ENGINE == "ruby" def test_configure_using_configure_and_merge numbered_state = {