ox 2.14.25 → 2.14.27

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: 1fdd6c47d31f46414501f3719dc4b3b0c938517cbd0a03f982110657b3b7261b
4
- data.tar.gz: 9d65bf85bc88e0421049230251ed87a2dfa078c0e2be5ab61b5da63f148d52e4
3
+ metadata.gz: b981c5e93cbbe907c8beb655007ebd2d3bfd740da911372ce82ea329e85773f0
4
+ data.tar.gz: 98935b178f268fad229368d02b2efd14529b4144aebae15b557aed80d20a5aac
5
5
  SHA512:
6
- metadata.gz: a82cba42c5957d123ea2022f8cce3f65923c31f96b1ff67c176a75f538fef35e3d58585ba232fc972a7a41e94386a72cdec9fd16f341cbd080ea1d1c1de04fc1
7
- data.tar.gz: 255570365253191fbe23824dbdc9262162dcea2fd5b297bc182ea2fd4eff25ecc4bc8484cadf56c3c4c5cee4fac2d0589140ee2d2d5e7f395f6e2edaf487cfe3
6
+ metadata.gz: aaa1d3f2183519ef26fd82b80f1bae7bef61216e8404ae03fcc0ad7c9b8e450b2acb423cb7f113be08fa3cab8ba448cf25425e7cc12e138a63e04c93642e2523
7
+ data.tar.gz: b0b0e20339a346d17a3a29b57e8c1d722b5999d346d775c6f587400aa8affa30e63186a5a6109dd59091fd13ecc172ef93d69e8168bc65a2aa70bd66e76f58ec
data/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  All changes to the Ox gem are documented here. Releases follow semantic versioning.
4
4
 
5
+ ## [2.14.27] - 2026-06-18
6
+
7
+ ### Fixed
8
+
9
+ - Fixed stack overflow issue in the parser that occurred when reading
10
+ special character sequences.
11
+
12
+ - Set a limit of 1000 as the maximum nesting depth of elements to
13
+ avoid stack exhaustion.
14
+
15
+ - Added a note on the symbolize_keys option indicating it should not
16
+ be used on unregulated input where the symbol table could grow
17
+ indefinitely.
18
+
19
+ ## [2.14.26] - 2026-05-09
20
+
21
+ ### Fixed
22
+
23
+ - Several fixes by by @albanpeignier
24
+
5
25
  ## [2.14.25] - 2026-04-23
6
26
 
7
27
  ### Added
data/ext/ox/base64.c CHANGED
@@ -11,7 +11,7 @@
11
11
  static char digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
12
12
 
13
13
  /* invalid or terminating characters are set to 'X' or \x58 */
14
- static uchar s_digits[256] = "\
14
+ static uchar s_digits[257] = "\
15
15
  \x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\
16
16
  \x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\
17
17
  \x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x58\x3E\x58\x58\x58\x3F\
