oj 3.16.3 β†’ 3.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a6b15904386e7010fa071ecc27b8f3a6d5023c8162ea7042fc39e29c373a20a
4
- data.tar.gz: b862e59cff0ba3a15eadec34fe807d5893c9c1d1e9fcce01ced66ab28e642181
3
+ metadata.gz: bd443226b6fbe558bd4a01e5e4e2c150149e697c22c1676007f67923b9832682
4
+ data.tar.gz: 0a46ac536c4fccaeac2bddde9ff8ef8d50343f5eeff3455b36c5bfb762aef073
5
5
  SHA512:
6
- metadata.gz: f186fb40ebcc3792b4949f81ce3b2efb22bee9dce9b44785eeca37cc27e25b79e8afa4fc5872232001d28a3d0b279fc222d5f7d2f8aaa091fe3a135246139b64
7
- data.tar.gz: 9c1536bfc15eb3fe02e92d803417ef1ccce5084036aaf8df5ae2b61995a429ab8ae00be5e9b7c79e11839d781c67523ed5bc534df36a40b59dcc61db6551158f
6
+ metadata.gz: 49e28b9151a1c84d9f97adcea6af90acd1141a194858f29e633bf5e2e1965bb5fab783c015e343f569692e6351ad3957059135f05eb50022b79ee799ae0a306a
7
+ data.tar.gz: 74f69e8e14c39aee7128f8b7dc23fa1fa60a8df383dec844257e67c6e7f34c58d430ac77bc06571e8d52a29f857ee0162cba8ad1e93ac8bc1f7c2ad0260a0002
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 3.16.4 - 2024-06-08
4
+
5
+ - Fixed Oj::Parse EOF issue on larger stream input.
6
+
3
7
  ## 3.16.3 - 2023-12-11
4
8
 
5
9
  - Fixed the gemspec to allow earlier versions of the bigdecimal gem.
data/ext/oj/dump.c CHANGED
@@ -198,7 +198,18 @@ inline static long rails_xss_friendly_size(const uint8_t *str, size_t len) {
198
198
  }
199
199
 
200
200
  inline static size_t rails_friendly_size(const uint8_t *str, size_t len) {
201
- return calculate_string_size(str, len, rails_friendly_chars);
201
+ long size = 0;
202
+ size_t i = len;
203
+ uint8_t hi = 0;
204
+
205
+ for (; 0 < i; str++, i--) {
206
+ size += rails_friendly_chars[*str];
207
+ hi |= *str & 0x80;
208
+ }
209
+ if (0 == hi) {
210
+ return size - len * (size_t)'0';
211
+ }
212
+ return -(size - len * (size_t)'0');
202
213
  }
203
214
 
