ox 2.14.27 → 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/parse.c CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  #include <errno.h>
7
7
  #include <stdbool.h>
8
+ #include <stdint.h>
8
9
  #include <stdio.h>
9
10
  #include <stdlib.h>
10
11
  #include <string.h>
@@ -137,115 +138,155 @@ static void mark_pi_cb(void *ptr) {
137
138
  }
138
139
  }
139
140
 
140
- VALUE
141
- ox_parse(char *xml, size_t len, ParseCallbacks pcb, char **endp, Options options, Err err) {
142
- struct _pInfo pi;
143
- int body_read = 0;
144
- int block_given = rb_block_given_p();
145
- volatile VALUE wrap;
141
+ // State shared between the parse body and its ensure block. Bundled so the
142
+ // whole parse can run under rb_ensure and clean up even when a callback raises.
143
+ typedef struct _parseCtx {
144
+ PInfo pi;
145
+ char **endp;
146
+ Err err;
147
+ VALUE wrap;
148
+ int block_given;
149
+ } *ParseCtx;
150
+
151
+ static VALUE ox_parse_body(VALUE ctxv) {
152
+ ParseCtx ctx = (ParseCtx)ctxv;
153
+ PInfo pi = ctx->pi;
154
+ char **endp = ctx->endp;
155
+ Err err = ctx->err;
156
+ int block_given = ctx->block_given;
157
+ int body_read = 0;
146
158
 
147
- if (0 == xml) {
148
- set_error(err, "Invalid arg, xml string can not be null", xml, 0);
149
- return Qnil;
150
- }
151
- if (DEBUG <= options->trace) {
152
- printf("Parsing xml:\n%s\n", xml);
153
- }
154
- // initialize parse info
155
- helper_stack_init(&pi.helpers);
156
- // Protect against GC
157
- wrap = TypedData_Wrap_Struct(rb_cObject, &ox_wrap_type, &pi);
158
-
159
- err_init(&pi.err);
160
- pi.str = xml;
161
- pi.end = pi.str + len;
162
- pi.s = xml;
163
- pi.pcb = pcb;
164
- pi.obj = Qnil;
165
- pi.circ_array = 0;
166
- pi.options = options;
167
- pi.marked = NULL;
168
- pi.mark_size = 0;
169
- pi.mark_cnt = 0;
170
159
  while (1) {
171
- next_non_white(&pi); // skip white space
172
- if ('\0' == *pi.s) {
160
+ next_non_white(pi); // skip white space
161
+ if ('\0' == *pi->s) {
173
162
  break;
174
163
  }
175
164
  if (body_read && 0 != endp) {
176
- *endp = pi.s;
165
+ *endp = pi->s;
177
166
  break;
178
167
  }
179
- if ('<' != *pi.s) { // all top level entities start with <
180
- set_error(err, "invalid format, expected <", pi.str, pi.s);
181
- helper_stack_cleanup(&pi.helpers);
168
+ if ('<' != *pi->s) { // all top level entities start with <
169
+ set_error(err, "invalid format, expected <", pi->str, pi->s);
182
170
  return Qnil;
183
171
  }
184
- pi.s++; // past <
185
- switch (*pi.s) {
172
+ pi->s++; // past <
173
+ switch (*pi->s) {
186
174
  case '?': // processing instruction
187
- pi.s++;
188
- read_instruction(&pi);
175
+ pi->s++;
176
+ read_instruction(pi);
189
177
  break;
190
178
  case '!': // comment or doctype
191
- pi.s++;
192
- if ('\0' == *pi.s) {
193
- set_error(err, "invalid format, DOCTYPE or comment not terminated", pi.str, pi.s);
194
- helper_stack_cleanup(&pi.helpers);
179
+ pi->s++;
180
+ if ('\0' == *pi->s) {
181
+ set_error(err, "invalid format, DOCTYPE or comment not terminated", pi->str, pi->s);
195
182
  return Qnil;
196
- } else if ('-' == *pi.s) {
197
- pi.s++; // skip -
198
- if ('-' != *pi.s) {
199
- set_error(err, "invalid format, bad comment format", pi.str, pi.s);
200
- helper_stack_cleanup(&pi.helpers);
183
+ } else if ('-' == *pi->s) {
184
+ pi->s++; // skip -
185
+ if ('-' != *pi->s) {
186
+ set_error(err, "invalid format, bad comment format", pi->str, pi->s);
201
187
  return Qnil;
202
188
  } else {
203
- pi.s++; // skip second -
204
- read_comment(&pi);
189
+ pi->s++; // skip second -
190
+ read_comment(pi);
205
191
  }
206
- } else if ((TolerantEffort == options->effort) ? 0 == strncasecmp("DOCTYPE", pi.s, 7)
207
- : 0 == strncmp("DOCTYPE", pi.s, 7)) {
208
- pi.s += 7;
209
- read_doctype(&pi);
192
+ } else if ((TolerantEffort == pi->options->effort) ? 0 == strncasecmp("DOCTYPE", pi->s, 7)
193
+ : 0 == strncmp("DOCTYPE", pi->s, 7)) {
194
+ pi->s += 7;
195
+ read_doctype(pi);
210
196
  } else {
211
- set_error(err, "invalid format, DOCTYPE or comment expected", pi.str, pi.s);
212
- helper_stack_cleanup(&pi.helpers);
197
+ set_error(err, "invalid format, DOCTYPE or comment expected", pi->str, pi->s);
213
198
  return Qnil;
214
199
  }
215
200
  break;
216
- case '\0':
217
- set_error(err, "invalid format, document not terminated", pi.str, pi.s);
218
- helper_stack_cleanup(&pi.helpers);
219
- return Qnil;
201
+ case '\0': set_error(err, "invalid format, document not terminated", pi->str, pi->s); return Qnil;
220
202
  default:
221
- read_element(&pi, 0);
203
+ read_element(pi, 0);
222
204
  body_read = 1;
223
205
  break;
224
206
  }
225
- if (err_has(&pi.err)) {
226
- *err = pi.err;
227
- helper_stack_cleanup(&pi.helpers);
207
+ if (err_has(&pi->err)) {
208
+ *err = pi->err;
228
209
  return Qnil;
229
210
  }
230
- if (block_given && Qnil != pi.obj && Qundef != pi.obj) {
231
- if (NULL != pcb->finish) {
232
- pcb->finish(&pi);
211
+ if (block_given && Qnil != pi->obj && Qundef != pi->obj) {
212
+ if (NULL != pi->pcb->finish) {
213
+ pi->pcb->finish(pi);
233
214
  }
234
- rb_yield(pi.obj);
215
+ rb_yield(pi->obj);
235
216
  }
236
217
  }
237
- DATA_PTR(wrap) = NULL;
238
- helper_stack_cleanup(&pi.helpers);
239
- if (NULL != pcb->finish) {
240
- pcb->finish(&pi);
218
+ if (NULL != pi->pcb->finish) {
219
+ pi->pcb->finish(pi);
220
+ }
221
+ return pi->obj;
222
+ }
223
+
224
+ static VALUE ox_parse_ensure(VALUE ctxv) {
225
+ ParseCtx ctx = (ParseCtx)ctxv;
226
+
227
+ // pi is a stack value wrapped for GC marking. Detach the wrapper before
228
+ // this frame is gone so a later conservative GC can not mark a dead helper
229
+ // stack, and free the helper stack whether the parse returned normally or a
230
+ // callback raised out of it.
231
+ if (Qnil != ctx->wrap) {
232
+ DATA_PTR(ctx->wrap) = NULL;
233
+ }
234
+ helper_stack_cleanup(&ctx->pi->helpers);
235
+ // end_element() only frees the circular reference table on a complete
236
+ // parse, so release it here for an error or a callback raising out.
237
+ if (0 != ctx->pi->circ_array) {
238
+ ox_circ_array_free(ctx->pi->circ_array);
239
+ ctx->pi->circ_array = 0;
240
+ }
241
+ // Same for hash mode's mark list, freed by finish() only if the loop ends.
242
+ xfree(ctx->pi->marked);
243
+ ctx->pi->marked = NULL;
244
+ ctx->pi->mark_size = 0;
245
+ ctx->pi->mark_cnt = 0;
246
+ return Qnil;
247
+ }
248
+
249
+ VALUE
250
+ ox_parse(char *xml, size_t len, ParseCallbacks pcb, char **endp, Options options, Err err) {
251
+ struct _pInfo pi;
252
+ struct _parseCtx ctx;
253
+
254
+ if (0 == xml) {
255
+ set_error(err, "Invalid arg, xml string can not be null", xml, 0);
256
+ return Qnil;
257
+ }
258
+ if (DEBUG <= options->trace) {
259
+ printf("Parsing xml:\n%s\n", xml);
241
260
  }
242
- return pi.obj;
261
+ // initialize parse info
262
+ helper_stack_init(&pi.helpers);
263
+ err_init(&pi.err);
264
+ pi.str = xml;
265
+ pi.end = pi.str + len;
266
+ pi.s = xml;
267
+ pi.pcb = pcb;
268
+ pi.obj = Qnil;
269
+ pi.circ_array = 0;
270
+ pi.options = options;
271
+ pi.marked = NULL;
272
+ pi.mark_size = 0;
273
+ pi.mark_cnt = 0;
274
+
275
+ ctx.pi = &pi;
276
+ ctx.endp = endp;
277
+ ctx.err = err;
278
+ ctx.block_given = rb_block_given_p();
279
+ // Protect against GC. The wrapper marks the helper stack while parsing;
280
+ // ox_parse_ensure detaches it on every exit, including a callback raise.
281
+ ctx.wrap = TypedData_Wrap_Struct(rb_cObject, &ox_wrap_type, &pi);
282
+
283
+ return rb_ensure(ox_parse_body, (VALUE)&ctx, ox_parse_ensure, (VALUE)&ctx);
243
284
  }
244
285
 
245
286
  // Entered after the "<?" sequence. Ready to read the rest.
246
287
  static void read_instruction(PInfo pi) {
247
288
  char content[256];
248
- char *content_ptr;
289
+ char *content_ptr = content;
249
290
  struct _attrStack attrs;
250
291
  char *attr_name;
251
292
  char *attr_value;
@@ -259,7 +300,7 @@ static void read_instruction(PInfo pi) {
259
300
  *content = '\0';
260
301
  attr_stack_init(&attrs);
261
302
  if (0 == (target = read_name_token(pi))) {
262
- return;
303
+ goto CLEANUP;
263
304
  }
264
305
  end = pi->s;
265
306
  for (; true; pi->s++) {
@@ -270,7 +311,7 @@ static void read_instruction(PInfo pi) {
270
311
  goto DONE;
271
312
  }
272
313
  break;
273
- case '\0': set_error(&pi->err, "processing instruction not terminated", pi->str, pi->s); return;
314
+ case '\0': set_error(&pi->err, "processing instruction not terminated", pi->str, pi->s); goto CLEANUP;
274
315
  default: break;
275
316
  }
276
317
  }
@@ -293,21 +334,18 @@ DONE:
293
334
  while ('?' != c) {
294
335
  pi->last = 0;
295
336
  if ('\0' == *pi->s) {
296
- attr_stack_cleanup(&attrs);
297
337
  set_error(&pi->err, "invalid format, processing instruction not terminated", pi->str, pi->s);
298
- return;
338
+ goto CLEANUP;
299
339
  }
300
340
  next_non_white(pi);
301
341
  if (0 == (attr_name = read_name_token(pi))) {
302
- attr_stack_cleanup(&attrs);
303
- return;
342
+ goto CLEANUP;
304
343
  }
305
344
  end = pi->s;
306
345
  next_non_white(pi);
307
346
  if ('\0' == *pi->s) {
308
- attr_stack_cleanup(&attrs);
309
347
  set_error(&pi->err, "invalid format, processing instruction not terminated", pi->str, pi->s);
310
- return;
348
+ goto CLEANUP;
311
349
  }
312
350
  if ('=' != *pi->s++) {
313
351
  attrs_ok = false;
@@ -317,8 +355,7 @@ DONE:
317
355
  // read value
318
356
  next_non_white(pi);
319
357
  if (0 == (attr_value = read_quoted_value(pi))) {
320
- attr_stack_cleanup(&attrs);
321
- return;
358
+ goto CLEANUP;
322
359
  }
323
360
  attr_stack_push(&attrs, attr_name, attr_value);
324
361
  next_non_white(pi);
@@ -336,9 +373,8 @@ DONE:
336
373
  }
337
374
  if (attrs_ok) {
338
375
  if ('>' != *pi->s++) {
339
- attr_stack_cleanup(&attrs);
340
376
  set_error(&pi->err, "invalid format, processing instruction not terminated", pi->str, pi->s);
341
- return;
377
+ goto CLEANUP;
342
378
  }
343
379
  } else {
344
380
  pi->s = cend + 1;
@@ -359,6 +395,9 @@ DONE:
359
395
  }
360
396
  }
361
397
  }
398
+ // Single exit, so no error return can leak the heap content buffer or the
399
+ // heap-grown attribute stack.
400
+ CLEANUP:
362
401
  attr_stack_cleanup(&attrs);
363
402
  if (content_ptr != content) {
364
403
  xfree(content_ptr);
@@ -382,6 +421,11 @@ static void read_delimited(PInfo pi, char end) {
382
421
  if (end == c) {
383
422
  return;
384
423
  }
424
+ if (MAX_PROLOG < (pi->s - pi->str)) {
425
+ pi->s--;
426
+ set_error(&pi->err, "prolog (doctype) too long", pi->str, pi->s);
427
+ return;
428
+ }
385
429
  switch (c) {
386
430
  case '\0':
387
431
  pi->s--;
@@ -559,6 +603,7 @@ static char *read_element(PInfo pi, int depth) {
559
603
  /* read value */
560
604
  next_non_white(pi);
561
605
  if (0 == (attr_value = read_quoted_value(pi))) {
606
+ attr_stack_cleanup(&attrs);
562
607
  return 0;
563
608
  }
564
609
  if (pi->options->convert_special && 0 != strchr(attr_value, '&')) {
@@ -589,7 +634,7 @@ static char *read_element(PInfo pi, int depth) {
589
634
  if (OffSkip == pi->options->skip && start < pi->s && '<' == *pi->s) {
590
635
  c = *pi->s;
591
636
  *pi->s = '\0';
592
- pi->pcb->add_text(pi, start, 1);
637
+ pi->pcb->add_text(pi, start, (size_t)(pi->s - start), 1);
593
638
  *pi->s = c;
594
639
  }
595
640
  c = *pi->s++;
@@ -671,7 +716,9 @@ static char *read_element(PInfo pi, int depth) {
671
716
  default: break;
672
717
  }
673
718
  if ('\0' != *start) {
674
- pi->pcb->add_text(pi, start, 1);
719
+ // The skip handling above moves the terminator, so
720
+ // the length is whatever survived it.
721
+ pi->pcb->add_text(pi, start, strlen(start), 1);
675
722
  }
676
723
  }
677
724
  pi->s++;
@@ -703,6 +750,7 @@ static char *read_element(PInfo pi, int depth) {
703
750
  return name;
704
751
  }
705
752
  } else if (err_has(&pi->err)) {
753
+ attr_stack_cleanup(&attrs);
706
754
  return 0;
707
755
  }
708
756
  break;
@@ -740,15 +788,109 @@ static char *read_element(PInfo pi, int depth) {
740
788
  return 0;
741
789
  }
742
790
 
791
+ #define TEXT_ONES 0x0101010101010101ULL
792
+ #define TEXT_HIGH 0x8080808080808080ULL
793
+
794
+ /* Sets the high bit of every byte of a word that read_text can not copy
795
+ * through as is, that is a byte less than or equal to 0x20, a '&', or a '<'.
796
+ * Bytes with the high bit set are never flagged because ~v clears them, which
797
+ * matches the byte loop where a signed char with the high bit set is negative
798
+ * and so falls to the plain copy.
799
+ *
800
+ * A borrow out of one byte can also flag the byte above it, but a borrow is
801
+ * only produced by a byte that matched, so the lowest flagged byte is always a
802
+ * real match and there are never false negatives.
803
+ */
804
+ inline static uint64_t text_bytes_of_interest(uint64_t v) {
805
+ uint64_t a = v ^ (TEXT_ONES * (uint64_t)'&');
806
+ uint64_t l = v ^ (TEXT_ONES * (uint64_t)'<');
807
+
808
+ return (((v - TEXT_ONES * 0x21) & ~v) | ((a - TEXT_ONES) & ~a) | ((l - TEXT_ONES) & ~l)) & TEXT_HIGH;
809
+ }
810
+
811
+ /* Offset of the lowest flagged byte, which is the first byte of the word that
812
+ * has to go through the switch below. Only ever called with a non-zero mask.
813
+ */
814
+ inline static int text_first_of_interest(const char *s, uint64_t mask) {
815
+ #if defined(__GNUC__) || defined(__clang__)
816
+ #if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
817
+ (void)s;
818
+ return (int)(__builtin_clzll(mask) >> 3);
819
+ #else
820
+ (void)s;
821
+ return (int)(__builtin_ctzll(mask) >> 3);
822
+ #endif
823
+ #else
824
+ int i;
825
+
826
+ (void)mask;
827
+ for (i = 0; i < 8; i++) {
828
+ unsigned char u = (unsigned char)s[i];
829
+
830
+ if (u <= 0x20 || '&' == u || '<' == u) {
831
+ break;
832
+ }
833
+ }
834
+ return i;
835
+ #endif
836
+ }
837
+
743
838
  static void read_text(PInfo pi) {
744
- char buf[MAX_TEXT_LEN];
745
- char *b = buf;
746
- char *alloc_buf = 0;
747
- char *end = b + sizeof(buf) - 2;
748
- char c;
749
- int done = 0;
839
+ char buf[MAX_TEXT_LEN];
840
+ char *b = buf;
841
+ char *alloc_buf = 0;
842
+ char *end = b + sizeof(buf) - 2;
843
+ char *text;
844
+ size_t len;
845
+ char c;
846
+ int done = 0;
847
+ /* fix_newlines only rewrites '\r'. SpcSkip folds every '\r' to a space, so
848
+ * the only way one reaches buf under it is an entity like &#13;. Any other
849
+ * skip mode can keep a '\r', so assume one might be present there and run
850
+ * fix_newlines as before. SpcSkip text with no such entity, the common
851
+ * case, then skips the fix_newlines scan and second pass entirely.
852
+ */
853
+ int maybe_cr = (SpcSkip != pi->options->skip);
750
854
 
751
855
  while (!done) {
856
+ /* A byte above 0x20 that is neither '&' nor '<' is copied through
857
+ * unchanged for every skip mode and every effort, so runs of them can
858
+ * be moved a word at a time instead of one switch dispatch per byte.
859
+ * pi->end addresses the terminating '\0' so the loads never leave the
860
+ * document, which keeps the loop clean under ASan.
861
+ */
862
+ while (pi->s + 8 <= pi->end && b + 8 <= end) {
863
+ uint64_t v;
864
+ uint64_t mask;
865
+
866
+ memcpy(&v, pi->s, 8);
867
+ mask = text_bytes_of_interest(v);
868
+ /* b + 8 <= end so the whole word can be stored even when only part
869
+ * of it is kept. The bytes past the kept prefix are overwritten by
870
+ * the next copy or cut off by the terminating '\0'.
871
+ */
872
+ memcpy(b, pi->s, 8);
873
+ if (0 != mask) {
874
+ int n = text_first_of_interest(pi->s, mask);
875
+
876
+ b += n;
877
+ pi->s += n;
878
+ break;
879
+ }
880
+ b += 8;
881
+ pi->s += 8;
882
+ }
883
+ /* Only reached when a bound stopped the word loop, or for the byte the
884
+ * word loop stopped on.
885
+ */
886
+ while (pi->s < pi->end && b < end) {
887
+ unsigned char u = (unsigned char)*pi->s;
888
+
889
+ if (u <= 0x20 || '&' == u || '<' == u) {
890
+ break;
891
+ }
892
+ *b++ = *pi->s++;
893
+ }
752
894
  c = *pi->s++;
753
895
  switch (c) {
754
896
  case '<':
@@ -758,7 +900,7 @@ static void read_text(PInfo pi) {
758
900
  case '\0':
759
901
  pi->s--;
760
902
  set_error(&pi->err, "invalid format, document not terminated", pi->str, pi->s);
761
- return;
903
+ goto CLEANUP;
762
904
  default:
763
905
  if (end <= (b + (('&' == c) ? 7 : 0))) { /* extra 8 for special just in case it is sequence of bytes */
764
906
  unsigned long size;
@@ -779,13 +921,15 @@ static void read_text(PInfo pi) {
779
921
  }
780
922
  if ('&' == c) {
781
923
  if (0 == (b = read_coded_chars(pi, b))) {
782
- return;
924
+ goto CLEANUP;
783
925
  }
926
+ /* An entity such as &#13; can decode to '\r'; be conservative. */
927
+ maybe_cr = 1;
784
928
  } else {
785
929
  if (0 <= c && c <= 0x20) {
786
930
  if (StrictEffort == pi->options->effort && 'x' == xml_valid_lower_chars[(unsigned char)c]) {
787
931
  set_error(&pi->err, "invalid character", pi->str, pi->s);
788
- return;
932
+ goto CLEANUP;
789
933
  }
790
934
  switch (pi->options->skip) {
791
935
  case CrSkip:
@@ -815,14 +959,21 @@ static void read_text(PInfo pi) {
815
959
  break;
816
960
  }
817
961
  }
818
- *b = '\0';
962
+ *b = '\0';
963
+ text = (0 != alloc_buf) ? alloc_buf : buf;
964
+ len = (size_t)(b - text);
965
+ if (maybe_cr) {
966
+ // fix_newlines can fold "\r\n" to "\n" and shorten the text, so the
967
+ // length is only known after it runs. When it was skipped the text is
968
+ // exactly b - text, so add_text avoids a strlen on the common path.
969
+ fix_newlines(text);
970
+ len = strlen(text);
971
+ }
972
+ pi->pcb->add_text(pi, text, len, ('/' == *(pi->s + 1)));
973
+ // Single exit, so no error return can leak the heap-grown text buffer.
974
+ CLEANUP:
819
975
  if (0 != alloc_buf) {
820
- fix_newlines(alloc_buf);
821
- pi->pcb->add_text(pi, alloc_buf, ('/' == *(pi->s + 1)));
822
976
  xfree(alloc_buf);
823
- } else {
824
- fix_newlines(buf);
825
- pi->pcb->add_text(pi, buf, ('/' == *(pi->s + 1)));
826
977
  }
827
978
  }
828
979
 
data/ext/ox/sax.c CHANGED
@@ -197,7 +197,7 @@ static void comment(SaxDrive dr, long pos, long line, long col) {
197
197
  Hint h = ox_hint_find(dr->options.hints, "!--");
198
198
 
199
199
  if (NULL == parent || NULL == parent->hint || OffOverlay != parent->hint->overlay ||
200
- (NULL != h && (ActiveOverlay == h->overlay || ActiveOverlay == h->overlay))) {
200
+ (NULL != h && (ActiveOverlay == h->overlay || NestOverlay == h->overlay))) {
201
201
  VALUE arg = rb_str_new2(dr->buf.str);
202
202
 
203
203
  if (0 != dr->encoding) {
@@ -581,7 +581,7 @@ static char read_instruction(SaxDrive dr) {
581
581
  return c;
582
582
  }
583
583
 
584
- static char read_delimited(SaxDrive dr, char end) {
584
+ static char read_delimited(SaxDrive dr, char end, int depth) {
585
585
  char c;
586
586
 
587
587
  if ('"' == end || '\'' == end) {
@@ -591,6 +591,9 @@ static char read_delimited(SaxDrive dr, char end) {
591
591
  return c;
592
592
  }
593
593
  }
594
+ } else if (MAX_PROLOG <= depth) {
595
+ rb_raise(ox_parse_error_class, "prolog (doctype) too long.\n");
596
+ return '\0';
594
597
  } else {
595
598
  while (1) {
596
599
  c = buf_get(&dr->buf);
@@ -599,10 +602,10 @@ static char read_delimited(SaxDrive dr, char end) {
599
602
  }
600
603
  switch (c) {
601
604
  case '\0': ox_sax_drive_error(dr, NO_TERM "doctype not terminated"); return c;
602
- case '"': c = read_delimited(dr, c); break;
603
- case '\'': c = read_delimited(dr, c); break;
604
- case '[': c = read_delimited(dr, ']'); break;
605
- case '<': c = read_delimited(dr, '>'); break;
605
+ case '"': c = read_delimited(dr, c, depth + 1); break;
606
+ case '\'': c = read_delimited(dr, c, depth + 1); break;
607
+ case '[': c = read_delimited(dr, ']', depth + 1); break;
608
+ case '<': c = read_delimited(dr, '>', depth + 1); break;
606
609
  default: break;
607
610
  }
608
611
  }
@@ -621,7 +624,7 @@ static char read_doctype(SaxDrive dr) {
621
624
 
622
625
  buf_backup(&dr->buf); /* back up to the start in case the doctype is empty */
623
626
  buf_protect(&dr->buf);
624
- read_delimited(dr, '>');
627
+ read_delimited(dr, '>', 0);
625
628
  if (dr->options.smart && 0 == dr->options.hints) {
626
629
  for (s = dr->buf.str; is_white(*s); s++) {
627
630
  }
@@ -876,16 +879,17 @@ static char read_element_start(SaxDrive dr) {
876
879
  }
877
880
  }
878
881
  }
879
- name = str2sym(dr, dr->buf.str, nlen, &ename);
880
- if (NULL == ename) {
881
- if (sizeof(ebuf) <= nlen) {
882
- ename = ox_strndup(dr->buf.str, nlen);
883
- efree = true;
884
- } else {
885
- memcpy(ebuf, dr->buf.str, nlen);
886
- ebuf[nlen] = '\0';
887
- ename = ebuf;
888
- }
882
+ name = str2sym(dr, dr->buf.str, nlen, NULL);
883
+ // ename lives until stack_push() copies it, across the start_element
884
+ // callback and read_attrs. The cache's key is inside a Slot, which a GC can
885
+ // retire and a later intern free, so copy it instead.
886
+ if (sizeof(ebuf) <= nlen) {
887
+ ename = ox_strndup(dr->buf.str, nlen);
888
+ efree = true;
889
+ } else {
890
+ memcpy(ebuf, dr->buf.str, nlen);
891
+ ebuf[nlen] = '\0';
892
+ ename = ebuf;
889
893
  }
890
894
  if (dr->has_start_element && 0 >= dr->blocked &&
891
895
  (NULL == h || ActiveOverlay == h->overlay || NestOverlay == h->overlay)) {
@@ -930,7 +934,7 @@ static char read_element_start(SaxDrive dr) {
930
934
  stack_push(&dr->stack, ename, nlen, name, h);
931
935
  }
932
936
  if (efree) {
933
- free((char *)ename);
937
+ xfree((char *)ename);
934
938
  }
935
939
  if ('>' != c) {
936
940
  ox_sax_drive_error(dr, WRONG_CHAR "element not closed");