data/ext/ox/ox.c CHANGED
@@ -274,7 +274,9 @@ static VALUE hints_to_overlay(Hints hints) {
274
274
  * - _:xsd_date_ [true|false|nil] use XSD date format instead of decimal format
275
275
  * - _:mode_ [:object|:generic|:limited|:hash|:hash_no_attrs|nil] load method to use for XML
276
276
  * - _:effort_ [:strict|:tolerant|:auto_define] set the tolerance level for loading
277
- * - _:symbolize_keys_ [true|false|nil] symbolize element attribute keys or leave as Strings
277
+ * - _:symbolize_keys_ [true|false|nil] symbolize element attribute keys or leave as Strings.
278
+ * Note that symbolized keys are more efficient but for uncontrolled input it can lead to unlimited
279
+ * growth of the symbol (intern) table.
278
280
  * - _:element_key_mod_ [Proc|nil] converts element keys on parse if not nil
279
281
  * - _:attr_key_mod_ [Proc|nil] converts attribute keys on parse if not nil
280
282
  * - _:skip_ [:skip_none|:skip_return|:skip_white|:skip_off] determines how to handle white space in text
data/ext/ox/parse.c CHANGED
@@ -18,11 +18,13 @@
18
18
  #include "ruby.h"
19
19
  #include "special.h"
20
20
 
21
+ #define MAX_ELEMENT_DEPTH 1000
22
+
21
23
  static void mark_pi_cb(void *ptr);
22
24
  static void read_instruction(PInfo pi);
23
25
  static void read_doctype(PInfo pi);
24
26
  static void read_comment(PInfo pi);
25
- static char *read_element(PInfo pi);
27
+ static char *read_element(PInfo pi, int depth);
26
28
  static void read_text(PInfo pi);
27
29
  /*static void read_reduced_text(PInfo pi); */
28
30
  static void read_cdata(PInfo pi);
@@ -216,7 +218,7 @@ ox_parse(char *xml, size_t len, ParseCallbacks pcb, char **endp, Options options
216
218
  helper_stack_cleanup(&pi.helpers);
217
219
  return Qnil;
218
220
  default:
219
- read_element(&pi);
221
+ read_element(&pi, 0);
220
222
  body_read = 1;
221
223
  break;
222
224
  }
@@ -302,6 +304,11 @@ DONE:
302
304
  }
303
305
  end = pi->s;
304
306
  next_non_white(pi);
