ox 2.14.28 → 2.14.29

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.
data/ext/ox/extconf.rb CHANGED
@@ -46,6 +46,9 @@ have_func('rb_ext_ractor_safe', 'ruby.h')
46
46
  have_func('pthread_mutex_init')
47
47
  have_func('rb_enc_interned_str')
48
48
  have_func('index')
49
+ # Seeds the name cache hash. Not on every implementation, so cache.c falls back
50
+ # to String#hash, which is per process randomized everywhere it matters.
51
+ have_func('rb_hash_start', 'ruby.h')
49
52
 
50
53
  have_header('ruby/st.h')
51
54
  have_header('sys/uio.h')
data/ext/ox/gen_load.c CHANGED
@@ -21,13 +21,11 @@ static void nomode_instruct(PInfo pi, const char *target, Attr attrs, const char
21
21
  static void add_doctype(PInfo pi, const char *docType);
22
22
  static void add_comment(PInfo pi, const char *comment);
23
23
  static void add_cdata(PInfo pi, const char *cdata, size_t len);
24
- static void add_text(PInfo pi, char *text, int closed);
24
+ static void add_text(PInfo pi, char *text, size_t len, int closed);
25
25
  static void add_element(PInfo pi, const char *ename, Attr attrs, int hasChildren);
26
26
  static void end_element(PInfo pi, const char *ename);
27
27
  static void add_instruct(PInfo pi, const char *name, Attr attrs, const char *content);
28
28
 
29
- extern ParseCallbacks ox_obj_callbacks;
30
-
31
29
  struct _parseCallbacks _ox_gen_callbacks = {
32
30
  instruct, /* instruct, */
33
31
  add_doctype,
@@ -163,14 +161,18 @@ static void nomode_instruct(PInfo pi, const char *target, Attr attrs, const char
163
161
  }
164
162
  } else if (0 == strcmp("mode", attrs->name)) {
165
163
  if (0 == strcmp("object", attrs->value)) {
166
- pi->pcb = ox_obj_callbacks;
167
- pi->obj = Qnil;
168
- helper_stack_init(&pi->helpers);
164
+ // Only the caller can select object mode.
165
+ if (TRACE <= pi->options->trace) {
166
+ printf("Object mode processing instruction ignored.\n");
167
+ }
169
168
  } else if (0 == strcmp("generic", attrs->value)) {
170
169
  pi->pcb = ox_gen_callbacks;
171
170
  } else if (0 == strcmp("limited", attrs->value)) {
172
171
  pi->pcb = ox_limited_callbacks;
173
172
  pi->obj = Qnil;
173
+ // The instruction can appear after the stack has grown, and
174
+ // helper_stack_init() only moves head back to base.
175
+ helper_stack_cleanup(&pi->helpers);
174
176
  helper_stack_init(&pi->helpers);
175
177
  } else {
176
178
  ox_err_set(&pi->err,
@@ -230,8 +232,8 @@ static void add_cdata(PInfo pi, const char *cdata, size_t len) {
230
232
  rb_ary_push(helper_stack_peek(&pi->helpers)->obj, n);
231
233
  }
232
234
 
233
- static void add_text(PInfo pi, char *text, int closed) {
234
- VALUE s = rb_str_new2(text);
235
+ static void add_text(PInfo pi, char *text, size_t len, int closed) {
236
+ VALUE s = rb_str_new(text, len);
235
237
 
236
238
  if (0 != pi->options->rb_enc) {
237
239
  rb_enc_associate(s, pi->options->rb_enc);
data/ext/ox/hash_load.c CHANGED
@@ -92,8 +92,8 @@ static void add_str(PInfo pi, VALUE s) {
92
92
  }
93
93
  }
94
94
 
95
- static void add_text(PInfo pi, char *text, int closed) {
96
- VALUE s = rb_str_new2(text);
95
+ static void add_text(PInfo pi, char *text, size_t len, int closed) {
96
+ VALUE s = rb_str_new(text, len);
97
97
  if (0 != pi->options->rb_enc) {
98
98
  rb_enc_associate(s, pi->options->rb_enc);
99
99
  }
@@ -239,7 +239,12 @@ static void end_element_no_attrs(PInfo pi, const char *ename) {
239
239
  }
240
240
 
241
241
  static void finish(PInfo pi) {
242
+ // Called once per yielded entity and once after the loop, so the pointer
243
+ // and the count have to go with the block.
242
244
  xfree(pi->marked);
245
+ pi->marked = NULL;
246
+ pi->mark_size = 0;
247
+ pi->mark_cnt = 0;
243
248
  }
244
249
 
245
250
  static void set_encoding_from_instruct(PInfo pi, Attr attrs) {
data/ext/ox/helper.h CHANGED
@@ -6,14 +6,17 @@
6
6
  #ifndef OX_HELPER_H
7
7
  #define OX_HELPER_H
8
8
 
9
+ #include <stdbool.h>
10
+
9
11
  #include "type.h"
10
12
 
11
13
  #define HELPER_STACK_INC 16
12
14
 
13
15
  typedef struct _helper {
14
- ID var; /* Object var ID */
15
- VALUE obj; /* object created or Qundef if not appropriate */
16
- Type type; /* type of object in obj */
16
+ ID var; /* Object var ID, or a Struct member index when index is set */
17
+ VALUE obj; /* object created or Qundef if not appropriate */
18
+ Type type; /* type of object in obj */
19
+ bool index; /* var is a member index and not an ID */
17
20
  } *Helper;
18
21
 
19
22
  typedef struct _helperStack {
@@ -58,9 +61,10 @@ inline static Helper helper_stack_push(HelperStack stack, ID var, VALUE obj, Typ
58
61
  stack->tail = stack->head + toff;
59
62
  stack->end = stack->head + len + HELPER_STACK_INC;
60
63
  }
61
- stack->tail->var = var;
62
- stack->tail->obj = obj;
63
- stack->tail->type = type;
64
+ stack->tail->var = var;
65
+ stack->tail->obj = obj;
66
+ stack->tail->type = type;
67
+ stack->tail->index = false; // obj_load.c sets its own
64
68
  stack->tail++;
65
69
 
66
70
  return stack->tail - 1;
data/ext/ox/intern.c CHANGED
@@ -68,7 +68,12 @@ static VALUE form_id(const char *str, size_t len) {
68
68
  }
69
69
 
70
70
  void ox_hash_init(void) {
71
- VALUE cache_class = rb_define_class_under(Ox, "Cache", rb_cObject);
71
+ VALUE cache_class;
72
+
73
+ // Before the first cache exists, so no name is ever hashed unseeded.
74
+ ox_hash_seed_init();
75
+
76
+ cache_class = rb_define_class_under(Ox, "Cache", rb_cObject);
72
77
  #if RUBY_API_VERSION_CODE >= 30200
73
78
  rb_undef_alloc_func(cache_class);
74
79
  #endif
data/ext/ox/obj_load.c CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  #include <errno.h>
7
7
  #include <stdarg.h>
8
+ #include <stdbool.h>
8
9
  #include <stdio.h>
9
10
  #include <stdlib.h>
10
11
  #include <string.h>
@@ -15,24 +16,27 @@
15
16
  #include "ox.h"
16
17
  #include "ruby.h"
17
18
  #include "ruby/encoding.h"
19
+ #include "time_conv.h"
20
+
21
+ // No Struct has this many members, so a larger index is always out of range.
22
+ #define MAX_STRUCT_INDEX (1 << 24)
18
23
 
19
24
  static void instruct(PInfo pi, const char *target, Attr attrs, const char *content);
20
- static void add_text(PInfo pi, char *text, int closed);
25
+ static void add_text(PInfo pi, char *text, size_t len, int closed);
21
26
  static void add_element(PInfo pi, const char *ename, Attr attrs, int hasChildren);
22
27
  static void end_element(PInfo pi, const char *ename);
23
28
 
24
29
  static VALUE parse_time(const char *text, VALUE clas);
25
30
  static VALUE parse_xsd_time(const char *text, VALUE clas);
26
31
  static VALUE parse_double_time(const char *text, VALUE clas);
27
- static VALUE parse_regexp(const char *text);
32
+ static VALUE parse_regexp(const char *text, PInfo pi);
28
33
 
29
- static ID get_var_sym_from_attrs(Attr a, void *encoding);
34
+ static ID get_var_sym_from_attrs(Attr a, void *encoding, bool *indexp);
30
35
  static VALUE get_obj_from_attrs(Attr a, PInfo pi, VALUE base_class);
31
36
  static VALUE get_class_from_attrs(Attr a, PInfo pi, VALUE base_class);
32
37
  static VALUE classname2class(const char *name, PInfo pi, VALUE base_class);
33
38
  static unsigned long get_id_from_attrs(PInfo pi, Attr a);
34
39
  static CircArray circ_array_new(void);
35
- static void circ_array_free(CircArray ca);
36
40
  static void circ_array_set(CircArray ca, VALUE obj, unsigned long id);
37
41
  static VALUE circ_array_get(CircArray ca, unsigned long id);
38
42
 
@@ -143,7 +147,8 @@ static VALUE classname2class(const char *name, PInfo pi, VALUE base_class) {
143
147
  if (Qundef == (clas = slot_cache_get(ox_class_cache, name, &slot, 0))) {
144
148
  char class_name[1024];
145
149
  char *s;
146
- const char *n = name;
150
+ char *class_name_end = class_name + sizeof(class_name) - 1;
151
+ const char *n = name;
147
152
 
148
153
  clas = rb_cObject;
149
154
  for (s = class_name; '\0' != *n; n++) {
@@ -159,6 +164,13 @@ static VALUE classname2class(const char *name, PInfo pi, VALUE base_class) {
159
164
  }
160
165
  s = class_name;
161
166
  } else {
167
+ if (class_name_end <= s) {
168
+ // Guard the fixed-size stack buffer: an over-long class name
169
+ // (from the attacker-controlled `c` attribute in object mode)
170
+ // would otherwise overflow class_name[].
171
+ set_error(&pi->err, "Invalid classname, too long", pi->str, pi->s);
172
+ return Qundef;
173
+ }
162
174
  *s++ = *n;
163
175
  }
164
176
  }
@@ -171,13 +183,30 @@ static VALUE classname2class(const char *name, PInfo pi, VALUE base_class) {
171
183
  return clas;
172
184
  }
173
185
 
174
- static ID get_var_sym_from_attrs(Attr a, void *encoding) {
186
+ // Returns a Struct member index as a Fixnum or an instance variable ID. An ID
187
+ // is odd, so it satisfies FIXNUM_P too; indexp says which one it is.
188
+ static ID get_var_sym_from_attrs(Attr a, void *encoding, bool *indexp) {
189
+ *indexp = false;
175
190
  for (; 0 != a->name; a++) {
176
191
  if ('a' == *a->name && '\0' == *(a->name + 1)) {
177
192
  const char *val = a->value;
178
193
 
179
194
  if ('0' <= *val && *val <= '9') {
180
- return INT2NUM(atoi(val));
195
+ // atoi() wrapped past INT_MAX into a negative index, which Ruby
196
+ // counts from the end. Stopping at INT_MAX is no good either:
197
+ // FIXNUM_MAX is 2**30 - 1 where long is 32 bits, and INT2NUM of
198
+ // INT_MAX came back as -1 there.
199
+ int i = 0;
200
+
201
+ for (; '0' <= *val && *val <= '9'; val++) {
202
+ i = i * 10 + (*val - '0');
203
+ if (MAX_STRUCT_INDEX < i) {
204
+ i = MAX_STRUCT_INDEX;
205
+ break;
206
+ }
207
+ }
208
+ *indexp = true;
209
+ return (ID)INT2NUM(i);
181
210
  }
182
211
  return ox_id_intern(val, strlen(val));
183
212
  }
@@ -212,16 +241,25 @@ static VALUE get_class_from_attrs(Attr a, PInfo pi, VALUE base_class) {
212
241
  return Qundef;
213
242
  }
214
243
 
244
+ // The id indexes the circular reference table, which is grown to fit it, so an
245
+ // unbounded id is an unbounded heap write. Cap it at the document length: every
246
+ // referenced object needs at least one element, so no valid id can exceed it.
215
247
  static unsigned long get_id_from_attrs(PInfo pi, Attr a) {
216
248
  for (; 0 != a->name; a++) {
217
249
  if ('i' == *a->name && '\0' == *(a->name + 1)) {
218
- unsigned long id = 0;
219
- const char *text = a->value;
220
- char c;
250
+ unsigned long id = 0;
251
+ const unsigned long limit = (unsigned long)(pi->end - pi->str);
252
+ const char *text = a->value;
253
+ char c;
221
254
 
222
255
  for (; '\0' != *text; text++) {
223
256
  c = *text;
224
257
  if ('0' <= c && c <= '9') {
258
+ // Checked before the multiply so id can not overflow.
259
+ if (limit / 10 < id || limit < id * 10 + (unsigned long)(c - '0')) {
260
+ set_error(&pi->err, "circular reference id out of range", pi->str, pi->s);
261
+ return 0;
262
+ }
225
263
  id = id * 10 + (c - '0');
226
264
  } else {
227
265
  set_error(&pi->err, "bad number format", pi->str, pi->s);
@@ -245,7 +283,8 @@ static CircArray circ_array_new(void) {
245
283
  return ca;
246
284
  }
247
285
 
248
- static void circ_array_free(CircArray ca) {
286
+ // Also called from ox_parse_ensure() when a parse ends before end_element().
287
+ void ox_circ_array_free(CircArray ca) {
249
288
  if (ca->objs != ca->obj_array) {
250
289
  xfree(ca->objs);
251
290
  }
@@ -281,17 +320,30 @@ static void circ_array_set(CircArray ca, VALUE obj, unsigned long id) {
281
320
  static VALUE circ_array_get(CircArray ca, unsigned long id) {
282
321
  VALUE obj = Qundef;
283
322
 
284
- if (id <= ca->cnt) {
323
+ // id is 0 when the `i` attribute is missing or invalid. Without the lower
324
+ // bound that reads objs[-1] and hands the word to Ruby as an object.
325
+ if (0 < id && id <= ca->cnt) {
285
326
  obj = ca->objs[id - 1];
286
327
  }
287
328
  return obj;
288
329
  }
289
330
 
290
- static VALUE parse_regexp(const char *text) {
331
+ static VALUE parse_regexp(const char *text, PInfo pi) {
291
332
  const char *te;
333
+ const char *s;
334
+ char *b;
335
+ VALUE src;
336
+ size_t len = strlen(text);
292
337
  int options = 0;
293
338
 
294
- te = text + strlen(text) - 1;
339
+ // The text is Regexp#inspect output, so the shortest it can be is "//".
340
+ // Below that te would be built from text - 1, which is not a pointer the
341
+ // string owns.
342
+ if (len < 2 || '/' != *text) {
343
+ set_error(&pi->err, "Invalid regexp format", pi->str, pi->s);
344
+ return Qundef;
345
+ }
346
+ te = text + len - 1;
295
347
  #ifdef ONIG_OPTION_IGNORECASE
296
348
  for (; text < te && '/' != *te; te--) {
297
349
  switch (*te) {
@@ -302,7 +354,41 @@ static VALUE parse_regexp(const char *text) {
302
354
  }
303
355
  }
304
356
  #endif
305
- return rb_reg_new(text + 1, te - text - 1, options);
357
+ // The scan above stops on the opening / when there is no closing one, and
358
+ // without ONIG_OPTION_IGNORECASE it never runs at all. Either way the
359
+ // length below goes negative if this is not checked.
360
+ if (te <= text || '/' != *te) {
361
+ set_error(&pi->err, "Invalid regexp format", pi->str, pi->s);
362
+ return Qundef;
363
+ }
364
+ // rb_reg_new() takes bytes, so it can not be told the document's encoding
365
+ // and leaves the source ASCII-8BIT. Going through the String also gets
366
+ // Ruby's own rule, which promotes an ASCII only source to US-ASCII the way
367
+ // a literal is.
368
+ src = rb_str_new(0, te - text - 1);
369
+ b = RSTRING_PTR(src);
370
+ // The dumper writes Regexp#inspect, which escapes a / in the source as \/,
371
+ // so take that back off. A backslash pair is stepped over whole or the /
372
+ // in "\\/" would be read as the escaped one.
373
+ s = text + 1;
374
+ while (s < te) {
375
+ if ('\\' == *s && s + 1 < te) {
376
+ if ('/' == s[1]) {
377
+ *b++ = '/';
378
+ } else {
379
+ *b++ = *s;
380
+ *b++ = s[1];
381
+ }
382
+ s += 2;
383
+ } else {
384
+ *b++ = *s++;
385
+ }
386
+ }
387
+ rb_str_set_len(src, b - RSTRING_PTR(src));
388
+ if (0 != pi->options->rb_enc) {
389
+ rb_enc_associate(src, pi->options->rb_enc);
390
+ }
391
+ return rb_reg_new_str(src, options);
306
392
  }
307
393
 
308
394
  static void instruct(PInfo pi, const char *target, Attr attrs, const char *content) {
@@ -315,7 +401,7 @@ static void instruct(PInfo pi, const char *target, Attr attrs, const char *conte
315
401
  }
316
402
  }
317
403
 
318
- static void add_text(PInfo pi, char *text, int closed) {
404
+ static void add_text(PInfo pi, char *text, size_t len, int closed) {
319
405
  Helper h = helper_stack_peek(&pi->helpers);
320
406
 
321
407
  if (!closed) {
@@ -335,7 +421,7 @@ static void add_text(PInfo pi, char *text, int closed) {
335
421
  switch (h->type) {
336
422
  case NoCode:
337
423
  case StringCode:
338
- h->obj = rb_str_new2(text);
424
+ h->obj = rb_str_new(text, len);
339
425
  if (0 != pi->options->rb_enc) {
340
426
  rb_enc_associate(h->obj, pi->options->rb_enc);
341
427
  }
@@ -381,42 +467,68 @@ static void add_text(PInfo pi, char *text, int closed) {
381
467
  case TimeCode: h->obj = parse_time(text, ox_time_class); break;
382
468
  case String64Code: {
383
469
  unsigned long str_size = b64_orig_size(text);
384
- VALUE v;
385
- char *str = ALLOCA_N(char, str_size + 1);
470
+ // Decode straight into the String. The size comes from the document, so
471
+ // an ALLOCA_N of it is an unbounded stack allocation, and the heap the
472
+ // String already needs costs nothing extra.
473
+ VALUE v = rb_str_new(0, (long)str_size);
386
474
 
387
- from_base64(text, (uchar *)str);
388
- v = rb_str_new(str, str_size);
475
+ from_base64(text, (uchar *)RSTRING_PTR(v), str_size + 1);
389
476
  if (0 != pi->options->rb_enc) {
390
477
  rb_enc_associate(v, pi->options->rb_enc);
391
478
  }
392
479
  if (0 != pi->circ_array) {
393
- circ_array_set(pi->circ_array, v, (unsigned long)h->obj);
480
+ // h->obj is still Qundef; add_element() parked the id in pi->id.
481
+ circ_array_set(pi->circ_array, v, pi->id);
394
482
  }
395
483
  h->obj = v;
396
484
  break;
397
485
  }
398
486
  case Symbol64Code: {
399
487
  unsigned long str_size = b64_orig_size(text);
400
- char *str = ALLOCA_N(char, str_size + 1);
488
+ VALUE v = rb_str_new(0, (long)str_size);
489
+ char *str = RSTRING_PTR(v);
401
490
 
402
- from_base64(text, (uchar *)str);
491
+ from_base64(text, (uchar *)str, str_size + 1);
403
492
  h->obj = ox_sym_intern(str, strlen(str), NULL);
493
+ RB_GC_GUARD(v);
404
494
  break;
405
495
  }
406
- case RegexpCode:
496
+ case RegexpCode: {
497
+ VALUE re;
498
+
407
499
  if ('/' == *text) {
408
- h->obj = parse_regexp(text);
500
+ re = parse_regexp(text, pi);
409
501
  } else {
410
502
  unsigned long str_size = b64_orig_size(text);
411
- char *str = ALLOCA_N(char, str_size + 1);
503
+ VALUE v = rb_str_new(0, (long)str_size);
504
+ char *str = RSTRING_PTR(v);
412
505
 
413
- from_base64(text, (uchar *)str);
414
- h->obj = parse_regexp(str);
506
+ from_base64(text, (uchar *)str, str_size + 1);
507
+ re = parse_regexp(str, pi);
508
+ RB_GC_GUARD(v);
509
+ }
510
+ if (Qundef == re) {
511
+ return;
415
512
  }
513
+ h->obj = re;
416
514
  break;
515
+ }
417
516
  case BignumCode: h->obj = rb_cstr_to_inum(text, 10, 1); break;
418
517
  case BigDecimalCode: h->obj = rb_funcall(rb_cObject, ox_bigdecimal_id, 1, rb_str_new2(text)); break;
419
- default: h->obj = Qnil; break;
518
+ default: {
519
+ // The rest are containers add_element() built. Replacing one with Qnil
520
+ // left end_element() reading the nil back as an Array or a Struct.
521
+ // skip_off delivers the indentation between children here.
522
+ size_t i;
523
+
524
+ for (i = 0; i < len; i++) {
525
+ if (' ' != text[i] && '\t' != text[i] && '\n' != text[i] && '\r' != text[i]) {
526
+ set_error(&pi->err, "Unexpected text", pi->str, pi->s);
527
+ return;
528
+ }
529
+ }
530
+ break;
531
+ }
420
532
  }
421
533
  }
422
534
 
@@ -424,6 +536,8 @@ static void add_element(PInfo pi, const char *ename, Attr attrs, int hasChildren
424
536
  Attr a;
425
537
  Helper h;
426
538
  unsigned long id;
539
+ bool index;
540
+ ID var;
427
541
 
428
542
  if (TRACE <= pi->options->trace) {
429
543
  char buf[1024];
@@ -454,7 +568,9 @@ static void add_element(PInfo pi, const char *ename, Attr attrs, int hasChildren
454
568
  set_error(&pi->err, "Invalid element name", pi->str, pi->s);
455
569
  return;
456
570
  }
457
- h = helper_stack_push(&pi->helpers, get_var_sym_from_attrs(attrs, (void *)pi->options->rb_enc), Qundef, *ename);
571
+ var = get_var_sym_from_attrs(attrs, (void *)pi->options->rb_enc, &index);
572
+ h = helper_stack_push(&pi->helpers, var, Qundef, *ename);
573
+ h->index = index;
458
574
  switch (h->type) {
459
575
  case NilClassCode: h->obj = Qnil; break;
460
576
  case TrueClassCode: h->obj = Qtrue; break;
@@ -527,7 +643,12 @@ static void add_element(PInfo pi, const char *ename, Attr attrs, int hasChildren
527
643
  }
528
644
  break;
529
645
  case StructCode:
530
- h->obj = get_struct_from_attrs(attrs);
646
+ // Returns Qundef with no c attribute and, unlike the helpers above, does
647
+ // not set the error itself. The Qundef reached rb_struct_aset().
648
+ if (Qundef == (h->obj = get_struct_from_attrs(attrs))) {
649
+ set_error(&pi->err, "Invalid element for object mode", pi->str, pi->s);
650
+ return;
651
+ }
531
652
  if (0 != pi->circ_array) {
532
653
  circ_array_set(pi->circ_array, h->obj, get_id_from_attrs(pi, attrs));
533
654
  }
@@ -594,7 +715,9 @@ static void end_element(PInfo pi, const char *ename) {
594
715
  case ExceptionCode:
595
716
  case ObjectCode:
596
717
  if (Qnil != ph->obj) {
597
- if (0 == h->var || NULL == rb_id2name(h->var)) {
718
+ // An index is a valid ID too, so a numeric a attribute set an
719
+ // instance variable named whatever was interned in that slot.
720
+ if (0 == h->var || h->index || NULL == rb_id2name(h->var)) {
598
721
  set_error(&pi->err, "Invalid element for object mode", pi->str, pi->s);
599
722
  return;
600
723
  }
@@ -606,7 +729,9 @@ static void end_element(PInfo pi, const char *ename) {
606
729
  }
607
730
  break;
608
731
  case StructCode:
609
- if (0 == h->var) {
732
+ // An ID landed in rb_struct_aset() as a Fixnum, so a name picked
733
+ // whichever member that number happened to be.
734
+ if (0 == h->var || !h->index) {
610
735
  set_error(&pi->err, "Invalid element for object mode", pi->str, pi->s);
611
736
  return;
612
737
  }
@@ -614,9 +739,14 @@ static void end_element(PInfo pi, const char *ename) {
614
739
  break;
615
740
  case HashCode:
616
741
  // put back h
617
- helper_stack_push(&pi->helpers, h->var, h->obj, KeyCode);
742
+ helper_stack_push(&pi->helpers, h->var, h->obj, KeyCode)->index = h->index;
618
743
  break;
619
744
  case RangeCode:
745
+ // An index can equal one of these IDs, so turn it away first.
746
+ if (h->index) {
747
+ set_error(&pi->err, "Invalid range attribute", pi->str, pi->s);
748
+ return;
749
+ }
620
750
  if (ox_beg_id == h->var) {
621
751
  rb_ary_store(ph->obj, 0, h->obj);
622
752
  } else if (ox_end_id == h->var) {
@@ -673,7 +803,7 @@ static void end_element(PInfo pi, const char *ename) {
673
803
  }
674
804
  }
675
805
  if (0 != pi->circ_array && helper_stack_empty(&pi->helpers)) {
676
- circ_array_free(pi->circ_array);
806
+ ox_circ_array_free(pi->circ_array);
677
807
  pi->circ_array = 0;
678
808
  }
679
809
  if (DEBUG <= pi->options->trace) {
@@ -682,17 +812,23 @@ static void end_element(PInfo pi, const char *ename) {
682
812
  }
683
813
 
684
814
  static VALUE parse_double_time(const char *text, VALUE clas) {
685
- long v = 0;
815
+ time_t v = 0;
686
816
  long v2 = 0;
687
817
  const char *dot = 0;
688
818
  char c;
819
+ bool neg = false;
689
820
 
821
+ // A time before the epoch is written as a sign followed by the magnitude.
822
+ if ('-' == *text) {
823
+ neg = true;
824
+ text++;
825
+ }
690
826
  for (; '.' != *text; text++) {
691
827
  c = *text;
692
828
  if (c < '0' || '9' < c) {
693
829
  return Qnil;
694
830
  }
695
- v = 10 * v + (long)(c - '0');
831
+ v = 10 * v + (time_t)(c - '0');
696
832
  }
697
833
  dot = text++;
698
834
  for (; '\0' != *text && text - dot <= 6; text++) {
@@ -705,6 +841,17 @@ static VALUE parse_double_time(const char *text, VALUE clas) {
705
841
  for (; text - dot <= 9; text++) {
706
842
  v2 *= 10;
707
843
  }
844
+ if (neg) {
845
+ // rb_time_nano_new() wants a non negative nanosecond count, so the
846
+ // fraction goes back into the second count the way dump_time_thin()
847
+ // took it out.
848
+ if (0 == v2) {
849
+ v = -v;
850
+ } else {
851
+ v = -v - 1;
852
+ v2 = 1000000000L - v2;
853
+ }
854
+ }
708
855
  return rb_time_nano_new(v, v2);
709
856
  }
710
857
 
@@ -731,7 +878,9 @@ static VALUE parse_xsd_time(const char *text, VALUE clas) {
731
878
  {2, '\0', '\0'},
732
879
  {0, '\0', '\0'}};
733
880
  Tp tp = tpa;
734
- struct tm tm;
881
+ long nsec = 0;
882
+ long offset;
883
+ bool neg = false;
735
884
 
736
885
  for (; 0 != tp->cnt; tp++) {
737
886
  for (i = tp->cnt, v = 0; 0 < i; text++, i--) {
@@ -744,19 +893,30 @@ static VALUE parse_xsd_time(const char *text, VALUE clas) {
744
893
  }
745
894
  v = 10 * v + (long)(c - '0');
746
895
  }
896
+ // The fraction is the only field terminated by the offset sign, and the
897
+ // sign is the only place the offset's direction is written down.
898
+ if ('+' == tp->end) {
899
+ nsec = v;
900
+ for (; 0 < i; i--) {
901
+ nsec *= 10;
902
+ }
903
+ neg = ('-' == *text);
904
+ }
747
905
  c = *text++;
748
906
  if (tp->end != c && tp->alt != c) {
749
907
  return Qnil;
750
908
  }
751
909
  *cp++ = v;
752
910
  }
753
- tm.tm_year = (int)cargs[0] - 1900;
754
- tm.tm_mon = (int)cargs[1] - 1;
755
- tm.tm_mday = (int)cargs[2];
756
- tm.tm_hour = (int)cargs[3];
757
- tm.tm_min = (int)cargs[4];
758
- tm.tm_sec = (int)cargs[5];
759
- return rb_time_nano_new(mktime(&tm), cargs[6]);
911
+ offset = cargs[7] * 3600 + cargs[8] * 60;
912
+ if (neg) {
913
+ offset = -offset;
914
+ }
915
+ // mktime() would read the wall clock as local time and throw the offset
916
+ // away, and returns -1 for everything before the epoch on Windows.
917
+ return rb_time_nano_new(
918
+ (time_t)(ox_epoch_from_civil(cargs[0], cargs[1], cargs[2], cargs[3], cargs[4], cargs[5]) - offset),
919
+ nsec);
760
920
  }
761
921
 
762
922
  // debug functions
@@ -791,7 +951,7 @@ static void debug_stack(PInfo pi, const char *comment) {
791
951
  clas = rb_class2name(c);
792
952
  }
793
953
  if (0 != h->var) {
794
- if (HashCode == h->type) {
954
+ if (h->index) {
795
955
  VALUE v;
796
956
 
797
957
  v = rb_String(h->var);