204
215
  const char *oj_nan_str(VALUE obj, int opt, int mode, bool plus, int *lenp) {
@@ -750,8 +761,9 @@ void oj_dump_raw_json(VALUE obj, int depth, Out out) {
750
761
  void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out out) {
751
762
  size_t size;
752
763
  char *cmap;
753
- const char *orig = str;
754
- bool has_hi = false;
764
+ const char *orig = str;
765
+ bool has_hi = false;
766
+ bool do_unicode_validation = false;
755
767
 
756
768
  switch (out->opts->escape_mode) {
757
769
  case NLEsc:
@@ -772,8 +784,9 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
772
784
  size = xss_friendly_size((uint8_t *)str, cnt);
773
785
  break;
774
786
  case JXEsc:
775
- cmap = hixss_friendly_chars;
776
- size = hixss_friendly_size((uint8_t *)str, cnt);
787
+ cmap = hixss_friendly_chars;
788
+ size = hixss_friendly_size((uint8_t *)str, cnt);
789
+ do_unicode_validation = true;
777
790
  break;
778
791
  case RailsXEsc: {
779
792
  long sz;
@@ -786,12 +799,22 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
786
799
  } else {
787
800
  size = (size_t)sz;
788
801
  }
802
+ do_unicode_validation = true;
789
803
  break;
790
804
  }
791
- case RailsEsc:
805
+ case RailsEsc: {
806
+ long sz;
792
807
  cmap = rails_friendly_chars;
793
- size = rails_friendly_size((uint8_t *)str, cnt);
808
+ sz = rails_friendly_size((uint8_t *)str, cnt);
809
+ if (sz < 0) {
810
+ has_hi = true;
811
+ size = (size_t)-sz;
812
+ } else {
813
+ size = (size_t)sz;
814
+ }
815
+ do_unicode_validation = true;
794
816
  break;
817
+ }
795
818
  case JSONEsc:
796
819
  default: cmap = hibit_friendly_chars; size = hibit_friendly_size((uint8_t *)str, cnt);
797
820
  }
@@ -822,7 +845,7 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
822
845
  for (; str < end; str++) {
823
846
  switch (cmap[(uint8_t)*str]) {
824
847
  case '1':
825
- if ((JXEsc == out->opts->escape_mode || RailsXEsc == out->opts->escape_mode) && check_start <= str) {
848
+ if (do_unicode_validation && check_start <= str) {
826
849
  if (0 != (0x80 & (uint8_t)*str)) {
827
850
  if (0xC0 == (0xC0 & (uint8_t)*str)) {
828
851
  check_start = check_unicode(str, end, orig);
@@ -846,8 +869,7 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
846
869
  }
847
870
  break;
848
871
  case '3': // Unicode
849
- if (0xe2 == (uint8_t)*str && (JXEsc == out->opts->escape_mode || RailsXEsc == out->opts->escape_mode) &&
850
- 2 <= end - str) {
872
+ if (0xe2 == (uint8_t)*str && do_unicode_validation && 2 <= end - str) {
851
873
  if (0x80 == (uint8_t)str[1] && (0xa8 == (uint8_t)str[2] || 0xa9 == (uint8_t)str[2])) {
852
874
  str = dump_unicode(str, end, out, orig);
853
875
  } else {
@@ -866,8 +888,7 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
866
888
  APPEND_CHARS(out->cur, "\\u00", 4);
867
889
  dump_hex((uint8_t)*str, out);
868
890
  } else {
869
- if (0xe2 == (uint8_t)*str &&
870
- (JXEsc == out->opts->escape_mode || RailsXEsc == out->opts->escape_mode) && 2 <= end - str) {
891
+ if (0xe2 == (uint8_t)*str && do_unicode_validation && 2 <= end - str) {
871
892
  if (0x80 == (uint8_t)str[1] && (0xa8 == (uint8_t)str[2] || 0xa9 == (uint8_t)str[2])) {
872
893
  str = dump_unicode(str, end, out, orig);
873
894
  } else {
@@ -884,8 +905,7 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
884
905
  }
885
906
  *out->cur++ = '"';
886
907
  }
887
- if ((JXEsc == out->opts->escape_mode || RailsXEsc == out->opts->escape_mode) && 0 < str - orig &&
888
- 0 != (0x80 & *(str - 1))) {
908
+ if (do_unicode_validation && 0 < str - orig && 0 != (0x80 & *(str - 1))) {
889
909
  uint8_t c = (uint8_t) * (str - 1);
890
910
  int i;
891
911
  int scnt = (int)(str - orig);
data/ext/oj/extconf.rb CHANGED
@@ -29,10 +29,9 @@ dflags = {
29
29
  have_func('rb_gc_mark_movable')
30
30
  have_func('stpcpy')
31
31
  have_func('pthread_mutex_init')
32
+ have_func('getrlimit', 'sys/resource.h')
32
33
  have_func('rb_enc_interned_str')
33
34
  have_func('rb_ext_ractor_safe', 'ruby.h')
34
- # rb_hash_bulk_insert is deep down in a header not included in normal build and that seems to fool have_func.
35
- have_func('rb_hash_bulk_insert', 'ruby.h') unless '2' == version[0] && '6' == version[1]
36
35
 
37
36
  dflags['OJ_DEBUG'] = true unless ENV['OJ_DEBUG'].nil?
38
37
 
data/ext/oj/fast.c CHANGED
@@ -40,7 +40,7 @@ typedef struct _doc {
40
40
  Leaf *where; // points to current location
41
41
  Leaf where_path[MAX_STACK]; // points to head of path
42
42
  char *json;
43
- unsigned long size; // number of leaves/branches in the doc
43
+ unsigned long size; // number of leaves/branches in the doc
44
44
  VALUE self;
45
45
  Batch batches;
46
46
  struct _batch batch0;
@@ -573,7 +573,7 @@ static char *read_quoted_value(ParseInfo pi) {
573
573
  char *h = pi->s; // head
574
574
  char *t = h; // tail
575
575
 
576
- h++; // skip quote character
576
+ h++; // skip quote character
577
577
  t++;
578
578
  value = h;
579
579
  for (; '"' != *h; h++, t++) {
@@ -765,7 +765,7 @@ static VALUE parse_json(VALUE clas, char *json, bool given) {
765
765
  pi.s = pi.str;
766
766
  doc_init(doc);
767
767
  pi.doc = doc;
768
- #if IS_WINDOWS
768
+ #if IS_WINDOWS || !defined(HAVE_GETRLIMIT)
769
769
  // assume a 1M stack and give half to ruby
770
770
  pi.stack_min = (void *)((char *)&pi - (512L * 1024L));
771
771
  #else
data/ext/oj/oj.c CHANGED
@@ -36,6 +36,7 @@ ID oj_as_json_id;
36
36
  ID oj_begin_id;
37
37
  ID oj_bigdecimal_id;
38
38
  ID oj_end_id;
39
+ ID oj_eofq_id;
39
40
  ID oj_exclude_end_id;
40
41
  ID oj_error_id;
41
42
  ID oj_file_id;
@@ -1849,6 +1850,7 @@ void Init_oj(void) {
1849
1850
  oj_begin_id = rb_intern("begin");
1850
1851
  oj_bigdecimal_id = rb_intern("BigDecimal");
1851
1852
  oj_end_id = rb_intern("end");
1853
+ oj_eofq_id = rb_intern("eof?");
1852
1854
  oj_error_id = rb_intern("error");
1853
1855
  oj_exclude_end_id = rb_intern("exclude_end?");
1854
1856
  oj_file_id = rb_intern("file?");
data/ext/oj/oj.h CHANGED
@@ -334,6 +334,7 @@ extern ID oj_as_json_id;
334
334
  extern ID oj_begin_id;
335
335
  extern ID oj_bigdecimal_id;
336
336
  extern ID oj_end_id;
337
+ extern ID oj_eofq_id;
337
338
  extern ID oj_error_id;
338
339
  extern ID oj_exclude_end_id;
339
340
  extern ID oj_file_id;
data/ext/oj/parser.c CHANGED
@@ -1114,9 +1114,6 @@ static void parse(ojParser p, const byte *json) {
1114
1114
  p->map = trail_map;
1115
1115
  }
1116
1116
  }
1117
- if (0 < p->depth) {
1118
- parse_error(p, "parse error, not closed");
1119
- }
1120
1117
  if (0 == p->depth) {
1121
1118
  switch (p->map[256]) {
1122
1119
  case '0':
@@ -1394,6 +1391,12 @@ static VALUE load(VALUE self) {
1394
1391
  if (0 < RSTRING_LEN(rbuf)) {
1395
1392
  parse(p, (byte *)StringValuePtr(rbuf));
1396
1393
  }
1394
+ if (Qtrue == rb_funcall(p->reader, oj_eofq_id, 0)) {
1395
+ if (0 < p->depth) {
1396
+ parse_error(p, "parse error, not closed");
1397
+ }
1398
+ break;
1399
+ }
1397
1400
  }
1398
1401
  return Qtrue;
1399
1402
  }
data/ext/oj/parser.h CHANGED
@@ -32,9 +32,9 @@ typedef struct _num {
32
32
  long double dub;
33
33
  int64_t fixnum; // holds all digits
34
34
  uint32_t len;
35
- int16_t div; // 10^div
35
+ int16_t div; // 10^div
36
36
  int16_t exp;
37
- uint8_t shift; // shift of fixnum to get decimal
37
+ uint8_t shift; // shift of fixnum to get decimal
38
38
  bool neg;
39
39
  bool exp_neg;
40
40
  // for numbers as strings, reuse buf
data/ext/oj/reader.c CHANGED
@@ -101,7 +101,7 @@ int oj_reader_read(Reader reader) {
101
101
  } else {
102
102
  shift = reader->pro - reader->head - 1; // leave one character so we can backup one
103
103
  }
104
- if (0 >= shift) { /* no space left so allocate more */
104
+ if (0 >= shift) { /* no space left so allocate more */
105
105
  const char *old = reader->head;
106
106
  size_t size = reader->end - reader->head + BUF_PAD;
107
107
 
data/ext/oj/saj.c CHANGED
@@ -578,7 +578,7 @@ static void saj_parse(VALUE handler, char *json) {
578
578
  /* initialize parse info */
579
579
  pi.str = json;
580
580
  pi.s = json;
581
- #if IS_WINDOWS
581
+ #if IS_WINDOWS || !defined(HAVE_GETRLIMIT)
582
582
  pi.stack_min = (void *)((char *)&obj - (512L * 1024L)); /* assume a 1M stack and give half to ruby */
583
583
  #else
584
584
  {
@@ -587,7 +587,7 @@ static void saj_parse(VALUE handler, char *json) {
587
587
  if (0 == getrlimit(RLIMIT_STACK, &lim) && RLIM_INFINITY != lim.rlim_cur) {
588
588
  pi.stack_min = (void *)((char *)&obj - (lim.rlim_cur / 4 * 3)); /* let 3/4ths of the stack be used only */
589
589
  } else {
590
- pi.stack_min = 0; /* indicates not to check stack limit */
590
+ pi.stack_min = 0; /* indicates not to check stack limit */
591
591
  }
592
592
  }
593
593
  #endif
data/ext/oj/usual.c CHANGED
@@ -281,7 +281,6 @@ static void close_object(ojParser p) {
281
281
  VALUE *head = d->vhead + c->vi + 1;
282
282
  volatile VALUE obj = rb_hash_new();
283
283
 
284
- #if HAVE_RB_HASH_BULK_INSERT
285
284
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
286
285
  *vp = d->get_key(p, kp);
287
286
  if (sizeof(kp->buf) <= (size_t)kp->len) {
@@ -289,14 +288,6 @@ static void close_object(ojParser p) {
289
288
  }
290
289
  }
291
290
  rb_hash_bulk_insert(d->vtail - head, head, obj);
292
- #else
293
- for (vp = head; kp < d->ktail; kp++, vp += 2) {
294
- rb_hash_aset(obj, d->get_key(p, kp), *(vp + 1));
295
- if (sizeof(kp->buf) <= (size_t)kp->len) {
296
- OJ_R_FREE(kp->key);
297
- }
298
- }
299
- #endif
300
291
  d->ktail = d->khead + c->ki;
301
292
  d->vtail = head;
302
293
  head--;
@@ -341,7 +332,6 @@ static void close_object_create(ojParser p) {
341
332
  head++;
342
333
  if (Qnil == d->hash_class) {
343
334
  obj = rb_hash_new();
344
- #if HAVE_RB_HASH_BULK_INSERT
345
335
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
346
336
  *vp = d->get_key(p, kp);
347
337
  if (sizeof(kp->buf) <= (size_t)kp->len) {
@@ -349,14 +339,6 @@ static void close_object_create(ojParser p) {
349
339
  }
350
340
  }
351
341
  rb_hash_bulk_insert(d->vtail - head, head, obj);
352
- #else
353
- for (vp = head; kp < d->ktail; kp++, vp += 2) {
354
- rb_hash_aset(obj, d->get_key(p, kp), *(vp + 1));
355
- if (sizeof(kp->buf) <= (size_t)kp->len) {
356
- OJ_R_FREE(kp->key);
357
- }
358
- }
359
- #endif
360
342
  } else {
361
343
  obj = rb_class_new_instance(0, NULL, d->hash_class);
362
344
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
@@ -373,7 +355,6 @@ static void close_object_create(ojParser p) {
373
355
  if (!d->ignore_json_create && rb_respond_to(clas, oj_json_create_id)) {
374
356
  volatile VALUE arg = rb_hash_new();
375
357
 
376
- #if HAVE_RB_HASH_BULK_INSERT
377
358
  for (vp = head; kp < d->ktail; kp++, vp += 2) {
378
359
  *vp = d->get_key(p, kp);
379
360
  if (sizeof(kp->buf) <= (size_t)kp->len) {
@@ -381,14 +362,6 @@ static void close_object_create(ojParser p) {
381
362
  }
382
363
  }
383
364
  rb_hash_bulk_insert(d->vtail - head, head, arg);
384
- #else
385
- for (vp = head; kp < d->ktail; kp++, vp += 2) {
386
- rb_hash_aset(arg, d->get_key(p, kp), *(vp + 1));
387
- if (sizeof(kp->buf) <= (size_t)kp->len) {
388
- OJ_R_FREE(kp->key);
389
- }
390
- }
391
- #endif
392
365
  obj = rb_funcall(clas, oj_json_create_id, 1, arg);
393
366
  } else {
394
367
  obj = rb_class_new_instance(0, NULL, clas);
data/lib/oj/schandler.rb CHANGED
@@ -64,13 +64,14 @@ module Oj
64
64
  #
65
65
  # hash_end
66
66
  #
67
- # When a hash key is encountered the hash_key method is called with the parsed
68
- # hash value key. The return value from the call is then used as the key in
69
- # the key-value pair that follows.
67
+ # At the end of a JSON object element the hash_end() callback is called if
68
+ # public.
70
69
  #
71
70
  # hash_key
72
71
  #
73
- # At the end of a JSON object element the hash_end() callback is called if public.
72
+ # When a hash key is encountered the hash_key() method is called with the
73
+ # parsed hash value key. The return value from the call is then used as the
74
+ # key in the key-value pair that follows.
74
75
  #
75
76
  # hash_set
76
77
  #
data/lib/oj/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Oj
2
2
  # Current version of the module.
3
- VERSION = '3.16.3'
3
+ VERSION = '3.16.4'
4
4
  end
@@ -74,42 +74,77 @@ class TestJSONEncoding < ActiveSupport::TestCase
74
74
  ActiveSupport.escape_html_entities_in_json = false
75
75
  end
76
76
 
77
- def test_utf8_string_encoded_properly
78
- # The original test seems to expect that
79
- # ActiveSupport.escape_html_entities_in_json reverts to true even after
80
- # being set to false. I haven't been able to figure that out so the value is
81
- # set to true, the default, before running the test. This might be wrong but
82
- # for now it will have to do.
83
- ActiveSupport.escape_html_entities_in_json = true
84
- result = ActiveSupport::JSON.encode("€2.99")
85
- assert_equal '"€2.99"', result
86
- assert_equal(Encoding::UTF_8, result.encoding)
87
-
88
- result = ActiveSupport::JSON.encode("✎☺")
89
- assert_equal '"✎☺"', result
90
- assert_equal(Encoding::UTF_8, result.encoding)
77
+ def test_hash_keys_encoding_without_escaping
78
+ assert_equal "{\"<>\":\"<>\"}", ActiveSupport::JSON.encode("<>" => "<>")
91
79
  end
92
80
 
93
- def test_non_utf8_string_transcodes
94
- s = "二".encode("Shift_JIS")
95
- result = ActiveSupport::JSON.encode(s)
96
- assert_equal '"二"', result
97
- assert_equal Encoding::UTF_8, result.encoding
81
+ module UnicodeTests
82
+ def test_utf8_string_encoded_properly
83
+ result = ActiveSupport::JSON.encode("€2.99")
84
+ assert_equal '"€2.99"', result
85
+ assert_equal(Encoding::UTF_8, result.encoding)
86
+
87
+ result = ActiveSupport::JSON.encode("✎☺")
88
+ assert_equal '"✎☺"', result
89
+ assert_equal(Encoding::UTF_8, result.encoding)
90
+ end
91
+
92
+ def test_non_utf8_string_transcodes
93
+ s = "二".encode("Shift_JIS")
94
+ result = ActiveSupport::JSON.encode(s)
95
+ assert_equal '"二"', result
96
+ assert_equal Encoding::UTF_8, result.encoding
97
+ end
98
+
99
+ def test_wide_utf8_chars
100
+ w = "𠜎"
101
+ result = ActiveSupport::JSON.encode(w)
102
+ assert_equal '"𠜎"', result
103
+ end
104
+
105
+ def test_wide_utf8_roundtrip
106
+ hash = { string: "𐒑" }
107
+ json = ActiveSupport::JSON.encode(hash)
108
+ decoded_hash = ActiveSupport::JSON.decode(json)
109
+ assert_equal "𐒑", decoded_hash["string"]
110
+ end
111
+
112
+ def test_invalid_encoding_raises
113
+ s = "\xAE\xFF\x9F"
114
+ refute s.valid_encoding?
115
+
116
+ # n.b. this raises EncodingError, because we didn't call Oj.mimic_JSON in the test setup; but,
117
+ # if you do that (even indirectly through Oj.optimize_rails), then this raises a
118
+ # JSON::GeneratorError instead of an EncodingError.
119
+ assert_raises(EncodingError) do
120
+ ActiveSupport::JSON.encode([s])
121
+ end
122
+ end
98
123
  end
99
124
 
100
- def test_wide_utf8_chars
101
- w = "𠜎"
102
- result = ActiveSupport::JSON.encode(w)
103
- assert_equal '"𠜎"', result
125
+ module UnicodeTestsWithEscapingOn
126
+ def setup
127
+ ActiveSupport.escape_html_entities_in_json = true
128
+ end
129
+
130
+ def teardown
131
+ ActiveSupport.escape_html_entities_in_json = false
132
+ end
133
+
134
+ include UnicodeTests
104
135
  end
105
136
 
106
- def test_wide_utf8_roundtrip
107
- hash = { string: "𐒑" }
108
- json = ActiveSupport::JSON.encode(hash)
109
- decoded_hash = ActiveSupport::JSON.decode(json)
110
- assert_equal "𐒑", decoded_hash["string"]
137
+ module UnicodeTestsWithEscapingOff
138
+ def setup
139
+ ActiveSupport.escape_html_entities_in_json = false
140
+ end
141
+
142
+ include UnicodeTests
111
143
  end
112
144
 
145
+ include UnicodeTestsWithEscapingOn
146
+ include UnicodeTestsWithEscapingOff
147
+
113
148
  def test_hash_key_identifiers_are_always_quoted
114
149
  values = { 0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B" }
115
150
  assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
@@ -8,9 +8,18 @@ require "active_support/time"
8
8
  require_relative "time_zone_test_helpers"
9
9
  require_relative "encoding_test_cases"
10
10
 
11
+ require 'oj'
12
+ # Sets the ActiveSupport encoder to be Oj and also wraps the setting of globals.
13
+ Oj::Rails.set_encoder()
14
+ Oj::Rails.optimize()
15
+
11
16
  class TestJSONEncoding < ActiveSupport::TestCase
12
17
  include TimeZoneTestHelpers
13
18
 
19
+ def test_is_actually_oj
20
+ assert_equal Oj::Rails::Encoder, ActiveSupport.json_encoder
21
+ end
22
+
14
23
  def sorted_json(json)
15
24
  if json.start_with?("{") && json.end_with?("}")
16
25
  "{" + json[1..-2].split(",").sort.join(",") + "}"
@@ -61,36 +70,77 @@ class TestJSONEncoding < ActiveSupport::TestCase
61
70
  ActiveSupport.escape_html_entities_in_json = false
62
71
  end
63
72
 
64
- def test_utf8_string_encoded_properly
65
- result = ActiveSupport::JSON.encode("€2.99")
66
- assert_equal '"€2.99"', result
67
- assert_equal(Encoding::UTF_8, result.encoding)
68
-
69
- result = ActiveSupport::JSON.encode("✎☺")
70
- assert_equal '"✎☺"', result
71
- assert_equal(Encoding::UTF_8, result.encoding)
73
+ def test_hash_keys_encoding_without_escaping
74
+ assert_equal "{\"<>\":\"<>\"}", ActiveSupport::JSON.encode("<>" => "<>")
72
75
  end
73
76
 
74
- def test_non_utf8_string_transcodes
75
- s = "二".encode("Shift_JIS")
76
- result = ActiveSupport::JSON.encode(s)
77
- assert_equal '"二"', result
78
- assert_equal Encoding::UTF_8, result.encoding
77
+ module UnicodeTests
78
+ def test_utf8_string_encoded_properly
79
+ result = ActiveSupport::JSON.encode("€2.99")
80
+ assert_equal '"€2.99"', result
81
+ assert_equal(Encoding::UTF_8, result.encoding)
82
+
83
+ result = ActiveSupport::JSON.encode("✎☺")
84
+ assert_equal '"✎☺"', result
85
+ assert_equal(Encoding::UTF_8, result.encoding)
86
+ end
87
+
88
+ def test_non_utf8_string_transcodes
89
+ s = "二".encode("Shift_JIS")
90
+ result = ActiveSupport::JSON.encode(s)
91
+ assert_equal '"二"', result
92
+ assert_equal Encoding::UTF_8, result.encoding
93
+ end
94
+
95
+ def test_wide_utf8_chars
96
+ w = "𠜎"
97
+ result = ActiveSupport::JSON.encode(w)
98
+ assert_equal '"𠜎"', result
99
+ end
100
+
101
+ def test_wide_utf8_roundtrip
102
+ hash = { string: "𐒑" }
103
+ json = ActiveSupport::JSON.encode(hash)
104
+ decoded_hash = ActiveSupport::JSON.decode(json)
105
+ assert_equal "𐒑", decoded_hash["string"]
106
+ end
107
+
108
+ def test_invalid_encoding_raises
109
+ s = "\xAE\xFF\x9F"
110
+ refute s.valid_encoding?
111
+
112
+ # n.b. this raises EncodingError, because we didn't call Oj.mimic_JSON in the test setup; but,
113
+ # if you do that (even indirectly through Oj.optimize_rails), then this raises a
114
+ # JSON::GeneratorError instead of an EncodingError.
115
+ assert_raises(EncodingError) do
116
+ ActiveSupport::JSON.encode([s])
117
+ end
118
+ end
79
119
  end
80
120
 
81
- def test_wide_utf8_chars
82
- w = "𠜎"
83
- result = ActiveSupport::JSON.encode(w)
84
- assert_equal '"𠜎"', result
121
+ module UnicodeTestsWithEscapingOn
122
+ def setup
123
+ ActiveSupport.escape_html_entities_in_json = true
124
+ end
125
+
126
+ def teardown
127
+ ActiveSupport.escape_html_entities_in_json = false
128
+ end
129
+
130
+ include UnicodeTests
85
131
  end
86
132
 
87
- def test_wide_utf8_roundtrip
88
- hash = { string: "𐒑" }
89
- json = ActiveSupport::JSON.encode(hash)
90
- decoded_hash = ActiveSupport::JSON.decode(json)
91
- assert_equal "𐒑", decoded_hash["string"]
133
+ module UnicodeTestsWithEscapingOff
134
+ def setup
135
+ ActiveSupport.escape_html_entities_in_json = false
136
+ end
137
+
138
+ include UnicodeTests
92
139
  end
93
140
 
141
+ include UnicodeTestsWithEscapingOn
142
+ include UnicodeTestsWithEscapingOff
143
+
94
144
  def test_hash_key_identifiers_are_always_quoted
95
145
  values = { 0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B" }
96
146
  assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oj
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.16.3
4
+ version: 3.16.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Ohler
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-11 00:00:00.000000000 Z
11
+ date: 2024-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bigdecimal