307
+ if ('\0' == *pi->s) {
308
+ attr_stack_cleanup(&attrs);
309
+ set_error(&pi->err, "invalid format, processing instruction not terminated", pi->str, pi->s);
310
+ return;
311
+ }
305
312
  if ('=' != *pi->s++) {
306
313
  attrs_ok = false;
307
314
  break;
@@ -364,7 +371,8 @@ static void read_delimited(PInfo pi, char end) {
364
371
  if ('"' == end || '\'' == end) {
365
372
  for (c = *pi->s++; end != c; c = *pi->s++) {
366
373
  if ('\0' == c) {
367
- set_error(&pi->err, "invalid format, dectype not terminated", pi->str, pi->s);
374
+ pi->s--;
375
+ set_error(&pi->err, "invalid format, doctype not terminated", pi->str, pi->s);
368
376
  return;
369
377
  }
370
378
  }
@@ -375,7 +383,10 @@ static void read_delimited(PInfo pi, char end) {
375
383
  return;
376
384
  }
377
385
  switch (c) {
378
- case '\0': set_error(&pi->err, "invalid format, dectype not terminated", pi->str, pi->s); return;
386
+ case '\0':
387
+ pi->s--;
388
+ set_error(&pi->err, "invalid format, doctype not terminated", pi->str, pi->s);
389
+ return;
379
390
  case '"': read_delimited(pi, c); break;
380
391
  case '\'': read_delimited(pi, c); break;
381
392
  case '[': read_delimited(pi, ']'); break;
@@ -443,7 +454,7 @@ static void read_comment(PInfo pi) {
443
454
 
444
455
  // Entered after the '<' and the first character after that. Returns stat
445
456
  // code.
446
- static char *read_element(PInfo pi) {
457
+ static char *read_element(PInfo pi, int depth) {
447
458
  struct _attrStack attrs;
448
459
  const char *attr_name;
449
460
  const char *attr_value;
@@ -455,6 +466,10 @@ static char *read_element(PInfo pi) {
455
466
  int hasChildren = 0;
456
467
  int done = 0;
457
468
 
469
+ if (MAX_ELEMENT_DEPTH < depth) {
470
+ set_error(&pi->err, "element nested too deeply, limit is 1000", pi->str, pi->s);
471
+ return 0;
472
+ }
458
473
  attr_stack_init(&attrs);
459
474
  if (0 == (ename = read_name_token(pi))) {
460
475
  return 0;
@@ -535,6 +550,7 @@ static char *read_element(PInfo pi) {
535
550
  break;
536
551
  } else {
537
552
  attr_stack_cleanup(&attrs);
553
+ pi->s--;
538
554
  set_error(&pi->err, "invalid format, no attribute value", pi->str, pi->s);
539
555
  return 0;
540
556
  }
@@ -579,7 +595,7 @@ static char *read_element(PInfo pi) {
579
595
  c = *pi->s++;
580
596
  if ('\0' == c) {
581
597
  attr_stack_cleanup(&attrs);
582
- set_error(&pi->err, "invalid format, document not terminated", pi->str, pi->s);
598
+ set_error(&pi->err, "invalid format, document not terminated", pi->str, pi->s - 1);
583
599
  return 0;
584
600
  }
585
601
  if ('<' == c) {
@@ -674,7 +690,7 @@ static char *read_element(PInfo pi) {
674
690
  first = 0;
675
691
  /* a child element */
676
692
  // Child closed with mismatched name.
677
- if (0 != (name = read_element(pi))) {
693
+ if (0 != (name = read_element(pi, depth + 1))) {
678
694
  attr_stack_cleanup(&attrs);
679
695
 
680
696
  if (0 ==
@@ -701,6 +717,11 @@ static char *read_element(PInfo pi) {
701
717
  read_text(pi);
702
718
  /*read_reduced_text(pi); */
703
719
 
720
+ if (err_has(&pi->err)) {
721
+ attr_stack_cleanup(&attrs);
722
+ return 0;
723
+ }
724
+
704
725
  /* to exit read_text with no errors the next character must be < */
705
726
  if ('/' == *(pi->s + 1) &&
706
727
  0 == ((TolerantEffort == pi->options->effort) ? strncasecmp(ename, pi->s + 2, elen)
@@ -734,7 +755,10 @@ static void read_text(PInfo pi) {
734
755
  done = 1;
735
756
  pi->s--;
736
757
  break;
737
- case '\0': set_error(&pi->err, "invalid format, document not terminated", pi->str, pi->s); return;
758
+ case '\0':
759
+ pi->s--;
760
+ set_error(&pi->err, "invalid format, document not terminated", pi->str, pi->s);
761
+ return;
738
762
  default:
739
763
  if (end <= (b + (('&' == c) ? 7 : 0))) { /* extra 8 for special just in case it is sequence of bytes */
740
764
  unsigned long size;
@@ -1027,6 +1051,10 @@ static char *read_coded_chars(PInfo pi, char *text) {
1027
1051
 
1028
1052
  for (b = buf, s = pi->s; b < end; b++, s++) {
1029
1053
  *b = *s;
1054
+ if ('\0' == *s) {
1055
+ set_error(&pi->err, "Not terminated coded char.", pi->str, pi->s);
1056
+ return NULL;
1057
+ }
1030
1058
  if (';' == *s) {
1031
1059
  *(b + 1) = '\0';
1032
1060
  blen = b - buf;
@@ -1034,7 +1062,8 @@ static char *read_coded_chars(PInfo pi, char *text) {
1034
1062
  break;
1035
1063
  }
1036
1064
  }
1037
- if (b > end) {
1065
+ if (b >= end) {
1066
+ // No terminating ; found in the first 31 bytes after an &.
1038
1067
  *text++ = '&';
1039
1068
  } else if ('#' == *buf) {
1040
1069
  uint64_t u = 0;
data/ext/ox/sax.c CHANGED
@@ -1496,7 +1496,11 @@ int ox_sax_collapse_special(SaxDrive dr, char *str, long pos, long line, long co
1496
1496
  c = '&';
1497
1497
  } else {
1498
1498
  b = bn;
1499
- s = s2 + 1;
1499
+ if ('\0' != *s2) {
1500
+ s = s2 + 1;
1501
+ } else {
1502
+ s = s2;
1503
+ }
1500
1504
  continue;
1501
1505
  }
1502
1506
  }
data/ext/ox/sax_buf.c CHANGED
@@ -69,8 +69,8 @@ void ox_sax_buf_init(Buf buf, VALUE io) {
69
69
  }
70
70
 
71
71
  int ox_sax_buf_read(Buf buf) {
72
- int err;
73
- size_t shift = 0;
72
+ int err;
73
+ long shift = 0;
74
74
 
75
75
  // if there is not much room to read into, shift or realloc a larger buffer.
76
76
  if (buf->head < buf->tail && 4096 > buf->end - buf->tail) {
data/lib/ox/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Ox
2
2
  # Current version of the module.
3
- VERSION = '2.14.25'
3
+ VERSION = '2.14.27'
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ox
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.14.25
4
+ version: 2.14.27
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Ohler
@@ -51,7 +51,6 @@ files:
51
51
  - ext/ox/err.c
52
52
  - ext/ox/err.h
53
53
  - ext/ox/extconf.rb
54
- - ext/ox/foo.h
55
54
  - ext/ox/gen_load.c
56
55
  - ext/ox/hash_load.c
57
56
  - ext/ox/helper.h
@@ -123,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
122
  - !ruby/object:Gem::Version
124
123
  version: '0'
125
124
  requirements: []
126
- rubygems_version: 4.0.3
125
+ rubygems_version: 4.0.6
127
126
  specification_version: 4
128
127
  summary: A fast XML parser and object serializer.
129
128
  test_files: []
data/ext/ox/foo.h DELETED
@@ -1,204 +0,0 @@
1
- /* sax_buf.h
2
- * Copyright (c) 2011, Peter Ohler
3
- * All rights reserved.
4
- */
5
-
6
- #ifndef OX_SAX_BUF_H
7
- #define OX_SAX_BUF_H
8
-
9
- #include <stdio.h>
10
-
11
- typedef struct _buf {
12
- char base[0x00001000];
13
- char *head;
14
- char *end;
15
- char *tail;
16
- char *read_end; /* one past last character read */
17
- char *pro; /* protection start, buffer can not slide past this point */
18
- char *str; /* start of current string being read */
19
- off_t pos;
20
- off_t line;
21
- off_t col;
22
- off_t pro_pos;
23
- off_t pro_line;
24
- off_t pro_col;
25
- int (*read_func)(struct _buf *buf);
26
- union {
27
- int fd;
28
- VALUE io;
29
- const char *str;
30
- } in;
31
- struct _saxDrive *dr;
32
- } *Buf;
33
-
34
- typedef struct _checkPt {
35
- off_t pro_dif;
36
- off_t pos;
37
- off_t line;
38
- off_t col;
39
- char c;
40
- } *CheckPt;
41
-
42
- #define CHECK_PT_INIT {-1, 0, 0, 0, '\0'}
43
-
44
- extern void ox_sax_buf_init(Buf buf, VALUE io);
45
- extern int ox_sax_buf_read(Buf buf);
46
-
47
- static inline char buf_get(Buf buf) {
48
- // printf("*** drive get from '%s' from start: %ld buf: %p from read_end: %ld\n", buf->tail, buf->tail -
49
- // buf->head, buf->head, buf->read_end - buf->tail);
50
- if (buf->read_end <= buf->tail) {
51
- if (0 != ox_sax_buf_read(buf)) {
52
- return '\0';
53
- }
54
- }
55
- if ('\n' == *buf->tail) {
56
- buf->line++;
57
- buf->col = 0;
58
- } else {
59
- buf->col++;
60
- }
61
- buf->pos++;
62
-
63
- return *buf->tail++;
64
- }
65
-
66
- static inline void buf_backup(Buf buf) {
67
- buf->tail--;
68
- buf->col--;
69
- buf->pos--;
70
- if (0 >= buf->col) {
71
- buf->line--;
72
- // allow col to be negative since we never backup twice in a row
73
- }
74
- }
75
-
76
- static inline void buf_protect(Buf buf) {
77
- buf->pro = buf->tail;
78
- buf->str = buf->tail; // can't have str before pro
79
- buf->pro_pos = buf->pos;
80
- buf->pro_line = buf->line;
81
- buf->pro_col = buf->col;
82
- }
83
-
84
- static inline void buf_reset(Buf buf) {
85
- buf->tail = buf->pro;
86
- buf->pos = buf->pro_pos;
87
- buf->line = buf->pro_line;
88
- buf->col = buf->pro_col;
89
- }
90
-
91
- /* Starts by reading a character so it is safe to use with an empty or
92
- * compacted buffer.
93
- */
94
- static inline char buf_next_non_white(Buf buf) {
95
- char c;
96
-
97
- while ('\0' != (c = buf_get(buf))) {
98
- switch (c) {
99
- case ' ':
100
- case '\t':
101
- case '\f':
102
- case '\n':
103
- case '\r': break;
104
- default: return c;
105
- }
106
- }
107
- return '\0';
108
- }
109
-
110
- /* Starts by reading a character so it is safe to use with an empty or
111
- * compacted buffer.
112
- */
113
- static inline char buf_next_white(Buf buf) {
114
- char c;
115
-
116
- while ('\0' != (c = buf_get(buf))) {
117
- switch (c) {
118
- case ' ':
119
- case '\t':
120
- case '\f':
121
- case '\n':
122
- case '\r':
123
- case '\0': return c;
124
- default: break;
125
- }
126
- }
127
- return '\0';
128
- }
129
-
130
- static inline void buf_cleanup(Buf buf) {
131
- if (buf->base != buf->head && 0 != buf->head) {
132
- xfree(buf->head);
133
- buf->head = 0;
134
- }
135
- }
136
-
137
- static inline int is_white(char c) {
138
- switch (c) {
139
- case ' ':
140
- case '\t':
141
- case '\f':
142
- case '\n':
143
- case '\r': return 1;
144
- default: break;
145
- }
146
- return 0;
147
- }
148
-
149
- static inline void buf_checkpoint(Buf buf, CheckPt cp) {
150
- cp->pro_dif = (int)(buf->tail - buf->pro);
151
- cp->pos = buf->pos;
152
- cp->line = buf->line;
153
- cp->col = buf->col;
154
- cp->c = *(buf->tail - 1);
155
- }
156
-
157
- static inline int buf_checkset(CheckPt cp) {
158
- return (0 <= cp->pro_dif);
159
- }
160
-
161
- static inline char buf_checkback(Buf buf, CheckPt cp) {
162
- buf->tail = buf->pro + cp->pro_dif;
163
- buf->pos = cp->pos;
164
- buf->line = cp->line;
165
- buf->col = cp->col;
166
- return cp->c;
167
- }
168
-
169
- static inline void buf_collapse_return(char *str) {
170
- char *s = str;
171
- char *back = str;
172
-
173
- for (; '\0' != *s; s++) {
174
- if (back != str && '\n' == *s && '\r' == *(back - 1)) {
175
- *(back - 1) = '\n';
176
- } else {
177
- *back++ = *s;
178
- }
179
- }
180
- *back = '\0';
181
- }
182
-
183
- static inline void buf_collapse_white(char *str) {
184
- char *s = str;
185
- char *back = str;
186
-
187
- for (; '\0' != *s; s++) {
188
- switch (*s) {
189
- case ' ':
190
- case '\t':
191
- case '\f':
192
- case '\n':
193
- case '\r':
194
- if (back == str || ' ' != *(back - 1)) {
195
- *back++ = ' ';
196
- }
197
- break;
198
- default: *back++ = *s; break;
199
- }
200
- }
201
- *back = '\0';
202
- }
203
-
204
- #endif /* OX_SAX_BUF_H */